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,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The decision harness demo (EE-054, EE-058): an agent works on a venture through Naive's MCP
|
|
3
|
+
// server, is stopped by a decision only a person can make, and goes on once the founder decides.
|
|
4
|
+
// Runs on a copy of the example workspace, so nothing real changes. No model is called: the agent's
|
|
5
|
+
// moves are scripted, so any harness (Claude Code, LangGraph, the Claude Agent SDK) can replay them.
|
|
6
|
+
//
|
|
7
|
+
// node interface/demo/decision-harness.mjs the founder types the decision at the prompt
|
|
8
|
+
// node interface/demo/decision-harness.mjs --auto the founder's step is taken for them (tests)
|
|
9
|
+
// node interface/demo/decision-harness.mjs --board also serves the live decision board for the run
|
|
10
|
+
// at http://127.0.0.1:4331/ and paces the steps
|
|
11
|
+
|
|
12
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
13
|
+
import { cpSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { createInterface } from "node:readline";
|
|
17
|
+
|
|
18
|
+
const repo = new URL("../../", import.meta.url).pathname;
|
|
19
|
+
const auto = process.argv.includes("--auto");
|
|
20
|
+
const tmp = mkdtempSync(join(tmpdir(), "naive-demo-"));
|
|
21
|
+
const ws = join(tmp, "workspace");
|
|
22
|
+
cpSync(join(repo, "schema/examples/workspace"), ws, { recursive: true });
|
|
23
|
+
const venture = "tandem", dir = join(ws, "ventures", venture);
|
|
24
|
+
const env = { ...process.env, MENTURA_WORKSPACE: ws };
|
|
25
|
+
const board = process.argv.includes("--board");
|
|
26
|
+
let boardServer = null;
|
|
27
|
+
if (board) { process.env.MENTURA_WORKSPACE = ws; boardServer = (await import("./board.mjs")).serve(4331, venture); console.log("Decision board: http://127.0.0.1:4331/"); }
|
|
28
|
+
const say = async (who, text) => {
|
|
29
|
+
if (board) await new Promise((r) => setTimeout(r, 2500));
|
|
30
|
+
console.log(`\n${who === "agent" ? "AGENT " : who === "naive" ? "NAIVE " : "FOUNDER"} ${text}`);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// The agent's side: an MCP client over stdio.
|
|
34
|
+
const server = spawn("node", [join(repo, "interface/mcp/server.mjs")], { env, stdio: ["pipe", "pipe", "inherit"] });
|
|
35
|
+
const pending = new Map();
|
|
36
|
+
let next = 1;
|
|
37
|
+
createInterface({ input: server.stdout }).on("line", (l) => { const m = JSON.parse(l); pending.get(m.id)?.(m); pending.delete(m.id); });
|
|
38
|
+
const rpc = (method, params) => new Promise((res) => { const id = next++; pending.set(id, res); server.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); });
|
|
39
|
+
const tool = async (name, args) => { const r = (await rpc("tools/call", { name, arguments: args })).result; return { error: r.isError, body: (() => { try { return JSON.parse(r.content[0].text); } catch { return r.content[0].text; } })() }; };
|
|
40
|
+
|
|
41
|
+
let failed = false;
|
|
42
|
+
const expect = (cond, what) => { if (!cond) { failed = true; console.error(`\nDEMO FAILED: ${what}`); } };
|
|
43
|
+
try {
|
|
44
|
+
await rpc("initialize", { protocolVersion: "2025-06-18", clientInfo: { name: "demo-agent", version: "0.1" } });
|
|
45
|
+
const tools = (await rpc("tools/list", {})).result.tools.map((t) => t.name);
|
|
46
|
+
await say("agent", `Connected to Naive over MCP: ${tools.length} tools.`);
|
|
47
|
+
|
|
48
|
+
const before = await tool("mentura_decisions", { venture, render: ["overdue", "now"] });
|
|
49
|
+
await say("naive", `${before.body.decisions.length} decisions render now for ${venture} (${before.body.phase}). The agent works only on those.`);
|
|
50
|
+
|
|
51
|
+
await say("agent", "I want to change the product card. First I propose the decision that change needs.");
|
|
52
|
+
const p = await tool("mentura_propose_decision", { venture, agent_id: "builder", model: "scripted", decision: {
|
|
53
|
+
id: "reposition-shop-app", question: "Do we reposition the shop app around payroll first?", at: "products", name: "shop-app.reposition",
|
|
54
|
+
answer: "Yes: lead with payroll, which owners named as the worst admin job.", due: new Date(Date.now() + 864e5).toISOString().replace(/\.\d+Z$/, "Z"),
|
|
55
|
+
outcomes: [{ id: "trial-signups", projected: "Ten shops start a trial within two weeks of the change", measure: "Trial sign-ups" }],
|
|
56
|
+
unlocks: [{ activity: "Rewrite the product card", routes: ["purpose/product/*"], for: ["builder"] }] } });
|
|
57
|
+
expect(!p.error && p.body.awaiting === "approval", `propose: ${JSON.stringify(p.body)}`);
|
|
58
|
+
await say("naive", `Proposed ${p.body.proposed}. It waits for a person: an agent cannot decide it.`);
|
|
59
|
+
|
|
60
|
+
const card = JSON.parse(readFileSync(join(dir, "products/shop-app.json"), "utf8"));
|
|
61
|
+
const changed = { ...card, description: "Payroll first, then bookings and stock, for small service shops" };
|
|
62
|
+
const w1 = await tool("mentura_write", { venture, route: "purpose/product/shop-app", record: changed, agent_id: "builder", note: "reposition around payroll" });
|
|
63
|
+
expect(w1.error && /locked until decision reposition-shop-app/.test(w1.body.error), `first write: ${JSON.stringify(w1.body)}`);
|
|
64
|
+
await say("naive", `Refused: ${w1.body.error}`);
|
|
65
|
+
await say("agent", "Paused. I'll go on when the founder decides.");
|
|
66
|
+
|
|
67
|
+
// The founder's step, outside the agent's reach: the CLI, with a typed confirm.
|
|
68
|
+
let typed = "reposition-shop-app";
|
|
69
|
+
if (!auto && process.stdin.isTTY) {
|
|
70
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
71
|
+
typed = await new Promise((res) => rl.question(`\nFOUNDER Type reposition-shop-app to approve the agent's wording (anything else declines): `, (a) => { rl.close(); res(a.trim()); }));
|
|
72
|
+
} else await say("founder", "(--auto) types reposition-shop-app");
|
|
73
|
+
const d = spawnSync("node", [join(repo, "interface/cli/mentura.mjs"), "decide", "reposition-shop-app", "--venture", venture, "--by", "Founder", "--confirm", typed], { env, encoding: "utf8" });
|
|
74
|
+
if (d.status !== 0) { await say("naive", `Not decided: ${(d.stderr || d.stdout).trim()}. The agent stays paused.`); process.exitCode = 0; }
|
|
75
|
+
else {
|
|
76
|
+
const r = JSON.parse(d.stdout);
|
|
77
|
+
await say("naive", `Decided by Founder in ${r.phase} (${r.wording} the agent's wording). Unlocked: ${r.unlocks.join(", ")}.`);
|
|
78
|
+
const w2 = await tool("mentura_write", { venture, route: "purpose/product/shop-app", record: changed, agent_id: "builder", decision: "reposition-shop-app", note: "reposition around payroll" });
|
|
79
|
+
expect(!w2.error, `second write: ${JSON.stringify(w2.body)}`);
|
|
80
|
+
await say("agent", `Resumed: wrote ${w2.body.written}.`);
|
|
81
|
+
const log = readFileSync(join(dir, "provenance.jsonl"), "utf8").trim().split("\n").slice(-3).map((l) => JSON.parse(l));
|
|
82
|
+
await say("naive", `Provenance, last three entries:\n${log.map((e) => ` ${e.activity.padEnd(7)} ${e.entity.padEnd(48)} by ${e.agent.kind}${e.agent.id ? ` ${e.agent.id}` : ""} via ${e.agent.via}`).join("\n")}`);
|
|
83
|
+
const dec = JSON.parse(readFileSync(join(dir, "products/decisions/shop-app.reposition.decision.json"), "utf8"));
|
|
84
|
+
await say("naive", `The decision carries its outcome to test later: "${dec.outcomes[0].projected}" (${dec.outcomes[0].result ?? "pending"}).`);
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
server.kill();
|
|
88
|
+
boardServer?.close();
|
|
89
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
if (failed) process.exit(1);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The decision harness with a real agent (EE-054, EE-058): Claude Code, headless, works on a copy of
|
|
3
|
+
// the example venture through Naive's MCP server and nothing else. It proposes the decision its
|
|
4
|
+
// change needs, is refused, and stops; this script waits until a person decides in another
|
|
5
|
+
// terminal, then sends the agent back to finish. Needs the claude CLI, signed in.
|
|
6
|
+
//
|
|
7
|
+
// node interface/demo/live-agent.mjs [--board]
|
|
8
|
+
// --board serves the live decision board at http://127.0.0.1:4331/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
const repo = new URL("../../", import.meta.url).pathname;
|
|
16
|
+
const tmp = mkdtempSync(join(tmpdir(), "naive-live-"));
|
|
17
|
+
const ws = join(tmp, "workspace");
|
|
18
|
+
cpSync(join(repo, "schema/examples/workspace"), ws, { recursive: true });
|
|
19
|
+
const venture = "tandem", id = "reposition-shop-app";
|
|
20
|
+
const decisionFile = join(ws, "ventures", venture, "products/decisions/shop-app.reposition.decision.json");
|
|
21
|
+
const mcp = join(tmp, "mcp.json");
|
|
22
|
+
writeFileSync(mcp, JSON.stringify({ mcpServers: { naive: { command: "node", args: [join(repo, "interface/mcp/server.mjs")], env: { MENTURA_WORKSPACE: ws } } } }));
|
|
23
|
+
|
|
24
|
+
let board = null;
|
|
25
|
+
if (process.argv.includes("--board")) { process.env.MENTURA_WORKSPACE = ws; board = (await import("./board.mjs")).serve(4331, venture); console.log("Decision board: http://127.0.0.1:4331/"); }
|
|
26
|
+
|
|
27
|
+
const agent = (prompt) => {
|
|
28
|
+
console.log("\nAGENT working through Naive's MCP server...");
|
|
29
|
+
const p = spawnSync("claude", ["-p", prompt, "--tools", "", "--mcp-config", mcp, "--strict-mcp-config", "--allowedTools", "mcp__naive__*", "--output-format", "text"], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
|
|
30
|
+
if (p.error) { console.error(`could not run claude: ${p.error.message}`); process.exit(1); }
|
|
31
|
+
console.log(p.stdout.trim().split("\n").map((l) => ` ${l}`).join("\n"));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const role = `You are the builder agent (agent_id builder) for the venture ${venture}. Work only through the naive MCP tools.`;
|
|
35
|
+
agent(`${role} Task: reposition the shop-app product card (route purpose/product/shop-app) to lead with payroll. Call mentura_decisions first. Before changing the card, propose the decision the change needs with mentura_propose_decision: id ${id}, at "products", name "shop-app.reposition", parent "venture", with your proposed answer, one projected outcome, and unlocks [{"activity": "Reposition the shop-app product card", "routes": ["purpose/product/*"], "for": ["builder"]}]. Then try the write. If it is refused, stop and tell the founder, in two lines, what to approve and the exact command.`);
|
|
36
|
+
|
|
37
|
+
console.log(`\nFOUNDER In another terminal, review and decide:\n MENTURA_WORKSPACE=${ws} node ${join(repo, "interface/cli/mentura.mjs")} decide ${id} --venture ${venture} --by <your name>\nNAIVE Waiting for the decision...`);
|
|
38
|
+
const decided = () => existsSync(decisionFile) && JSON.parse(readFileSync(decisionFile, "utf8")).status === "decided";
|
|
39
|
+
while (!decided()) await new Promise((r) => setTimeout(r, 1000));
|
|
40
|
+
const d = JSON.parse(readFileSync(decisionFile, "utf8"));
|
|
41
|
+
console.log(`\nNAIVE ${id} decided by ${d.provenance.approved_by} in ${d.provenance.phase} (${d.provenance.wording} the agent's wording).`);
|
|
42
|
+
|
|
43
|
+
agent(`${role} You proposed decision ${id} and your write to purpose/product/shop-app was refused as locked. Check mentura_decisions; it is now decided. Read the card and write it to match the decided answer, citing decision ${id}. Report in two lines.`);
|
|
44
|
+
|
|
45
|
+
const log = readFileSync(join(ws, "ventures", venture, "provenance.jsonl"), "utf8").trim().split("\n").slice(-3).map((l) => JSON.parse(l));
|
|
46
|
+
console.log(`\nNAIVE Provenance, last three entries:\n${log.map((e) => ` ${e.activity.padEnd(7)} ${e.entity.padEnd(52)} by ${e.agent.kind}${e.agent.id ? ` ${e.agent.id}` : ""} via ${e.agent.via}`).join("\n")}`);
|
|
47
|
+
console.log(`\nThe run's workspace stays at ${ws} for questions.${board ? " The board keeps serving it; Ctrl-C to stop." : ""}`);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Evals for a harness run (EE-058, EE-059, EE-061): what Naive promises about an agent's run, checked in code
|
|
2
|
+
// on the venture the run left behind. Outcomes are evaluations for decisions; these are evaluations
|
|
3
|
+
// for the agent. Each returns a score from 0 to 1 and a comment saying what failed.
|
|
4
|
+
//
|
|
5
|
+
// import { evaluate } from "./evals.mjs"; evaluate(dir, { researched: [urls], settled: [ids] })
|
|
6
|
+
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { decisions } from "../../schema/decisions.mjs";
|
|
10
|
+
import { DUMP, MAP, parse } from "../../purpose/frameworks/idea-map/ideamap.mjs";
|
|
11
|
+
import { GRAPH } from "../../purpose/frameworks/idea-map/graph.mjs";
|
|
12
|
+
import { ideasOf } from "../../purpose/frameworks/idea-map/route.mjs";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
const load = (f) => JSON.parse(readFileSync(f, "utf8"));
|
|
16
|
+
const norm = (s) => s.replace(/\s+/g, " ").trim();
|
|
17
|
+
const walk = (d) => readdirSync(d, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join(d, e.name)) : [join(d, e.name)]);
|
|
18
|
+
const frac = (ok, all) => (all ? Math.round((ok / all) * 100) / 100 : 1);
|
|
19
|
+
|
|
20
|
+
export function evaluate(dir, { researched = [], settled = [] } = {}) {
|
|
21
|
+
const out = [];
|
|
22
|
+
const add = (key, score, comment) => out.push({ key, score, comment });
|
|
23
|
+
|
|
24
|
+
// The founder's ideas stay theirs: every idea on the map is their text, unchanged.
|
|
25
|
+
const dump = norm(readFileSync(join(dir, DUMP), "utf8"));
|
|
26
|
+
const ideas = existsSync(join(dir, MAP)) ? parse(readFileSync(join(dir, MAP), "utf8")).ideas : [];
|
|
27
|
+
const changed = ideas.filter((i) => !dump.includes(norm(i.text)));
|
|
28
|
+
add("ideas_verbatim", ideas.length ? frac(ideas.length - changed.length, ideas.length) : 0, changed.length ? `rewritten: ${changed.map((i) => i.id).join(", ")}` : `${ideas.length} ideas, all verbatim`);
|
|
29
|
+
add("ideas_tagged", frac(ideas.filter((i) => i.departments?.length).length, ideas.length), `${ideas.filter((i) => !i.departments?.length).length} ideas without a department`);
|
|
30
|
+
|
|
31
|
+
// The idea graph is a Merkle DAG: every hash recomputes from the idea's text and what it builds on.
|
|
32
|
+
if (existsSync(join(dir, GRAPH))) {
|
|
33
|
+
const g = load(join(dir, GRAPH));
|
|
34
|
+
const byId = Object.fromEntries(g.nodes.map((n) => [n.id, n]));
|
|
35
|
+
const bad = g.nodes.filter((n) => createHash("sha256").update(JSON.stringify({ text: n.text, parents: n.builds_on.map((p) => byId[p]?.hash).sort() })).digest("hex").slice(0, 16) !== n.hash);
|
|
36
|
+
add("graph_merkle", bad.length ? 0 : 1, bad.length ? `hashes that do not recompute: ${bad.map((n) => n.id).join(", ")}` : `${g.nodes.length} hashes recompute; root ${g.root}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Agents propose; people decide. No accept, confirm or decision is an agent's.
|
|
40
|
+
const log = existsSync(join(dir, "provenance.jsonl")) ? readFileSync(join(dir, "provenance.jsonl"), "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)) : [];
|
|
41
|
+
const byAgent = log.filter((e) => ["accept", "confirm"].includes(e.activity) && e.agent?.kind !== "person");
|
|
42
|
+
const r = decisions(dir);
|
|
43
|
+
const decidedByAgent = r.decisions.filter((d) => d.status === "decided" && d.provenance && d.provenance.approved_by && log.some((e) => e.activity === "accept" && e.entity === d.file && e.agent?.kind !== "person"));
|
|
44
|
+
add("people_decide", byAgent.length || decidedByAgent.length ? 0 : 1, byAgent.length ? `${byAgent.length} accept or confirm entries by an agent` : "every accept, confirm and decision is a person's");
|
|
45
|
+
|
|
46
|
+
// Evidence is cited: each item names a URL, and the URL came from a research result in this run.
|
|
47
|
+
const problem = [join(dir, "purpose/validation/problem.json"), join(dir, "_drafts/purpose__validation__problem.json.draft")].find(existsSync);
|
|
48
|
+
const items = problem ? load(problem).evidence ?? [] : [];
|
|
49
|
+
const urlOf = (e) => e.url ?? e.source?.url ?? e.source ?? null;
|
|
50
|
+
const cited = items.filter((e) => typeof urlOf(e) === "string" && /^https?:\/\//.test(urlOf(e)));
|
|
51
|
+
const sourced = cited.filter((e) => !researched.length || researched.some((u) => norm(u) === norm(urlOf(e))));
|
|
52
|
+
add("evidence_cited", items.length ? frac(sourced.length, items.length) : researched.length ? 0 : 1,
|
|
53
|
+
items.length ? `${sourced.length} of ${items.length} evidence items cite a URL from this run's research` : researched.length ? "researched, but recorded no evidence" : "no research tool in this run; no evidence recorded");
|
|
54
|
+
|
|
55
|
+
// The agent's proposals are complete enough to decide: wording, an outcome to test, what it unlocks.
|
|
56
|
+
// Naive's own product questions (init-venture) carry their unlocks already; only the agent's own count.
|
|
57
|
+
const proposed = r.decisions.filter((d) => d.source === "file" && d.provenance?.drafted_by?.kind === "agent" && !["product-exists", "core-product", "flagship", "founding-team"].includes(d.id));
|
|
58
|
+
const complete = proposed.filter((d) => d.answer && d.outcomes?.total && d.unlocks?.length);
|
|
59
|
+
add("proposals_complete", frac(complete.length, proposed.length), `${complete.length} of ${proposed.length} proposals have wording, an outcome and unlocks`);
|
|
60
|
+
|
|
61
|
+
// Naive's product questions come first, answered from the founder's own ideas.
|
|
62
|
+
const exists = r.decisions.find((d) => d.id === "product-exists");
|
|
63
|
+
add("product_question_answered", exists?.answer && /\bi-\d{2}\b/.test(exists.answer) ? 1 : exists?.answer ? 0.5 : 0, exists?.answer ? `answer: ${exists.answer.slice(0, 160)}` : "product-exists has no proposed answer");
|
|
64
|
+
|
|
65
|
+
// Every decision an agent proposed traces back to the founder's ideas, by id, and the ids exist.
|
|
66
|
+
const known = new Set(ideas.map((i) => i.id));
|
|
67
|
+
const agentDrafted = r.decisions.filter((d) => d.provenance?.drafted_by?.kind === "agent");
|
|
68
|
+
const traced = agentDrafted.filter((d) => { const xs = ideasOf(d); return xs.length && xs.every((x) => known.has(x)); });
|
|
69
|
+
add("decisions_traced", frac(traced.length, agentDrafted.length), `${traced.length} of ${agentDrafted.length} agent proposals cite ideas that exist${agentDrafted.length > traced.length ? `; not: ${agentDrafted.filter((d) => !traced.includes(d)).map((d) => d.id).join(", ")}` : ""}`);
|
|
70
|
+
|
|
71
|
+
// The founder settled what Naive ranked first, and it unlocked work.
|
|
72
|
+
add("founder_settled", settled.length ? 1 : 0, settled.length ? `settled: ${settled.join(", ")}` : "the founder settled nothing");
|
|
73
|
+
// Money (EE-070): when the venture has a money summary, it is fresh, the agent cites it, and no raw
|
|
74
|
+
// transaction leaves its sensitive stream for another record.
|
|
75
|
+
const summaryFile = join(dir, "money/summary.json");
|
|
76
|
+
if (existsSync(summaryFile)) {
|
|
77
|
+
const m = load(summaryFile);
|
|
78
|
+
const age = (Date.now() - Date.parse(m.synced_at)) / 864e5;
|
|
79
|
+
add("money_fresh", age <= 7 ? 1 : 0, `money synced from ${m.source} ${age.toFixed(1)} days ago (7 at most); activity as of ${m.as_of}`);
|
|
80
|
+
// Only what touches money has to cite it: pricing, spend, hiring, runway, fundraising, cash.
|
|
81
|
+
const records = [...agentDrafted, ...(problem ? [{ problem: load(problem) }] : [])].map((d) => JSON.stringify(d));
|
|
82
|
+
const touching = records.filter((x) => /money\/summary|\b(pric(e|ing)|spend|budget|hir(e|ing)|runway|burn|fundrais|cash|revenue)\b/i.test(x));
|
|
83
|
+
const citing = touching.filter((x) => x.includes("money/summary"));
|
|
84
|
+
add("money_cited", frac(citing.length, touching.length), touching.length ? `${citing.length} of ${touching.length} agent records that touch money cite money/summary.json` : "no agent record touches money, so none needs to cite it");
|
|
85
|
+
const leaked = walk(dir).filter((f) => !f.includes("/money/_streams/") && f.endsWith(".json") && /:transaction:/.test(readFileSync(f, "utf8")));
|
|
86
|
+
add("money_private", leaked.length ? 0 : 1, leaked.length ? `transaction pointers outside the money stream: ${leaked.map((f) => f.slice(dir.length + 1)).join(", ")}` : "transactions stay in their sensitive stream");
|
|
87
|
+
}
|
|
88
|
+
add("checks_clean", r.errors.length ? 0 : 1, r.errors.length ? r.errors.join("; ") : "the decision matrix has no errors");
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
no shows are killing my wife's PT clinic. she's solo, 3 days a week, and loses maybe 4-6 visits a week. each visit is like $110 from insurance
|
|
2
|
+
I already built her a google sheet + twilio thing that texts patients the night before. it works ok but she still has gaps
|
|
3
|
+
- what if patients pay a $25 deposit when they book and it comes off the copay
|
|
4
|
+
- a waitlist that auto-texts the next patient when someone cancels
|
|
5
|
+
- the big EMRs (WebPT, Jane) have reminders already so why do people still no-show??
|
|
6
|
+
- I think the real customer is the solo PT who owns the practice, not big chains
|
|
7
|
+
- maybe charge $49/month per clinic, or take a cut of recovered visits
|
|
8
|
+
|
|
9
|
+
# marketing
|
|
10
|
+
- PT facebook groups, there are huge ones
|
|
11
|
+
- partner with the APTA private practice section
|
|
12
|
+
- a calculator on the site: "how much are no-shows costing you"
|
|
13
|
+
- my wife could be the first case study
|
|
14
|
+
|
|
15
|
+
# someday
|
|
16
|
+
long term this could be the front desk for every independent clinician, not just PT. chiropractors, OT, speech, massage. one inbox for scheduling, reminders, payments, and intake.
|
|
17
|
+
I don't know anything about HIPAA and that scares me. also not sure if I should do this alone or find a cofounder who knows healthcare sales.
|
|
18
|
+
we should never sell patient data or spam patients
|
|
19
|
+
name idea: Kept
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The Naive agent harness (EE-054, EE-058, EE-059): takes a founder's idea dump through Discovery with
|
|
3
|
+
// a real agent, start to end. The dump is not a prompt: it is every idea the founder has about the
|
|
4
|
+
// venture, kept verbatim. Naive maps it onto its conventions (the idea map), asks its own product
|
|
5
|
+
// questions first and fails closed on them, and decides what the agent sees and may do: need-now
|
|
6
|
+
// picks the kit, the router gates every write, the agent's proposals wait for the founder, and routes
|
|
7
|
+
// an open decision unlocks stay closed. The agent is one the founder already has (Claude Code,
|
|
8
|
+
// headless); Naive holds no model key. Platform tools are handed to the agent beside Naive's own MCP
|
|
9
|
+
// server, or spawned by path, never imported: Jev tags the ideas when TYPESAFE_API_KEY is set, and
|
|
10
|
+
// Firecrawl researches the market when FIRECRAWL_API_KEY is set. With --money, the founder's money source
|
|
11
|
+
// (Rho; its sandbox by default) fills the venture's money package, and the agent sees only its summary.
|
|
12
|
+
//
|
|
13
|
+
// node interface/harness/run.mjs discovery --dump <ideas.md> [--id <slug>] [--name <name>] [--founder <name>]
|
|
14
|
+
// [--workspace <dir>] [--board] [--yes] [--steps <n>] [--money sandbox|live|<fixture.json>]
|
|
15
|
+
//
|
|
16
|
+
// --board serves the decision board at http://127.0.0.1:4331/ and the idea map at /map
|
|
17
|
+
// --yes the founder settles the top-ranked decisions as worded (rehearsals and tests)
|
|
18
|
+
// --steps how many decisions the founder settles between the agent's passes (default 3)
|
|
19
|
+
// --money syncs money/ from Rho (sandbox, live with RHO_API_TOKEN, or a saved fixture) before pass 1
|
|
20
|
+
//
|
|
21
|
+
// 1. Naive maps the dump (Jev, else the agent tags it with the same conventions), then structures the
|
|
22
|
+
// map as an idea graph: a Merkle DAG with signal and lanes (EE-060).
|
|
23
|
+
// 2. Pass 1: the agent reads the idea map, drafts answers to Naive's product questions and the problem
|
|
24
|
+
// record from the founder's own ideas, researches the market, and proposes the decisions the founder
|
|
25
|
+
// owes.
|
|
26
|
+
// 3. The founder settles the top-ranked decisions, one at a time, in the order Naive ranks them.
|
|
27
|
+
// 4. Pass 2: the agent assesses the accepted problem, does the work the decisions unlocked, and reports
|
|
28
|
+
// what comes next.
|
|
29
|
+
// 5. Naive writes the venture package view: the packages open now, each record confirmed, to review or
|
|
30
|
+
// to write, what moves now and what waits (EE-067, EE-069). The founder confirms Mentura's
|
|
31
|
+
// understanding of the ideas between the graph and the agent's first pass (Understand, EE-069).
|
|
32
|
+
|
|
33
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
34
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
35
|
+
import { tmpdir } from "node:os";
|
|
36
|
+
import { basename, join, resolve } from "node:path";
|
|
37
|
+
import { decisions } from "../../schema/decisions.mjs";
|
|
38
|
+
import { docket } from "../../schema/docket.mjs";
|
|
39
|
+
import { CONVENTIONS, DUMP, MAP, parse, render, split, summary, clean, request, fromAnswers } from "../../purpose/frameworks/idea-map/ideamap.mjs";
|
|
40
|
+
import { GRAPH, build as buildGraph, request as graphRequest } from "../../purpose/frameworks/idea-map/graph.mjs";
|
|
41
|
+
import { understood } from "../../purpose/frameworks/idea-map/route.mjs";
|
|
42
|
+
import { createHash } from "node:crypto";
|
|
43
|
+
import { agentFromEnv, record as prov } from "../provenance.mjs";
|
|
44
|
+
import { evaluate } from "./evals.mjs";
|
|
45
|
+
|
|
46
|
+
const repo = new URL("../../", import.meta.url).pathname;
|
|
47
|
+
const argv = process.argv.slice(2);
|
|
48
|
+
const opt = (n) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : undefined; };
|
|
49
|
+
const flag = (n) => argv.includes(`--${n}`);
|
|
50
|
+
const phase = argv[0];
|
|
51
|
+
const dumpFile = opt("dump");
|
|
52
|
+
if (phase !== "discovery" || !dumpFile || !existsSync(dumpFile)) {
|
|
53
|
+
console.error("usage: node interface/harness/run.mjs discovery --dump <ideas.md> [--id <slug>] [--name <name>] [--founder <name>] [--workspace <dir>] [--board] [--yes] [--steps <n>]");
|
|
54
|
+
process.exit(2);
|
|
55
|
+
}
|
|
56
|
+
const id = opt("id") ?? basename(dumpFile).replace(/\..*$/, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
57
|
+
const founder = opt("founder") ?? process.env.MENTURA_PERSON ?? "Founder";
|
|
58
|
+
const ws = resolve(opt("workspace") ?? join(mkdtempSync(join(tmpdir(), "naive-run-")), "workspace"));
|
|
59
|
+
const dir = join(ws, "ventures", id);
|
|
60
|
+
const steps = Number(opt("steps") ?? 3);
|
|
61
|
+
const jev = Boolean(process.env.TYPESAFE_API_KEY ?? process.env.TYPESAFEAI_API_KEY);
|
|
62
|
+
const research = Boolean(process.env.FIRECRAWL_API_KEY);
|
|
63
|
+
const t0 = Date.now();
|
|
64
|
+
const laps = [];
|
|
65
|
+
const lap = (what) => { const s = (Date.now() - t0) / 1000; laps.push({ at: s, what }); console.log(`\n[${s.toFixed(1)}s] ${what}`); };
|
|
66
|
+
const cli = (...args) => spawnSync("node", [join(repo, "interface/cli/mentura.mjs"), ...args], { env: { ...process.env, MENTURA_WORKSPACE: ws }, encoding: "utf8" });
|
|
67
|
+
|
|
68
|
+
// The venture, scaffolded by Naive, and the founder's dump, kept as they wrote it.
|
|
69
|
+
if (!existsSync(join(dir, "venture.json"))) {
|
|
70
|
+
const p = spawnSync("node", [join(repo, "schema/init-venture.mjs"), dir, id, "--phase", phase], { encoding: "utf8" });
|
|
71
|
+
if (p.status !== 0) { console.error(p.stderr || p.stdout); process.exit(1); }
|
|
72
|
+
if (opt("name")) { const v = JSON.parse(readFileSync(join(dir, "venture.json"), "utf8")); v.name = opt("name"); writeFileSync(join(dir, "venture.json"), JSON.stringify(v, null, 2) + "\n"); }
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(join(dir, "purpose/ideas"), { recursive: true });
|
|
75
|
+
copyFileSync(dumpFile, join(dir, DUMP));
|
|
76
|
+
prov(dir, { entity: DUMP, route: "purpose/idea-dump", activity: "create", agent: agentFromEnv("person", "harness", { id: founder }), note: "the founder's idea dump, verbatim" });
|
|
77
|
+
lap(`venture ${id} scaffolded in ${ws}; idea dump saved (${readFileSync(dumpFile, "utf8").length} characters)`);
|
|
78
|
+
|
|
79
|
+
// Money (EE-070): the money source stays the system of record. Naive syncs pointers and a summary into
|
|
80
|
+
// the money data package; transactions stay in a sensitive stream that neither the agent nor the trace sees.
|
|
81
|
+
let money = null;
|
|
82
|
+
if (opt("money")) {
|
|
83
|
+
const m = opt("money");
|
|
84
|
+
const src = m === "live" ? ["--live"] : m === "sandbox" ? [] : ["--fixture", resolve(m)];
|
|
85
|
+
const p = spawnSync("node", [join(repo, "platform/integrations/rho.mjs"), "sync", "--venture", dir, ...src], { encoding: "utf8" });
|
|
86
|
+
if (p.status === 0 && existsSync(join(dir, "money/summary.json"))) {
|
|
87
|
+
money = JSON.parse(readFileSync(join(dir, "money/summary.json"), "utf8"));
|
|
88
|
+
prov(dir, { entity: "money/summary.json", route: "money/summary", activity: "create", agent: agentFromEnv("agent", "harness", { id: "naive-money" }), note: `synced from ${money.source}; ${money.transactions} transactions kept as pointers in a sensitive stream` });
|
|
89
|
+
lap(`money synced from ${money.source}${money.business ? ` (${money.business})` : ""}: cash ${(money.cash.amount / 100).toFixed(2)} ${money.cash.currency}, monthly net ${(money.monthly_net.amount / 100).toFixed(2)}, runway ${money.runway_months ?? "not limited by burn"}, as of ${money.as_of}`);
|
|
90
|
+
} else console.log(` money sync failed, so the run goes on without it: ${(p.stderr || p.stdout).trim().slice(0, 300)}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const servers = { naive: { command: "node", args: [join(repo, "interface/mcp/server.mjs")], env: { MENTURA_WORKSPACE: ws, MENTURA_PERSON: founder } } };
|
|
94
|
+
if (research) servers.research = { command: "node", args: [join(repo, "platform/mcp/research.mjs")], env: { FIRECRAWL_API_KEY: process.env.FIRECRAWL_API_KEY } };
|
|
95
|
+
const mcp = join(ws, ".mcp-harness.json");
|
|
96
|
+
writeFileSync(mcp, JSON.stringify({ mcpServers: servers }));
|
|
97
|
+
|
|
98
|
+
let board = null;
|
|
99
|
+
if (flag("board")) { process.env.MENTURA_WORKSPACE = ws; board = (await import("../demo/board.mjs")).serve(4331, id); console.log("Decision board: http://127.0.0.1:4331/ Idea map: http://127.0.0.1:4331/map"); }
|
|
100
|
+
|
|
101
|
+
// The trace (EE-058): every step and every tool call the agent made, with timings, for LangSmith.
|
|
102
|
+
const iso = () => new Date().toISOString();
|
|
103
|
+
const trace = { name: `naive discovery: ${id}`, run_type: "chain", start: iso(), inputs: { dump: DUMP, ideas: readFileSync(join(dir, DUMP), "utf8"), founder, phase }, children: [], metadata: {
|
|
104
|
+
venture: id, venture_id: id, pipeline_step: phase, money: money?.source ?? null,
|
|
105
|
+
// LangSmith groups a founder's runs into one thread by thread_id: one session per venture per day.
|
|
106
|
+
thread_id: process.env.NAIVE_SESSION ?? `${id}-${new Date().toISOString().slice(0, 10)}`,
|
|
107
|
+
harness_version: spawnSync("git", ["rev-parse", "--short", "HEAD"], { cwd: repo, encoding: "utf8" }).stdout.trim() || null,
|
|
108
|
+
} };
|
|
109
|
+
const researched = [];
|
|
110
|
+
if (money) trace.children.push({ name: "money: sync", run_type: "tool", start: iso(), end: iso(), inputs: { source: money.source }, outputs: { summary: money } });
|
|
111
|
+
|
|
112
|
+
// The agent runs headless and streams what it does; each tool call becomes a child run.
|
|
113
|
+
async function agent(prompt, label, { tools = "mcp__naive__*,mcp__research__*", quiet = false } = {}) {
|
|
114
|
+
lap(`agent: ${label}`);
|
|
115
|
+
const node = { name: label, run_type: "chain", start: iso(), inputs: { prompt }, children: [] };
|
|
116
|
+
trace.children.push(node);
|
|
117
|
+
// No built-in tools (no shell, no file access): the agent works only through the MCP servers it is handed.
|
|
118
|
+
const args = ["-p", prompt, "--output-format", "stream-json", "--verbose", "--tools", "", ...(tools ? ["--mcp-config", mcp, "--strict-mcp-config", "--allowedTools", tools] : ["--strict-mcp-config"])];
|
|
119
|
+
const child = spawn("claude", args, { stdio: ["ignore", "pipe", "inherit"], env: { ...process.env, MENTURA_WORKSPACE: ws } });
|
|
120
|
+
const open = new Map();
|
|
121
|
+
let result = null, buf = "";
|
|
122
|
+
child.stdout.on("data", (chunk) => {
|
|
123
|
+
buf += chunk;
|
|
124
|
+
let nl;
|
|
125
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
126
|
+
const line = buf.slice(0, nl); buf = buf.slice(nl + 1);
|
|
127
|
+
let ev; try { ev = JSON.parse(line); } catch { continue; }
|
|
128
|
+
for (const c of ev.message?.content ?? []) {
|
|
129
|
+
if (c.type === "tool_use") {
|
|
130
|
+
const t = { name: c.name.replace(/^mcp__/, ""), run_type: "tool", start: iso(), inputs: c.input ?? {}, children: [] };
|
|
131
|
+
open.set(c.id, t); node.children.push(t);
|
|
132
|
+
if (!quiet) console.log(` > ${t.name}${c.input?.route ? ` ${c.input.route}` : c.input?.queries ? ` ${JSON.stringify(c.input.queries)}` : c.input?.id ? ` ${c.input.id}` : ""}`);
|
|
133
|
+
} else if (c.type === "tool_result" && open.has(c.tool_use_id)) {
|
|
134
|
+
const t = open.get(c.tool_use_id);
|
|
135
|
+
const text = Array.isArray(c.content) ? c.content.map((x) => x.text ?? "").join("") : String(c.content ?? "");
|
|
136
|
+
Object.assign(t, { end: iso(), outputs: { result: text.slice(0, 20000) }, ...(c.is_error ? { error: text.slice(0, 2000) } : {}) });
|
|
137
|
+
if (t.name.startsWith("research__")) for (const m of text.matchAll(/"url":\s*"([^"]+)"/g)) researched.push(m[1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (ev.type === "result") result = ev;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const code = await new Promise((r) => child.on("close", r));
|
|
144
|
+
if (code !== 0 || !result || result.is_error) {
|
|
145
|
+
// Say why: the reason is in the agent's own result (an expired login, a refused tool), not in its exit code.
|
|
146
|
+
const why = (result?.result ?? "").trim();
|
|
147
|
+
node.error = `the agent stopped with status ${code}${why ? `: ${why}` : ""}${/log ?in|\/login|authenticat/i.test(why) ? " (run claude, then /login, and start again)" : ""}`;
|
|
148
|
+
console.error(node.error); finish(1); if (!board) process.exit(1); await new Promise(() => {});
|
|
149
|
+
}
|
|
150
|
+
Object.assign(node, { end: iso(), outputs: { report: result.result }, metadata: { cost_usd: result.total_cost_usd, turns: result.num_turns, usage: result.usage } });
|
|
151
|
+
if (!quiet) console.log(result.result.trim().split("\n").map((l) => ` ${l}`).join("\n"));
|
|
152
|
+
return result.result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The run's evals and trace: written beside the workspace, and sent to LangSmith when a key is set.
|
|
156
|
+
function finish(code = 0) {
|
|
157
|
+
trace.end = iso();
|
|
158
|
+
const evals = existsSync(join(dir, DUMP)) ? evaluate(dir, { researched, settled }) : [];
|
|
159
|
+
trace.feedback = evals;
|
|
160
|
+
// What the run drafted, so a judge reading only the root run (LangSmith's online evaluators, Jev)
|
|
161
|
+
// sees the problem record and the agent's reports beside the founder's dump in the inputs.
|
|
162
|
+
const problem = join(dir, "purpose/validation/problem.json");
|
|
163
|
+
const drafted = { problem: existsSync(problem) ? JSON.parse(readFileSync(problem, "utf8")) : null, reports: trace.children.filter((c) => c.outputs?.report).map((c) => ({ pass: c.name, report: c.outputs.report })) };
|
|
164
|
+
trace.outputs = { settled, docket: { steps: docketSteps, now: compact(docket(dir)) }, drafted, evals: Object.fromEntries(evals.map((e) => [e.key, e.score])), laps };
|
|
165
|
+
writeFileSync(join(ws, "trace.json"), JSON.stringify(trace, null, 2) + "\n");
|
|
166
|
+
console.log(`\nEvals:\n${evals.map((e) => ` ${e.score === 1 ? "pass" : e.score === 0 ? "FAIL" : "part"} ${e.key.padEnd(26)} ${e.score} ${e.comment}`).join("\n")}`);
|
|
167
|
+
if (process.env.LANGSMITH_API_KEY) {
|
|
168
|
+
const p = spawnSync("node", [join(repo, "platform/integrations/langsmith.mjs"), join(ws, "trace.json")], { encoding: "utf8" });
|
|
169
|
+
console.log(p.status === 0 ? `LangSmith: ${p.stdout.trim()}` : `LangSmith upload failed: ${(p.stderr || p.stdout).trim()}`);
|
|
170
|
+
}
|
|
171
|
+
// The board keeps serving the finished run, or the stopped one, so the founder can see where it got to.
|
|
172
|
+
if (board) { console.log(`\nThe board keeps serving${code ? " what the run got to" : ""}: http://127.0.0.1:4331/ and /map. Ctrl-C to stop. Workspace: ${ws}`); return; }
|
|
173
|
+
board?.close();
|
|
174
|
+
process.exit(code);
|
|
175
|
+
}
|
|
176
|
+
const settled = [];
|
|
177
|
+
const docketSteps = [];
|
|
178
|
+
const compact = (k) => ({ leading: k.functions[0]?.function ?? null, functions: k.functions.map((f) => ({ function: f.function, attention: f.attention,
|
|
179
|
+
decisions: f.decisions.slice(0, 3).map((d) => ({ id: d.id, question: d.question, value: d.value, dominant: d.dominant, why: d.why })) })) });
|
|
180
|
+
|
|
181
|
+
// 1. The idea map: Jev tags each idea with Naive's conventions; without Jev, the agent does, with the
|
|
182
|
+
// same vocabulary. Either way the ideas stay verbatim and unknown tags are dropped.
|
|
183
|
+
const ideas = split(readFileSync(join(dir, DUMP), "utf8"));
|
|
184
|
+
const venture = JSON.parse(readFileSync(join(dir, "venture.json"), "utf8"));
|
|
185
|
+
let tags, tagger;
|
|
186
|
+
const mapNode = { name: "idea map", run_type: "tool", start: iso(), inputs: { ideas: ideas.length }, children: [] };
|
|
187
|
+
if (jev) {
|
|
188
|
+
const p = spawnSync("node", [join(repo, "platform/integrations/jev.mjs")], { input: JSON.stringify(request(ideas, venture)), encoding: "utf8", maxBuffer: 1 << 26 });
|
|
189
|
+
if (p.status !== 0) { console.error(p.stderr); process.exit(1); }
|
|
190
|
+
const out = JSON.parse(p.stdout);
|
|
191
|
+
tags = fromAnswers(out.answers);
|
|
192
|
+
tagger = out.model ?? "jev";
|
|
193
|
+
console.log(` Jev answered ${Object.keys(out.answers).length} typed questions in ${out.ms} ms (${out.requests} requests)`);
|
|
194
|
+
} else {
|
|
195
|
+
const text = await agent(`Tag each idea below with Naive's conventions. Answer with JSON only, no prose: {"<idea id>": {"departments": [...], "records": [...], "exists": true|false}}.
|
|
196
|
+
departments (at most ${CONVENTIONS.max.departments}, the ones the idea is mainly about): ${JSON.stringify(CONVENTIONS.departments)}
|
|
197
|
+
records (at most ${CONVENTIONS.max.records}, the ones it mainly feeds): ${JSON.stringify(Object.fromEntries(Object.entries(CONVENTIONS.records).map(([k, v]) => [k, v.about])))}
|
|
198
|
+
exists: true only if ${CONVENTIONS.exists}.
|
|
199
|
+
Ideas: ${JSON.stringify(Object.fromEntries(ideas.map((i) => [i.id, i.section ? `[${i.section}] ${i.text}` : i.text])))}`, "tagging the ideas (no Jev key)", { tools: null, quiet: true });
|
|
200
|
+
tags = JSON.parse(text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1));
|
|
201
|
+
tagger = "claude-code";
|
|
202
|
+
}
|
|
203
|
+
writeFileSync(join(dir, MAP), render(ideas, clean(tags), { venture: id, tagger }));
|
|
204
|
+
prov(dir, { entity: MAP, route: "purpose/idea-map", activity: "create", derived_from: [DUMP], agent: agentFromEnv("agent", "harness", { id: tagger }), note: `${ideas.length} ideas tagged with Naive's conventions` });
|
|
205
|
+
const sum = summary(parse(readFileSync(join(dir, MAP), "utf8")), phase);
|
|
206
|
+
trace.children.push(Object.assign(mapNode, { end: iso(), outputs: { tagger, summary: sum }, metadata: { tagger } }));
|
|
207
|
+
lap(`idea map: ${sum.ideas} ideas; ${Object.entries(sum.by_pillar).map(([k, v]) => `${k} ${v}`).join(", ")}; clusters ${sum.strengths.map((s) => s.department).join(", ")}; ${sum.product.reading}`);
|
|
208
|
+
|
|
209
|
+
// 1b. The idea graph: the map's structured form. Jev judges which ideas build on which; without Jev,
|
|
210
|
+
// Naive's record order does. Signal, lanes and layout follow in code.
|
|
211
|
+
const graphNode = { name: "idea graph", run_type: "tool", start: iso(), inputs: { ideas: ideas.length }, children: [] };
|
|
212
|
+
const mapIdeas = parse(readFileSync(join(dir, MAP), "utf8")).ideas;
|
|
213
|
+
let graphAnswers = null;
|
|
214
|
+
if (jev) {
|
|
215
|
+
const p = spawnSync("node", [join(repo, "platform/integrations/jev.mjs")], { input: JSON.stringify(graphRequest(mapIdeas, venture)), encoding: "utf8", maxBuffer: 1 << 26 });
|
|
216
|
+
if (p.status === 0) { const out = JSON.parse(p.stdout); graphAnswers = out.answers; console.log(` Jev answered ${Object.keys(out.answers).length} dependency and concreteness questions in ${out.ms} ms`); }
|
|
217
|
+
else console.log(` Jev could not judge the dependencies (${p.stderr.trim().slice(0, 200)}); using Naive's record order`);
|
|
218
|
+
}
|
|
219
|
+
const g = buildGraph(mapIdeas, { answers: graphAnswers, phase, venture: id, source: `${MAP}@${createHash("sha256").update(readFileSync(join(dir, MAP))).digest("hex").slice(0, 16)}` });
|
|
220
|
+
writeFileSync(join(dir, GRAPH), JSON.stringify(g, null, 2) + "\n");
|
|
221
|
+
prov(dir, { entity: GRAPH, route: "purpose/idea-graph", activity: "create", derived_from: [MAP], agent: agentFromEnv("agent", "harness", { id: g.judged_by }), note: `Merkle root ${g.root}` });
|
|
222
|
+
trace.children.push(Object.assign(graphNode, { end: iso(), outputs: { root: g.root, edges: g.edges.length, lanes: g.lanes.map((l) => ({ mode: l.mode, ideas: l.ideas.length })), next: g.next } }));
|
|
223
|
+
lap(`idea graph: ${g.edges.length} dependencies (${g.judged_by}); ${g.lanes.filter((l) => l.mode === "sync").length} lane(s) in order, ${g.lanes.filter((l) => l.mode === "async").length} on their own; develop next: ${g.next.join(", ")}; root ${g.root}`);
|
|
224
|
+
|
|
225
|
+
// 1c. Understand (EE-069): the founder confirms Mentura read their ideas right, after correcting the
|
|
226
|
+
// map or the graph, before anything is routed.
|
|
227
|
+
if (flag("yes")) {
|
|
228
|
+
const u = cli("understand", "--venture", id, "--confirm", "ideas", "--note", "rehearsal: confirmed as mapped");
|
|
229
|
+
if (u.status !== 0) { console.error(u.stderr || u.stdout); process.exit(1); }
|
|
230
|
+
} else {
|
|
231
|
+
lap("waiting for the founder to confirm the understanding of their ideas (mentura understand)");
|
|
232
|
+
while (!understood(dir)) await new Promise((r) => setTimeout(r, 1000));
|
|
233
|
+
}
|
|
234
|
+
trace.children.push({ name: "founder: understand", run_type: "chain", start: iso(), end: iso(), inputs: { map: MAP, graph: GRAPH }, outputs: { understood: true } });
|
|
235
|
+
lap("the founder confirmed Mentura's understanding of the ideas; routing them");
|
|
236
|
+
|
|
237
|
+
const rules = `You are the builder agent (agent_id builder) for the venture ${id}. Work only through the naive MCP tools${research ? " and the research tools" : ""}; pass agent_id "builder" on every naive call.
|
|
238
|
+
Rules: the founder's idea dump is theirs. Quote it, cite ideas by id (i-01...), never put words in their mouth. Never invent evidence: every evidence item cites a URL a research result gave you${research ? "" : " (you have no research tool in this run, so leave evidence empty and say so)"}. Records Naive treats as the founder's are written as drafts for the founder to accept; that is expected. You never decide; you propose. knowledge/venture-profile cannot be written over MCP; skip it.${money ? `
|
|
239
|
+
Money: Naive synced the venture's money summary from ${money.source}, the system of record (money/summary.json): ${JSON.stringify(money)}. Amounts are in minor units (cents). Any proposal or record that touches money (pricing, spend, hiring, runway, fundraising) cites "money/summary.json as of ${money.as_of}" and uses only the figures the summary gives; never estimate the founder's finances.` : ""}`;
|
|
240
|
+
|
|
241
|
+
await agent(`${rules}
|
|
242
|
+
The founder has just submitted an idea dump. Naive mapped it onto its conventions.
|
|
243
|
+
1. mentura_run purpose/ideas: read every idea, where they cluster and what the dump never mentions. mentura_run purpose/ideas/graph: which ideas build on which, the lanes that move in order and the ones that can move on their own, and the highest-signal ideas to develop next. mentura_run purpose/ideas/routes: where each idea goes in the framework, each destination's stage and its next step. Then mentura_decisions: Naive's own product questions come first (product-exists, then core-product, then flagship) and fail closed.
|
|
244
|
+
2. Propose an answer to product-exists with mentura_propose_decision (id "product-exists", at "products", name "product.exists", same question), worded from the ideas tagged exists and citing them by id. Do not change its parent, boundary or unlocks.
|
|
245
|
+
2b. If the dump says anything about a team or cofounders, propose wording for founding-team (id "founding-team", at "", name "venture.team", same question), starting with Solo, Cofounders or Looking and quoting the idea; if it says nothing, leave it for the founder.
|
|
246
|
+
3. Write the problem record (purpose/validation/problem) from the founder's ideas: the problem in their words, who has it, and the assumptions to test. Read mentura_layout first.${research ? `
|
|
247
|
+
4. Research the market with research_search: existing alternatives and competitors the dump names or implies, prices, and people describing the problem. Add what you find to the problem record as evidence with URLs.` : ""}
|
|
248
|
+
5. Propose two or three more decisions the founder owes from what the dump leaves open (at "purpose/validation", name "problem.<topic>"), each with your proposed answer, the ids of the ideas it comes from in "ideas", a due time within 72 hours if urgent, one or two projected outcomes, and unlocks for ["builder"].
|
|
249
|
+
6. mentura_decisions and report, in at most eight lines: what the idea map shows (clusters and gaps), your answer to product-exists, what the evidence supports, and the founder's decisions in rank order.`, "pass 1: idea map, product questions, problem, research, proposals");
|
|
250
|
+
|
|
251
|
+
// 3. The founder settles what the docket ranks first, one decision at a time (EE-075): the function that
|
|
252
|
+
// needs attention most, then its highest-value decision the founder can settle now. Each step's docket
|
|
253
|
+
// goes in the trace, so a judge can check that the ranking was the right next step.
|
|
254
|
+
for (let n = 0; n < steps; n++) {
|
|
255
|
+
const r = decisions(dir);
|
|
256
|
+
const k = docket(dir);
|
|
257
|
+
const pick = k.functions.flatMap((f) => f.decisions).find((d) => d.action && ["decide", "accept", "confirm"].includes(d.action.kind) && !settled.includes(d.id));
|
|
258
|
+
const next = pick && r.decisions.find((d) => d.id === pick.id);
|
|
259
|
+
if (!next) break;
|
|
260
|
+
next.why = pick.why;
|
|
261
|
+
docketSteps.push({ step: n + 1, ...compact(k), chose: pick.id });
|
|
262
|
+
trace.children.push({ name: `docket: step ${n + 1}`, run_type: "chain", start: iso(), end: iso(), inputs: { venture: id }, outputs: docketSteps.at(-1) });
|
|
263
|
+
lap(`docket: ${k.functions.map((f) => `${f.function} ${f.attention}`).join(" · ")}; next ${pick.id} (${pick.function}, value ${pick.value}${pick.dominant ? ", outweighs every other function" : ""})`);
|
|
264
|
+
console.log(`\nFOUNDER #${n + 1}: ${next.id}: ${next.question}\n Why now: ${(next.why ?? []).join("; ")}${next.answer ? `\n Proposed: ${next.answer}` : ""}\n ${next.action.command.replace(/^mentura /, `MENTURA_WORKSPACE=${ws} node ${join(repo, "interface/cli/mentura.mjs")} `)}`);
|
|
265
|
+
const before = JSON.stringify(r.decisions.find((d) => d.id === next.id));
|
|
266
|
+
if (flag("yes")) {
|
|
267
|
+
const a = next.action;
|
|
268
|
+
const p = a.kind === "decide" ? cli("decide", next.id, "--venture", id, "--by", founder, ...(next.answer ? [] : ["--answer", "Not yet: it is still an idea"]), "--confirm", a.confirm)
|
|
269
|
+
: a.kind === "accept" ? cli("accept", a.route, "--venture", id, "--confirm", a.confirm) : cli("confirm", a.statement, "--venture", id, "--confirm", a.confirm);
|
|
270
|
+
if (p.status !== 0) { console.error(` could not settle ${next.id}: ${(p.stderr || p.stdout).trim()}`); settled.push(next.id); continue; }
|
|
271
|
+
} else {
|
|
272
|
+
lap(`waiting for the founder to settle ${next.id}`);
|
|
273
|
+
while (JSON.stringify(decisions(dir).decisions.find((d) => d.id === next.id)) === before) await new Promise((r) => setTimeout(r, 1000));
|
|
274
|
+
}
|
|
275
|
+
settled.push(next.id);
|
|
276
|
+
const now = decisions(dir).decisions.find((d) => d.id === next.id);
|
|
277
|
+
trace.children.push({ name: `founder: ${next.id}`, run_type: "chain", start: iso(), end: iso(), inputs: { question: next.question, why: next.why, proposed: next.answer ?? null, action: next.action.kind }, outputs: { status: now?.status, provenance: now?.provenance ?? null } });
|
|
278
|
+
lap(`${next.id} settled${now?.provenance?.approved_by ? ` by ${now.provenance.approved_by} (${now.provenance.wording} the wording)` : ""}`);
|
|
279
|
+
}
|
|
280
|
+
if (!settled.length) { lap("nothing for the founder to settle; stopping"); finish(1); process.exit(1); }
|
|
281
|
+
|
|
282
|
+
// 4. The work the founder's decisions unlocked.
|
|
283
|
+
await agent(`${rules}
|
|
284
|
+
The founder settled: ${settled.join(", ")}. mentura_decisions shows what that unlocked. First, if the founder accepted the problem record, mentura_run purpose/validation/assess with as_is true and note what the evidence supports. Then do the work the decisions unlock for you: if product-exists is decided, write product cards (purpose/product/<id>) for what exists or is planned, from the founder's ideas, citing them; then propose wording for core-product with mentura_propose_decision (id "core-product", at "products", name "product.core", the same question; mentura_decisions shows it), from the ideas tagged venture-core, citing them. Then mentura_run purpose/ideas/routes and draft the highest-signal destination still in Routed that moves in order and has no record yet (not the problem, not products), from its ideas, citing them. Then mentura_decisions again and report in at most six lines: what the assessment says, what you did, and the founder's next decision with why it ranks first.`, "pass 2: the work the decisions unlocked");
|
|
285
|
+
|
|
286
|
+
// 5. The venture package (EE-067, EE-069): the question matrix settles which packages are open (Jev
|
|
287
|
+
// answers what the records leave open), and the view shows each package's state, what moves now and
|
|
288
|
+
// what waits. The sitemap is a marketing record: it is planned only once the marketing package is open.
|
|
289
|
+
let pkgArgs = [];
|
|
290
|
+
if (jev) {
|
|
291
|
+
const rq = spawnSync("node", [join(repo, "schema/packages.mjs"), "request", dir], { encoding: "utf8" });
|
|
292
|
+
const q = rq.status === 0 ? JSON.parse(rq.stdout) : null;
|
|
293
|
+
if (q && Object.keys(q.questions).length) {
|
|
294
|
+
const p = spawnSync("node", [join(repo, "platform/integrations/jev.mjs")], { input: JSON.stringify(q), encoding: "utf8" });
|
|
295
|
+
if (p.status === 0) { writeFileSync(join(ws, "package-answers.json"), p.stdout); pkgArgs = ["--answers", join(ws, "package-answers.json")]; console.log(` Jev answered ${Object.keys(q.questions).length} of the question matrix's questions`); }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let pk = spawnSync("node", [join(repo, "schema/packages.mjs"), dir, ...pkgArgs, "--json"], { encoding: "utf8" });
|
|
299
|
+
if (pk.status === 0 && JSON.parse(pk.stdout).core.some((x) => x.id === "marketing" && x.in === "now")) {
|
|
300
|
+
const sp = spawnSync("node", [join(repo, "operations/marketing/site/plan.mjs"), dir, "--write"], { encoding: "utf8" });
|
|
301
|
+
if (sp.status === 0) prov(dir, { entity: "operations/marketing/sitemap.json", route: "operations/marketing/sitemap", activity: "create", derived_from: ["purpose/icp.json", "products/"], agent: agentFromEnv("agent", "harness", { id: "naive-planner" }), note: "the site planned from the venture's records: the marketing package is open" });
|
|
302
|
+
}
|
|
303
|
+
pk = spawnSync("node", [join(repo, "schema/packages.mjs"), dir, ...pkgArgs, "--write", "--json"], { encoding: "utf8" });
|
|
304
|
+
if (pk.status === 0) {
|
|
305
|
+
const r = JSON.parse(pk.stdout);
|
|
306
|
+
const open = r.core.filter((x) => x.in === "now");
|
|
307
|
+
trace.children.push({ name: "venture package", run_type: "tool", start: iso(), end: iso(), inputs: { phase: r.phase }, outputs: { progress: r.progress, open: open.map((x) => ({ id: x.id, state: x.state, progress: x.progress, moving: x.moving, missing: x.missing })), questions_open: r.situation.questions.filter((x) => x.value == null).map((x) => x.id) } });
|
|
308
|
+
lap(`venture package: ${Math.round(r.progress * 100)}% across ${open.map((x) => `${x.title} (${x.state}, ${Math.round(x.progress * 100)}%)`).join(", ")}; next phase: ${r.core.filter((x) => x.in === "next").map((x) => x.title).join(", ") || "none"}; view in ${join(dir, "package")}`);
|
|
309
|
+
} else console.log(` the venture package failed: ${pk.stderr.trim().slice(0, 300)}`);
|
|
310
|
+
|
|
311
|
+
const end = decisions(dir);
|
|
312
|
+
lap(`done. ${end.decisions.filter((d) => d.source === "file").length} decision records, ${end.decisions.filter((d) => d.rank != null).length} ranked for the founder, ${settled.length} settled; workspace ${ws}`);
|
|
313
|
+
finish(0);
|