naive-framework 0.1.0 → 0.2.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 +2 -1
- package/docs/decisions/decisions.json +27 -0
- package/docs/reference/ROUTES.md +36 -37
- package/governance/layer.json +2 -1
- package/governance/venture-template/roles/venture-roles.json +62 -0
- package/interface/acting.mjs +83 -0
- package/interface/adapters/claude-code.json +16 -0
- package/interface/adapters/codex.json +14 -0
- package/interface/adapters/cursor.json +16 -0
- package/interface/adapters.mjs +182 -0
- package/interface/api/server.mjs +4 -2
- package/interface/boundaries.mjs +218 -0
- package/interface/brand.mjs +214 -0
- package/interface/cli/freshness.mjs +59 -0
- package/interface/cli/guard.mjs +6 -0
- package/interface/cli/mentura.mjs +78 -8
- package/interface/cli/naive.mjs +33 -8
- package/interface/context.mjs +524 -0
- package/interface/demo/board.mjs +1 -1
- package/interface/dev/setup.mjs +84 -0
- package/interface/gate-hooks.mjs +36 -0
- package/interface/harness/evals.mjs +3 -3
- package/interface/harness/run.mjs +4 -4
- package/interface/instrumentation.mjs +86 -0
- package/interface/kit-boundary.mjs +73 -0
- package/interface/layer.json +8 -2
- package/interface/mcp/server.mjs +47 -7
- package/interface/package-routes.mjs +196 -0
- package/interface/provenance.mjs +24 -1
- package/interface/reads.mjs +173 -0
- package/interface/release-card.mjs +42 -0
- package/interface/router.mjs +101 -28
- package/interface/skills/naive-setup/SKILL.md +71 -0
- package/interface/skills/naive-setup/artifact.html +83 -0
- package/interface/vitals.mjs +115 -0
- package/interface/writes.mjs +190 -0
- package/knowledge/freshness.json +85 -0
- package/knowledge/layer.json +11 -3
- package/knowledge/templates/glossary.md +23 -0
- package/operations/layer.json +10 -3
- package/operations/templates/marketing-brand.md +46 -0
- package/operations/templates/public-assets.md +39 -0
- package/package.json +3 -1
- package/packages/catalog.json +8 -0
- package/packages/customers/package.json +21 -1
- package/packages/deals/package.json +27 -0
- package/packages/decisions/package.json +12 -1
- package/packages/finance/frames/fund-without-ceding-control.frame.json +177 -0
- package/packages/foundation/package.json +8 -1
- package/packages/fund/package.json +48 -0
- package/packages/marketing/package.json +10 -4
- package/packages/money/package.json +24 -3
- package/packages/validation/package.json +13 -0
- package/platform/README.md +6 -0
- package/platform/adapter.json +15 -0
- package/platform/integrations/remote-image.mjs +79 -0
- package/platform/integrations/rho.mjs +7 -6
- package/platform/kits/README.md +8 -1
- package/platform/kits/agent-upgrade/kit.json +15 -0
- package/platform/kits/agent-upgrade/run.mjs +82 -0
- package/platform/kits/agent-upgrade/stand-in-agent.mjs +32 -0
- package/platform/kits/validation-kickoff/kit.json +29 -0
- package/platform/webhooks/server.mjs +70 -0
- package/purpose/frameworks/idea-map/ideamap.mjs +4 -2
- package/schema/README.md +1 -0
- package/schema/access.mjs +57 -0
- package/schema/adapter.schema.json +33 -0
- package/schema/assets.mjs +212 -0
- package/schema/brand.mjs +373 -0
- package/schema/build-release.mjs +61 -0
- package/schema/caching.mjs +505 -0
- package/schema/check-assets.mjs +24 -0
- package/schema/check-brand.mjs +24 -0
- package/schema/check-config.mjs +110 -0
- package/schema/check-nextjs-status.mjs +3 -1
- package/schema/check-people.mjs +70 -0
- package/schema/check-sources.mjs +9 -1
- package/schema/data-model.json +334 -8
- package/schema/deal.schema.json +80 -0
- package/schema/decisions.mjs +7 -1
- package/schema/examples/frames/judge.mjs +10 -0
- package/schema/examples/venture-type/jev.mjs +12 -0
- package/schema/examples/workspace/ventures/acme-labs/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/acme-labs/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/discovery-goal.decision.json +23 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-icp.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-problem.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/venture.team.decision.json +37 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/sources.json +46 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000001.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000002.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000003.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000004.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000005.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000006.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000007.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000008.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000009.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000010.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000011.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000012.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000013.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000014.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_streams/transactions/stream.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_summary.json +30 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/people/founders/maya-chen.json +15 -0
- package/schema/examples/workspace/ventures/acme-labs/people/people.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.exists.decision.json +38 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/acme-labs/profile.md +12 -0
- package/schema/examples/workspace/ventures/acme-labs/provenance.jsonl +12 -0
- package/schema/examples/workspace/ventures/acme-labs/raw/dump.md +6 -0
- package/schema/examples/workspace/ventures/acme-labs/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/deals/ductline.json +8 -0
- package/schema/examples/workspace/ventures/northstar/deals/kilnworks.json +14 -0
- package/schema/examples/workspace/ventures/northstar/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/evidence/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.first-close.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/thesis.decision.json +26 -0
- package/schema/examples/workspace/ventures/northstar/fund/terms.json +22 -0
- package/schema/examples/workspace/ventures/northstar/fund/thesis.json +22 -0
- package/schema/examples/workspace/ventures/northstar/governance/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ana-ruiz.json +13 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ben-okafor.json +7 -0
- package/schema/examples/workspace/ventures/northstar/people/people.json +27 -0
- package/schema/examples/workspace/ventures/northstar/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.exists.decision.json +32 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/provenance.jsonl +3 -0
- package/schema/examples/workspace/ventures/northstar/purpose/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/venture.json +49 -0
- package/schema/founder.schema.json +72 -0
- package/schema/frame.schema.json +240 -0
- package/schema/frames.mjs +228 -0
- package/schema/freshness-log.schema.json +40 -0
- package/schema/freshness-profiles.schema.json +44 -0
- package/schema/freshness.mjs +473 -0
- package/schema/fund-terms.schema.json +107 -0
- package/schema/fund-thesis.schema.json +87 -0
- package/schema/gate-drafts.mjs +130 -0
- package/schema/gate.mjs +105 -11
- package/schema/glossary.schema.json +25 -0
- package/schema/init-venture.mjs +38 -8
- package/schema/judgment.schema.json +38 -0
- package/schema/kit.schema.json +47 -0
- package/schema/kits.mjs +24 -0
- package/schema/knowledge-sources.schema.json +145 -15
- package/schema/layer.json +16 -3
- package/schema/marketing-brand.schema.json +116 -0
- package/schema/migrations/kit-to-package.mjs +94 -0
- package/schema/migrations/package-layout.mjs +86 -0
- package/schema/model-steps.mjs +717 -0
- package/schema/money-summary.schema.json +138 -0
- package/schema/naive-config.mjs +165 -0
- package/schema/naive-config.schema.json +268 -0
- package/schema/need-now.json +171 -0
- package/schema/need-now.mjs +3 -2
- package/schema/packages.mjs +10 -5
- package/schema/people.schema.json +86 -0
- package/schema/public-assets.schema.json +76 -0
- package/schema/release-read.mjs +111 -0
- package/schema/releases.mjs +497 -0
- package/schema/setup.mjs +164 -0
- package/schema/stale.mjs +111 -0
- package/schema/start-release.mjs +41 -0
- package/schema/structure.mjs +186 -0
- package/schema/systems-catalog.json +126 -0
- package/schema/systems-catalog.schema.json +78 -0
- package/schema/trigger.schema.json +44 -0
- package/schema/triggers.mjs +237 -0
- package/schema/upgrade-registry.schema.json +60 -0
- package/schema/upgrade-venture.mjs +96 -118
- package/schema/upgrades.json +69 -0
- package/schema/upgrades.mjs +385 -0
- package/schema/validate.py +19 -10
- package/schema/venture-role.schema.json +64 -0
- package/schema/venture-type.mjs +62 -0
- package/schema/venture-types.json +346 -0
- package/schema/venture-types.schema.json +129 -0
- package/schema/venture.schema.json +53 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// Upgrades (EE-076; the Next.js translation's chapter 18, rows U-1 to U-6): what `next upgrade`, its
|
|
2
|
+
// codemods, `--agent`, the canary channel and the version guides become in Naive.
|
|
3
|
+
//
|
|
4
|
+
// U-1 naive upgrade moves a venture onto the installed framework and pins it and its packages in
|
|
5
|
+
// naive.config.json; --framework first updates the installed framework (git pull --ff-only).
|
|
6
|
+
// U-2 migrations (schema/migrations/*.mjs) rewrite records when the framework or a package's schema
|
|
7
|
+
// changes, with route aliases for renamed folders. The kit-to-package rename is the first record
|
|
8
|
+
// migration; the package layout move before it is the first layout one.
|
|
9
|
+
// U-3 a staged upgrade runs on a copy under .naive/upgrade/, then the checks run on the copy; it lands
|
|
10
|
+
// only when they pass and a person types the confirmation. The agent that works the copy is a
|
|
11
|
+
// Mentura kit (platform/kits/agent-upgrade); this module is the part that decides.
|
|
12
|
+
// U-5 channels: stable, or canary for experimental packages; a later phase's package can be adopted
|
|
13
|
+
// early, by name.
|
|
14
|
+
// U-6 each framework version has a guide bundled in docs/upgrades/, printed from the installed copy.
|
|
15
|
+
//
|
|
16
|
+
// Code decides here: no model is called. The registry is schema/upgrades.json (NAIVE_UPGRADES points
|
|
17
|
+
// at another one, for tests).
|
|
18
|
+
|
|
19
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
23
|
+
import { pathToFileURL } from "node:url";
|
|
24
|
+
import { agentFromEnv, record } from "../interface/provenance.mjs";
|
|
25
|
+
import { routes, glob } from "../interface/router.mjs";
|
|
26
|
+
import { CONFIG, pins, readConfig, writeConfig } from "./naive-config.mjs";
|
|
27
|
+
import { packages as packageFit } from "./packages.mjs";
|
|
28
|
+
|
|
29
|
+
export const repo = new URL("..", import.meta.url).pathname;
|
|
30
|
+
export const STAGE = ".naive/upgrade";
|
|
31
|
+
export const STAGED = `${STAGE}/venture`;
|
|
32
|
+
export const DRAFT = `${STAGE}/draft.json`;
|
|
33
|
+
export const CONFIRM = "upgrade";
|
|
34
|
+
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
35
|
+
const now = () => new Date().toISOString().replace(/\.\d+Z$/, "Z");
|
|
36
|
+
const sha = (buf) => createHash("sha256").update(buf).digest("hex");
|
|
37
|
+
|
|
38
|
+
/** Compares two x.y.z versions. */
|
|
39
|
+
export const cmp = (a, b) => {
|
|
40
|
+
const x = String(a).split(".").map(Number), y = String(b).split(".").map(Number);
|
|
41
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) - (y[i] ?? 0);
|
|
42
|
+
return 0;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The upgrade registry of an installed framework (default: this copy). */
|
|
46
|
+
export function registry(root = repo) {
|
|
47
|
+
const f = root === repo && process.env.NAIVE_UPGRADES ? process.env.NAIVE_UPGRADES : join(root, "schema/upgrades.json");
|
|
48
|
+
return load(f);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const abs = (p, root = repo) => (isAbsolute(p) ? p : join(root, p));
|
|
52
|
+
|
|
53
|
+
/** The framework version a venture is on: its pin, or 0.1.0 for a venture made before pins. */
|
|
54
|
+
export function versionOf(dir) {
|
|
55
|
+
return readConfig(dir).naive?.version ?? "0.1.0";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const kindOf = (name) => { const f = join(repo, "packages", name, "package.json"); return existsSync(f) ? load(f).kind : null; };
|
|
59
|
+
|
|
60
|
+
/** The packages a venture should be pinned to: what it pins now, what fits its phase now, and its data folders. */
|
|
61
|
+
function wanted(dir, reg, cfg) {
|
|
62
|
+
const names = new Set(Object.keys(pins(cfg)));
|
|
63
|
+
try { const p = packageFit(dir); for (const x of [...p.core, ...p.handoff]) if (x.in === "now") names.add(x.id); } catch {}
|
|
64
|
+
for (const n of Object.keys(reg.packages)) if (["data", "raw", "folder"].includes(kindOf(n))) names.add(n);
|
|
65
|
+
return [...names].sort();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function loadMigration(m) {
|
|
69
|
+
const mod = await import(pathToFileURL(abs(m.module)).href);
|
|
70
|
+
if (typeof mod.apply !== "function") throw new Error(`${m.module} exports no apply(dir, ctx)`);
|
|
71
|
+
return { ...m, describe: mod.describe ?? m.id, apply: mod.apply };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What an upgrade would do, without doing it: the framework versions it crosses, with their guides and
|
|
76
|
+
* migrations, and each package's pin before and after with the package migrations it runs.
|
|
77
|
+
*/
|
|
78
|
+
export function plan(dir, { reg = registry() } = {}) {
|
|
79
|
+
if (!existsSync(join(dir, "venture.json"))) throw new Error(`${dir} is not a venture (no venture.json)`);
|
|
80
|
+
const cfg = readConfig(dir);
|
|
81
|
+
const from = versionOf(dir), to = reg.framework;
|
|
82
|
+
const byId = new Map(reg.migrations.map((m) => [m.id, m]));
|
|
83
|
+
const steps = reg.versions.filter((v) => cmp(v.version, from) > 0 && cmp(v.version, to) <= 0).sort((a, b) => cmp(a.version, b.version))
|
|
84
|
+
.map((v) => ({ version: v.version, summary: v.summary, guide: v.guide, migrations: v.migrations.map((id) => { const m = byId.get(id); if (!m) throw new Error(`version ${v.version} names migration ${id}, which is not in the registry`); return m; }) }));
|
|
85
|
+
const current = pins(cfg);
|
|
86
|
+
const pkgs = wanted(dir, reg, cfg).map((name) => {
|
|
87
|
+
const info = reg.packages[name];
|
|
88
|
+
const was = current[name]?.version ?? null;
|
|
89
|
+
if (!info) return { name, from: was, to: was, missing: true, migrations: [] };
|
|
90
|
+
const migrations = reg.migrations.filter((m) => m.package === name && cmp(m.to, info.version) <= 0 && (was === null || (cmp(was, m.from ?? "0.0.0") >= 0 && cmp(was, m.to) < 0)));
|
|
91
|
+
return { name, from: was, to: info.version, channel: info.channel, early: !!current[name]?.early, migrations };
|
|
92
|
+
});
|
|
93
|
+
const pending = steps.length > 0 || pkgs.some((p) => !p.missing && (p.from === null || cmp(p.from, p.to) !== 0)) || cfg.naive?.version !== to;
|
|
94
|
+
return { venture: load(join(dir, "venture.json")).id, from, to, channel: cfg.naive?.channel ?? "stable", steps, packages: pkgs, pending };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The changes one migration makes, all through here, so a dry run reports them and a real run logs them.
|
|
98
|
+
function context(dir, { dry, git, cfg, at, onConfig }) {
|
|
99
|
+
const moved = [], writes = [], escalated = [], notes = [], kept = [];
|
|
100
|
+
const tracked = (p) => git && spawnSync("git", ["ls-files", "--error-unmatch", p], { cwd: dir, stdio: "pipe" }).status === 0;
|
|
101
|
+
const removeEmpty = (p) => {
|
|
102
|
+
if (!existsSync(p) || !statSync(p).isDirectory()) return;
|
|
103
|
+
for (const e of readdirSync(p, { withFileTypes: true })) if (e.isDirectory()) removeEmpty(join(p, e.name));
|
|
104
|
+
if (readdirSync(p).length === 0) rmSync(p, { recursive: true });
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
at, config: cfg, moved, writes, escalated, notes, kept,
|
|
108
|
+
mv(a, b) {
|
|
109
|
+
if (!existsSync(join(dir, a))) return;
|
|
110
|
+
if (existsSync(join(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
|
+
if (!dry) {
|
|
112
|
+
mkdirSync(dirname(join(dir, b)), { recursive: true });
|
|
113
|
+
if (tracked(a)) execFileSync("git", ["mv", a, b], { cwd: dir, stdio: "pipe" });
|
|
114
|
+
else renameSync(join(dir, a), join(dir, b));
|
|
115
|
+
}
|
|
116
|
+
moved.push([a, b]);
|
|
117
|
+
},
|
|
118
|
+
write(rel, text, { activity = "write", note, derived_from, log = true } = {}) {
|
|
119
|
+
if (!dry) { mkdirSync(dirname(join(dir, rel)), { recursive: true }); writeFileSync(join(dir, rel), text); }
|
|
120
|
+
writes.push({ path: rel, activity, note, derived_from, log });
|
|
121
|
+
},
|
|
122
|
+
escalate: (msg) => escalated.push(msg),
|
|
123
|
+
note: (msg) => notes.push(msg),
|
|
124
|
+
configChanged: () => onConfig(),
|
|
125
|
+
removeEmpty: (rel) => { if (!dry) removeEmpty(join(dir, rel)); },
|
|
126
|
+
keep(sub) {
|
|
127
|
+
if (existsSync(join(dir, sub))) return;
|
|
128
|
+
if (!dry) { mkdirSync(join(dir, sub), { recursive: true }); writeFileSync(join(dir, sub, ".gitkeep"), ""); }
|
|
129
|
+
kept.push(sub);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Upgrades a venture in place (U-1, U-2): runs each migration the plan names, pins the framework and
|
|
136
|
+
* the packages in naive.config.json, writes .release/upgrade-<version>.json and logs every change in
|
|
137
|
+
* provenance. dry: report only. Triggers do not fire on an upgrade's own writes.
|
|
138
|
+
*/
|
|
139
|
+
export async function upgrade(dir, { reg = registry(), dry = false, git = true } = {}) {
|
|
140
|
+
const p = plan(dir, { reg });
|
|
141
|
+
const cfg = readConfig(dir);
|
|
142
|
+
const at = now();
|
|
143
|
+
let configChanged = false;
|
|
144
|
+
const ran = [];
|
|
145
|
+
const held = process.env.NAIVE_TRIGGERS_ACTIVE;
|
|
146
|
+
process.env.NAIVE_TRIGGERS_ACTIVE = "upgrade";
|
|
147
|
+
const agent = agentFromEnv("agent", "cli", { id: process.env.MENTURA_ACTOR_ID ?? "naive-upgrade" });
|
|
148
|
+
try {
|
|
149
|
+
const order = [...p.steps.flatMap((s) => s.migrations.map((m) => ({ ...m, version: s.version }))), ...p.packages.flatMap((x) => x.migrations.map((m) => ({ ...m, version: `${x.name}@${m.to}` })))];
|
|
150
|
+
// A dry run first, so a conflict stops the upgrade before anything moves.
|
|
151
|
+
if (!dry) for (const m of order) { const mod = await loadMigration(m); mod.apply(dir, context(dir, { dry: true, git, cfg: structuredClone(cfg), at, onConfig() {} })); }
|
|
152
|
+
for (const m of order) {
|
|
153
|
+
const mod = await loadMigration(m);
|
|
154
|
+
const ctx = context(dir, { dry, git, cfg, at, onConfig: () => { configChanged = true; } });
|
|
155
|
+
mod.apply(dir, ctx);
|
|
156
|
+
ran.push({ id: m.id, version: m.version, describe: mod.describe, decision: m.decision ?? null, moves: ctx.moved.map(([from, to]) => ({ from, to })),
|
|
157
|
+
rewrote: ctx.writes.filter((w) => w.activity === "write").map((w) => w.path), escalated: ctx.escalated, notes: ctx.notes });
|
|
158
|
+
if (dry) continue;
|
|
159
|
+
for (const [from, to] of ctx.moved) record(dir, { entity: to, activity: "move", agent, derived_from: [from], note: `moved from ${from} (${m.decision ?? m.id})` });
|
|
160
|
+
for (const w of ctx.writes.filter((x) => x.log)) record(dir, { entity: w.path, activity: w.activity, agent, derived_from: w.derived_from, note: `${m.id}: ${w.note ?? "rewritten"}`, decision: m.decision ?? undefined });
|
|
161
|
+
}
|
|
162
|
+
// Pins (U-1, U-4): the framework, the channel, and every package the venture uses.
|
|
163
|
+
const before = JSON.stringify(cfg);
|
|
164
|
+
cfg.naive = { ...(cfg.naive ?? {}), version: p.to, channel: cfg.naive?.channel ?? "stable" };
|
|
165
|
+
const now_ = pins(cfg);
|
|
166
|
+
const escalated = [];
|
|
167
|
+
for (const x of p.packages) {
|
|
168
|
+
if (x.missing) { escalated.push(`package ${x.name} is pinned but no longer in Naive ${p.to}; its pin is kept`); continue; }
|
|
169
|
+
now_[x.name] = { ...(now_[x.name] ?? {}), version: x.to, ...(x.channel === "experimental" ? { channel: "experimental" } : {}) };
|
|
170
|
+
}
|
|
171
|
+
// A package a migration named (a kit that was a package) is pinned to the installed version too.
|
|
172
|
+
for (const [n, v] of Object.entries(now_)) if (!v.version && reg.packages[n]) now_[n] = { ...v, version: reg.packages[n].version };
|
|
173
|
+
cfg.packages = Object.fromEntries(Object.entries(now_).sort(([a], [b]) => a.localeCompare(b)));
|
|
174
|
+
if (JSON.stringify(cfg) !== before) configChanged = true;
|
|
175
|
+
const release = { version: p.to, from: p.from, at, venture: p.venture, channel: cfg.naive.channel, migrations: ran,
|
|
176
|
+
packages: p.packages.filter((x) => !x.missing).map((x) => ({ name: x.name, from: x.from, to: x.to })), guides: p.steps.map((s) => s.guide).filter(Boolean), escalated: [...ran.flatMap((r) => r.escalated), ...escalated] };
|
|
177
|
+
if (!dry && (p.pending || configChanged)) {
|
|
178
|
+
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
|
+
const rel = `.release/upgrade-${p.to}.json`;
|
|
180
|
+
mkdirSync(join(dir, ".release"), { recursive: true });
|
|
181
|
+
writeFileSync(join(dir, rel), JSON.stringify(release, null, 2) + "\n");
|
|
182
|
+
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
|
+
// Adapters installed in the venture (Z-4) are refreshed, so their copies of package skills follow the upgrade.
|
|
184
|
+
release.adapters = await refreshAdapters(dir);
|
|
185
|
+
}
|
|
186
|
+
return { ...p, dry, release, changed: !dry && (p.pending || configChanged) };
|
|
187
|
+
} finally {
|
|
188
|
+
if (held === undefined) delete process.env.NAIVE_TRIGGERS_ACTIVE; else process.env.NAIVE_TRIGGERS_ACTIVE = held;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The guide bundled for one framework version (U-6), or null. */
|
|
193
|
+
export function guide(version, { reg = registry() } = {}) {
|
|
194
|
+
const v = reg.versions.find((x) => x.version === version);
|
|
195
|
+
if (!v) throw new Error(`Naive has no version ${version}: ${reg.versions.map((x) => x.version).join(", ")}`);
|
|
196
|
+
return v.guide && existsSync(abs(v.guide)) ? { version, path: v.guide, text: readFileSync(abs(v.guide), "utf8") } : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Switches the venture's channel (U-5): stable, or canary for experimental packages. */
|
|
200
|
+
export function setChannel(dir, channel, { reg = registry() } = {}) {
|
|
201
|
+
if (!["stable", "canary"].includes(channel)) throw new Error(`channel ${channel}: stable or canary`);
|
|
202
|
+
const cfg = readConfig(dir);
|
|
203
|
+
if (cfg.naive?.channel === channel) return { changed: false, channel };
|
|
204
|
+
const experimental = Object.entries(pins(cfg)).filter(([n]) => reg.packages[n]?.channel === "experimental").map(([n]) => n);
|
|
205
|
+
if (channel === "stable" && experimental.length) throw new Error(`the venture uses experimental packages (${experimental.join(", ")}): drop them first (--drop <package>)`);
|
|
206
|
+
cfg.naive = { version: versionOf(dir), ...(cfg.naive ?? {}), channel };
|
|
207
|
+
writeConfig(dir, cfg);
|
|
208
|
+
record(dir, { entity: CONFIG, activity: "write", agent: agentFromEnv(process.env.MENTURA_ACTOR === "agent" ? "agent" : "person", "cli"), note: `channel set to ${channel} (U-5)` });
|
|
209
|
+
return { changed: true, channel };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Adopts a package before its phase (U-5): it is pinned with early: true, and the venture package
|
|
214
|
+
* opens it now. An experimental package needs the canary channel. The venture must be on the
|
|
215
|
+
* installed framework first.
|
|
216
|
+
*/
|
|
217
|
+
export function adopt(dir, name, { reg = registry() } = {}) {
|
|
218
|
+
const info = reg.packages[name];
|
|
219
|
+
if (!info) throw new Error(`no package ${name} in Naive ${reg.framework}: ${Object.keys(reg.packages).join(", ")}`);
|
|
220
|
+
if (cmp(versionOf(dir), reg.framework) !== 0) throw new Error(`the venture is on Naive ${versionOf(dir)}: upgrade it to ${reg.framework} before adopting packages`);
|
|
221
|
+
const cfg = readConfig(dir);
|
|
222
|
+
if (info.channel === "experimental" && cfg.naive?.channel !== "canary") throw new Error(`${name} is experimental (${info.note ?? "not modeled yet"}): switch to the canary channel first (--canary)`);
|
|
223
|
+
const current = pins(cfg);
|
|
224
|
+
if (current[name]?.early) return { changed: false, name, note: `${name} is already adopted early` };
|
|
225
|
+
let fit = null;
|
|
226
|
+
try { fit = [...packageFit(dir).core, ...packageFit(dir).handoff].find((x) => x.id === name) ?? null; } catch {}
|
|
227
|
+
if (fit?.in === "now" && info.channel !== "experimental") return { changed: false, name, note: `${name} already fits this phase (fit ${fit.fit}); nothing to adopt` };
|
|
228
|
+
current[name] = { version: info.version, early: true, ...(info.channel === "experimental" ? { channel: "experimental" } : {}) };
|
|
229
|
+
cfg.packages = Object.fromEntries(Object.entries(current).sort(([a], [b]) => a.localeCompare(b)));
|
|
230
|
+
writeConfig(dir, cfg);
|
|
231
|
+
record(dir, { entity: CONFIG, activity: "write", agent: agentFromEnv(process.env.MENTURA_ACTOR === "agent" ? "agent" : "person", "cli"), note: `adopted the ${name} package early (U-5)` });
|
|
232
|
+
return { changed: true, name, version: info.version, channel: info.channel, fit: fit?.fit ?? null };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Undoes an early adoption: the pin goes, and the package fits by its phase prior again. */
|
|
236
|
+
export function drop(dir, name) {
|
|
237
|
+
const cfg = readConfig(dir);
|
|
238
|
+
const current = pins(cfg);
|
|
239
|
+
if (!current[name]?.early) throw new Error(`${name} was not adopted early, so there is nothing to drop`);
|
|
240
|
+
delete current[name];
|
|
241
|
+
cfg.packages = current;
|
|
242
|
+
writeConfig(dir, cfg);
|
|
243
|
+
record(dir, { entity: CONFIG, activity: "write", agent: agentFromEnv(process.env.MENTURA_ACTOR === "agent" ? "agent" : "person", "cli"), note: `dropped the early adoption of ${name} (U-5)` });
|
|
244
|
+
return { changed: true, name };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Updates the installed framework (U-1): a fast-forward pull of the git checkout Naive runs from. A
|
|
249
|
+
* checkout with local changes, or one that cannot fast-forward, is left as it is.
|
|
250
|
+
*/
|
|
251
|
+
export function updateFramework(root = repo) {
|
|
252
|
+
const git = (...a) => spawnSync("git", ["-C", root, ...a], { encoding: "utf8" });
|
|
253
|
+
if (git("rev-parse", "--is-inside-work-tree").stdout.trim() !== "true") return { ok: false, error: `${root} is not a git checkout: install Naive again from its repository` };
|
|
254
|
+
if (git("status", "--porcelain", "--untracked-files=no").stdout.trim()) return { ok: false, error: `${root} has local changes: commit or stash them, then update` };
|
|
255
|
+
const from = registry(root).framework, head = git("rev-parse", "HEAD").stdout.trim();
|
|
256
|
+
const pull = git("pull", "--ff-only");
|
|
257
|
+
if (pull.status !== 0) return { ok: false, error: `could not fast-forward ${root}: ${(pull.stderr || pull.stdout).trim()}` };
|
|
258
|
+
const after = git("rev-parse", "HEAD").stdout.trim();
|
|
259
|
+
return { ok: true, from, to: registry(root).framework, commits: after === head ? 0 : Number(git("rev-list", "--count", `${head}..${after}`).stdout.trim()), head: after };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Staged upgrades (U-3). The copy holds everything but .naive/, _private/ (never sent to an agent) and
|
|
263
|
+
// nested ventures, which upgrade on their own.
|
|
264
|
+
export function snapshot(dir, { skipPrivate = true } = {}) {
|
|
265
|
+
const out = {};
|
|
266
|
+
const walk = (rel) => {
|
|
267
|
+
for (const e of readdirSync(join(dir, rel), { withFileTypes: true })) {
|
|
268
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
269
|
+
if (!rel && (r === ".naive" || r === ".git" || (skipPrivate && r === "_private"))) continue;
|
|
270
|
+
if (e.isDirectory()) { if (!existsSync(join(dir, r, "venture.json"))) walk(r); }
|
|
271
|
+
else if (e.isFile()) out[r] = sha(readFileSync(join(dir, r)));
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
walk("");
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const writeDraft = (dir, d) => { mkdirSync(join(dir, STAGE), { recursive: true }); writeFileSync(join(dir, DRAFT), JSON.stringify(d, null, 2) + "\n"); return d; };
|
|
279
|
+
/** Adds to the staged upgrade's draft record (what an agent did, say). */
|
|
280
|
+
export function annotate(dir, extra) {
|
|
281
|
+
const d = draft(dir);
|
|
282
|
+
if (!d) throw new Error("no upgrade is staged");
|
|
283
|
+
return writeDraft(dir, { ...d, ...extra });
|
|
284
|
+
}
|
|
285
|
+
/** The staged upgrade's draft record, or null. */
|
|
286
|
+
export const draft = (dir) => (existsSync(join(dir, DRAFT)) ? load(join(dir, DRAFT)) : null);
|
|
287
|
+
|
|
288
|
+
/** Copies the venture to .naive/upgrade/venture and upgrades the copy. Nothing in the venture changes. */
|
|
289
|
+
export async function stage(dir, { reg = registry(), by = "naive" } = {}) {
|
|
290
|
+
if (existsSync(join(dir, STAGE))) throw new Error(`an upgrade is already staged in ${STAGE}: accept it (--accept --confirm ${CONFIRM}) or reject it (--reject)`);
|
|
291
|
+
const p = plan(dir, { reg });
|
|
292
|
+
if (!p.pending) throw new Error(`${p.venture} is already on Naive ${p.to} with every package pinned: nothing to stage`);
|
|
293
|
+
const base = snapshot(dir);
|
|
294
|
+
const out = join(dir, STAGED);
|
|
295
|
+
for (const f of Object.keys(base)) { mkdirSync(dirname(join(out, f)), { recursive: true }); copyFileSync(join(dir, f), join(out, f)); }
|
|
296
|
+
const result = await upgrade(out, { reg, git: false });
|
|
297
|
+
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
|
+
|
|
300
|
+
// The checks a staged upgrade must pass before it lands: the venture-wide checkers, every routed JSON
|
|
301
|
+
// record against its schema, and nothing left to upgrade.
|
|
302
|
+
export const CHECKS = ["schema/check-ventures.mjs", "schema/check-provenance.mjs", "schema/decisions.mjs", "schema/check-data-packages.mjs", "schema/check-private.mjs", "schema/check-sources.mjs", "schema/check-where-used.mjs", "schema/check-governance.mjs"];
|
|
303
|
+
|
|
304
|
+
/** Runs the checks on any venture folder: [{ check, passed, output }]. */
|
|
305
|
+
export function runChecks(vdir, { reg = registry() } = {}) {
|
|
306
|
+
const out = CHECKS.map((c) => { const r = spawnSync("node", [join(repo, c), vdir], { encoding: "utf8" }); return { check: c, passed: r.status === 0, output: (r.stdout + r.stderr).trim().slice(-1500) }; });
|
|
307
|
+
const files = Object.keys(snapshot(vdir, { skipPrivate: true })).filter((f) => f.endsWith(".json"));
|
|
308
|
+
const bad = [];
|
|
309
|
+
let n = 0;
|
|
310
|
+
for (const r of routes().filter((x) => x.kind === "record" && x.schema && (x.form ?? "json") === "json")) {
|
|
311
|
+
for (const f of files.filter((f) => r.files.some((g) => glob(g).test(f)))) {
|
|
312
|
+
n++;
|
|
313
|
+
const v = spawnSync("python3", [join(repo, "schema/validate.py"), "--schema", r.schema.replace(/^schema\//, ""), "--file", join(vdir, f)], { encoding: "utf8" });
|
|
314
|
+
if (v.status !== 0) bad.push(`${f} does not match ${r.schema}: ${(v.stdout + v.stderr).trim().split("\n")[0]}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
out.push({ check: "records match their schemas", passed: !bad.length, output: bad.length ? bad.join("\n") : `${n} records valid` });
|
|
318
|
+
let left;
|
|
319
|
+
try { left = plan(vdir, { reg }); } catch (e) { left = { pending: true, error: e.message }; }
|
|
320
|
+
out.push({ check: "nothing left to upgrade", passed: !left.pending, output: left.pending ? (left.error ?? `still on ${left.from}, or a package is not pinned to ${left.to}`) : `on Naive ${left.to}` });
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** What the staged copy changes: files added, changed and removed (the venture's _private/ is never removed). */
|
|
325
|
+
export function changes(dir) {
|
|
326
|
+
const live = snapshot(dir), staged = snapshot(join(dir, STAGED), { skipPrivate: false });
|
|
327
|
+
return {
|
|
328
|
+
added: Object.keys(staged).filter((f) => !(f in live) && !existsSync(join(dir, f))).sort(),
|
|
329
|
+
changed: Object.keys(staged).filter((f) => (f in live && live[f] !== staged[f]) || (!(f in live) && existsSync(join(dir, f)) && sha(readFileSync(join(dir, f))) !== staged[f])).sort(),
|
|
330
|
+
removed: Object.keys(live).filter((f) => !(f in staged)).sort(),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Runs the checks on the staged copy and records the result: ready, or checks-failed. */
|
|
335
|
+
export function check(dir, { reg = registry() } = {}) {
|
|
336
|
+
const d = draft(dir);
|
|
337
|
+
if (!d) throw new Error("no upgrade is staged: stage one with --stage or --agent");
|
|
338
|
+
const checks = runChecks(join(dir, STAGED), { reg });
|
|
339
|
+
const ok = checks.every((c) => c.passed);
|
|
340
|
+
return writeDraft(dir, { ...d, status: ok ? "ready" : "checks-failed", checked_at: now(), checks, changes: changes(dir) });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Lands a staged upgrade (U-3): only with the typed confirmation, only when the checks pass again now,
|
|
345
|
+
* and only when the venture has not changed since the copy was made. Its provenance log continues the
|
|
346
|
+
* venture's, so the chain holds; the person's acceptance is the last line.
|
|
347
|
+
*/
|
|
348
|
+
export async function accept(dir, { confirm, by, reg = registry() } = {}) {
|
|
349
|
+
const d = draft(dir);
|
|
350
|
+
if (!d) throw new Error("no upgrade is staged: stage one with --stage or --agent");
|
|
351
|
+
if (confirm !== CONFIRM) throw new Error(`not accepted: type the confirmation (--confirm ${CONFIRM})`);
|
|
352
|
+
const live = snapshot(dir);
|
|
353
|
+
const drift = [...new Set([...Object.keys(live), ...Object.keys(d.base)])].filter((f) => live[f] !== d.base[f]);
|
|
354
|
+
if (drift.length) throw new Error(`the venture changed since the upgrade was staged (${drift.slice(0, 5).join(", ")}): reject it and stage again`);
|
|
355
|
+
const checked = check(dir, { reg });
|
|
356
|
+
if (checked.status !== "ready") throw new Error(`not accepted: the checks fail on the staged copy:\n${checked.checks.filter((c) => !c.passed).map((c) => ` ${c.check}: ${c.output.split("\n").slice(-3).join(" / ")}`).join("\n")}`);
|
|
357
|
+
const ch = checked.changes;
|
|
358
|
+
const clash = ch.changed.filter((f) => f.startsWith("_private/"));
|
|
359
|
+
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 = join(dir, STAGED);
|
|
361
|
+
for (const f of [...ch.added, ...ch.changed]) { mkdirSync(dirname(join(dir, f)), { recursive: true }); copyFileSync(join(src, f), join(dir, f)); }
|
|
362
|
+
for (const f of ch.removed) rmSync(join(dir, f));
|
|
363
|
+
// Folders the upgrade emptied go too, unless the copy still has them.
|
|
364
|
+
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(join(dir, x)) && !existsSync(join(src, x)) && readdirSync(join(dir, x)).length === 0) rmSync(join(dir, x), { recursive: true });
|
|
366
|
+
rmSync(join(dir, STAGE), { recursive: true, force: true });
|
|
367
|
+
return landed(dir, d, ch, by);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function refreshAdapters(dir) {
|
|
371
|
+
try { const { refresh } = await import("../interface/adapters.mjs"); return refresh(dir); } catch (e) { return { error: e.message }; }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function landed(dir, d, ch, by) {
|
|
375
|
+
const rel = `.release/upgrade-${d.to}.json`;
|
|
376
|
+
const e = existsSync(join(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
|
+
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
|
+
|
|
380
|
+
/** Discards a staged upgrade. The venture never changed, so nothing is logged. */
|
|
381
|
+
export function reject(dir) {
|
|
382
|
+
if (!existsSync(join(dir, STAGE))) throw new Error("no upgrade is staged");
|
|
383
|
+
rmSync(join(dir, STAGE), { recursive: true, force: true });
|
|
384
|
+
return { rejected: true };
|
|
385
|
+
}
|
package/schema/validate.py
CHANGED
|
@@ -33,17 +33,26 @@ if "--file" in sys.argv:
|
|
|
33
33
|
errs = [f"{'/'.join(map(str, e.absolute_path)) or '(root)'}: {e.message}" for e in v.iter_errors(one)]
|
|
34
34
|
print("\n".join(errs) if errs else "valid")
|
|
35
35
|
sys.exit(1 if errs else 0)
|
|
36
|
+
# A file matched by two record types' globs is validated once, by the more specific glob (the one with
|
|
37
|
+
# more literal characters): .../departments/products/department.json is a department, not a product.
|
|
38
|
+
best = {}
|
|
36
39
|
for name, schema, form, globs in RULES:
|
|
37
|
-
v = jsonschema.Draft202012Validator(schemas[Path(schema).name], registry=registry, format_checker=jsonschema.FormatChecker())
|
|
38
40
|
for g in globs:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
weight = len(g.replace("**/", "").replace("*", ""))
|
|
42
|
+
for f in glob.glob(str(root / g), recursive=True):
|
|
43
|
+
if f not in best or weight > best[f][0]:
|
|
44
|
+
best[f] = (weight, schema, form)
|
|
45
|
+
validators = {}
|
|
46
|
+
for f in sorted(best):
|
|
47
|
+
_, schema, form = best[f]
|
|
48
|
+
v = validators.setdefault(schema, jsonschema.Draft202012Validator(schemas[Path(schema).name], registry=registry, format_checker=jsonschema.FormatChecker()))
|
|
49
|
+
text = Path(f).read_text()
|
|
50
|
+
docs = [json.loads(l) for l in text.splitlines() if l.strip()] if form == "json-lines" else json.loads(text)
|
|
51
|
+
docs = docs if form != "json" else [docs]
|
|
52
|
+
for d in docs:
|
|
53
|
+
checked += 1
|
|
54
|
+
for e in v.iter_errors(d):
|
|
55
|
+
failed += 1
|
|
56
|
+
print(f"{Path(f).relative_to(root)}: {'/'.join(map(str, e.absolute_path)) or '(root)'}: {e.message}")
|
|
48
57
|
print(f"{checked} records checked against their schemas: {'all valid' if not failed else f'{failed} error(s)'}.")
|
|
49
58
|
sys.exit(1 if failed else 0)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "venture-role.schema.json",
|
|
4
|
+
"title": "Venture roles",
|
|
5
|
+
"description": "The roles a person can hold in a venture (governance/venture-template/roles/venture-roles.json), as distinct from the agent roles beside it. create-venture asks for one; people/people.json records who holds which.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"version",
|
|
9
|
+
"roles"
|
|
10
|
+
],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"version": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"note": {
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"roles": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": [
|
|
24
|
+
"id",
|
|
25
|
+
"name",
|
|
26
|
+
"kind",
|
|
27
|
+
"description",
|
|
28
|
+
"founder",
|
|
29
|
+
"owns_when_solo"
|
|
30
|
+
],
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"pattern": "^[a-z0-9-]+$"
|
|
36
|
+
},
|
|
37
|
+
"name": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"kind": {
|
|
41
|
+
"enum": [
|
|
42
|
+
"builder",
|
|
43
|
+
"investor",
|
|
44
|
+
"operator",
|
|
45
|
+
"advisor",
|
|
46
|
+
"governor"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"description": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
52
|
+
"founder": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"description": "Whether the holder is a founder of this venture, with a file in people/founders/ and a place in the founder agreement"
|
|
55
|
+
},
|
|
56
|
+
"owns_when_solo": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"description": "Whether a person alone in this role owns every function the venture imports"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Venture types (EE-082): the registry, and a classifier that reads a founder's few words and says
|
|
2
|
+
// which type the venture is. The classifier asks Jev one yes-or-no question per type (Noul) and ranks
|
|
3
|
+
// the answers; below the threshold it is uncertain, and the founder picks from the list instead. Without
|
|
4
|
+
// TYPESAFE_API_KEY there is no model call at all: the list is the way to choose.
|
|
5
|
+
//
|
|
6
|
+
// node schema/venture-type.mjs list
|
|
7
|
+
// node schema/venture-type.mjs classify "<a few words about the venture>"
|
|
8
|
+
//
|
|
9
|
+
// MENTURA_TYPE_CMD replaces the call to platform/integrations/jev.mjs (it reads the request on stdin and
|
|
10
|
+
// prints {answers}), for tests.
|
|
11
|
+
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const repo = new URL("..", import.meta.url).pathname;
|
|
18
|
+
export const THRESHOLD = 0.5;
|
|
19
|
+
|
|
20
|
+
export function types() {
|
|
21
|
+
return JSON.parse(readFileSync(join(repo, "schema/venture-types.json"), "utf8")).types;
|
|
22
|
+
}
|
|
23
|
+
export const typeOf = (id) => types().find((t) => t.id === id) ?? null;
|
|
24
|
+
|
|
25
|
+
export function roles() {
|
|
26
|
+
return JSON.parse(readFileSync(join(repo, "governance/venture-template/roles/venture-roles.json"), "utf8")).roles;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Whether a model can classify: a TypeSafe key, or a stand-in command. */
|
|
30
|
+
export const canClassify = () => Boolean(process.env.MENTURA_TYPE_CMD || process.env.TYPESAFE_API_KEY || process.env.TYPESAFEAI_API_KEY);
|
|
31
|
+
|
|
32
|
+
/** The typed request: for each type, is the venture described this kind of venture? */
|
|
33
|
+
export function request(description) {
|
|
34
|
+
const ts = types();
|
|
35
|
+
const questions = Object.fromEntries(ts.map((t) => [`type:${t.id}`, {
|
|
36
|
+
type: "noul",
|
|
37
|
+
instructions: `Is the venture described in \`venture.description\` a ${t.label.toLowerCase()} (${t.description} Founders call it things like: ${t.examples.join(", ")})?`,
|
|
38
|
+
criteria: { true: `Yes: it is mainly a ${t.label.toLowerCase()}`, false: "No: it is mainly something else" },
|
|
39
|
+
}]));
|
|
40
|
+
return { state: { venture: { description } }, model: "jev-latest", questions };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Ranked types for a description, most likely first, with uncertain set when none reaches the threshold. */
|
|
44
|
+
export function classify(description) {
|
|
45
|
+
const cmd = process.env.MENTURA_TYPE_CMD ? process.env.MENTURA_TYPE_CMD.split(" ") : ["node", join(repo, "platform/integrations/jev.mjs")];
|
|
46
|
+
const p = spawnSync(cmd[0], cmd.slice(1), { input: JSON.stringify(request(description)), encoding: "utf8", env: process.env });
|
|
47
|
+
if (p.status !== 0) throw new Error(`the type classifier stopped: ${(p.stderr || p.stdout).trim().slice(0, 300)}`);
|
|
48
|
+
const { answers = {}, model = null } = JSON.parse(p.stdout);
|
|
49
|
+
const ranked = types().map((t) => {
|
|
50
|
+
const a = answers[`type:${t.id}`]?.noul;
|
|
51
|
+
return { id: t.id, label: t.label, p: typeof a === "number" ? a : a === true ? 1 : 0 };
|
|
52
|
+
}).sort((a, b) => b.p - a.p);
|
|
53
|
+
return { model, ranked, best: ranked[0].p >= THRESHOLD ? ranked[0] : null, uncertain: ranked[0].p < THRESHOLD };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
57
|
+
const [cmd, ...words] = process.argv.slice(2);
|
|
58
|
+
if (cmd === "list") for (const t of types()) console.log(`${t.id.padEnd(18)} ${t.label}: ${t.description}`);
|
|
59
|
+
else if (cmd === "classify" && words.length) {
|
|
60
|
+
try { console.log(JSON.stringify(classify(words.join(" ")), null, 2)); } catch (e) { console.error(e.message); process.exit(1); }
|
|
61
|
+
} else { console.error('usage: node schema/venture-type.mjs list | classify "<a few words>"'); process.exit(2); }
|
|
62
|
+
}
|