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
package/schema/port-venture.mjs
CHANGED
|
@@ -1,161 +1,235 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// naive port:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// project into the Naive convention) is the other way in
|
|
2
|
+
// naive port: moves an existing folder into a venture, as it is (EE-076, revised by Torian on
|
|
3
|
+
// 2026-10-29: port moves, it no longer copies files into packages). The folder is moved whole, with a
|
|
4
|
+
// rename, so not one file in it is opened or changed; the venture names it in sources.json as a source
|
|
5
|
+
// of truth (a read-only filesystem source) and logs the move in provenance.jsonl with the path it came
|
|
6
|
+
// from. Migrate (rewriting a project into the Naive convention) is the other way in.
|
|
7
7
|
//
|
|
8
|
-
// node schema/port-venture.mjs --
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
8
|
+
// node schema/port-venture.mjs <folder> --venture <dir> [--to <path>] [--links <dir> ...]
|
|
9
|
+
// adds the move to the plan at <venture>/.release/port-plan.json and prints it; moves nothing.
|
|
10
|
+
// The folder goes to <venture>/repo/<its name> unless --to says where (relative to the venture
|
|
11
|
+
// or absolute). Run once per folder: each run adds or replaces that folder's item.
|
|
12
|
+
// node schema/port-venture.mjs --venture <dir> --apply --confirm port [--even-if-busy]
|
|
13
|
+
// carries out every planned move the founder left as move; any item can be set to leave first
|
|
13
14
|
//
|
|
14
|
-
// The
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// The plan says what a move would break: worktrees of a git repository (they point at its old path
|
|
16
|
+
// until `git worktree repair` runs from the new one), symbolic links that point into the folder, and
|
|
17
|
+
// processes working inside it, which refuse the apply unless --even-if-busy. A port never overwrites,
|
|
18
|
+
// never copies (a move across disks is refused, not copied), and refuses an item whose folder changed
|
|
19
|
+
// identity since the plan. Every run writes a trace to .release/port-trace.json, in the shape
|
|
20
|
+
// platform/integrations/langsmith.mjs uploads.
|
|
17
21
|
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
18
23
|
import { createHash } from "node:crypto";
|
|
19
|
-
import {
|
|
20
|
-
import { basename, dirname,
|
|
21
|
-
import { record } from "../interface/provenance.mjs";
|
|
22
|
-
import {
|
|
24
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
26
|
+
import { agentFromEnv, record } from "../interface/provenance.mjs";
|
|
27
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
23
28
|
|
|
24
29
|
export const PLAN = ".release/port-plan.json";
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
30
|
+
export const TRACE = ".release/port-trace.json";
|
|
31
|
+
const MAP = "knowledge/sources.json";
|
|
32
|
+
const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+|-$/g, "") || "folder";
|
|
33
|
+
const inside = (a, b) => a === b || a.startsWith(b + sep);
|
|
34
|
+
const real = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
|
|
29
35
|
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
// Files and bytes under a folder, without following links, to show the size of what moves, and a
|
|
37
|
+
// manifest hash (each path with its size, never its contents) for the provenance entry.
|
|
38
|
+
function measure(root) {
|
|
39
|
+
let files = 0, bytes = 0;
|
|
40
|
+
const lines = [];
|
|
41
|
+
const stack = [root];
|
|
42
|
+
while (stack.length) {
|
|
43
|
+
const d = stack.pop();
|
|
44
|
+
let list = [];
|
|
45
|
+
try { list = readdirSync(d, { withFileTypes: true }); } catch { continue; }
|
|
46
|
+
for (const e of list) {
|
|
47
|
+
const p = join(d, e.name);
|
|
48
|
+
if (e.isDirectory()) stack.push(p);
|
|
49
|
+
else { files++; let n = 0; try { n = lstatSync(p).size; } catch {} bytes += n; lines.push(`${relative(root, p)} ${n}`); }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { files, bytes, manifest: createHash("sha256").update(lines.sort().join("\n")).digest("hex") };
|
|
53
|
+
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
// Symbolic links under `roots` (to `depth` levels, skipping node_modules) whose target is inside `src`.
|
|
56
|
+
// A link inside `src` that points inside it with a relative path keeps working, so only absolute ones count.
|
|
57
|
+
export function linksInto(src, roots, depth = 4) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const visit = (d, n) => {
|
|
61
|
+
if (n > depth || seen.has(d)) return;
|
|
62
|
+
seen.add(d);
|
|
63
|
+
let list = [];
|
|
64
|
+
try { list = readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
65
|
+
for (const e of list) {
|
|
66
|
+
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
67
|
+
const p = join(d, e.name);
|
|
68
|
+
if (e.isSymbolicLink()) {
|
|
69
|
+
const t = readlinkSync(p);
|
|
70
|
+
const target = resolve(dirname(p), t);
|
|
71
|
+
if (inside(target, src) && !(inside(p, src) && !isAbsolute(t))) out.push({ link: p, target: t });
|
|
72
|
+
} else if (e.isDirectory()) visit(p, n + 1);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
for (const r of roots) visit(resolve(r), 0);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
51
78
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
if (
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (hit) return { action: "port", to: `${hit[1]}/${basename(rel)}`, function: hit[1].split("/").at(-1), confidence, reason: `${confidence === "high" ? "its name" : `its folder ${where}`} ${hit[2]}` };
|
|
63
|
-
}
|
|
64
|
-
if (/^readme$/i.test(name)) return { action: "leave", reason: "the venture has its own AGENTS.md and profile.md; read this to write profile.md" };
|
|
65
|
-
return { action: "leave", reason: "nothing in its name or folders matches a package; place it by hand or migrate it" };
|
|
79
|
+
// The git facts a move touches: whether the folder is a repository, its other worktrees, and how
|
|
80
|
+
// many changes are uncommitted (the move keeps them; it is only said).
|
|
81
|
+
function git(src) {
|
|
82
|
+
const g = join(src, ".git");
|
|
83
|
+
if (!existsSync(g)) return null;
|
|
84
|
+
if (statSync(g).isFile()) return { linked: true, worktrees: [], uncommitted: null };
|
|
85
|
+
const run = (...a) => spawnSync("git", ["-C", src, ...a], { encoding: "utf8" });
|
|
86
|
+
const wt = (run("worktree", "list", "--porcelain").stdout ?? "").split("\n").filter((l) => l.startsWith("worktree ")).map((l) => l.slice(9)).filter((p) => real(p) !== real(src));
|
|
87
|
+
const st = run("status", "--porcelain");
|
|
88
|
+
return { linked: false, worktrees: wt, uncommitted: st.status === 0 ? st.stdout.split("\n").filter(Boolean).length : null };
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
else if (
|
|
91
|
+
// Processes whose working directory is inside the folder (macOS and Linux lsof).
|
|
92
|
+
export function busy(src) {
|
|
93
|
+
const r = spawnSync("lsof", ["-d", "cwd", "-Fpcn"], { encoding: "utf8" });
|
|
94
|
+
if (r.error || !r.stdout) return [];
|
|
95
|
+
const out = [];
|
|
96
|
+
let p = null, c = null;
|
|
97
|
+
for (const l of r.stdout.split("\n")) {
|
|
98
|
+
if (l[0] === "p") p = l.slice(1);
|
|
99
|
+
else if (l[0] === "c") c = l.slice(1);
|
|
100
|
+
else if (l[0] === "n" && inside(l.slice(1), real(src)) && p !== String(process.pid)) out.push({ pid: Number(p), command: c, cwd: l.slice(1) });
|
|
77
101
|
}
|
|
78
102
|
return out;
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
105
|
+
const readPlan = (dir) => existsSync(ventureFile(dir, PLAN)) ? JSON.parse(readFileSync(ventureFile(dir, PLAN), "utf8")) : null;
|
|
106
|
+
const readMap = (dir) => existsSync(ventureFile(dir, MAP)) ? JSON.parse(readFileSync(ventureFile(dir, MAP), "utf8")) : { sources: [], records: {} };
|
|
107
|
+
|
|
108
|
+
/** The planned move of `folder` into the venture at `dir`. Reads both, writes neither. */
|
|
109
|
+
export function planOne(folder, dir, { to, links = [] } = {}) {
|
|
110
|
+
if (!existsSync(folder) || !lstatSync(folder).isDirectory()) throw new Error(`${folder} is not a directory`);
|
|
85
111
|
if (!existsSync(join(dir, "venture.json"))) throw new Error(`${dir} is not a venture: create one first with naive init`);
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
const src = real(folder);
|
|
113
|
+
const v = real(dir);
|
|
114
|
+
if (inside(v, src)) throw new Error("the venture cannot sit inside the folder it ports");
|
|
115
|
+
const dest = resolve(v, to ?? join("repo", basename(src)));
|
|
116
|
+
const s = statSync(src);
|
|
117
|
+
const item = { action: "move", from: src, to: dest, source: slug(basename(src)), identity: { dev: s.dev, ino: s.ino }, ...measure(src) };
|
|
118
|
+
const warnings = [];
|
|
119
|
+
const leave = (why) => Object.assign(item, { action: "leave", reason: why });
|
|
120
|
+
if (existsSync(dest)) leave(`${dest} already exists; a port never overwrites`);
|
|
121
|
+
else if (inside(dest, src)) leave("the destination is inside the folder itself");
|
|
122
|
+
else if (!inside(dest, v)) warnings.push("the destination is outside the venture folder; sources.json will name it by absolute path");
|
|
123
|
+
// A rename only works on one disk: the nearest existing parent of the destination must share it.
|
|
124
|
+
let up = dirname(dest);
|
|
125
|
+
while (!existsSync(up)) up = dirname(up);
|
|
126
|
+
if (item.action === "move" && statSync(up).dev !== s.dev) leave("the destination is on another disk; a port moves, it never copies");
|
|
127
|
+
if (item.action === "move" && readMap(dir).sources.some((x) => x.id === item.source)) leave(`sources.json already has a source ${item.source}`);
|
|
128
|
+
const g = git(src);
|
|
129
|
+
item.git = g;
|
|
130
|
+
if (g?.linked) warnings.push("this folder is a linked git worktree; its main repository will point at the old path until `git worktree repair` runs there");
|
|
131
|
+
if (g?.worktrees.length) warnings.push(`${g.worktrees.length} worktree${g.worktrees.length === 1 ? "" : "s"} of this repository point at its old path until \`git -C ${dest} worktree repair\` runs: ${g.worktrees.join(", ")}`);
|
|
132
|
+
if (g?.uncommitted) warnings.push(`${g.uncommitted} uncommitted change${g.uncommitted === 1 ? "" : "s"} move with it, unchanged`);
|
|
133
|
+
item.links = linksInto(src, [dirname(src), src, ...links]);
|
|
134
|
+
if (item.links.length) warnings.push(`${item.links.length} symbolic link${item.links.length === 1 ? "" : "s"} point into it and will break: ${item.links.map((x) => x.link).join(", ")}`);
|
|
135
|
+
item.busy = busy(src);
|
|
136
|
+
if (item.busy.length) warnings.push(`${item.busy.length} process${item.busy.length === 1 ? " is" : "es are"} working inside it (${[...new Set(item.busy.map((x) => x.command))].join(", ")}); apply refuses unless --even-if-busy`);
|
|
137
|
+
item.warnings = warnings;
|
|
138
|
+
return item;
|
|
107
139
|
}
|
|
108
140
|
|
|
109
|
-
/**
|
|
110
|
-
export function
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
/** Adds (or replaces) one folder's item in the venture's plan and writes it. */
|
|
142
|
+
export function plan(folder, dir, opts = {}) {
|
|
143
|
+
const item = planOne(folder, dir, opts);
|
|
144
|
+
const p = readPlan(dir) ?? { venture: JSON.parse(readFileSync(join(dir, "venture.json"), "utf8")).id, items: [] };
|
|
145
|
+
const others = p.items.filter((i) => i.from !== item.from && i.action === "move" && !i.done);
|
|
146
|
+
if (others.some((i) => i.source === item.source)) Object.assign(item, { action: "leave", reason: `another planned move already uses the source id ${item.source}` });
|
|
147
|
+
if (others.some((i) => i.to === item.to)) Object.assign(item, { action: "leave", reason: `another planned move already goes to ${item.to}` });
|
|
148
|
+
p.items = [...p.items.filter((i) => i.from !== item.from || i.done), item];
|
|
149
|
+
p.at = new Date().toISOString();
|
|
150
|
+
mkdirSync(dirname(ventureFile(dir, PLAN)), { recursive: true });
|
|
151
|
+
writeFileSync(ventureFile(dir, PLAN), JSON.stringify(p, null, 2) + "\n");
|
|
152
|
+
return { plan: p, item };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Carries out the plan as the founder left it. Checks every item before moving any. */
|
|
156
|
+
export function apply(dir, p, { agent = agentFromEnv("person", "cli"), evenIfBusy = false } = {}) {
|
|
157
|
+
const moves = p.items.filter((i) => i.action === "move" && !i.done);
|
|
158
|
+
const ids = new Set(readMap(dir).sources.map((s) => s.id));
|
|
159
|
+
for (const i of moves) {
|
|
160
|
+
if (!existsSync(i.from)) throw new Error(`${i.from} is gone; write the plan again`);
|
|
161
|
+
const s = statSync(i.from);
|
|
162
|
+
if (s.dev !== i.identity.dev || s.ino !== i.identity.ino) throw new Error(`${i.from} is not the folder the plan was written for; write the plan again`);
|
|
163
|
+
if (existsSync(i.to)) throw new Error(`${i.to} already exists; a port never overwrites`);
|
|
164
|
+
if (ids.has(i.source)) throw new Error(`sources.json already has a source ${i.source}`);
|
|
165
|
+
const b = busy(i.from);
|
|
166
|
+
if (b.length && !evenIfBusy) throw new Error(`${b.length} process${b.length === 1 ? " is" : "es are"} working inside ${i.from} (${b.map((x) => `${x.command} ${x.pid}`).join(", ")}); stop them or add --even-if-busy`);
|
|
115
167
|
}
|
|
116
|
-
const done =
|
|
117
|
-
for (const i of
|
|
118
|
-
mkdirSync(dirname(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
map.sources.push({ id: i.source, connector: "filesystem", writes: "none", root: join(src, i.from === "." ? "" : i.from), note: `ported as a pointer: ${i.reason}` });
|
|
130
|
-
done.pointed.push(i.source);
|
|
131
|
-
}
|
|
132
|
-
mkdirSync(dirname(f), { recursive: true });
|
|
133
|
-
writeFileSync(f, JSON.stringify(map, null, 2) + "\n");
|
|
134
|
-
if (done.pointed.length) record(dir, { entity: "knowledge/sources.json", activity: "port", agent, note: `pointers to ${done.pointed.join(", ")}` });
|
|
168
|
+
const done = [];
|
|
169
|
+
for (const i of moves) {
|
|
170
|
+
mkdirSync(dirname(i.to), { recursive: true });
|
|
171
|
+
renameSync(i.from, i.to);
|
|
172
|
+
const map = readMap(dir);
|
|
173
|
+
const root = inside(i.to, real(dir)) ? relative(real(dir), i.to) : i.to;
|
|
174
|
+
map.sources.push({ id: i.source, connector: "filesystem", writes: "none", root, note: `source of truth, moved here unchanged from ${i.from} by naive port` });
|
|
175
|
+
mkdirSync(dirname(ventureFile(dir, MAP)), { recursive: true });
|
|
176
|
+
writeFileSync(ventureFile(dir, MAP), JSON.stringify(map, null, 2) + "\n");
|
|
177
|
+
record(dir, { entity: root, activity: "port", agent, derived_from: [`file://${i.from}`], sha256: measure(i.to).manifest, note: `moved unchanged (${i.files} files); named in sources.json as source ${i.source}` });
|
|
178
|
+
i.done = new Date().toISOString();
|
|
179
|
+
done.push(i);
|
|
180
|
+
writeFileSync(ventureFile(dir, PLAN), JSON.stringify(p, null, 2) + "\n");
|
|
135
181
|
}
|
|
136
182
|
return done;
|
|
137
183
|
}
|
|
138
184
|
|
|
185
|
+
// One run as a trace for platform/integrations/langsmith.mjs: the run, and a child per item.
|
|
186
|
+
function trace(dir, name, inputs, start, items, error) {
|
|
187
|
+
const end = new Date().toISOString();
|
|
188
|
+
const t = { name, run_type: "chain", start, end, inputs, outputs: { items: items.map(({ from, to, action, done }) => ({ from, to, action, done })) }, ...(error ? { error } : {}),
|
|
189
|
+
tags: ["naive", "port"], children: items.map((i) => ({ name: `${i.action} ${basename(i.from)}`, run_type: "tool", start, end, inputs: { from: i.from, to: i.to },
|
|
190
|
+
outputs: { action: i.action, reason: i.reason, files: i.files, bytes: i.bytes, warnings: i.warnings, done: i.done } })) };
|
|
191
|
+
try { mkdirSync(dirname(ventureFile(dir, TRACE)), { recursive: true }); writeFileSync(ventureFile(dir, TRACE), JSON.stringify(t, null, 2) + "\n"); } catch { return; }
|
|
192
|
+
// The harness's way (interface/harness/run.mjs): with a key set, the platform exporter ships the trace.
|
|
193
|
+
if (process.env.LANGSMITH_API_KEY) {
|
|
194
|
+
const p = spawnSync("node", [join(dirname(new URL(import.meta.url).pathname), "../platform/integrations/langsmith.mjs"), ventureFile(dir, TRACE), "--project", process.env.LANGSMITH_PROJECT ?? "naive-port"], { encoding: "utf8" });
|
|
195
|
+
console.log(p.status === 0 ? `LangSmith: ${p.stdout.trim()}` : `LangSmith upload failed: ${(p.stderr || p.stdout).trim()}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const mb = (b) => `${(b / 1024 / 1024).toFixed(1)} MB`;
|
|
200
|
+
|
|
139
201
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
140
202
|
const args = process.argv.slice(2);
|
|
141
203
|
const opt = (k) => { const i = args.indexOf(`--${k}`); return i < 0 ? undefined : args[i + 1]; };
|
|
204
|
+
const all = (k) => args.flatMap((a, i) => a === `--${k}` ? [args[i + 1]] : []);
|
|
205
|
+
const VALUED = new Set(["--venture", "--to", "--links", "--confirm", "--from"]);
|
|
206
|
+
const folder = opt("from") ?? args.find((a, i) => !a.startsWith("--") && !VALUED.has(args[i - 1]));
|
|
142
207
|
const dir = opt("venture");
|
|
143
|
-
if (!dir) { console.error("usage: node schema/port-venture.mjs
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
208
|
+
if (!dir) { console.error("usage: node schema/port-venture.mjs <folder> --venture <dir> [--to <path>] | --venture <dir> --apply --confirm port"); process.exit(2); }
|
|
209
|
+
const start = new Date().toISOString();
|
|
210
|
+
if (args.includes("--apply")) {
|
|
211
|
+
if (opt("confirm") !== "port") { console.error("type --confirm port to carry out the plan"); process.exit(1); }
|
|
212
|
+
const p = readPlan(dir);
|
|
213
|
+
if (!p) { console.error(`no plan at ${ventureFile(dir, PLAN)}: run naive port <folder> first`); process.exit(1); }
|
|
214
|
+
try {
|
|
215
|
+
const d = apply(dir, p, { evenIfBusy: args.includes("--even-if-busy") });
|
|
216
|
+
trace(dir, "naive port apply", { venture: dir }, start, d);
|
|
217
|
+
for (const i of d) console.log(`moved ${i.from} -> ${i.to} (${i.files} files, unchanged; source ${i.source})`);
|
|
218
|
+
for (const i of d.filter((x) => x.git?.worktrees.length)) console.log(`\nrepair its worktrees: git -C ${i.to} worktree repair`);
|
|
219
|
+
console.log(`\nmoved ${d.length} folder${d.length === 1 ? "" : "s"}. Trace: ${relative(process.cwd(), ventureFile(dir, TRACE))}`);
|
|
220
|
+
} catch (e) { trace(dir, "naive port apply", { venture: dir }, start, p.items, e.message); console.error(e.message); process.exit(1); }
|
|
221
|
+
} else {
|
|
222
|
+
if (!folder) { console.error("name the folder to port: naive port <folder>"); process.exit(2); }
|
|
223
|
+
try {
|
|
224
|
+
const { plan: p, item } = plan(folder, dir, { to: opt("to"), links: all("links") });
|
|
225
|
+
trace(dir, "naive port plan", { folder, venture: dir, to: opt("to") }, start, [item]);
|
|
226
|
+
for (const i of p.items) {
|
|
227
|
+
console.log(`${(i.done ? "moved" : i.action).padEnd(5)} ${i.from} -> ${i.to} (${i.files} files, ${mb(i.bytes)}${i.reason ? `; ${i.reason}` : ""})`);
|
|
228
|
+
if (!i.done) for (const w of i.warnings ?? []) console.log(` ! ${w}`);
|
|
229
|
+
}
|
|
230
|
+
const n = p.items.filter((i) => i.action === "move" && !i.done).length;
|
|
231
|
+
console.log(`\n${n} folder${n === 1 ? "" : "s"} to move, unchanged. Nothing has moved yet.`);
|
|
232
|
+
console.log(`Read ${relative(process.cwd(), ventureFile(dir, PLAN))}, set any item's action to leave, then run with --apply --confirm port.`);
|
|
233
|
+
} catch (e) { trace(dir, "naive port plan", { folder, venture: dir }, start, [], e.message); console.error(e.message); process.exit(1); }
|
|
234
|
+
}
|
|
161
235
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "public-assets.schema.json",
|
|
4
|
+
"title": "Public assets",
|
|
5
|
+
"description": "The register of what the venture ships as it is, at public/assets.json (I-23, J-2, J-3, J-4): each file in public/ with its source, license and credit, facts read by code from its bytes, and alt text with who wrote it; remote images as links with a note in raw/sources/<id>/; and remote_hosts, the hosts an agent may fetch from. Checked by schema/check-assets.mjs. Version 0.x, unevaluated.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "assets"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"$defs": {
|
|
10
|
+
"alt": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["text", "by"],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"text": { "type": "string", "minLength": 1 },
|
|
16
|
+
"by": { "enum": ["agent", "person"], "description": "Who wrote it. An agent's alt text stays pending until a person confirms it in provenance" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"facts": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["type", "bytes", "sha256"],
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"description": "Read from the bytes by code (schema/assets.mjs), never written by hand",
|
|
24
|
+
"properties": {
|
|
25
|
+
"type": { "type": "string" },
|
|
26
|
+
"width": { "type": "integer" },
|
|
27
|
+
"height": { "type": "integer" },
|
|
28
|
+
"sizes": { "type": "array", "items": { "type": "string" } },
|
|
29
|
+
"bytes": { "type": "integer" },
|
|
30
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
31
|
+
"filled_at": { "type": "string" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"properties": {
|
|
36
|
+
"version": { "type": "string" },
|
|
37
|
+
"remote_hosts": { "type": "array", "items": { "type": "string" }, "description": "Hosts an agent may fetch images from (exact, or *.example.com); the platform fetcher refuses any other" },
|
|
38
|
+
"assets": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"required": ["path", "source", "license"],
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"properties": {
|
|
45
|
+
"path": { "type": "string", "pattern": "^public/(?!.*\\.\\./).+" },
|
|
46
|
+
"source": { "type": "string", "description": "Where it came from: who made it, or a URL" },
|
|
47
|
+
"license": { "type": "string", "description": "An SPDX id (CC-BY-4.0, OFL-1.1), owned (made by or for the venture), licensed (bought), or the restriction (all-rights-reserved)" },
|
|
48
|
+
"credit": { "type": "string", "description": "Who to credit; a missing credit fails the check" },
|
|
49
|
+
"alt": { "$ref": "#/$defs/alt" },
|
|
50
|
+
"decorative": { "type": "boolean", "description": "An image that says nothing, so it needs no alt text" },
|
|
51
|
+
"origin_url": { "type": "string", "description": "The remote work this is a copy of" },
|
|
52
|
+
"design_system": { "type": "string", "description": "The design system (brand design_systems id) this came from" },
|
|
53
|
+
"facts": { "$ref": "#/$defs/facts" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"remote": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"description": "Remote images the venture links to rather than ships",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": ["id", "url", "license"],
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"properties": {
|
|
65
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "The raw source holding the note: raw/sources/<id>/" },
|
|
66
|
+
"url": { "type": "string" },
|
|
67
|
+
"license": { "type": "string" },
|
|
68
|
+
"credit": { "type": "string" },
|
|
69
|
+
"alt": { "$ref": "#/$defs/alt" },
|
|
70
|
+
"fetched": { "$ref": "#/$defs/facts", "description": "Facts from a fetch by the platform; the original stays private in raw/sources/<id>/original.*" },
|
|
71
|
+
"copy": { "type": "string", "pattern": "^public/", "description": "Where a full copy was kept, only when the license allows it" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Reading a release without the router (C-12, I-17). A release is the venture's static shell
|
|
2
|
+
// (C-11): the confirmed records frozen in, and named holes for what stays live. Everything here
|
|
3
|
+
// reads <venture>/.release/ and nothing else, so a release can be read after the venture's records
|
|
4
|
+
// change, on a machine with no router, or handed to an investor as files. It never imports
|
|
5
|
+
// interface/router.mjs; schema/releases-test.mjs checks that.
|
|
6
|
+
//
|
|
7
|
+
// The answer to a path is { status, type, body }:
|
|
8
|
+
// / the release index: venture, phase, release id, routes in the release, holes
|
|
9
|
+
// /release.json the manifest; /card.json the release card's data (P-25, O-7)
|
|
10
|
+
// /records/<file> one frozen record, as it was when it was confirmed
|
|
11
|
+
// /<route> a route as the release holds it: a frozen record, a collection's frozen items,
|
|
12
|
+
// a frozen command answer (H-4), or a hole that names where to read it live.
|
|
13
|
+
// An item the release does not hold answers 404 with the items it does and the
|
|
14
|
+
// command that adds it without rebuilding the rest (C-18, N-10).
|
|
15
|
+
// Any method other than GET or HEAD answers 405: a release is read-only.
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { join, normalize } from "node:path";
|
|
19
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
20
|
+
|
|
21
|
+
export const RELEASE_DIR = ".release";
|
|
22
|
+
export const MANIFEST = ".release/release.json";
|
|
23
|
+
export const CARD = ".release/card.json";
|
|
24
|
+
export const RECORDS = ".release/records";
|
|
25
|
+
|
|
26
|
+
/** The release manifest of a venture, or null when it has none. */
|
|
27
|
+
export function readRelease(dir) {
|
|
28
|
+
const f = ventureFile(dir, MANIFEST);
|
|
29
|
+
return existsSync(f) ? JSON.parse(readFileSync(f, "utf8")) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The frozen copy of one record in a release, as text, or null. */
|
|
33
|
+
export function frozenText(dir, file) {
|
|
34
|
+
const rel = normalize(file);
|
|
35
|
+
if (rel.startsWith("..") || rel.startsWith("/")) return null;
|
|
36
|
+
const f = join(ventureFile(dir, RECORDS), rel);
|
|
37
|
+
return existsSync(f) ? readFileSync(f, "utf8") : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parse = (file, text) => file.endsWith(".json") ? JSON.parse(text) : text;
|
|
41
|
+
const json = (status, body) => ({ status, type: "application/json; charset=utf-8", body: JSON.stringify(body, null, 2) + "\n" });
|
|
42
|
+
|
|
43
|
+
/** The index a reader starts from. */
|
|
44
|
+
export function index(m) {
|
|
45
|
+
return { release: m.release, venture: m.venture, name: m.name, phase: m.phase, built_at: m.built_at, now: m.now,
|
|
46
|
+
routes: m.routes.filter((r) => r.in === "release").map((r) => r.route),
|
|
47
|
+
holes: m.holes.map((h) => ({ kind: h.kind, route: h.route, name: h.name, why: h.why })),
|
|
48
|
+
card: "/card.json", manifest: "/release.json",
|
|
49
|
+
note: "A release is read-only. Holes stay live: read them in the venture through the router." };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What a route holds in a release: { status, body }. Matches a concrete route (product/kettle) to its entry. */
|
|
53
|
+
export function resolveRoute(dir, m, route) {
|
|
54
|
+
route = route.replace(/^\/+|\/+$/g, "");
|
|
55
|
+
const exact = m.routes.find((r) => r.route === route);
|
|
56
|
+
const entry = exact ?? m.routes.find((r) => r.route.endsWith("/[id]") && route.startsWith(r.route.slice(0, -4)) && !route.slice(r.route.length - 4).includes("/"));
|
|
57
|
+
if (!entry) return { status: 404, body: { error: `no route ${route} in release ${m.release}` } };
|
|
58
|
+
const id = exact ? null : route.slice(entry.route.length - 4);
|
|
59
|
+
const holes = m.holes.filter((h) => h.route === route || h.route_template === entry.route);
|
|
60
|
+
// A decision is also found by its id (decisions/founding-team), whatever its file is called.
|
|
61
|
+
const own = holes.find((h) => (h.route === route || (h.kind === "decision" && `decisions/${h.name}` === route)) && h.kind !== "attribute");
|
|
62
|
+
if (entry.in === "hole") return { status: 200, body: { route, hole: true, kind: own?.kind ?? entry.kind, why: own?.why ?? entry.why, live: own?.live ?? entry.live ?? null, ...(own ? {} : { holes }) } };
|
|
63
|
+
if (entry.in === "left out") return { status: 404, body: { route, left_out: true, why: entry.why } };
|
|
64
|
+
if (entry.kind === "command") {
|
|
65
|
+
const out = m.commands?.[entry.route];
|
|
66
|
+
return out === undefined ? { status: 404, body: { route, empty: true, why: entry.why ?? "not in this release" } } : { status: 200, body: { route, frozen: true, built_from: out.built_from, result: out.result } };
|
|
67
|
+
}
|
|
68
|
+
const recs = m.records.filter((r) => r.route_template === entry.route);
|
|
69
|
+
const fieldHoles = (r) => m.holes.filter((h) => h.kind === "attribute" && h.file === r.file).map((h) => h.field);
|
|
70
|
+
const one = (r) => ({ file: r.file, as_of: r.as_of, confirmed_by: r.confirmed_by, ...(fieldHoles(r).length ? { live_fields: fieldHoles(r) } : {}), record: parse(r.file, frozenText(dir, r.file)) });
|
|
71
|
+
if (entry.collection === "list") {
|
|
72
|
+
const items = m.records.filter((r) => r.route_template === `${entry.route}/[id]`);
|
|
73
|
+
return { status: 200, body: { route, items: Object.fromEntries(items.map((r) => [r.id, one(r)])), holes } };
|
|
74
|
+
}
|
|
75
|
+
if (entry.collection === "item") {
|
|
76
|
+
const hit = recs.find((r) => r.id === id);
|
|
77
|
+
if (own) return { status: 200, body: { route, hole: true, kind: own.kind, why: own.why, live: own.live ?? null } };
|
|
78
|
+
if (!hit) return { status: 404, body: { route, not_in_release: true, release: m.release, items: recs.map((r) => r.id), ...(holes.length ? { live_items: holes.filter((h) => h.kind !== "attribute").map((h) => h.name) } : {}),
|
|
79
|
+
note: `${route} was not known when release ${m.release} was built`, add: `node schema/build-release.mjs <venture dir> --add ${route}` } };
|
|
80
|
+
return { status: 200, body: { route, ...one(hit) } };
|
|
81
|
+
}
|
|
82
|
+
if (!recs.length) return { status: 404, body: { route, empty: true, why: entry.why ?? "no confirmed record when the release was built" } };
|
|
83
|
+
return { status: 200, body: { route, ...(recs.length === 1 ? one(recs[0]) : { records: recs.map(one) }) } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Answers one request against a venture's release. draw, when given, turns the card's data into an
|
|
88
|
+
* image (the interface's job, O-7): draw(card) -> SVG text.
|
|
89
|
+
*/
|
|
90
|
+
export function answer(dir, method, path, { draw } = {}) {
|
|
91
|
+
if (method !== "GET" && method !== "HEAD") return { ...json(405, { error: "a release is read-only: only GET and HEAD" }), allow: "GET, HEAD" };
|
|
92
|
+
const m = readRelease(dir);
|
|
93
|
+
if (!m) return json(503, { error: `no release in ${dir}: run node schema/build-release.mjs ${dir} first` });
|
|
94
|
+
const p = decodeURIComponent(path.split("?")[0]);
|
|
95
|
+
if (p.split("/").includes("..")) return json(400, { error: "no .. in a release path" });
|
|
96
|
+
if (p === "/" || p === "") return json(200, index(m));
|
|
97
|
+
if (p === "/release.json") return json(200, m);
|
|
98
|
+
if (p === "/card.json") return existsSync(ventureFile(dir, CARD)) ? json(200, JSON.parse(readFileSync(ventureFile(dir, CARD), "utf8"))) : json(404, { error: "this release has no card" });
|
|
99
|
+
if (p === "/card.svg") {
|
|
100
|
+
if (!draw) return json(404, { error: "no card drawing here" });
|
|
101
|
+
return { status: 200, type: "image/svg+xml; charset=utf-8", body: draw(JSON.parse(readFileSync(ventureFile(dir, CARD), "utf8"))) };
|
|
102
|
+
}
|
|
103
|
+
if (p.startsWith("/records/")) {
|
|
104
|
+
const file = p.slice("/records/".length);
|
|
105
|
+
const r = m.records.find((x) => x.file === file);
|
|
106
|
+
const text = r ? frozenText(dir, file) : null;
|
|
107
|
+
if (text === null) return json(404, { error: `${file} is not in release ${m.release}` });
|
|
108
|
+
return { status: 200, type: file.endsWith(".json") ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8", body: text };
|
|
109
|
+
}
|
|
110
|
+
const r = resolveRoute(dir, m, p);
|
|
111
|
+
return json(r.status, r.body);
|
|
112
|
+
}
|