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.
Files changed (338) hide show
  1. package/AGENTS.md +61 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -2
  4. package/docs/decisions/decisions.json +861 -0
  5. package/docs/ontology.json +796 -0
  6. package/docs/ontology.md +163 -0
  7. package/docs/reference/ROUTES.md +125 -0
  8. package/evidence/benchmarks/2026-10-27-golden.jev.after-catalog-fix.json +558 -0
  9. package/evidence/benchmarks/2026-10-27-golden.jev.json +501 -0
  10. package/evidence/benchmarks/2026-10-27-golden.reasoning-answers.json +177 -0
  11. package/evidence/benchmarks/JEV_VS_REASONING_MODEL_2026-10-27.md +68 -0
  12. package/evidence/capabilities/agent-capabilities.json +86 -0
  13. package/evidence/design-documents.registry.json +2896 -0
  14. package/evidence/evals/evals.json +25 -0
  15. package/evidence/evals/harness-evals.json +140 -0
  16. package/evidence/layer.json +51 -0
  17. package/evidence/skills/agent-capabilities/SKILL.md +35 -0
  18. package/evidence/skills/design-coverage/SKILL.md +57 -0
  19. package/evidence/skills/design-coverage/check.mjs +192 -0
  20. package/evidence/skills/design-coverage/scan.mjs +45 -0
  21. package/evidence/skills/fidelity-ladder-audit/SKILL.md +121 -0
  22. package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +180 -0
  23. package/evidence/skills/findability/SKILL.md +32 -0
  24. package/evidence/skills/findability/inventory.rb +41 -0
  25. package/evidence/skills/foreshadowing-audit/SKILL.md +183 -0
  26. package/evidence/skills/metrics-review/SKILL.md +177 -0
  27. package/evidence/tooling/integrations.json +153 -0
  28. package/governance/layer.json +101 -0
  29. package/governance/references/references.json +26 -0
  30. package/governance/skills/board-design/SKILL.md +68 -0
  31. package/governance/skills/brand-pillar/SKILL.md +90 -0
  32. package/governance/skills/business-pillar/SKILL.md +96 -0
  33. package/governance/skills/chief-of-staff/SKILL.md +45 -0
  34. package/governance/skills/cofounder-structure/SKILL.md +64 -0
  35. package/governance/skills/cross-pillar-tradeoff/SKILL.md +168 -0
  36. package/governance/skills/equity-and-share-classes/SKILL.md +68 -0
  37. package/governance/skills/executive-roles/SKILL.md +95 -0
  38. package/governance/skills/exit-planning/SKILL.md +66 -0
  39. package/governance/skills/financing-and-investor-rights/SKILL.md +73 -0
  40. package/governance/skills/office-hours-mode/SKILL.md +113 -0
  41. package/governance/skills/product-pillar/SKILL.md +81 -0
  42. package/governance/skills/publication-policy/SKILL.md +63 -0
  43. package/governance/skills/role-and-candidate-authoring/SKILL.md +198 -0
  44. package/governance/skills/stakeholder-update/SKILL.md +177 -0
  45. package/governance/tooling/integrations.json +37 -0
  46. package/governance/venture-template/AGENT_ROLES_AND_DECISION_RIGHTS.md +58 -0
  47. package/governance/venture-template/COMPANY_VAULT_DEFINITION_2026-10-25.md +204 -0
  48. package/governance/venture-template/README.md +74 -0
  49. package/governance/venture-template/decision-rights.json +186 -0
  50. package/governance/venture-template/index.md +151 -0
  51. package/governance/venture-template/pillars/brand.md +209 -0
  52. package/governance/venture-template/pillars/business.md +255 -0
  53. package/governance/venture-template/pillars/product.md +254 -0
  54. package/governance/venture-template/render-roles.py +11 -0
  55. package/governance/venture-template/roles/README.md +85 -0
  56. package/governance/venture-template/roles/agent-roles.json +202 -0
  57. package/governance/venture-template/roles/ceo.md +163 -0
  58. package/governance/venture-template/roles/chief-of-staff.md +153 -0
  59. package/governance/venture-template/roles/cmo.md +165 -0
  60. package/governance/venture-template/roles/coo.md +189 -0
  61. package/governance/venture-template/roles/cto.md +158 -0
  62. package/governance/venture-template/roles/department-head.md +47 -0
  63. package/governance/venture-template/roles/forward-deployed-engineer.md +51 -0
  64. package/governance/venture-template/roles/founder.md +173 -0
  65. package/governance/venture-template/roles/hr-manager.md +49 -0
  66. package/governance/venture-template/roles/talent-acquisition-manager.md +50 -0
  67. package/interface/api/server.mjs +69 -0
  68. package/interface/cli/find-venture.mjs +15 -0
  69. package/interface/cli/mentura.mjs +271 -0
  70. package/interface/cli/naive.mjs +169 -0
  71. package/interface/demo/board.mjs +581 -0
  72. package/interface/demo/decision-harness.mjs +91 -0
  73. package/interface/demo/live-agent.mjs +47 -0
  74. package/interface/harness/evals.mjs +90 -0
  75. package/interface/harness/examples/pt-no-shows.dump.md +19 -0
  76. package/interface/harness/run.mjs +313 -0
  77. package/interface/harness/site.mjs +59 -0
  78. package/interface/layer.json +31 -0
  79. package/interface/mcp/server.mjs +122 -0
  80. package/interface/operator-console/README.md +58 -0
  81. package/interface/provenance.mjs +42 -0
  82. package/interface/references/references.json +28 -0
  83. package/interface/route-aliases.json +32 -0
  84. package/interface/router.mjs +580 -0
  85. package/interface/skills/install-mentura/SKILL.md +88 -0
  86. package/interface/tooling/integrations.json +112 -0
  87. package/knowledge/connectors/README.md +54 -0
  88. package/knowledge/connectors/catalog.mjs +118 -0
  89. package/knowledge/connectors/contract.mjs +56 -0
  90. package/knowledge/connectors/eval.mjs +176 -0
  91. package/knowledge/connectors/filesystem.mjs +79 -0
  92. package/knowledge/connectors/index.mjs +102 -0
  93. package/knowledge/connectors/resolve.mjs +44 -0
  94. package/knowledge/connectors/suite.mjs +80 -0
  95. package/knowledge/layer.json +42 -0
  96. package/knowledge/skills/find-reference/SKILL.md +38 -0
  97. package/knowledge/skills/industry-awareness/SKILL.md +277 -0
  98. package/knowledge/tooling/integrations.json +127 -0
  99. package/knowledge/tooling/questions.md +46 -0
  100. package/operations/FOUNDER_OPERATING_MODEL_CAMPAIGN_2026-10-14.md +16 -0
  101. package/operations/departments.json +162 -0
  102. package/operations/handoff-v2.md +93 -0
  103. package/operations/layer.json +60 -0
  104. package/operations/marketing/check-public.mjs +68 -0
  105. package/operations/marketing/site/README.md +86 -0
  106. package/operations/marketing/site/matrix.json +731 -0
  107. package/operations/marketing/site/plan.mjs +117 -0
  108. package/operations/marketing/site/presence.mjs +252 -0
  109. package/operations/references/references.json +45 -0
  110. package/operations/skills/recruiting/SKILL.md +32 -0
  111. package/operations/skills/team-design/SKILL.md +36 -0
  112. package/operations/skills/tool-selection/SKILL.md +46 -0
  113. package/operations/templates/recruiting-brief.md +35 -0
  114. package/operations/tooling/integrations.json +158 -0
  115. package/package.json +57 -4
  116. package/packages/catalog.json +272 -0
  117. package/packages/cofounder/package.json +42 -0
  118. package/packages/counsel/package.json +47 -0
  119. package/packages/customers/package.json +21 -0
  120. package/packages/decisions/package.json +17 -0
  121. package/packages/engineering/package.json +41 -0
  122. package/packages/evidence/package.json +17 -0
  123. package/packages/finance/package.json +48 -0
  124. package/packages/foundation/package.json +52 -0
  125. package/packages/investor/package.json +57 -0
  126. package/packages/investors/package.json +21 -0
  127. package/packages/legal/package.json +73 -0
  128. package/packages/marketing/package.json +57 -0
  129. package/packages/money/package.json +21 -0
  130. package/packages/operations/package.json +74 -0
  131. package/packages/partner/package.json +38 -0
  132. package/packages/people/package.json +64 -0
  133. package/packages/product/package.json +49 -0
  134. package/packages/raw/package.json +19 -0
  135. package/packages/relationships/package.json +52 -0
  136. package/packages/validation/package.json +99 -0
  137. package/platform/README.md +45 -0
  138. package/platform/connectors/eval.mjs +154 -0
  139. package/platform/connectors/fake-github.mjs +91 -0
  140. package/platform/connectors/github.mjs +105 -0
  141. package/platform/integrations/jev.mjs +30 -0
  142. package/platform/integrations/langsmith-docket.mjs +53 -0
  143. package/platform/integrations/langsmith-setup.mjs +115 -0
  144. package/platform/integrations/langsmith.mjs +47 -0
  145. package/platform/integrations/rho.mjs +145 -0
  146. package/platform/integrations/site.mjs +75 -0
  147. package/platform/kits/README.md +7 -0
  148. package/platform/mcp/research.mjs +64 -0
  149. package/purpose/checklists/vault-and-tooling-consultancy-setup.md +216 -0
  150. package/purpose/frameworks/business-discovery-framework-amendments-2026-05-20.md +181 -0
  151. package/purpose/frameworks/business-discovery-framework.html +938 -0
  152. package/purpose/frameworks/business-discovery-framework.md +326 -0
  153. package/purpose/frameworks/idea-map/README.md +74 -0
  154. package/purpose/frameworks/idea-map/conventions.json +146 -0
  155. package/purpose/frameworks/idea-map/graph.mjs +182 -0
  156. package/purpose/frameworks/idea-map/ideamap.mjs +179 -0
  157. package/purpose/frameworks/idea-map/route.mjs +140 -0
  158. package/purpose/frameworks/onboarding-handoff-framework.md +269 -0
  159. package/purpose/frameworks/post-discovery-deliverable-playbook.md +274 -0
  160. package/purpose/frameworks/product-anatomy/README.md +63 -0
  161. package/purpose/frameworks/product-anatomy/anatomy.mjs +195 -0
  162. package/purpose/frameworks/product-anatomy/examples/polity.answers.reasoned.json +11 -0
  163. package/purpose/frameworks/product-anatomy/examples/polity.json +89 -0
  164. package/purpose/frameworks/product-anatomy/phases.json +107 -0
  165. package/purpose/frameworks/product-anatomy/questions.json +74 -0
  166. package/purpose/kits/validation/README.md +14 -0
  167. package/purpose/kits/validation/references/references.json +122 -0
  168. package/purpose/kits/validation/skills/problem-validation/SKILL.md +43 -0
  169. package/purpose/kits/validation/skills/validation-score/SKILL.md +35 -0
  170. package/purpose/kits/validation/templates/go-to-market.md +19 -0
  171. package/purpose/kits/validation/templates/operations-readiness.md +20 -0
  172. package/purpose/kits/validation/templates/pricing.md +22 -0
  173. package/purpose/kits/validation/templates/problem-interview.md +36 -0
  174. package/purpose/kits/validation/templates/problem.md +45 -0
  175. package/purpose/kits/validation/templates/product-market-fit.md +23 -0
  176. package/purpose/kits/validation/templates/solution.md +18 -0
  177. package/purpose/kits/validation/templates/team.md +19 -0
  178. package/purpose/kits/validation/weights.json +22 -0
  179. package/purpose/layer.json +53 -0
  180. package/purpose/references/references.json +324 -0
  181. package/purpose/skills/ambition-tier/SKILL.md +205 -0
  182. package/purpose/skills/telos/SKILL.md +52 -0
  183. package/purpose/strategic-foundation.json +133 -0
  184. package/purpose/templates/telos-based-smart.md +39 -0
  185. package/purpose/tooling/integrations.json +48 -0
  186. package/schema/README.md +61 -0
  187. package/schema/VERSION +1 -0
  188. package/schema/account.schema.json +79 -0
  189. package/schema/add-department.mjs +41 -0
  190. package/schema/agent-capability.schema.json +94 -0
  191. package/schema/alignment.schema.json +61 -0
  192. package/schema/assess-problem.mjs +131 -0
  193. package/schema/bdd.mjs +56 -0
  194. package/schema/board.schema.json +101 -0
  195. package/schema/check-boundary.mjs +54 -0
  196. package/schema/check-data-model.mjs +170 -0
  197. package/schema/check-data-packages.mjs +83 -0
  198. package/schema/check-departments.mjs +45 -0
  199. package/schema/check-execution.mjs +85 -0
  200. package/schema/check-governance.mjs +98 -0
  201. package/schema/check-integrations.mjs +33 -0
  202. package/schema/check-nextjs-status.mjs +77 -0
  203. package/schema/check-nouns.mjs +49 -0
  204. package/schema/check-private.mjs +61 -0
  205. package/schema/check-provenance.mjs +55 -0
  206. package/schema/check-publication.mjs +96 -0
  207. package/schema/check-ready.mjs +89 -0
  208. package/schema/check-sources.mjs +66 -0
  209. package/schema/check-team.mjs +94 -0
  210. package/schema/check-telos.mjs +59 -0
  211. package/schema/check-validation.mjs +165 -0
  212. package/schema/check-ventures.mjs +81 -0
  213. package/schema/check-where-used.mjs +58 -0
  214. package/schema/data-master.schema.json +14 -0
  215. package/schema/data-model.json +1258 -0
  216. package/schema/data-model.schema.json +152 -0
  217. package/schema/decision-analytics.mjs +77 -0
  218. package/schema/decision-record.schema.json +290 -0
  219. package/schema/decision-rights.schema.json +34 -0
  220. package/schema/decision.schema.json +30 -0
  221. package/schema/decisions.mjs +321 -0
  222. package/schema/department-catalog.schema.json +55 -0
  223. package/schema/department.schema.json +15 -0
  224. package/schema/departments.mjs +32 -0
  225. package/schema/docket-eval.mjs +89 -0
  226. package/schema/docket.mjs +135 -0
  227. package/schema/eval-suite.schema.json +45 -0
  228. package/schema/evidence.schema.json +110 -0
  229. package/schema/examples/migrate/agent.mjs +12 -0
  230. package/schema/examples/workspace/ventures/studio/accounts/bakery/account.json +18 -0
  231. package/schema/examples/workspace/ventures/studio/evidence/.gitkeep +0 -0
  232. package/schema/examples/workspace/ventures/studio/governance/.gitkeep +0 -0
  233. package/schema/examples/workspace/ventures/studio/knowledge/.gitkeep +0 -0
  234. package/schema/examples/workspace/ventures/studio/operations/.gitkeep +0 -0
  235. package/schema/examples/workspace/ventures/studio/print/knowledge/.gitkeep +0 -0
  236. package/schema/examples/workspace/ventures/studio/print/purpose/.gitkeep +0 -0
  237. package/schema/examples/workspace/ventures/studio/print/venture.json +37 -0
  238. package/schema/examples/workspace/ventures/studio/products/brand-care.json +19 -0
  239. package/schema/examples/workspace/ventures/studio/products/brand-kit.json +23 -0
  240. package/schema/examples/workspace/ventures/studio/provenance.jsonl +3 -0
  241. package/schema/examples/workspace/ventures/studio/purpose/.gitkeep +0 -0
  242. package/schema/examples/workspace/ventures/studio/venture.json +43 -0
  243. package/schema/examples/workspace/ventures/tandem/accounts/.gitkeep +0 -0
  244. package/schema/examples/workspace/ventures/tandem/evidence/README.md +3 -0
  245. package/schema/examples/workspace/ventures/tandem/governance/README.md +3 -0
  246. package/schema/examples/workspace/ventures/tandem/governance/agreements/dee-fast.md +3 -0
  247. package/schema/examples/workspace/ventures/tandem/governance/board.json +51 -0
  248. package/schema/examples/workspace/ventures/tandem/governance/departments/finance/charter.md +5 -0
  249. package/schema/examples/workspace/ventures/tandem/governance/departments/finance/department.json +5 -0
  250. package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/charter.md +5 -0
  251. package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/department.json +5 -0
  252. package/schema/examples/workspace/ventures/tandem/governance/exit.json +33 -0
  253. package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.json +24 -0
  254. package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.md +3 -0
  255. package/schema/examples/workspace/ventures/tandem/governance/ownership.json +131 -0
  256. package/schema/examples/workspace/ventures/tandem/governance/publication-policy.json +171 -0
  257. package/schema/examples/workspace/ventures/tandem/knowledge/README.md +3 -0
  258. package/schema/examples/workspace/ventures/tandem/knowledge/research/friends-feedback-2026-08.md +3 -0
  259. package/schema/examples/workspace/ventures/tandem/knowledge/research/pilot-2026-10.md +3 -0
  260. package/schema/examples/workspace/ventures/tandem/knowledge/research/problem-interviews-2026-09.md +20 -0
  261. package/schema/examples/workspace/ventures/tandem/knowledge/sources.json +8 -0
  262. package/schema/examples/workspace/ventures/tandem/knowledge/where-used.json +13 -0
  263. package/schema/examples/workspace/ventures/tandem/operations/README.md +3 -0
  264. package/schema/examples/workspace/ventures/tandem/operations/departments/finance/README.md +5 -0
  265. package/schema/examples/workspace/ventures/tandem/operations/departments/finance/department.json +5 -0
  266. package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/README.md +5 -0
  267. package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/department.json +5 -0
  268. package/schema/examples/workspace/ventures/tandem/pillars/brand.md +5 -0
  269. package/schema/examples/workspace/ventures/tandem/pillars/business.md +5 -0
  270. package/schema/examples/workspace/ventures/tandem/pillars/product.md +5 -0
  271. package/schema/examples/workspace/ventures/tandem/products/.gitkeep +0 -0
  272. package/schema/examples/workspace/ventures/tandem/products/shop-app.json +22 -0
  273. package/schema/examples/workspace/ventures/tandem/profile.md +5 -0
  274. package/schema/examples/workspace/ventures/tandem/provenance.jsonl +13 -0
  275. package/schema/examples/workspace/ventures/tandem/purpose/README.md +3 -0
  276. package/schema/examples/workspace/ventures/tandem/purpose/telos.json +78 -0
  277. package/schema/examples/workspace/ventures/tandem/purpose/validation/go-to-market.json +35 -0
  278. package/schema/examples/workspace/ventures/tandem/purpose/validation/pricing.json +37 -0
  279. package/schema/examples/workspace/ventures/tandem/purpose/validation/problem.json +74 -0
  280. package/schema/examples/workspace/ventures/tandem/purpose/validation/product-market-fit.json +66 -0
  281. package/schema/examples/workspace/ventures/tandem/purpose/validation/solution.json +33 -0
  282. package/schema/examples/workspace/ventures/tandem/purpose/validation/team.json +52 -0
  283. package/schema/examples/workspace/ventures/tandem/venture.json +79 -0
  284. package/schema/examples/workspace/ventures/woodworking/knowledge/.gitkeep +0 -0
  285. package/schema/examples/workspace/ventures/woodworking/venture.json +21 -0
  286. package/schema/examples/workspace/workspace.json +8 -0
  287. package/schema/exit.schema.json +74 -0
  288. package/schema/foundation.mjs +118 -0
  289. package/schema/founder-agreement.schema.json +83 -0
  290. package/schema/gate.mjs +39 -0
  291. package/schema/handoff.schema.json +239 -0
  292. package/schema/icp.schema.json +136 -0
  293. package/schema/idea-graph.schema.json +61 -0
  294. package/schema/init-venture.mjs +98 -0
  295. package/schema/integration.schema.json +143 -0
  296. package/schema/knowledge-sources.schema.json +130 -0
  297. package/schema/layer.json +46 -0
  298. package/schema/layer.schema.json +119 -0
  299. package/schema/load-packages.mjs +26 -0
  300. package/schema/marketing-sitemap.schema.json +232 -0
  301. package/schema/migrate-venture.mjs +192 -0
  302. package/schema/need-now.json +601 -0
  303. package/schema/need-now.mjs +107 -0
  304. package/schema/nextjs-status.schema.json +29 -0
  305. package/schema/ownership.schema.json +282 -0
  306. package/schema/packages.json +4 -0
  307. package/schema/packages.mjs +247 -0
  308. package/schema/payouts.mjs +91 -0
  309. package/schema/port-venture.mjs +161 -0
  310. package/schema/product.schema.json +223 -0
  311. package/schema/provenance-entry.schema.json +154 -0
  312. package/schema/publication-policy.schema.json +293 -0
  313. package/schema/raw-source.schema.json +15 -0
  314. package/schema/reference.schema.json +115 -0
  315. package/schema/route-aliases.schema.json +25 -0
  316. package/schema/run-handoff.mjs +51 -0
  317. package/schema/scaffold/defaults.json +54 -0
  318. package/schema/site-presence.schema.json +337 -0
  319. package/schema/strategic-foundation.schema.json +74 -0
  320. package/schema/stream.schema.json +14 -0
  321. package/schema/team-plan.schema.json +157 -0
  322. package/schema/telos.schema.json +197 -0
  323. package/schema/time-to-ready.mjs +93 -0
  324. package/schema/upgrade-venture.mjs +129 -0
  325. package/schema/use-model.mjs +10 -0
  326. package/schema/validate.py +49 -0
  327. package/schema/validation-evidence.schema.json +123 -0
  328. package/schema/validation-go-to-market.schema.json +110 -0
  329. package/schema/validation-operations-readiness.schema.json +91 -0
  330. package/schema/validation-pricing.schema.json +114 -0
  331. package/schema/validation-problem.schema.json +118 -0
  332. package/schema/validation-product-market-fit.schema.json +135 -0
  333. package/schema/validation-solution.schema.json +79 -0
  334. package/schema/validation-team.schema.json +116 -0
  335. package/schema/venture.schema.json +407 -0
  336. package/schema/vocab/business-models.json +298 -0
  337. package/schema/where-used.schema.json +56 -0
  338. package/schema/workspace.schema.json +38 -0
