naive-framework 0.0.0-stage → 0.1.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 +61 -0
- package/LICENSE +21 -0
- package/README.md +37 -2
- package/docs/decisions/decisions.json +861 -0
- package/docs/ontology.json +796 -0
- package/docs/ontology.md +163 -0
- package/docs/reference/ROUTES.md +125 -0
- package/evidence/benchmarks/2026-10-27-golden.jev.after-catalog-fix.json +558 -0
- package/evidence/benchmarks/2026-10-27-golden.jev.json +501 -0
- package/evidence/benchmarks/2026-10-27-golden.reasoning-answers.json +177 -0
- package/evidence/benchmarks/JEV_VS_REASONING_MODEL_2026-10-27.md +68 -0
- package/evidence/capabilities/agent-capabilities.json +86 -0
- package/evidence/design-documents.registry.json +2896 -0
- package/evidence/evals/evals.json +25 -0
- package/evidence/evals/harness-evals.json +140 -0
- package/evidence/layer.json +51 -0
- package/evidence/skills/agent-capabilities/SKILL.md +35 -0
- package/evidence/skills/design-coverage/SKILL.md +57 -0
- package/evidence/skills/design-coverage/check.mjs +192 -0
- package/evidence/skills/design-coverage/scan.mjs +45 -0
- package/evidence/skills/fidelity-ladder-audit/SKILL.md +121 -0
- package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +180 -0
- package/evidence/skills/findability/SKILL.md +32 -0
- package/evidence/skills/findability/inventory.rb +41 -0
- package/evidence/skills/foreshadowing-audit/SKILL.md +183 -0
- package/evidence/skills/metrics-review/SKILL.md +177 -0
- package/evidence/tooling/integrations.json +153 -0
- package/governance/layer.json +101 -0
- package/governance/references/references.json +26 -0
- package/governance/skills/board-design/SKILL.md +68 -0
- package/governance/skills/brand-pillar/SKILL.md +90 -0
- package/governance/skills/business-pillar/SKILL.md +96 -0
- package/governance/skills/chief-of-staff/SKILL.md +45 -0
- package/governance/skills/cofounder-structure/SKILL.md +64 -0
- package/governance/skills/cross-pillar-tradeoff/SKILL.md +168 -0
- package/governance/skills/equity-and-share-classes/SKILL.md +68 -0
- package/governance/skills/executive-roles/SKILL.md +95 -0
- package/governance/skills/exit-planning/SKILL.md +66 -0
- package/governance/skills/financing-and-investor-rights/SKILL.md +73 -0
- package/governance/skills/office-hours-mode/SKILL.md +113 -0
- package/governance/skills/product-pillar/SKILL.md +81 -0
- package/governance/skills/publication-policy/SKILL.md +63 -0
- package/governance/skills/role-and-candidate-authoring/SKILL.md +198 -0
- package/governance/skills/stakeholder-update/SKILL.md +177 -0
- package/governance/tooling/integrations.json +37 -0
- package/governance/venture-template/AGENT_ROLES_AND_DECISION_RIGHTS.md +58 -0
- package/governance/venture-template/COMPANY_VAULT_DEFINITION_2026-10-25.md +204 -0
- package/governance/venture-template/README.md +74 -0
- package/governance/venture-template/decision-rights.json +186 -0
- package/governance/venture-template/index.md +151 -0
- package/governance/venture-template/pillars/brand.md +209 -0
- package/governance/venture-template/pillars/business.md +255 -0
- package/governance/venture-template/pillars/product.md +254 -0
- package/governance/venture-template/render-roles.py +11 -0
- package/governance/venture-template/roles/README.md +85 -0
- package/governance/venture-template/roles/agent-roles.json +202 -0
- package/governance/venture-template/roles/ceo.md +163 -0
- package/governance/venture-template/roles/chief-of-staff.md +153 -0
- package/governance/venture-template/roles/cmo.md +165 -0
- package/governance/venture-template/roles/coo.md +189 -0
- package/governance/venture-template/roles/cto.md +158 -0
- package/governance/venture-template/roles/department-head.md +47 -0
- package/governance/venture-template/roles/forward-deployed-engineer.md +51 -0
- package/governance/venture-template/roles/founder.md +173 -0
- package/governance/venture-template/roles/hr-manager.md +49 -0
- package/governance/venture-template/roles/talent-acquisition-manager.md +50 -0
- package/interface/api/server.mjs +69 -0
- package/interface/cli/find-venture.mjs +15 -0
- package/interface/cli/mentura.mjs +271 -0
- package/interface/cli/naive.mjs +169 -0
- package/interface/demo/board.mjs +581 -0
- package/interface/demo/decision-harness.mjs +91 -0
- package/interface/demo/live-agent.mjs +47 -0
- package/interface/harness/evals.mjs +90 -0
- package/interface/harness/examples/pt-no-shows.dump.md +19 -0
- package/interface/harness/run.mjs +313 -0
- package/interface/harness/site.mjs +59 -0
- package/interface/layer.json +31 -0
- package/interface/mcp/server.mjs +122 -0
- package/interface/operator-console/README.md +58 -0
- package/interface/provenance.mjs +42 -0
- package/interface/references/references.json +28 -0
- package/interface/route-aliases.json +32 -0
- package/interface/router.mjs +580 -0
- package/interface/skills/install-mentura/SKILL.md +88 -0
- package/interface/tooling/integrations.json +112 -0
- package/knowledge/connectors/README.md +54 -0
- package/knowledge/connectors/catalog.mjs +118 -0
- package/knowledge/connectors/contract.mjs +56 -0
- package/knowledge/connectors/eval.mjs +176 -0
- package/knowledge/connectors/filesystem.mjs +79 -0
- package/knowledge/connectors/index.mjs +102 -0
- package/knowledge/connectors/resolve.mjs +44 -0
- package/knowledge/connectors/suite.mjs +80 -0
- package/knowledge/layer.json +42 -0
- package/knowledge/skills/find-reference/SKILL.md +38 -0
- package/knowledge/skills/industry-awareness/SKILL.md +277 -0
- package/knowledge/tooling/integrations.json +127 -0
- package/knowledge/tooling/questions.md +46 -0
- package/operations/FOUNDER_OPERATING_MODEL_CAMPAIGN_2026-10-14.md +16 -0
- package/operations/departments.json +162 -0
- package/operations/handoff-v2.md +93 -0
- package/operations/layer.json +60 -0
- package/operations/marketing/check-public.mjs +68 -0
- package/operations/marketing/site/README.md +86 -0
- package/operations/marketing/site/matrix.json +731 -0
- package/operations/marketing/site/plan.mjs +117 -0
- package/operations/marketing/site/presence.mjs +252 -0
- package/operations/references/references.json +45 -0
- package/operations/skills/recruiting/SKILL.md +32 -0
- package/operations/skills/team-design/SKILL.md +36 -0
- package/operations/skills/tool-selection/SKILL.md +46 -0
- package/operations/templates/recruiting-brief.md +35 -0
- package/operations/tooling/integrations.json +158 -0
- package/package.json +57 -4
- package/packages/catalog.json +272 -0
- package/packages/cofounder/package.json +42 -0
- package/packages/counsel/package.json +47 -0
- package/packages/customers/package.json +21 -0
- package/packages/decisions/package.json +17 -0
- package/packages/engineering/package.json +41 -0
- package/packages/evidence/package.json +17 -0
- package/packages/finance/package.json +48 -0
- package/packages/foundation/package.json +52 -0
- package/packages/investor/package.json +57 -0
- package/packages/investors/package.json +21 -0
- package/packages/legal/package.json +73 -0
- package/packages/marketing/package.json +57 -0
- package/packages/money/package.json +21 -0
- package/packages/operations/package.json +74 -0
- package/packages/partner/package.json +38 -0
- package/packages/people/package.json +64 -0
- package/packages/product/package.json +49 -0
- package/packages/raw/package.json +19 -0
- package/packages/relationships/package.json +52 -0
- package/packages/validation/package.json +99 -0
- package/platform/README.md +45 -0
- package/platform/connectors/eval.mjs +154 -0
- package/platform/connectors/fake-github.mjs +91 -0
- package/platform/connectors/github.mjs +105 -0
- package/platform/integrations/jev.mjs +30 -0
- package/platform/integrations/langsmith-docket.mjs +53 -0
- package/platform/integrations/langsmith-setup.mjs +115 -0
- package/platform/integrations/langsmith.mjs +47 -0
- package/platform/integrations/rho.mjs +145 -0
- package/platform/integrations/site.mjs +75 -0
- package/platform/kits/README.md +7 -0
- package/platform/mcp/research.mjs +64 -0
- package/purpose/checklists/vault-and-tooling-consultancy-setup.md +216 -0
- package/purpose/frameworks/business-discovery-framework-amendments-2026-05-20.md +181 -0
- package/purpose/frameworks/business-discovery-framework.html +938 -0
- package/purpose/frameworks/business-discovery-framework.md +326 -0
- package/purpose/frameworks/idea-map/README.md +74 -0
- package/purpose/frameworks/idea-map/conventions.json +146 -0
- package/purpose/frameworks/idea-map/graph.mjs +182 -0
- package/purpose/frameworks/idea-map/ideamap.mjs +179 -0
- package/purpose/frameworks/idea-map/route.mjs +140 -0
- package/purpose/frameworks/onboarding-handoff-framework.md +269 -0
- package/purpose/frameworks/post-discovery-deliverable-playbook.md +274 -0
- package/purpose/frameworks/product-anatomy/README.md +63 -0
- package/purpose/frameworks/product-anatomy/anatomy.mjs +195 -0
- package/purpose/frameworks/product-anatomy/examples/polity.answers.reasoned.json +11 -0
- package/purpose/frameworks/product-anatomy/examples/polity.json +89 -0
- package/purpose/frameworks/product-anatomy/phases.json +107 -0
- package/purpose/frameworks/product-anatomy/questions.json +74 -0
- package/purpose/kits/validation/README.md +14 -0
- package/purpose/kits/validation/references/references.json +122 -0
- package/purpose/kits/validation/skills/problem-validation/SKILL.md +43 -0
- package/purpose/kits/validation/skills/validation-score/SKILL.md +35 -0
- package/purpose/kits/validation/templates/go-to-market.md +19 -0
- package/purpose/kits/validation/templates/operations-readiness.md +20 -0
- package/purpose/kits/validation/templates/pricing.md +22 -0
- package/purpose/kits/validation/templates/problem-interview.md +36 -0
- package/purpose/kits/validation/templates/problem.md +45 -0
- package/purpose/kits/validation/templates/product-market-fit.md +23 -0
- package/purpose/kits/validation/templates/solution.md +18 -0
- package/purpose/kits/validation/templates/team.md +19 -0
- package/purpose/kits/validation/weights.json +22 -0
- package/purpose/layer.json +53 -0
- package/purpose/references/references.json +324 -0
- package/purpose/skills/ambition-tier/SKILL.md +205 -0
- package/purpose/skills/telos/SKILL.md +52 -0
- package/purpose/strategic-foundation.json +133 -0
- package/purpose/templates/telos-based-smart.md +39 -0
- package/purpose/tooling/integrations.json +48 -0
- package/schema/README.md +61 -0
- package/schema/VERSION +1 -0
- package/schema/account.schema.json +79 -0
- package/schema/add-department.mjs +41 -0
- package/schema/agent-capability.schema.json +94 -0
- package/schema/alignment.schema.json +61 -0
- package/schema/assess-problem.mjs +131 -0
- package/schema/bdd.mjs +56 -0
- package/schema/board.schema.json +101 -0
- package/schema/check-boundary.mjs +54 -0
- package/schema/check-data-model.mjs +170 -0
- package/schema/check-data-packages.mjs +83 -0
- package/schema/check-departments.mjs +45 -0
- package/schema/check-execution.mjs +85 -0
- package/schema/check-governance.mjs +98 -0
- package/schema/check-integrations.mjs +33 -0
- package/schema/check-nextjs-status.mjs +77 -0
- package/schema/check-nouns.mjs +49 -0
- package/schema/check-private.mjs +61 -0
- package/schema/check-provenance.mjs +55 -0
- package/schema/check-publication.mjs +96 -0
- package/schema/check-ready.mjs +89 -0
- package/schema/check-sources.mjs +66 -0
- package/schema/check-team.mjs +94 -0
- package/schema/check-telos.mjs +59 -0
- package/schema/check-validation.mjs +165 -0
- package/schema/check-ventures.mjs +81 -0
- package/schema/check-where-used.mjs +58 -0
- package/schema/data-master.schema.json +14 -0
- package/schema/data-model.json +1258 -0
- package/schema/data-model.schema.json +152 -0
- package/schema/decision-analytics.mjs +77 -0
- package/schema/decision-record.schema.json +290 -0
- package/schema/decision-rights.schema.json +34 -0
- package/schema/decision.schema.json +30 -0
- package/schema/decisions.mjs +321 -0
- package/schema/department-catalog.schema.json +55 -0
- package/schema/department.schema.json +15 -0
- package/schema/departments.mjs +32 -0
- package/schema/docket-eval.mjs +89 -0
- package/schema/docket.mjs +135 -0
- package/schema/eval-suite.schema.json +45 -0
- package/schema/evidence.schema.json +110 -0
- package/schema/examples/migrate/agent.mjs +12 -0
- package/schema/examples/workspace/ventures/studio/accounts/bakery/account.json +18 -0
- package/schema/examples/workspace/ventures/studio/evidence/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/governance/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/knowledge/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/operations/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/print/knowledge/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/print/purpose/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/print/venture.json +37 -0
- package/schema/examples/workspace/ventures/studio/products/brand-care.json +19 -0
- package/schema/examples/workspace/ventures/studio/products/brand-kit.json +23 -0
- package/schema/examples/workspace/ventures/studio/provenance.jsonl +3 -0
- package/schema/examples/workspace/ventures/studio/purpose/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/studio/venture.json +43 -0
- package/schema/examples/workspace/ventures/tandem/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/tandem/evidence/README.md +3 -0
- package/schema/examples/workspace/ventures/tandem/governance/README.md +3 -0
- package/schema/examples/workspace/ventures/tandem/governance/agreements/dee-fast.md +3 -0
- package/schema/examples/workspace/ventures/tandem/governance/board.json +51 -0
- package/schema/examples/workspace/ventures/tandem/governance/departments/finance/charter.md +5 -0
- package/schema/examples/workspace/ventures/tandem/governance/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/charter.md +5 -0
- package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/tandem/governance/exit.json +33 -0
- package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.json +24 -0
- package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.md +3 -0
- package/schema/examples/workspace/ventures/tandem/governance/ownership.json +131 -0
- package/schema/examples/workspace/ventures/tandem/governance/publication-policy.json +171 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/research/friends-feedback-2026-08.md +3 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/research/pilot-2026-10.md +3 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/research/problem-interviews-2026-09.md +20 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/sources.json +8 -0
- package/schema/examples/workspace/ventures/tandem/knowledge/where-used.json +13 -0
- package/schema/examples/workspace/ventures/tandem/operations/README.md +3 -0
- package/schema/examples/workspace/ventures/tandem/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/tandem/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/tandem/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/tandem/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/tandem/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/tandem/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/tandem/products/shop-app.json +22 -0
- package/schema/examples/workspace/ventures/tandem/profile.md +5 -0
- package/schema/examples/workspace/ventures/tandem/provenance.jsonl +13 -0
- package/schema/examples/workspace/ventures/tandem/purpose/README.md +3 -0
- package/schema/examples/workspace/ventures/tandem/purpose/telos.json +78 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/go-to-market.json +35 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/pricing.json +37 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/problem.json +74 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/product-market-fit.json +66 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/solution.json +33 -0
- package/schema/examples/workspace/ventures/tandem/purpose/validation/team.json +52 -0
- package/schema/examples/workspace/ventures/tandem/venture.json +79 -0
- package/schema/examples/workspace/ventures/woodworking/knowledge/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/woodworking/venture.json +21 -0
- package/schema/examples/workspace/workspace.json +8 -0
- package/schema/exit.schema.json +74 -0
- package/schema/foundation.mjs +118 -0
- package/schema/founder-agreement.schema.json +83 -0
- package/schema/gate.mjs +39 -0
- package/schema/handoff.schema.json +239 -0
- package/schema/icp.schema.json +136 -0
- package/schema/idea-graph.schema.json +61 -0
- package/schema/init-venture.mjs +98 -0
- package/schema/integration.schema.json +143 -0
- package/schema/knowledge-sources.schema.json +130 -0
- package/schema/layer.json +46 -0
- package/schema/layer.schema.json +119 -0
- package/schema/load-packages.mjs +26 -0
- package/schema/marketing-sitemap.schema.json +232 -0
- package/schema/migrate-venture.mjs +192 -0
- package/schema/need-now.json +601 -0
- package/schema/need-now.mjs +107 -0
- package/schema/nextjs-status.schema.json +29 -0
- package/schema/ownership.schema.json +282 -0
- package/schema/packages.json +4 -0
- package/schema/packages.mjs +247 -0
- package/schema/payouts.mjs +91 -0
- package/schema/port-venture.mjs +161 -0
- package/schema/product.schema.json +223 -0
- package/schema/provenance-entry.schema.json +154 -0
- package/schema/publication-policy.schema.json +293 -0
- package/schema/raw-source.schema.json +15 -0
- package/schema/reference.schema.json +115 -0
- package/schema/route-aliases.schema.json +25 -0
- package/schema/run-handoff.mjs +51 -0
- package/schema/scaffold/defaults.json +54 -0
- package/schema/site-presence.schema.json +337 -0
- package/schema/strategic-foundation.schema.json +74 -0
- package/schema/stream.schema.json +14 -0
- package/schema/team-plan.schema.json +157 -0
- package/schema/telos.schema.json +197 -0
- package/schema/time-to-ready.mjs +93 -0
- package/schema/upgrade-venture.mjs +129 -0
- package/schema/use-model.mjs +10 -0
- package/schema/validate.py +49 -0
- package/schema/validation-evidence.schema.json +123 -0
- package/schema/validation-go-to-market.schema.json +110 -0
- package/schema/validation-operations-readiness.schema.json +91 -0
- package/schema/validation-pricing.schema.json +114 -0
- package/schema/validation-problem.schema.json +118 -0
- package/schema/validation-product-market-fit.schema.json +135 -0
- package/schema/validation-solution.schema.json +79 -0
- package/schema/validation-team.schema.json +116 -0
- package/schema/venture.schema.json +407 -0
- package/schema/vocab/business-models.json +298 -0
- package/schema/where-used.schema.json +56 -0
- package/schema/workspace.schema.json +38 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Checks the data model (schema/data-model.json), the layer manifests and the venture
|
|
2
|
+
// packages against the repository (EE-018, EE-071). Rules: record type ids are unique and
|
|
3
|
+
// owned by a known layer; every JSON file in the repo is a record of some type (or ignored
|
|
4
|
+
// with a reason), and every schema file governs one (or is a part one $refs); each reference
|
|
5
|
+
// names a known record type and a check that exists, or says that nothing checks it yet;
|
|
6
|
+
// every layer has a manifest at <layer>/layer.json; each record type is owned by exactly one
|
|
7
|
+
// layer or one package; a layer that owns a record is of that record's layer; a package
|
|
8
|
+
// declares use_model (an array, empty when it calls no model) and at most one skill;
|
|
9
|
+
// layers and packages read and write only known types they do not own; every SKILL.md
|
|
10
|
+
// belongs to exactly one layer or package.
|
|
11
|
+
//
|
|
12
|
+
// node schema/check-data-model.mjs check, and list unchecked references
|
|
13
|
+
// node schema/check-data-model.mjs --md print the layer relationships derived from the manifests
|
|
14
|
+
//
|
|
15
|
+
// Layer relationships are never written by hand: they come from each manifest's reads and writes.
|
|
16
|
+
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
19
|
+
import { join, relative } from "node:path";
|
|
20
|
+
|
|
21
|
+
// EE_ROOT points the check at another copy of the repository (used by schema/test.mjs).
|
|
22
|
+
const root = process.env.EE_ROOT?.replace(/\/?$/, "/") ?? new URL("..", import.meta.url).pathname;
|
|
23
|
+
const load = (p) => JSON.parse(readFileSync(join(root, p), "utf8"));
|
|
24
|
+
const model = load("schema/data-model.json");
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
|
|
28
|
+
const files = (dir) => readdirSync(join(root, dir), { withFileTypes: true }).flatMap((e) =>
|
|
29
|
+
[".git", "node_modules", ".naive"].includes(e.name) ? [] : e.isDirectory() ? files(join(dir, e.name)) : [join(dir, e.name)]);
|
|
30
|
+
// The files git would track (so build output and installed packages under .gitignore are left out); a
|
|
31
|
+
// copy outside git, as the tests use, is walked instead.
|
|
32
|
+
const tracked = spawnSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { cwd: root, encoding: "utf8" });
|
|
33
|
+
const all = tracked.status === 0 && tracked.stdout.trim() && !process.env.EE_ROOT ? tracked.stdout.trim().split("\n").filter((f) => existsSync(join(root, f))) : files(".").map((f) => relative(".", f));
|
|
34
|
+
const glob = (g) => new RegExp("^" + g.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*/g, "[^/]*").replace(/\0/g, "(?:[^/]+/)*") + "$");
|
|
35
|
+
const matches = (g) => all.filter((f) => glob(g).test(f));
|
|
36
|
+
|
|
37
|
+
// Record types.
|
|
38
|
+
const types = new Map();
|
|
39
|
+
const layers = new Set(["system", ...model.layers]);
|
|
40
|
+
for (const r of model.records) {
|
|
41
|
+
if (types.has(r.id)) errors.push(`data model: duplicate record type ${r.id}`);
|
|
42
|
+
types.set(r.id, r);
|
|
43
|
+
if (!layers.has(r.layer)) errors.push(`${r.id}: unknown layer ${r.layer}`);
|
|
44
|
+
if (r.schema && !existsSync(join(root, r.schema))) errors.push(`${r.id}: schema ${r.schema} not found`);
|
|
45
|
+
r.files = [...new Set(r.paths.flatMap(matches))];
|
|
46
|
+
if (!r.files.length) warnings.push(`${r.id}: no records yet at ${r.paths.join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
for (const r of types.values()) for (const ref of r.refs ?? []) {
|
|
49
|
+
if (!ref.to.startsWith("(") && !types.has(ref.to)) errors.push(`${r.id}.${ref.field}: refers to unknown record type ${ref.to}`);
|
|
50
|
+
if (ref.checked_by && !existsSync(join(root, ref.checked_by))) errors.push(`${r.id}.${ref.field}: check ${ref.checked_by} not found`);
|
|
51
|
+
if (!ref.checked_by) warnings.push(`${r.id}.${ref.field} -> ${ref.to}: nothing checks this reference yet`);
|
|
52
|
+
}
|
|
53
|
+
for (const c of model.records.filter((r) => r.contains)) for (const t of c.contains) if (!types.has(t)) errors.push(`${c.id}: contains unknown record type ${t}`);
|
|
54
|
+
|
|
55
|
+
// Every JSON file is a record of some type; every schema file governs one.
|
|
56
|
+
const claimed = new Set([...types.values()].flatMap((r) => r.files));
|
|
57
|
+
const ignored = new Set(model.ignore.map((i) => i.path));
|
|
58
|
+
const schemas = new Set([...types.values()].map((r) => r.schema).filter(Boolean));
|
|
59
|
+
// A schema that a named schema $refs (a shared part, such as the validation evidence item) counts as named.
|
|
60
|
+
for (const s of [...schemas]) for (const m of readFileSync(join(root, s), "utf8").matchAll(/"\$ref":\s*"([a-z0-9-]+\.schema\.json)/g)) schemas.add(`schema/${m[1]}`);
|
|
61
|
+
for (const f of all.filter((f) => f.endsWith(".json"))) {
|
|
62
|
+
if (f.endsWith(".schema.json")) { if (!schemas.has(f)) errors.push(`${f}: a schema no record type names`); continue; }
|
|
63
|
+
if (!claimed.has(f) && !ignored.has(f)) errors.push(`${f}: no record type in schema/data-model.json covers this file`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Decision classes a record type names must exist in the decision-rights template (EE-032).
|
|
67
|
+
const classIds = new Set(load("governance/venture-template/decision-rights.json").classes.map((c) => c.id));
|
|
68
|
+
for (const r of types.values()) if (r.decision_class && !classIds.has(r.decision_class)) errors.push(`${r.id}: unknown decision class ${r.decision_class}`);
|
|
69
|
+
|
|
70
|
+
// Layers and packages (EE-071). A layer manifest is <layer>/layer.json. A package that lists
|
|
71
|
+
// `records` owns those record types (the validation package absorbed the old validation kit).
|
|
72
|
+
const layerFiles = [...model.layers.map((l) => `${l}/layer.json`), "schema/layer.json"];
|
|
73
|
+
const packageFiles = all.filter((f) => /^packages\/[^/]+\/package\.json$/.test(f));
|
|
74
|
+
const skillsOf = (k) => [...(k.skills ?? []), ...(k.skill ? [k.skill] : [])];
|
|
75
|
+
const kits = new Map();
|
|
76
|
+
const owner = new Map();
|
|
77
|
+
const own = (id, t, k, p) => {
|
|
78
|
+
if (!types.has(t)) { errors.push(`${id}: owns unknown record type ${t}`); return; }
|
|
79
|
+
if (owner.has(t)) errors.push(`${t}: owned by both ${owner.get(t)} and ${id}`);
|
|
80
|
+
owner.set(t, id);
|
|
81
|
+
if (k.layer && types.get(t).layer !== k.layer) errors.push(`${id}: owns ${t}, which the data model gives to ${types.get(t).layer}`);
|
|
82
|
+
if (!k.layer && p.startsWith("packages/")) errors.push(`${id}: owns ${t} but names no layer`);
|
|
83
|
+
};
|
|
84
|
+
for (const p of layerFiles) {
|
|
85
|
+
if (!existsSync(join(root, p))) { errors.push(`${p}: missing; every layer has a manifest`); continue; }
|
|
86
|
+
const k = { ...load(p), path: p };
|
|
87
|
+
const folderLayer = p === "schema/layer.json" ? "system" : p.split("/")[0];
|
|
88
|
+
if (k.layer !== folderLayer) errors.push(`${p}: layer says ${k.layer}`);
|
|
89
|
+
const id = k.name ?? k.kit;
|
|
90
|
+
kits.set(id, k);
|
|
91
|
+
for (const t of k.records ?? []) own(id, t, k, p);
|
|
92
|
+
for (const c of k.checks ?? []) if (!existsSync(join(root, c))) errors.push(`${id}: check ${c} not found`);
|
|
93
|
+
}
|
|
94
|
+
for (const p of packageFiles) {
|
|
95
|
+
const k = { ...load(p), path: p };
|
|
96
|
+
if (!Array.isArray(k.use_model)) errors.push(`${p}: a package declares use_model, an array, empty when it calls no model`);
|
|
97
|
+
if (skillsOf(k).length > 1) errors.push(`${p}: a package has at most one skill`);
|
|
98
|
+
const id = k.name;
|
|
99
|
+
if ((k.records ?? []).length) {
|
|
100
|
+
kits.set(id, k);
|
|
101
|
+
for (const t of k.records) own(id, t, k, p);
|
|
102
|
+
}
|
|
103
|
+
for (const c of k.checks ?? []) if (!existsSync(join(root, c))) errors.push(`${id}: check ${c} not found`);
|
|
104
|
+
if (k.skill && !existsSync(join(root, k.skill.path))) errors.push(`${id}: skill ${k.skill.path} not found`);
|
|
105
|
+
if (k.template_dir && !existsSync(join(root, k.template_dir))) errors.push(`${id}: template dir ${k.template_dir} not found`);
|
|
106
|
+
}
|
|
107
|
+
for (const t of types.keys()) if (!owner.has(t)) errors.push(`${t}: no layer or package owns this record type`);
|
|
108
|
+
for (const k of kits.values()) for (const rel of ["reads", "writes"]) for (const t of k[rel] ?? []) {
|
|
109
|
+
const id = k.name ?? k.kit;
|
|
110
|
+
if (!types.has(t)) errors.push(`${id}: ${rel} unknown record type ${t}`);
|
|
111
|
+
else if (owner.get(t) === id) errors.push(`${id}: ${rel} ${t}, which it owns; list only records owned elsewhere`);
|
|
112
|
+
}
|
|
113
|
+
for (const k of kits.values()) if (k.product) {
|
|
114
|
+
const id = k.name ?? k.kit;
|
|
115
|
+
const [v, pid] = k.product.split("/");
|
|
116
|
+
const card = all.find((f) => f.endsWith(`/${v}/products/${pid}.json`) || f.endsWith(`/${v}/product/${pid}.json`));
|
|
117
|
+
if (!card) errors.push(`${id}: product card ${k.product} not found`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Skills: each SKILL.md belongs to exactly one layer or package.
|
|
121
|
+
const skillFiles = all.filter((f) => /(^|\/)SKILL\.md$/.test(f) && !f.includes("/_archive/") && model.layers.includes(f.split("/")[0]));
|
|
122
|
+
const listed = new Map();
|
|
123
|
+
for (const k of kits.values()) for (const s of skillsOf(k)) {
|
|
124
|
+
const id = k.name ?? k.kit;
|
|
125
|
+
if (!existsSync(join(root, s.path))) errors.push(`${id}/${s.id}: ${s.path} not found`);
|
|
126
|
+
if (listed.has(s.path)) errors.push(`${s.path}: listed by ${listed.get(s.path)} and ${id}`);
|
|
127
|
+
listed.set(s.path, id);
|
|
128
|
+
if (s.move_to && !kits.has(s.move_to)) errors.push(`${id}/${s.id}: move_to unknown owner ${s.move_to}`);
|
|
129
|
+
}
|
|
130
|
+
for (const f of skillFiles) if (!listed.has(f)) errors.push(`${f}: no layer or package lists this skill`);
|
|
131
|
+
|
|
132
|
+
// Layer relationships, derived. The layer is the record type's layer (EE-071).
|
|
133
|
+
const layerOf = (t) => types.get(t)?.layer;
|
|
134
|
+
const edges = new Map();
|
|
135
|
+
const edge = (from, to, t) => { if (!from || from === to) return; const key = `${from}>${to}`; edges.set(key, [...(edges.get(key) ?? []), t]); };
|
|
136
|
+
for (const k of kits.values()) {
|
|
137
|
+
for (const t of k.reads ?? []) edge(layerOf(t), k.layer, t);
|
|
138
|
+
for (const t of k.writes ?? []) edge(k.layer, layerOf(t), t);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (process.argv.includes("--md")) {
|
|
142
|
+
const order = [...model.layers, "system"];
|
|
143
|
+
const rows = order.map((l) => {
|
|
144
|
+
const k = [...kits.values()].find((x) => x.layer === l && String(x.path).endsWith("/layer.json"));
|
|
145
|
+
const from = [...edges].filter(([e]) => e.endsWith(`>${l}`)).map(([e, ts]) => `${e.split(">")[0]} (${[...new Set(ts)].join(", ")})`);
|
|
146
|
+
const to = [...edges].filter(([e]) => e.startsWith(`${l}>`)).map(([e, ts]) => `${e.split(">")[1]} (${[...new Set(ts)].join(", ")})`);
|
|
147
|
+
return `| ${l} | ${k?.skills.length ?? 0} | ${k?.records.join(", ") || "none"} | ${from.join("; ") || "none"} | ${to.join("; ") || "none"} |`;
|
|
148
|
+
});
|
|
149
|
+
console.log([
|
|
150
|
+
"### Layer relationships (generated by `node schema/check-data-model.mjs --md`)",
|
|
151
|
+
"",
|
|
152
|
+
"Each arrow is a record type one layer owns and another reads, or one layer writes into another. A reader depends on the owner: change the owner's record and the reader's checks run.",
|
|
153
|
+
"",
|
|
154
|
+
"| Layer | Skills | Owns | Takes from | Gives to |",
|
|
155
|
+
"| --- | --- | --- | --- | --- |",
|
|
156
|
+
...rows,
|
|
157
|
+
"",
|
|
158
|
+
"```mermaid",
|
|
159
|
+
"flowchart LR",
|
|
160
|
+
...[...edges].map(([e, ts]) => ` ${e.split(">")[0]} -->|${[...new Set(ts)].join(", ")}| ${e.split(">")[1]}`),
|
|
161
|
+
"```",
|
|
162
|
+
].join("\n"));
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const w of warnings) console.log(`warn ${w}`);
|
|
167
|
+
if (errors.length) { for (const e of errors) console.error(`FAIL ${e}`); process.exit(1); }
|
|
168
|
+
const moves = [...kits.values()].flatMap((k) => skillsOf(k).filter((s) => s.move_to)).length;
|
|
169
|
+
const layerCount = layerFiles.filter((p) => existsSync(join(root, p))).length;
|
|
170
|
+
console.log(`${types.size} record types, ${claimed.size} record files, ${layerCount} layers, ${packageFiles.length} packages, ${listed.size} skills (${moves} proposed moves): the data model holds.`);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Data packages and raw (EE-070). customers, investors and money hold [id].json master
|
|
3
|
+
// records plus append-only _streams/<id>/. A stream.json declares sensitive. A sensitive
|
|
4
|
+
// stream may version only stream.json and summary.md. raw/ is immutable: sources/<id>/
|
|
5
|
+
// holds source.json and text.md, and original.* stays out of git. An idea map cites raw
|
|
6
|
+
// ids once the venture has raw material.
|
|
7
|
+
//
|
|
8
|
+
// node schema/check-data-packages.mjs <workspace or venture dir>
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
const dir = process.argv[2];
|
|
15
|
+
if (!dir) { console.error("usage: node schema/check-data-packages.mjs <workspace or venture dir>"); process.exit(2); }
|
|
16
|
+
|
|
17
|
+
const PACKS = ["customers", "investors", "money"];
|
|
18
|
+
const KEEP = new Set(["stream.json", "summary.md"]);
|
|
19
|
+
const walk = (d) => existsSync(d) ? readdirSync(d, { withFileTypes: true }).flatMap((e) => e.name === "node_modules" || e.name.startsWith(".") ? []
|
|
20
|
+
: e.isDirectory() ? walk(join(d, e.name)) : e.name === "venture.json" ? [d] : []) : [];
|
|
21
|
+
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
22
|
+
const tracked = (root, rel) => {
|
|
23
|
+
const g = spawnSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: root, encoding: "utf8" });
|
|
24
|
+
return g.status === 0;
|
|
25
|
+
};
|
|
26
|
+
const inGit = (root) => spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root, encoding: "utf8" }).status === 0;
|
|
27
|
+
|
|
28
|
+
const errors = [];
|
|
29
|
+
let n = 0;
|
|
30
|
+
for (const v of walk(dir)) {
|
|
31
|
+
n++;
|
|
32
|
+
const id = load(join(v, "venture.json")).id;
|
|
33
|
+
const git = inGit(v);
|
|
34
|
+
for (const pack of PACKS) {
|
|
35
|
+
const base = join(v, pack);
|
|
36
|
+
if (!existsSync(base)) continue;
|
|
37
|
+
for (const name of readdirSync(base)) {
|
|
38
|
+
if (name.startsWith("_") || name.startsWith(".")) continue;
|
|
39
|
+
const p = join(base, name);
|
|
40
|
+
if (!statSync(p).isFile() || !name.endsWith(".json")) errors.push(`${id}/${pack}/${name}: a master record is <id>.json`);
|
|
41
|
+
}
|
|
42
|
+
const streams = join(base, "_streams");
|
|
43
|
+
if (!existsSync(streams)) continue;
|
|
44
|
+
for (const s of readdirSync(streams)) {
|
|
45
|
+
const sd = join(streams, s);
|
|
46
|
+
if (!statSync(sd).isDirectory()) { errors.push(`${id}/${pack}/_streams/${s}: a stream is a folder`); continue; }
|
|
47
|
+
const meta = join(sd, "stream.json");
|
|
48
|
+
if (!existsSync(meta)) { errors.push(`${id}/${pack}/_streams/${s}: missing stream.json`); continue; }
|
|
49
|
+
const rec = load(meta);
|
|
50
|
+
if (typeof rec.sensitive !== "boolean") errors.push(`${id}/${pack}/_streams/${s}: stream.json must declare sensitive`);
|
|
51
|
+
if (rec.sensitive !== true) continue;
|
|
52
|
+
for (const f of readdirSync(sd)) {
|
|
53
|
+
if (KEEP.has(f)) continue;
|
|
54
|
+
const rel = join(pack, "_streams", s, f);
|
|
55
|
+
if (!git || tracked(v, rel)) errors.push(`${id}/${rel}: a sensitive stream versions only stream.json and summary.md`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const sources = join(v, "raw/sources");
|
|
60
|
+
if (existsSync(sources)) for (const s of readdirSync(sources)) {
|
|
61
|
+
const sd = join(sources, s);
|
|
62
|
+
if (!statSync(sd).isDirectory() || s.startsWith(".")) continue;
|
|
63
|
+
if (!existsSync(join(sd, "source.json"))) errors.push(`${id}/raw/sources/${s}: missing source.json`);
|
|
64
|
+
if (!existsSync(join(sd, "text.md"))) errors.push(`${id}/raw/sources/${s}: missing text.md`);
|
|
65
|
+
for (const f of readdirSync(sd)) if (f.startsWith("original.") && (!git || tracked(v, join("raw/sources", s, f)))) errors.push(`${id}/raw/sources/${s}/${f}: original.* stays out of git`);
|
|
66
|
+
}
|
|
67
|
+
const map = ["ideas/map.mdx", "purpose/ideas/map.mdx"].map((f) => join(v, f)).find((p) => existsSync(p));
|
|
68
|
+
const rawIds = new Set();
|
|
69
|
+
const dump = join(v, "raw/dump");
|
|
70
|
+
if (existsSync(dump)) for (const f of readdirSync(dump)) if (f !== ".gitkeep") rawIds.add(f.replace(/\.[^.]+$/, ""));
|
|
71
|
+
if (existsSync(sources)) for (const s of readdirSync(sources)) if (!s.startsWith(".")) rawIds.add(s);
|
|
72
|
+
if (map && rawIds.size) {
|
|
73
|
+
const text = readFileSync(map, "utf8");
|
|
74
|
+
for (const m of text.matchAll(/<Idea ([^>]*)>/g)) {
|
|
75
|
+
const raw = m[1].match(/\braw="([^"]*)"/);
|
|
76
|
+
const ids = raw ? raw[1].split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
77
|
+
if (!ids.length) errors.push(`${id}: an idea map node cites no raw id`);
|
|
78
|
+
for (const rid of ids) if (!rawIds.has(rid)) errors.push(`${id}: idea map cites raw id ${rid}, which is not in raw/`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
83
|
+
console.log(`${n} ventures: data packages and raw hold.`);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Checks each venture's departments (EE-047): every id in venture.json `departments` is in the
|
|
2
|
+
// catalog (operations/departments.json) and has at least one folder; every department folder
|
|
3
|
+
// holds a department.json whose tags match the catalog, is named for its department, sits
|
|
4
|
+
// directly under departments/ or under the grouping folder for its place in the value chain, and
|
|
5
|
+
// is listed in `departments`. A venture with no departments passes: they are imported only when
|
|
6
|
+
// needed (EE-048).
|
|
7
|
+
//
|
|
8
|
+
// node schema/check-departments.mjs <workspace or venture dir>
|
|
9
|
+
|
|
10
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { catalog, foldersOf } from "./departments.mjs";
|
|
13
|
+
|
|
14
|
+
const dir = process.argv[2];
|
|
15
|
+
if (!dir) { console.error("usage: node schema/check-departments.mjs <workspace or venture dir>"); process.exit(2); }
|
|
16
|
+
const walk = (d) => readdirSync(d, { withFileTypes: true }).flatMap((e) => e.name === "node_modules" || e.name.startsWith(".") ? []
|
|
17
|
+
: e.isDirectory() ? walk(join(d, e.name)) : e.name === "venture.json" ? [d] : []);
|
|
18
|
+
const byId = new Map(catalog().map((d) => [d.id, d]));
|
|
19
|
+
const errors = [];
|
|
20
|
+
let ventures = 0, folders = 0;
|
|
21
|
+
for (const v of walk(dir)) {
|
|
22
|
+
const venture = JSON.parse(readFileSync(join(v, "venture.json"), "utf8"));
|
|
23
|
+
const listed = venture.departments ?? [];
|
|
24
|
+
const found = foldersOf(v);
|
|
25
|
+
if (!listed.length && !found.length) continue;
|
|
26
|
+
ventures++;
|
|
27
|
+
for (const id of listed) {
|
|
28
|
+
if (!byId.has(id)) errors.push(`${venture.id}: departments lists ${id}, which is not in operations/departments.json`);
|
|
29
|
+
else if (!found.some((f) => f.tag?.department === id)) errors.push(`${venture.id}: ${id} is listed but has no folder (node schema/add-department.mjs ${v} ${id})`);
|
|
30
|
+
}
|
|
31
|
+
for (const f of found) {
|
|
32
|
+
folders++;
|
|
33
|
+
const where = `${venture.id}: ${f.path}`;
|
|
34
|
+
if (!f.tag) { errors.push(`${where} has no department.json`); continue; }
|
|
35
|
+
const d = byId.get(f.tag.department);
|
|
36
|
+
if (!d) { errors.push(`${where}: unknown department ${f.tag.department}`); continue; }
|
|
37
|
+
if (f.folder !== d.id) errors.push(`${where}: folder is named ${f.folder} but holds ${d.id}`);
|
|
38
|
+
if (f.tag.value_chain !== d.value_chain) errors.push(`${where}: value_chain ${f.tag.value_chain}, the catalog says ${d.value_chain}`);
|
|
39
|
+
if (f.tag.apqc !== d.apqc.id) errors.push(`${where}: apqc ${f.tag.apqc}, the catalog says ${d.apqc.id}`);
|
|
40
|
+
if (f.group && f.group !== d.value_chain) errors.push(`${where}: grouped under ${f.group}/ but ${d.id} is a ${d.value_chain} activity`);
|
|
41
|
+
if (!listed.includes(d.id)) errors.push(`${where}: ${d.id} is not in venture.json departments`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
45
|
+
console.log(`departments: ${ventures} venture(s), ${folders} folder(s), all match the catalog`);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Checks a venture's handoffs (operations, EE-038) and evidence records against its decision rights (EE-017).
|
|
2
|
+
// Validate each file against its schema first; this checks how they fit together.
|
|
3
|
+
//
|
|
4
|
+
// node schema/check-execution.mjs <venture dir>
|
|
5
|
+
//
|
|
6
|
+
// Rules: a venture's decision rights only tighten the template unless a decision is named;
|
|
7
|
+
// every handoff names a known role, carries only classes that role owns, has a brief, at
|
|
8
|
+
// least one conformance and one self-proof criterion (EE-006), stays within max_attempts
|
|
9
|
+
// or is escalated, has passed its readiness check before going in progress (EE-019), and is escalated with an open question when its status says so. A done
|
|
10
|
+
// handoff has a PR and passing evidence for every done-criterion; a review passes only from
|
|
11
|
+
// its named reviewer, who is never the holder's role. Every evidence file's
|
|
12
|
+
// passed flag matches its results.
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
15
|
+
import { dirname, join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
const dir = process.argv[2];
|
|
18
|
+
if (!dir) { console.error("usage: node schema/check-execution.mjs <venture dir>"); process.exit(2); }
|
|
19
|
+
const root = new URL("..", import.meta.url).pathname;
|
|
20
|
+
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
21
|
+
const jsonIn = (d) => existsSync(d) ? readdirSync(d).filter((f) => f.endsWith(".json")).map((f) => join(d, f)) : [];
|
|
22
|
+
const RANK = { decide: 0, "decide-inform": 1, "recommend-approve": 2, escalate: 3 };
|
|
23
|
+
|
|
24
|
+
const venture = load(join(dir, "venture.json"));
|
|
25
|
+
const roles = new Set(load(join(root, "governance/venture-template/roles/agent-roles.json")).roles.map((r) => r.id));
|
|
26
|
+
const template = load(join(root, "governance/venture-template/decision-rights.json"));
|
|
27
|
+
const errors = [];
|
|
28
|
+
|
|
29
|
+
const classes = new Map(template.classes.map((c) => [c.id, c]));
|
|
30
|
+
const own = join(dir, "governance/decision-rights.json");
|
|
31
|
+
if (existsSync(own)) for (const c of load(own).classes) {
|
|
32
|
+
const t = classes.get(c.id);
|
|
33
|
+
if (t && RANK[c.level] < RANK[t.level] && !c.decision) errors.push(`decision-rights/${c.id}: loosens ${t.level} to ${c.level} without naming a decision`);
|
|
34
|
+
classes.set(c.id, { ...t, ...c });
|
|
35
|
+
}
|
|
36
|
+
for (const c of classes.values()) if (!roles.has(c.owner)) errors.push(`decision-rights/${c.id}: owner ${c.owner} is not a role`);
|
|
37
|
+
|
|
38
|
+
const results = new Map();
|
|
39
|
+
for (const f of jsonIn(join(dir, "evidence/results"))) {
|
|
40
|
+
const e = load(f);
|
|
41
|
+
if (e.passed !== e.results.every((r) => r.passed)) errors.push(`evidence/${e.id}: passed says ${e.passed} but its results disagree`);
|
|
42
|
+
results.set(resolve(f), e);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let count = 0;
|
|
46
|
+
for (const f of jsonIn(join(dir, "operations/handoffs"))) {
|
|
47
|
+
const h = load(f), at = `handoff/${h.id}`;
|
|
48
|
+
count++;
|
|
49
|
+
if (h.venture !== venture.id) errors.push(`${at}: venture ${h.venture} is not ${venture.id}`);
|
|
50
|
+
if (!roles.has(h.role)) errors.push(`${at}: role ${h.role} is not a role`);
|
|
51
|
+
if (!existsSync(join(dirname(f), h.brief))) errors.push(`${at}: brief ${h.brief} not found`);
|
|
52
|
+
for (const id of h.decision_classes) {
|
|
53
|
+
const c = classes.get(id);
|
|
54
|
+
if (!c) errors.push(`${at}: unknown decision class ${id}`);
|
|
55
|
+
else if (c.owner !== h.role) errors.push(`${at}: class ${id} belongs to ${c.owner}, not ${h.role}`);
|
|
56
|
+
}
|
|
57
|
+
const kinds = new Set(h.done_criteria.map((d) => d.kind));
|
|
58
|
+
if (!kinds.has("conformance")) errors.push(`${at}: no conformance criterion`);
|
|
59
|
+
if (!kinds.has("self-proof")) errors.push(`${at}: no self-proof criterion (EE-006)`);
|
|
60
|
+
for (const d of h.done_criteria) if (d.kind === "review" && (!roles.has(d.reviewer) || d.reviewer === h.role)) errors.push(`${at}/${d.id}: review needs a reviewer role other than the holder's`);
|
|
61
|
+
if (h.attempts > h.max_attempts) errors.push(`${at}: ${h.attempts} attempts exceed max_attempts ${h.max_attempts}`);
|
|
62
|
+
if (h.attempts === h.max_attempts && !["done", "escalated"].includes(h.status)) errors.push(`${at}: attempts used up, so it is done or escalated`);
|
|
63
|
+
const open = (h.escalations ?? []).filter((e) => !e.answer);
|
|
64
|
+
if (h.status === "escalated" && !open.length) errors.push(`${at}: escalated with no open question`);
|
|
65
|
+
if (h.status === "in-progress" && !h.ready_check) errors.push(`${at}: in progress without a recorded readiness check (node schema/check-ready.mjs --record)`);
|
|
66
|
+
if (!["draft", "ready"].includes(h.status) && !h.claimed_by) errors.push(`${at}: ${h.status} but not claimed`);
|
|
67
|
+
if (h.status === "done") {
|
|
68
|
+
if (!h.pr) errors.push(`${at}: done without a PR`);
|
|
69
|
+
const proved = new Set();
|
|
70
|
+
for (const p of h.evidence ?? []) {
|
|
71
|
+
const e = results.get(resolve(dirname(f), p));
|
|
72
|
+
if (!e) { errors.push(`${at}: evidence ${p} not found under evidence/results`); continue; }
|
|
73
|
+
if (e.subject.kind !== "handoff" || e.subject.id !== h.id) errors.push(`${at}: evidence ${e.id} is about ${e.subject.kind} ${e.subject.id}`);
|
|
74
|
+
for (const r of e.results.filter((x) => x.passed)) {
|
|
75
|
+
const c = h.done_criteria.find((d) => d.id === r.criterion);
|
|
76
|
+
if (c?.kind === "review" && (r.by !== c.reviewer || r.by === h.role)) { errors.push(`${at}: review ${c.id} must be passed by ${c.reviewer}, not ${r.by ?? "nobody named"}`); continue; }
|
|
77
|
+
proved.add(r.criterion);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const d of h.done_criteria) if (!proved.has(d.id)) errors.push(`${at}: done, but criterion ${d.id} has no passing evidence`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
85
|
+
console.log(`${venture.id}: ${classes.size} decision classes, ${count} handoffs, ${results.size} evidence files: all execution rules hold.`);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Checks each venture's ownership-and-control records (EE-022): governance/ownership.json,
|
|
2
|
+
// board.json, founder-agreement.json and exit.json, against each other and against
|
|
3
|
+
// venture.json. These are records of legal facts; the check catches records that cannot
|
|
4
|
+
// all be true, not bad advice. Rules:
|
|
5
|
+
// ownership: ids unique; grants and instruments name known holders and classes; a
|
|
6
|
+
// preferred class states its issue price; a cliff is no longer than its vesting; a
|
|
7
|
+
// percent split adds up to 100; an S corporation has one class of stock (IRC 1361);
|
|
8
|
+
// an LLC counts units and a corporation shares (warning).
|
|
9
|
+
// founders: two or more founder holders need a founder agreement that lists exactly
|
|
10
|
+
// them; unsigned or IP not assigned is a warning.
|
|
11
|
+
// boards: at most one formal board; reserved matters only on it; a seat is appointed by
|
|
12
|
+
// a known holder or class, or by the board; an investor seat by one holding board-seat.
|
|
13
|
+
// venture: venture-capital funding needs a cap table and a formal board; angel funding
|
|
14
|
+
// a cap table; two or more people in a formalized venture should have one (warning).
|
|
15
|
+
// exit: scenario ids unique, and every scenario's payouts add up to its value.
|
|
16
|
+
//
|
|
17
|
+
// node schema/check-governance.mjs <workspace or venture dir>
|
|
18
|
+
|
|
19
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { payouts } from "./payouts.mjs";
|
|
22
|
+
|
|
23
|
+
const dir = process.argv[2];
|
|
24
|
+
if (!dir) { console.error("usage: node schema/check-governance.mjs <workspace or venture dir>"); process.exit(2); }
|
|
25
|
+
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
26
|
+
const walk = (d) => readdirSync(d, { withFileTypes: true }).flatMap((e) => e.name === "node_modules" || e.name.startsWith(".") ? []
|
|
27
|
+
: e.isDirectory() ? walk(join(d, e.name)) : e.name === "venture.json" ? [d] : []);
|
|
28
|
+
const errors = [], warnings = [];
|
|
29
|
+
let checked = 0;
|
|
30
|
+
|
|
31
|
+
for (const root of walk(dir)) {
|
|
32
|
+
const v = load(join(root, "venture.json"));
|
|
33
|
+
const g = (f) => { const p = join(root, "governance", f); return existsSync(p) ? load(p) : null; };
|
|
34
|
+
const own = g("ownership.json"), boards = g("board.json"), fa = g("founder-agreement.json"), exit = g("exit.json");
|
|
35
|
+
const at = (f) => `${v.id}/governance/${f}`;
|
|
36
|
+
const people = (v.members ?? []).filter((m) => typeof m === "string" || m.kind !== "agent").length;
|
|
37
|
+
const form = v.form ?? {};
|
|
38
|
+
if (form.funding === "venture-capital" && (!own || !boards?.boards.some((b) => b.kind === "formal"))) errors.push(`${v.id}: venture-capital funding needs governance/ownership.json and a formal board in governance/board.json`);
|
|
39
|
+
if (form.funding === "angel" && !own) errors.push(`${v.id}: angel funding needs governance/ownership.json`);
|
|
40
|
+
if (!own && people >= 2 && form.legal_form && form.legal_form !== "none") warnings.push(`${v.id}: ${people} people in a ${form.legal_form} but no governance/ownership.json`);
|
|
41
|
+
if (!own) { if (boards || fa || exit) errors.push(`${v.id}: board, founder agreement or exit records need governance/ownership.json`); continue; }
|
|
42
|
+
checked++;
|
|
43
|
+
|
|
44
|
+
const holders = new Map(), classes = new Map();
|
|
45
|
+
for (const h of own.holders) { if (holders.has(h.id)) errors.push(`${at("ownership.json")}: duplicate holder ${h.id}`); holders.set(h.id, h); }
|
|
46
|
+
for (const c of own.classes) { if (classes.has(c.id)) errors.push(`${at("ownership.json")}: duplicate class ${c.id}`); classes.set(c.id, c); }
|
|
47
|
+
for (const c of own.classes) if (c.kind === "preferred" && !(c.price_per_share > 0)) errors.push(`${at("ownership.json")}: preferred class ${c.id} needs price_per_share, the basis of its preference`);
|
|
48
|
+
for (const gr of own.grants) {
|
|
49
|
+
if (!holders.has(gr.holder)) errors.push(`${at("ownership.json")}: grant to unknown holder ${gr.holder}`);
|
|
50
|
+
if (!classes.has(gr.class)) errors.push(`${at("ownership.json")}: grant of unknown class ${gr.class}`);
|
|
51
|
+
if (gr.vesting && (gr.vesting.cliff_months ?? 0) > gr.vesting.months) errors.push(`${at("ownership.json")}: ${gr.holder}'s cliff is longer than their vesting`);
|
|
52
|
+
}
|
|
53
|
+
for (const i of own.instruments ?? []) if (!holders.has(i.holder)) errors.push(`${at("ownership.json")}: instrument ${i.id} held by unknown holder ${i.holder}`);
|
|
54
|
+
if (own.unit === "percent") { const sum = own.grants.reduce((n, gr) => n + gr.amount, 0); if (Math.abs(sum - 100) > 0.01) errors.push(`${at("ownership.json")}: a percent split adds up to ${sum}, not 100`); }
|
|
55
|
+
if (form.legal_form === "s-corp" && new Set(own.classes.map((c) => `${c.kind}:${c.price_per_share ?? ""}:${JSON.stringify(c.liquidation ?? {})}`)).size > 1) errors.push(`${at("ownership.json")}: an S corporation may have only one class of stock (classes may differ in votes only)`);
|
|
56
|
+
if (form.legal_form === "llc" && own.unit === "shares") warnings.push(`${at("ownership.json")}: an LLC usually counts units, not shares`);
|
|
57
|
+
if (["c-corp", "s-corp", "benefit-corp"].includes(form.legal_form) && own.unit === "units") warnings.push(`${at("ownership.json")}: a corporation counts shares, not units`);
|
|
58
|
+
|
|
59
|
+
const founders = own.holders.filter((h) => h.kind === "founder").map((h) => h.id);
|
|
60
|
+
if (founders.length >= 2 && !fa) errors.push(`${v.id}: ${founders.length} founders share ownership but there is no governance/founder-agreement.json`);
|
|
61
|
+
if (fa) {
|
|
62
|
+
const listed = fa.founders.map((f) => f.holder);
|
|
63
|
+
for (const f of founders) if (!listed.includes(f)) errors.push(`${at("founder-agreement.json")}: founder ${f} is not in the agreement`);
|
|
64
|
+
for (const f of listed) if (!founders.includes(f)) errors.push(`${at("founder-agreement.json")}: ${f} is not a founder holder in ownership.json`);
|
|
65
|
+
if (!fa.signed) warnings.push(`${at("founder-agreement.json")}: not signed yet`);
|
|
66
|
+
for (const f of fa.founders) if (!f.ip_assigned) warnings.push(`${at("founder-agreement.json")}: ${f.holder} has not assigned their IP to the venture`);
|
|
67
|
+
if (!existsSync(join(root, fa.document))) errors.push(`${at("founder-agreement.json")}: document ${fa.document} not found (paths are relative to the venture folder)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (boards) {
|
|
71
|
+
if (boards.boards.filter((b) => b.kind === "formal").length > 1) errors.push(`${at("board.json")}: more than one formal board`);
|
|
72
|
+
for (const b of boards.boards) {
|
|
73
|
+
if (b.kind !== "formal" && b.reserved_matters?.length) errors.push(`${at("board.json")}: advisory board ${b.id} has reserved matters; an advisory board decides nothing`);
|
|
74
|
+
for (const s of b.seats) {
|
|
75
|
+
const by = s.appointed_by;
|
|
76
|
+
if (by !== "board" && !holders.has(by) && !classes.has(by)) errors.push(`${at("board.json")}: seat ${s.id} appointed by unknown ${by}`);
|
|
77
|
+
if (s.type === "investor" && b.kind === "formal") {
|
|
78
|
+
const rights = classes.get(by)?.rights ?? (own.instruments ?? []).filter((i) => i.holder === by).flatMap((i) => i.rights ?? []).concat(own.grants.filter((gr) => gr.holder === by).flatMap((gr) => classes.get(gr.class)?.rights ?? []));
|
|
79
|
+
if (!rights.includes("board-seat")) errors.push(`${at("board.json")}: investor seat ${s.id} is appointed by ${by}, which holds no board-seat right`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (exit) {
|
|
86
|
+
const ids = (exit.scenarios ?? []).map((s) => s.id);
|
|
87
|
+
if (new Set(ids).size !== ids.length) errors.push(`${at("exit.json")}: duplicate scenario ids`);
|
|
88
|
+
for (const s of exit.scenarios ?? []) {
|
|
89
|
+
const r = payouts(own, s.value);
|
|
90
|
+
const sum = r.payouts.reduce((n, p) => n + p.amount, 0);
|
|
91
|
+
if (Math.abs(sum - s.value) > 1) errors.push(`${at("exit.json")}: scenario ${s.id} pays out ${sum}, not ${s.value}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const w of warnings) console.warn(`warning: ${w}`);
|
|
97
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
98
|
+
console.log(`${checked} ventures with ownership records: every governance rule holds.`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Checks every layer's tooling/integrations.json for the rules one entry's schema cannot
|
|
2
|
+
// state: ids are unique within a layer, each entry's layer matches its folder, and every
|
|
3
|
+
// entry under interface/ is an interface: it names its kind and reaches at least three of
|
|
4
|
+
// the five other layers in one session (four of six before execution folded into operations, EE-038). A tool that serves one or two layers is an
|
|
5
|
+
// integration of those layers (Obsidian under knowledge), however good its screen is.
|
|
6
|
+
//
|
|
7
|
+
// node schema/check-integrations.mjs
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
// EE_ROOT points the check at another copy of the repository (used by schema/test.mjs).
|
|
13
|
+
const root = process.env.EE_ROOT?.replace(/\/?$/, "/") ?? new URL("..", import.meta.url).pathname;
|
|
14
|
+
const MIN_REACH = 3;
|
|
15
|
+
const errors = [];
|
|
16
|
+
let count = 0;
|
|
17
|
+
for (const layer of readdirSync(root)) {
|
|
18
|
+
const f = join(root, layer, "tooling/integrations.json");
|
|
19
|
+
if (!existsSync(f)) continue;
|
|
20
|
+
const ids = new Set();
|
|
21
|
+
for (const t of JSON.parse(readFileSync(f, "utf8"))) {
|
|
22
|
+
count++;
|
|
23
|
+
if (ids.has(t.id)) errors.push(`${layer}: duplicate id ${t.id}`);
|
|
24
|
+
ids.add(t.id);
|
|
25
|
+
if (t.layer !== layer) errors.push(`${layer}/${t.id}: layer says ${t.layer}`);
|
|
26
|
+
if (layer === "interface") {
|
|
27
|
+
if (!t.interface_kind) errors.push(`interface/${t.id}: no interface_kind`);
|
|
28
|
+
if ((t.reach ?? []).length < MIN_REACH) errors.push(`interface/${t.id}: reaches ${(t.reach ?? []).length} layers; an interface reaches at least ${MIN_REACH}, so list it under the layers it serves`);
|
|
29
|
+
} else if (t.reach || t.interface_kind) errors.push(`${layer}/${t.id}: reach and interface_kind belong to interface entries only`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
33
|
+
console.log(`${count} integrations: every interface reaches at least ${MIN_REACH} layers.`);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Checks docs/design/naive-nextjs-status.json against the Next.js translation ledger and the code: every
|
|
3
|
+
// concept row in the ledger has exactly one status; a built or partial row cites files that exist and a
|
|
4
|
+
// docs page that exists; and the generated "What's built" page matches the data.
|
|
5
|
+
//
|
|
6
|
+
// node schema/check-nextjs-status.mjs check, and print the counts
|
|
7
|
+
// node schema/check-nextjs-status.mjs --write write site/content/docs/get-started/whats-built.mdx
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
const repo = new URL("..", import.meta.url).pathname;
|
|
13
|
+
const LEDGER = "docs/design/NAIVE_NEXTJS_TRANSLATION_2026-10-28.md";
|
|
14
|
+
const STATUS = "docs/design/naive-nextjs-status.json";
|
|
15
|
+
const PAGE = "site/content/docs/get-started/whats-built.mdx";
|
|
16
|
+
const STATES = ["built", "partial", "not built", "n/a"];
|
|
17
|
+
|
|
18
|
+
const ids = [...readFileSync(join(repo, LEDGER), "utf8").matchAll(/^\| ([A-Z]-\d+) \|/gm)].map((m) => m[1]);
|
|
19
|
+
const data = JSON.parse(readFileSync(join(repo, STATUS), "utf8"));
|
|
20
|
+
const errors = [];
|
|
21
|
+
const seen = new Map();
|
|
22
|
+
for (const r of data.rows) {
|
|
23
|
+
if (seen.has(r.id)) errors.push(`${r.id} has two entries`);
|
|
24
|
+
seen.set(r.id, r);
|
|
25
|
+
if (!ids.includes(r.id)) errors.push(`${r.id} is not a row of the ledger`);
|
|
26
|
+
if (!STATES.includes(r.status)) errors.push(`${r.id}: status ${r.status} is not one of ${STATES.join(", ")}`);
|
|
27
|
+
if (r.status === "built" || r.status === "partial") {
|
|
28
|
+
if (!r.evidence?.length) errors.push(`${r.id} is ${r.status} but cites no file`);
|
|
29
|
+
for (const f of r.evidence ?? []) if (!existsSync(join(repo, f))) errors.push(`${r.id} cites ${f}, which does not exist`);
|
|
30
|
+
if (!r.page) errors.push(`${r.id} is ${r.status} but names no docs page`);
|
|
31
|
+
else if (!existsSync(join(repo, "site/content/docs", `${r.page}.mdx`))) errors.push(`${r.id} names page ${r.page}, which does not exist`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const id of ids) if (!seen.has(id)) errors.push(`${id} is in the ledger but has no status`);
|
|
35
|
+
|
|
36
|
+
const count = (s) => data.rows.filter((r) => r.status === s).length;
|
|
37
|
+
const counts = Object.fromEntries(STATES.map((s) => [s, count(s)]));
|
|
38
|
+
|
|
39
|
+
function page() {
|
|
40
|
+
const chapters = [...new Set(data.rows.map((r) => r.chapter))];
|
|
41
|
+
const cell = (s) => String(s ?? "").replace(/\|/g, "\\|");
|
|
42
|
+
const lines = [
|
|
43
|
+
"---",
|
|
44
|
+
"title: What's built",
|
|
45
|
+
"description: Which of the 234 concepts translated from Next.js work today, which partly work, and which are design only.",
|
|
46
|
+
"type: reference",
|
|
47
|
+
"status: draft",
|
|
48
|
+
`lastReviewed: ${data.checked}`,
|
|
49
|
+
"owner: builder",
|
|
50
|
+
"---",
|
|
51
|
+
"",
|
|
52
|
+
`Use this page to tell what you can run today from what is designed but not built. Every concept in the Next.js translation ledger is listed, in the ledger's chapter order. It is generated from \`${STATUS}\` by \`node schema/check-nextjs-status.mjs --write\`, and the test suite fails when the two disagree.`,
|
|
53
|
+
"",
|
|
54
|
+
"## Summary",
|
|
55
|
+
"",
|
|
56
|
+
"| Status | Concepts | Meaning |",
|
|
57
|
+
"| --- | --- | --- |",
|
|
58
|
+
`| built | ${counts.built} | Works end to end and is tested |`,
|
|
59
|
+
`| partial | ${counts.partial} | Some of it works; the note says what is missing |`,
|
|
60
|
+
`| not built | ${counts["not built"]} | Designed in the ledger, not in the code yet |`,
|
|
61
|
+
`| n/a | ${counts["n/a"]} | The ledger marks it as not converting from Next.js |`,
|
|
62
|
+
"",
|
|
63
|
+
];
|
|
64
|
+
for (const c of chapters) {
|
|
65
|
+
const rows = data.rows.filter((r) => r.chapter === c);
|
|
66
|
+
lines.push(`## ${c.replace(/^Chapter \d+: /, "")}`, "", "| Concept | Status | Explained in | Note |", "| --- | --- | --- | --- |");
|
|
67
|
+
for (const r of rows) lines.push(`| ${r.id} | ${r.status} | ${r.page && r.page !== "get-started/whats-built" ? `[${r.page.split("/").at(-1)}](/docs/${r.page})` : ""} | ${cell(r.note)} |`);
|
|
68
|
+
lines.push("");
|
|
69
|
+
}
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (process.argv.includes("--write")) { writeFileSync(join(repo, PAGE), page()); console.log(`wrote ${PAGE}`); }
|
|
74
|
+
else if (!existsSync(join(repo, PAGE)) || readFileSync(join(repo, PAGE), "utf8") !== page()) errors.push(`${PAGE} is out of date: run node schema/check-nextjs-status.mjs --write`);
|
|
75
|
+
|
|
76
|
+
if (errors.length) { console.error(errors.join("\n")); process.exit(1); }
|
|
77
|
+
console.log(`${data.rows.length} concepts: ${STATES.map((s) => `${counts[s]} ${s}`).join(", ")}`);
|