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
package/schema/setup.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { spawnSync } from "node:child_process";
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
15
|
import { basename, dirname, join, resolve } from "node:path";
|
|
16
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
16
17
|
|
|
17
18
|
const repo = new URL("..", import.meta.url).pathname;
|
|
18
19
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -23,8 +24,8 @@ const GOAL = "discovery-goal";
|
|
|
23
24
|
export async function state(dir) {
|
|
24
25
|
const { decisions } = await import("./decisions.mjs");
|
|
25
26
|
const v = load(join(dir, "venture.json"));
|
|
26
|
-
const people = existsSync(join(dir, "people/people.json")) ? load(join(dir, "people/people.json")) : null;
|
|
27
|
-
const sources = existsSync(join(dir, "knowledge/sources.json")) ? load(join(dir, "knowledge/sources.json")) : { sources: [], records: {} };
|
|
27
|
+
const people = existsSync(join(ventureFile(dir, "people/people.json"))) ? load(join(ventureFile(dir, "people/people.json"))) : null;
|
|
28
|
+
const sources = existsSync(join(ventureFile(dir, "knowledge/sources.json"))) ? load(join(ventureFile(dir, "knowledge/sources.json"))) : { sources: [], records: {} };
|
|
28
29
|
const catalog = load(join(repo, "schema/systems-catalog.json"));
|
|
29
30
|
const types = load(join(repo, "schema/venture-types.json")).types;
|
|
30
31
|
const all = decisions(dir).decisions;
|
|
@@ -51,7 +52,7 @@ export async function state(dir) {
|
|
|
51
52
|
// An open decision whose wording an agent wrote: in setup, the founder's words the model relayed.
|
|
52
53
|
const relayed = (d) => d.status !== "decided" && !!d.answer && d.provenance?.drafted_by?.kind === "agent";
|
|
53
54
|
|
|
54
|
-
const founderName = (dir) => existsSync(join(dir, "people/people.json")) ? load(join(dir, "people/people.json")).people[0]?.name ?? "Founder" : "Founder";
|
|
55
|
+
const founderName = (dir) => existsSync(join(ventureFile(dir, "people/people.json"))) ? load(join(ventureFile(dir, "people/people.json"))).people[0]?.name ?? "Founder" : "Founder";
|
|
55
56
|
|
|
56
57
|
// One answer: a decision on file is approved; a foundation statement (or the goal) becomes a decision a person approves.
|
|
57
58
|
export async function decide(dir, { id, answer, via = "api", where = "/setup" }) {
|
|
@@ -79,7 +80,7 @@ export async function decide(dir, { id, answer, via = "api", where = "/setup" })
|
|
|
79
80
|
export async function point(dir, { fn, system, address, via = "api", actor = "person", agentId }) {
|
|
80
81
|
const catalog = load(join(repo, "schema/systems-catalog.json"));
|
|
81
82
|
if (!catalog.functions[fn]?.includes(system)) return { ok: false, error: `${system} is not offered for ${fn}` };
|
|
82
|
-
const file = join(dir, "knowledge/sources.json");
|
|
83
|
+
const file = join(ventureFile(dir, "knowledge/sources.json"));
|
|
83
84
|
const map = existsSync(file) ? load(file) : { sources: [], records: {}, truth: "local" };
|
|
84
85
|
const f = (map.functions ??= {})[fn] ??= { systems: [] };
|
|
85
86
|
const entry = { system, ...(address ? { address } : {}), status: "pointed" };
|
package/schema/stale.mjs
CHANGED
|
@@ -23,13 +23,14 @@ import { entries } from "../interface/provenance.mjs";
|
|
|
23
23
|
import { resolve as resolveRoute } from "../interface/router.mjs";
|
|
24
24
|
import { address } from "../knowledge/connectors/contract.mjs";
|
|
25
25
|
import { LOCAL, sourcesOf } from "../knowledge/connectors/index.mjs";
|
|
26
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
26
27
|
|
|
27
28
|
export const MARKS = ".naive/stale.json";
|
|
28
29
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
29
30
|
const now = () => new Date().toISOString();
|
|
30
31
|
const WRITES = new Set(["write", "accept", "baseline"]);
|
|
31
32
|
|
|
32
|
-
const readMarks = (dir) => (existsSync(
|
|
33
|
+
const readMarks = (dir) => (existsSync(ventureFile(dir, MARKS)) ? load(ventureFile(dir, MARKS)) : { marks: [] });
|
|
33
34
|
|
|
34
35
|
// The latest write of each file in the log.
|
|
35
36
|
function lastWrites(dir) {
|
|
@@ -52,14 +53,14 @@ export function notify(dir, { source, paths, version, at = now(), via = "framewo
|
|
|
52
53
|
if (!paths.length) throw new Error("a notification names at least one changed path");
|
|
53
54
|
// The changed items as the venture refers to them: the address always, and the bare path for its own folder
|
|
54
55
|
// or for a source whose records the venture keeps a local copy of at the same path.
|
|
55
|
-
const items = paths.flatMap((p) => [address(source, p), ...(source === LOCAL || existsSync(
|
|
56
|
+
const items = paths.flatMap((p) => [address(source, p), ...(source === LOCAL || existsSync(ventureFile(dir, p)) ? [p] : [])]);
|
|
56
57
|
const readers = new Map();
|
|
57
58
|
for (const [file, e] of lastWrites(dir)) {
|
|
58
59
|
if (items.includes(file)) continue;
|
|
59
60
|
const hit = (e.derived_from ?? []).find((d) => items.some((i) => covers(d, i)));
|
|
60
61
|
if (hit) readers.set(file, { via: "provenance", reads: hit });
|
|
61
62
|
}
|
|
62
|
-
const wu = join(dir, "knowledge/where-used.json");
|
|
63
|
+
const wu = join(ventureFile(dir, "knowledge/where-used.json"));
|
|
63
64
|
if (existsSync(wu)) {
|
|
64
65
|
for (const fact of load(wu).facts ?? []) {
|
|
65
66
|
const { r, params } = resolveRoute(fact.parent.route);
|
|
@@ -74,13 +75,13 @@ export function notify(dir, { source, paths, version, at = now(), via = "framewo
|
|
|
74
75
|
for (const [file, how] of readers) state.marks.push({ file, ...how, because, marked_at: now() });
|
|
75
76
|
state.notifications = [...(state.notifications ?? []), { ...because, marked: readers.size, received_at: now() }].slice(-100);
|
|
76
77
|
mkdirSync(join(dir, ".naive"), { recursive: true });
|
|
77
|
-
writeFileSync(
|
|
78
|
+
writeFileSync(ventureFile(dir, MARKS), JSON.stringify(state, null, 2) + "\n");
|
|
78
79
|
return { source, paths, stale: [...readers.keys()].sort() };
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/** Whether one file is stale: the marks made after its latest write, or null when it is fresh. */
|
|
82
83
|
export function staleOf(dir, file) {
|
|
83
|
-
if (!existsSync(
|
|
84
|
+
if (!existsSync(ventureFile(dir, MARKS))) return null;
|
|
84
85
|
const written = lastWrites(dir).get(file)?.at ?? "";
|
|
85
86
|
const live = readMarks(dir).marks.filter((m) => m.file === file && m.marked_at > written);
|
|
86
87
|
return live.length ? { since: live[0].marked_at, because: live.map((m) => ({ ...m.because, reads: m.reads, via: m.via })), fix: `write ${file} again from the changed source; it is fresh once written` } : null;
|
package/schema/structure.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
23
24
|
|
|
24
25
|
const repo = new URL("..", import.meta.url).pathname;
|
|
25
26
|
const load = (f) => JSON.parse(readFileSync(f, "utf8"));
|
|
@@ -74,7 +75,7 @@ export function routable(file, pattern) {
|
|
|
74
75
|
|
|
75
76
|
// Every entry under a folder, as venture-relative paths, stopping at nested ventures and dot folders other than reserved ones.
|
|
76
77
|
function walk(dir, sub = "") {
|
|
77
|
-
const d =
|
|
78
|
+
const d = ventureFile(dir, sub);
|
|
78
79
|
return readdirSync(d, { withFileTypes: true }).flatMap((e) => {
|
|
79
80
|
const rel = sub ? `${sub}/${e.name}` : e.name;
|
|
80
81
|
if (e.isDirectory()) {
|
|
@@ -105,7 +106,7 @@ export function structure(dir) {
|
|
|
105
106
|
if (!s.re.test(name)) continue;
|
|
106
107
|
if (s.name === "(group)") {
|
|
107
108
|
if (parent) errors.push(`${e.path}/: a route group folder sits at the venture root (P-36)`);
|
|
108
|
-
const inside = readdirSync(
|
|
109
|
+
const inside = readdirSync(ventureFile(dir, e.path)).filter((f) => f !== "AGENTS.md" && f !== ".gitkeep");
|
|
109
110
|
if (inside.length) errors.push(`${e.path}/: ${s.rule}; move ${inside.join(", ")} into its package`);
|
|
110
111
|
} else warnings.push(`${e.path}/: ${s.name} is ${s.rule} (P-32)`);
|
|
111
112
|
}
|
|
@@ -129,7 +130,7 @@ export async function manifest(dir) {
|
|
|
129
130
|
// The packages that fit the phase now, plus every package that already holds a record.
|
|
130
131
|
const { packages } = await import("./packages.mjs");
|
|
131
132
|
const p = packages(dir);
|
|
132
|
-
const holding = names.filter((n) => { const k = pkgOf(n); return (k.records ?? []).length && existsSync(
|
|
133
|
+
const holding = names.filter((n) => { const k = pkgOf(n); return (k.records ?? []).length && existsSync(ventureFile(dir, n)) && readdirSync(ventureFile(dir, n)).some((f) => /\.(json|md|mdx)$/.test(f)); });
|
|
133
134
|
derived = [...new Set([...p.core.filter((x) => x.in === "now").map((x) => x.id), ...holding])];
|
|
134
135
|
}
|
|
135
136
|
const used = v.packages ?? derived;
|
|
@@ -141,7 +142,7 @@ export async function manifest(dir) {
|
|
|
141
142
|
return {
|
|
142
143
|
identity: { id: v.id, name: v.name, kind: v.kind ?? "venture", parent: v.parent, owner: v.owner, status: v.status },
|
|
143
144
|
phase: v.phase,
|
|
144
|
-
governance: { governed_by: v.governed_by ?? null, legal_form: v.form?.legal_form ?? "none", members: v.members ?? [], decision_rights: existsSync(join(dir, "governance/decision-rights.json")) ? "governance/decision-rights.json" : "the framework's template" },
|
|
145
|
+
governance: { governed_by: v.governed_by ?? null, legal_form: v.form?.legal_form ?? "none", members: v.members ?? [], decision_rights: existsSync(join(ventureFile(dir, "governance/decision-rights.json"))) ? "governance/decision-rights.json" : "the framework's template" },
|
|
145
146
|
functions,
|
|
146
147
|
packages: { declared: !!v.packages, list: used.map((n) => ({ name: n, kind: names.includes(n) ? pkgOf(n).kind : null, known: names.includes(n) })) },
|
|
147
148
|
kits: (v.kits ?? []).map((k) => ({ ...k, note: "a kit is Mentura's; the framework checks only that it names a package the venture uses" })),
|
package/schema/time-to-ready.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { execFileSync } from "node:child_process";
|
|
|
15
15
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { needNow } from "./need-now.mjs";
|
|
18
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
18
19
|
|
|
19
20
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
20
21
|
const day = (d) => d ? d.slice(0, 10) : null;
|
|
@@ -43,19 +44,19 @@ export function timeToReady(dir) {
|
|
|
43
44
|
const rels = (model.records.find((r) => r.id === type)?.paths ?? []).filter((p) => p.startsWith("instance/**/")).map((p) => p.slice("instance/**/".length));
|
|
44
45
|
return here.filter((x) => rels.some((g) => glob(g).test(x)));
|
|
45
46
|
};
|
|
46
|
-
const seen = (type) => { const fs = where(type); return fs.length ? fs.map((f) => firstSeen(
|
|
47
|
+
const seen = (type) => { const fs = where(type); return fs.length ? fs.map((f) => firstSeen(ventureFile(dir, f))).sort()[0] : null; };
|
|
47
48
|
|
|
48
49
|
// Running.
|
|
49
50
|
const nn = needNow(dir);
|
|
50
|
-
const products = where("product").map((f) => ({ f, p: load(
|
|
51
|
+
const products = where("product").map((f) => ({ f, p: load(ventureFile(dir, f)) }));
|
|
51
52
|
const offered = products.filter(({ p }) => p.status === "live");
|
|
52
53
|
const telosFile = where("telos")[0];
|
|
53
|
-
const telos = telosFile ? load(
|
|
54
|
+
const telos = telosFile ? load(ventureFile(dir, telosFile)) : null;
|
|
54
55
|
const conditions = {
|
|
55
56
|
promise: v.core?.promise ? firstSeen(join(dir, "venture.json")) : null,
|
|
56
|
-
product_offered: offered.length ? offered.map(({ f }) => firstSeen(
|
|
57
|
+
product_offered: offered.length ? offered.map(({ f }) => firstSeen(ventureFile(dir, f))).sort()[0] : null,
|
|
57
58
|
first_account: seen("account"),
|
|
58
|
-
next_exit_criteria: nn.next && telos?.phases?.some((p) => p.phase === nn.next) ? firstSeen(
|
|
59
|
+
next_exit_criteria: nn.next && telos?.phases?.some((p) => p.phase === nn.next) ? firstSeen(ventureFile(dir, telosFile)) : null,
|
|
59
60
|
};
|
|
60
61
|
const missing = Object.entries(conditions).filter(([, d]) => !d).map(([k]) => k);
|
|
61
62
|
const runningOn = missing.length ? null : Object.values(conditions).sort().at(-1);
|
|
@@ -75,7 +76,7 @@ export function timeToReady(dir) {
|
|
|
75
76
|
|
|
76
77
|
// MAD tiers.
|
|
77
78
|
const opsFile = where("validation-operations-readiness")[0];
|
|
78
|
-
const tiers = opsFile ? load(
|
|
79
|
+
const tiers = opsFile ? load(ventureFile(dir, opsFile)).tiers.map((t) => ({ tier: t.tier, ready_on: t.ready_on ?? null, days: days(start, t.ready_on) })) : [];
|
|
79
80
|
|
|
80
81
|
return { venture: v.id, phase: nn.phase, started: start, running: { on: runningOn, days: days(start, runningOn), missing, conditions }, layers: layerRows, tiers };
|
|
81
82
|
}
|
package/schema/triggers.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { decisions, propose } from "./decisions.mjs";
|
|
|
26
26
|
import { kit } from "./kits.mjs";
|
|
27
27
|
import { CONFIG, readConfig, writeConfig } from "./naive-config.mjs";
|
|
28
28
|
import { packages } from "./packages.mjs";
|
|
29
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
29
30
|
|
|
30
31
|
export const STATE = ".naive/triggers.json";
|
|
31
32
|
export const ERRORS = ".naive/trigger-errors.jsonl";
|
|
@@ -63,19 +64,19 @@ export function matches(t, e) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
function readState(dir) {
|
|
66
|
-
const f =
|
|
67
|
+
const f = ventureFile(dir, STATE);
|
|
67
68
|
if (existsSync(f)) {
|
|
68
69
|
try { const s = load(f); if (Array.isArray(s.runs) && Array.isArray(s.waiting)) return s; } catch {}
|
|
69
70
|
// A state file that cannot be read is kept beside it, and the failure is raised; runs start again from now.
|
|
70
71
|
const kept = `.naive/triggers.unreadable-${Date.now()}.json`;
|
|
71
|
-
renameSync(f,
|
|
72
|
+
renameSync(f, ventureFile(dir, kept));
|
|
72
73
|
return { since: now(), cursor: null, runs: [], waiting: [], unreadable: kept };
|
|
73
74
|
}
|
|
74
75
|
// Triggers start with the events after they were configured, not the venture's whole history.
|
|
75
|
-
const cfg =
|
|
76
|
+
const cfg = ventureFile(dir, CONFIG);
|
|
76
77
|
return { since: existsSync(cfg) ? statSync(cfg).mtime.toISOString() : now(), cursor: null, runs: [], waiting: [] };
|
|
77
78
|
}
|
|
78
|
-
const saveState = (dir, s) => { mkdirSync(join(dir, ".naive"), { recursive: true }); writeFileSync(
|
|
79
|
+
const saveState = (dir, s) => { mkdirSync(join(dir, ".naive"), { recursive: true }); writeFileSync(ventureFile(dir, STATE), JSON.stringify(s, null, 2) + "\n"); };
|
|
79
80
|
|
|
80
81
|
const complete = (dir, name) => { try { return packages(dir).complete.includes(name); } catch { return false; } };
|
|
81
82
|
const brief = (x) => (x == null ? null : typeof x === "string" ? x.slice(0, 300) : JSON.stringify(x).slice(0, 300));
|
|
@@ -155,7 +156,7 @@ export async function fire(dir, { rounds = 20 } = {}) {
|
|
|
155
156
|
delete state.unreadable; state.runs.push(run); made.push(run); saveState(dir, state);
|
|
156
157
|
}
|
|
157
158
|
// A crash the provenance hook kept is a failure too.
|
|
158
|
-
const ef =
|
|
159
|
+
const ef = ventureFile(dir, ERRORS);
|
|
159
160
|
if (existsSync(ef)) {
|
|
160
161
|
for (const l of readFileSync(ef, "utf8").split("\n").filter(Boolean)) {
|
|
161
162
|
const x = JSON.parse(l);
|
|
@@ -211,7 +212,7 @@ export async function fire(dir, { rounds = 20 } = {}) {
|
|
|
211
212
|
export function report(dir) {
|
|
212
213
|
const { triggers, problems } = triggersOf(dir);
|
|
213
214
|
// Read only: a listing never moves an unreadable state file aside (the next run does, and raises it).
|
|
214
|
-
const s = (() => { try { return load(
|
|
215
|
+
const s = (() => { try { return load(ventureFile(dir, STATE)); } catch { return { runs: [], waiting: [] }; } })();
|
|
215
216
|
return { triggers: triggers.map((t) => ({ id: t.id, from: t.from, on: t.on, run: t.run.propose ? { propose: t.run.propose.id } : { route: t.run.route, action: t.run.action }, needs: t.needs ?? null, needs_complete: t.needs ? complete(dir, t.needs) : null, enabled: t.enabled, once: !!t.once })),
|
|
216
217
|
problems, runs: s.runs.slice(-10), waiting: s.waiting, open: s.runs.filter((r) => r.status === "raised").map((r) => ({ run: r.n, trigger: r.trigger, error: r.error, decision: r.decision, decide: `mentura decide ${r.decision} --venture ${load(join(dir, "venture.json")).id} --by <your name> --answer "Retry" (or "Skip this event", "Turn the trigger off")` })) };
|
|
217
218
|
}
|
package/schema/upgrades.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { agentFromEnv, record } from "../interface/provenance.mjs";
|
|
|
25
25
|
import { routes, glob } from "../interface/router.mjs";
|
|
26
26
|
import { CONFIG, pins, readConfig, writeConfig } from "./naive-config.mjs";
|
|
27
27
|
import { packages as packageFit } from "./packages.mjs";
|
|
28
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
28
29
|
|
|
29
30
|
export const repo = new URL("..", import.meta.url).pathname;
|
|
30
31
|
export const STAGE = ".naive/upgrade";
|
|
@@ -106,26 +107,26 @@ function context(dir, { dry, git, cfg, at, onConfig }) {
|
|
|
106
107
|
return {
|
|
107
108
|
at, config: cfg, moved, writes, escalated, notes, kept,
|
|
108
109
|
mv(a, b) {
|
|
109
|
-
if (!existsSync(
|
|
110
|
-
if (existsSync(
|
|
110
|
+
if (!existsSync(ventureFile(dir, a))) return;
|
|
111
|
+
if (existsSync(ventureFile(dir, b)) && !moved.some(([, t]) => t === b)) throw new Error(`${b} already exists, so ${a} cannot move there; move one of them yourself and upgrade again`);
|
|
111
112
|
if (!dry) {
|
|
112
|
-
mkdirSync(dirname(
|
|
113
|
+
mkdirSync(dirname(ventureFile(dir, b)), { recursive: true });
|
|
113
114
|
if (tracked(a)) execFileSync("git", ["mv", a, b], { cwd: dir, stdio: "pipe" });
|
|
114
|
-
else renameSync(
|
|
115
|
+
else renameSync(ventureFile(dir, a), ventureFile(dir, b));
|
|
115
116
|
}
|
|
116
117
|
moved.push([a, b]);
|
|
117
118
|
},
|
|
118
119
|
write(rel, text, { activity = "write", note, derived_from, log = true } = {}) {
|
|
119
|
-
if (!dry) { mkdirSync(dirname(
|
|
120
|
+
if (!dry) { mkdirSync(dirname(ventureFile(dir, rel)), { recursive: true }); writeFileSync(ventureFile(dir, rel), text); }
|
|
120
121
|
writes.push({ path: rel, activity, note, derived_from, log });
|
|
121
122
|
},
|
|
122
123
|
escalate: (msg) => escalated.push(msg),
|
|
123
124
|
note: (msg) => notes.push(msg),
|
|
124
125
|
configChanged: () => onConfig(),
|
|
125
|
-
removeEmpty: (rel) => { if (!dry) removeEmpty(
|
|
126
|
+
removeEmpty: (rel) => { if (!dry) removeEmpty(ventureFile(dir, rel)); },
|
|
126
127
|
keep(sub) {
|
|
127
|
-
if (existsSync(
|
|
128
|
-
if (!dry) { mkdirSync(
|
|
128
|
+
if (existsSync(ventureFile(dir, sub))) return;
|
|
129
|
+
if (!dry) { mkdirSync(ventureFile(dir, sub), { recursive: true }); writeFileSync(join(ventureFile(dir, sub), ".gitkeep"), ""); }
|
|
129
130
|
kept.push(sub);
|
|
130
131
|
},
|
|
131
132
|
};
|
|
@@ -177,8 +178,8 @@ export async function upgrade(dir, { reg = registry(), dry = false, git = true }
|
|
|
177
178
|
if (!dry && (p.pending || configChanged)) {
|
|
178
179
|
if (configChanged) { writeConfig(dir, cfg); record(dir, { entity: CONFIG, activity: "write", agent, note: `pinned Naive ${p.to} and ${Object.keys(cfg.packages).length} packages (U-1)` }); }
|
|
179
180
|
const rel = `.release/upgrade-${p.to}.json`;
|
|
180
|
-
mkdirSync(join(dir, ".release"), { recursive: true });
|
|
181
|
-
writeFileSync(
|
|
181
|
+
mkdirSync(join(ventureFile(dir, ".release")), { recursive: true });
|
|
182
|
+
writeFileSync(ventureFile(dir, rel), JSON.stringify(release, null, 2) + "\n");
|
|
182
183
|
record(dir, { entity: rel, activity: "release", agent, derived_from: [...new Set([...ran.flatMap((r) => [...r.moves.map((m) => m.to), ...r.rewrote]), ...(configChanged ? [CONFIG] : [])])], note: `upgraded from Naive ${p.from} to ${p.to}` });
|
|
183
184
|
// Adapters installed in the venture (Z-4) are refreshed, so their copies of package skills follow the upgrade.
|
|
184
185
|
release.adapters = await refreshAdapters(dir);
|
|
@@ -264,18 +265,18 @@ export function updateFramework(root = repo) {
|
|
|
264
265
|
export function snapshot(dir, { skipPrivate = true } = {}) {
|
|
265
266
|
const out = {};
|
|
266
267
|
const walk = (rel) => {
|
|
267
|
-
for (const e of readdirSync(
|
|
268
|
+
for (const e of readdirSync(ventureFile(dir, rel), { withFileTypes: true })) {
|
|
268
269
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
269
270
|
if (!rel && (r === ".naive" || r === ".git" || (skipPrivate && r === "_private"))) continue;
|
|
270
271
|
if (e.isDirectory()) { if (!existsSync(join(dir, r, "venture.json"))) walk(r); }
|
|
271
|
-
else if (e.isFile()) out[r] = sha(readFileSync(
|
|
272
|
+
else if (e.isFile()) out[r] = sha(readFileSync(ventureFile(dir, r)));
|
|
272
273
|
}
|
|
273
274
|
};
|
|
274
275
|
walk("");
|
|
275
276
|
return out;
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
const writeDraft = (dir, d) => { mkdirSync(
|
|
279
|
+
const writeDraft = (dir, d) => { mkdirSync(ventureFile(dir, STAGE), { recursive: true }); writeFileSync(ventureFile(dir, DRAFT), JSON.stringify(d, null, 2) + "\n"); return d; };
|
|
279
280
|
/** Adds to the staged upgrade's draft record (what an agent did, say). */
|
|
280
281
|
export function annotate(dir, extra) {
|
|
281
282
|
const d = draft(dir);
|
|
@@ -283,16 +284,16 @@ export function annotate(dir, extra) {
|
|
|
283
284
|
return writeDraft(dir, { ...d, ...extra });
|
|
284
285
|
}
|
|
285
286
|
/** The staged upgrade's draft record, or null. */
|
|
286
|
-
export const draft = (dir) => (existsSync(
|
|
287
|
+
export const draft = (dir) => (existsSync(ventureFile(dir, DRAFT)) ? load(ventureFile(dir, DRAFT)) : null);
|
|
287
288
|
|
|
288
289
|
/** Copies the venture to .naive/upgrade/venture and upgrades the copy. Nothing in the venture changes. */
|
|
289
290
|
export async function stage(dir, { reg = registry(), by = "naive" } = {}) {
|
|
290
|
-
if (existsSync(
|
|
291
|
+
if (existsSync(ventureFile(dir, STAGE))) throw new Error(`an upgrade is already staged in ${STAGE}: accept it (--accept --confirm ${CONFIRM}) or reject it (--reject)`);
|
|
291
292
|
const p = plan(dir, { reg });
|
|
292
293
|
if (!p.pending) throw new Error(`${p.venture} is already on Naive ${p.to} with every package pinned: nothing to stage`);
|
|
293
294
|
const base = snapshot(dir);
|
|
294
|
-
const out =
|
|
295
|
-
for (const f of Object.keys(base)) { mkdirSync(dirname(join(out, f)), { recursive: true }); copyFileSync(
|
|
295
|
+
const out = ventureFile(dir, STAGED);
|
|
296
|
+
for (const f of Object.keys(base)) { mkdirSync(dirname(join(out, f)), { recursive: true }); copyFileSync(ventureFile(dir, f), join(out, f)); }
|
|
296
297
|
const result = await upgrade(out, { reg, git: false });
|
|
297
298
|
return writeDraft(dir, { status: "staged", from: p.from, to: p.to, staged_at: now(), by, base, migrations: result.release.migrations.map((m) => ({ id: m.id, moves: m.moves.length, rewrote: m.rewrote.length })), escalated: result.release.escalated, guides: result.release.guides });
|
|
298
299
|
}
|
|
@@ -310,7 +311,7 @@ export function runChecks(vdir, { reg = registry() } = {}) {
|
|
|
310
311
|
for (const r of routes().filter((x) => x.kind === "record" && x.schema && (x.form ?? "json") === "json")) {
|
|
311
312
|
for (const f of files.filter((f) => r.files.some((g) => glob(g).test(f)))) {
|
|
312
313
|
n++;
|
|
313
|
-
const v = spawnSync("python3", [join(repo, "schema/validate.py"), "--schema", r.schema.replace(/^schema\//, ""), "--file",
|
|
314
|
+
const v = spawnSync("python3", [join(repo, "schema/validate.py"), "--schema", r.schema.replace(/^schema\//, ""), "--file", ventureFile(vdir, f)], { encoding: "utf8" });
|
|
314
315
|
if (v.status !== 0) bad.push(`${f} does not match ${r.schema}: ${(v.stdout + v.stderr).trim().split("\n")[0]}`);
|
|
315
316
|
}
|
|
316
317
|
}
|
|
@@ -323,10 +324,10 @@ export function runChecks(vdir, { reg = registry() } = {}) {
|
|
|
323
324
|
|
|
324
325
|
/** What the staged copy changes: files added, changed and removed (the venture's _private/ is never removed). */
|
|
325
326
|
export function changes(dir) {
|
|
326
|
-
const live = snapshot(dir), staged = snapshot(
|
|
327
|
+
const live = snapshot(dir), staged = snapshot(ventureFile(dir, STAGED), { skipPrivate: false });
|
|
327
328
|
return {
|
|
328
|
-
added: Object.keys(staged).filter((f) => !(f in live) && !existsSync(
|
|
329
|
-
changed: Object.keys(staged).filter((f) => (f in live && live[f] !== staged[f]) || (!(f in live) && existsSync(
|
|
329
|
+
added: Object.keys(staged).filter((f) => !(f in live) && !existsSync(ventureFile(dir, f))).sort(),
|
|
330
|
+
changed: Object.keys(staged).filter((f) => (f in live && live[f] !== staged[f]) || (!(f in live) && existsSync(ventureFile(dir, f)) && sha(readFileSync(ventureFile(dir, f))) !== staged[f])).sort(),
|
|
330
331
|
removed: Object.keys(live).filter((f) => !(f in staged)).sort(),
|
|
331
332
|
};
|
|
332
333
|
}
|
|
@@ -335,7 +336,7 @@ export function changes(dir) {
|
|
|
335
336
|
export function check(dir, { reg = registry() } = {}) {
|
|
336
337
|
const d = draft(dir);
|
|
337
338
|
if (!d) throw new Error("no upgrade is staged: stage one with --stage or --agent");
|
|
338
|
-
const checks = runChecks(
|
|
339
|
+
const checks = runChecks(ventureFile(dir, STAGED), { reg });
|
|
339
340
|
const ok = checks.every((c) => c.passed);
|
|
340
341
|
return writeDraft(dir, { ...d, status: ok ? "ready" : "checks-failed", checked_at: now(), checks, changes: changes(dir) });
|
|
341
342
|
}
|
|
@@ -357,13 +358,13 @@ export async function accept(dir, { confirm, by, reg = registry() } = {}) {
|
|
|
357
358
|
const ch = checked.changes;
|
|
358
359
|
const clash = ch.changed.filter((f) => f.startsWith("_private/"));
|
|
359
360
|
if (clash.length) throw new Error(`not accepted: the upgrade would overwrite private files that differ (${clash.join(", ")}); move them aside, reject and stage again`);
|
|
360
|
-
const src =
|
|
361
|
-
for (const f of [...ch.added, ...ch.changed]) { mkdirSync(dirname(
|
|
362
|
-
for (const f of ch.removed) rmSync(
|
|
361
|
+
const src = ventureFile(dir, STAGED);
|
|
362
|
+
for (const f of [...ch.added, ...ch.changed]) { mkdirSync(dirname(ventureFile(dir, f)), { recursive: true }); copyFileSync(join(src, f), ventureFile(dir, f)); }
|
|
363
|
+
for (const f of ch.removed) rmSync(ventureFile(dir, f));
|
|
363
364
|
// Folders the upgrade emptied go too, unless the copy still has them.
|
|
364
365
|
const dirs = [...new Set(ch.removed.flatMap((f) => f.split("/").slice(0, -1).map((_, i, a) => a.slice(0, i + 1).join("/"))))].sort((a, b) => b.length - a.length);
|
|
365
|
-
for (const x of dirs) if (existsSync(
|
|
366
|
-
rmSync(
|
|
366
|
+
for (const x of dirs) if (existsSync(ventureFile(dir, x)) && !existsSync(join(src, x)) && readdirSync(ventureFile(dir, x)).length === 0) rmSync(ventureFile(dir, x), { recursive: true });
|
|
367
|
+
rmSync(ventureFile(dir, STAGE), { recursive: true, force: true });
|
|
367
368
|
return landed(dir, d, ch, by);
|
|
368
369
|
}
|
|
369
370
|
|
|
@@ -373,13 +374,13 @@ async function refreshAdapters(dir) {
|
|
|
373
374
|
|
|
374
375
|
async function landed(dir, d, ch, by) {
|
|
375
376
|
const rel = `.release/upgrade-${d.to}.json`;
|
|
376
|
-
const e = existsSync(
|
|
377
|
+
const e = existsSync(ventureFile(dir, rel)) ? record(dir, { entity: rel, activity: "accept", agent: agentFromEnv("person", "cli", by ? { id: by } : {}), note: `accepted the staged upgrade to Naive ${d.to}${d.by && d.by !== "naive" ? ` (worked by ${d.by})` : ""}` }) : null;
|
|
377
378
|
return { accepted: d.to, from: d.from, added: ch.added.length, changed: ch.changed.length, removed: ch.removed.length, provenance: e?.hash ?? null, adapters: await refreshAdapters(dir) };
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
/** Discards a staged upgrade. The venture never changed, so nothing is logged. */
|
|
381
382
|
export function reject(dir) {
|
|
382
|
-
if (!existsSync(
|
|
383
|
-
rmSync(
|
|
383
|
+
if (!existsSync(ventureFile(dir, STAGE))) throw new Error("no upgrade is staged");
|
|
384
|
+
rmSync(ventureFile(dir, STAGE), { recursive: true, force: true });
|
|
384
385
|
return { rejected: true };
|
|
385
386
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Where a venture's files physically are (EE-086). Code names a file by its logical path, the path a
|
|
2
|
+
// record has always had (foundation/problem.json, provenance.jsonl, knowledge/sources.json), and `at`
|
|
3
|
+
// says where it sits on disk under the venture's layout:
|
|
4
|
+
//
|
|
5
|
+
// flat (no layout in venture.json, every venture made before EE-086): the logical path, unchanged
|
|
6
|
+
// structured (venture.json layout: "structured"):
|
|
7
|
+
// venture.json, AGENTS.md, README.md, package.json at the top (profile.md is README.md)
|
|
8
|
+
// provenance.jsonl, sources.json, naive.config.json in meta/; .release/ is meta/releases/
|
|
9
|
+
// every package (foundation/, people/, _drafts/ ...) in k-base/, or in k-base/(group)/ when the
|
|
10
|
+
// founder mirrors a function as a folder
|
|
11
|
+
// code the venture keeps (a marketing site, a ported repository) in codebase/
|
|
12
|
+
// A structured venture made by 0.3.0 has knowledge/ where k-base/ now is; it is read where it is
|
|
13
|
+
// until naive upgrade renames it.
|
|
14
|
+
//
|
|
15
|
+
// `logical` goes the other way, for code that walks the disk and matches files against the data model.
|
|
16
|
+
|
|
17
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
18
|
+
import { join, relative, sep } from "node:path";
|
|
19
|
+
|
|
20
|
+
const TOP = new Set(["venture.json", "AGENTS.md", "README.md", "package.json", "package-lock.json", ".gitignore"]);
|
|
21
|
+
const META = { "provenance.jsonl": "meta/provenance.jsonl", "knowledge/sources.json": "meta/sources.json", "sources.json": "meta/sources.json", "naive.config.json": "meta/naive.config.json" };
|
|
22
|
+
const cache = new Map();
|
|
23
|
+
export const KBASE = "k-base";
|
|
24
|
+
export const CODEBASE = "codebase";
|
|
25
|
+
|
|
26
|
+
/** The venture's knowledge-base folder: k-base/, or knowledge/ in a structured venture made before it was renamed. */
|
|
27
|
+
export function kbaseOf(dir) {
|
|
28
|
+
return !existsSync(join(dir, KBASE)) && existsSync(join(dir, "knowledge", "foundation")) ? "knowledge" : KBASE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The venture's layout: "structured" (EE-086) or "flat". Read once per venture.json change. */
|
|
32
|
+
export function layoutOf(dir) {
|
|
33
|
+
const f = join(dir, "venture.json");
|
|
34
|
+
let m = 0;
|
|
35
|
+
try { m = statSync(f).mtimeMs; } catch { return "flat"; }
|
|
36
|
+
const hit = cache.get(dir);
|
|
37
|
+
if (hit && hit.m === m) return hit.layout;
|
|
38
|
+
let layout = "flat";
|
|
39
|
+
try { layout = JSON.parse(readFileSync(f, "utf8")).layout === "structured" ? "structured" : "flat"; } catch {}
|
|
40
|
+
cache.set(dir, { m, layout });
|
|
41
|
+
return layout;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The (group)/ folder a package sits in, when the founder mirrors functions on disk.
|
|
45
|
+
function groupOf(dir, pkg) {
|
|
46
|
+
const kb = join(dir, kbaseOf(dir));
|
|
47
|
+
if (!pkg || existsSync(join(kb, pkg)) || !existsSync(kb)) return null;
|
|
48
|
+
for (const g of readdirSync(kb)) if (/^\(.+\)$/.test(g) && existsSync(join(kb, g, pkg))) return g;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The venture-relative physical path of a logical path. */
|
|
53
|
+
export function physical(dir, rel) {
|
|
54
|
+
const r = String(rel).split(sep).join("/").replace(/^\.\//, "");
|
|
55
|
+
if (layoutOf(dir) !== "structured") return r;
|
|
56
|
+
if (TOP.has(r)) return r;
|
|
57
|
+
if (r === "profile.md") return "README.md";
|
|
58
|
+
if (META[r]) return META[r];
|
|
59
|
+
if (r === ".release" || r.startsWith(".release/")) return "meta/releases" + r.slice(".release".length);
|
|
60
|
+
if (r.startsWith(".naive/") || r === ".naive" || r.startsWith("node_modules") || r.startsWith(".git")) return r;
|
|
61
|
+
const kb = kbaseOf(dir);
|
|
62
|
+
if (r.startsWith(`${kb}/`) || r === kb || r.startsWith("meta/") || r.startsWith(`${CODEBASE}/`) || r.startsWith("repo/")) return r;
|
|
63
|
+
const pkg = r.split("/")[0];
|
|
64
|
+
const g = groupOf(dir, pkg);
|
|
65
|
+
return `${kb}/${g ? `${g}/` : ""}${r}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The absolute path of a logical path in a venture. */
|
|
69
|
+
export const ventureFile = (dir, rel) => join(dir, physical(dir, rel));
|
|
70
|
+
|
|
71
|
+
/** The logical path of a venture-relative physical path: the inverse of `physical`. */
|
|
72
|
+
export function logical(dir, rel) {
|
|
73
|
+
const r = String(rel).split(sep).join("/");
|
|
74
|
+
if (layoutOf(dir) !== "structured") return r;
|
|
75
|
+
if (r === "README.md") return "profile.md";
|
|
76
|
+
for (const [l, p] of Object.entries(META)) if (r === p) return l === "sources.json" ? "knowledge/sources.json" : l;
|
|
77
|
+
if (r === "meta/releases" || r.startsWith("meta/releases/")) return ".release" + r.slice("meta/releases".length);
|
|
78
|
+
const kb = kbaseOf(dir);
|
|
79
|
+
if (r.startsWith(`${kb}/`)) return r.slice(kb.length + 1).replace(/^\([^/]+\)\//, "");
|
|
80
|
+
return r;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Every file of a venture by its logical path, skipping what is never a record. */
|
|
84
|
+
export function logicalFiles(dir) {
|
|
85
|
+
const out = [];
|
|
86
|
+
const walk = (d) => {
|
|
87
|
+
if (!existsSync(d)) return;
|
|
88
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
89
|
+
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
90
|
+
const p = join(d, e.name);
|
|
91
|
+
if (e.isDirectory()) { if (!existsSync(join(p, "venture.json"))) walk(p); }
|
|
92
|
+
else out.push(logical(dir, relative(dir, p)));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
walk(dir);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
@@ -34,6 +34,31 @@
|
|
|
34
34
|
"pattern": "^[a-z0-9-]+$",
|
|
35
35
|
"description": "The venture type, an id from schema/venture-types.json: what kind of venture this is (a software startup, a VC fund), which decides its starting packages, first decisions, functions and roles (EE-082)"
|
|
36
36
|
},
|
|
37
|
+
"layout": {
|
|
38
|
+
"enum": [
|
|
39
|
+
"flat",
|
|
40
|
+
"structured"
|
|
41
|
+
],
|
|
42
|
+
"default": "flat",
|
|
43
|
+
"description": "Where the venture's files sit (EE-086). structured: venture.json, README.md and AGENTS.md at the top, the knowledge base in knowledge/, sources.json, naive.config.json, the provenance log and releases in meta/, and code in an optional repo/. flat: every venture made before EE-086, with packages at the top."
|
|
44
|
+
},
|
|
45
|
+
"functions": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"description": "The venture's functions and their one accountable owner each (EE-071), keyed by function id from operations/departments.json. A solo founder owns them all, which is what the manifest assumes when this is left out.",
|
|
48
|
+
"additionalProperties": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"required": [
|
|
51
|
+
"owner"
|
|
52
|
+
],
|
|
53
|
+
"properties": {
|
|
54
|
+
"owner": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "A role or a person's name; no personal details"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"additionalProperties": false
|
|
60
|
+
}
|
|
61
|
+
},
|
|
37
62
|
"parent": {
|
|
38
63
|
"type": [
|
|
39
64
|
"string",
|
|
@@ -181,23 +206,6 @@
|
|
|
181
206
|
"additionalProperties": false
|
|
182
207
|
}
|
|
183
208
|
},
|
|
184
|
-
"functions": {
|
|
185
|
-
"type": "object",
|
|
186
|
-
"description": "The venture's functions and their one accountable owner each (EE-071), keyed by function id from operations/departments.json. A solo founder owns them all, which is what the manifest assumes when this is left out.",
|
|
187
|
-
"additionalProperties": {
|
|
188
|
-
"type": "object",
|
|
189
|
-
"required": [
|
|
190
|
-
"owner"
|
|
191
|
-
],
|
|
192
|
-
"properties": {
|
|
193
|
-
"owner": {
|
|
194
|
-
"type": "string",
|
|
195
|
-
"description": "A role or a person's name; no personal details"
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
"additionalProperties": false
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
209
|
"brand": {
|
|
202
210
|
"type": "object",
|
|
203
211
|
"description": "The venture's verbal brand elements, owned by the brand pillar. Visual identity (tokens, logos) is a separate record.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "workspace.schema.json",
|
|
4
4
|
"title": "Workspace",
|
|
5
|
-
"description": "A builder's workspace: one instance of the system. A workspace.json at its top says which schema version it follows, where its vault is and where its repositories are (EE-002, EE-011, EE-020). A workspace always has a vault; it has a code folder of repositories once any venture builds software.",
|
|
5
|
+
"description": "A builder's workspace: one instance of the system. A naive-workspace.json (earlier workspace.json) at its top says which schema version it follows, where its vault is and where its repositories are (EE-002, EE-011, EE-020). A workspace always has a vault; it has a code folder of repositories once any venture builds software.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"schema",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
19
19
|
"description": "The version in schema/VERSION that this workspace follows"
|
|
20
20
|
},
|
|
21
|
+
"name": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "The workspace's name, shown to the founder. create-venture names a new workspace \"Naive workspace\"; the founder can rename it"
|
|
24
|
+
},
|
|
21
25
|
"ventures": {
|
|
22
26
|
"type": "array",
|
|
23
27
|
"items": {
|