@@ -0,0 +1,580 @@
1
+ // The route table and its actions, shared by the CLI, the MCP server and the HTTP API (EE-030,
2
+ // EE-032). Routes are generated from the data model and the kit manifests, never written by hand,
3
+ // so they cannot drift from the files. The conventions follow the Next.js App Router:
4
+ //
5
+ // segments a route is <layer>[/<kit>]/<record>; layers stay in the path, so a route is the
6
+ // record's folder path (purpose/validation/problem -> purpose/validation/problem.json)
7
+ // [id] a collection (products, accounts, handoffs, evidence) has a list route and an
8
+ // item route: purpose/product and purpose/product/[id]
9
+ // [...venture] the venture is the dynamic segment in front of every route: an id, or its folder
10
+ // path for a nested venture (studio/print)
11
+ // route record routes: read, write, check, template; command routes: run
12
+ // page view: a Markdown page of a record or command result, for people
13
+ // layout layout: the context wrapping a route (system, layer and kit purpose, references,
14
+ // skills, the gate and what need-now says), given to an agent before it acts
15
+ // not-found read of a missing record answers with its template and need-now status
16
+ // loading read of a record with an agent's draft waiting says so
17
+ // error a failed check or write answers with the kit's skills and the template to fix it
18
+ // (group) route groups by phase: (discovery) ... (platform) gather the routes need-now
19
+ // suggests in that phase, with the phase as their layout; the path is unchanged
20
+ // @slot system/overview renders slots side by side (telos, validation, need-now, ready,
21
+ // team, provenance); a failing slot shows its error and the others still render
22
+ // _private _drafts/ is never routed
23
+ // proxy gates run before a write: the schema, the decision class the data model gives the
24
+ // record (escalate -> an agent's write is a draft), and the role's allowed routes
25
+ // robots a team-plan role may list the routes it may read and write; system/robots shows them
26
+ // aliases interface/route-aliases.json maps a renamed route to its new one, with a note
27
+ // storage a record is read and written through the connector for the source its record
28
+ // type lives in (knowledge/connectors, EE-037): the venture's own folder unless its
29
+ // knowledge/sources.json names another, such as a GitHub repository, where a write
30
+ // opens a draft pull request. A read says which source and version it came from.
31
+ //
32
+ // Partial adoption: a route needs only its own record and what its kit reads.
33
+
34
+ import { execFileSync, spawnSync } from "node:child_process";
35
+ import { createHash } from "node:crypto";
36
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
37
+ import { tmpdir } from "node:os";
38
+ import { basename, dirname, join, relative } from "node:path";
39
+ import { agentFromEnv, entries as provEntries, record as prov } from "./provenance.mjs";
40
+ import { address } from "../knowledge/connectors/contract.mjs";
41
+ import { holding, locked } from "../schema/decisions.mjs";
42
+ import { configOf, hold } from "../schema/gate.mjs";
43
+ import { LOCAL, attributeSources, connect, sourceFor } from "../knowledge/connectors/index.mjs";
44
+
45
+ export const repo = new URL("..", import.meta.url).pathname;
46
+ const load = (p) => JSON.parse(readFileSync(p, "utf8"));
47
+ const defaultWorkspace = () => process.env.MENTURA_WORKSPACE ?? join(repo, "instance");
48
+ export const PHASES = load(join(repo, "purpose/frameworks/product-anatomy/phases.json")).phases;
49
+
50
+ export const glob = (g) => new RegExp("^" + g.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*\*/g, "\u0001").replace(/\*/g, "[^/]*").replace(/\0/g, "(?:[^/]+/)*").replace(/\u0001/g, ".*") + "$");
51
+ // A venture's files, stopping at a nested venture (which has its own records) and skipping dot and private folders.
52
+ export const files = (d, base = d) => existsSync(d) ? readdirSync(d, { withFileTypes: true }).flatMap((e) =>
53
+ e.name.startsWith(".") ? [] : e.isDirectory() ? (existsSync(join(d, e.name, "venture.json")) || (d === base && e.name.startsWith("_")) ? [] : files(join(d, e.name), base)) : [join(d, e.name).slice(base.length + 1)]) : [];
54
+
55
+ const skillsOf = (k) => (k.skills?.length ? k.skills : k.skill ? [k.skill] : []);
56
+
57
+ /** Layer manifests and any package that owns record types. `kit` is the name, kept so the board still groups routes. */
58
+ export function kits() {
59
+ const model = load(join(repo, "schema/data-model.json"));
60
+ const paths = [...model.layers.map((l) => `${l}/layer.json`), "schema/layer.json"];
61
+ const pkgDir = join(repo, "packages");
62
+ if (existsSync(pkgDir)) for (const d of readdirSync(pkgDir)) if (existsSync(join(pkgDir, d, "package.json"))) paths.push(`packages/${d}/package.json`);
63
+ return paths.filter((p) => existsSync(join(repo, p))).map((p) => {
64
+ const k = load(join(repo, p));
65
+ const name = k.name ?? k.kit;
66
+ return { path: p, ...k, name, kit: name };
67
+ }).filter((k) => k.path.endsWith("/layer.json") || (k.records ?? []).length);
68
+ }
69
+
70
+ const needNowPriors = () => load(join(repo, "schema/need-now.json"));
71
+
72
+ /** The route table, generated from schema/data-model.json, the kit manifests and schema/need-now.json. */
73
+ export function routes() {
74
+ const model = load(join(repo, "schema/data-model.json"));
75
+ const ks = kits();
76
+ const owner = new Map(ks.flatMap((k) => k.records.map((r) => [r, k])));
77
+ const N = needNowPriors();
78
+ const out = [];
79
+ for (const r of model.records.filter((x) => x.level === "venture" && x.id !== "venture")) {
80
+ const k = owner.get(r.id);
81
+ const sub = k?.path.match(/^[a-z]+\/kits\/([^/]+)\//)?.[1] ?? (k?.path.startsWith("packages/") && r.id.startsWith(`${k.name}-`) ? k.name : null);
82
+ const short = sub && r.id.startsWith(`${sub}-`) ? r.id.slice(sub.length + 1) : r.id;
83
+ const filesOf = r.paths.filter((p) => p.startsWith("instance/**/")).map((p) => p.slice("instance/**/".length));
84
+ // The template is the package's template dir, or <manifest folder>/templates/<record>.md, or one whose name starts <record>- (telos-based-smart.md).
85
+ const tdir = k?.template_dir ?? (k && !k.path.startsWith("packages/") ? join(dirname(k.path), "templates") : null);
86
+ const tname = tdir && existsSync(join(repo, tdir)) ? readdirSync(join(repo, tdir)).filter((f) => f === `${short}.md` || (f.startsWith(`${short}-`) && f.endsWith(".md"))).sort((a, b) => (a === `${short}.md` ? -1 : b === `${short}.md` ? 1 : a.localeCompare(b)))[0] : null;
87
+ const template = tname ? `${tdir}/${tname}` : null;
88
+ const checks = [...new Set((r.refs ?? []).map((x) => x.checked_by).filter((c) => c && c.startsWith("schema/check-") && c !== "schema/check-data-model.mjs"))];
89
+ const legacy = [r.layer === "system" ? "system" : r.layer, sub, short].filter(Boolean).join("/");
90
+ const base = PACKAGE_ROUTE[r.id] ?? legacy;
91
+ const form = r.form ?? "json";
92
+ const groups = PHASES.map((p) => p.id).filter((p) => (N.records[r.id]?.base?.[p] ?? 0) >= N.suggest).map((p) => `(${p})`);
93
+ const common = { kind: "record", record: r.id, layer: r.layer, kit: k?.name ?? null, package: k?.path?.startsWith("packages/") ? k.name : null, kit_path: k?.path ?? null, schema: r.schema, form, checks, template,
94
+ decision_class: r.decision_class ?? null, groups, note: r.note ?? "" };
95
+ // A collection's one wildcard is a whole id at the start of a path segment (products/*.json, accounts/*/account.json).
96
+ const collection = filesOf.length >= 1 && filesOf.every((p) => (p.match(/\*/g) ?? []).length === 1 && /(^|\/)\*[./]/.test(p) && !p.includes("**"));
97
+ const concrete = filesOf.every((f) => !f.includes("*"));
98
+ const writable = !!(r.schema && form === "json" && (collection || concrete));
99
+ if (collection) {
100
+ out.push({ ...common, route: base, files: filesOf, collection: "list", actions: ["read", "view", "layout", ...(checks.length ? ["check"] : []), ...(template ? ["template"] : [])] });
101
+ out.push({ ...common, route: `${base}/[id]`, files: filesOf, collection: "item",
102
+ actions: ["read", "view", "layout", ...(writable ? ["write"] : []), ...(checks.length ? ["check"] : []), ...(template ? ["template"] : [])] });
103
+ } else {
104
+ out.push({ ...common, route: base, files: filesOf,
105
+ actions: ["read", "view", "layout", ...(writable && !filesOf[0].includes("*") ? ["write"] : []), ...(checks.length ? ["check"] : []), ...(template ? ["template"] : [])] });
106
+ }
107
+ }
108
+ const cmd = (route, script, note, extra = {}) => ({ route, kind: "command", actions: ["run", "view", "layout"], script, groups: [], note, ...extra });
109
+ out.push(
110
+ cmd("system/package", "schema/packages.mjs", "The venture package (EE-067, EE-069): the core packages open now and the handoffs, each record confirmed, to review or to write, what moves on its own and what waits, and what completion unlocks; --write renders the view and records a version. Assumes a solo founder or small founding team in Discovery and Validation; agents are a package enhancement"),
111
+ cmd("system/need-now", "schema/need-now.mjs", "What the venture needs now, soon, or should hold back (EE-028)"),
112
+ cmd("system/time-to-ready", "schema/time-to-ready.mjs", "Time to running, and to ready per layer and MAD tier"),
113
+ cmd("purpose/validation/assess", "schema/assess-problem.mjs", "What the problem's evidence is strong enough for: each assumption labelled demonstrated, inferred, assumed, unknown or contradicted, the commitment it supports and the next test (EE-050). Asks first when the record is incomplete; input as_is runs it anyway", { as_is: true }),
114
+ cmd("governance/analytics", "schema/decision-analytics.mjs", "Decision analytics (EE-072): how many decisions of each type each function has made, open against decided, in whose words, in which phase, and their outcomes. Data only; a view is the platform's, or a visualization skill's", { actions: ["run", "layout"] }),
115
+ cmd("governance/decisions", "schema/decisions.mjs", "The decision matrix: every decision the venture owes by layer and phase, with its parent and boundary, and whether it renders now, waits, comes later, is stale or deferred (EE-054, EE-057)"),
116
+ cmd("operations/marketing/sitemap", "operations/marketing/site/plan.mjs", "Plan the public site from the venture's records: the product decides whom it is offered to, that decides the audiences, and each page gets its sections in order with the MDX component, need, audience, reason, condition, source record and the public variant to write (EE-064)"),
117
+ cmd("operations/marketing/public", "operations/marketing/check-public.mjs", "The public gate: every marketing content file is a variant of a named internal record, says its audience and whether it is published, has a person's approval when published, and copies no run of ten words from its internal record (EE-064)"),
118
+ cmd("operations/marketing/site", "operations/marketing/site/presence.mjs", "How present the venture's public records are on its marketing site: a score weighed by who the site must speak to now (raising, hiring, contributors, ICPs), and each item missing, not prominent enough or present, with where it belongs (EE-062). Read the site first with interface/harness/site.mjs"),
119
+ cmd("purpose/ideas/routes", "purpose/frameworks/idea-map/route.mjs", "Where each idea goes in the framework: the records it feeds, each destination's stage (routed, drafted, your decision, waiting, done), whether it moves in order or on its own, and the one step that moves it; plus the decision docket traced to the ideas (EE-061)"),
120
+ cmd("purpose/ideas/graph", "purpose/frameworks/idea-map/graph.mjs", "The idea graph: which ideas build on which, each idea's signal, the lanes that move in order and the ones that can move on their own, and the ideas to develop next (EE-060)"),
121
+ cmd("purpose/ideas", "purpose/frameworks/idea-map/ideamap.mjs", "The founder's idea dump mapped onto Naive's conventions: each idea verbatim with its departments, records and whether it exists, where the ideas cluster, what the dump never mentions, and what it says about whether a product exists (EE-059)"),
122
+ cmd("purpose/foundation", "schema/foundation.mjs", "The venture's strategic foundation by the Five Ws and How plus whether, each confirmed, drafted by an agent, waiting as a draft, or missing (EE-051)"),
123
+ cmd("purpose/validation/score", "schema/check-validation.mjs", "Held (EE-050): the single validation score from graded evidence (EE-026); purpose/validation/assess replaces it for the problem", { args: ["--score"] }),
124
+ cmd("governance/payouts", "schema/payouts.mjs", "Who gets what at a sale value, from the cap table (EE-022); input value, else the scenarios in exit.json", { needs: ["governance/ownership.json"], venture_arg: "id" }),
125
+ cmd("system/overview", null, "The venture at a glance: telos, validation, need-now, time to ready, team and recent provenance, rendered as parallel slots", { internal: "overview" }),
126
+ cmd("system/robots", null, "Which roles may read and write which routes, from the team plan", { internal: "robots" }),
127
+ cmd("knowledge/catalog", "knowledge/connectors/catalog.mjs", "Build or refresh the catalog: every item in every source the venture keeps records in, with its address and version, no content (EE-037)"),
128
+ cmd("knowledge/search", "knowledge/connectors/catalog.mjs", "Find items across every source through the catalog; input value is the query (EE-037)", { value_flag: "--search" }),
129
+ );
130
+ return out.sort((a, b) => a.route.localeCompare(b.route));
131
+ }
132
+
133
+ /** Route groups: (phase) gathers the routes need-now suggests in that phase. The path is unchanged. */
134
+ export function groups() {
135
+ const rs = routes();
136
+ return PHASES.map((p) => ({ group: `(${p.id})`, phase: p.id, question: p.question, gate: p.gate, routes: rs.filter((r) => r.groups.includes(`(${p.id})`)).map((r) => r.route) }));
137
+ }
138
+
139
+ // Aliases for renamed routes (stability): interface/route-aliases.json, or MENTURA_ROUTE_ALIASES.
140
+ function aliases() {
141
+ const f = process.env.MENTURA_ROUTE_ALIASES ?? join(repo, "interface/route-aliases.json");
142
+ return existsSync(f) ? load(f).aliases : [];
143
+ }
144
+
145
+ /** Matches a concrete route (purpose/product/kettle) to its table entry and params. Follows aliases. */
146
+ export function resolve(route) {
147
+ const rs = routes();
148
+ const alias = aliases().find((a) => a.from === route || route.startsWith(`${a.from}/`));
149
+ const concrete = alias ? alias.to + route.slice(alias.from.length) : route;
150
+ const exact = rs.find((r) => r.route === concrete);
151
+ if (exact) return { r: exact, params: {}, alias };
152
+ for (const r of rs.filter((x) => x.route.endsWith("/[id]"))) {
153
+ const head = r.route.slice(0, -"/[id]".length);
154
+ if (concrete.startsWith(`${head}/`) && !concrete.slice(head.length + 1).includes("/")) return { r, params: { id: concrete.slice(head.length + 1) }, alias };
155
+ }
156
+ return { r: null, params: {}, alias };
157
+ }
158
+
159
+ const walkVentures = (d) => readdirSync(d, { withFileTypes: true }).flatMap((e) => e.name === "node_modules" || e.name.startsWith(".") || e.name.startsWith("_") ? []
160
+ : e.isDirectory() ? walkVentures(join(d, e.name)) : e.name === "venture.json" ? [d] : []);
161
+
162
+ /**
163
+ * Finds a venture folder ([...venture]): by id, or by its folder path under the workspace or its
164
+ * ventures/ folder, so a nested venture can be named studio/print.
165
+ */
166
+ export function ventureDir(id, workspace = defaultWorkspace()) {
167
+ if (!id) return null;
168
+ if (id.includes("/")) {
169
+ for (const base of [workspace, join(workspace, "ventures")]) {
170
+ const d = join(base, id);
171
+ if (!relative(workspace, d).startsWith("..") && existsSync(join(d, "venture.json"))) return d;
172
+ }
173
+ return null;
174
+ }
175
+ for (const d of walkVentures(workspace)) if (load(join(d, "venture.json")).id === id) return d;
176
+ return null;
177
+ }
178
+
179
+ /** The ventures under a workspace: id, name, phase, path (the [...venture] segment) and folder. */
180
+ export function ventures(workspace = defaultWorkspace()) {
181
+ const root = existsSync(join(workspace, "ventures")) ? join(workspace, "ventures") : workspace;
182
+ return walkVentures(workspace).map((d) => { const v = load(join(d, "venture.json")); return { id: v.id, name: v.name, phase: v.phase, path: relative(root, d), dir: d }; });
183
+ }
184
+
185
+ // The venture's decision rights: the template, tightened by the venture's own file.
186
+ function decisionRights(dir) {
187
+ const t = load(join(repo, "governance/venture-template/decision-rights.json"));
188
+ const classes = new Map(t.classes.map((c) => [c.id, c]));
189
+ const own = join(dir, "governance/decision-rights.json");
190
+ if (existsSync(own)) for (const c of load(own).classes) classes.set(c.id, { ...classes.get(c.id), ...c, level: classes.get(c.id)?.level === "escalate" ? "escalate" : c.level });
191
+ return classes;
192
+ }
193
+
194
+ /** The gate on a record: its decision class from the data model, and whether an agent's write is a draft. */
195
+ export function gateOf(r, dir) {
196
+ if (!r.decision_class) return null;
197
+ const c = dir ? decisionRights(dir).get(r.decision_class) : null;
198
+ const level = c?.level ?? "escalate";
199
+ return { class: r.decision_class, owner: c?.owner ?? null, level, drafts: level === "escalate" || level === "recommend-approve" };
200
+ }
201
+
202
+ // Robots: a team-plan role may list the routes it may read and write (globs over routes).
203
+ function allowed(dir, actorId, action, route) {
204
+ if (!actorId || !dir) return true;
205
+ const plan = [join(dir, "people/team-plan.json"), join(dir, "operations/team-plan.json")].find((p) => existsSync(p));
206
+ if (!plan) return true;
207
+ const role = load(plan).roles.find((x) => x.id === actorId);
208
+ if (!role?.routes) return true;
209
+ const list = action === "write" ? role.routes.write ?? [] : role.routes.read ?? ["**"];
210
+ return list.some((g) => glob(g).test(route));
211
+ }
212
+
213
+ const PACKAGE_ROUTE = {
214
+ "validation-problem": "foundation/problem", icp: "foundation/icp", telos: "foundation/telos", alignment: "foundation/alignment",
215
+ "validation-solution": "validation/solution", "validation-pricing": "validation/pricing", "validation-go-to-market": "validation/go-to-market",
216
+ "validation-product-market-fit": "validation/product-market-fit", "validation-operations-readiness": "validation/operations-readiness",
217
+ "validation-team": "people/team", "team-plan": "people/team-plan", "marketing-sitemap": "marketing/sitemap", "marketing-content": "marketing/content",
218
+ "idea-dump": "raw/dump", "idea-map": "ideas/map", "idea-graph": "ideas/graph", product: "product", evidence: "evidence",
219
+ "anatomy-answers": "product/anatomy-answers", "anatomy-input": "product/anatomy", "venture-package": ".release/venture-package",
220
+ "decision-record": "decisions",
221
+ customer: "customers", investor: "investors", "money-account": "money", "raw-source": "raw/sources",
222
+ };
223
+
224
+ // Prefer a file that is already there, so an unmigrated venture keeps its old path. A new write uses the first path, which is the package layout.
225
+ const fileFor = (r, params, dir) => {
226
+ const cands = (r.collection === "item" ? r.files.map((f) => f.replace("*", params.id)) : r.files).filter((f) => !f.includes("*"));
227
+ if (dir) {
228
+ const hit = cands.find((f) => existsSync(join(dir, f)));
229
+ if (hit) return hit;
230
+ // A new collection item joins the folder the venture already keeps that collection in.
231
+ if (r.collection === "item") {
232
+ const lived = cands.find((f) => { const d = join(dir, dirname(f)); return existsSync(d) && readdirSync(d).some((n) => n.endsWith(".json") || n.endsWith(".md") || n.endsWith(".mdx")); });
233
+ if (lived) return lived;
234
+ }
235
+ }
236
+ return cands[0];
237
+ };
238
+ const draftPath = (file) => join("_drafts", `${file.replace(/\//g, "__")}.draft`);
239
+
240
+ /** Where an agent's draft of a gated record waits: { dir, file, draft, gate } or null. */
241
+ export function draftOf(route, venture, workspace) {
242
+ const { r, params } = resolve(route);
243
+ const dir = ventureDir(venture, workspace);
244
+ if (!r || r.kind !== "record" || r.collection === "list" || !dir) return null;
245
+ const file = fileFor(r, params, dir);
246
+ return { dir, file, draft: draftPath(file), gate: gateOf(r, dir), route: r.collection === "item" ? r.route.replace("[id]", params.id) : r.route };
247
+ }
248
+
249
+ function agentsChain(dir, route) {
250
+ if (!dir) return [];
251
+ const chain = [];
252
+ const add = (rel) => { const f = join(dir, rel); if (existsSync(f)) chain.push({ path: rel, text: readFileSync(f, "utf8") }); };
253
+ add("AGENTS.md");
254
+ const parts = route.split("/").filter(Boolean);
255
+ let acc = "";
256
+ for (const p of parts) { acc = acc ? `${acc}/${p}` : p; add(`${acc}/AGENTS.md`); }
257
+ return chain;
258
+ }
259
+ function instructionsOf(chain) {
260
+ const out = {};
261
+ for (const file of chain) for (const line of file.text.split("\n")) {
262
+ const m = line.match(/^([a-z][a-z0-9_-]*)\s*:\s*(.+)$/);
263
+ if (m) out[m[1]] = m[2].trim();
264
+ }
265
+ return out;
266
+ }
267
+
268
+ /** A score below its threshold is uncertain: not an error and not an answer. It routes to the function owner. */
269
+ export function judge(score, threshold, owner) {
270
+ if (typeof score !== "number" || score < threshold) return { outcome: "uncertain", owner, result: null };
271
+ return { outcome: "answer", result: score };
272
+ }
273
+
274
+ /** Runs fn. A throw is the uncaught outcome. */
275
+ export function capture(fn) {
276
+ try { fn(); return { outcome: "answer" }; }
277
+ catch (e) { return { outcome: "uncaught", error: String(e.message ?? e) }; }
278
+ }
279
+
280
+ /** The layout wrapping a route: system layer, the record's layer, and its package when one owns it.
281
+ * chain is the AGENTS.md files from the venture root down to the route, nearest last. */
282
+ export function layout(r, dir) {
283
+ const ks = kits();
284
+ const owned = r.kit_path && (r.kit_path.startsWith("packages/") || r.kit_path.includes("/kits/")) ? ks.find((k) => k.path === r.kit_path) : null;
285
+ const stack = [ks.find((k) => k.path === "schema/layer.json"), ks.find((k) => k.path === `${r.layer ?? r.route.split("/")[0]}/layer.json`), owned]
286
+ .filter(Boolean).map((k) => {
287
+ const refs = (k.references && existsSync(join(repo, k.references)) ? k.references : null)
288
+ ?? [join(dirname(k.path), "references/references.json"), join(dirname(k.path), "references")].find((p) => existsSync(join(repo, p)));
289
+ return { name: k.name, kit: k.name, path: k.path, purpose: k.purpose, skills: skillsOf(k).map((s) => s.path), references: refs ?? null, gaps: k.gaps ?? [] };
290
+ });
291
+ const chain = agentsChain(dir, r.route);
292
+ const pkg = owned ? { skill: skillsOf(owned).map((s) => s.path), template: r.template ?? null, gates: owned.gates ?? [] } : null;
293
+ const out = { route: r.route, stack, chain, instructions: instructionsOf(chain), package: pkg, note: r.note, groups: r.groups ?? [] };
294
+ if (r.kind === "record") Object.assign(out, { schema: r.schema, template: r.template, checks: r.checks, gate: gateOf(r, dir) });
295
+ if (dir && r.kind === "record") {
296
+ try {
297
+ const nn = JSON.parse(spawnSync("node", [join(repo, "schema/need-now.mjs"), dir, "--json"], { encoding: "utf8" }).stdout);
298
+ const row = nn.rows.find((x) => x.type === r.record);
299
+ if (row) out.need_now = { phase: nn.phase, status: row.status, now: row.now, next: row.next, why: row.why };
300
+ } catch {}
301
+ }
302
+ return out;
303
+ }
304
+
305
+ const fix = (r) => ({ skills: r.kit_path ? skillsOf(kits().find((k) => k.path === r.kit_path) ?? {}).map((s) => s.path) : [], template: r.template ?? null, schema: r.schema ?? null });
306
+
307
+ /**
308
+ * Runs one action on one route for one venture. Returns { ok, result } or { ok: false, error }.
309
+ * input: the record to write (write), or { value } for payouts; actor: "person" or "agent" (default
310
+ * agent); actor_id: the role acting (default MENTURA_ACTOR_ID), checked against the team plan's
311
+ * routes; via: the interface (cli, mcp, api; default router); provenance: { derived_from,
312
+ * decision, note, agent } for the log.
313
+ */
314
+ export async function call({ venture, route, action, input, actor = "agent", actor_id = process.env.MENTURA_ACTOR_ID, workspace, via = "router", provenance = {} }) {
315
+ const requested = route;
316
+ const { r, params, alias } = resolve(route);
317
+ if (!r) return { ok: false, outcome: "error", error: `no route ${route}` };
318
+ const deprecated = alias ? { deprecated: `${alias.from} is now ${alias.to}${alias.note ? `: ${alias.note}` : ""}` } : {};
319
+ route = r.collection === "item" ? r.route.replace("[id]", params.id) : r.route;
320
+ if (!r.actions.includes(action)) return { ok: false, error: `${route} has no action ${action} (it has ${r.actions.join(", ")})` };
321
+ if (action === "template") return { ok: true, result: readFileSync(join(repo, r.template), "utf8"), ...deprecated };
322
+ const dir = venture ? ventureDir(venture, workspace) : null;
323
+ if (action === "layout") { if (venture && !dir) return { ok: false, error: `no venture ${venture}` }; return { ok: true, result: layout(r, dir), ...deprecated }; }
324
+ if (!dir) return { ok: false, error: `no venture ${venture}` };
325
+ const act = action === "write" ? "write" : "read";
326
+ if (!allowed(dir, actor_id, act, requested) && !allowed(dir, actor_id, act, route)) return { ok: false, error: `${actor_id} may not ${act} ${route} (team plan routes)` };
327
+ // An activity a decision unlocks stays closed to agents until a person makes that decision (EE-058).
328
+ const lock = actor !== "person" && (action === "write" || action === "run") ? (locked(dir, requested, actor_id) || locked(dir, route, actor_id)) : null;
329
+ if (lock) return { ok: false, error: `${route} is locked until decision ${lock.id} is made: ${lock.question}`, locked_by: lock.id,
330
+ fix: { who: "a person, not an agent", command: `mentura decide ${lock.id} --venture ${venture} --by <their name> [--answer "<their wording>"]`, then: "retry this call" } };
331
+
332
+ if (action === "view") {
333
+ const inner = await call({ venture, route, action: r.kind === "command" ? "run" : "read", input, actor, actor_id, workspace, via, provenance });
334
+ return inner.ok ? { ok: true, result: render(route, inner), ...deprecated } : inner;
335
+ }
336
+
337
+ if (r.kind === "command") {
338
+ if (r.internal === "overview") return { ok: true, result: await overview(venture, workspace), ...deprecated };
339
+ if (r.internal === "robots") return { ok: true, result: robots(dir), ...deprecated };
340
+ const lacking = (r.needs ?? []).filter((f) => !existsSync(join(dir, f)));
341
+ if (lacking.length) return { ok: false, error: `${route} needs ${lacking.join(", ")} in ${venture}`, fix: { missing: lacking } };
342
+ if (r.value_flag && !input?.value) return { ok: false, error: `${route} needs a value (${r.note})` };
343
+ const args = r.venture_arg === "id" ? [dir, ...(input?.value ? [String(input.value)] : []), "--json"] : r.value_flag ? [dir, r.value_flag, String(input.value), "--json"] : [dir, ...(r.args ?? []), ...(r.as_is && input?.as_is ? ["--as-is"] : []), "--json"];
344
+ const p = spawnSync("node", [join(repo, r.script), ...args], { encoding: "utf8" });
345
+ if (p.status !== 0) return { ok: false, error: (p.stderr || p.stdout).trim() };
346
+ // A command that needs input first (EE-050) answers needs_input: the caller asks the person
347
+ // whether to fill the record in or run as is, and runs again with input as_is to go on.
348
+ try { const out = JSON.parse(p.stdout); return { ok: true, result: out.needs_input ? null : out, ...(out.needs_input ? { needs_input: out.needs_input } : {}), ...deprecated }; } catch { return { ok: true, result: p.stdout, ...deprecated }; }
349
+ }
350
+
351
+ // Storage: the connector for the source this record type lives in.
352
+ let src, store, attrs = [];
353
+ if (r.kind === "record" && (action === "read" || action === "write")) {
354
+ const context = { actor, ...(r.collection === "item" && (r.record === "product" || r.record === "account") ? { [r.record]: params.id } : {}) };
355
+ try {
356
+ src = sourceFor(dir, r.record, context); store = await connect(src, dir);
357
+ // Attributes mastered elsewhere (EE-042): read from and written to their own system of record.
358
+ attrs = r.files.every((f) => f.endsWith(".json")) ? await Promise.all(attributeSources(dir, r.record, context).map(async (a) => ({ ...a, store: await connect(a.source, dir) }))) : [];
359
+ } catch (e) { return { ok: false, error: String(e.message ?? e) }; }
360
+ }
361
+ const remote = src && src.id !== LOCAL;
362
+ const cite = (x) => ({ source: src.id, address: address(src.id, x.path), version: x.version, ...(x.commit ? { commit: x.commit } : {}), ...(x.permalink ? { permalink: x.permalink } : {}), ...(x.attributes ? { attributes: x.attributes } : {}) });
363
+
364
+ if (action === "read") {
365
+ const patterns = r.collection === "item" ? [fileFor(r, params, dir)] : r.files;
366
+ let items;
367
+ try {
368
+ const paths = patterns.some((g) => g.includes("*")) ? (await store.list()).map((x) => x.path).filter((f) => patterns.some((g) => glob(g).test(f))) : patterns;
369
+ items = (await Promise.all(paths.map((f) => store.read(f)))).filter(Boolean);
370
+ } catch (e) { return { ok: false, error: `${src.id}: ${String(e.message ?? e)}` }; }
371
+ const found = items.map((x) => x.path);
372
+ const draft = !remote && r.collection !== "list" && existsSync(join(dir, draftPath(fileFor(r, params, dir)))) ? draftPath(fileFor(r, params, dir)) : null;
373
+ const loading = draft ? { pending_draft: { file: draft, since: provEntries(dir).filter((e) => e.entity === draft).at(-1)?.at ?? null, accept: `mentura accept ${route} --venture ${venture}` } } : {};
374
+ if (remote) {
375
+ const prs = provEntries(dir).filter((e) => e.source?.pull_request && found.concat(patterns).some((f) => e.entity === address(src.id, f)));
376
+ if (prs.length) loading.pending_pull_requests = prs.map((e) => ({ entity: e.entity, pull_request: e.source.pull_request, since: e.at }));
377
+ }
378
+ if (!found.length) {
379
+ const l = layout(r, dir);
380
+ return { ok: true, result: null, not_found: { note: r.collection === "list" ? `no ${r.record} records yet` : `no ${r.record} record yet`, ...(remote ? { source: src.id } : {}), template: r.template, schema: r.schema, need_now: l.need_now ?? null, skills: fix(r).skills }, ...loading, ...deprecated };
381
+ }
382
+ const parse = (x) => x.path.endsWith(".json") ? JSON.parse(x.content) : x.path.endsWith(".jsonl") ? x.content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l)) : x.content;
383
+ // Overlay each attribute mastered elsewhere with the value its system of record holds, and
384
+ // cite where it came from. A source that holds nothing for it leaves the record's own value.
385
+ const overlays = new Map();
386
+ for (const x of items) {
387
+ const cites = {};
388
+ for (const a of attrs) {
389
+ let got;
390
+ try { got = await a.store.read(x.path); } catch (e) { return { ok: false, error: `${a.source.id}: ${String(e.message ?? e)}` }; }
391
+ const value = got ? JSON.parse(got.content)[a.attribute] : undefined;
392
+ cites[a.attribute] = value === undefined ? { source: a.source.id, missing: true } : { source: a.source.id, address: address(a.source.id, x.path), version: got.version };
393
+ if (value !== undefined) overlays.set(x.path, { ...(overlays.get(x.path) ?? {}), [a.attribute]: value });
394
+ }
395
+ if (attrs.length) x.attributes = cites;
396
+ }
397
+ const parseOne = parse;
398
+ const parseWith = (x) => { const v = parseOne(x); return overlays.has(x.path) ? { ...v, ...overlays.get(x.path) } : v; };
399
+ if (r.collection === "list") {
400
+ const idOf = (path) => {
401
+ for (const g of r.files) {
402
+ if (!g.includes("*")) continue;
403
+ const re = new RegExp("^" + g.split("*").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&")).join("([^/]*)") + "$");
404
+ const m = path.match(re);
405
+ if (m) return m[1];
406
+ }
407
+ return path;
408
+ };
409
+ return { ok: true, result: Object.fromEntries(items.map((x) => [idOf(x.path), parseWith(x)])), sources: Object.fromEntries(items.map((x) => [idOf(x.path), cite(x)])), ...deprecated };
410
+ }
411
+ const data = Object.fromEntries(items.map((x) => [x.path, parseWith(x)]));
412
+ return { ok: true, result: items.length === 1 ? data[found[0]] : data, ...(items.length === 1 ? { source: cite(items[0]) } : { sources: Object.fromEntries(items.map((x) => [x.path, cite(x)])) }), ...loading, ...deprecated };
413
+ }
414
+
415
+ if (action === "check") {
416
+ const outs = r.checks.map((c) => ({ check: c, ...spawnSync("node", [join(repo, c), dir], { encoding: "utf8" }) }));
417
+ const ok = outs.every((o) => o.status === 0);
418
+ return { ok, result: outs.map((o) => ({ check: o.check, passed: o.status === 0, output: (o.stdout + o.stderr).trim() })), ...(ok ? {} : { fix: fix(r) }), ...deprecated };
419
+ }
420
+
421
+ if (action === "write") {
422
+ if (!input || typeof input !== "object") return { ok: false, error: "write needs the record as input" };
423
+ if (r.collection === "item" && !/^[a-z0-9][a-z0-9-]*$/.test(params.id)) return { ok: false, error: `${params.id} is not a valid id (lowercase letters, digits and hyphens)` };
424
+ const file = fileFor(r, params, dir);
425
+ if (["idea-dump", "raw-source", "raw-text"].includes(r.record) && existsSync(join(dir, file))) return { ok: false, error: `${route} is raw and immutable: add a new capture, do not edit this one` };
426
+ const gate = gateOf(r, dir);
427
+ const text = JSON.stringify(input, null, 2) + "\n";
428
+ // The schema first, on a scratch copy, so nothing reaches any source unless it matches.
429
+ const scratch = mkdtempSync(join(tmpdir(), "mentura-write-"));
430
+ const tmp = join(scratch, basename(file));
431
+ writeFileSync(tmp, text);
432
+ const v = spawnSync("python3", [join(repo, "schema/validate.py"), "--schema", r.schema.replace(/^schema\//, ""), "--file", tmp], { encoding: "utf8" });
433
+ rmSync(scratch, { recursive: true, force: true });
434
+ if (v.status !== 0) return { ok: false, error: `does not match ${r.schema}:\n${(v.stdout + v.stderr).trim()}`, fix: fix(r) };
435
+ const agent = agentFromEnv(actor === "person" ? "person" : "agent", via, { ...(actor_id && actor !== "person" ? { id: actor_id } : {}), ...provenance.agent });
436
+ const log = (entity, activity, extra = {}) => prov(dir, { entity, route, activity, agent, derived_from: provenance.derived_from, decision: provenance.decision ?? gate?.class ?? undefined, note: provenance.note, ...extra });
437
+
438
+ // Attributes mastered elsewhere (EE-042) go to their own system of record, each as a JSON
439
+ // object holding only that attribute at the record's path, and leave the record's own file, so
440
+ // the fact lives in one place. The whole record was checked against its schema above. A gated
441
+ // agent write stays whole, as a draft, until a person accepts it.
442
+ const split = attrs.filter((a) => a.attribute in input);
443
+ // A decision record that is open and fails closed, or waits on one that is, holds the record
444
+ // (EE-054): an agent's write becomes a draft, as for a gated record.
445
+ const held = !remote && actor !== "person" ? holding(dir, file) : null;
446
+ // The venture's own fail-closed routes (naive.config.json, the gate, EE-073) hold an agent's write too.
447
+ const configured = !remote ? hold(configOf(dir), r.route, actor) : null;
448
+ const gatedDraft = !remote && (!!gate?.drafts || !!held || !!configured) && actor !== "person";
449
+ const attributeWrites = [];
450
+ if (split.length && !gatedDraft) {
451
+ for (const a of split) {
452
+ if (a.store.writes === "none") return { ok: false, error: `${r.record}.${a.attribute} is mastered in ${a.source.id}, which is read-only` };
453
+ }
454
+ for (const a of split) {
455
+ const part = JSON.stringify({ [a.attribute]: input[a.attribute] }, null, 2) + "\n";
456
+ let w;
457
+ try { w = await a.store.write(file, part, { message: `${route}.${a.attribute}: ${provenance.note ?? "written through Mentura"}`, title: `Mentura: ${route}.${a.attribute} (${venture})` }); }
458
+ catch (e) { return { ok: false, error: `${a.source.id}: ${String(e.message ?? e)}` }; }
459
+ const at = address(a.source.id, file);
460
+ log(`${at}#${a.attribute}`, a.store.writes === "pull-request" ? "draft" : provenance.activity ?? "write", { sha256: createHash("sha256").update(part).digest("hex"),
461
+ source: { id: a.source.id, connector: a.source.connector, version: w.version, ...(w.pull_request ? { pull_request: w.pull_request.url } : {}) } });
462
+ attributeWrites.push({ attribute: a.attribute, written: at, version: w.version, ...(w.pull_request ? { pull_request: w.pull_request.url } : {}) });
463
+ }
464
+ input = Object.fromEntries(Object.entries(input).filter(([k]) => !split.some((a) => a.attribute === k)));
465
+ }
466
+ const attributesOut = attributeWrites.length ? { attributes: attributeWrites } : {};
467
+ const recordText = attributeWrites.length ? JSON.stringify(input, null, 2) + "\n" : text;
468
+
469
+ if (remote) {
470
+ // Another source: its connector decides how a write lands. A pull-request source opens a
471
+ // draft pull request, which a person accepts by merging, so it is a draft for anyone.
472
+ if (store.writes === "none") return { ok: false, error: `${route} lives in ${src.id}, which is read-only` };
473
+ let w;
474
+ try { w = await store.write(file, recordText, { message: `${route}: ${provenance.note ?? `written by ${actor === "person" ? "a person" : "an agent"} through Mentura`}`, title: `Mentura: ${route} (${venture})` }); }
475
+ catch (e) { return { ok: false, error: `${src.id}: ${String(e.message ?? e)}` }; }
476
+ const draft = store.writes === "pull-request";
477
+ const at = address(src.id, file);
478
+ const entry = log(at, draft ? "draft" : provenance.activity ?? "write", { sha256: createHash("sha256").update(recordText).digest("hex"),
479
+ source: { id: src.id, connector: src.connector, version: w.version, ...(w.commit ? { commit: w.commit } : {}), ...(w.pull_request ? { pull_request: w.pull_request.url } : {}) } });
480
+ return { ok: true, result: { written: at, draft, gate: gate?.class ?? null, provenance: entry.hash, version: w.version, ...(w.pull_request ? { pull_request: w.pull_request.url } : {}),
481
+ note: draft ? `opened draft pull request ${w.pull_request.url}; merging it accepts the write` : "written", ...attributesOut }, ...deprecated };
482
+ }
483
+
484
+ const draft = gatedDraft;
485
+ if (draft) {
486
+ const dest = join(dir, draftPath(file));
487
+ mkdirSync(dirname(dest), { recursive: true });
488
+ writeFileSync(dest, text);
489
+ } else await store.write(file, recordText);
490
+ const entry = log(draft ? draftPath(file) : file, draft ? "draft" : provenance.activity ?? "write");
491
+ return { ok: true, result: { written: draft ? draftPath(file) : file, draft, gate: gate?.class ?? null, provenance: entry.hash, note: draft ? (gate?.drafts ? `${r.record} is a ${gate.class} decision (${gate.level}, owner ${gate.owner}): written as a draft for a person to accept` : held ? `decision ${held.id} ${held.why}: written as a draft for a person to accept` : `${configured.rule} fails closed in naive.config.json: written as a draft for a person to accept`) : "written", ...(held ? { held_by: held.id } : {}), ...(configured ? { gate_rule: configured.rule } : {}), ...attributesOut }, ...deprecated };
492
+ }
493
+ return { ok: false, error: `unhandled action ${action}` };
494
+ }
495
+
496
+ // Views (pages): Markdown for people. A generic renderer: objects become headings and lists,
497
+ // arrays of flat objects become tables.
498
+ function md(x, depth = 2) {
499
+ if (x === null || x === undefined) return "_none_";
500
+ if (typeof x !== "object") return String(x);
501
+ if (Array.isArray(x)) {
502
+ if (!x.length) return "_none_";
503
+ const flat = x.every((o) => o && typeof o === "object" && !Array.isArray(o) && Object.values(o).every((v) => v === null || typeof v !== "object"));
504
+ if (flat) {
505
+ const cols = [...new Set(x.flatMap((o) => Object.keys(o)))];
506
+ return [`| ${cols.join(" | ")} |`, `| ${cols.map(() => "---").join(" | ")} |`, ...x.map((o) => `| ${cols.map((c) => String(o[c] ?? "").replace(/\|/g, "/").replace(/\n/g, " ")).join(" | ")} |`)].join("\n");
507
+ }
508
+ return x.every((v) => v === null || typeof v !== "object") ? x.map((v) => `- ${v}`).join("\n") : x.map((v) => typeof v === "object" ? md(v, depth + 1) : `- ${v}`).join("\n\n");
509
+ }
510
+ const blocks = [];
511
+ for (const [k, v] of Object.entries(x)) {
512
+ if (v !== null && typeof v === "object") blocks.push(`${"#".repeat(Math.min(depth, 6))} ${k}\n\n${md(v, depth + 1)}`);
513
+ else if (blocks.length && blocks.at(-1).startsWith("- **")) blocks[blocks.length - 1] += `\n- **${k}:** ${v}`;
514
+ else blocks.push(`- **${k}:** ${v}`);
515
+ }
516
+ return blocks.join("\n\n");
517
+ }
518
+
519
+ function render(route, res) {
520
+ if (res.result?.slots) return [`# ${res.result.venture}: overview`, ...res.result.slots.map((s) => `## ${s.slot}\n\n${s.error ? `_could not render: ${s.error}_` : md(s.data, 3)}`)].join("\n\n");
521
+ const head = `# ${route}`;
522
+ if (res.result === null && res.not_found) return `${head}\n\n${res.not_found.note}.${res.not_found.template ? ` Start from \`${res.not_found.template}\`.` : ""}${res.not_found.need_now ? ` Need-now: ${res.not_found.need_now.status} (${res.not_found.need_now.now}).` : ""}`;
523
+ return `${head}\n\n${res.pending_draft ? `> A draft is waiting for a person to accept: \`${res.pending_draft.accept}\`\n\n` : ""}${md(res.result)}`;
524
+ }
525
+
526
+ // The overview's slots (@slot): each is computed on its own, so one failing does not stop the rest.
527
+ async function overview(venture, workspace) {
528
+ const dir = ventureDir(venture, workspace);
529
+ const v = load(join(dir, "venture.json"));
530
+ const slot = async (name, f) => { try { return { slot: name, data: await f() }; } catch (e) { return { slot: name, error: String(e.message ?? e).split("\n")[0] }; } };
531
+ const run = async (route) => { const r = await call({ venture, route, action: "run", workspace }); if (!r.ok) throw new Error(r.error); return r.result; };
532
+ const read = async (route) => { const r = await call({ venture, route, action: "read", workspace }); if (!r.ok) throw new Error(r.error); return r.result ?? r.not_found?.note; };
533
+ return {
534
+ venture: v.id,
535
+ slots: await Promise.all([
536
+ slot("@venture", () => ({ name: v.name, phase: v.phase, ambition: v.ambition ?? null, status: v.status, promise: v.core?.promise ?? null })),
537
+ slot("@telos", async () => { const t = await read("purpose/telos"); return typeof t === "string" ? t : { end: t.end ?? t.statement ?? null, phases: (t.phases ?? []).map((p) => p.phase) }; }),
538
+ slot("@validation", async () => { const r = await call({ venture, route: "purpose/validation/assess", action: "run", input: { as_is: true }, workspace }); if (!r.ok) throw new Error(r.error); const a = r.result; return { supports: a.supports, next: a.next, assumptions: a.assumptions.map((x) => `${x.assumption}: ${x.label}`).join(", "), missing: a.missing.length }; }),
539
+ slot("@need-now", async () => (await run("system/need-now")).rows.filter((x) => x.status === "suggest now").map((x) => ({ record: x.type, now: x.now, why: x.why }))),
540
+ slot("@ready", async () => { const t = await run("system/time-to-ready"); return { running: t.running.on ?? `not yet: ${t.running.missing.join(", ")}`, layers: t.layers.map((l) => ({ layer: l.layer, ready: l.ready_on ?? `missing ${l.missing.join(", ")}` })) }; }),
541
+ slot("@team", async () => { const p = await read("people/team-plan"); if (typeof p === "string") return p; return p.roles.map((x) => ({ role: x.id, occupant: x.occupant, status: x.status, needed_by: x.needed_by })); }),
542
+ slot("@provenance", () => provEntries(dir).slice(-5).reverse().map((e) => ({ at: e.at, activity: e.activity, entity: e.entity, by: `${e.agent.kind}${e.agent.id ? ` ${e.agent.id}` : ""} via ${e.agent.via}` }))),
543
+ ]),
544
+ };
545
+ }
546
+
547
+ // Robots: per role, the routes it may read and write.
548
+ function robots(dir) {
549
+ const plan = [join(dir, "people/team-plan.json"), join(dir, "operations/team-plan.json")].find((p) => existsSync(p));
550
+ if (!plan) return { note: "no team plan: every actor may reach every route; gates still apply", roles: [] };
551
+ return { note: "a role without routes may reach every route; gates still apply", roles: load(plan).roles.map((r) => ({ role: r.id, occupant: r.occupant, read: (r.routes?.read ?? ["**"]).join(", "), write: (r.routes?.write ?? (r.routes ? [] : ["**"])).join(", ") || "none" })) };
552
+ }
553
+
554
+ /** The route table as Markdown, for docs/reference/ROUTES.md. */
555
+ export function routesMd() {
556
+ const rows = routes().map((r) => `| \`${r.route}\` | ${r.kind}${r.collection ? ` (${r.collection})` : ""} | ${r.actions.join(", ")} | ${r.kind === "record" ? `\`${r.files.join("`, `")}\`` : r.script ? `\`${r.script}\`` : "router"} | ${r.decision_class ?? ""} | ${r.groups.join(" ")} | ${r.note.replace(/\|/g, "/")} |`);
557
+ const gs = groups().map((g) => `| \`${g.group}\` | ${g.question} | ${g.routes.length ? g.routes.map((x) => `\`${x}\``).join(", ") : "none"} |`);
558
+ return [
559
+ "## Routes (generated by `node interface/router.mjs --md`)",
560
+ "",
561
+ "Every route works on one venture, named in front of it: its id, or its folder path for a nested venture (`studio/print`). Record routes read, write, check, view, lay out or show the template of one record type; a collection has a list route and an `[id]` item route; command routes run a venture-wide tool and can be viewed. A write is checked against the record's schema first; a record whose decision class escalates is written as a draft when an agent writes it, for a person to accept.",
562
+ "",
563
+ "| Route | Kind | Actions | Files or script | Decision class | Groups | What it is |",
564
+ "| --- | --- | --- | --- | --- | --- | --- |",
565
+ ...rows,
566
+ "",
567
+ "### Route groups (generated)",
568
+ "",
569
+ "A group gathers the routes need-now suggests in a phase. It changes the layout an agent is given, not the path.",
570
+ "",
571
+ "| Group | The phase asks | Routes |",
572
+ "| --- | --- | --- |",
573
+ ...gs,
574
+ ].join("\n");
575
+ }
576
+
577
+ if (import.meta.url === `file://${process.argv[1]}`) {
578
+ if (process.argv.includes("--md")) console.log(routesMd());
579
+ else console.log(JSON.stringify(routes(), null, 2));
580
+ }