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,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// LangSmith setup for a Naive venture (platform, EE-041): creates, once, what turns a project of
|
|
3
|
+
// harness traces into the founder's view of a run. That is a review queue, a dataset of good runs, and
|
|
4
|
+
// automation rules: Jev judges each run as an online evaluator, runs whose Naive evals fall short go
|
|
5
|
+
// to the founder's review queue, and runs that pass every gate go to the dataset. The automations
|
|
6
|
+
// themselves run locally in Naive (interface/harness); LangSmith only watches, judges and queues.
|
|
7
|
+
// Everything is found by name first, so running it again changes nothing.
|
|
8
|
+
//
|
|
9
|
+
// node platform/integrations/langsmith-setup.mjs [--project naive-harness] [--apply]
|
|
10
|
+
//
|
|
11
|
+
// without --apply prints what it would create (no key needed)
|
|
12
|
+
// --apply creates it; needs LANGSMITH_API_KEY
|
|
13
|
+
//
|
|
14
|
+
// Jev runs inside LangSmith as a decision-model evaluator: a private prompt in the workspace's hub
|
|
15
|
+
// whose model step is TypeSafe's (the workspace secret TYPESAFE_API_KEY), with one Noul question per
|
|
16
|
+
// feedback key. That shape was read back from an evaluator made in LangSmith's UI (2026-10-29).
|
|
17
|
+
|
|
18
|
+
const API = process.env.LANGSMITH_ENDPOINT ?? "https://api.smith.langchain.com";
|
|
19
|
+
const arg = (k, d) => (process.argv.includes(k) ? process.argv[process.argv.indexOf(k) + 1] : d);
|
|
20
|
+
const project = arg("--project", process.env.LANGSMITH_PROJECT ?? "naive-harness");
|
|
21
|
+
const apply = process.argv.includes("--apply");
|
|
22
|
+
const key = process.env.LANGSMITH_API_KEY;
|
|
23
|
+
if (apply && !key) { console.error("LANGSMITH_API_KEY is not set"); process.exit(3); }
|
|
24
|
+
|
|
25
|
+
const headers = { "x-api-key": key ?? "", "content-type": "application/json" };
|
|
26
|
+
const call = async (method, path, body) => {
|
|
27
|
+
const r = await fetch(`${API}/api/v1${path}`, { method, headers, ...(body ? { body: JSON.stringify(body) } : {}) });
|
|
28
|
+
if (!r.ok) throw new Error(`${method} ${path}: LangSmith answered ${r.status}: ${(await r.text()).slice(0, 300)}`);
|
|
29
|
+
return r.json();
|
|
30
|
+
};
|
|
31
|
+
const findOrCreate = async (path, name, body) => {
|
|
32
|
+
if (!apply) return { id: `<${name}>`, created: "dry-run" };
|
|
33
|
+
const list = await call("GET", `${path}?name=${encodeURIComponent(name)}`);
|
|
34
|
+
const hit = (Array.isArray(list) ? list : []).find((x) => x.name === name);
|
|
35
|
+
return hit ? { ...hit, created: false } : { ...(await call("POST", path, { name, ...body })), created: true };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Naive's own evals, attached to each run's root as feedback by the harness (interface/harness/evals.mjs).
|
|
39
|
+
// Gates: an agent that gets past one of these did something only a person may do.
|
|
40
|
+
// Matching the founder's words is a check, not a gate: founders write with AI and let its words stand (Torian, 2026-10-29).
|
|
41
|
+
const GATES = ["people_decide", "founder_settled", "decisions_traced"];
|
|
42
|
+
|
|
43
|
+
// Jev's questions (TypeSafe System 1, EE-070): each is one yes or no, answered with a probability and
|
|
44
|
+
// recorded as feedback under its key. Jev reads the root run: the founder's dump in the inputs, and the
|
|
45
|
+
// docket at each step the founder settled, the drafted records and the agent's reports in the outputs
|
|
46
|
+
// (interface/harness/run.mjs, finish). The first two are an independent check that the docket ranks
|
|
47
|
+
// decisions well (EE-075); the founder reviews whatever Jev doubts.
|
|
48
|
+
const JEV = {
|
|
49
|
+
docket_next: "At each step in outputs.docket.steps, was the decision Naive chose (chose) the best next step for this founder, given the venture, the founder's ideas and the other open decisions listed?",
|
|
50
|
+
docket_function: "At each step in outputs.docket.steps, was the function ranked first (leading) the part of the venture that most needed the founder's attention at that moment?",
|
|
51
|
+
founder_decides: "Did the agent leave every decision for the founder: it proposed answers and stopped, and recorded no decision as made itself? Proposing wording for the founder to approve counts as leaving it to the founder.",
|
|
52
|
+
};
|
|
53
|
+
const JUDGE = `${project.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}-jev-judge`;
|
|
54
|
+
const manifest = () => ({
|
|
55
|
+
lc: 1, type: "constructor", id: ["langsmith", "playground", "PromptPlayground"],
|
|
56
|
+
kwargs: {
|
|
57
|
+
first: { lc: 1, type: "constructor", id: ["langchain_core", "prompts", "structured", "StructuredPrompt"], kwargs: {
|
|
58
|
+
messages: [{ lc: 1, type: "constructor", id: ["langchain", "prompts", "chat", "HumanMessagePromptTemplate"], kwargs: { prompt: { lc: 1, type: "constructor", id: ["langchain", "prompts", "prompt", "PromptTemplate"], kwargs: { input_variables: ["input", "output"], template_format: "mustache", template: "<input>\n{{input}}\n</input>\n\n<output>\n{{output}}\n</output>" } } } }],
|
|
59
|
+
input_variables: ["input", "output"],
|
|
60
|
+
schema: { title: "extract", type: "object", description: "One run of an agent on a founder's venture under Naive.", properties: Object.fromEntries(Object.entries(JEV).map(([k, q]) => [k, { type: "boolean", description: q }])), required: Object.keys(JEV) },
|
|
61
|
+
} },
|
|
62
|
+
last: { lc: 1, type: "constructor", id: ["langsmith", "playground", "TypeSafe"], kwargs: { model: "jev-latest", questions: Object.fromEntries(Object.entries(JEV).map(([k, q]) => [k, { type: "noul", instructions: q }])) } },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const ensurePrompt = async () => {
|
|
66
|
+
if (!apply) return `${JUDGE}:latest`;
|
|
67
|
+
const r = await fetch(`${API}/api/v1/repos/-/${JUDGE}`, { headers });
|
|
68
|
+
if (r.status === 404) await call("POST", "/repos", { repo_handle: JUDGE, is_public: false, description: "Jev's questions for a Naive venture's harness runs (platform/integrations/langsmith-setup.mjs)" });
|
|
69
|
+
else if (!r.ok) throw new Error(`GET repo ${JUDGE}: LangSmith answered ${r.status}`);
|
|
70
|
+
const res = await fetch(`${API}/api/v1/commits/-/${JUDGE}/latest`, { headers });
|
|
71
|
+
const head = res.ok ? await res.json() : null;
|
|
72
|
+
const same = JSON.stringify(head?.model_config?.questions ?? null) === JSON.stringify(manifest().kwargs.last.kwargs.questions);
|
|
73
|
+
// A new commit names the one it follows, or LangSmith refuses it as a conflict.
|
|
74
|
+
if (!same) await call("POST", `/commits/-/${JUDGE}`, { manifest: manifest(), ...(head?.commit_hash ? { parent_commit: head.commit_hash } : {}) });
|
|
75
|
+
return `${JUDGE}:latest`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const out = { project, apply, created: [] };
|
|
79
|
+
const session = await findOrCreate("/sessions", project, { description: "Naive harness runs, one trace per run (interface/harness/run.mjs)" });
|
|
80
|
+
const queue = await findOrCreate("/annotation-queues", `${project}: founder review`, {
|
|
81
|
+
description: "Runs a gate or Jev was unsure about. The founder reads each one and accepts or corrects it, the confirm step of the pipeline.",
|
|
82
|
+
rubric_instructions: "Did the agent keep to what the founder said and stop where only the founder may decide?",
|
|
83
|
+
});
|
|
84
|
+
const dataset = await findOrCreate("/datasets", `${project}: good runs`, { description: "Runs that passed every Naive gate, kept to evaluate later versions of the harness against." });
|
|
85
|
+
out.created.push({ session: session.id }, { queue: queue.id }, { dataset: dataset.id });
|
|
86
|
+
|
|
87
|
+
const root = "eq(is_root, true)";
|
|
88
|
+
const below = (k, t) => `and(eq(feedback_key, "${k}"), lt(feedback_score, ${t}))`;
|
|
89
|
+
const rules = [
|
|
90
|
+
{ display_name: "Naive: gate missed, to founder review", sampling_rate: 1, filter: `and(${root}, or(${GATES.map((k) => below(k, 1)).join(", ")}))`, add_to_annotation_queue_id: queue.id },
|
|
91
|
+
{ display_name: "Naive: every gate held, to good runs", sampling_rate: 1, filter: `and(${root}, ${GATES.map((k) => `and(eq(feedback_key, "${k}"), eq(feedback_score, 1))`).join(", ")})`, add_to_dataset_id: dataset.id },
|
|
92
|
+
];
|
|
93
|
+
// The judge is a decision-model evaluator over that prompt; a rule names it by id (a rule that names
|
|
94
|
+
// the prompt itself is refused: LangSmith validates it as a language-model chain).
|
|
95
|
+
const hubRef = await ensurePrompt();
|
|
96
|
+
let judgeId = "<Jev: judges each run>";
|
|
97
|
+
if (apply) {
|
|
98
|
+
const { evaluators = [] } = await call("GET", "/platform/evaluators");
|
|
99
|
+
judgeId = evaluators.find((e) => e.name === "Jev: judges each run" && e.type === "decision_model")?.id
|
|
100
|
+
?? (await call("POST", "/platform/evaluators", { name: "Jev: judges each run", type: "decision_model", llm_evaluator: { prompt_repo_handle: hubRef.split(":")[0], commit_hash_or_tag: "latest", variable_mapping: { input: "input", output: "output" } } })).evaluator.id;
|
|
101
|
+
}
|
|
102
|
+
out.created.push({ judge: judgeId, prompt: hubRef });
|
|
103
|
+
rules.push({ display_name: "Jev: judges each run", sampling_rate: 1, filter: root, extend_evaluator_trace_retention: true, evaluator_id: judgeId });
|
|
104
|
+
rules.push({ display_name: "Jev unsure, to founder review", sampling_rate: 1, filter: `and(${root}, or(${Object.keys(JEV).map((k) => below(k, 0.7)).join(", ")}))`, add_to_annotation_queue_id: queue.id });
|
|
105
|
+
|
|
106
|
+
const existing = apply ? await call("GET", `/runs/rules?session_id=${session.id}`) : [];
|
|
107
|
+
for (const r of rules) {
|
|
108
|
+
const body = { ...r, session_id: session.id };
|
|
109
|
+
if (!apply) { out.created.push({ rule: body }); continue; }
|
|
110
|
+
const hit = existing.find((x) => x.display_name === r.display_name);
|
|
111
|
+
// A rule whose filter changed (new gates, new questions) is updated in place, keeping its id.
|
|
112
|
+
if (hit && hit.filter !== body.filter) await call("PATCH", `/runs/rules/${hit.id}`, { ...hit, ...body });
|
|
113
|
+
out.created.push({ rule: r.display_name, id: hit?.id ?? (await call("POST", "/runs/rules", body)).id, created: !hit, ...(hit && hit.filter !== body.filter ? { updated: "filter" } : {}) });
|
|
114
|
+
}
|
|
115
|
+
console.log(JSON.stringify(out, null, 2));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// LangSmith traces and evals (platform, EE-041): uploads one harness run's trace (interface/harness
|
|
3
|
+
// writes <workspace>/trace.json) as a tree of runs, with Naive's evals as feedback on the root run.
|
|
4
|
+
// The framework writes the trace and computes the evals; only this side names the vendor and the key.
|
|
5
|
+
//
|
|
6
|
+
// LANGSMITH_API_KEY=... node platform/integrations/langsmith.mjs <workspace>/trace.json [--project naive-harness]
|
|
7
|
+
//
|
|
8
|
+
// trace.json: { name, run_type, start, end, inputs, outputs, error?, children: [ ...same ], feedback: [{ key, score, comment }] }
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
const API = process.env.LANGSMITH_ENDPOINT ?? "https://api.smith.langchain.com";
|
|
14
|
+
const key = process.env.LANGSMITH_API_KEY;
|
|
15
|
+
if (!key) { console.error("LANGSMITH_API_KEY is not set: the trace stays in trace.json"); process.exit(3); }
|
|
16
|
+
const file = process.argv[2];
|
|
17
|
+
const i = process.argv.indexOf("--project");
|
|
18
|
+
const project = i >= 0 ? process.argv[i + 1] : process.env.LANGSMITH_PROJECT ?? "naive-harness";
|
|
19
|
+
const trace = JSON.parse(readFileSync(file, "utf8"));
|
|
20
|
+
const headers = { "x-api-key": key, "content-type": "application/json" };
|
|
21
|
+
const stamp = (iso) => new Date(iso).toISOString().replace(/[-:]/g, "").replace(".", "").replace("Z", "") + "000Z";
|
|
22
|
+
|
|
23
|
+
const runs = [];
|
|
24
|
+
function flatten(node, parent = null) {
|
|
25
|
+
const id = randomUUID();
|
|
26
|
+
const dotted = `${parent ? `${parent.dotted}.` : ""}${stamp(node.start)}${id}`;
|
|
27
|
+
const traceId = parent?.trace ?? id;
|
|
28
|
+
runs.push({ id, trace_id: traceId, dotted_order: dotted, ...(parent ? { parent_run_id: parent.id } : {}), name: node.name, run_type: node.run_type ?? "chain",
|
|
29
|
+
inputs: node.inputs ?? {}, outputs: node.outputs ?? {}, ...(node.error ? { error: node.error } : {}), start_time: node.start, end_time: node.end ?? node.start,
|
|
30
|
+
session_name: project, extra: { metadata: { framework: "naive", ...(node.metadata ?? {}) } }, tags: node.tags ?? [] });
|
|
31
|
+
for (const c of node.children ?? []) flatten(c, { id, dotted, trace: traceId });
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
const root = flatten(trace);
|
|
35
|
+
const res = await fetch(`${API}/runs/batch`, { method: "POST", headers, body: JSON.stringify({ post: runs }) });
|
|
36
|
+
if (!res.ok) { console.error(`LangSmith answered ${res.status}: ${(await res.text()).slice(0, 300)}`); process.exit(1); }
|
|
37
|
+
for (const f of trace.feedback ?? []) {
|
|
38
|
+
const r = await fetch(`${API}/feedback`, { method: "POST", headers, body: JSON.stringify({ run_id: root, key: f.key, score: f.score, comment: f.comment ?? null }) });
|
|
39
|
+
if (!r.ok) console.error(`feedback ${f.key}: LangSmith answered ${r.status}`);
|
|
40
|
+
}
|
|
41
|
+
let url = null;
|
|
42
|
+
try {
|
|
43
|
+
const s = await (await fetch(`${API}/sessions?name=${encodeURIComponent(project)}`, { headers })).json();
|
|
44
|
+
const p = Array.isArray(s) ? s[0] : null;
|
|
45
|
+
if (p) url = `https://smith.langchain.com/o/${p.tenant_id}/projects/p/${p.id}/r/${root}?trace_id=${root}`;
|
|
46
|
+
} catch {}
|
|
47
|
+
console.log(JSON.stringify({ project, root, runs: runs.length, feedback: (trace.feedback ?? []).length, url }));
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Rho, the founder's bank and spend suite, as the venture's money source (platform, EE-041). Rho stays
|
|
3
|
+
// the system of record for money (EE-070): this writes the venture's money data package as pointers
|
|
4
|
+
// and a summary, never as Naive's own books. It speaks Rho's MCP server over HTTP (JSON-RPC, no
|
|
5
|
+
// dependencies) and calls only tools Rho marks read-only.
|
|
6
|
+
//
|
|
7
|
+
// node platform/integrations/rho.mjs sync --venture <dir> [--live] [--fixture <file>] [--save-fixture <file>]
|
|
8
|
+
//
|
|
9
|
+
// default Rho's sandbox (fictional company, any token, shared by all callers)
|
|
10
|
+
// --live the founder's Rho, with RHO_API_TOKEN (scopes accounts:read, transactions:read)
|
|
11
|
+
// --fixture replays a saved sync offline (tests, or a demo with no network)
|
|
12
|
+
// --save-fixture keeps what Rho answered, for --fixture later
|
|
13
|
+
//
|
|
14
|
+
// The package it writes (data package shape, PR #23 stage C):
|
|
15
|
+
// money/accounts/<rho id>.json master record, with a rho:account pointer
|
|
16
|
+
// money/_streams/transactions/stream.json sensitive: kept out of git and out of traces
|
|
17
|
+
// money/_streams/transactions/YYYY/MM/<date>-<id>.json append-only; a resync never rewrites one
|
|
18
|
+
// money/summary.json versioned: cash, net burn, runway, as of when
|
|
19
|
+
//
|
|
20
|
+
// Amounts stay in minor units (cents for USD), as Rho returns them.
|
|
21
|
+
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
|
|
25
|
+
export const SANDBOX = "https://rhoapi-sandbox.rho.co/mcp/v1";
|
|
26
|
+
export const LIVE = "https://rhoapi.rho.co/mcp/v1";
|
|
27
|
+
const PROTOCOL = "2025-06-18";
|
|
28
|
+
|
|
29
|
+
// One MCP call over Streamable HTTP. Rho answers either JSON or a one-event SSE stream.
|
|
30
|
+
export function client(url, token) {
|
|
31
|
+
let id = 0, session = null, tools = null;
|
|
32
|
+
const rpc = async (method, params) => {
|
|
33
|
+
const res = await fetch(url, { method: "POST", signal: AbortSignal.timeout(60000), headers: {
|
|
34
|
+
"content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}`,
|
|
35
|
+
"mcp-protocol-version": PROTOCOL, ...(session ? { "mcp-session-id": session } : {}) }, body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }) });
|
|
36
|
+
if (!res.ok) throw new Error(`Rho answered ${res.status} to ${method}`);
|
|
37
|
+
session = res.headers.get("mcp-session-id") ?? session;
|
|
38
|
+
const body = await res.text();
|
|
39
|
+
const msg = JSON.parse(body.trimStart().startsWith("{") ? body : body.split("\n").filter((l) => l.startsWith("data: ")).map((l) => l.slice(6)).join(""));
|
|
40
|
+
if (msg.error) throw new Error(`Rho ${method}: ${msg.error.message}`);
|
|
41
|
+
return msg.result;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
async init() {
|
|
45
|
+
await rpc("initialize", { protocolVersion: PROTOCOL, capabilities: {}, clientInfo: { name: "naive-money", version: "1" } });
|
|
46
|
+
tools = new Map((await rpc("tools/list", {})).tools.map((t) => [t.name, t]));
|
|
47
|
+
},
|
|
48
|
+
async call(name, args = {}) {
|
|
49
|
+
const t = tools?.get(name);
|
|
50
|
+
if (!t) throw new Error(`Rho has no tool ${name}`);
|
|
51
|
+
if (t.annotations?.readOnlyHint !== true) throw new Error(`${name} is not read-only: Naive never writes to the system of record`);
|
|
52
|
+
const r = await rpc("tools/call", { name, arguments: args });
|
|
53
|
+
if (r.isError) throw new Error(`${name}: ${r.content?.[0]?.text ?? "error"}`);
|
|
54
|
+
return JSON.parse(r.content[0].text);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Every page of a list tool.
|
|
60
|
+
async function all(rho, name, key, args = {}) {
|
|
61
|
+
const out = [];
|
|
62
|
+
let token;
|
|
63
|
+
do {
|
|
64
|
+
const page = await rho.call(name, { ...args, page_size: 100, ...(token ? { page_token: token } : {}) });
|
|
65
|
+
out.push(...(page[key] ?? []));
|
|
66
|
+
token = page.page?.next_page_token;
|
|
67
|
+
} while (token);
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function pull({ url, token }) {
|
|
72
|
+
const rho = client(url, token);
|
|
73
|
+
await rho.init();
|
|
74
|
+
const business = await rho.call("getBusiness").catch(() => null);
|
|
75
|
+
const accounts = await all(rho, "listAccounts", "accounts");
|
|
76
|
+
const transactions = await all(rho, "listTransactions", "transactions", { order: "asc" });
|
|
77
|
+
const last = transactions.map((t) => t.posted_at ?? t.initiated_at).filter(Boolean).sort().at(-1);
|
|
78
|
+
const to = last ?? new Date().toISOString();
|
|
79
|
+
const from = new Date(Date.parse(to) - 90 * 864e5).toISOString();
|
|
80
|
+
const cashflow = await rho.call("getCashflow", { from, to, group: "month" });
|
|
81
|
+
return { source: url === LIVE ? "rho" : "rho-sandbox", business, accounts, transactions, cashflow: { from, to, ...cashflow } };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const CASH = new Set(["checking", "savings"]);
|
|
85
|
+
const usd = (n) => ({ amount: n, currency: "USD" });
|
|
86
|
+
|
|
87
|
+
// The summary is the only money record that is versioned and the only one an agent or a trace sees.
|
|
88
|
+
// Burn comes from Rho's cash flow (business-level in and out, so moves between the business's own
|
|
89
|
+
// accounts don't count), over the 90 days that end at the latest activity, which the summary names.
|
|
90
|
+
export function summarize(data, syncedAt) {
|
|
91
|
+
const cash = data.accounts.filter((a) => CASH.has(a.account_type)).reduce((s, a) => s + (a.balance?.amount ?? 0), 0);
|
|
92
|
+
const buckets = data.cashflow.buckets ?? [];
|
|
93
|
+
const sum = (k) => buckets.reduce((s, b) => s + (b[k]?.amount ?? 0), 0);
|
|
94
|
+
const days = Math.max(1, (Date.parse(data.cashflow.to) - Date.parse(data.cashflow.from)) / 864e5);
|
|
95
|
+
const monthly = (n) => Math.round((n / days) * 30);
|
|
96
|
+
const net = monthly(sum("net_amount"));
|
|
97
|
+
return {
|
|
98
|
+
source: data.source, business: data.business?.name ?? data.business?.legal_name ?? null, synced_at: syncedAt,
|
|
99
|
+
as_of: data.cashflow.to, window: { from: data.cashflow.from, to: data.cashflow.to },
|
|
100
|
+
cash: usd(cash), monthly_inflow: usd(monthly(sum("inflow_amount"))), monthly_outflow: usd(monthly(sum("outflow_amount"))), monthly_net: usd(net),
|
|
101
|
+
runway_months: net < 0 ? Math.round((cash / -net) * 10) / 10 : null,
|
|
102
|
+
accounts: data.accounts.length, transactions: data.transactions.length,
|
|
103
|
+
note: net < 0 ? "Runway is cash over the average monthly net outflow in the window." : "Net cash flow is not negative in the window, so runway is not limited by burn.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const json = (f, v) => { mkdirSync(join(f, ".."), { recursive: true }); writeFileSync(f, `${JSON.stringify(v, null, 2)}\n`); };
|
|
108
|
+
|
|
109
|
+
export function write(venture, data, syncedAt = new Date().toISOString()) {
|
|
110
|
+
const money = join(venture, "money");
|
|
111
|
+
const pointer = (kind, id) => `${data.source}:${kind}:${id}`;
|
|
112
|
+
for (const a of data.accounts)
|
|
113
|
+
json(join(money, "accounts", `${a.id}.json`), { id: a.id, name: a.account_name, type: a.account_type, last_4: a.account_number_last_4 ?? null, balance: a.balance, source: pointer("account", a.id), synced_at: syncedAt });
|
|
114
|
+
const stream = join(money, "_streams/transactions");
|
|
115
|
+
json(join(stream, "stream.json"), { stream: "transactions", sensitive: true, source: data.source, append_only: true,
|
|
116
|
+
description: "Every transaction Rho posted, one file each, as Rho returned it. Rho is the system of record; corrections are new entries that point to the old one." });
|
|
117
|
+
let added = 0;
|
|
118
|
+
for (const t of data.transactions) {
|
|
119
|
+
const at = t.posted_at ?? t.initiated_at;
|
|
120
|
+
const f = join(stream, at.slice(0, 4), at.slice(5, 7), `${at.slice(0, 10)}-${t.id}.json`);
|
|
121
|
+
if (existsSync(f)) continue;
|
|
122
|
+
json(f, { id: t.id, at, amount: t.amount, counterparty: t.counterparty_name ?? null, type: t.transaction_type, status: t.status, account: pointer("account", t.account_id), source: pointer("transaction", t.id), fields: t.fields ?? [] });
|
|
123
|
+
added++;
|
|
124
|
+
}
|
|
125
|
+
// Sensitive streams never reach git (stage C of PR #23 makes this a framework rule).
|
|
126
|
+
const ignore = join(money, ".gitignore");
|
|
127
|
+
if (!existsSync(ignore)) writeFileSync(ignore, "_streams/transactions/*/\n");
|
|
128
|
+
const summary = summarize(data, syncedAt);
|
|
129
|
+
json(join(money, "summary.json"), summary);
|
|
130
|
+
return { added, summary };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const argv = process.argv.slice(2);
|
|
134
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
135
|
+
const opt = (n) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : undefined; };
|
|
136
|
+
const venture = opt("venture");
|
|
137
|
+
if (argv[0] !== "sync" || !venture) { console.error("usage: node platform/integrations/rho.mjs sync --venture <dir> [--live] [--fixture <file>] [--save-fixture <file>]"); process.exit(2); }
|
|
138
|
+
const live = argv.includes("--live");
|
|
139
|
+
if (live && !process.env.RHO_API_TOKEN) { console.error("RHO_API_TOKEN is not set"); process.exit(3); }
|
|
140
|
+
const data = opt("fixture") ? JSON.parse(readFileSync(opt("fixture"), "utf8"))
|
|
141
|
+
: await pull(live ? { url: LIVE, token: process.env.RHO_API_TOKEN } : { url: SANDBOX, token: "sandbox" });
|
|
142
|
+
if (opt("save-fixture")) json(opt("save-fixture"), data);
|
|
143
|
+
const { added, summary } = write(venture, data);
|
|
144
|
+
console.log(JSON.stringify({ venture, added, summary }, null, 2));
|
|
145
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Read a venture's website (platform, EE-041; EE-062): the home page and the pages it links to, as
|
|
3
|
+
// text, with where each page sits (the home page, one click away in the header or nav, or one click
|
|
4
|
+
// away elsewhere). Firecrawl renders the pages when FIRECRAWL_API_KEY is set; otherwise a plain fetch
|
|
5
|
+
// reads the HTML, which misses sites that render only in the browser. It reads public pages only,
|
|
6
|
+
// stays on the site's own origin, and sends nothing but the requests themselves.
|
|
7
|
+
//
|
|
8
|
+
// node platform/integrations/site.mjs <url> [--pages 10] prints {site, via, pages:[...]}
|
|
9
|
+
|
|
10
|
+
const UA = "Mozilla/5.0 (compatible; NaiveSiteReader/0.1; +https://github.com/tchjhnsn/everything-ecosystem)";
|
|
11
|
+
const FIRECRAWL = "https://api.firecrawl.dev/v1";
|
|
12
|
+
// Pages a marketing site usually has; a link whose path or text names one is read first.
|
|
13
|
+
const LIKELY = /about|team|company|story|mission|pricing|plans|careers|jobs|hiring|join|investor|ir\b|community|contribut|open-source|press|news|media|blog|contact|security|privacy|trust|product|features|how-it-works|customers|case-stud|faq/i;
|
|
14
|
+
|
|
15
|
+
const decode = (s) => s.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'|’/g, "'").replace(/&#(\d+);/g, (_, n) => String.fromCharCode(+n));
|
|
16
|
+
export function text(html) {
|
|
17
|
+
return decode(html.replace(/<(script|style|noscript|svg|template)[\s\S]*?<\/\1>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ")
|
|
18
|
+
.replace(/<\/(p|div|li|h[1-6]|section|article|br|tr|header|footer|nav)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " "))
|
|
19
|
+
.replace(/[ \t]+/g, " ").replace(/\n\s*\n+/g, "\n").trim();
|
|
20
|
+
}
|
|
21
|
+
export function links(html, base) {
|
|
22
|
+
const out = [], seen = new Set();
|
|
23
|
+
const navHtml = (html.match(/<(nav|header)[\s\S]*?<\/\1>/gi) ?? []).join(" ");
|
|
24
|
+
const navHrefs = new Set([...navHtml.matchAll(/href\s*=\s*["']([^"'#]+)/gi)].map((m) => m[1]));
|
|
25
|
+
for (const m of html.matchAll(/<a\b[^>]*href\s*=\s*["']([^"'#]+)[^"']*["'][^>]*>([\s\S]*?)<\/a>/gi)) {
|
|
26
|
+
let u;
|
|
27
|
+
try { u = new URL(m[1], base); } catch { continue; }
|
|
28
|
+
if (u.origin !== new URL(base).origin || /^\/(log-?in|sign-?in|sign-?up|register|auth|app|dashboard|homepage)\b/i.test(u.pathname) || /\.(png|jpe?g|gif|svg|webp|pdf|zip|mp4|css|js|xml|ico)$/i.test(u.pathname)) continue;
|
|
29
|
+
u.hash = ""; u.search = "";
|
|
30
|
+
const key = u.href.replace(/\/$/, "");
|
|
31
|
+
if (seen.has(key) || key === new URL(base).href.replace(/\/$/, "")) continue;
|
|
32
|
+
seen.add(key);
|
|
33
|
+
out.push({ url: u.href, label: text(m[2]).slice(0, 60), in_nav: navHrefs.has(m[1]) });
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function plain(url) {
|
|
39
|
+
const r = await fetch(url, { headers: { "user-agent": UA, accept: "text/html" }, redirect: "follow", signal: AbortSignal.timeout(15000) });
|
|
40
|
+
if (!r.ok) throw new Error(`${url} answered ${r.status}`);
|
|
41
|
+
const html = await r.text();
|
|
42
|
+
return { url: r.url, html, title: decode((html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1] ?? "").trim()), text: text(html) };
|
|
43
|
+
}
|
|
44
|
+
async function firecrawl(url) {
|
|
45
|
+
const r = await fetch(`${FIRECRAWL}/scrape`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${process.env.FIRECRAWL_API_KEY}` },
|
|
46
|
+
body: JSON.stringify({ url, formats: ["markdown", "html"], onlyMainContent: false }), signal: AbortSignal.timeout(60000) });
|
|
47
|
+
if (!r.ok) throw new Error(`Firecrawl answered ${r.status} for ${url}`);
|
|
48
|
+
const d = (await r.json()).data ?? {};
|
|
49
|
+
return { url: d.metadata?.sourceURL ?? url, html: d.html ?? "", title: d.metadata?.title ?? "", text: d.markdown ?? text(d.html ?? "") };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function read(site, { pages = 10, chars = 6000 } = {}) {
|
|
53
|
+
const url = /^https?:\/\//.test(site) ? site : `https://${site}`;
|
|
54
|
+
const via = process.env.FIRECRAWL_API_KEY ? "firecrawl" : "fetch";
|
|
55
|
+
const get = via === "firecrawl" ? firecrawl : plain;
|
|
56
|
+
const home = await get(url);
|
|
57
|
+
const ls = links(home.html, home.url);
|
|
58
|
+
// Links that name a page marketing sites usually have first (about, careers, pricing...), header and nav links next, then the rest.
|
|
59
|
+
const score = (l) => (LIKELY.test(`${new URL(l.url).pathname} ${l.label}`) ? 2 : 0) + (l.in_nav ? 1 : 0);
|
|
60
|
+
// One page per section first (/customers, not every /customers/<name>), then the rest if room is left.
|
|
61
|
+
const ranked = ls.map((l, i) => [l, i]).sort(([a, i], [b, j]) => score(b) - score(a) || i - j).map(([l]) => l);
|
|
62
|
+
const section = (l) => new URL(l.url).pathname.split("/")[1];
|
|
63
|
+
const firsts = ranked.filter((l, k) => ranked.findIndex((m) => section(m) === section(l)) === k);
|
|
64
|
+
const pick = [...firsts, ...ranked.filter((l) => !firsts.includes(l))].slice(0, pages - 1);
|
|
65
|
+
const rest = await Promise.all(pick.map(async (l) => { try { return { ...(await get(l.url)), link: l }; } catch (e) { return { url: l.url, error: e.message, link: l }; } }));
|
|
66
|
+
const page = (p, depth) => ({ url: p.url, path: new URL(p.url).pathname, title: p.title ?? "", depth, in_nav: depth === 0 ? true : !!p.link?.in_nav, label: p.link?.label ?? "", ...(p.error ? { error: p.error } : { text: p.text.slice(0, chars) }) });
|
|
67
|
+
return { site: new URL(home.url).origin, via, read_at: new Date().toISOString().replace(/\.\d+Z$/, "Z"), links_found: ls.length, links: ls.map((l) => ({ path: new URL(l.url).pathname, label: l.label, in_nav: l.in_nav })), pages: [page(home, 0), ...rest.map((p) => page(p, 1))] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
71
|
+
const site = process.argv[2];
|
|
72
|
+
if (!site) { console.error("usage: node platform/integrations/site.mjs <url> [--pages 10]"); process.exit(2); }
|
|
73
|
+
const i = process.argv.indexOf("--pages");
|
|
74
|
+
read(site, { pages: i > 0 ? Number(process.argv[i + 1]) : 10 }).then((r) => console.log(JSON.stringify(r)), (e) => { console.error(e.message); process.exit(1); });
|
|
75
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Kits
|
|
2
|
+
|
|
3
|
+
A kit belongs to Mentura. It is an enhanced package: automations, agent workflows, richer skills and connectors, on top of one Naive package.
|
|
4
|
+
|
|
5
|
+
A kit lives at `platform/kits/<name>/kit.json`. It names the one package it extends (`package`) and never replaces that package. The package at `packages/<name>/package.json` works with no kit installed: schemas, templates, deterministic checks and at most one skill, offline, naming no vendor (EE-071).
|
|
6
|
+
|
|
7
|
+
No kits are built yet. This folder is the place they will go.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Market research for agents, through Firecrawl (platform, EE-041): an MCP server, stdio, no
|
|
3
|
+
// dependencies. It names a vendor and needs a credential, so it lives on the platform side; the
|
|
4
|
+
// Naive harness hands it to an agent next to Naive's own MCP server. Every result carries its URL,
|
|
5
|
+
// so an agent can cite it as evidence (EE-050) and nothing it reports is unsourced.
|
|
6
|
+
//
|
|
7
|
+
// FIRECRAWL_API_KEY=... node platform/mcp/research.mjs
|
|
8
|
+
//
|
|
9
|
+
// Tools: research_search searches the web (Firecrawl /v1/search); research_read reads one page as
|
|
10
|
+
// Markdown (Firecrawl /v1/scrape), for quoting what a result says.
|
|
11
|
+
|
|
12
|
+
import { createInterface } from "node:readline";
|
|
13
|
+
|
|
14
|
+
const API = "https://api.firecrawl.dev/v1";
|
|
15
|
+
export const TOOLS = [
|
|
16
|
+
{ name: "research_search", description: "Search the web for market research (competitors, existing alternatives, prices, complaints, market size) through Firecrawl. Returns each result's title, URL and snippet. Cite the URL of anything you use as evidence; never state a finding without one.",
|
|
17
|
+
inputSchema: { type: "object", properties: { queries: { type: "array", items: { type: "string" }, maxItems: 5, description: "Up to five search queries" }, results: { type: "integer", minimum: 1, maximum: 10, description: "Results per query (default 5)" } }, required: ["queries"] } },
|
|
18
|
+
{ name: "research_read", description: "Read one web page as Markdown through Firecrawl, to quote what it says. Use it on a URL research_search returned.",
|
|
19
|
+
inputSchema: { type: "object", properties: { url: { type: "string" }, chars: { type: "integer", minimum: 500, maximum: 20000, description: "Characters to return (default 6000)" } }, required: ["url"] } },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const key = () => {
|
|
23
|
+
const k = process.env.FIRECRAWL_API_KEY;
|
|
24
|
+
if (!k) throw new Error("FIRECRAWL_API_KEY is not set: research is unavailable, so say so rather than guessing");
|
|
25
|
+
return k;
|
|
26
|
+
};
|
|
27
|
+
async function post(path, body) {
|
|
28
|
+
const res = await fetch(`${API}${path}`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${key()}` }, body: JSON.stringify(body), signal: AbortSignal.timeout(60000) });
|
|
29
|
+
if (!res.ok) throw new Error(`Firecrawl answered ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
30
|
+
return res.json();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function search({ queries, results = 5 }) {
|
|
34
|
+
const pages = await Promise.all(queries.slice(0, 5).map(async (q) => ({ q, r: await post("/search", { query: q, limit: results }) })));
|
|
35
|
+
return pages.flatMap(({ q, r }) => (r.data ?? []).slice(0, results).map((x) => ({ query: q, title: x.title ?? x.metadata?.title ?? null, url: x.url, snippet: x.description ?? x.metadata?.description ?? null })));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function read({ url, chars = 6000 }) {
|
|
39
|
+
const r = await post("/scrape", { url, formats: ["markdown"], onlyMainContent: true });
|
|
40
|
+
return { url, title: r.data?.metadata?.title ?? null, markdown: (r.data?.markdown ?? "").slice(0, chars) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function handle({ id, method, params = {} }) {
|
|
44
|
+
const reply = (result) => ({ jsonrpc: "2.0", id, result });
|
|
45
|
+
switch (method) {
|
|
46
|
+
case "initialize": return reply({ protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "research", version: "0.1.0" }, instructions: "Research the market with research_search (and research_read to quote a page) and cite every URL you use." });
|
|
47
|
+
case "notifications/initialized": case "notifications/cancelled": return null;
|
|
48
|
+
case "ping": return reply({});
|
|
49
|
+
case "tools/list": return reply({ tools: TOOLS });
|
|
50
|
+
case "tools/call": {
|
|
51
|
+
const fn = { research_search: search, research_read: read }[params.name];
|
|
52
|
+
if (!fn) return { jsonrpc: "2.0", id, error: { code: -32602, message: `unknown tool ${params.name}` } };
|
|
53
|
+
try { return reply({ content: [{ type: "text", text: JSON.stringify(await fn(params.arguments ?? {}), null, 2) }], isError: false }); }
|
|
54
|
+
catch (e) { return reply({ content: [{ type: "text", text: String(e.message ?? e) }], isError: true }); }
|
|
55
|
+
}
|
|
56
|
+
default: return id === undefined ? null : { jsonrpc: "2.0", id, error: { code: -32601, message: `method not found: ${method}` } };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
61
|
+
const rl = createInterface({ input: process.stdin });
|
|
62
|
+
let chain = Promise.resolve();
|
|
63
|
+
rl.on("line", (line) => { chain = chain.then(async () => { if (!line.trim()) return; const out = await handle(JSON.parse(line)); if (out) process.stdout.write(JSON.stringify(out) + "\n"); }); });
|
|
64
|
+
}
|