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,182 @@
1
+ #!/usr/bin/env node
2
+ // The idea graph (EE-060): the step after the idea map in the pipeline dump -> map -> graph -> roadmap.
3
+ // The dump and the map stay loose; the graph is the first structured form. It is a Merkle DAG: an
4
+ // idea builds on the ideas that must be worked out before it, and its id is a hash of its text and
5
+ // the hashes of those ideas, so a change upstream changes every id downstream, as in git. The graph
6
+ // has no decisions in it; decisions come later, from what it shows.
7
+ //
8
+ // From the graph:
9
+ // - signal: how much each idea is worth developing next (graph.json `signal`, version 0.x, ours and
10
+ // unevaluated): what the venture's phase needs of the records it feeds, how much builds on it,
11
+ // how concrete it is, and how much of the dump circles it;
12
+ // - lanes: ideas tied by dependencies form one lane and move in order (synchronous); a lane with no
13
+ // tie to another moves on its own (asynchronous) and can run beside it;
14
+ // - layout: left to right by dependency depth, lanes and ideas top down by signal.
15
+ //
16
+ // Dependencies come from Jev (one typed question per ordered pair of ideas) when the platform hands
17
+ // over its answers; otherwise from Naive's convention of which records build on which.
18
+ //
19
+ // node graph.mjs request <venture> the Jev request (pairs and concreteness)
20
+ // node graph.mjs build <venture> [<answers.json>] writes purpose/ideas/graph.json
21
+ // node graph.mjs <venture> [--json] summary of the graph
22
+
23
+ import { createHash } from "node:crypto";
24
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
25
+ import { dirname, join } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+ import { CONVENTIONS, MAP, parse } from "./ideamap.mjs";
28
+
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ const repo = join(here, "../../..");
31
+ const load = (p) => JSON.parse(readFileSync(p, "utf8"));
32
+ export const GRAPH = "purpose/ideas/graph.json";
33
+ const NEED = load(join(repo, "schema/need-now.json")).records;
34
+
35
+ // Naive's convention of which records build on which, used when no judge has answered. An idea that
36
+ // feeds a later record builds on an idea that feeds an earlier one in the same department.
37
+ export const ORDER = ["telos", "validation-problem", "icp", "venture-core", "validation-solution", "product", "validation-pricing", "validation-go-to-market", "brand", "validation-team", "alignment"];
38
+ const EXTRA_NEED = { "venture-core": { discovery: 0.8, validation: 0.9, beachhead: 0.9, scale: 0.9, platform: 0.9 }, brand: { discovery: 0.2, validation: 0.4, beachhead: 0.7, scale: 0.8, platform: 0.8 } };
39
+ export const WEIGHTS = { need: 0.35, unblocks: 0.25, concrete: 0.25, emphasis: 0.15 };
40
+ // Jev answers dependency questions conservatively (in our first runs few pairs passed 0.75), so the bar is 0.5.
41
+ const THRESHOLD = 0.5;
42
+
43
+ const CONCRETE = ["A wish or a theme: nothing anyone could act on yet", "A direction: it names what, but not how or for whom", "A plan: someone could start on it with a few questions answered", "Actionable now: someone could act on it this week as written"];
44
+
45
+ /** The Jev request: for each ordered pair, whether one idea must be worked out first; and how concrete each is. */
46
+ export function request(ideas, venture = {}) {
47
+ const questions = {};
48
+ for (const a of ideas) for (const b of ideas) if (a.id !== b.id)
49
+ questions[`dep:${a.id}>${b.id}`] = { type: "noul", instructions: `Does developing the idea in \`ideas.${b.id}\` depend on the idea in \`ideas.${a.id}\` being worked out first (it builds on it, needs its answer, or makes no sense without it)?`,
50
+ criteria: { true: "Yes: it builds directly on that idea", false: "No: it could be developed without it" } };
51
+ for (const i of ideas) questions[`concrete:${i.id}`] = { type: "score", instructions: `How concrete is the idea in \`ideas.${i.id}\`?`, criteria: CONCRETE };
52
+ return { state: { venture: { id: venture.id, phase: venture.phase }, guidelines: CONVENTIONS.guidelines.graph, ideas: Object.fromEntries(ideas.map((i) => [i.id, i.section ? `[${i.section}] ${i.text}` : i.text])) }, model: "jev-latest", questions };
53
+ }
54
+
55
+ /** Dependencies by convention: b builds on a when a feeds an earlier record and they share a department. */
56
+ export function conventionEdges(ideas) {
57
+ const rank = (i) => Math.min(...(i.records ?? []).map((r) => ORDER.indexOf(r)).filter((x) => x >= 0), 99);
58
+ const edges = [];
59
+ for (const a of ideas) for (const b of ideas) {
60
+ if (a.id === b.id || rank(a) >= rank(b) || rank(a) === 99) continue;
61
+ if ((a.departments ?? []).some((d) => (b.departments ?? []).includes(d))) edges.push({ from: a.id, to: b.id, p: 0.8, by: "convention" });
62
+ }
63
+ return edges;
64
+ }
65
+
66
+ // Keeps the graph a DAG: edges are added strongest first, and one that would close a cycle is dropped.
67
+ function acyclic(ids, edges) {
68
+ const out = new Map(ids.map((i) => [i, new Set()]));
69
+ const reaches = (from, to) => { const seen = new Set(), stack = [from]; while (stack.length) { const x = stack.pop(); if (x === to) return true; if (seen.has(x)) continue; seen.add(x); stack.push(...out.get(x)); } return false; };
70
+ const kept = [], dropped = [];
71
+ for (const e of [...edges].sort((a, b) => b.p - a.p)) {
72
+ if (reaches(e.to, e.from)) { dropped.push(e); continue; }
73
+ out.get(e.from).add(e.to); kept.push(e);
74
+ }
75
+ // Transitive reduction: a -> c goes when a -> b -> c already says it.
76
+ const reduced = kept.filter((e) => ![...out.get(e.from)].some((m) => m !== e.to && reaches(m, e.to)));
77
+ return { edges: reduced, dropped };
78
+ }
79
+
80
+ const sha = (s) => createHash("sha256").update(s).digest("hex");
81
+
82
+ /** The Merkle DAG with signal, lanes and layout. */
83
+ export function build(ideas, { answers = null, phase = "discovery", venture, source } = {}) {
84
+ const ids = ideas.map((i) => i.id);
85
+ let raw = [], concrete = {};
86
+ if (answers) {
87
+ for (const [k, a] of Object.entries(answers)) {
88
+ if (k.startsWith("dep:")) { const [from, to] = k.slice(4).split(">"); const p = typeof a.noul === "number" ? a.noul : a.noul === true ? 1 : 0; if (p >= THRESHOLD) raw.push({ from, to, p: Math.round(p * 100) / 100, by: "jev" }); }
89
+ else if (k.startsWith("concrete:")) concrete[k.slice(9)] = typeof a.score === "number" ? a.score / (CONCRETE.length - 1) : null;
90
+ }
91
+ } else raw = conventionEdges(ideas);
92
+ const { edges, dropped } = acyclic(ids, raw);
93
+ const parents = new Map(ids.map((i) => [i, edges.filter((e) => e.to === i).map((e) => e.from)]));
94
+ const children = new Map(ids.map((i) => [i, edges.filter((e) => e.from === i).map((e) => e.to)]));
95
+
96
+ // Topological order, depth (longest path from a root) and Merkle hashes.
97
+ const order = [], depth = {}, hash = {};
98
+ const indeg = new Map(ids.map((i) => [i, parents.get(i).length]));
99
+ const queue = ids.filter((i) => !indeg.get(i));
100
+ while (queue.length) { const x = queue.shift(); order.push(x); for (const c of children.get(x)) { indeg.set(c, indeg.get(c) - 1); if (!indeg.get(c)) queue.push(c); } }
101
+ const byId = Object.fromEntries(ideas.map((i) => [i.id, i]));
102
+ for (const x of order) {
103
+ depth[x] = Math.max(-1, ...parents.get(x).map((p) => depth[p])) + 1;
104
+ hash[x] = sha(JSON.stringify({ text: byId[x].text, parents: parents.get(x).map((p) => hash[p]).sort() })).slice(0, 16);
105
+ }
106
+ const descendants = (x, seen = new Set()) => { for (const c of children.get(x)) if (!seen.has(c)) { seen.add(c); descendants(c, seen); } return seen; };
107
+
108
+ // Signal (version 0.x): need of the records it feeds in this phase, what builds on it, how concrete
109
+ // it is, and how much of the dump shares its department.
110
+ const need = (r) => (NEED[r]?.base ?? EXTRA_NEED[r])?.[phase] ?? 0;
111
+ const deptCount = {};
112
+ for (const i of ideas) for (const d of i.departments ?? []) deptCount[d] = (deptCount[d] ?? 0) + 1;
113
+ const maxDesc = Math.max(1, ...ids.map((x) => descendants(x).size));
114
+ const maxDept = Math.max(1, ...Object.values(deptCount));
115
+ const nodes = ids.map((x) => {
116
+ const i = byId[x];
117
+ const parts = {
118
+ need: Math.max(0, ...(i.records ?? []).map(need)),
119
+ unblocks: descendants(x).size / maxDesc,
120
+ concrete: concrete[x] ?? (i.exists ? 1 : 0.5),
121
+ emphasis: Math.max(0, ...(i.departments ?? []).map((d) => deptCount[d] / maxDept)),
122
+ };
123
+ const signal = Object.entries(WEIGHTS).reduce((s, [k, w]) => s + w * parts[k], 0);
124
+ return { id: x, hash: hash[x], text: i.text, ...(i.section ? { section: i.section } : {}), pillar: i.pillar, departments: i.departments ?? [], records: i.records ?? [], exists: !!i.exists,
125
+ depth: depth[x], builds_on: parents.get(x), signal: Math.round(signal * 100) / 100, parts: Object.fromEntries(Object.entries(parts).map(([k, v]) => [k, Math.round(v * 100) / 100])) };
126
+ });
127
+
128
+ // Lanes: the weakly connected parts of the DAG. A lane with dependencies is synchronous inside; lanes
129
+ // are asynchronous to each other. Lanes and their ideas are ordered by signal.
130
+ const lane = {}, lanes = [];
131
+ for (const x of ids) {
132
+ if (lane[x] !== undefined) continue;
133
+ const members = [], stack = [x];
134
+ while (stack.length) { const y = stack.pop(); if (lane[y] !== undefined) continue; lane[y] = lanes.length; members.push(y); stack.push(...parents.get(y), ...children.get(y)); }
135
+ lanes.push(members);
136
+ }
137
+ const nodeOf = Object.fromEntries(nodes.map((n) => [n.id, n]));
138
+ const top = (ms) => Math.max(...ms.map((m) => nodeOf[m].signal));
139
+ const ranked = lanes.map((ms) => ms.sort((a, b) => nodeOf[a].depth - nodeOf[b].depth || nodeOf[b].signal - nodeOf[a].signal)).sort((a, b) => top(b) - top(a) || b.length - a.length);
140
+ const laneOut = ranked.map((ms, k) => {
141
+ const depts = {}; for (const m of ms) for (const d of nodeOf[m].departments.slice(0, 1)) depts[d] = (depts[d] ?? 0) + 1;
142
+ const id = `lane-${k + 1}`;
143
+ for (const m of ms) nodeOf[m].lane = id;
144
+ return { id, mode: ms.length > 1 ? "sync" : "async", ideas: ms, department: Object.entries(depts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null, signal: top(ms),
145
+ why: ms.length > 1 ? `${ms.length} ideas that build on each other: they move in order` : "stands alone: it can move on its own, beside the other lanes" };
146
+ });
147
+ const sinks = nodes.filter((n) => !children.get(n.id).length).map((n) => n.hash).sort();
148
+ return {
149
+ version: "0.1.0", venture, phase, created: new Date().toISOString().replace(/\.\d+Z$/, "Z"), source,
150
+ root: sha(sinks.join("")).slice(0, 16), judged_by: answers ? "jev" : "convention", weights: WEIGHTS,
151
+ nodes: nodes.sort((a, b) => b.signal - a.signal), edges, dropped, lanes: laneOut,
152
+ next: nodes.filter((n) => n.depth === 0).sort((a, b) => b.signal - a.signal).slice(0, 3).map((n) => n.id),
153
+ };
154
+ }
155
+
156
+ /** Which ideas changed between two graphs: a changed hash means the idea or something it builds on changed. */
157
+ export function diff(a, b) {
158
+ const A = new Map(a.nodes.map((n) => [n.id, n.hash])), B = new Map(b.nodes.map((n) => [n.id, n.hash]));
159
+ return { changed: [...B].filter(([id, h]) => A.has(id) && A.get(id) !== h).map(([id]) => id), added: [...B.keys()].filter((id) => !A.has(id)), removed: [...A.keys()].filter((id) => !B.has(id)) };
160
+ }
161
+
162
+ if (import.meta.url === `file://${process.argv[1]}`) {
163
+ const [a, b, c] = process.argv.slice(2);
164
+ const venture = (d) => load(join(d, "venture.json"));
165
+ const mapOf = (d) => parse(readFileSync(join(d, MAP), "utf8"));
166
+ if (a === "request") console.log(JSON.stringify(request(mapOf(b).ideas, venture(b))));
167
+ else if (a === "build") {
168
+ const answers = c ? (load(c).answers ?? load(c)) : null;
169
+ const g = build(mapOf(b).ideas, { answers, phase: venture(b).phase, venture: venture(b).id, source: `${MAP}@${sha(readFileSync(join(b, MAP), "utf8")).slice(0, 16)}` });
170
+ writeFileSync(join(b, GRAPH), JSON.stringify(g, null, 2) + "\n");
171
+ console.log(`wrote ${GRAPH}: ${g.nodes.length} ideas, ${g.edges.length} dependencies, ${g.lanes.filter((l) => l.mode === "sync").length} sync lanes, ${g.lanes.filter((l) => l.mode === "async").length} async; root ${g.root}`);
172
+ } else if (a && existsSync(join(a, "venture.json"))) {
173
+ if (!existsSync(join(a, GRAPH))) { console.error(`${GRAPH} does not exist yet: build it from the idea map (node graph.mjs build <venture>)`); process.exit(1); }
174
+ const g = load(join(a, GRAPH));
175
+ if (process.argv.includes("--json")) console.log(JSON.stringify(g, null, 2));
176
+ else {
177
+ console.log(`${g.nodes.length} ideas, ${g.edges.length} dependencies (${g.judged_by}), root ${g.root}`);
178
+ for (const l of g.lanes) console.log(`${l.id} ${l.mode.padEnd(5)} ${String(l.signal).padEnd(4)} ${l.department ?? ""}: ${l.ideas.join(" -> ")}`);
179
+ console.log(`Highest signal to develop next: ${g.next.join(", ")}`);
180
+ }
181
+ } else { console.error("usage: node graph.mjs request <venture> | build <venture> [<answers.json>] | <venture> [--json]"); process.exit(2); }
182
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ // The idea map (EE-059): a founder's raw idea dump, mapped onto Naive's conventions. The dump is not
3
+ // a prompt; it is every idea the founder has about the venture, kept verbatim in
4
+ // purpose/ideas/dump.md. Naive splits it into ideas, a tagger (Jev, or the founder's own agent) tags
5
+ // each with the departments it bears on, the records it feeds and whether it already exists
6
+ // (conventions.json), and the result is purpose/ideas/map.mdx: one <Idea> per idea, its text
7
+ // unchanged. The canvas (interface/demo/board.mjs, /map) draws it as a mind map; the summary says
8
+ // where the founder's ideas cluster, what the venture's phase needs that the dump never mentions,
9
+ // and what the dump says about whether a product exists.
10
+ //
11
+ // node ideamap.mjs <venture> [--json] summary of purpose/ideas/map.mdx
12
+ // node ideamap.mjs split <dump.md> the ideas Naive reads in a dump
13
+ // node ideamap.mjs request <venture> the Jev request for the venture's dump
14
+ // node ideamap.mjs answers <venture> <answers.json> Jev's answers to tags (prints tags JSON)
15
+ // node ideamap.mjs write <venture> <tags.json> --tagger <name> writes map.mdx
16
+ //
17
+ // The tagger never rewrites an idea and never adds one; tags outside conventions.json are dropped.
18
+
19
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
20
+ import { dirname, join } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ const here = dirname(fileURLToPath(import.meta.url));
24
+ const repo = join(here, "../../..");
25
+ const load = (p) => JSON.parse(readFileSync(p, "utf8"));
26
+ export const CONVENTIONS = load(join(here, "conventions.json"));
27
+ const DEPARTMENTS = load(join(repo, "operations/departments.json")).departments;
28
+ export const DUMP = "purpose/ideas/dump.md", MAP = "purpose/ideas/map.mdx";
29
+ const pillarOf = Object.fromEntries(Object.entries(CONVENTIONS.pillars).flatMap(([p, ds]) => ds.map((d) => [d, p])));
30
+
31
+ /** The ideas in a raw dump: each bullet or numbered line is one idea; any other line is its
32
+ * own idea too, unless it continues a hard-wrapped line, and a long one splits into sentences. Headings name the section that follows. */
33
+ export function split(text) {
34
+ const ideas = [];
35
+ let section = null, para = null;
36
+ const push = (t, line) => {
37
+ const clean = t.replace(/\s+/g, " ").trim();
38
+ if (!clean) return;
39
+ const parts = clean.length > 200 ? clean.match(/[^.!?]+(?:[.!?]+["')\]]*|$)\s*/g).reduce((acc, s) => {
40
+ if (acc.length && (acc.at(-1) + s).length < 160) acc[acc.length - 1] += s; else acc.push(s);
41
+ return acc;
42
+ }, []) : [clean];
43
+ for (const p of parts.map((x) => x.trim()).filter(Boolean)) ideas.push({ id: `i-${String(ideas.length + 1).padStart(2, "0")}`, line, ...(section ? { section } : {}), text: p });
44
+ };
45
+ const flush = () => { if (para) push(para.text, para.line); para = null; };
46
+ text.split(/\r?\n/).forEach((raw, i) => {
47
+ const line = raw.trim();
48
+ if (!line) return flush();
49
+ const h = line.match(/^#{1,6}\s+(.*)$/);
50
+ if (h) { flush(); section = h[1].trim(); return; }
51
+ const b = line.match(/^(?:[-*+•]|\d+[.)])\s+(.*)$/);
52
+ if (b) { flush(); push(b[1], i + 1); return; }
53
+ // A line is its own idea unless it continues a hard-wrapped one: the line before ran long with no
54
+ // closing punctuation and this one starts in lower case.
55
+ if (para && para.last.length > 60 && !/[.!?:;)"']$/.test(para.last) && /^[a-z]/.test(line)) { para.text += ` ${line}`; para.last = line; return; }
56
+ flush(); para = { text: line, line: i + 1, last: line };
57
+ });
58
+ flush();
59
+ return ideas;
60
+ }
61
+
62
+ /** One typed request for Jev: for each idea, a yes-no-unknown judgment per department, per record,
63
+ * and whether it already exists. Jev answers each with a probability. */
64
+ export function request(ideas, venture = {}) {
65
+ const questions = {};
66
+ const ask = (key, id, about) => { questions[`${key}:${id}`] = { type: "noul", instructions: `Is the idea in \`ideas.${id}\` mainly about ${about}?`, criteria: { true: "Yes: this is one of the one or two things the idea is mainly about", false: "No: it is about this only in passing, or not at all" } }; };
67
+ for (const i of ideas) {
68
+ for (const [d, about] of Object.entries(CONVENTIONS.departments)) ask(`dept.${d}`, i.id, about);
69
+ for (const [r, { about }] of Object.entries(CONVENTIONS.records)) ask(`rec.${r}`, i.id, about);
70
+ questions[`exists:${i.id}`] = { type: "noul", instructions: `Does the idea in \`ideas.${i.id}\` say that ${CONVENTIONS.exists}?`, criteria: { true: "Yes: it says this exists now", false: "No: it is planned, wished for or unclear" } };
71
+ }
72
+ return { state: { venture: { id: venture.id, phase: venture.phase }, guidelines: { dump: CONVENTIONS.guidelines.dump, tags: CONVENTIONS.guidelines.tags }, ideas: Object.fromEntries(ideas.map((i) => [i.id, i.section ? `[${i.section}] ${i.text}` : i.text])) }, model: "jev-latest", questions };
73
+ }
74
+
75
+ /** Jev's answers as tags: every judgment at or above the threshold, with the weakest one kept as confidence. */
76
+ export function fromAnswers(answers, threshold = CONVENTIONS.threshold) {
77
+ const tags = {};
78
+ for (const [key, a] of Object.entries(answers)) {
79
+ const [q, id] = key.split(":");
80
+ const p = typeof a.noul === "number" ? a.noul : a.noul === true ? 1 : 0;
81
+ const t = (tags[id] ??= { departments: [], records: [], exists: false, scores: {} });
82
+ t.scores[q] = Math.round(p * 100) / 100;
83
+ if (p < threshold) continue;
84
+ if (q.startsWith("dept.")) t.departments.push(q.slice(5));
85
+ else if (q.startsWith("rec.")) t.records.push(q.slice(4));
86
+ else if (q === "exists") t.exists = true;
87
+ }
88
+ for (const t of Object.values(tags)) {
89
+ const strongest = (prefix) => Object.entries(t.scores).filter(([k]) => k.startsWith(prefix)).sort((a, b) => b[1] - a[1]);
90
+ // An idea always sits somewhere on the map: below the threshold it takes its likeliest department.
91
+ if (!t.departments.length) { const [k] = strongest("dept.")[0] ?? []; if (k) t.departments.push(k.slice(5)), t.tentative = true; }
92
+ t.departments = t.departments.sort((a, b) => t.scores[`dept.${b}`] - t.scores[`dept.${a}`]).slice(0, CONVENTIONS.max.departments);
93
+ t.records = t.records.sort((a, b) => t.scores[`rec.${b}`] - t.scores[`rec.${a}`]).slice(0, CONVENTIONS.max.records);
94
+ }
95
+ return tags;
96
+ }
97
+
98
+ /** Tags limited to the conventions, with each idea's pillar from its first department. */
99
+ export function clean(tags) {
100
+ const out = {};
101
+ for (const [id, t] of Object.entries(tags ?? {})) {
102
+ const departments = (t.departments ?? []).filter((d) => d in CONVENTIONS.departments);
103
+ const records = (t.records ?? []).filter((r) => r in CONVENTIONS.records);
104
+ out[id] = { departments, records, exists: t.exists === true, pillar: pillarOf[departments[0]] ?? null, ...(t.tentative ? { tentative: true } : {}) };
105
+ }
106
+ return out;
107
+ }
108
+
109
+ const attr = (v) => typeof v === "string" ? JSON.stringify(v) : `{${JSON.stringify(v)}}`;
110
+ const esc = (s) => s.replace(/[{}<>]/g, (c) => ({ "{": "&#123;", "}": "&#125;", "<": "&lt;", ">": "&gt;" }[c]));
111
+ const unesc = (s) => s.replace(/&#123;|&#125;|&lt;|&gt;/g, (c) => ({ "&#123;": "{", "&#125;": "}", "&lt;": "<", "&gt;": ">" }[c]));
112
+
113
+ /** map.mdx: frontmatter, then one <Idea> per idea, text verbatim, tags as props. */
114
+ export function render(ideas, tags, { venture, tagger, created = new Date().toISOString().replace(/\.\d+Z$/, "Z") }) {
115
+ const t = clean(tags);
116
+ const head = ["---", "type: idea-map", `venture: ${venture}`, `created: ${created}`, `source: ${DUMP}`, `tagged_by: ${tagger}`, `conventions: purpose/frameworks/idea-map/conventions.json@${CONVENTIONS.version}`, "---", "",
117
+ `{/* Generated by Naive from ${DUMP}. Each idea is the founder's text, unchanged; the props are tags from Naive's conventions. */}`, ""];
118
+ const body = ideas.map((i) => {
119
+ const x = t[i.id] ?? { departments: [], records: [], exists: false, pillar: null };
120
+ const props = { id: i.id, line: i.line, ...(i.section ? { section: i.section } : {}), pillar: x.pillar ?? "unmapped", departments: x.departments, records: x.records, exists: x.exists, ...(x.tentative ? { tentative: true } : {}) };
121
+ return `<Idea ${Object.entries(props).map(([k, v]) => `${k}=${attr(v)}`).join(" ")}>\n${esc(i.text)}\n</Idea>\n`;
122
+ });
123
+ return head.join("\n") + body.join("\n");
124
+ }
125
+
126
+ /** The ideas and their tags back out of map.mdx. */
127
+ export function parse(mdx) {
128
+ const meta = Object.fromEntries((mdx.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "").split("\n").map((l) => l.split(/:\s(.*)/s).slice(0, 2)).filter((x) => x.length === 2));
129
+ const ideas = [...mdx.matchAll(/<Idea ([^>]*)>\n([\s\S]*?)\n<\/Idea>/g)].map(([, props, text]) => {
130
+ const o = { text: unesc(text) };
131
+ for (const [, k, s, j] of props.matchAll(/(\w+)=(?:("(?:[^"\\]|\\.)*")|\{([^}]*)\})/g)) o[k] = JSON.parse(s ?? j);
132
+ return o;
133
+ });
134
+ return { meta, ideas };
135
+ }
136
+
137
+ /** Where the ideas cluster, what the phase needs that the dump never mentions, and what it says about products. */
138
+ export function summary(map, phase = "discovery") {
139
+ const { ideas } = map;
140
+ const count = (f) => Object.fromEntries(f.map((k) => [k, 0]));
141
+ const byPillar = count(Object.keys(CONVENTIONS.pillars)), byDept = count(Object.keys(CONVENTIONS.departments)), byRecord = count(Object.keys(CONVENTIONS.records));
142
+ for (const i of ideas) {
143
+ if (i.pillar in byPillar) byPillar[i.pillar]++;
144
+ for (const d of i.departments ?? []) byDept[d]++;
145
+ for (const r of i.records ?? []) byRecord[r]++;
146
+ }
147
+ const n = ideas.length || 1;
148
+ const strengths = Object.entries(byDept).filter(([, c]) => c).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([d, c]) => ({ department: d, ideas: c, share: Math.round((c / n) * 100) / 100 }));
149
+ const prior = (d) => { const x = DEPARTMENTS.find((y) => y.id === d); return x?.base?.[phase] ?? 0; };
150
+ const gaps = Object.entries(byDept).filter(([d, c]) => !c && prior(d) >= 0.4).map(([d]) => ({ department: d, need_now: prior(d), why: DEPARTMENTS.find((y) => y.id === d)?.why ?? null }));
151
+ const founderRecords = ["validation-problem", "icp", "product", "venture-core"];
152
+ const unasked = founderRecords.filter((r) => !byRecord[r]).map((r) => ({ record: r, about: CONVENTIONS.records[r].about }));
153
+ const products = ideas.filter((i) => (i.records ?? []).includes("product"));
154
+ const existing = ideas.filter((i) => i.exists);
155
+ return { ideas: ideas.length, by_pillar: byPillar, by_department: byDept, by_record: byRecord, strengths, gaps, unasked,
156
+ product: { ideas: products.map((i) => i.id), exists: existing.map((i) => ({ id: i.id, text: i.text })), reading: existing.length ? "the dump says something already exists; confirm it in product-exists" : products.length ? "product ideas, none described as existing yet" : "no product ideas in the dump" } };
157
+ }
158
+
159
+ const ventureOf = (dir) => load(join(dir, "venture.json"));
160
+
161
+ if (import.meta.url === `file://${process.argv[1]}`) {
162
+ const [a, b, c] = process.argv.slice(2);
163
+ const opt = (n) => { const i = process.argv.indexOf(`--${n}`); return i >= 0 ? process.argv[i + 1] : undefined; };
164
+ const json = (x) => console.log(JSON.stringify(x, null, 2));
165
+ if (a === "split") json(split(readFileSync(b, "utf8")));
166
+ else if (a === "request") json(request(split(readFileSync(join(b, DUMP), "utf8")), ventureOf(b)));
167
+ else if (a === "answers") json(fromAnswers(load(c).answers ?? load(c)));
168
+ else if (a === "write") {
169
+ const ideas = split(readFileSync(join(b, DUMP), "utf8"));
170
+ mkdirSync(join(b, dirname(MAP)), { recursive: true });
171
+ writeFileSync(join(b, MAP), render(ideas, load(c), { venture: ventureOf(b).id, tagger: opt("tagger") ?? "unknown" }));
172
+ console.log(`wrote ${MAP}: ${ideas.length} ideas`);
173
+ } else if (a && existsSync(join(a, "venture.json"))) {
174
+ if (!existsSync(join(a, MAP))) { console.error(`${MAP} does not exist yet: the founder submits an idea dump first (node interface/harness/run.mjs discovery --dump <file>)`); process.exit(1); }
175
+ const s = summary(parse(readFileSync(join(a, MAP), "utf8")), ventureOf(a).phase);
176
+ if (process.argv.includes("--json")) json({ ...s, ideas_list: parse(readFileSync(join(a, MAP), "utf8")).ideas });
177
+ else console.log(`${s.ideas} ideas. Pillars: ${Object.entries(s.by_pillar).map(([k, v]) => `${k} ${v}`).join(", ")}.\nStrengths: ${s.strengths.map((x) => `${x.department} (${x.ideas})`).join(", ")}.\nGaps for this phase: ${s.gaps.map((x) => x.department).join(", ") || "none"}.\nNever mentioned: ${s.unasked.map((x) => x.record).join(", ") || "none"}.\nProduct: ${s.product.reading}.`);
178
+ } else { console.error("usage: node ideamap.mjs <venture> [--json] | split <dump.md> | request <venture> | answers <venture> <answers.json> | write <venture> <tags.json> --tagger <name>"); process.exit(2); }
179
+ }
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ // Idea routes (EE-061): the step after the idea graph in the pipeline dump -> map -> graph -> routes.
3
+ // The dump's payoff is seeing the ideas on one canvas; the graph's is seeing structure on them. The
4
+ // routes' payoff is next steps: each tagged idea goes to the records it feeds (the framework's
5
+ // destinations, purpose/frameworks/idea-map/conventions.json `records`), and each destination shows
6
+ // where it stands in the framework and the one step that moves it. Nothing here is invented: a
7
+ // destination's stage and next step come from the decision matrix (schema/decisions.mjs), the records
8
+ // on disk and the graph. Read as two Kanban boards:
9
+ // - destinations, by stage: routed (ideas, nothing drafted yet), drafted (an agent's draft awaits the
10
+ // founder), yours (a decision only a person can make), waiting (held by another decision), done;
11
+ // - the decision docket, the same stages for every open decision, each traced to the ideas it comes
12
+ // from (a decision record's `ideas`, or the i-NN ids its question and answer cite).
13
+ // A destination moves in order (sync) when it sits on the fail-closed path or its ideas share a lane
14
+ // that builds in order; it moves on its own (async) when its decisions fail open or its ideas stand alone.
15
+ // Each destination names its home package (schema/packages.json), and the routes say whether the
16
+ // founder has confirmed the understanding of their ideas yet: routing follows that gate (EE-069).
17
+ //
18
+ // node route.mjs <venture> [--json]
19
+
20
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { decisions } from "../../../schema/decisions.mjs";
24
+ import { CONVENTIONS, MAP, parse } from "./ideamap.mjs";
25
+ import { GRAPH, ORDER } from "./graph.mjs";
26
+ import { confirmedIn } from "../../../schema/foundation.mjs";
27
+ import { loadPackages } from "../../../schema/load-packages.mjs";
28
+
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ const load = (p) => JSON.parse(readFileSync(p, "utf8"));
31
+ const MODEL = load(join(here, "../../../schema/data-model.json")).records;
32
+
33
+ // Each destination's home package (EE-069, EE-071), so routing shows the packages taking shape.
34
+ const PKG = loadPackages(join(here, "../../.."));
35
+ const HOME = { "venture-core": "foundation", brand: "marketing", ...Object.fromEntries(Object.entries(PKG.core).flatMap(([id, p]) => [...p.required, ...p.optional].map((r) => [r, id]))) };
36
+
37
+ /** Understand is a gate (EE-069): the founder confirms Mentura read the ideas right, after any
38
+ * corrections, before they are routed. A person's confirm of the map after its last agent write. */
39
+ export function understood(dir) {
40
+ return existsSync(join(dir, MAP)) && confirmedIn(dir, MAP, "ideas");
41
+ }
42
+
43
+ export const STAGES = [["routed", "Routed"], ["drafted", "Drafted"], ["yours", "Your decision"], ["waiting", "Waiting"], ["done", "Done"]];
44
+
45
+ /** A decision's Kanban stage: what a person or an agent does next to move it. */
46
+ export function stageOf(d) {
47
+ if (["done", "withdrawn"].includes(d.render)) return "done";
48
+ if (["waiting", "later", "deferred"].includes(d.render)) return "waiting";
49
+ if (d.action?.kind === "write") return "routed";
50
+ if (d.awaiting || ["accept", "confirm"].includes(d.action?.kind)) return "drafted";
51
+ return "yours";
52
+ }
53
+
54
+ /** The ideas a decision comes from: its own `ideas`, then the ids its question and answer cite. */
55
+ export function ideasOf(d) {
56
+ return [...new Set([...(d.ideas ?? []), ...(`${d.question ?? ""} ${d.answer ?? ""}`.match(/\bi-\d{2,}\b/g) ?? [])])];
57
+ }
58
+
59
+ // Whether the record a destination writes is on disk (outside _drafts/).
60
+ function written(dir, key) {
61
+ const m = MODEL.find((r) => r.id === key);
62
+ const rel = m?.paths?.[0]?.replace(/^instance\/\*\*\//, "");
63
+ if (!rel) return null;
64
+ if (rel.includes("*")) { const d = join(dir, dirname(rel)); return existsSync(d) && readdirSync(d).some((f) => f.endsWith(".json")); }
65
+ return existsSync(join(dir, rel));
66
+ }
67
+
68
+ export function route(dir, { now } = {}) {
69
+ const venture = load(join(dir, "venture.json"));
70
+ const g = existsSync(join(dir, GRAPH)) ? load(join(dir, GRAPH)) : null;
71
+ const nodes = g?.nodes ?? (existsSync(join(dir, MAP)) ? parse(readFileSync(join(dir, MAP), "utf8")).ideas.map((i) => ({ ...i, signal: null, lane: null })) : []);
72
+ if (!nodes.length) return { venture: venture.id, phase: venture.phase, waiting: "no idea map yet: map the idea dump first (purpose/ideas)" };
73
+ const lanes = new Map((g?.lanes ?? []).map((l) => [l.id, l]));
74
+ const m = decisions(dir, now);
75
+ const ds = m.decisions.filter((d) => d.source !== "root");
76
+ const byId = new Map(ds.map((d) => [d.id, d]));
77
+ const short = (i) => ({ id: i.id, text: i.text, signal: i.signal, lane: i.lane });
78
+
79
+ const destinations = [];
80
+ for (const [key, c] of Object.entries(CONVENTIONS.records)) {
81
+ const ideas = nodes.filter((n) => n.records?.includes(key)).sort((a, b) => (b.signal ?? 0) - (a.signal ?? 0));
82
+ const related = ds.filter((d) => c.decisions?.includes(d.id) || (["file", "draft"].includes(d.source) && d.decides?.record === key));
83
+ const open = related.filter((d) => stageOf(d) !== "done");
84
+ const ranked = open.filter((d) => d.rank != null).sort((a, b) => b.rank - a.rank);
85
+ const isWritten = c.route.includes("venture.json") ? related.length > 0 && !open.length : written(dir, key);
86
+ if (!ideas.length && !related.length) continue;
87
+ // The step that moves it: the top-ranked decision a person can settle now; else what holds it;
88
+ // else, with ideas and nothing drafted, an agent's draft from those ideas.
89
+ const top = ranked.find((d) => stageOf(d) !== "routed") ?? null;
90
+ const held = !top && open.find((d) => stageOf(d) === "waiting");
91
+ const ids = ideas.slice(0, 4).map((i) => i.id);
92
+ let stage, next;
93
+ if (top) { stage = stageOf(top); next = { by: "founder", decision: top.id, do: top.question, command: top.action?.command }; }
94
+ else if (held) { const h = byId.get(held.held_by); stage = "waiting"; next = { by: null, decision: held.id, do: h ? `Waits on ${h.id}: ${h.question}` : `Waits until the ${held.needed_in ?? "next"} phase` }; }
95
+ else if (isWritten && !open.length) { stage = "done"; next = null; }
96
+ else if (ideas.length) { stage = "routed"; const inVenture = c.route.includes("venture.json");
97
+ next = { by: "agent", do: `Draft ${(c.title ?? key).toLowerCase()} (${inVenture ? `the ${c.route.split(" ")[1]} statement in venture.json` : c.route}) from ${ids.join(", ")}; the draft waits for the founder to accept`, route: inVenture ? null : c.route }; }
98
+ else { stage = stageOf(ranked[0] ?? open[0]); next = { by: "founder", decision: (ranked[0] ?? open[0]).id, do: (ranked[0] ?? open[0]).question, command: (ranked[0] ?? open[0]).action?.command }; }
99
+ const ideaLanes = [...new Set(ideas.map((i) => i.lane).filter(Boolean))].map((l) => lanes.get(l)).filter(Boolean);
100
+ const failOpen = related.length && related.every((d) => d.boundary === "fail-open");
101
+ const lane = failOpen || (ideaLanes.length && ideaLanes.every((l) => l.mode === "async")) ? "async" : "sync";
102
+ destinations.push({ id: key, title: c.title ?? key, route: c.route, stage, lane, order: lane === "sync" ? ORDER.indexOf(key) + 1 : null,
103
+ signal: ideas.length && ideas[0].signal != null ? ideas[0].signal : null, ideas: ideas.map(short), decisions: related.map((d) => ({ id: d.id, stage: stageOf(d) })), next });
104
+ }
105
+ destinations.sort((a, b) => (b.signal ?? -1) - (a.signal ?? -1) || (a.order ?? 99) - (b.order ?? 99));
106
+
107
+ // The docket: every decision in a stage, traced to its ideas, or else to its destination's ideas.
108
+ const destOf = new Map(destinations.flatMap((x) => x.decisions.map((d) => [d.id, x])));
109
+ const docket = ds.map((d) => {
110
+ const own = ideasOf(d), dest = destOf.get(d.id);
111
+ return { id: d.id, question: d.question, stage: stageOf(d), render: d.render, rank: d.rank ?? null, lane: d.lane, boundary: d.boundary, destination: dest?.id ?? null,
112
+ ideas: own.length ? own : (dest?.ideas ?? []).slice(0, 3).map((i) => i.id), cited: own.length > 0, ...(d.awaiting ? { awaiting: d.awaiting } : {}),
113
+ ...(d.held_by ? { held_by: d.held_by } : {}), ...(d.action ? { action: d.action } : {}), ...(d.answer ? { answer: d.answer } : {}) };
114
+ });
115
+
116
+ const routed = new Set(destinations.flatMap((x) => x.ideas.map((i) => i.id)));
117
+ for (const d of destinations) d.package = HOME[d.id] ?? null;
118
+ return { venture: venture.id, name: venture.name, phase: venture.phase, graph: g?.root ?? null, understood: understood(dir), stages: STAGES, destinations,
119
+ loose: nodes.filter((n) => !routed.has(n.id)).map(short),
120
+ gaps: Object.entries(CONVENTIONS.records).filter(([k]) => !destinations.some((x) => x.id === k)).map(([k, c]) => ({ id: k, title: c.title ?? k, about: c.about })),
121
+ docket, counts: Object.fromEntries(STAGES.map(([s]) => [s, destinations.filter((x) => x.stage === s).length])) };
122
+ }
123
+
124
+ if (import.meta.url === `file://${process.argv[1]}`) {
125
+ const dir = process.argv[2];
126
+ if (!dir || !existsSync(join(dir, "venture.json"))) { console.error("usage: node route.mjs <venture> [--json]"); process.exit(2); }
127
+ const r = route(dir);
128
+ if (process.argv.includes("--json")) console.log(JSON.stringify(r, null, 2));
129
+ else if (r.waiting) console.log(r.waiting);
130
+ else {
131
+ for (const [s, label] of STAGES) {
132
+ const xs = r.destinations.filter((x) => x.stage === s);
133
+ if (!xs.length) continue;
134
+ console.log(`${label}:`);
135
+ for (const x of xs) console.log(` ${x.title.padEnd(18)} ${x.lane === "sync" ? `in order #${x.order}` : "on its own"} ${x.ideas.map((i) => i.id).join(" ")}${x.next ? `\n next (${x.next.by ?? "nobody yet"}): ${x.next.do}` : ""}`);
136
+ }
137
+ if (r.loose.length) console.log(`Loose, on the map only: ${r.loose.map((i) => i.id).join(", ")}`);
138
+ if (r.gaps.length) console.log(`Nothing in the dump feeds: ${r.gaps.map((x) => x.title).join(", ")}`);
139
+ }
140
+ }