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,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: knowledge
|
|
3
|
+
tags: [everything-ecosystem, mentura, skills, interface, install, mcp, cli]
|
|
4
|
+
ai-model: claude-opus-5.5
|
|
5
|
+
created: 2026-10-27T20:52:58Z
|
|
6
|
+
source: hybrid
|
|
7
|
+
name: install-mentura
|
|
8
|
+
description: >
|
|
9
|
+
Install Mentura (the Everything Ecosystem) into a coding agent: the MCP server
|
|
10
|
+
for Claude Code or Cursor, the CLI, and a first venture, using the whole system
|
|
11
|
+
or only the layers the builder wants (EE-030).
|
|
12
|
+
MANDATORY TRIGGERS: install mentura, set up mentura, add the everything
|
|
13
|
+
ecosystem, use mentura in claude code, use mentura in cursor, mentura mcp,
|
|
14
|
+
adopt one layer, only the validation kit, start a venture.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Install Mentura
|
|
18
|
+
|
|
19
|
+
Interface layer (EE-030). Mentura is a set of records, checks and kits in a git repository. This skill connects an agent harness to it. Every interface (CLI, MCP server, HTTP API) goes through `interface/router.mjs`, so each one has the same routes and gates. The routes are listed in `docs/reference/ROUTES.md`.
|
|
20
|
+
|
|
21
|
+
## 1. Ask what the builder wants to adopt
|
|
22
|
+
|
|
23
|
+
Partial adoption is the default. Ask which of these they want, and do not install more:
|
|
24
|
+
|
|
25
|
+
- **The whole system:** every layer, with the phase defaults.
|
|
26
|
+
- **One layer:** for example `purpose` alone, to state a telos and validate.
|
|
27
|
+
- **One kit inside a layer:** for example the validation kit. Turn on its layer only; the kit's routes need nothing else.
|
|
28
|
+
|
|
29
|
+
A layer the builder leaves off is written as `false` in `venture.json`. The need-now score then lists that layer's records as "layer off" instead of suggesting them.
|
|
30
|
+
|
|
31
|
+
## 2. Get the repository and check it runs
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
git clone <the Mentura repository> mentura
|
|
35
|
+
cd mentura && node test.mjs # Node 20+ and python3 with jsonschema (pip install jsonschema)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 3. Start a venture, or point at existing ones
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
node interface/cli/mentura.mjs init <folder>/my-venture --id my-venture --layers purpose
|
|
42
|
+
node interface/cli/mentura.mjs system/need-now run --venture my-venture --workspace <folder>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`init` creates new ventures only: it refuses a directory that already has files. To bring an existing project in, create the venture, then run `node interface/cli/naive.mjs port --from <project> --venture <folder>/my-venture`, read the plan it writes, and apply it with `--apply --confirm port`. To have an agent rewrite the project as Naive records instead, use `naive.mjs migrate` with the same flags, have the founder settle each `migrate-<function>` decision, then run `migrate --venture <folder>/my-venture --apply`. Never run `init` on someone's existing project.
|
|
46
|
+
|
|
47
|
+
To use the whole system, leave out `--layers`. The workspace is the folder that holds the ventures. Set `MENTURA_WORKSPACE` to it so that you do not have to pass `--workspace` each time.
|
|
48
|
+
|
|
49
|
+
## 4. Connect the harness
|
|
50
|
+
|
|
51
|
+
**Claude Code** (one command; add `--scope project` to share it in the venture's repo through `.mcp.json`):
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
claude mcp add mentura --env MENTURA_WORKSPACE=<folder> --env MENTURA_HARNESS="Claude Code" -- node <mentura>/interface/mcp/server.mjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Cursor** (`.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` for every project):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"mentura": {
|
|
63
|
+
"command": "node",
|
|
64
|
+
"args": ["<mentura>/interface/mcp/server.mjs"],
|
|
65
|
+
"env": { "MENTURA_WORKSPACE": "<folder>", "MENTURA_HARNESS": "Cursor" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Any other harness or script:** use the CLI (`node interface/cli/mentura.mjs help`). You can also run `node interface/api/server.mjs`. It listens only on 127.0.0.1:4330 and has no authentication, so keep it on the builder's own machine.
|
|
72
|
+
|
|
73
|
+
Use absolute paths in both configs. Check the connection by asking the agent to call `mentura_ventures`, then `mentura_view` on `system/overview`. Before an agent acts on a route, it should call `mentura_layout` for that route: it returns the kit's skills, references, template and gate.
|
|
74
|
+
|
|
75
|
+
## 5. Tell the builder how gates work
|
|
76
|
+
|
|
77
|
+
- An agent's write to a gated record becomes a draft in `<venture>/_drafts/`. The gated records are ownership, board, founder agreement, exit, telos, decision rights and publication policy. Writes through the MCP server and the API always count as an agent's.
|
|
78
|
+
- A person accepts a draft with `mentura accept <route> --venture <id>`.
|
|
79
|
+
- The CLI cannot tell whether a person or an agent is typing. The real gate is the repository's review: a person merges the pull request.
|
|
80
|
+
- Every write is logged in `<venture>/provenance.jsonl` (EE-031): who wrote it, through which interface, what it came from and the decision it acted under. Set `MENTURA_ACTOR_ID`, `MENTURA_MODEL`, `MENTURA_SESSION` and `MENTURA_HARNESS` in the harness config so that each line names the agent. The MCP server also records the harness name that the client reports. For a venture that existed before the log, run `mentura baseline --venture <id>` once.
|
|
81
|
+
- Every write is checked against the record's schema first. A record that does not match is refused, and nothing changes.
|
|
82
|
+
|
|
83
|
+
## Sources
|
|
84
|
+
|
|
85
|
+
- W3C PROV-DM, the provenance data model: https://www.w3.org/TR/prov-dm/ (checked 2026-10-27).
|
|
86
|
+
- Model Context Protocol specification: https://modelcontextprotocol.io/specification/2025-06-18 (checked 2026-10-27).
|
|
87
|
+
- Claude Code MCP setup (`claude mcp add`, scopes): https://docs.claude.com/en/docs/claude-code/mcp (checked 2026-10-27).
|
|
88
|
+
- Cursor MCP configuration (`.cursor/mcp.json`): https://docs.cursor.com/context/model-context-protocol (checked 2026-10-27).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "claude-code",
|
|
4
|
+
"name": "Claude Code",
|
|
5
|
+
"layer": "interface",
|
|
6
|
+
"role": "default interface: agent sessions over the vault, repos, skills and connectors",
|
|
7
|
+
"interfaces": [
|
|
8
|
+
"cli",
|
|
9
|
+
"app"
|
|
10
|
+
],
|
|
11
|
+
"reach": [
|
|
12
|
+
"purpose",
|
|
13
|
+
"knowledge",
|
|
14
|
+
"operations",
|
|
15
|
+
"evidence",
|
|
16
|
+
"governance"
|
|
17
|
+
],
|
|
18
|
+
"interface_kind": "agent-harness",
|
|
19
|
+
"status": "in-use",
|
|
20
|
+
"reason": "Reads and writes the vault and every repo, runs skills and hooks, reaches Linear, Notion and GitHub through connectors"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "cursor",
|
|
24
|
+
"name": "Cursor",
|
|
25
|
+
"layer": "interface",
|
|
26
|
+
"role": "editor-centred agent harness over the repos and vault",
|
|
27
|
+
"interfaces": [
|
|
28
|
+
"app"
|
|
29
|
+
],
|
|
30
|
+
"reach": [
|
|
31
|
+
"purpose",
|
|
32
|
+
"knowledge",
|
|
33
|
+
"operations",
|
|
34
|
+
"evidence",
|
|
35
|
+
"governance"
|
|
36
|
+
],
|
|
37
|
+
"interface_kind": "agent-harness",
|
|
38
|
+
"status": "in-use",
|
|
39
|
+
"reason": ".cursor/ rules and agents in the vault and repos"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "codex",
|
|
43
|
+
"name": "OpenAI Codex",
|
|
44
|
+
"layer": "interface",
|
|
45
|
+
"role": "agent harness (CLI and app)",
|
|
46
|
+
"interfaces": [
|
|
47
|
+
"cli",
|
|
48
|
+
"app"
|
|
49
|
+
],
|
|
50
|
+
"reach": [
|
|
51
|
+
"knowledge",
|
|
52
|
+
"operations",
|
|
53
|
+
"evidence"
|
|
54
|
+
],
|
|
55
|
+
"interface_kind": "agent-harness",
|
|
56
|
+
"status": "candidate",
|
|
57
|
+
"reason": "Named by the owner; same shape as Claude Code"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "hermes-agent",
|
|
61
|
+
"name": "Hermes Agent",
|
|
62
|
+
"layer": "interface",
|
|
63
|
+
"role": "open-source agent harness",
|
|
64
|
+
"interfaces": [
|
|
65
|
+
"cli"
|
|
66
|
+
],
|
|
67
|
+
"reach": [
|
|
68
|
+
"knowledge",
|
|
69
|
+
"operations",
|
|
70
|
+
"evidence"
|
|
71
|
+
],
|
|
72
|
+
"interface_kind": "agent-harness",
|
|
73
|
+
"status": "candidate",
|
|
74
|
+
"reason": "Named by the owner; reach to confirm"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "grok",
|
|
78
|
+
"name": "Grok agent",
|
|
79
|
+
"layer": "interface",
|
|
80
|
+
"role": "agent harness",
|
|
81
|
+
"interfaces": [
|
|
82
|
+
"app"
|
|
83
|
+
],
|
|
84
|
+
"reach": [
|
|
85
|
+
"knowledge",
|
|
86
|
+
"operations",
|
|
87
|
+
"evidence"
|
|
88
|
+
],
|
|
89
|
+
"interface_kind": "agent-harness",
|
|
90
|
+
"status": "candidate",
|
|
91
|
+
"reason": "Named by the owner as Grokbot; which xAI product is meant, and its reach, to confirm"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "operator-console",
|
|
95
|
+
"name": "Operator console",
|
|
96
|
+
"layer": "interface",
|
|
97
|
+
"role": "local viewer over the records",
|
|
98
|
+
"interfaces": [
|
|
99
|
+
"app"
|
|
100
|
+
],
|
|
101
|
+
"status": "candidate",
|
|
102
|
+
"reason": "interface/operator-console",
|
|
103
|
+
"reach": [
|
|
104
|
+
"purpose",
|
|
105
|
+
"knowledge",
|
|
106
|
+
"operations",
|
|
107
|
+
"evidence",
|
|
108
|
+
"governance"
|
|
109
|
+
],
|
|
110
|
+
"interface_kind": "custom"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
source: hybrid
|
|
4
|
+
created: 2026-10-28T01:10:00Z
|
|
5
|
+
ai-model: claude-opus-5.5
|
|
6
|
+
tags: [everything-ecosystem, knowledge, connectors, storage]
|
|
7
|
+
description: "How the knowledge layer reaches the sources a venture keeps records in: the connector contract, the source map, the catalog and the evals (EE-037)."
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Knowledge connectors
|
|
11
|
+
|
|
12
|
+
The knowledge layer is where every record is stored and found. It does not copy records into one place. Each venture keeps its records wherever it already works, and Mentura reaches each source through a connector (EE-037).
|
|
13
|
+
|
|
14
|
+
## The parts
|
|
15
|
+
|
|
16
|
+
| Part | File | What it does |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Contract | `contract.mjs` | The five things every connector does: `list`, `read`, `version`, `search`, `write`. A version is the git blob SHA-1 of the content in every source. |
|
|
19
|
+
| Registry | `../tooling/integrations.json` | An integration with a `connector` names the module that implements the contract for it. |
|
|
20
|
+
| Source map | `<venture>/knowledge/sources.json` | Optional. The venture's sources, and which record type lives in which. Unnamed record types stay in the venture folder. Schema: `schema/knowledge-sources.schema.json`; checked by `schema/check-sources.mjs`. |
|
|
21
|
+
| Connectors | `filesystem.mjs` | A folder or git repository (writes in place). The framework ships only this one: it works offline and names no vendor. Connectors that reach a vendor, such as GitHub, are platform and live in `platform/connectors/` (EE-041). |
|
|
22
|
+
| Matrix | `resolve.mjs` | The source-of-truth matrix (EE-042): `rules` in the source map name a system of record and owner per record type, attribute and context (phase, product, account, actor). The most specific rule wins; a tie is an error. `records` is shorthand for rules that match anything. An attribute rule (a price in another source) is read from and written to that source, so the record's own file holds no second copy. |
|
|
23
|
+
| Where-used | `<venture>/knowledge/where-used.json` | For each fact kept once (a price, a product name), every place that repeats it, live or frozen. `schema/check-where-used.mjs` reports drift offline; delivering the change into vendors is the platform's. |
|
|
24
|
+
| Catalog | `catalog.mjs` | One index over every source: address, version, route, title and words. No content. Routes `knowledge/catalog` and `knowledge/search`. |
|
|
25
|
+
| Evals | `suite.mjs`, `eval.mjs` | `suite.mjs` is the contract suite every connector passes, framework or platform. `eval.mjs` runs it on the filesystem connector, then the router over a mapped folder and the catalog. The GitHub connector's evals are `platform/connectors/eval.mjs`. |
|
|
26
|
+
|
|
27
|
+
## A source map
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"sources": [
|
|
32
|
+
{ "id": "team-repo", "connector": "github", "repo": "acme/tandem", "ref": "main", "root": "ventures/tandem" }
|
|
33
|
+
],
|
|
34
|
+
"records": { "product": "team-repo" }
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `github` connector is a platform connector (`platform/connectors/github.mjs`). With this map, `purpose/product/kettle` reads `ventures/tandem/products/kettle.json` from `acme/tandem` and says which version and commit it came from. A write opens a draft pull request, which a person accepts by merging. Every other record stays in the venture folder. The venture folder always keeps `venture.json`, the provenance log and drafts.
|
|
39
|
+
|
|
40
|
+
## Adding a connector
|
|
41
|
+
|
|
42
|
+
1. Decide which side it is on. If it works offline, needs no credentials and names no vendor, it is framework and goes in `knowledge/connectors/<id>.mjs`. Otherwise it is platform and goes in `platform/connectors/<id>.mjs` (EE-041). `node schema/check-boundary.mjs` enforces this.
|
|
43
|
+
2. Export `connect(options)`, returning an object that meets the contract.
|
|
44
|
+
3. Add a `connector` to its entry in `knowledge/tooling/integrations.json`: the module, its write modes and its options.
|
|
45
|
+
4. Run it through `contract()` from `suite.mjs`: against a fake of its API offline, and against the real one when credentials are given.
|
|
46
|
+
|
|
47
|
+
## Running the evals
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
node knowledge/connectors/eval.mjs # framework
|
|
51
|
+
node platform/connectors/eval.mjs # platform (GitHub, against a fake)
|
|
52
|
+
MENTURA_EVAL_GITHUB_REPO=owner/name GITHUB_TOKEN=... node platform/connectors/eval.mjs # also live
|
|
53
|
+
MENTURA_EVAL_GITHUB_WRITE=1 ... # and open one real draft PR
|
|
54
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// The catalog (EE-037): one index over every item in every source of a venture, so a person or an
|
|
2
|
+
// agent can find a record without knowing where it lives. For each item it keeps the address,
|
|
3
|
+
// source, path, version, route and record type (when the item is a record), a title and the set
|
|
4
|
+
// of words in it. It keeps no content: the words are a sorted set, not the text, and reading an
|
|
5
|
+
// item always goes back to its source. A rebuild reads only items whose version changed.
|
|
6
|
+
//
|
|
7
|
+
// node knowledge/connectors/catalog.mjs <venture dir> [--json] build or refresh
|
|
8
|
+
// node knowledge/connectors/catalog.mjs <venture dir> --search <q> [--json] [--live]
|
|
9
|
+
// search the catalog (built first if there is none); --live asks each source instead
|
|
10
|
+
//
|
|
11
|
+
// The catalog is written to <venture>/_catalog/catalog.json. It is derived, so a venture may
|
|
12
|
+
// ignore it in git; the router never routes _ folders.
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { address, isText, words } from "./contract.mjs";
|
|
17
|
+
import { connectAll } from "./index.mjs";
|
|
18
|
+
|
|
19
|
+
export const FILE = "_catalog/catalog.json";
|
|
20
|
+
const MAX_BYTES = 1_000_000;
|
|
21
|
+
|
|
22
|
+
function title(path, content) {
|
|
23
|
+
if (path.endsWith(".json")) {
|
|
24
|
+
try { const j = JSON.parse(content); const t = j && typeof j === "object" && !Array.isArray(j) ? j.name ?? j.title ?? j.statement ?? j.id : null; if (typeof t === "string") return t.slice(0, 160); } catch {}
|
|
25
|
+
}
|
|
26
|
+
const fm = content.match(/^---\n[\s\S]*?\ntitle:\s*["']?(.+?)["']?\n[\s\S]*?\n---/);
|
|
27
|
+
if (fm) return fm[1];
|
|
28
|
+
const h = content.match(/^#\s+(.+)$/m);
|
|
29
|
+
return h ? h[1].trim() : path.split("/").at(-1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Which route a file in a source belongs to, from the route table (lazy, so the catalog works
|
|
33
|
+
// without the router for a plain folder).
|
|
34
|
+
let table = null;
|
|
35
|
+
async function routeOf(path) {
|
|
36
|
+
if (!table) {
|
|
37
|
+
const { routes, glob } = await import("../../interface/router.mjs");
|
|
38
|
+
table = routes().filter((r) => r.kind === "record" && r.collection !== "list").map((r) => ({ r, res: r.files.map((g) => glob(g)) }));
|
|
39
|
+
}
|
|
40
|
+
for (const { r, res } of table) {
|
|
41
|
+
if (!res.some((re) => re.test(path))) continue;
|
|
42
|
+
if (r.collection !== "item") return { route: r.route, record: r.record };
|
|
43
|
+
for (const g of r.files) {
|
|
44
|
+
if (!g.includes("*")) continue;
|
|
45
|
+
const m = path.match(new RegExp("^" + g.split("*").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&")).join("([^/]*)") + "$"));
|
|
46
|
+
if (m) return { route: r.route.replace("[id]", m[1]), record: r.record };
|
|
47
|
+
}
|
|
48
|
+
return { route: r.route, record: r.record };
|
|
49
|
+
}
|
|
50
|
+
return { route: null, record: null };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const load = (dir) => existsSync(join(dir, FILE)) ? JSON.parse(readFileSync(join(dir, FILE), "utf8")) : null;
|
|
54
|
+
|
|
55
|
+
/** Builds or refreshes the catalog. Returns it with stats: items, read (items fetched), reused. */
|
|
56
|
+
export async function build(dir, { write = true, sources } = {}) {
|
|
57
|
+
const prior = new Map((load(dir)?.items ?? []).map((x) => [x.address, x]));
|
|
58
|
+
const conns = sources ?? await connectAll(dir);
|
|
59
|
+
const items = [];
|
|
60
|
+
const stats = { sources: conns.length, items: 0, read: 0, reused: 0, skipped: 0, errors: [] };
|
|
61
|
+
for (const { source, connector } of conns) {
|
|
62
|
+
let listed;
|
|
63
|
+
try { listed = await connector.list(); } catch (e) { stats.errors.push(`${source.id}: ${e.message}`); continue; }
|
|
64
|
+
for (const { path, version } of listed) {
|
|
65
|
+
const at = address(source.id, path);
|
|
66
|
+
const old = prior.get(at);
|
|
67
|
+
const where = await routeOf(path);
|
|
68
|
+
if (old && old.version === version) { items.push({ ...old, ...where }); stats.reused++; continue; }
|
|
69
|
+
if (!isText(path)) { items.push({ address: at, source: source.id, path, version, ...where, title: path.split("/").at(-1), terms: words(path) }); stats.skipped++; continue; }
|
|
70
|
+
const got = await connector.read(path);
|
|
71
|
+
stats.read++;
|
|
72
|
+
if (!got || got.content.length > MAX_BYTES || !isText(path, got.content)) { items.push({ address: at, source: source.id, path, version, ...where, title: path.split("/").at(-1), terms: words(path) }); continue; }
|
|
73
|
+
items.push({ address: at, source: source.id, path, version: got.version, ...where, title: title(path, got.content), terms: [...new Set(words(`${path}\n${got.content}`))].sort() });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
stats.items = items.length;
|
|
77
|
+
const catalog = { built_at: new Date().toISOString(), sources: conns.map(({ source }) => ({ id: source.id, connector: source.connector })), items };
|
|
78
|
+
if (write) { mkdirSync(join(dir, "_catalog"), { recursive: true }); writeFileSync(join(dir, FILE), JSON.stringify(catalog) + "\n"); }
|
|
79
|
+
return { catalog, stats };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Items holding every word of the query, best first: words in the title count double. */
|
|
83
|
+
export function search(catalog, query, { limit = 20 } = {}) {
|
|
84
|
+
const q = words(query);
|
|
85
|
+
if (!q.length) return [];
|
|
86
|
+
return catalog.items.map((x) => {
|
|
87
|
+
const terms = new Set(x.terms), t = words(x.title).join(" ");
|
|
88
|
+
if (!q.every((w) => terms.has(w) || x.terms.some((y) => y.startsWith(w)))) return null;
|
|
89
|
+
return { score: q.reduce((s, w) => s + (terms.has(w) ? 2 : 1) + (t.includes(w) ? 2 : 0), 0), ...x };
|
|
90
|
+
}).filter(Boolean).sort((a, b) => b.score - a.score || a.address.localeCompare(b.address)).slice(0, limit)
|
|
91
|
+
.map(({ terms, ...x }) => x);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Asks each source's own search instead of the catalog. */
|
|
95
|
+
export async function searchLive(dir, query, { limit = 20 } = {}) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const { source, connector } of await connectAll(dir)) {
|
|
98
|
+
try { for (const x of await connector.search(query, { limit })) out.push({ address: address(source.id, x.path), source: source.id, path: x.path, version: x.version, ...(await routeOf(x.path)) }); }
|
|
99
|
+
catch (e) { out.push({ source: source.id, error: e.message }); }
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
105
|
+
const [dir] = process.argv.slice(2).filter((a, i, all) => !a.startsWith("--") && all[i - 1] !== "--search");
|
|
106
|
+
const i = process.argv.indexOf("--search");
|
|
107
|
+
const json = process.argv.includes("--json");
|
|
108
|
+
if (!dir) { console.error("usage: node knowledge/connectors/catalog.mjs <venture dir> [--search <q>] [--live] [--json]"); process.exit(2); }
|
|
109
|
+
if (i >= 0) {
|
|
110
|
+
const q = process.argv[i + 1] ?? "";
|
|
111
|
+
const hits = process.argv.includes("--live") ? await searchLive(dir, q) : search(load(dir) ?? (await build(dir)).catalog, q);
|
|
112
|
+
console.log(json ? JSON.stringify({ query: q, hits }, null, 2) : hits.map((h) => `${h.address} ${h.title ?? ""}${h.route ? ` (${h.route})` : ""}`).join("\n") || "no items match");
|
|
113
|
+
} else {
|
|
114
|
+
const { stats } = await build(dir);
|
|
115
|
+
console.log(json ? JSON.stringify(stats, null, 2) : `catalog: ${stats.items} items from ${stats.sources} sources (${stats.read} read, ${stats.reused} unchanged)${stats.errors.length ? `\n${stats.errors.join("\n")}` : ""}`);
|
|
116
|
+
if (stats.errors.length) process.exitCode = 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The connector contract (EE-037). A connector reaches one source of records (a folder, a GitHub
|
|
2
|
+
// repository, later a database, Notion or Salesforce) and does the same five things for every one:
|
|
3
|
+
//
|
|
4
|
+
// list(prefix) [{ path, version }] for every item under the prefix, sorted by path
|
|
5
|
+
// read(path) { path, content, version, commit?, permalink? }, or null when it is missing
|
|
6
|
+
// version(path) the item's version, or null when it is missing
|
|
7
|
+
// search(query, {limit}) [{ path, version }] for items whose text holds every word of the query
|
|
8
|
+
// write(path, content, {message, title})
|
|
9
|
+
// { path, version, commit?, pull_request? }; how it writes is the
|
|
10
|
+
// connector's `writes`: direct (in place), pull-request (a draft pull
|
|
11
|
+
// request; merging it accepts the write) or none (read-only, write throws)
|
|
12
|
+
//
|
|
13
|
+
// Paths are relative to the source's root and use forward slashes; a path that leaves the root is
|
|
14
|
+
// refused. Content is UTF-8 text. A version is the git blob SHA-1 of the content, whichever
|
|
15
|
+
// connector holds it, so the same content has the same version in every source and a version
|
|
16
|
+
// can be checked without asking the source. An item's address is <source id>://<path>; with its
|
|
17
|
+
// version it is what provenance cites.
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
|
|
21
|
+
export const METHODS = ["list", "read", "version", "search", "write"];
|
|
22
|
+
export const WRITES = ["direct", "pull-request", "none"];
|
|
23
|
+
|
|
24
|
+
/** The git blob SHA-1 of some content: what `git hash-object` prints. */
|
|
25
|
+
export const blobSha = (content) => {
|
|
26
|
+
const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
|
|
27
|
+
return createHash("sha1").update(`blob ${buf.length}\0`).update(buf).digest("hex");
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** A path inside a source, normalised, or an error for one that is empty or leaves the root. */
|
|
31
|
+
export function safePath(p) {
|
|
32
|
+
if (typeof p !== "string" || !p.length || p.includes("\0") || p.includes("\\") || p.startsWith("/")) throw new Error(`${JSON.stringify(p)} is not a path inside the source`);
|
|
33
|
+
const parts = p.split("/").filter((s) => s && s !== ".");
|
|
34
|
+
if (!parts.length || parts.includes("..")) throw new Error(`${p} is not a path inside the source`);
|
|
35
|
+
return parts.join("/");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const address = (sourceId, path) => `${sourceId}://${path}`;
|
|
39
|
+
|
|
40
|
+
/** Every word of the query, lowercased, appears in the text. */
|
|
41
|
+
export const matches = (text, query) => {
|
|
42
|
+
const t = text.toLowerCase();
|
|
43
|
+
return words(query).every((w) => t.includes(w));
|
|
44
|
+
};
|
|
45
|
+
export const words = (s) => String(s).toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => w.length > 1);
|
|
46
|
+
|
|
47
|
+
/** Text a catalog may index: small files that are not binary. */
|
|
48
|
+
export const isText = (path, content) => !/\.(png|jpe?g|gif|webp|ico|pdf|zip|gz|woff2?|ttf|mp4|mov)$/i.test(path) && (content === undefined || !content.includes("\0"));
|
|
49
|
+
|
|
50
|
+
/** Checks an object has the contract's methods and a known `writes`. Returns the problems. */
|
|
51
|
+
export function conformance(c) {
|
|
52
|
+
const problems = METHODS.filter((m) => typeof c?.[m] !== "function").map((m) => `no ${m}()`);
|
|
53
|
+
if (!WRITES.includes(c?.writes)) problems.push(`writes is ${c?.writes}, not one of ${WRITES.join(", ")}`);
|
|
54
|
+
if (typeof c?.id !== "string") problems.push("no id");
|
|
55
|
+
return problems;
|
|
56
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Evals for the framework's connectors (EE-037, EE-041). The framework ships one connector, the
|
|
2
|
+
// file system, which works offline and names no vendor; connectors that reach a vendor are
|
|
3
|
+
// platform and have their own evals (platform/connectors/eval.mjs), against the same suite.
|
|
4
|
+
// Three parts:
|
|
5
|
+
//
|
|
6
|
+
// contract the shared suite (suite.mjs) on the filesystem connector, in a plain folder and in
|
|
7
|
+
// a git repository, plus the venture-folder rules
|
|
8
|
+
// router a venture whose source map keeps one record type in another folder: reads come
|
|
9
|
+
// from there with their version, a write lands there and is logged by address,
|
|
10
|
+
// nothing invalid reaches the source, and every other record stays in the venture
|
|
11
|
+
// catalog one index across both sources, and a rebuild reads only what changed
|
|
12
|
+
//
|
|
13
|
+
// node knowledge/connectors/eval.mjs
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { blobSha } from "./contract.mjs";
|
|
19
|
+
import { contract, FIXTURE, harness, OUTSIDE } from "./suite.mjs";
|
|
20
|
+
import { connect as fsConnect } from "./filesystem.mjs";
|
|
21
|
+
import { build, FILE, load, search } from "./catalog.mjs";
|
|
22
|
+
|
|
23
|
+
const repo = new URL("../..", import.meta.url).pathname;
|
|
24
|
+
const h = harness("connectors");
|
|
25
|
+
const { expect, write, scratch, finish } = h;
|
|
26
|
+
|
|
27
|
+
// The filesystem connector, in a plain folder.
|
|
28
|
+
const fsMake = (git) => async ({ writes = "direct" } = {}) => {
|
|
29
|
+
const d = mkdtempSync(join(scratch, "fs-"));
|
|
30
|
+
write(join(d, "kb"), FIXTURE);
|
|
31
|
+
write(d, OUTSIDE);
|
|
32
|
+
if (git) {
|
|
33
|
+
const g = (...a) => spawnSync("git", ["-C", d, ...a], { encoding: "utf8" });
|
|
34
|
+
g("init", "-q"); g("add", "."); g("-c", "user.name=eval", "-c", "user.email=eval@example.com", "commit", "-qm", "fixture");
|
|
35
|
+
}
|
|
36
|
+
return { c: fsConnect({ id: "folder", root: join(d, "kb"), writes }), dir: d };
|
|
37
|
+
};
|
|
38
|
+
await contract(h, "filesystem (plain folder)", fsMake(false));
|
|
39
|
+
const gitT = await contract(h, "filesystem (git repository)", fsMake(true));
|
|
40
|
+
{
|
|
41
|
+
const fresh = await fsMake(true)();
|
|
42
|
+
const r = await fresh.c.read("notes/beta.md");
|
|
43
|
+
const head = spawnSync("git", ["-C", fresh.dir, "rev-parse", "HEAD"], { encoding: "utf8" }).stdout.trim();
|
|
44
|
+
expect("filesystem (git repository): a committed, unchanged item gives the commit that last touched it", r.commit === head);
|
|
45
|
+
await fresh.c.write("notes/beta.md", "changed\n");
|
|
46
|
+
expect("filesystem (git repository): a changed item gives no commit, since no commit holds that version", !(await fresh.c.read("notes/beta.md")).commit);
|
|
47
|
+
void gitT;
|
|
48
|
+
}
|
|
49
|
+
{
|
|
50
|
+
const d = mkdtempSync(join(scratch, "venture-"));
|
|
51
|
+
write(d, { "venture.json": "{}", "purpose/telos.json": "{}", "_drafts/x.draft": "{}", "studio/venture.json": "{}", "studio/purpose/telos.json": "{}" });
|
|
52
|
+
const v = fsConnect({ root: d, venture: true });
|
|
53
|
+
expect("filesystem (venture folder): listing stops at a nested venture and skips private _ folders", JSON.stringify((await v.list()).map((x) => x.path)) === JSON.stringify(["purpose/telos.json", "venture.json"]));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- Router --------------------------------------------------------------------------------------
|
|
57
|
+
console.log("\n# router over a mapped source");
|
|
58
|
+
const ws = join(scratch, "workspace");
|
|
59
|
+
cpSync(join(repo, "schema/examples/workspace"), ws, { recursive: true });
|
|
60
|
+
process.env.MENTURA_WORKSPACE = ws;
|
|
61
|
+
const tandem = join(ws, "ventures/tandem");
|
|
62
|
+
const shared = join(ws, "shared");
|
|
63
|
+
const { call } = await import("../../interface/router.mjs");
|
|
64
|
+
const { entries } = await import("../../interface/provenance.mjs");
|
|
65
|
+
const kettle = { ...Object.values((await call({ venture: "tandem", route: "purpose/product", action: "read" })).result)[0], id: "kettle", name: "Kettle (in the shared folder)" };
|
|
66
|
+
write(shared, { "products/kettle.json": JSON.stringify(kettle, null, 2) + "\n", "notes/launch.md": "# Launch plan\n\nShip the kettle subscription in spring.\n" });
|
|
67
|
+
const map = { sources: [{ id: "shared", connector: "filesystem", root: "../../shared" }], records: { product: "shared" } };
|
|
68
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
|
|
69
|
+
const localProducts = existsSync(join(tandem, "products")) ? spawnSync("ls", [join(tandem, "products")], { encoding: "utf8" }).stdout.trim().split("\n") : [];
|
|
70
|
+
|
|
71
|
+
const list = await call({ venture: "tandem", route: "purpose/product", action: "read" });
|
|
72
|
+
expect("router: a mapped record type is read from its source, not the venture folder", list.ok && JSON.stringify(Object.keys(list.result)) === JSON.stringify(["kettle"]) && localProducts.length > 0, JSON.stringify(list).slice(0, 300));
|
|
73
|
+
const one = await call({ venture: "tandem", route: "purpose/product/kettle", action: "read" });
|
|
74
|
+
expect("router: a read says which source and version it came from", one.result?.name === "Kettle (in the shared folder)" && one.source?.address === "shared://products/kettle.json"
|
|
75
|
+
&& one.source.version === blobSha(JSON.stringify(kettle, null, 2) + "\n"), JSON.stringify(one.source));
|
|
76
|
+
const local = await call({ venture: "tandem", route: "purpose/validation/problem", action: "read" });
|
|
77
|
+
expect("router: every record type the map does not name stays in the venture folder", local.ok && local.source?.source === "venture" && local.source.version === blobSha(readFileSync(join(tandem, "purpose/validation/problem.json"))));
|
|
78
|
+
const teapot = { ...kettle, id: "teapot", name: "Teapot" };
|
|
79
|
+
const bad = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: { id: "teapot" } });
|
|
80
|
+
expect("router: a write that does not match the schema never reaches the source", !bad.ok && /does not match/.test(bad.error) && !existsSync(join(shared, "products/teapot.json")));
|
|
81
|
+
const w = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person", via: "cli", provenance: { note: "add the teapot" } });
|
|
82
|
+
expect("router: a write lands in the mapped source, not the venture folder", w.ok && JSON.parse(readFileSync(join(shared, "products/teapot.json"), "utf8")).name === "Teapot" && !existsSync(join(tandem, "products/teapot.json")), JSON.stringify(w));
|
|
83
|
+
const e = entries(tandem).at(-1);
|
|
84
|
+
expect("router: the write is logged by address, with the source's version", e.entity === "shared://products/teapot.json" && e.source?.connector === "filesystem" && e.source.version === blobSha(readFileSync(join(shared, "products/teapot.json"))), JSON.stringify(e));
|
|
85
|
+
const prov = spawnSync("node", [join(repo, "schema/check-provenance.mjs"), ws], { encoding: "utf8" });
|
|
86
|
+
const srcCheck = (d) => spawnSync("node", [join(repo, "schema/check-sources.mjs"), d], { encoding: "utf8" });
|
|
87
|
+
expect("router: check-provenance accepts entries for records in another source", prov.status === 0, prov.stderr);
|
|
88
|
+
expect("router: check-sources accepts the map", srcCheck(ws).status === 0, srcCheck(ws).stderr);
|
|
89
|
+
const valid = spawnSync("python3", ["-I", join(repo, "schema/validate.py"), "--schema", "knowledge-sources.schema.json", "--file", join(tandem, "knowledge/sources.json")], { encoding: "utf8" });
|
|
90
|
+
expect("router: the map matches its schema", valid.status === 0, valid.stdout + valid.stderr);
|
|
91
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ sources: [{ id: "team-repo", connector: "notion" }], records: { product: "team-repo", venture: "team-repo" } }) });
|
|
92
|
+
const broken = srcCheck(ws);
|
|
93
|
+
expect("router: check-sources refuses an unknown connector and a record type that must stay in the folder", broken.status === 1 && /no connector notion/.test(broken.stderr) && /venture stays in the venture folder/.test(broken.stderr), broken.stderr);
|
|
94
|
+
const refused = await call({ venture: "tandem", route: "purpose/product", action: "read" });
|
|
95
|
+
expect("router: a read through an unknown connector fails and says which connectors exist", !refused.ok && /no connector notion: .*filesystem/.test(refused.error), refused.error);
|
|
96
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ ...map, sources: [{ ...map.sources[0], writes: "none" }] }) });
|
|
97
|
+
const ro = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person" });
|
|
98
|
+
expect("router: a write to a read-only source is refused", !ro.ok && /read-only/.test(ro.error));
|
|
99
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
|
|
100
|
+
|
|
101
|
+
// ---- Catalog -------------------------------------------------------------------------------------
|
|
102
|
+
console.log("\n# catalog");
|
|
103
|
+
const { stats } = await build(tandem);
|
|
104
|
+
const cat = load(tandem);
|
|
105
|
+
expect("catalog: indexes every item in both sources", cat.items.some((x) => x.source === "shared" && x.path === "notes/launch.md") && cat.items.some((x) => x.source === "venture" && x.path === "purpose/validation/problem.json") && stats.errors.length === 0, JSON.stringify(stats));
|
|
106
|
+
expect("catalog: every item has an address and its version", cat.items.every((x) => x.address === `${x.source}://${x.path}` && /^[0-9a-f]{40}$/.test(x.version)));
|
|
107
|
+
expect("catalog: records are tied to their routes, wherever they live", cat.items.find((x) => x.address === "shared://products/kettle.json")?.route === "product/kettle" && cat.items.find((x) => x.path === "purpose/validation/problem.json")?.route === "foundation/problem");
|
|
108
|
+
const s1 = search(cat, "kettle subscription spring");
|
|
109
|
+
expect("catalog: search finds an item in the mapped source", s1[0]?.address === "shared://notes/launch.md" && s1[0].title === "Launch plan", JSON.stringify(s1.slice(0, 2)));
|
|
110
|
+
const research = readFileSync(join(tandem, "knowledge/research/pilot-2026-10.md"), "utf8");
|
|
111
|
+
expect("catalog: search finds an item in the venture folder", search(cat, "pilot").some((x) => x.address === "venture://knowledge/research/pilot-2026-10.md"));
|
|
112
|
+
const raw = readFileSync(join(tandem, FILE), "utf8");
|
|
113
|
+
const phrase = research.split("\n").find((l) => l.split(" ").length > 6)?.slice(0, 40);
|
|
114
|
+
expect("catalog: keeps no content (no sentence of an item appears in it)", phrase && !raw.includes(phrase) && !raw.includes("Ship the kettle subscription"), phrase);
|
|
115
|
+
const again = (await build(tandem)).stats;
|
|
116
|
+
expect("catalog: a rebuild with nothing changed reads nothing", again.read === 0 && again.reused === stats.items, JSON.stringify(again));
|
|
117
|
+
write(shared, { "notes/later.md": "# Later\n\nAdded after the first build.\n" });
|
|
118
|
+
const changed = (await build(tandem)).stats;
|
|
119
|
+
expect("catalog: after a change, a rebuild reads only the new item", changed.read === 1 && load(tandem).items.some((x) => x.address === "shared://notes/later.md"), JSON.stringify(changed));
|
|
120
|
+
|
|
121
|
+
// ---- Matrix (EE-042) ------------------------------------------------------------------------------
|
|
122
|
+
console.log("\n# source-of-truth matrix");
|
|
123
|
+
const { resolve: resolveRule } = await import("./resolve.mjs");
|
|
124
|
+
const { sourceFor } = await import("./index.mjs");
|
|
125
|
+
const rule = (o) => ({ record: "product", attribute: "*", phase: "*", product: "*", account: "*", actor: "*", owner: "founder", ...o });
|
|
126
|
+
const matrix = { sources: map.sources, records: {}, rules: [
|
|
127
|
+
rule({ system_of_record: "venture" }),
|
|
128
|
+
rule({ phase: "validation", system_of_record: "shared" }),
|
|
129
|
+
rule({ product: "kettle", phase: "validation", system_of_record: "venture", note: "the kettle stays in the folder" }),
|
|
130
|
+
rule({ attribute: "price", system_of_record: "shared" }),
|
|
131
|
+
] };
|
|
132
|
+
expect("matrix: the most specific rule wins", resolveRule(matrix, { record: "product", phase: "validation", product: "kettle" }).rule.note === "the kettle stays in the folder");
|
|
133
|
+
expect("matrix: a rule narrowed to a phase holds only in that phase", resolveRule(matrix, { record: "product", phase: "validation" }).rule.system_of_record === "shared" && resolveRule(matrix, { record: "product", phase: "scale" }).rule.system_of_record === "venture");
|
|
134
|
+
expect("matrix: a field the query leaves out matches only \"*\"", resolveRule(matrix, { record: "product" }).rule.system_of_record === "venture");
|
|
135
|
+
expect("matrix: an attribute rule answers for that attribute, not for whole-record reads", resolveRule(matrix, { record: "product", attribute: "price" }).rule.attribute === "price" && resolveRule(matrix, { record: "product" }).rule.attribute === "*");
|
|
136
|
+
expect("matrix: a record type no rule names falls back to the records shorthand, then to nothing", resolveRule({ records: { account: "shared" } }, { record: "account" }).rule.system_of_record === "shared" && resolveRule(matrix, { record: "telos" }) === null);
|
|
137
|
+
expect("matrix: two rules that hold at once at the same specificity tie", resolveRule({ rules: [rule({ phase: "validation", system_of_record: "a" }), rule({ actor: "agent", system_of_record: "b" })] }, { record: "product", phase: "validation", actor: "agent" }).ties.length === 2);
|
|
138
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(matrix, null, 2) + "\n" });
|
|
139
|
+
expect("matrix: sourceFor reads the venture's phase from venture.json (tandem is in validation)", sourceFor(tandem, "product").id === "shared" && sourceFor(tandem, "product", { product: "kettle" }).id === "venture");
|
|
140
|
+
const viaRouter = await call({ venture: "tandem", route: "purpose/product/teapot", action: "read" });
|
|
141
|
+
const kettleWrite = await call({ venture: "tandem", route: "purpose/product/kettle", action: "write", input: { ...kettle, name: "Kettle (kept in the folder)" }, actor: "person" });
|
|
142
|
+
expect("matrix: the router routes each read and write by its context", viaRouter.source?.source === "shared" && kettleWrite.ok && existsSync(join(tandem, "products/kettle.json")) && JSON.parse(readFileSync(join(shared, "products/kettle.json"), "utf8")).name === "Kettle (in the shared folder)", JSON.stringify([viaRouter.source, kettleWrite]));
|
|
143
|
+
expect("matrix: check-sources accepts the matrix", srcCheck(ws).status === 0, srcCheck(ws).stderr);
|
|
144
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ ...matrix, rules: [...matrix.rules, rule({ actor: "agent", system_of_record: "venture" })] }, null, 2) + "\n" });
|
|
145
|
+
const tie = srcCheck(ws);
|
|
146
|
+
expect("matrix: check-sources refuses two rules that can tie", tie.status === 1 && /tie at the same specificity/.test(tie.stderr), tie.stderr);
|
|
147
|
+
const tieRead = await call({ venture: "tandem", route: "purpose/product", action: "read", actor: "agent" });
|
|
148
|
+
expect("matrix: a read that two rules tie on fails and says so", !tieRead.ok && /tie at the same specificity/.test(tieRead.error), tieRead.error);
|
|
149
|
+
|
|
150
|
+
// Attributes mastered elsewhere: the product's description lives in the shared folder, the rest
|
|
151
|
+
// of the product in the venture folder.
|
|
152
|
+
const byAttr = { sources: map.sources, records: {}, rules: [rule({ system_of_record: "venture" }), rule({ attribute: "description", system_of_record: "shared" })] };
|
|
153
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(byAttr, null, 2) + "\n" });
|
|
154
|
+
write(shared, { "products/shop-app.json": JSON.stringify({ description: "Mastered in the shared folder" }, null, 2) + "\n" });
|
|
155
|
+
const shop = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "read" });
|
|
156
|
+
expect("attributes: a read fills each attribute from its own system of record and cites it", shop.result?.description === "Mastered in the shared folder" && shop.source?.source === "venture" && shop.source.attributes?.description?.address === "shared://products/shop-app.json", JSON.stringify(shop.source));
|
|
157
|
+
const shopList = await call({ venture: "tandem", route: "purpose/product", action: "read" });
|
|
158
|
+
expect("attributes: list reads overlay too", shopList.result?.["shop-app"]?.description === "Mastered in the shared folder");
|
|
159
|
+
write(shared, { "products/kettle.json": "{}\n" });
|
|
160
|
+
const noFrag = await call({ venture: "tandem", route: "purpose/product/kettle", action: "read" });
|
|
161
|
+
expect("attributes: a source holding nothing for the attribute leaves the record's own value and says so", noFrag.result?.name === "Kettle (kept in the folder)" && noFrag.result?.description === kettle.description && noFrag.source.attributes?.description?.missing === true, JSON.stringify(noFrag.source));
|
|
162
|
+
const shopWrite = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: { ...shop.result, description: "Changed once, in one place" }, actor: "person", provenance: { note: "new description" } });
|
|
163
|
+
const own = JSON.parse(readFileSync(join(tandem, "products/shop-app.json"), "utf8"));
|
|
164
|
+
const frag = JSON.parse(readFileSync(join(shared, "products/shop-app.json"), "utf8"));
|
|
165
|
+
expect("attributes: a write sends the attribute to its system of record and leaves it out of the record's own file", shopWrite.ok && frag.description === "Changed once, in one place" && !("description" in own) && shopWrite.result.attributes?.[0]?.written === "shared://products/shop-app.json", JSON.stringify(shopWrite));
|
|
166
|
+
expect("attributes: the attribute write is logged by address and attribute", entries(tandem).some((x) => x.entity === "shared://products/shop-app.json#description"));
|
|
167
|
+
const after = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "read" });
|
|
168
|
+
expect("attributes: the next read shows the one value", after.result?.description === "Changed once, in one place");
|
|
169
|
+
const badAttr = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: { id: "shop-app" }, actor: "person" });
|
|
170
|
+
expect("attributes: a record that fails its schema writes nothing anywhere", !badAttr.ok && JSON.parse(readFileSync(join(shared, "products/shop-app.json"), "utf8")).description === "Changed once, in one place");
|
|
171
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ ...byAttr, sources: [{ ...map.sources[0], writes: "none" }] }, null, 2) + "\n" });
|
|
172
|
+
const roAttr = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: after.result, actor: "person" });
|
|
173
|
+
expect("attributes: a write whose attribute is mastered in a read-only source is refused before anything lands", !roAttr.ok && /read-only/.test(roAttr.error));
|
|
174
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
|
|
175
|
+
|
|
176
|
+
finish();
|