naive-framework 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/docs/decisions/decisions.json +42 -0
- package/docs/reference/ROUTES.md +36 -37
- package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +2 -1
- package/governance/layer.json +2 -1
- package/governance/venture-template/roles/venture-roles.json +62 -0
- package/interface/acting.mjs +83 -0
- package/interface/adapters/claude-code.json +16 -0
- package/interface/adapters/codex.json +14 -0
- package/interface/adapters/cursor.json +16 -0
- package/interface/adapters.mjs +183 -0
- package/interface/api/server.mjs +4 -2
- package/interface/boundaries.mjs +219 -0
- package/interface/brand.mjs +215 -0
- package/interface/cli/freshness.mjs +59 -0
- package/interface/cli/guard.mjs +6 -0
- package/interface/cli/mentura.mjs +82 -11
- package/interface/cli/naive.mjs +44 -14
- package/interface/context.mjs +525 -0
- package/interface/demo/board.mjs +9 -8
- package/interface/demo/decision-harness.mjs +4 -3
- package/interface/dev/setup.mjs +84 -0
- package/interface/gate-hooks.mjs +37 -0
- package/interface/harness/evals.mjs +10 -9
- package/interface/harness/run.mjs +16 -15
- package/interface/instrumentation.mjs +87 -0
- package/interface/kit-boundary.mjs +73 -0
- package/interface/layer.json +8 -2
- package/interface/mcp/server.mjs +47 -7
- package/interface/package-routes.mjs +197 -0
- package/interface/provenance.mjs +28 -4
- package/interface/reads.mjs +174 -0
- package/interface/release-card.mjs +43 -0
- package/interface/router.mjs +110 -36
- package/interface/skills/naive-setup/SKILL.md +71 -0
- package/interface/skills/naive-setup/artifact.html +83 -0
- package/interface/vitals.mjs +116 -0
- package/interface/writes.mjs +190 -0
- package/knowledge/connectors/catalog.mjs +3 -2
- package/knowledge/connectors/filesystem.mjs +10 -5
- package/knowledge/connectors/index.mjs +3 -2
- package/knowledge/freshness.json +85 -0
- package/knowledge/layer.json +11 -3
- package/knowledge/templates/glossary.md +23 -0
- package/operations/layer.json +10 -3
- package/operations/marketing/check-public.mjs +7 -6
- package/operations/marketing/site/plan.mjs +4 -3
- package/operations/marketing/site/presence.mjs +4 -3
- package/operations/templates/marketing-brand.md +46 -0
- package/operations/templates/public-assets.md +39 -0
- package/package.json +3 -1
- package/packages/catalog.json +8 -0
- package/packages/customers/package.json +21 -1
- package/packages/deals/package.json +27 -0
- package/packages/decisions/package.json +12 -1
- package/packages/finance/frames/fund-without-ceding-control.frame.json +177 -0
- package/packages/foundation/package.json +8 -1
- package/packages/fund/package.json +48 -0
- package/packages/marketing/package.json +10 -4
- package/packages/money/package.json +24 -3
- package/packages/validation/package.json +13 -0
- package/platform/README.md +6 -0
- package/platform/adapter.json +15 -0
- package/platform/integrations/remote-image.mjs +79 -0
- package/platform/integrations/rho.mjs +9 -7
- package/platform/kits/README.md +8 -1
- package/platform/kits/agent-upgrade/kit.json +15 -0
- package/platform/kits/agent-upgrade/run.mjs +82 -0
- package/platform/kits/agent-upgrade/stand-in-agent.mjs +32 -0
- package/platform/kits/validation-kickoff/kit.json +29 -0
- package/platform/webhooks/server.mjs +70 -0
- package/purpose/frameworks/idea-map/ideamap.mjs +4 -2
- package/purpose/frameworks/idea-map/route.mjs +5 -4
- package/purpose/frameworks/product-anatomy/anatomy.mjs +2 -1
- package/schema/README.md +1 -0
- package/schema/access.mjs +58 -0
- package/schema/adapter.schema.json +33 -0
- package/schema/add-department.mjs +3 -2
- package/schema/assess-problem.mjs +3 -2
- package/schema/assets.mjs +213 -0
- package/schema/brand.mjs +374 -0
- package/schema/build-release.mjs +61 -0
- package/schema/caching.mjs +506 -0
- package/schema/check-assets.mjs +24 -0
- package/schema/check-brand.mjs +24 -0
- package/schema/check-config.mjs +111 -0
- package/schema/check-data-model.mjs +2 -1
- package/schema/check-data-packages.mjs +5 -4
- package/schema/check-execution.mjs +4 -3
- package/schema/check-nextjs-status.mjs +3 -1
- package/schema/check-people.mjs +84 -0
- package/schema/check-private.mjs +2 -1
- package/schema/check-ready.mjs +6 -5
- package/schema/check-sources.mjs +11 -2
- package/schema/check-where-used.mjs +4 -3
- package/schema/data-model.json +334 -8
- package/schema/deal.schema.json +80 -0
- package/schema/decision-analytics.mjs +2 -1
- package/schema/decisions.mjs +21 -12
- package/schema/examples/frames/judge.mjs +10 -0
- package/schema/examples/venture-type/jev.mjs +12 -0
- package/schema/examples/workspace/ventures/acme-labs/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/acme-labs/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/discovery-goal.decision.json +23 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-icp.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-problem.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/venture.team.decision.json +37 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/sources.json +46 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000001.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000002.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000003.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000004.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000005.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000006.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000007.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000008.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000009.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000010.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000011.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000012.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000013.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000014.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_streams/transactions/stream.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_summary.json +30 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/people/founders/maya-chen.json +15 -0
- package/schema/examples/workspace/ventures/acme-labs/people/people.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.exists.decision.json +38 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/acme-labs/profile.md +12 -0
- package/schema/examples/workspace/ventures/acme-labs/provenance.jsonl +12 -0
- package/schema/examples/workspace/ventures/acme-labs/raw/dump.md +6 -0
- package/schema/examples/workspace/ventures/acme-labs/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/deals/ductline.json +8 -0
- package/schema/examples/workspace/ventures/northstar/deals/kilnworks.json +14 -0
- package/schema/examples/workspace/ventures/northstar/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/evidence/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.first-close.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/thesis.decision.json +26 -0
- package/schema/examples/workspace/ventures/northstar/fund/terms.json +22 -0
- package/schema/examples/workspace/ventures/northstar/fund/thesis.json +22 -0
- package/schema/examples/workspace/ventures/northstar/governance/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ana-ruiz.json +13 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ben-okafor.json +7 -0
- package/schema/examples/workspace/ventures/northstar/people/people.json +27 -0
- package/schema/examples/workspace/ventures/northstar/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.exists.decision.json +32 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/provenance.jsonl +3 -0
- package/schema/examples/workspace/ventures/northstar/purpose/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/venture.json +49 -0
- package/schema/foundation.mjs +8 -7
- package/schema/founder.schema.json +72 -0
- package/schema/frame.schema.json +240 -0
- package/schema/frames.mjs +229 -0
- package/schema/freshness-log.schema.json +40 -0
- package/schema/freshness-profiles.schema.json +44 -0
- package/schema/freshness.mjs +474 -0
- package/schema/fund-terms.schema.json +107 -0
- package/schema/fund-thesis.schema.json +87 -0
- package/schema/gate-drafts.mjs +131 -0
- package/schema/gate.mjs +105 -11
- package/schema/glossary.schema.json +25 -0
- package/schema/init-venture.mjs +69 -21
- package/schema/judgment.schema.json +38 -0
- package/schema/kit.schema.json +47 -0
- package/schema/kits.mjs +24 -0
- package/schema/knowledge-sources.schema.json +145 -15
- package/schema/layer.json +16 -3
- package/schema/marketing-brand.schema.json +116 -0
- package/schema/migrate-venture.mjs +8 -7
- package/schema/migrations/kit-to-package.mjs +95 -0
- package/schema/migrations/package-layout.mjs +90 -0
- package/schema/model-steps.mjs +718 -0
- package/schema/money-summary.schema.json +138 -0
- package/schema/naive-config.mjs +166 -0
- package/schema/naive-config.schema.json +268 -0
- package/schema/need-now.json +171 -0
- package/schema/need-now.mjs +8 -6
- package/schema/packages.mjs +21 -15
- package/schema/payouts.mjs +3 -2
- package/schema/people.schema.json +85 -0
- package/schema/port-venture.mjs +206 -132
- package/schema/public-assets.schema.json +76 -0
- package/schema/release-read.mjs +112 -0
- package/schema/releases.mjs +498 -0
- package/schema/run-handoff.mjs +5 -4
- package/schema/setup.mjs +165 -0
- package/schema/stale.mjs +112 -0
- package/schema/start-release.mjs +41 -0
- package/schema/structure.mjs +187 -0
- package/schema/systems-catalog.json +126 -0
- package/schema/systems-catalog.schema.json +78 -0
- package/schema/time-to-ready.mjs +7 -6
- package/schema/trigger.schema.json +44 -0
- package/schema/triggers.mjs +238 -0
- package/schema/upgrade-registry.schema.json +60 -0
- package/schema/upgrade-venture.mjs +96 -118
- package/schema/upgrades.json +69 -0
- package/schema/upgrades.mjs +386 -0
- package/schema/validate.py +19 -10
- package/schema/venture-paths.mjs +85 -0
- package/schema/venture-role.schema.json +64 -0
- package/schema/venture-type.mjs +62 -0
- package/schema/venture-types.json +346 -0
- package/schema/venture-types.schema.json +129 -0
- package/schema/venture.schema.json +61 -0
- package/schema/workspace.schema.json +5 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agent vitals (N-6): Naive's Core Web Vitals. Where Next.js measures time to first byte and time to
|
|
3
|
+
// interactive, Naive measures what it costs an agent to get work done through the router:
|
|
4
|
+
//
|
|
5
|
+
// calls_per_task router calls an agent makes in one session (a task)
|
|
6
|
+
// tokens_per_call tokens of context each call sends back
|
|
7
|
+
// time_to_useful_ms time from a session's first call to its first useful answer
|
|
8
|
+
// decision_hours time from a decision opening to a person deciding it (open ones count their age)
|
|
9
|
+
//
|
|
10
|
+
// Like Web Vitals, each is assessed at the 75th percentile and checked against a threshold (C-15),
|
|
11
|
+
// so vitals are checked, not only measured. Calls and judgments come from the events log that
|
|
12
|
+
// instrumentation writes (interface/instrumentation.mjs); decisions come from provenance.jsonl, so
|
|
13
|
+
// decision latency is measured even with instrumentation off. Thresholds are the defaults below,
|
|
14
|
+
// overridden by "vitals" in the venture's naive.config.json.
|
|
15
|
+
//
|
|
16
|
+
// node interface/vitals.mjs <venture dir> [--json] [--check] --check exits 1 when a vital fails
|
|
17
|
+
// node interface/vitals.mjs <venture dir> --trace [--session <id>] the events as a trace, for
|
|
18
|
+
// platform/integrations/langsmith.mjs
|
|
19
|
+
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { events } from "./instrumentation.mjs";
|
|
23
|
+
import { entries } from "./provenance.mjs";
|
|
24
|
+
import { ventureFile } from "../schema/venture-paths.mjs";
|
|
25
|
+
|
|
26
|
+
export const THRESHOLDS = { calls_per_task: 25, tokens_per_call: 6000, time_to_useful_ms: 10000, decision_hours: 72 };
|
|
27
|
+
const DECISION = /(^|\/)decisions\/[^/]+\.decision\.json$/;
|
|
28
|
+
|
|
29
|
+
/** The 75th percentile, nearest rank. */
|
|
30
|
+
export const p75 = (xs) => { if (!xs.length) return null; const s = [...xs].sort((a, b) => a - b); return s[Math.ceil(0.75 * s.length) - 1]; };
|
|
31
|
+
const startOf = (e) => Date.parse(e.at) - (e.ms ?? 0);
|
|
32
|
+
const config = (dir) => { const f = join(ventureFile(dir, "naive.config.json")); try { return existsSync(f) ? JSON.parse(readFileSync(f, "utf8")) : {}; } catch { return {}; } };
|
|
33
|
+
|
|
34
|
+
/** Top-level calls grouped by session: one task per session. */
|
|
35
|
+
export function tasks(dir) {
|
|
36
|
+
const calls = events(dir).filter((e) => e.kind === "call" && !e.parent);
|
|
37
|
+
const judgments = events(dir).filter((e) => e.kind === "judgment");
|
|
38
|
+
const by = new Map();
|
|
39
|
+
for (const e of calls) { const k = e.session ?? "(no session)"; if (!by.has(k)) by.set(k, []); by.get(k).push(e); }
|
|
40
|
+
return [...by.entries()].map(([session, es]) => {
|
|
41
|
+
const t0 = Math.min(...es.map(startOf));
|
|
42
|
+
const useful = es.filter((e) => e.useful).sort((a, b) => Date.parse(a.at) - Date.parse(b.at))[0];
|
|
43
|
+
return { session, calls: es.length, tokens_per_call: Math.round(es.reduce((n, e) => n + (e.tokens ?? 0), 0) / es.length), max_tokens: Math.max(...es.map((e) => e.tokens ?? 0)),
|
|
44
|
+
time_to_useful_ms: useful ? Date.parse(useful.at) - t0 : null, judgments: judgments.filter((j) => (j.session ?? "(no session)") === session).length };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Each decision the provenance log saw open: when it opened, when a person decided it, and the hours between. */
|
|
49
|
+
export function decisionLatency(dir, now = Date.now()) {
|
|
50
|
+
const by = new Map();
|
|
51
|
+
for (const e of entries(dir).filter((x) => DECISION.test(x.entity ?? ""))) {
|
|
52
|
+
const d = by.get(e.entity) ?? { file: e.entity, opened: null, decided: null };
|
|
53
|
+
if (!d.opened) d.opened = e.at;
|
|
54
|
+
if (e.activity === "accept" && !d.decided) d.decided = e.at;
|
|
55
|
+
by.set(e.entity, d);
|
|
56
|
+
}
|
|
57
|
+
return [...by.values()].map((d) => {
|
|
58
|
+
let id = d.file.split("/").at(-1).replace(/\.decision\.json$/, "");
|
|
59
|
+
try { id = JSON.parse(readFileSync(ventureFile(dir, d.file), "utf8")).id ?? id; } catch {}
|
|
60
|
+
const end = d.decided ? Date.parse(d.decided) : now;
|
|
61
|
+
return { id, ...d, open: !d.decided, hours: Math.round(((end - Date.parse(d.opened)) / 36e5) * 100) / 100 };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The venture's vitals, each at p75 against its threshold. */
|
|
66
|
+
export function vitals(dir, now = Date.now()) {
|
|
67
|
+
const limits = { ...THRESHOLDS, ...(config(dir).vitals ?? {}) };
|
|
68
|
+
const ts = tasks(dir), ds = decisionLatency(dir, now);
|
|
69
|
+
const measure = (name, xs, never = 0) => {
|
|
70
|
+
const v = p75(xs);
|
|
71
|
+
const n = xs.length + never;
|
|
72
|
+
const passed = n === 0 ? null : never > 0 && never / n > 0.25 ? false : v === null ? false : v <= limits[name];
|
|
73
|
+
return { p75: v, threshold: limits[name], n, passed, status: passed === null ? "no data" : passed ? "good" : "needs work", ...(never ? { never_useful: never } : {}) };
|
|
74
|
+
};
|
|
75
|
+
const out = {
|
|
76
|
+
calls_per_task: measure("calls_per_task", ts.map((t) => t.calls)),
|
|
77
|
+
tokens_per_call: measure("tokens_per_call", ts.map((t) => t.tokens_per_call)),
|
|
78
|
+
time_to_useful_ms: measure("time_to_useful_ms", ts.filter((t) => t.time_to_useful_ms !== null).map((t) => t.time_to_useful_ms), ts.filter((t) => t.time_to_useful_ms === null).length),
|
|
79
|
+
decision_hours: measure("decision_hours", ds.map((d) => d.hours)),
|
|
80
|
+
};
|
|
81
|
+
const failing = Object.entries(out).filter(([, v]) => v.passed === false).map(([k]) => k);
|
|
82
|
+
return { events: events(dir).length, tasks: ts, decisions: ds, vitals: out, passed: failing.length === 0, failing,
|
|
83
|
+
note: events(dir).length ? "p75 of each vital against its threshold (naive.config.json vitals overrides the defaults)" : "no events yet: turn instrumentation on in naive.config.json ({\"instrumentation\": {\"events\": true}}) to measure calls, tokens and time to a useful answer" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The events as one trace (root run per session, a child per call, nested calls under their parent), with the vitals as feedback. */
|
|
87
|
+
export function trace(dir, session) {
|
|
88
|
+
const es = events(dir).filter((e) => !session || (e.session ?? "(no session)") === session);
|
|
89
|
+
const calls = es.filter((e) => e.kind === "call");
|
|
90
|
+
const node = (e) => ({ name: `${e.action} ${e.route}`, run_type: e.action === "run" ? "tool" : "retriever", start: new Date(startOf(e)).toISOString(), end: e.at,
|
|
91
|
+
inputs: { venture: e.venture, route: e.route, action: e.action }, outputs: { ok: e.ok, useful: e.useful, tokens: e.tokens, ms: e.ms }, ...(e.ok ? {} : { error: "call failed" }),
|
|
92
|
+
metadata: { via: e.via, actor: e.actor }, children: calls.filter((c) => c.parent === e.id).map(node) });
|
|
93
|
+
const judged = es.filter((e) => e.kind === "judgment").map((e) => ({ name: `judgment ${e.question ?? e.route ?? ""}`.trim(), run_type: "llm", start: new Date(Date.parse(e.at) - (e.ms ?? 0)).toISOString(), end: e.at,
|
|
94
|
+
inputs: { question: e.question, route: e.route }, outputs: { score: e.score, threshold: e.threshold, outcome: e.outcome }, metadata: { model: e.model, owner: e.owner } }));
|
|
95
|
+
const v = vitals(dir);
|
|
96
|
+
const start = es.length ? new Date(Math.min(...es.map((e) => Date.parse(e.at) - (e.ms ?? 0)))).toISOString() : new Date().toISOString();
|
|
97
|
+
const end = es.length ? es.at(-1).at : start;
|
|
98
|
+
return { name: `naive ${JSON.parse(readFileSync(join(dir, "venture.json"), "utf8")).id}${session ? ` ${session}` : ""}`, run_type: "chain", start, end,
|
|
99
|
+
inputs: { session: session ?? null }, outputs: { calls: calls.filter((c) => !c.parent).length, judgments: judged.length },
|
|
100
|
+
children: [...calls.filter((c) => !c.parent).map(node), ...judged].sort((a, b) => a.start.localeCompare(b.start)),
|
|
101
|
+
feedback: Object.entries(v.vitals).filter(([, x]) => x.passed !== null).map(([key, x]) => ({ key, score: x.passed ? 1 : 0, comment: `p75 ${x.p75} against ${x.threshold}` })) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
105
|
+
const dir = process.argv[2];
|
|
106
|
+
if (!dir || !existsSync(join(dir, "venture.json"))) { console.error("usage: node interface/vitals.mjs <venture dir> [--json] [--check] [--trace [--session <id>]]"); process.exit(2); }
|
|
107
|
+
const arg = (n) => { const i = process.argv.indexOf(`--${n}`); return i >= 0 ? process.argv[i + 1] : undefined; };
|
|
108
|
+
if (process.argv.includes("--trace")) { console.log(JSON.stringify(trace(dir, arg("session")), null, 2)); process.exit(0); }
|
|
109
|
+
const r = vitals(dir);
|
|
110
|
+
if (process.argv.includes("--json")) console.log(JSON.stringify(r, null, 2));
|
|
111
|
+
else {
|
|
112
|
+
console.log(`${r.events} events, ${r.tasks.length} tasks, ${r.decisions.length} decisions. ${r.note}`);
|
|
113
|
+
for (const [k, x] of Object.entries(r.vitals)) console.log(` ${k.padEnd(18)} ${x.status.padEnd(10)} p75 ${x.p75 ?? "-"} (threshold ${x.threshold}, n ${x.n})`);
|
|
114
|
+
}
|
|
115
|
+
if (process.argv.includes("--check") && !r.passed) { console.error(`vitals failing: ${r.failing.join(", ")}`); process.exit(1); }
|
|
116
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// What a write answers, what a layout offers, and the wrapper the router puts around every call
|
|
2
|
+
// (Next.js translation M-3, M-9, M-17; it also wires in interface/reads.mjs, interface/boundaries.mjs
|
|
3
|
+
// and interface/package-routes.mjs).
|
|
4
|
+
//
|
|
5
|
+
// changed M-3 one round trip: a write answers with the new state and what it changed: the
|
|
6
|
+
// record's state before and after, each package whose progress or state moved,
|
|
7
|
+
// the records that move now because of it, what it unlocked (a package that
|
|
8
|
+
// completed and what that opens, a decision that no longer waits) and the
|
|
9
|
+
// need-now rows it settled
|
|
10
|
+
// next M-17 a write answers with the next route to act on: accept the draft it made, else
|
|
11
|
+
// the next record in the same package that moves now, else what it unlocked,
|
|
12
|
+
// else what need-now suggests, else the decision that matters most
|
|
13
|
+
// actions M-9 a layout lists the actions available in its folder to the actor asking: each
|
|
14
|
+
// route's actions, which of them this actor may take now, and whether a write
|
|
15
|
+
// lands as the record, as a draft, or is locked or refused, with why
|
|
16
|
+
//
|
|
17
|
+
// around() runs before and after the router's own call: the framework and venture global errors
|
|
18
|
+
// first (E-8), then package, group and venture routes (L-6, P-36, P-3), then the call, then what
|
|
19
|
+
// reads and layouts add (L-9, L-10, O-1 to O-3, P-28) and what writes answer (M-3, M-17). It never
|
|
20
|
+
// calls a model.
|
|
21
|
+
|
|
22
|
+
import { allowed, draftOf, gateOf, resolve, routes, ventureDir } from "./router.mjs";
|
|
23
|
+
import { frameworkHealth, globalError, ventureHealth } from "./boundaries.mjs";
|
|
24
|
+
import { groupLayout, packageOfRecord, routeOfType, specialCall } from "./package-routes.mjs";
|
|
25
|
+
import { filterRead, hierarchy, metadataFor, resolvedBy, withParams } from "./reads.mjs";
|
|
26
|
+
|
|
27
|
+
/** The state a write can change, taken before and after it. */
|
|
28
|
+
export async function snapshot(dir) {
|
|
29
|
+
const safe = async (f) => { try { return await f(); } catch { return null; } };
|
|
30
|
+
const { packages } = await import("../schema/packages.mjs");
|
|
31
|
+
const { needNow } = await import("../schema/need-now.mjs");
|
|
32
|
+
const { decisions } = await import("../schema/decisions.mjs");
|
|
33
|
+
return { packages: await safe(() => packages(dir)), need: await safe(() => needNow(dir)), decisions: await safe(() => decisions(dir)) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pkgMap = (s) => new Map([...(s?.packages?.core ?? []), ...(s?.packages?.handoff ?? [])].map((p) => [p.id, p]));
|
|
37
|
+
const rowOf = (s, type) => (s?.packages?.core ?? []).flatMap((p) => p.records).find((x) => x.record === type && x.role !== "reads") ?? null;
|
|
38
|
+
|
|
39
|
+
/** What a write changed (M-3), from the state before and after it. */
|
|
40
|
+
export async function changed(before, after, { record }) {
|
|
41
|
+
const b = pkgMap(before), a = pkgMap(after);
|
|
42
|
+
const packages = [...a.values()].filter((p) => { const o = b.get(p.id); return !o || o.progress !== p.progress || o.state !== p.state; })
|
|
43
|
+
.map((p) => ({ package: p.id, title: p.title, progress: { from: b.get(p.id)?.progress ?? null, to: p.progress }, state: { from: b.get(p.id)?.state ?? null, to: p.state } }));
|
|
44
|
+
const was = new Map((before?.packages?.core ?? []).flatMap((p) => p.records.map((x) => [`${p.id}:${x.record}`, x])));
|
|
45
|
+
const moves = [];
|
|
46
|
+
for (const p of after?.packages?.core ?? []) for (const x of p.records) {
|
|
47
|
+
const o = was.get(`${p.id}:${x.record}`);
|
|
48
|
+
if (x.role !== "reads" && x.moves === "now" && o?.moves === "waits" && !moves.some((m) => m.record === x.record)) moves.push({ record: x.record, package: p.id, route: await routeOfType(x.record) });
|
|
49
|
+
}
|
|
50
|
+
const unlocked = [...a.values()].filter((p) => p.state === "complete" && b.get(p.id)?.state !== "complete").map((p) => ({ package: p.id, unlocks: p.unlocks ?? [] }));
|
|
51
|
+
const dBefore = new Map((before?.decisions?.decisions ?? []).map((d) => [d.id, d.render]));
|
|
52
|
+
const freed = (after?.decisions?.decisions ?? []).filter((d) => ["waiting", "later"].includes(dBefore.get(d.id)) && ["now", "overdue"].includes(d.render)).map((d) => ({ decision: d.id, question: d.question ?? null }));
|
|
53
|
+
const nBefore = new Map((before?.need?.rows ?? []).map((x) => [x.type, x.status]));
|
|
54
|
+
const need = (after?.need?.rows ?? []).filter((x) => nBefore.has(x.type) && nBefore.get(x.type) !== x.status).map((x) => ({ record: x.type, from: nBefore.get(x.type), to: x.status }));
|
|
55
|
+
return {
|
|
56
|
+
record: { type: record, from: rowOf(before, record)?.status ?? null, to: rowOf(after, record)?.status ?? null, package: packageOfRecord(record) },
|
|
57
|
+
packages, moves_now: moves, unlocked: [...unlocked, ...freed], need_now: need,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const commandFor = (route, venture) => route?.endsWith("[id]") ? `mentura ${route.replace("[id]", "<id>")} write --venture ${venture} --input <file>` : `mentura ${route} write --venture ${venture} --input <file>`;
|
|
62
|
+
|
|
63
|
+
/** The next route to act on after a write (M-17). */
|
|
64
|
+
export async function next(after, { record, route, draft, venture, change }) {
|
|
65
|
+
if (draft) return { route, action: "accept", who: "a person", command: `mentura accept ${route} --venture ${venture}`, why: "the write is held as a draft until a person accepts it" };
|
|
66
|
+
const home = packageOfRecord(record);
|
|
67
|
+
const p = (after?.packages?.core ?? []).find((x) => x.id === home);
|
|
68
|
+
const missing = (p?.records ?? []).filter((x) => x.role !== "reads" && x.record !== record && x.moves === "now" && x.status === "missing");
|
|
69
|
+
const required = missing.find((x) => x.role === "required");
|
|
70
|
+
if (required) { const rt = await routeOfType(required.record); return { route: rt, action: "write", command: commandFor(rt, venture), why: `the next required record in ${p.title} that moves now` }; }
|
|
71
|
+
// An agent's write waits for a person before what reads it can move.
|
|
72
|
+
const review = (p?.records ?? []).filter((x) => x.role !== "reads" && x.status === "agent");
|
|
73
|
+
if (review.length) { const rt = await routeOfType(review[0].record); return { route: rt, action: "review", who: "a person", command: `mentura ${rt} read --venture ${venture}`, why: `${review[0].title} was written by an agent: a person confirms it by writing it with --as person, and what reads it moves then` }; }
|
|
74
|
+
const freed = change?.moves_now?.[0];
|
|
75
|
+
if (freed) return { route: freed.route, action: "write", command: commandFor(freed.route, venture), why: `this write let ${freed.record} move` };
|
|
76
|
+
if (missing.length) { const rt = await routeOfType(missing[0].record); return { route: rt, action: "write", command: commandFor(rt, venture), why: `the next ${missing[0].role} record in ${p.title} that moves now` }; }
|
|
77
|
+
const suggest = (after?.need?.rows ?? []).filter((x) => x.status === "suggest now" && x.type !== record).sort((a, b) => b.now - a.now)[0];
|
|
78
|
+
if (suggest) { const rt = await routeOfType(suggest.type); if (rt) return { route: rt, action: "write", command: commandFor(rt, venture), why: `need-now suggests it now: ${suggest.why}` }; }
|
|
79
|
+
const { rank } = await import("../schema/decisions.mjs");
|
|
80
|
+
const ds = after?.decisions ? rank(after.decisions.decisions, after.decisions.phase).filter((d) => d.rank != null && d.source === "file") : [];
|
|
81
|
+
if (ds.length) return { route: "governance/decisions", action: "decide", who: "a person", command: `mentura decide ${ds[0].id} --venture ${venture} --by <your name>`, why: ds[0].question };
|
|
82
|
+
return { route: null, action: null, why: "nothing waits on this write" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The actions available in a route's folder to one actor (M-9). */
|
|
86
|
+
export async function actionsIn({ dir, r, route, actor = "agent", actor_id, venture }) {
|
|
87
|
+
const { holding, locked } = await import("../schema/decisions.mjs");
|
|
88
|
+
const { configOf, hold } = await import("../schema/gate.mjs");
|
|
89
|
+
const folder = r.collection === "item" ? r.route.slice(0, -"/[id]".length) : route.includes("/") ? route.slice(0, route.lastIndexOf("/")) : route;
|
|
90
|
+
const rs = routes().filter((x) => x.route === folder || x.route.startsWith(`${folder}/`));
|
|
91
|
+
const list = [];
|
|
92
|
+
for (const x of rs) {
|
|
93
|
+
const concrete = x.route === r.route ? route : x.route;
|
|
94
|
+
const entry = { route: concrete, kind: x.kind, actions: x.actions };
|
|
95
|
+
if (!dir) { list.push(entry); continue; }
|
|
96
|
+
const reads = allowed(dir, actor_id, "read", concrete);
|
|
97
|
+
const avail = x.actions.filter((a) => !["write", "run"].includes(a) && (reads || a === "template" || a === "layout"));
|
|
98
|
+
if (x.actions.includes("run")) {
|
|
99
|
+
const lock = actor !== "person" ? locked(dir, concrete, actor_id) : null;
|
|
100
|
+
if (reads && !lock) avail.push("run");
|
|
101
|
+
entry.run = lock ? { runs: false, why: `locked until decision ${lock.id} is made` } : reads ? { runs: true } : { runs: false, why: "the team plan does not let this role reach it" };
|
|
102
|
+
}
|
|
103
|
+
if (x.actions.includes("write")) {
|
|
104
|
+
let w;
|
|
105
|
+
if (!allowed(dir, actor_id, "write", concrete)) w = { lands: null, why: `${actor_id} may not write it (team plan routes)` };
|
|
106
|
+
else {
|
|
107
|
+
const lock = actor !== "person" ? locked(dir, concrete, actor_id) : null;
|
|
108
|
+
if (lock) w = { lands: null, why: `locked until decision ${lock.id} is made: ${lock.question}` };
|
|
109
|
+
else if (actor === "person") w = { lands: "record", why: "a person's write goes straight to the record" };
|
|
110
|
+
else {
|
|
111
|
+
const gate = gateOf(x, dir);
|
|
112
|
+
const d = !concrete.includes("[id]") ? draftOf(concrete, venture, undefined) : null;
|
|
113
|
+
const held = d ? holding(dir, d.file) : null;
|
|
114
|
+
const cfg = hold(configOf(dir), x.route, actor);
|
|
115
|
+
w = gate?.drafts ? { lands: "draft", why: `${x.record} is a ${gate.class} decision (${gate.level})` } : held ? { lands: "draft", why: `decision ${held.id} ${held.why}` } : cfg ? { lands: "draft", why: `${cfg.rule} fails closed in naive.config.json` } : { lands: "record", why: "no gate holds it" };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (w.lands) avail.push("write");
|
|
119
|
+
entry.write = w;
|
|
120
|
+
}
|
|
121
|
+
entry.available = avail;
|
|
122
|
+
list.push(entry);
|
|
123
|
+
}
|
|
124
|
+
return { folder, actor: { kind: actor, ...(actor_id ? { id: actor_id } : {}) }, routes: list };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The venture-relative file behind a read's source address (venture://path), for local sources only.
|
|
128
|
+
const fileOfAddress = (a) => a?.startsWith("venture://") ? a.slice("venture://".length) : null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The wrapper around the router's call (see the top of this file). inner is the router's own call;
|
|
132
|
+
* args are what call takes. Returns what call returns, with the additions above.
|
|
133
|
+
*/
|
|
134
|
+
export async function around(inner, args) {
|
|
135
|
+
const { venture, route, action, input, actor = "agent", workspace } = args;
|
|
136
|
+
const actor_id = args.actor_id ?? process.env.MENTURA_ACTOR_ID;
|
|
137
|
+
// E-8: a broken framework file stops every route, with its recovery steps.
|
|
138
|
+
const fw = frameworkHealth();
|
|
139
|
+
if (fw) return globalError(fw);
|
|
140
|
+
let res;
|
|
141
|
+
try { res = resolve(route); } catch (e) { return globalError({ scope: "framework", file: "schema/data-model.json", problem: `the route table cannot be built: ${String(e.message ?? e).split("\n")[0]}`, recovery: ["Check the data model and the manifests: node schema/check-data-model.mjs", "Restore the file it names from git"] }); }
|
|
142
|
+
const dir = venture ? ventureDir(venture, workspace) : null;
|
|
143
|
+
// E-8: a broken venture.json answers with recovery steps, before anything reads it.
|
|
144
|
+
if (dir) { const h = ventureHealth(dir); if (h) return globalError(h, venture); }
|
|
145
|
+
// L-6, P-36, P-3: package, group and venture routes are answered here, outside the route table.
|
|
146
|
+
if (!res.r) return (await specialCall(args)) ?? inner(args);
|
|
147
|
+
const r = res.r, params = res.params;
|
|
148
|
+
const concrete = r.collection === "item" ? r.route.replace("[id]", params.id) : r.route;
|
|
149
|
+
const before = action === "write" && dir && r.kind === "record" ? await snapshot(dir) : null;
|
|
150
|
+
let out;
|
|
151
|
+
try { out = await inner(args); }
|
|
152
|
+
catch (e) {
|
|
153
|
+
// E-6: a record that cannot be read is that record's error, not a crash.
|
|
154
|
+
if (action !== "read") throw e;
|
|
155
|
+
return { ok: false, outcome: "error", error: `${concrete}: the record does not parse: ${String(e.message ?? e).split("\n")[0]}`, fix: { schema: r.schema ?? null, template: r.template ?? null }, resolved_by: "error" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (action === "read" && dir && r.kind === "record") {
|
|
159
|
+
if (out.ok) {
|
|
160
|
+
const items = r.collection === "list" ? Object.fromEntries(Object.entries(out.sources ?? {}).map(([id, s]) => [id, fileOfAddress(s.address)]).filter(([, f]) => f))
|
|
161
|
+
: out.source && fileOfAddress(out.source.address) ? { [params.id ?? r.record]: fileOfAddress(out.source.address) } : {};
|
|
162
|
+
out = { ...out, params, metadata: await metadataFor({ dir, r, route: concrete, params, result: out.result, items }) };
|
|
163
|
+
out = filterRead(out, input?.where, r);
|
|
164
|
+
}
|
|
165
|
+
out.resolved_by = resolvedBy(out);
|
|
166
|
+
if (input?.hierarchy) {
|
|
167
|
+
const { layout } = await import("./router.mjs");
|
|
168
|
+
const pkg = packageOfRecord(r.record);
|
|
169
|
+
const lay = withParams(layout(r, dir), { dir, params, file: draftOf(concrete, venture, workspace)?.file, group: pkg ? groupLayout(pkg, dir) : null });
|
|
170
|
+
out.hierarchy = hierarchy({ answer: out, layout: lay }).levels;
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (action === "layout" && out.ok) {
|
|
176
|
+
const pkg = r.kind === "record" ? packageOfRecord(r.record) : null;
|
|
177
|
+
const file = dir && r.kind === "record" && r.collection !== "list" && !concrete.includes("[id]") ? draftOf(concrete, venture, workspace)?.file : null;
|
|
178
|
+
const result = withParams(out.result, { dir, params, file, group: pkg ? groupLayout(pkg, dir) : null });
|
|
179
|
+
result.actions = await actionsIn({ dir, r, route: concrete, actor, actor_id, venture: venture ?? null });
|
|
180
|
+
return { ...out, result };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (action === "write" && before && out.ok && out.result?.written) {
|
|
184
|
+
const after = await snapshot(dir);
|
|
185
|
+
const change = await changed(before, after, { record: r.record });
|
|
186
|
+
out.result.changed = change;
|
|
187
|
+
out.result.next = await next(after, { record: r.record, route: concrete, draft: out.result.draft, venture, change });
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
@@ -15,6 +15,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { address, isText, words } from "./contract.mjs";
|
|
17
17
|
import { connectAll } from "./index.mjs";
|
|
18
|
+
import { ventureFile } from "../../schema/venture-paths.mjs";
|
|
18
19
|
|
|
19
20
|
export const FILE = "_catalog/catalog.json";
|
|
20
21
|
const MAX_BYTES = 1_000_000;
|
|
@@ -50,7 +51,7 @@ async function routeOf(path) {
|
|
|
50
51
|
return { route: null, record: null };
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
export const load = (dir) => existsSync(
|
|
54
|
+
export const load = (dir) => existsSync(ventureFile(dir, FILE)) ? JSON.parse(readFileSync(ventureFile(dir, FILE), "utf8")) : null;
|
|
54
55
|
|
|
55
56
|
/** Builds or refreshes the catalog. Returns it with stats: items, read (items fetched), reused. */
|
|
56
57
|
export async function build(dir, { write = true, sources } = {}) {
|
|
@@ -75,7 +76,7 @@ export async function build(dir, { write = true, sources } = {}) {
|
|
|
75
76
|
}
|
|
76
77
|
stats.items = items.length;
|
|
77
78
|
const catalog = { built_at: new Date().toISOString(), sources: conns.map(({ source }) => ({ id: source.id, connector: source.connector })), items };
|
|
78
|
-
if (write) { mkdirSync(join(dir, "_catalog"), { recursive: true }); writeFileSync(
|
|
79
|
+
if (write) { mkdirSync(join(dir, "_catalog"), { recursive: true }); writeFileSync(ventureFile(dir, FILE), JSON.stringify(catalog) + "\n"); }
|
|
79
80
|
return { catalog, stats };
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -13,6 +13,7 @@ import { spawnSync } from "node:child_process";
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { blobSha, isText, matches, safePath } from "./contract.mjs";
|
|
16
|
+
import { layoutOf, logicalFiles, ventureFile } from "../../schema/venture-paths.mjs";
|
|
16
17
|
|
|
17
18
|
const SKIP = new Set(["node_modules"]);
|
|
18
19
|
|
|
@@ -29,7 +30,11 @@ export function connect({ id = "venture", root, writes = "direct", venture = fal
|
|
|
29
30
|
}
|
|
30
31
|
return e.isFile() ? [r] : [];
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
+
// A venture in the structured layout (EE-086) keeps its records in knowledge/ and meta/: read and write them
|
|
34
|
+
// by their logical paths, and list them that way, so routes never see the layout.
|
|
35
|
+
const structured = venture && layoutOf(root) === "structured";
|
|
36
|
+
const file = (p) => (structured ? ventureFile(root, safePath(p)) : join(root, safePath(p)));
|
|
37
|
+
const listed = () => structured ? logicalFiles(root).filter((r) => !r.split("/").some((s) => s.startsWith(".")) && !r.startsWith("_") && !r.startsWith("node_modules/")) : walk(root);
|
|
33
38
|
const git = (...args) => spawnSync("git", ["-C", root, ...args], { encoding: "utf8" });
|
|
34
39
|
const lastCommit = (p, content) => {
|
|
35
40
|
const tracked = git("ls-files", "-s", "--", p);
|
|
@@ -42,8 +47,8 @@ export function connect({ id = "venture", root, writes = "direct", venture = fal
|
|
|
42
47
|
id, kind: "filesystem", writes, root,
|
|
43
48
|
async list(prefix = "") {
|
|
44
49
|
const pre = prefix ? safePath(prefix) : "";
|
|
45
|
-
return
|
|
46
|
-
.map((path) => ({ path, version: blobSha(readFileSync(
|
|
50
|
+
return listed().filter((p) => !pre || p === pre || p.startsWith(`${pre}/`)).sort()
|
|
51
|
+
.map((path) => ({ path, version: blobSha(readFileSync(file(path))) }));
|
|
47
52
|
},
|
|
48
53
|
async read(p) {
|
|
49
54
|
const f = file(p);
|
|
@@ -58,9 +63,9 @@ export function connect({ id = "venture", root, writes = "direct", venture = fal
|
|
|
58
63
|
},
|
|
59
64
|
async search(query, { limit = 50 } = {}) {
|
|
60
65
|
const out = [];
|
|
61
|
-
for (const path of
|
|
66
|
+
for (const path of listed().sort()) {
|
|
62
67
|
if (out.length >= limit) break;
|
|
63
|
-
const content = readFileSync(
|
|
68
|
+
const content = readFileSync(file(path), "utf8");
|
|
64
69
|
if (isText(path, content) && matches(`${path}\n${content}`, query)) out.push({ path, version: blobSha(content) });
|
|
65
70
|
}
|
|
66
71
|
return out;
|
|
@@ -17,6 +17,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { isAbsolute, join, resolve } from "node:path";
|
|
18
18
|
import { conformance } from "./contract.mjs";
|
|
19
19
|
import { ANY, resolve as resolveRule } from "./resolve.mjs";
|
|
20
|
+
import { ventureFile } from "../../schema/venture-paths.mjs";
|
|
20
21
|
|
|
21
22
|
const repo = new URL("../..", import.meta.url).pathname;
|
|
22
23
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -34,8 +35,8 @@ export function registry() {
|
|
|
34
35
|
|
|
35
36
|
/** A venture's source map, with the implicit venture source first. */
|
|
36
37
|
export function sourcesOf(dir) {
|
|
37
|
-
const own = existsSync(
|
|
38
|
-
return { sources: [{ id: LOCAL, connector: "filesystem", root: ".", writes: "direct", implicit: true }, ...(own.sources ?? [])], records: own.records ?? {}, rules: own.rules ?? [], mapped: existsSync(
|
|
38
|
+
const own = existsSync(ventureFile(dir, MAP)) ? load(ventureFile(dir, MAP)) : { sources: [], records: {} };
|
|
39
|
+
return { sources: [{ id: LOCAL, connector: "filesystem", root: ".", writes: "direct", implicit: true }, ...(own.sources ?? [])], records: own.records ?? {}, rules: own.rules ?? [], mapped: existsSync(ventureFile(dir, MAP)) };
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schema/freshness-profiles.schema.json",
|
|
3
|
+
"note": "Default freshness profiles (Revalidating, V-1 to V-3): how long a record stays fresh, by record type, after Next.js cacheLife profiles. A venture overrides any of them under freshness in its naive.config.json, as a Next.js app defines cacheLife profiles in next.config.js. stale: how long an agent may reuse what it read without reading it again. revalidate: after this the record is stale, still readable with a stale flag, and should be checked or redone. expire: after this it is expired and not frozen into a release. per_phase: the record is stale once the venture changes phase after it was last checked. live: a fast-changing record, never frozen into a release; it stays a live hole. Durations are seconds, or a number with s, m, h, d or w. Version 0.x: ours and unevaluated.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"default": "max",
|
|
6
|
+
"profiles": {
|
|
7
|
+
"live": {
|
|
8
|
+
"live": true,
|
|
9
|
+
"stale": "0s",
|
|
10
|
+
"revalidate": "7d",
|
|
11
|
+
"note": "Pipeline, money and streams: read from their system of record every time, synced at least weekly, never frozen"
|
|
12
|
+
},
|
|
13
|
+
"feedback": {
|
|
14
|
+
"stale": "1d",
|
|
15
|
+
"revalidate": "30d",
|
|
16
|
+
"expire": "90d",
|
|
17
|
+
"note": "What customers said: stale after 30 days, too old to rely on after 90"
|
|
18
|
+
},
|
|
19
|
+
"research": {
|
|
20
|
+
"stale": "1d",
|
|
21
|
+
"revalidate": "90d",
|
|
22
|
+
"expire": "365d",
|
|
23
|
+
"note": "Market research, product cards and site measurements: stale after 90 days"
|
|
24
|
+
},
|
|
25
|
+
"assumption": {
|
|
26
|
+
"per_phase": true,
|
|
27
|
+
"stale": "1d",
|
|
28
|
+
"revalidate": "90d",
|
|
29
|
+
"expire": "365d",
|
|
30
|
+
"note": "What the venture believes about its problem, solution, price and market: tested again each phase, and stale after 90 days"
|
|
31
|
+
},
|
|
32
|
+
"foundation": {
|
|
33
|
+
"per_phase": true,
|
|
34
|
+
"stale": "1d",
|
|
35
|
+
"revalidate": "365d",
|
|
36
|
+
"note": "The strategic foundation: tested again each phase; in Discovery even confirmed statements are"
|
|
37
|
+
},
|
|
38
|
+
"max": {
|
|
39
|
+
"stale": "1d",
|
|
40
|
+
"revalidate": "365d",
|
|
41
|
+
"note": "Governance and other slow records: looked at again once a year"
|
|
42
|
+
},
|
|
43
|
+
"none": {
|
|
44
|
+
"note": "Generated or configuration records: never stale by time, only when what they depend on changes"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"records": {
|
|
48
|
+
"customer": "live",
|
|
49
|
+
"investor": "live",
|
|
50
|
+
"money-account": "live",
|
|
51
|
+
"data-stream": "live",
|
|
52
|
+
"raw-source": "feedback",
|
|
53
|
+
"raw-text": "feedback",
|
|
54
|
+
"evidence": "feedback",
|
|
55
|
+
"product": "research",
|
|
56
|
+
"account": "research",
|
|
57
|
+
"site-presence": "research",
|
|
58
|
+
"anatomy-input": "research",
|
|
59
|
+
"anatomy-answers": "research",
|
|
60
|
+
"validation-problem": "assumption",
|
|
61
|
+
"validation-solution": "assumption",
|
|
62
|
+
"validation-product-market-fit": "assumption",
|
|
63
|
+
"validation-pricing": "assumption",
|
|
64
|
+
"validation-go-to-market": "assumption",
|
|
65
|
+
"validation-team": "assumption",
|
|
66
|
+
"validation-operations-readiness": "assumption",
|
|
67
|
+
"telos": "foundation",
|
|
68
|
+
"icp": "foundation",
|
|
69
|
+
"alignment": "foundation",
|
|
70
|
+
"idea-dump": "none",
|
|
71
|
+
"idea-map": "none",
|
|
72
|
+
"idea-graph": "none",
|
|
73
|
+
"venture-profile": "max",
|
|
74
|
+
"provenance": "none",
|
|
75
|
+
"freshness-log": "none",
|
|
76
|
+
"venture-package": "none",
|
|
77
|
+
"knowledge-sources": "none",
|
|
78
|
+
"where-used": "none",
|
|
79
|
+
"decision-record": "none",
|
|
80
|
+
"decision-rights": "none",
|
|
81
|
+
"team-plan": "max",
|
|
82
|
+
"marketing-sitemap": "none",
|
|
83
|
+
"marketing-content": "none"
|
|
84
|
+
}
|
|
85
|
+
}
|
package/knowledge/layer.json
CHANGED
|
@@ -19,11 +19,18 @@
|
|
|
19
19
|
"design-document",
|
|
20
20
|
"nextjs-status",
|
|
21
21
|
"knowledge-sources",
|
|
22
|
-
"where-used"
|
|
22
|
+
"where-used",
|
|
23
|
+
"freshness-profiles",
|
|
24
|
+
"freshness-log",
|
|
25
|
+
"judgment",
|
|
26
|
+
"systems-catalog",
|
|
27
|
+
"glossary"
|
|
23
28
|
],
|
|
24
29
|
"reads": [
|
|
25
30
|
"venture",
|
|
26
|
-
"product"
|
|
31
|
+
"product",
|
|
32
|
+
"provenance",
|
|
33
|
+
"decision-record"
|
|
27
34
|
],
|
|
28
35
|
"writes": [
|
|
29
36
|
"reference"
|
|
@@ -36,7 +43,8 @@
|
|
|
36
43
|
"No check on the venture profile or design documents of its own; design-coverage (evidence) is the only one",
|
|
37
44
|
"Two connectors so far (filesystem and GitHub); a database, Notion, Slack or Salesforce each need their own module meeting the same contract",
|
|
38
45
|
"Checks and command routes (need-now, time-to-ready, validation score) still read the venture folder, so they do not yet see a record kept in another source",
|
|
39
|
-
"A merged pull request is not logged as an accept in provenance; the read after the merge is the only sign"
|
|
46
|
+
"A merged pull request is not logged as an accept in provenance; the read after the merge is the only sign",
|
|
47
|
+
"Freshness (schema/freshness.mjs) checks children in the venture folder only; a child in another source is listed as needing the platform, and a webhook from a system of record that marks summaries stale (V-9) is the platform's"
|
|
40
48
|
],
|
|
41
49
|
"name": "knowledge"
|
|
42
50
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: template
|
|
3
|
+
tags: [everything-ecosystem, knowledge, glossary, template]
|
|
4
|
+
ai-model: claude-opus-5.5
|
|
5
|
+
created: 2026-10-29T06:04:07Z
|
|
6
|
+
source: hybrid
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Template: package glossary
|
|
10
|
+
|
|
11
|
+
Fills `<venture>/glossary/<package>.json` (`schema/glossary.schema.json`). A package's terms stay local to it: an agent working in another package sees only the terms marked `export`. See what a package sees with `mentura glossary/terms run --value <package>`.
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"package": "finance",
|
|
16
|
+
"terms": [
|
|
17
|
+
{ "term": "burn multiple", "meaning": "net burn divided by net new ARR" },
|
|
18
|
+
{ "term": "runway", "meaning": "months of cash at the current burn", "export": true }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Two packages may not export one term with different meanings. A package's own term wins over another's exported one.
|
package/operations/layer.json
CHANGED
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
"marketing-matrix",
|
|
24
24
|
"site-presence",
|
|
25
25
|
"marketing-sitemap",
|
|
26
|
-
"marketing-content"
|
|
26
|
+
"marketing-content",
|
|
27
|
+
"people",
|
|
28
|
+
"founder",
|
|
29
|
+
"marketing-brand",
|
|
30
|
+
"public-assets"
|
|
27
31
|
],
|
|
28
32
|
"reads": [
|
|
29
33
|
"agent-capability",
|
|
@@ -37,7 +41,8 @@
|
|
|
37
41
|
"publication-policy",
|
|
38
42
|
"role",
|
|
39
43
|
"telos",
|
|
40
|
-
"venture"
|
|
44
|
+
"venture",
|
|
45
|
+
"raw-source"
|
|
41
46
|
],
|
|
42
47
|
"writes": [
|
|
43
48
|
"evidence"
|
|
@@ -50,7 +55,9 @@
|
|
|
50
55
|
"schema/check-departments.mjs",
|
|
51
56
|
"operations/marketing/site/presence.mjs",
|
|
52
57
|
"operations/marketing/site/plan.mjs",
|
|
53
|
-
"operations/marketing/check-public.mjs"
|
|
58
|
+
"operations/marketing/check-public.mjs",
|
|
59
|
+
"schema/check-brand.mjs",
|
|
60
|
+
"schema/check-assets.mjs"
|
|
54
61
|
],
|
|
55
62
|
"gaps": [
|
|
56
63
|
"No run-handoff skill: the handoff contract is prose (operations/handoff-v2.md) beside three scripts in schema/ (check-ready, run-handoff, check-execution)",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
12
|
import { join, relative } from "node:path";
|
|
13
|
+
import { ventureFile } from "../../schema/venture-paths.mjs";
|
|
13
14
|
|
|
14
15
|
export const CONTENT = "operations/marketing/content";
|
|
15
16
|
const RUN = 10;
|
|
@@ -27,24 +28,24 @@ export function frontmatter(text) {
|
|
|
27
28
|
|
|
28
29
|
function files(dir) {
|
|
29
30
|
if (!existsSync(dir)) return [];
|
|
30
|
-
return readdirSync(dir).flatMap((f) => statSync(
|
|
31
|
+
return readdirSync(dir).flatMap((f) => statSync(ventureFile(dir, f)).isDirectory() ? files(ventureFile(dir, f)) : f.endsWith(".mdx") ? [ventureFile(dir, f)] : []);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function check(dir) {
|
|
34
35
|
const venture = existsSync(join(dir, "venture.json")) ? JSON.parse(readFileSync(join(dir, "venture.json"), "utf8")) : {};
|
|
35
36
|
const publicText = [venture.name, venture.brand?.tagline].filter(Boolean);
|
|
36
|
-
const pdir = join(dir, "products");
|
|
37
|
+
const pdir = join(ventureFile(dir, "products"));
|
|
37
38
|
if (existsSync(pdir)) for (const f of readdirSync(pdir).filter((f) => f.endsWith(".json"))) publicText.push(JSON.parse(readFileSync(join(pdir, f), "utf8")).name ?? "");
|
|
38
39
|
const allowed = new Set(publicText.flatMap((t) => [...runs(t)]));
|
|
39
|
-
const results = files(
|
|
40
|
+
const results = files(ventureFile(dir, CONTENT)).map((f) => {
|
|
40
41
|
const rel = relative(dir, f), { meta, body } = frontmatter(readFileSync(f, "utf8")), errors = [];
|
|
41
42
|
if (!meta.variant_of) errors.push("names no internal record (variant_of): marketing content is a variant of an internal record");
|
|
42
|
-
else if (!existsSync(
|
|
43
|
+
else if (!existsSync(ventureFile(dir, meta.variant_of))) errors.push(`variant_of ${meta.variant_of} does not exist`);
|
|
43
44
|
if (!meta.audience) errors.push("names no audience");
|
|
44
45
|
if (meta.publish === undefined) errors.push("does not say whether it is published (publish: true or false)");
|
|
45
46
|
if (meta.publish === true && !meta.approved_by) errors.push("is published without a person's approval (approved_by, EE-025)");
|
|
46
|
-
if (meta.variant_of && existsSync(
|
|
47
|
-
const src =
|
|
47
|
+
if (meta.variant_of && existsSync(ventureFile(dir, meta.variant_of))) {
|
|
48
|
+
const src = ventureFile(dir, meta.variant_of);
|
|
48
49
|
const raw = readFileSync(src, "utf8");
|
|
49
50
|
const internal = new Set(strings(src.endsWith(".json") ? JSON.parse(raw) : raw).flatMap((s) => [...runs(s)]));
|
|
50
51
|
const leaks = [...runs(body)].filter((r) => internal.has(r) && !allowed.has(r));
|