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,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "validation",
|
|
3
|
+
"title": "Validation",
|
|
4
|
+
"kind": "core",
|
|
5
|
+
"purpose": "Whether the problem is real, whether this solves it, whether people pay and keep using it, with the evidence graded.",
|
|
6
|
+
"department": "strategy",
|
|
7
|
+
"required": [
|
|
8
|
+
"validation-solution",
|
|
9
|
+
"validation-pricing",
|
|
10
|
+
"validation-go-to-market"
|
|
11
|
+
],
|
|
12
|
+
"optional": [
|
|
13
|
+
"validation-product-market-fit",
|
|
14
|
+
"evidence"
|
|
15
|
+
],
|
|
16
|
+
"refs": [
|
|
17
|
+
"validation-problem",
|
|
18
|
+
"icp"
|
|
19
|
+
],
|
|
20
|
+
"planned": [
|
|
21
|
+
"customer feedback"
|
|
22
|
+
],
|
|
23
|
+
"base": {
|
|
24
|
+
"discovery": 0.6,
|
|
25
|
+
"validation": 0.95,
|
|
26
|
+
"beachhead": 0.6,
|
|
27
|
+
"scale": 0.3,
|
|
28
|
+
"platform": 0.2
|
|
29
|
+
},
|
|
30
|
+
"when": [
|
|
31
|
+
{
|
|
32
|
+
"if": "ambition:hobby",
|
|
33
|
+
"at_most": 0.4
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"why": "The work of Discovery and Validation; it fades once retention holds",
|
|
37
|
+
"unlocks": [
|
|
38
|
+
"interview scheduling",
|
|
39
|
+
"surveys",
|
|
40
|
+
"research runs"
|
|
41
|
+
],
|
|
42
|
+
"gates": [
|
|
43
|
+
"a person has confirmed every required record and every record this package reads"
|
|
44
|
+
],
|
|
45
|
+
"schemas": [
|
|
46
|
+
"schema/validation-problem.schema.json",
|
|
47
|
+
"schema/validation-solution.schema.json",
|
|
48
|
+
"schema/validation-product-market-fit.schema.json",
|
|
49
|
+
"schema/validation-pricing.schema.json",
|
|
50
|
+
"schema/validation-go-to-market.schema.json",
|
|
51
|
+
"schema/validation-team.schema.json",
|
|
52
|
+
"schema/validation-operations-readiness.schema.json",
|
|
53
|
+
"schema/evidence.schema.json"
|
|
54
|
+
],
|
|
55
|
+
"templates": [
|
|
56
|
+
"purpose/kits/validation/templates/go-to-market.md",
|
|
57
|
+
"purpose/kits/validation/templates/operations-readiness.md",
|
|
58
|
+
"purpose/kits/validation/templates/pricing.md",
|
|
59
|
+
"purpose/kits/validation/templates/problem-interview.md",
|
|
60
|
+
"purpose/kits/validation/templates/problem.md",
|
|
61
|
+
"purpose/kits/validation/templates/product-market-fit.md",
|
|
62
|
+
"purpose/kits/validation/templates/solution.md",
|
|
63
|
+
"purpose/kits/validation/templates/team.md"
|
|
64
|
+
],
|
|
65
|
+
"checks": [
|
|
66
|
+
"schema/check-validation.mjs",
|
|
67
|
+
"schema/assess-problem.mjs",
|
|
68
|
+
"schema/bdd.mjs"
|
|
69
|
+
],
|
|
70
|
+
"skill": {
|
|
71
|
+
"id": "problem-validation",
|
|
72
|
+
"path": "purpose/kits/validation/skills/problem-validation/SKILL.md"
|
|
73
|
+
},
|
|
74
|
+
"use_model": [],
|
|
75
|
+
"layer": "purpose",
|
|
76
|
+
"records": [
|
|
77
|
+
"validation-problem",
|
|
78
|
+
"validation-solution",
|
|
79
|
+
"validation-product-market-fit",
|
|
80
|
+
"validation-pricing",
|
|
81
|
+
"validation-go-to-market",
|
|
82
|
+
"validation-team",
|
|
83
|
+
"validation-operations-readiness",
|
|
84
|
+
"validation-weights"
|
|
85
|
+
],
|
|
86
|
+
"reads": [
|
|
87
|
+
"venture",
|
|
88
|
+
"product"
|
|
89
|
+
],
|
|
90
|
+
"writes": [],
|
|
91
|
+
"template_dir": "purpose/kits/validation/templates",
|
|
92
|
+
"references": "purpose/kits/validation/references/references.json",
|
|
93
|
+
"gaps": [
|
|
94
|
+
"Jev does not score the probability yet; the example uses an owner value",
|
|
95
|
+
"Validation weights and floors are ours and unevaluated (version 0.x)",
|
|
96
|
+
"The single validation score (validation-score skill, purpose/validation/score) is held, not removed: EE-050 replaces it for the problem; the other sub-scores are not yet assessed the new way"
|
|
97
|
+
],
|
|
98
|
+
"absorbed": "purpose/kits/validation/kit.json (EE-071). The held validation-score skill stays on the purpose layer."
|
|
99
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
source: hybrid
|
|
4
|
+
created: 2026-10-28T03:07:56Z
|
|
5
|
+
ai-model: claude-opus-5.5
|
|
6
|
+
tags: [everything-ecosystem, mentura, platform, framework, boundary]
|
|
7
|
+
description: "The platform side of Mentura: what lives here, the boundary with the framework, and how it is enforced (EE-041)."
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Platform
|
|
11
|
+
|
|
12
|
+
The system has two sides, the way Next.js and Vercel do (EE-041): the framework Naive and the platform Mentura.
|
|
13
|
+
|
|
14
|
+
- **The framework**, Naive (EE-043), is the conventions. It works offline, with no account, and names no vendor. It covers record schemas and the data model, kits, the router and the CLI, MCP server and local API over it, routes, provenance, the checks, need-now priors, validation scoring, `init-venture`, the connector contract with the filesystem connector, and the docs. That is everything in this repository outside `platform/`.
|
|
15
|
+
- **The platform** hosts, connects and integrates. It holds connectors that reach a vendor over the network with credentials, the connect flow, integrations that call outside services (analytics, Listen Labs, Jev routing, treg), a hosted catalog and search, the operator console, and a gallery of templates. That is this folder.
|
|
16
|
+
|
|
17
|
+
A template is framework, the way `create-next-app --example` is. The gallery that hosts templates is platform.
|
|
18
|
+
|
|
19
|
+
## The boundary
|
|
20
|
+
|
|
21
|
+
The platform builds on the framework, and never the other way round. `node schema/check-boundary.mjs` (run by `node test.mjs`) fails when:
|
|
22
|
+
|
|
23
|
+
1. a file outside `platform/` imports anything from `platform/`;
|
|
24
|
+
2. a connector registered outside `platform/` reaches the network or names credentials;
|
|
25
|
+
3. a registered connector module does not exist.
|
|
26
|
+
|
|
27
|
+
The framework can still load a platform connector by the module path in `knowledge/tooling/integrations.json`. That is inversion of control, like a Next.js deployment adapter: the framework defines the contract, and the platform implements it for a vendor.
|
|
28
|
+
|
|
29
|
+
## What is here
|
|
30
|
+
|
|
31
|
+
| Path | Holds |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `connectors/github.mjs` | The GitHub connector: reads from a branch, writes by draft pull request (EE-037) |
|
|
34
|
+
| `connectors/fake-github.mjs` | A fake of the GitHub REST API for offline evals |
|
|
35
|
+
| `connectors/eval.mjs` | The GitHub connector against the framework's contract suite (`knowledge/connectors/suite.mjs`), the router over a GitHub source, the catalog, and an optional live run |
|
|
36
|
+
| `mcp/research.mjs` | Market research for agents through Firecrawl, as an MCP server: search and read a page, every result with its URL (EE-059). Needs `FIRECRAWL_API_KEY` |
|
|
37
|
+
| `integrations/jev.mjs` | Jev (TypeSafe) for typed judgments: sends a request the framework built, such as the idea map's tags, and prints the answers (EE-059). Needs `TYPESAFE_API_KEY` |
|
|
38
|
+
| `integrations/langsmith.mjs` | Uploads a harness run's trace, with Naive's evals as feedback, to LangSmith (EE-059). Needs `LANGSMITH_API_KEY` |
|
|
39
|
+
| `integrations/langsmith-docket.mjs` | The docket's evidence as a LangSmith experiment: the scenario states as a dataset, and one scored run of the docket and the judge (Jev) as an experiment over it (EE-075). Needs `LANGSMITH_API_KEY` |
|
|
40
|
+
| `integrations/rho.mjs` | Rho, the founder's bank and spend suite, as the venture's money source: syncs `money/` (accounts, a sensitive append-only transactions stream, and a summary) over Rho's MCP server, calling only read-only tools (EE-070). Rho's sandbox by default; `--live` needs `RHO_API_TOKEN` |
|
|
41
|
+
| `integrations/site.mjs` | Reads a venture's website: the home page and the pages it links to, as text, with where each sits (EE-062). Uses `FIRECRAWL_API_KEY` when set, a plain fetch otherwise |
|
|
42
|
+
|
|
43
|
+
## Not yet moved
|
|
44
|
+
|
|
45
|
+
Each layer's `tooling/integrations.json` stays in the framework for now. Most entries are opinionated defaults with references (EE-014), plain data that calls nothing, like a framework's docs recommending a host. An entry moves here once it carries code that calls a vendor.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Evals for the platform's GitHub connector (EE-037, EE-041). The GitHub connector names a vendor
|
|
2
|
+
// and reaches it over the network, so it is platform, not framework; it proves itself against the
|
|
3
|
+
// framework's contract suite (knowledge/connectors/suite.mjs). Three parts:
|
|
4
|
+
//
|
|
5
|
+
// contract the shared suite, run against a fake of the GitHub API, plus GitHub's own cases
|
|
6
|
+
// router a venture whose source map keeps one record type in a GitHub repository: reads
|
|
7
|
+
// come from there with their version, a write opens a draft pull request and is
|
|
8
|
+
// logged with the source's version, nothing invalid reaches the source, and every
|
|
9
|
+
// other record stays in the venture folder
|
|
10
|
+
// catalog one index across both sources, rebuilt after the pull request is merged
|
|
11
|
+
//
|
|
12
|
+
// node platform/connectors/eval.mjs
|
|
13
|
+
//
|
|
14
|
+
// Live GitHub: with MENTURA_EVAL_GITHUB_REPO=owner/name (and a token in GITHUB_TOKEN, or the
|
|
15
|
+
// variable MENTURA_EVAL_GITHUB_TOKEN names), the read half of the contract also runs against that
|
|
16
|
+
// repository (MENTURA_EVAL_GITHUB_REF, MENTURA_EVAL_GITHUB_ROOT narrow it), and with
|
|
17
|
+
// MENTURA_EVAL_GITHUB_WRITE=1 it opens one real draft pull request. Without them it says it skipped.
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { cpSync, existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { blobSha } from "../../knowledge/connectors/contract.mjs";
|
|
23
|
+
import { contract, FIXTURE, harness, OUTSIDE } from "../../knowledge/connectors/suite.mjs";
|
|
24
|
+
import { useFetch } from "../../knowledge/connectors/index.mjs";
|
|
25
|
+
import { build, FILE, load, search } from "../../knowledge/connectors/catalog.mjs";
|
|
26
|
+
import { fakeGitHub } from "./fake-github.mjs";
|
|
27
|
+
import { connect as ghConnect } from "./github.mjs";
|
|
28
|
+
|
|
29
|
+
const repo = new URL("../..", import.meta.url).pathname;
|
|
30
|
+
const h = harness("platform-connectors");
|
|
31
|
+
const { expect, throws, skip, write, scratch, finish } = h;
|
|
32
|
+
|
|
33
|
+
// The GitHub connector, against the fake.
|
|
34
|
+
const ghMake = async ({ writes = "pull-request", search = "api" } = {}) => {
|
|
35
|
+
const gh = fakeGitHub({ repo: "acme/knowledge", files: { ...Object.fromEntries(Object.entries(FIXTURE).map(([p, c]) => [`kb/${p}`, c])), ...OUTSIDE } });
|
|
36
|
+
return { gh, c: ghConnect({ id: "acme", repo: "acme/knowledge", root: "kb", writes, search, token: "MENTURA_EVAL_FAKE_TOKEN", fetch: gh.fetch }),
|
|
37
|
+
base: (p) => gh.files()[`kb/${p}`] ?? null, later: (p, branch) => gh.files(branch)[`kb/${p}`] ?? null, pulls: () => gh.pulls };
|
|
38
|
+
};
|
|
39
|
+
process.env.MENTURA_EVAL_FAKE_TOKEN = "fake";
|
|
40
|
+
const ghT = await contract(h, "github (fake API)", ghMake);
|
|
41
|
+
{
|
|
42
|
+
const a = await ghT.c.read("notes/alpha.md");
|
|
43
|
+
expect("github (fake API): read gives the branch's commit and a permalink to the file at that commit", /^[0-9a-f]{40}$/.test(a.commit) && a.permalink === `https://github.com/acme/knowledge/blob/${a.commit}/kb/notes/alpha.md`);
|
|
44
|
+
expect("github (fake API): a pull request targets the source's branch and says merging accepts it", ghT.gh.pulls.every((p) => p.base.ref === "main" && /Merging this pull request accepts the write/.test(p.body)));
|
|
45
|
+
const scan = await ghMake({ search: "scan" });
|
|
46
|
+
expect("github (fake API): search by scanning finds the same items as code search", JSON.stringify((await scan.c.search("kettle pilot")).map((x) => x.path)) === JSON.stringify(["notes/alpha.md"]));
|
|
47
|
+
delete process.env.MENTURA_EVAL_FAKE_TOKEN;
|
|
48
|
+
expect("github (fake API): without a token it fails and names the variable to set", await throws(() => ghT.c.list(), /no valid token in MENTURA_EVAL_FAKE_TOKEN/));
|
|
49
|
+
process.env.MENTURA_EVAL_FAKE_TOKEN = "fake";
|
|
50
|
+
expect("github: a source with no repo, or that asks to write directly, is refused", await throws(() => ghConnect({ repo: "nope" })) && await throws(() => ghConnect({ repo: "a/b", writes: "direct" })));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---- Router --------------------------------------------------------------------------------------
|
|
54
|
+
console.log("\n# router over a mapped source");
|
|
55
|
+
const ws = join(scratch, "workspace");
|
|
56
|
+
cpSync(join(repo, "schema/examples/workspace"), ws, { recursive: true });
|
|
57
|
+
process.env.MENTURA_WORKSPACE = ws;
|
|
58
|
+
const tandem = join(ws, "ventures/tandem");
|
|
59
|
+
const { call } = await import("../../interface/router.mjs");
|
|
60
|
+
const { entries } = await import("../../interface/provenance.mjs");
|
|
61
|
+
const kettle = { ...Object.values((await call({ venture: "tandem", route: "purpose/product", action: "read" })).result)[0], id: "kettle", name: "Kettle (in GitHub)" };
|
|
62
|
+
const team = fakeGitHub({ repo: "acme/tandem", files: { "ventures/tandem/products/kettle.json": JSON.stringify(kettle, null, 2) + "\n", "ventures/tandem/notes/launch.md": "# Launch plan\n\nShip the kettle subscription in spring.\n" } });
|
|
63
|
+
useFetch(team.fetch);
|
|
64
|
+
process.env.MENTURA_EVAL_FAKE_TOKEN = "fake";
|
|
65
|
+
const map = { sources: [{ id: "team-repo", connector: "github", repo: "acme/tandem", ref: "main", root: "ventures/tandem", token: "MENTURA_EVAL_FAKE_TOKEN" }], records: { product: "team-repo" } };
|
|
66
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
|
|
67
|
+
const localProducts = existsSync(join(tandem, "products")) ? spawnSync("ls", [join(tandem, "products")], { encoding: "utf8" }).stdout.trim().split("\n") : [];
|
|
68
|
+
|
|
69
|
+
const list = await call({ venture: "tandem", route: "purpose/product", action: "read" });
|
|
70
|
+
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));
|
|
71
|
+
const one = await call({ venture: "tandem", route: "purpose/product/kettle", action: "read" });
|
|
72
|
+
expect("router: a read says which source and version it came from, with a permalink to cite", one.result?.name === "Kettle (in GitHub)" && one.source?.address === "team-repo://products/kettle.json"
|
|
73
|
+
&& one.source.version === blobSha(JSON.stringify(kettle, null, 2) + "\n") && /github\.com\/acme\/tandem\/blob\/[0-9a-f]{40}\/ventures\/tandem\/products\/kettle\.json$/.test(one.source.permalink ?? ""), JSON.stringify(one.source));
|
|
74
|
+
const local = await call({ venture: "tandem", route: "purpose/validation/problem", action: "read" });
|
|
75
|
+
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"))));
|
|
76
|
+
const teapot = { ...kettle, id: "teapot", name: "Teapot" };
|
|
77
|
+
const puts = () => team.calls.filter((x) => x.startsWith("PUT")).length;
|
|
78
|
+
const before = puts();
|
|
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) && puts() === before);
|
|
81
|
+
const w = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person", via: "cli", provenance: { derived_from: [one.source.permalink], note: "add the teapot" } });
|
|
82
|
+
expect("router: a write to a pull-request source opens a draft pull request, for a person as for an agent", w.ok && w.result.draft === true && /^https:\/\/github\.com\/acme\/tandem\/pull\/1$/.test(w.result.pull_request ?? "") && team.pulls[0]?.draft === true, JSON.stringify(w));
|
|
83
|
+
expect("router: nothing lands in the venture folder or on the source's branch until the pull request is merged",
|
|
84
|
+
!existsSync(join(tandem, "products/teapot.json")) && !team.files()["ventures/tandem/products/teapot.json"] && JSON.parse(team.files(team.pulls[0].head.ref)["ventures/tandem/products/teapot.json"]).name === "Teapot");
|
|
85
|
+
const e = entries(tandem).at(-1);
|
|
86
|
+
expect("router: the write is logged by address, with the source's version and the pull request",
|
|
87
|
+
e.entity === "team-repo://products/teapot.json" && e.activity === "draft" && e.source?.version === w.result.version && e.source.pull_request === w.result.pull_request && e.source.connector === "github" && e.derived_from?.[0] === one.source.permalink, JSON.stringify(e));
|
|
88
|
+
const pending = await call({ venture: "tandem", route: "purpose/product/teapot", action: "read" });
|
|
89
|
+
expect("router: reading it before the merge says a pull request is waiting", pending.result === null && pending.pending_pull_requests?.[0]?.pull_request === w.result.pull_request);
|
|
90
|
+
const prov = spawnSync("node", [join(repo, "schema/check-provenance.mjs"), ws], { encoding: "utf8" });
|
|
91
|
+
const srcCheck = (d) => spawnSync("node", [join(repo, "schema/check-sources.mjs"), d], { encoding: "utf8" });
|
|
92
|
+
expect("router: check-provenance accepts entries for records in another source", prov.status === 0, prov.stderr);
|
|
93
|
+
expect("router: check-sources accepts the map", srcCheck(ws).status === 0, srcCheck(ws).stderr);
|
|
94
|
+
const valid = spawnSync("python3", ["-I", join(repo, "schema/validate.py"), "--schema", "knowledge-sources.schema.json", "--file", join(tandem, "knowledge/sources.json")], { encoding: "utf8" });
|
|
95
|
+
expect("router: the map matches its schema", valid.status === 0, valid.stdout + valid.stderr);
|
|
96
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ sources: [{ id: "team-repo", connector: "notion" }], records: { product: "team-repo", venture: "team-repo" } }) });
|
|
97
|
+
const broken = srcCheck(ws);
|
|
98
|
+
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);
|
|
99
|
+
const refused = await call({ venture: "tandem", route: "purpose/product", action: "read" });
|
|
100
|
+
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);
|
|
101
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify({ ...map, sources: [{ ...map.sources[0], writes: "none" }] }) });
|
|
102
|
+
const ro = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person" });
|
|
103
|
+
expect("router: a write to a read-only source is refused", !ro.ok && /read-only/.test(ro.error));
|
|
104
|
+
write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
|
|
105
|
+
|
|
106
|
+
// ---- Catalog -------------------------------------------------------------------------------------
|
|
107
|
+
console.log("\n# catalog");
|
|
108
|
+
const { stats } = await build(tandem);
|
|
109
|
+
const cat = load(tandem);
|
|
110
|
+
expect("catalog: indexes every item in both sources", cat.items.some((x) => x.source === "team-repo" && 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));
|
|
111
|
+
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)));
|
|
112
|
+
expect("catalog: records are tied to their routes, wherever they live", cat.items.find((x) => x.address === "team-repo://products/kettle.json")?.route === "product/kettle" && cat.items.find((x) => x.path === "purpose/validation/problem.json")?.route === "foundation/problem");
|
|
113
|
+
const s1 = search(cat, "kettle subscription spring");
|
|
114
|
+
expect("catalog: search finds an item in the GitHub source", s1[0]?.address === "team-repo://notes/launch.md" && s1[0].title === "Launch plan", JSON.stringify(s1.slice(0, 2)));
|
|
115
|
+
const research = readFileSync(join(tandem, "knowledge/research/pilot-2026-10.md"), "utf8");
|
|
116
|
+
const s2 = search(cat, "pilot");
|
|
117
|
+
expect("catalog: search finds an item in the venture folder", s2.some((x) => x.address === "venture://knowledge/research/pilot-2026-10.md"));
|
|
118
|
+
const raw = readFileSync(join(tandem, FILE), "utf8");
|
|
119
|
+
const phrase = research.split("\n").find((l) => l.split(" ").length > 6)?.slice(0, 40);
|
|
120
|
+
expect("catalog: keeps no content (no sentence of an item appears in it)", phrase && !raw.includes(phrase) && !raw.includes("Ship the kettle subscription"), phrase);
|
|
121
|
+
const again = (await build(tandem)).stats;
|
|
122
|
+
expect("catalog: a rebuild with nothing changed reads nothing", again.read === 0 && again.reused === stats.items, JSON.stringify(again));
|
|
123
|
+
team.branches.set("main", team.branches.get(team.pulls[0].head.ref)); // the pull request is merged
|
|
124
|
+
const merged = (await build(tandem)).stats;
|
|
125
|
+
expect("catalog: after the merge, a rebuild reads only the new item", merged.read === 1 && load(tandem).items.some((x) => x.address === "team-repo://products/teapot.json"), JSON.stringify(merged));
|
|
126
|
+
expect("router: after the merge the record reads from the source", (await call({ venture: "tandem", route: "purpose/product/teapot", action: "read" })).result?.name === "Teapot");
|
|
127
|
+
useFetch(null);
|
|
128
|
+
|
|
129
|
+
// ---- Live GitHub (optional) ------------------------------------------------------------------------
|
|
130
|
+
console.log("\n# live github");
|
|
131
|
+
const live = process.env.MENTURA_EVAL_GITHUB_REPO;
|
|
132
|
+
const tokenVar = process.env.MENTURA_EVAL_GITHUB_TOKEN ?? "GITHUB_TOKEN";
|
|
133
|
+
if (!live || !process.env[tokenVar]) {
|
|
134
|
+
skip(`live GitHub: set MENTURA_EVAL_GITHUB_REPO=owner/name and a token in ${tokenVar} to run it`);
|
|
135
|
+
} else {
|
|
136
|
+
const c = ghConnect({ id: "live", repo: live, ref: process.env.MENTURA_EVAL_GITHUB_REF ?? "main", root: process.env.MENTURA_EVAL_GITHUB_ROOT ?? "", token: tokenVar });
|
|
137
|
+
try {
|
|
138
|
+
const items = await c.list();
|
|
139
|
+
expect(`live github: lists ${live}`, items.length > 0);
|
|
140
|
+
const first = items.find((x) => /\.(md|json|txt)$/.test(x.path)) ?? items[0];
|
|
141
|
+
const r = await c.read(first.path);
|
|
142
|
+
expect("live github: read gives content whose git blob SHA-1 is the version GitHub reports", r && blobSha(r.content) === r.version && r.version === first.version);
|
|
143
|
+
expect("live github: read gives a permalink at a commit", /\/blob\/[0-9a-f]{40}\//.test(r.permalink ?? ""));
|
|
144
|
+
const word = r.content.split(/[^A-Za-z]+/).find((x) => x.length > 6);
|
|
145
|
+
if (word) { const hits = await c.search(word); expect(`live github: search for "${word}" returns items with versions`, hits.every((x) => /^[0-9a-f]{40}$/.test(x.version))); }
|
|
146
|
+
if (process.env.MENTURA_EVAL_GITHUB_WRITE === "1") {
|
|
147
|
+
const w = await c.write(`mentura-eval/${Date.now()}.md`, "# Mentura eval\n\nSafe to close.\n", { message: "Mentura connector eval (safe to close)" });
|
|
148
|
+
expect("live github: a write opens a draft pull request", /\/pull\/\d+$/.test(w.pull_request?.url ?? ""), JSON.stringify(w));
|
|
149
|
+
console.log(` opened ${w.pull_request?.url}; close it and delete branch ${w.pull_request?.branch}`);
|
|
150
|
+
}
|
|
151
|
+
} catch (err) { expect("live github: reachable", false, err.message); }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
finish();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// A fake of the part of GitHub's REST API the GitHub connector uses, as a fetch function, so the
|
|
2
|
+
// connector's evals run offline. It follows the real API's shapes and refusals: trees, contents
|
|
3
|
+
// (404 for a missing file, 422 for a PUT over an existing file without its sha), commits, refs,
|
|
4
|
+
// pulls and code search (default branch only), and 401 without a token. It is a test double, not a
|
|
5
|
+
// second implementation: the live eval (MENTURA_EVAL_GITHUB_REPO) checks the same contract on the
|
|
6
|
+
// real API.
|
|
7
|
+
//
|
|
8
|
+
// const gh = fakeGitHub({ repo: "acme/knowledge", files: { "docs/a.md": "..." } });
|
|
9
|
+
// connect({ repo: "acme/knowledge", fetch: gh.fetch })
|
|
10
|
+
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { blobSha, matches } from "../../knowledge/connectors/contract.mjs";
|
|
13
|
+
|
|
14
|
+
export function fakeGitHub({ repo, files = {}, branch = "main", token = true } = {}) {
|
|
15
|
+
let n = 0;
|
|
16
|
+
const commits = new Map();
|
|
17
|
+
const commit = (snapshot) => { const sha = createHash("sha1").update(`commit ${++n}`).digest("hex"); commits.set(sha, new Map(snapshot)); return sha; };
|
|
18
|
+
const branches = new Map([[branch, commit(Object.entries(files))]]);
|
|
19
|
+
const pulls = [];
|
|
20
|
+
const calls = [];
|
|
21
|
+
const at = (ref) => commits.get(branches.get(ref) ?? ref) ?? null;
|
|
22
|
+
const json = (status, body) => new Response(body === undefined ? null : JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
23
|
+
|
|
24
|
+
async function fetch(url, { method = "GET", headers = {}, body } = {}) {
|
|
25
|
+
const u = new URL(url);
|
|
26
|
+
calls.push(`${method} ${u.pathname}`);
|
|
27
|
+
if (token && !headers.authorization) return json(401, { message: "Requires authentication" });
|
|
28
|
+
const p = decodeURIComponent(u.pathname);
|
|
29
|
+
const rp = `/repos/${repo}`;
|
|
30
|
+
const b = body ? JSON.parse(body) : {};
|
|
31
|
+
let m;
|
|
32
|
+
if (method === "GET" && (m = p.match(new RegExp(`^${rp}/git/trees/(.+)$`)))) {
|
|
33
|
+
const s = at(m[1]);
|
|
34
|
+
if (!s) return json(404, { message: "Not Found" });
|
|
35
|
+
return json(200, { sha: branches.get(m[1]) ?? m[1], truncated: false, tree: [...s].map(([path, c]) => ({ path, type: "blob", sha: blobSha(c), size: Buffer.byteLength(c) })) });
|
|
36
|
+
}
|
|
37
|
+
if (method === "GET" && (m = p.match(new RegExp(`^${rp}/commits/(.+)$`)))) {
|
|
38
|
+
const sha = branches.get(m[1]) ?? (commits.has(m[1]) ? m[1] : null);
|
|
39
|
+
return sha ? json(200, { sha }) : json(404, { message: "Not Found" });
|
|
40
|
+
}
|
|
41
|
+
if (method === "GET" && (m = p.match(new RegExp(`^${rp}/git/ref/heads/(.+)$`)))) {
|
|
42
|
+
return branches.has(m[1]) ? json(200, { ref: `refs/heads/${m[1]}`, object: { sha: branches.get(m[1]), type: "commit" } }) : json(404, { message: "Not Found" });
|
|
43
|
+
}
|
|
44
|
+
if (method === "POST" && p === `${rp}/git/refs`) {
|
|
45
|
+
const name = b.ref.replace(/^refs\/heads\//, "");
|
|
46
|
+
if (branches.has(name)) return json(422, { message: "Reference already exists" });
|
|
47
|
+
if (!commits.has(b.sha)) return json(422, { message: "Object does not exist" });
|
|
48
|
+
branches.set(name, b.sha);
|
|
49
|
+
return json(201, { ref: b.ref, object: { sha: b.sha } });
|
|
50
|
+
}
|
|
51
|
+
if ((m = p.match(new RegExp(`^${rp}/contents/(.+)$`)))) {
|
|
52
|
+
const path = m[1];
|
|
53
|
+
if (method === "GET") {
|
|
54
|
+
const s = at(u.searchParams.get("ref") ?? branch);
|
|
55
|
+
if (!s || !s.has(path)) return json(404, { message: "Not Found" });
|
|
56
|
+
const c = s.get(path);
|
|
57
|
+
return json(200, { type: "file", path, sha: blobSha(c), encoding: "base64", content: Buffer.from(c).toString("base64") });
|
|
58
|
+
}
|
|
59
|
+
if (method === "PUT") {
|
|
60
|
+
const on = b.branch ?? branch;
|
|
61
|
+
const s = at(on);
|
|
62
|
+
if (!s) return json(404, { message: "Branch not found" });
|
|
63
|
+
if (s.has(path) && b.sha !== blobSha(s.get(path))) return json(b.sha ? 409 : 422, { message: b.sha ? `${path} does not match ${b.sha}` : "Invalid request.\n\n\"sha\" wasn't supplied." });
|
|
64
|
+
const next = new Map(s);
|
|
65
|
+
const c = Buffer.from(b.content, "base64").toString("utf8");
|
|
66
|
+
next.set(path, c);
|
|
67
|
+
const sha = commit(next);
|
|
68
|
+
branches.set(on, sha);
|
|
69
|
+
return json(s.has(path) ? 200 : 201, { content: { path, sha: blobSha(c) }, commit: { sha, message: b.message } });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (method === "POST" && p === `${rp}/pulls`) {
|
|
73
|
+
if (!branches.has(b.head) || !branches.has(b.base)) return json(422, { message: "Validation Failed" });
|
|
74
|
+
const pr = { number: pulls.length + 1, html_url: `https://github.com/${repo}/pull/${pulls.length + 1}`, title: b.title, body: b.body, draft: !!b.draft, head: { ref: b.head }, base: { ref: b.base }, state: "open" };
|
|
75
|
+
pulls.push(pr);
|
|
76
|
+
return json(201, pr);
|
|
77
|
+
}
|
|
78
|
+
if (method === "GET" && p === "/search/code") {
|
|
79
|
+
const q = u.searchParams.get("q") ?? "";
|
|
80
|
+
const quals = Object.fromEntries([...q.matchAll(/(\w+):(\S+)/g)].map((x) => [x[1], x[2]]));
|
|
81
|
+
if (quals.repo !== repo) return json(422, { message: "Validation Failed" });
|
|
82
|
+
const terms = q.replace(/\w+:\S+/g, " ").trim();
|
|
83
|
+
const items = [...at(branch)].filter(([path, c]) => (!quals.path || path.startsWith(`${quals.path}/`) || path === quals.path) && matches(`${path}\n${c}`, terms))
|
|
84
|
+
.map(([path, c]) => ({ name: path.split("/").at(-1), path, sha: blobSha(c) }));
|
|
85
|
+
return json(200, { total_count: items.length, incomplete_results: false, items });
|
|
86
|
+
}
|
|
87
|
+
return json(404, { message: `the fake has no ${method} ${p}` });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { fetch, branches, pulls, calls, files: (ref = branch) => Object.fromEntries(at(ref) ?? []) };
|
|
91
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The GitHub connector (EE-037): one repository, at one branch, under one folder, through the REST
|
|
2
|
+
// API with plain fetch (no SDK). Reads come from the branch; a write opens a draft pull request
|
|
3
|
+
// against it, and merging the pull request is what accepts the write (Torian's choice, 2026-10-28).
|
|
4
|
+
//
|
|
5
|
+
// repo owner/name
|
|
6
|
+
// ref the branch reads come from and pull requests target (default main)
|
|
7
|
+
// root the folder inside the repository the source covers (default the whole repository)
|
|
8
|
+
// token the name of the environment variable holding a token (default GITHUB_TOKEN); the
|
|
9
|
+
// token itself is never stored. Without one, only public repositories can be read.
|
|
10
|
+
// api the API base (default https://api.github.com; GitHub Enterprise has its own)
|
|
11
|
+
// search api (default): code search, which covers the default branch only, falling back to a
|
|
12
|
+
// scan when it is refused; scan: read every text file under root and match
|
|
13
|
+
// writes pull-request (default) or none
|
|
14
|
+
// fetch a fetch function (default globalThis.fetch); the evals pass a fake GitHub here
|
|
15
|
+
//
|
|
16
|
+
// version is the blob SHA GitHub reports, which is the git blob SHA-1 of the content. read also
|
|
17
|
+
// gives the branch's commit and a permalink to the file at that commit, which is what to cite.
|
|
18
|
+
|
|
19
|
+
import { blobSha, isText, matches, safePath } from "../../knowledge/connectors/contract.mjs";
|
|
20
|
+
|
|
21
|
+
export function connect({ id = "github", repo, ref = "main", root = "", token = "GITHUB_TOKEN", api = "https://api.github.com", search = "api", writes = "pull-request", fetch: f = globalThis.fetch } = {}) {
|
|
22
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo ?? "")) throw new Error(`the github connector needs repo as owner/name, not ${repo}`);
|
|
23
|
+
if (writes === "direct") throw new Error("the github connector writes by pull request or not at all");
|
|
24
|
+
const base = root ? safePath(root) : "";
|
|
25
|
+
const full = (p) => (base ? `${base}/${safePath(p)}` : safePath(p));
|
|
26
|
+
const inside = (p) => !base || p.startsWith(`${base}/`);
|
|
27
|
+
const rel = (p) => (base ? p.slice(base.length + 1) : p);
|
|
28
|
+
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
29
|
+
const web = api === "https://api.github.com" ? "https://github.com" : api.replace(/\/api\/v3\/?$/, "");
|
|
30
|
+
|
|
31
|
+
async function gh(method, path, body) {
|
|
32
|
+
const secret = process.env[token];
|
|
33
|
+
const res = await f(`${api}${path}`, { method, headers: { accept: "application/vnd.github+json", "x-github-api-version": "2022-11-28", "user-agent": "mentura",
|
|
34
|
+
...(secret ? { authorization: `Bearer ${secret}` } : {}), ...(body ? { "content-type": "application/json" } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}) });
|
|
35
|
+
if (res.status === 404 && method === "GET") return null;
|
|
36
|
+
const data = res.status === 204 ? null : await res.json().catch(() => null);
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const why = res.status === 401 ? `no valid token in ${token}` : res.status === 403 ? `forbidden (token scope, or a rate limit)` : data?.message ?? res.statusText;
|
|
39
|
+
const e = new Error(`GitHub ${method} ${path}: ${res.status} ${why}`);
|
|
40
|
+
e.status = res.status;
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const head = async () => (await gh("GET", `/repos/${repo}/commits/${encodeURIComponent(ref)}`))?.sha ?? null;
|
|
47
|
+
const tree = async () => {
|
|
48
|
+
const t = await gh("GET", `/repos/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`);
|
|
49
|
+
if (!t) throw new Error(`${repo} has no branch ${ref}`);
|
|
50
|
+
if (t.truncated) throw new Error(`${repo}@${ref} is too large for one tree listing; set root to a smaller folder`);
|
|
51
|
+
return t.tree.filter((x) => x.type === "blob" && inside(x.path));
|
|
52
|
+
};
|
|
53
|
+
const contents = async (p, at = ref) => {
|
|
54
|
+
const x = await gh("GET", `/repos/${repo}/contents/${enc(full(p))}?ref=${encodeURIComponent(at)}`);
|
|
55
|
+
return x && !Array.isArray(x) && x.type === "file" ? x : null;
|
|
56
|
+
};
|
|
57
|
+
const decode = (x) => Buffer.from(x.content ?? "", x.encoding === "base64" ? "base64" : "utf8").toString("utf8");
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
id, kind: "github", writes, repo, ref, root: base,
|
|
61
|
+
async list(prefix = "") {
|
|
62
|
+
const pre = prefix ? safePath(prefix) : "";
|
|
63
|
+
return (await tree()).map((x) => ({ path: rel(x.path), version: x.sha })).filter((x) => !pre || x.path === pre || x.path.startsWith(`${pre}/`)).sort((a, b) => a.path.localeCompare(b.path));
|
|
64
|
+
},
|
|
65
|
+
async read(p) {
|
|
66
|
+
const x = await contents(p);
|
|
67
|
+
if (!x) return null;
|
|
68
|
+
const commit = await head();
|
|
69
|
+
return { path: safePath(p), content: decode(x), version: x.sha, ...(commit ? { commit, permalink: `${web}/${repo}/blob/${commit}/${enc(full(p))}` } : {}) };
|
|
70
|
+
},
|
|
71
|
+
async version(p) {
|
|
72
|
+
return (await contents(p))?.sha ?? null;
|
|
73
|
+
},
|
|
74
|
+
async search(query, { limit = 50 } = {}) {
|
|
75
|
+
if (search === "api") {
|
|
76
|
+
try {
|
|
77
|
+
const q = `${query} repo:${repo}${base ? ` path:${base}` : ""}`;
|
|
78
|
+
const r = await gh("GET", `/search/code?q=${encodeURIComponent(q)}&per_page=${Math.min(limit, 100)}`);
|
|
79
|
+
return (r?.items ?? []).filter((x) => inside(x.path)).map((x) => ({ path: rel(x.path), version: x.sha })).slice(0, limit);
|
|
80
|
+
} catch (e) { if (![403, 422].includes(e.status)) throw e; }
|
|
81
|
+
}
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const x of await tree()) {
|
|
84
|
+
if (out.length >= limit) break;
|
|
85
|
+
if (!isText(x.path)) continue;
|
|
86
|
+
const c = await contents(rel(x.path));
|
|
87
|
+
if (c && matches(`${rel(x.path)}\n${decode(c)}`, query)) out.push({ path: rel(x.path), version: x.sha });
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
},
|
|
91
|
+
async write(p, content, { message, title } = {}) {
|
|
92
|
+
if (writes === "none") throw new Error(`${id} is read-only`);
|
|
93
|
+
const path = full(p);
|
|
94
|
+
const from = (await gh("GET", `/repos/${repo}/git/ref/heads/${enc(ref)}`))?.object?.sha;
|
|
95
|
+
if (!from) throw new Error(`${repo} has no branch ${ref}`);
|
|
96
|
+
const branch = `mentura/${path.replace(/[^\w.-]+/g, "-").slice(0, 60)}-${Date.now().toString(36)}`;
|
|
97
|
+
await gh("POST", `/repos/${repo}/git/refs`, { ref: `refs/heads/${branch}`, sha: from });
|
|
98
|
+
const existing = await contents(p, branch);
|
|
99
|
+
const put = await gh("PUT", `/repos/${repo}/contents/${enc(path)}`, { message: message ?? `Update ${path}`, content: Buffer.from(content, "utf8").toString("base64"), branch, ...(existing ? { sha: existing.sha } : {}) });
|
|
100
|
+
const pr = await gh("POST", `/repos/${repo}/pulls`, { title: title ?? message ?? `Update ${path}`, head: branch, base: ref, draft: true,
|
|
101
|
+
body: `${message ?? `Update ${path}`}\n\nOpened by Mentura. Merging this pull request accepts the write.` });
|
|
102
|
+
return { path: safePath(p), version: put?.content?.sha ?? blobSha(content), commit: put?.commit?.sha, pull_request: { number: pr.number, url: pr.html_url, branch } };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Jev (TypeSafe), for typed judgments (platform, EE-041): reads one Naive request on stdin (state,
|
|
3
|
+
// model, questions), sends it in chunks, and prints {model, answers, usage} on stdout. The framework
|
|
4
|
+
// builds the request and reads the answers (purpose/frameworks/idea-map, product-anatomy); only
|
|
5
|
+
// this side names the vendor and the credential.
|
|
6
|
+
//
|
|
7
|
+
// node purpose/frameworks/idea-map/ideamap.mjs request <venture> | TYPESAFE_API_KEY=... node platform/integrations/jev.mjs
|
|
8
|
+
|
|
9
|
+
const ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
10
|
+
const key = process.env.TYPESAFE_API_KEY ?? process.env.TYPESAFEAI_API_KEY;
|
|
11
|
+
if (!key) { console.error("TYPESAFE_API_KEY is not set: tag with the founder's agent instead"); process.exit(3); }
|
|
12
|
+
let input = "";
|
|
13
|
+
for await (const c of process.stdin) input += c;
|
|
14
|
+
const req = JSON.parse(input);
|
|
15
|
+
const keys = Object.keys(req.questions);
|
|
16
|
+
const size = Number(process.env.JEV_CHUNK ?? 260);
|
|
17
|
+
const chunks = Array.from({ length: Math.ceil(keys.length / size) }, (_, i) => keys.slice(i * size, (i + 1) * size));
|
|
18
|
+
const out = { model: null, answers: {}, usage: { input_tokens: 0, output_tokens: 0 }, requests: chunks.length };
|
|
19
|
+
const t0 = Date.now();
|
|
20
|
+
await Promise.all(chunks.map(async (ks) => {
|
|
21
|
+
const res = await fetch(ENDPOINT, { method: "POST", headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({ ...req, questions: Object.fromEntries(ks.map((k) => [k, req.questions[k]])) }), signal: AbortSignal.timeout(120000) });
|
|
23
|
+
if (!res.ok) throw new Error(`TypeSafe returned ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
24
|
+
const r = await res.json();
|
|
25
|
+
out.model = r.model;
|
|
26
|
+
Object.assign(out.answers, r.answers);
|
|
27
|
+
out.usage.input_tokens += r.usage?.input_tokens ?? 0; out.usage.output_tokens += r.usage?.output_tokens ?? 0;
|
|
28
|
+
}));
|
|
29
|
+
out.ms = Date.now() - t0;
|
|
30
|
+
console.log(JSON.stringify(out));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The docket's evidence as a LangSmith experiment (platform, EE-041, EE-075): the scenario states
|
|
3
|
+
// become a dataset (each with its expected next step and source), and one scored run of the docket
|
|
4
|
+
// and the judge becomes an experiment over it, with docket_right, docket_function_right, judge_right
|
|
5
|
+
// and agree as feedback on each row. Each run of this script adds one experiment, so versions of the
|
|
6
|
+
// docket can be compared side by side.
|
|
7
|
+
//
|
|
8
|
+
// node schema/docket-eval.mjs --answers a.json --json | LANGSMITH_API_KEY=... node platform/integrations/langsmith-docket.mjs [--name <experiment>]
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { states } from "../../schema/docket-eval.mjs";
|
|
12
|
+
|
|
13
|
+
const API = (process.env.LANGSMITH_ENDPOINT ?? "https://api.smith.langchain.com") + "/api/v1";
|
|
14
|
+
const key = process.env.LANGSMITH_API_KEY;
|
|
15
|
+
if (!key) { console.error("LANGSMITH_API_KEY is not set"); process.exit(3); }
|
|
16
|
+
const headers = { "x-api-key": key, "content-type": "application/json" };
|
|
17
|
+
const call = async (method, path, body) => {
|
|
18
|
+
const r = await fetch(API + path, { method, headers, ...(body ? { body: JSON.stringify(body) } : {}) });
|
|
19
|
+
if (!r.ok) throw new Error(`${method} ${path}: LangSmith answered ${r.status}: ${(await r.text()).slice(0, 300)}`);
|
|
20
|
+
return r.status === 204 ? null : r.json();
|
|
21
|
+
};
|
|
22
|
+
let input = "";
|
|
23
|
+
for await (const c of process.stdin) input += c;
|
|
24
|
+
const scored = JSON.parse(input);
|
|
25
|
+
const arg = (k, d) => (process.argv.includes(k) ? process.argv[process.argv.indexOf(k) + 1] : d);
|
|
26
|
+
|
|
27
|
+
const NAME = "Naive: docket scenarios";
|
|
28
|
+
const list = states();
|
|
29
|
+
let [dataset] = await call("GET", `/datasets?name=${encodeURIComponent(NAME)}`);
|
|
30
|
+
if (!dataset) dataset = await call("POST", "/datasets", { name: NAME, description: "Venture states where the right next step is argued from a cited source (schema/examples/docket-scenarios.json). Labels are an agent's draft until the founder reviews them." });
|
|
31
|
+
const have = await call("GET", `/examples?dataset=${dataset.id}&limit=100`);
|
|
32
|
+
const byState = new Map(have.map((e) => [e.inputs.state, e]));
|
|
33
|
+
for (const st of list) if (!byState.has(st.id)) {
|
|
34
|
+
const e = await call("POST", "/examples", { dataset_id: dataset.id, inputs: { state: st.id, phase: st.phase, situation: st.scenario.situation, decisions: st.decisions.map((d) => ({ id: d.id, question: d.question, function: d.function.id, render: d.render, held_by: d.held_by ?? null })) },
|
|
35
|
+
outputs: { decision: st.expected.decision, function: st.expected.function, why: st.scenario.why, source: st.scenario.source } });
|
|
36
|
+
byState.set(st.id, e);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const name = arg("--name", `docket ${new Date().toISOString().slice(0, 16)}`);
|
|
40
|
+
const session = await call("POST", "/sessions", { name, reference_dataset_id: dataset.id, description: `docket next ${scored.docket_next}, function ${scored.docket_function}${scored.judge_next != null ? `; judge next ${scored.judge_next}, agree ${scored.docket_judge_agree}` : ""}` });
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
const post = [];
|
|
43
|
+
const feedback = [];
|
|
44
|
+
for (const r of scored.rows) {
|
|
45
|
+
const id = randomUUID();
|
|
46
|
+
const dotted = `${now.replace(/[-:]/g, "").replace(".", "").replace("Z", "")}000Z${id}`;
|
|
47
|
+
post.push({ id, trace_id: id, dotted_order: dotted, name: "docket", run_type: "chain", start_time: now, end_time: now, session_id: session.id, reference_example_id: byState.get(r.state).id,
|
|
48
|
+
inputs: byState.get(r.state).inputs, outputs: { docket: r.docket, judge: r.judge ?? null }, extra: { metadata: { framework: "naive", state: r.state } } });
|
|
49
|
+
for (const k of ["docket_right", "docket_function_right", "judge_right", "agree"]) if (r[k] != null) feedback.push({ run_id: id, key: k, score: r[k] ? 1 : 0 });
|
|
50
|
+
}
|
|
51
|
+
await call("POST", "/runs/batch", { post });
|
|
52
|
+
for (const f of feedback) await call("POST", "/feedback", f);
|
|
53
|
+
console.log(JSON.stringify({ dataset: dataset.id, experiment: session.id, name, rows: post.length, url: `https://smith.langchain.com/o/${session.tenant_id}/datasets/${dataset.id}/compare?selectedSessions=${session.id}` }));
|