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,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Adapters (Z-4). Next.js adapters let a platform customize build and deploy, and a verified adapter
|
|
3
|
+
// passes the Next.js test suite. Naive's adapters run the framework on a platform or in an agent
|
|
4
|
+
// harness. Mentura is the first, as Vercel is for Next.js (platform/adapter.json). Claude Code, Codex and
|
|
5
|
+
// Cursor adapt the framework through the venture's AGENTS.md and skills (interface/adapters/*.json):
|
|
6
|
+
// adapt() writes the entry files a harness reads and copies the skills of the packages open now. An
|
|
7
|
+
// adapter is verified when it passes the framework's tests: verify() runs them.
|
|
8
|
+
//
|
|
9
|
+
// node interface/adapters.mjs [--json] the adapters
|
|
10
|
+
// node interface/adapters.mjs adapt <adapter> <venture dir> write a harness's files into a venture
|
|
11
|
+
// node interface/adapters.mjs verify [<adapter>] [--json] run the framework's tests an adapter must pass
|
|
12
|
+
//
|
|
13
|
+
// adapt() records what it wrote in <venture>/.naive/adapters.json and never overwrites a file it did not
|
|
14
|
+
// write, or one a person changed since: it keeps theirs and says so. An upgrade refreshes the adapters
|
|
15
|
+
// a venture has (schema/upgrades.mjs), so the skill copies follow the installed packages.
|
|
16
|
+
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
|
|
23
|
+
const repo = new URL("..", import.meta.url).pathname;
|
|
24
|
+
export const STATE = ".naive/adapters.json";
|
|
25
|
+
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
26
|
+
const sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
27
|
+
|
|
28
|
+
/** Every adapter: the harness adapters in interface/adapters/, and the platform's if this copy has one. */
|
|
29
|
+
export function adapters() {
|
|
30
|
+
const d = join(repo, "interface/adapters");
|
|
31
|
+
const out = existsSync(d) ? readdirSync(d).filter((f) => f.endsWith(".json")).sort().map((f) => ({ ...load(join(d, f)), path: `interface/adapters/${f}` })) : [];
|
|
32
|
+
if (existsSync(join(repo, "platform/adapter.json"))) out.unshift({ ...load(join(repo, "platform/adapter.json")), path: "platform/adapter.json" });
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
const byName = (name) => { const a = adapters().find((x) => x.name === name); if (!a) throw new Error(`no adapter ${name}: ${adapters().map((x) => x.name).join(", ")}`); return a; };
|
|
36
|
+
|
|
37
|
+
/** The skills of the packages open now in a venture: [{ id, path, package }]. */
|
|
38
|
+
export async function skillsFor(dir) {
|
|
39
|
+
const { packages } = await import("../schema/packages.mjs");
|
|
40
|
+
const p = packages(dir);
|
|
41
|
+
return [...p.core, ...p.handoff].filter((x) => x.in === "now").flatMap((x) => {
|
|
42
|
+
const f = join(repo, "packages", x.id, "package.json");
|
|
43
|
+
const s = existsSync(f) ? load(f).skill : null;
|
|
44
|
+
return s && existsSync(join(repo, s.path)) ? [{ id: s.id, path: s.path, package: x.id }] : [];
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const readState = (dir) => (existsSync(join(dir, STATE)) ? load(join(dir, STATE)) : { adapters: {} });
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Writes a harness adapter's files into a venture: its entry files, and a copy of each open package's
|
|
52
|
+
* skill in its skills folder. Returns what it wrote, what was already current, and what it kept.
|
|
53
|
+
*/
|
|
54
|
+
export async function adapt(dir, name) {
|
|
55
|
+
if (!existsSync(join(dir, "venture.json"))) throw new Error(`${dir} is not a venture`);
|
|
56
|
+
const a = byName(name);
|
|
57
|
+
if (a.kind !== "harness") throw new Error(`${name} is a ${a.kind} adapter: it runs the framework itself and writes nothing into a venture`);
|
|
58
|
+
const state = readState(dir);
|
|
59
|
+
const mine = state.adapters[name]?.files ?? {};
|
|
60
|
+
const want = [...a.files.map((f) => ({ path: f.path, text: f.text }))];
|
|
61
|
+
if (a.skills) for (const s of await skillsFor(dir)) want.push({ path: `${a.skills}/${s.id}/SKILL.md`, text: readFileSync(join(repo, s.path), "utf8"), skill: s.id, package: s.package });
|
|
62
|
+
const written = [], current = [], kept = [], files = {};
|
|
63
|
+
for (const w of want) {
|
|
64
|
+
const f = join(dir, w.path);
|
|
65
|
+
const now = existsSync(f) ? readFileSync(f, "utf8") : null;
|
|
66
|
+
if (now === w.text) { current.push(w.path); files[w.path] = sha(w.text); continue; }
|
|
67
|
+
// Never overwrite a file a person wrote, or one they changed after the adapter wrote it.
|
|
68
|
+
if (now !== null && mine[w.path] !== sha(now)) { kept.push({ path: w.path, why: mine[w.path] ? "changed since the adapter wrote it" : "it was there before the adapter" }); if (mine[w.path]) files[w.path] = mine[w.path]; continue; }
|
|
69
|
+
mkdirSync(dirname(f), { recursive: true });
|
|
70
|
+
writeFileSync(f, w.text);
|
|
71
|
+
written.push(w.path);
|
|
72
|
+
files[w.path] = sha(w.text);
|
|
73
|
+
}
|
|
74
|
+
// A skill copy for a package that is no longer open goes, if the adapter wrote it and nobody changed it.
|
|
75
|
+
const removed = [];
|
|
76
|
+
for (const [p, h] of Object.entries(mine)) {
|
|
77
|
+
if (want.some((w) => w.path === p) || !existsSync(join(dir, p))) continue;
|
|
78
|
+
if (sha(readFileSync(join(dir, p), "utf8")) === h) { rmSync(join(dir, p)); removed.push(p); } else kept.push({ path: p, why: "no longer needed, but changed by a person, so kept" });
|
|
79
|
+
}
|
|
80
|
+
state.adapters[name] = { at: new Date().toISOString(), files };
|
|
81
|
+
mkdirSync(join(dir, ".naive"), { recursive: true });
|
|
82
|
+
writeFileSync(join(dir, STATE), JSON.stringify(state, null, 2) + "\n");
|
|
83
|
+
return { adapter: name, written, current, kept, removed, skills: want.filter((w) => w.skill).map((w) => w.skill) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Re-adapts every adapter the venture has, after an upgrade. */
|
|
87
|
+
export async function refresh(dir) {
|
|
88
|
+
const names = Object.keys(readState(dir).adapters);
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const n of names) { try { out.push(await adapt(dir, n)); } catch (e) { out.push({ adapter: n, error: e.message }); } }
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The framework's tests for a harness adapter, run on a new venture in a temporary folder.
|
|
95
|
+
async function harnessSuite(a) {
|
|
96
|
+
const results = [];
|
|
97
|
+
const t = (test, passed, detail = "") => results.push({ test, passed: !!passed, detail });
|
|
98
|
+
const ws = mkdtempSync(join(tmpdir(), `naive-adapter-${a.name}-`));
|
|
99
|
+
try {
|
|
100
|
+
const dir = join(ws, "adapted");
|
|
101
|
+
const init = spawnSync("node", [join(repo, "schema/init-venture.mjs"), dir, "adapted"], { encoding: "utf8" });
|
|
102
|
+
const up = spawnSync("node", [join(repo, "schema/upgrade-venture.mjs"), dir], { encoding: "utf8" });
|
|
103
|
+
t("a new venture is created and upgraded to the installed framework", init.status === 0 && up.status === 0, init.stderr + up.stderr);
|
|
104
|
+
const first = await adapt(dir, a.name);
|
|
105
|
+
t("every entry file is written with the adapter's text", a.files.every((f) => existsSync(join(dir, f.path)) && readFileSync(join(dir, f.path), "utf8") === f.text), JSON.stringify(first.written));
|
|
106
|
+
t("the harness reaches the venture's AGENTS.md (it reads it, or an entry file imports or names it)", a.reads.includes("AGENTS.md") || a.files.some((f) => /(^|\s)@AGENTS\.md\b|AGENTS\.md/.test(f.text)));
|
|
107
|
+
t("no entry file is private or outside the venture", a.files.every((f) => !f.path.startsWith("_private/") && !f.path.includes("..") && !f.path.startsWith("/")));
|
|
108
|
+
const { call } = await import("./router.mjs");
|
|
109
|
+
const lay = await call({ venture: "adapted", workspace: ws, route: "foundation/problem", action: "layout" });
|
|
110
|
+
t("the layout an agent gets starts with the venture's AGENTS.md and its instructions", lay.ok && lay.result.chain[0]?.path === "AGENTS.md" && lay.result.instructions.private === "_private/", lay.error ?? "");
|
|
111
|
+
if (a.skills) {
|
|
112
|
+
const want = await skillsFor(dir);
|
|
113
|
+
t("the venture has packages open now with skills to copy", want.length > 0, "no open package has a skill");
|
|
114
|
+
t("each open package's skill is copied byte for byte where the harness loads skills", want.every((s) => existsSync(join(dir, a.skills, s.id, "SKILL.md")) && readFileSync(join(dir, a.skills, s.id, "SKILL.md"), "utf8") === readFileSync(join(repo, s.path), "utf8")), JSON.stringify(want.map((s) => s.id)));
|
|
115
|
+
rmSync(join(dir, a.skills, want[0].id, "SKILL.md"));
|
|
116
|
+
const back = await refresh(dir);
|
|
117
|
+
t("a refresh (as after an upgrade) puts a missing skill copy back", back[0]?.written?.includes(`${a.skills}/${want[0].id}/SKILL.md`), JSON.stringify(back));
|
|
118
|
+
}
|
|
119
|
+
const again = await adapt(dir, a.name);
|
|
120
|
+
t("adapting again changes nothing", again.written.length === 0 && again.kept.length === 0, JSON.stringify(again));
|
|
121
|
+
if (a.files.length) {
|
|
122
|
+
const f = join(dir, a.files[0].path);
|
|
123
|
+
writeFileSync(f, readFileSync(f, "utf8") + "\nThe founder's own line.\n");
|
|
124
|
+
const edited = readFileSync(f, "utf8");
|
|
125
|
+
const kept = await adapt(dir, a.name);
|
|
126
|
+
t("a file the founder changed is kept, never overwritten", kept.kept.some((k) => k.path === a.files[0].path) && readFileSync(f, "utf8") === edited, JSON.stringify(kept));
|
|
127
|
+
const other = join(ws, "own");
|
|
128
|
+
spawnSync("node", [join(repo, "schema/init-venture.mjs"), other, "own"], { encoding: "utf8" });
|
|
129
|
+
mkdirSync(dirname(join(other, a.files[0].path)), { recursive: true });
|
|
130
|
+
writeFileSync(join(other, a.files[0].path), "the founder's own file\n");
|
|
131
|
+
const theirs = await adapt(other, a.name);
|
|
132
|
+
t("a file that was there before the adapter is kept", theirs.kept.some((k) => k.path === a.files[0].path) && readFileSync(join(other, a.files[0].path), "utf8") === "the founder's own file\n", JSON.stringify(theirs));
|
|
133
|
+
}
|
|
134
|
+
} catch (e) { t("the suite ran to the end", false, String(e.stack ?? e)); }
|
|
135
|
+
finally { rmSync(ws, { recursive: true, force: true }); }
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A platform adapter runs the framework itself; it is verified by the test programs it names.
|
|
140
|
+
function platformSuite(a) {
|
|
141
|
+
if (!a.verify?.length) return [{ test: "names the framework tests it must pass", passed: false, detail: "verify is empty" }];
|
|
142
|
+
return a.verify.map((p) => {
|
|
143
|
+
if (!existsSync(join(repo, p))) return { test: p, passed: false, detail: "not found" };
|
|
144
|
+
const r = spawnSync("node", [join(repo, p)], { cwd: repo, encoding: "utf8", timeout: 600000 });
|
|
145
|
+
const tail = (r.stdout + r.stderr).trim().split("\n").slice(-4).join(" / ");
|
|
146
|
+
return { test: p, passed: r.status === 0, detail: tail };
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Runs the framework's tests for one adapter: { adapter, kind, verified, results }. */
|
|
151
|
+
export async function verify(name) {
|
|
152
|
+
const a = byName(name);
|
|
153
|
+
const results = a.kind === "harness" ? await harnessSuite(a) : platformSuite(a);
|
|
154
|
+
return { adapter: a.name, kind: a.kind, verified: results.every((r) => r.passed), results };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
158
|
+
const [cmd, ...rest] = process.argv.slice(2).filter((x) => x !== "--json");
|
|
159
|
+
const json = process.argv.includes("--json");
|
|
160
|
+
try {
|
|
161
|
+
if (!cmd) {
|
|
162
|
+
const list = adapters().map((a) => ({ name: a.name, title: a.title, kind: a.kind, path: a.path, reads: a.reads }));
|
|
163
|
+
console.log(json ? JSON.stringify(list, null, 2) : list.map((a) => `${a.name.padEnd(12)} ${a.kind.padEnd(9)} ${a.path} reads ${a.reads.join(", ")}`).join("\n"));
|
|
164
|
+
} else if (cmd === "adapt") {
|
|
165
|
+
const [name, dir] = rest;
|
|
166
|
+
if (!name || !dir) throw new Error("usage: node interface/adapters.mjs adapt <adapter> <venture dir>");
|
|
167
|
+
const r = await adapt(dir, name);
|
|
168
|
+
console.log(json ? JSON.stringify(r, null, 2) : [`adapted for ${r.adapter}: ${r.written.length} written, ${r.current.length} already current, ${r.kept.length} kept${r.skills.length ? `; skills: ${r.skills.join(", ")}` : ""}`,
|
|
169
|
+
...r.written.map((p) => `wrote ${p}`), ...r.kept.map((k) => `kept ${k.path} (${k.why})`), ...r.removed.map((p) => `removed ${p}`)].join("\n"));
|
|
170
|
+
} else if (cmd === "verify") {
|
|
171
|
+
const names = rest.length ? rest : adapters().map((a) => a.name);
|
|
172
|
+
const rs = [];
|
|
173
|
+
for (const n of names) rs.push(await verify(n));
|
|
174
|
+
if (json) console.log(JSON.stringify(rs, null, 2));
|
|
175
|
+
else for (const r of rs) {
|
|
176
|
+
console.log(`${r.verified ? "verified" : "NOT verified"} ${r.adapter} (${r.kind})`);
|
|
177
|
+
for (const x of r.results) console.log(` ${x.passed ? "pass" : "FAIL"} ${x.test}${x.passed ? "" : ` ${x.detail}`}`);
|
|
178
|
+
}
|
|
179
|
+
process.exit(rs.every((r) => r.verified) ? 0 : 1);
|
|
180
|
+
} else throw new Error("usage: node interface/adapters.mjs [adapt <adapter> <venture dir> | verify [<adapter>]] [--json]");
|
|
181
|
+
} catch (e) { console.error(e.message); process.exit(1); }
|
|
182
|
+
}
|
package/interface/api/server.mjs
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
// GET /ventures/<venture>/<route>/layout the layout an agent gets before acting
|
|
15
15
|
// PUT /ventures/<venture>/<route> write a record (JSON body); provenance from the
|
|
16
16
|
// headers x-mentura-derived-from (comma-separated), x-mentura-decision, x-mentura-model;
|
|
17
|
-
// the role acting from x-mentura-actor (checked against the team plan's routes)
|
|
17
|
+
// the role acting from x-mentura-actor (checked against the team plan's routes); a confidence
|
|
18
|
+
// score computed before the write from x-mentura-confidence
|
|
18
19
|
// POST /ventures/<venture>/<route>/check run its checks
|
|
19
20
|
// POST /ventures/<venture>/<route>/run run a command route (optional JSON body {value})
|
|
20
21
|
//
|
|
@@ -47,7 +48,8 @@ export async function route(method, url, body, headers = {}) {
|
|
|
47
48
|
if (!action || (verb && VERBS[verb] !== method)) return [405, { ok: false, error: `${method} is not allowed on ${path}` }];
|
|
48
49
|
const h = (k) => headers[k] || undefined;
|
|
49
50
|
const r = await call({ venture, route: rt, action, input: body, actor: "agent", actor_id: h("x-mentura-actor"), via: "api",
|
|
50
|
-
|
|
51
|
+
// Gate group hook (X-4): x-mentura-confidence carries a score computed before the write, for the gate's confidence rules.
|
|
52
|
+
provenance: { derived_from: h("x-mentura-derived-from")?.split(",").map((x) => x.trim()).filter(Boolean), decision: h("x-mentura-decision"), confidence: h("x-mentura-confidence"),
|
|
51
53
|
agent: Object.fromEntries(Object.entries({ model: h("x-mentura-model"), harness: h("user-agent") }).filter(([, v]) => v)) } });
|
|
52
54
|
return [status(r), r];
|
|
53
55
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Error boundaries (Next.js translation P-15, E-5, E-6, E-8). Expected errors stay answers, never throws.
|
|
3
|
+
//
|
|
4
|
+
// global-error a broken venture.json, or a broken framework file (the data model, a layer or package
|
|
5
|
+
// manifest), answers with what is wrong and the recovery steps, instead of a stack trace.
|
|
6
|
+
// One broken venture never stops the router from finding the others.
|
|
7
|
+
// boundary a failing check in one package is contained there: the other packages proceed, and
|
|
8
|
+
// only a venture-level failure (venture.json, the folder structure) reaches the venture.
|
|
9
|
+
// per record one bad record (a file that does not parse or does not match its schema) does not fail
|
|
10
|
+
// its package's read or check: the others come back, and the bad one is named with its fix.
|
|
11
|
+
//
|
|
12
|
+
// node interface/boundaries.mjs check <venture dir> [--package <name>] [--json] the boundaries, exit 1 on a failure
|
|
13
|
+
// node interface/boundaries.mjs recover <venture dir> --confirm recover rebuild a broken venture.json
|
|
14
|
+
//
|
|
15
|
+
// The router calls these through interface/writes.mjs; `mentura venture check` and `mentura <package> check`
|
|
16
|
+
// run them. Never calls a model.
|
|
17
|
+
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { basename, dirname, join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const repo = new URL("..", import.meta.url).pathname;
|
|
23
|
+
const load = (f) => JSON.parse(readFileSync(f, "utf8"));
|
|
24
|
+
|
|
25
|
+
// Where a JSON parse failed: line and column from the position in the message, when it gives one.
|
|
26
|
+
function where(text, message) {
|
|
27
|
+
const m = message.match(/position (\d+)/);
|
|
28
|
+
if (!m && /end of JSON input/.test(message)) return ` (at the end of the file, line ${text.split("\n").length})`;
|
|
29
|
+
if (!m || /line \d+/.test(message)) return "";
|
|
30
|
+
const before = text.slice(0, Number(m[1]));
|
|
31
|
+
const line = before.split("\n").length, col = before.length - before.lastIndexOf("\n");
|
|
32
|
+
return ` (line ${line}, column ${col})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Why a JSON file is broken, or null. */
|
|
36
|
+
export function jsonProblem(file) {
|
|
37
|
+
if (!existsSync(file)) return "is missing";
|
|
38
|
+
const text = readFileSync(file, "utf8");
|
|
39
|
+
try { JSON.parse(text); return null; } catch (e) { return `does not parse: ${String(e.message).split("\n")[0]}${where(text, String(e.message))}`; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const VENTURE_SCHEMA = () => load(join(repo, "schema/venture.schema.json"));
|
|
43
|
+
|
|
44
|
+
/** A minimal venture.json for a folder, from the schema's required fields: what recover writes. */
|
|
45
|
+
export function minimalVenture(dir) {
|
|
46
|
+
const id = basename(dir).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "venture";
|
|
47
|
+
return { id, name: id, parent: null, phase: "discovery", owner: "self", status: "active", profile: "profile.md",
|
|
48
|
+
layers: Object.fromEntries(["purpose", "knowledge", "operations", "evidence", "governance", "interface"].map((l) => [l, existsSync(join(dir, l)) ? { location: `${l}/` } : false])) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The venture's global error (E-8, P-15): null when venture.json is sound, else what is wrong and
|
|
53
|
+
* how to recover. Checks what the router needs before anything else runs: it parses, its required
|
|
54
|
+
* fields are there, its id and phase are valid. The full schema check stays in schema/validate.py.
|
|
55
|
+
*/
|
|
56
|
+
export function ventureHealth(dir) {
|
|
57
|
+
const file = join(dir, "venture.json");
|
|
58
|
+
let problem = jsonProblem(file);
|
|
59
|
+
if (!problem) {
|
|
60
|
+
const v = load(file), s = VENTURE_SCHEMA();
|
|
61
|
+
const missing = v && typeof v === "object" ? s.required.filter((k) => !(k in v)) : [];
|
|
62
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) problem = "is not a JSON object";
|
|
63
|
+
else if (missing.length) problem = `is missing ${missing.join(", ")}`;
|
|
64
|
+
else if (!new RegExp(s.properties.id.pattern).test(String(v.id))) problem = `has id ${JSON.stringify(v.id)}, which is not lowercase letters, digits and hyphens`;
|
|
65
|
+
else if (!s.properties.phase.enum.includes(v.phase)) problem = `has phase ${JSON.stringify(v.phase)}, which is not one of ${s.properties.phase.enum.join(", ")}`;
|
|
66
|
+
else if (typeof v.layers !== "object" || v.layers === null) problem = "has layers that are not an object";
|
|
67
|
+
}
|
|
68
|
+
if (!problem) return null;
|
|
69
|
+
const git = spawnSync("git", ["-C", dir, "ls-files", "--error-unmatch", "venture.json"], { encoding: "utf8" }).status === 0;
|
|
70
|
+
return {
|
|
71
|
+
scope: "venture", file, problem: `venture.json ${problem}`,
|
|
72
|
+
recovery: [
|
|
73
|
+
...(git ? [`Restore the last committed version: git -C ${dir} checkout -- venture.json`] : []),
|
|
74
|
+
`Fix it by hand: it must be a JSON object with ${VENTURE_SCHEMA().required.join(", ")}`,
|
|
75
|
+
`Or rebuild a minimal one, keeping the broken file in _private/: node interface/boundaries.mjs recover ${dir} --confirm recover`,
|
|
76
|
+
`Then check it: python3 schema/validate.py --schema venture.schema.json --file ${file}`,
|
|
77
|
+
],
|
|
78
|
+
template: minimalVenture(dir),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The framework files every route depends on. A broken one stops every route, so it is checked first.
|
|
83
|
+
function frameworkFiles() {
|
|
84
|
+
const fixed = ["schema/data-model.json", "schema/need-now.json", "schema/venture.schema.json", "packages/catalog.json", "purpose/frameworks/product-anatomy/phases.json", "purpose/strategic-foundation.json", "operations/departments.json"];
|
|
85
|
+
let layers = [];
|
|
86
|
+
try { layers = load(join(repo, "schema/data-model.json")).layers.map((l) => `${l}/layer.json`); } catch {}
|
|
87
|
+
const pkgs = existsSync(join(repo, "packages")) ? readdirSync(join(repo, "packages")).map((n) => `packages/${n}/package.json`).filter((f) => existsSync(join(repo, f))) : [];
|
|
88
|
+
return [...fixed, ...layers, "schema/layer.json", ...pkgs];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The framework's global error (E-8): null when the data model and manifests parse, else the file, the problem and how to recover. */
|
|
92
|
+
export function frameworkHealth() {
|
|
93
|
+
for (const f of frameworkFiles()) {
|
|
94
|
+
const problem = jsonProblem(join(repo, f));
|
|
95
|
+
if (!problem) continue;
|
|
96
|
+
return { scope: "framework", file: f, problem: `${f} ${problem}`, recovery: [
|
|
97
|
+
`Restore it: git -C ${repo} checkout -- ${f}`,
|
|
98
|
+
"Then check the data model and the manifests: node schema/check-data-model.mjs",
|
|
99
|
+
"No venture record was touched: every route stops until the framework file parses again.",
|
|
100
|
+
] };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The answer a global error gives: an expected error with its recovery steps, never a throw. */
|
|
106
|
+
export function globalError(h, venture) {
|
|
107
|
+
const head = h.scope === "venture" ? `${venture ?? basename(dirname(h.file))}: ${h.problem}` : `the framework is broken: ${h.problem}`;
|
|
108
|
+
return { ok: false, outcome: "error", error: [head, "Recovery:", ...h.recovery.map((s, i) => ` ${i + 1}. ${s}`)].join("\n"), global_error: h };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Per-record containment (E-6): parses each item on its own; a bad one becomes an error beside the good ones. */
|
|
112
|
+
export function contain(items, parse, idOf = (x) => x.path) {
|
|
113
|
+
const values = [], errors = {};
|
|
114
|
+
for (const x of items) {
|
|
115
|
+
try { values.push([x, parse(x)]); } catch (e) { errors[idOf(x.path)] = { file: x.path, error: `${x.path} does not parse: ${String(e.message ?? e).split("\n")[0]}` }; }
|
|
116
|
+
}
|
|
117
|
+
return { values, errors };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateFile(file, schema) {
|
|
121
|
+
const p = spawnSync("python3", [join(repo, "schema/validate.py"), "--schema", schema.replace(/^schema\//, ""), "--file", file], { encoding: "utf8" });
|
|
122
|
+
return p.status === 0 ? null : (p.stdout + p.stderr).trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The boundaries of a venture (E-5): the venture level first (venture.json and its folder structure),
|
|
127
|
+
* then each package the venture uses, each contained. Inside a package each record is checked on its
|
|
128
|
+
* own (E-6), then the package's checks run. options.only limits it to some packages.
|
|
129
|
+
*/
|
|
130
|
+
export async function boundaries(dir, { only = null } = {}) {
|
|
131
|
+
const { structure, checkManifest, manifest } = await import("../schema/structure.mjs");
|
|
132
|
+
const { routes, glob } = await import("./router.mjs");
|
|
133
|
+
const { routable } = await import("../schema/structure.mjs");
|
|
134
|
+
const h = ventureHealth(dir);
|
|
135
|
+
if (h) return { ok: false, reached: "venture", venture: { ok: false, error: h.problem, recovery: h.recovery }, packages: [], proceeds: [] };
|
|
136
|
+
const s = structure(dir);
|
|
137
|
+
const verrors = [...s.errors, ...checkManifest(dir)];
|
|
138
|
+
const venture = { ok: !verrors.length, ...(verrors.length ? { errors: verrors, fix: "node schema/structure.mjs " + dir } : {}), warnings: s.warnings };
|
|
139
|
+
const m = await manifest(dir);
|
|
140
|
+
// Asked for by name, a package is checked whether or not the venture lists it.
|
|
141
|
+
const names = only ? only.filter((n) => existsSync(join(repo, "packages", n, "package.json"))) : m.packages.list.filter((p) => p.known).map((p) => p.name);
|
|
142
|
+
const rs = routes().filter((r) => r.kind === "record" && r.collection !== "list");
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const name of names) {
|
|
145
|
+
// A package's failure stays in its result: an exception here is caught and reported as that package's.
|
|
146
|
+
try {
|
|
147
|
+
const pkg = load(join(repo, "packages", name, "package.json"));
|
|
148
|
+
const types = [...new Set([...(pkg.required ?? []), ...(pkg.optional ?? []), ...(pkg.records ?? [])])];
|
|
149
|
+
const records = [], checks = new Set((pkg.checks ?? []).filter((c) => /^schema\/check-[a-z-]+\.mjs$/.test(c) && c !== "schema/check-data-model.mjs" && existsSync(join(repo, c))));
|
|
150
|
+
for (const t of types) {
|
|
151
|
+
const r = rs.find((x) => x.record === t);
|
|
152
|
+
if (!r) continue;
|
|
153
|
+
for (const c of r.checks ?? []) checks.add(c);
|
|
154
|
+
const pats = r.files;
|
|
155
|
+
const hits = files(dir).filter((f) => pats.some((g) => glob(g).test(f) && routable(f, g)));
|
|
156
|
+
for (const f of hits) {
|
|
157
|
+
const problem = f.endsWith(".json") ? jsonProblem(join(dir, f)) ?? (r.schema ? validateFile(join(dir, f), r.schema) : null) : null;
|
|
158
|
+
records.push({ record: t, route: r.collection === "item" ? r.route.replace("[id]", starOf(f, pats) ?? basename(f)) : r.route, file: f, ok: !problem,
|
|
159
|
+
...(problem ? { error: problem, fix: { schema: r.schema, template: r.template, skills: pkg.skill?.path ? [pkg.skill.path] : [] } } : {}) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const ran = [...checks].map((c) => { const p = spawnSync("node", [join(repo, c), dir], { encoding: "utf8" }); return { check: c, passed: p.status === 0, output: (p.stdout + p.stderr).trim().split("\n").slice(-5).join("\n") }; });
|
|
163
|
+
const ok = records.every((x) => x.ok) && ran.every((x) => x.passed);
|
|
164
|
+
out.push({ package: name, ok, records, checks: ran });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
out.push({ package: name, ok: false, error: `uncaught in ${name}: ${String(e.message ?? e).split("\n")[0]}`, records: [], checks: [] });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const failed = out.filter((p) => !p.ok).map((p) => p.package);
|
|
170
|
+
return { ok: venture.ok && !failed.length, reached: !venture.ok ? "venture" : failed.length ? "package" : null, venture, packages: out, contained: failed, proceeds: out.filter((p) => p.ok).map((p) => p.package) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The id a collection pattern's one star matches in a file (customers/[id].json, accounts/[id]/account.json).
|
|
174
|
+
export function starOf(file, patterns) {
|
|
175
|
+
for (const g of patterns) {
|
|
176
|
+
if (!g.includes("*") || g.includes("**")) continue;
|
|
177
|
+
const m = file.match(new RegExp("^" + g.split("*").map((x) => x.replace(/[.+^${}()|[\]\\]/g, "\\$&")).join("([^/]*)") + "$"));
|
|
178
|
+
if (m) return m[1];
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// A venture's files, skipping dot folders, nested ventures and the root's private folders (as the router does).
|
|
184
|
+
function files(d, base = d) {
|
|
185
|
+
return existsSync(d) ? readdirSync(d, { withFileTypes: true }).flatMap((e) => e.name.startsWith(".") ? [] : e.isDirectory()
|
|
186
|
+
? (existsSync(join(d, e.name, "venture.json")) || (d === base && e.name.startsWith("_")) ? [] : files(join(d, e.name), base)) : [join(d, e.name).slice(base.length + 1)]) : [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Rebuilds a broken venture.json (E-8): the broken file moves to _private/, and a minimal one takes its place. */
|
|
190
|
+
export function recover(dir) {
|
|
191
|
+
const h = ventureHealth(dir);
|
|
192
|
+
if (!h) return { ok: true, result: "venture.json is sound: nothing to recover" };
|
|
193
|
+
const kept = join("_private", `venture.json.broken-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
194
|
+
if (existsSync(join(dir, "venture.json"))) { mkdirSync(join(dir, "_private"), { recursive: true }); renameSync(join(dir, "venture.json"), join(dir, kept)); }
|
|
195
|
+
writeFileSync(join(dir, "venture.json"), JSON.stringify(minimalVenture(dir), null, 2) + "\n");
|
|
196
|
+
return { ok: true, result: { written: "venture.json", kept: existsSync(join(dir, kept)) ? kept : null, note: "a minimal venture.json: copy back what you need from the kept file, then check it" } };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
200
|
+
const [cmd, dir] = process.argv.slice(2);
|
|
201
|
+
const opt = (n) => { const i = process.argv.indexOf(`--${n}`); return i >= 0 ? process.argv[i + 1] : undefined; };
|
|
202
|
+
if (!["check", "recover"].includes(cmd) || !dir || !existsSync(dir)) { console.error("usage: node interface/boundaries.mjs check <venture dir> [--package <name>] [--json] | recover <venture dir> --confirm recover"); process.exit(2); }
|
|
203
|
+
if (cmd === "recover") {
|
|
204
|
+
if (opt("confirm") !== "recover") { console.error("recover rewrites venture.json: pass --confirm recover"); process.exit(1); }
|
|
205
|
+
const r = recover(dir);
|
|
206
|
+
console.log(typeof r.result === "string" ? r.result : JSON.stringify(r.result, null, 2));
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
const fw = frameworkHealth();
|
|
210
|
+
if (fw) { console.error(globalError(fw).error); process.exit(1); }
|
|
211
|
+
const b = await boundaries(dir, { only: opt("package") ? [opt("package")] : null });
|
|
212
|
+
if (process.argv.includes("--json")) console.log(JSON.stringify(b, null, 2));
|
|
213
|
+
else {
|
|
214
|
+
console.log(`venture: ${b.venture.ok ? "ok" : `failed: ${(b.venture.errors ?? [b.venture.error]).join("; ")}`}`);
|
|
215
|
+
for (const p of b.packages) console.log(`${p.package.padEnd(14)} ${p.ok ? "ok" : `contained: ${[p.error, ...p.records.filter((x) => !x.ok).map((x) => `${x.file} ${x.error.split("\n")[0]}`), ...p.checks.filter((x) => !x.passed).map((x) => `${x.check} failed`)].filter(Boolean).join("; ")}`}`);
|
|
216
|
+
}
|
|
217
|
+
process.exit(b.ok ? 0 : 1);
|
|
218
|
+
}
|