@topogram/cli 0.3.44 → 0.3.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -172,6 +172,7 @@ export function buildBundleAdoptionPriorities(report, confidenceRank) {
172
172
  enums: bundle.enums.length,
173
173
  capabilities: bundle.capabilities.length,
174
174
  shapes: bundle.shapes.length,
175
+ components: bundle.components?.length || 0,
175
176
  screens: bundle.screens.length,
176
177
  workflows: bundle.workflows.length,
177
178
  docs: bundle.docs.length
package/src/cli.js CHANGED
@@ -595,9 +595,9 @@ function printGeneratorHelp() {
595
595
  console.log("Inspects generator manifests and checks generator pack conformance.");
596
596
  console.log("");
597
597
  console.log("Notes:");
598
- console.log(" - list shows bundled generators plus installed package-backed generators declared in package.json.");
599
- console.log(" - show accepts an installed package name or a bundled fallback generator id.");
600
- console.log(" - check validates a local generator package path or an already installed package.");
598
+ console.log(" - list shows bundled generators plus installed package-backed generators declared in package.json; it reads manifests only.");
599
+ console.log(" - show accepts an installed package name or a bundled fallback generator id; it does not load adapter code.");
600
+ console.log(" - check validates a local generator package path or an already installed package by loading the adapter and running smoke generation.");
601
601
  console.log(" - Topogram does not install generator packages during show or check.");
602
602
  console.log(` - package-backed project generators are governed by ${GENERATOR_POLICY_FILE}; bundled topogram/* generators are allowed.`);
603
603
  console.log("");
@@ -1250,6 +1250,7 @@ function printGeneratorCheck(payload) {
1250
1250
  console.log(`Projection platforms: ${payload.manifest.projectionPlatforms.join(", ")}`);
1251
1251
  console.log(`Source mode: ${payload.manifest.source}`);
1252
1252
  }
1253
+ console.log("Executes package code: yes (loads adapter and runs smoke generate)");
1253
1254
  console.log("");
1254
1255
  console.log("Checks:");
1255
1256
  for (const check of payload.checks || []) {
@@ -1285,6 +1286,8 @@ function generatorManifestSummary(manifest, metadata = {}) {
1285
1286
  stack: manifest.stack || {},
1286
1287
  capabilities: manifest.capabilities || {},
1287
1288
  source: manifest.source,
1289
+ loadsAdapter: false,
1290
+ executesPackageCode: false,
1288
1291
  ...(manifest.profile ? { profile: manifest.profile } : {}),
1289
1292
  ...(manifest.package ? { package: manifest.package } : {}),
1290
1293
  ...(installCommand ? { installCommand } : {}),
@@ -1496,6 +1499,8 @@ function printGeneratorList(payload) {
1496
1499
  const stack = Object.values(generator.stack || {}).join(" + ") || "not declared";
1497
1500
  console.log(`- ${id}${generator.version ? `@${generator.version}` : ""} (${generator.surface || "unknown"}, ${status})`);
1498
1501
  console.log(` Source: ${generator.source}`);
1502
+ console.log(" Adapter loaded: no");
1503
+ console.log(" Executes package code: no");
1499
1504
  if (generator.source === "package") {
1500
1505
  console.log(` Installed: ${generator.installed ? "yes" : "no"}`);
1501
1506
  }
@@ -1529,6 +1534,8 @@ function printGeneratorShow(payload) {
1529
1534
  console.log(`Generator: ${generator.id}@${generator.version}`);
1530
1535
  console.log(`Surface: ${generator.surface}`);
1531
1536
  console.log(`Source: ${generator.source}${generator.planned ? " (planned)" : ""}`);
1537
+ console.log("Adapter loaded: no");
1538
+ console.log("Executes package code: no");
1532
1539
  if (generator.source === "package") {
1533
1540
  console.log(`Installed: ${generator.installed ? "yes" : "no"}`);
1534
1541
  }
@@ -5558,6 +5565,32 @@ function importAdoptCommand(projectRoot, selector, write = false) {
5558
5565
  return `topogram import adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
5559
5566
  }
5560
5567
 
5568
+ const BROWNFIELD_BROAD_ADOPT_SELECTORS = [
5569
+ {
5570
+ selector: "from-plan",
5571
+ kind: "plan",
5572
+ label: "approved or pending plan items",
5573
+ matches: (item) => item.current_state === "stage" || item.current_state === "accept"
5574
+ },
5575
+ { selector: "actors", kind: "kind", label: "actors", matches: (item) => item.kind === "actor" },
5576
+ { selector: "roles", kind: "kind", label: "roles", matches: (item) => item.kind === "role" },
5577
+ { selector: "enums", kind: "kind", label: "enums", matches: (item) => item.kind === "enum" },
5578
+ { selector: "shapes", kind: "kind", label: "shapes", matches: (item) => item.kind === "shape" },
5579
+ { selector: "entities", kind: "kind", label: "entities", matches: (item) => item.kind === "entity" },
5580
+ { selector: "capabilities", kind: "kind", label: "capabilities", matches: (item) => item.kind === "capability" },
5581
+ { selector: "components", kind: "kind", label: "components", matches: (item) => item.kind === "component" },
5582
+ { selector: "docs", kind: "track", label: "docs", matches: (item) => item.track === "docs" },
5583
+ {
5584
+ selector: "journeys",
5585
+ kind: "track",
5586
+ label: "journey docs",
5587
+ matches: (item) => item.track === "docs" && String(item.canonical_rel_path || "").startsWith("docs/journeys/")
5588
+ },
5589
+ { selector: "workflows", kind: "track", label: "workflows", matches: (item) => item.track === "workflows" || item.kind === "decision" },
5590
+ { selector: "verification", kind: "kind", label: "verification", matches: (item) => item.kind === "verification" },
5591
+ { selector: "ui", kind: "track", label: "UI reports and components", matches: (item) => item.track === "ui" }
5592
+ ];
5593
+
5561
5594
  function readImportAdoptionArtifacts(inputPath) {
5562
5595
  const projectRoot = normalizeProjectRoot(inputPath);
5563
5596
  const topogramRoot = normalizeTopogramPath(inputPath);
@@ -5582,6 +5615,27 @@ function readImportAdoptionArtifacts(inputPath) {
5582
5615
  };
5583
5616
  }
5584
5617
 
5618
+ function buildBrownfieldBroadAdoptSelectors(projectRoot, adoptionPlan) {
5619
+ const surfaces = adoptionPlan.imported_proposal_surfaces || [];
5620
+ return BROWNFIELD_BROAD_ADOPT_SELECTORS.map((definition) => {
5621
+ const items = surfaces.filter(definition.matches);
5622
+ const pendingItems = items.filter((item) => !["accept", "accepted", "applied"].includes(item.current_state));
5623
+ const appliedItems = items.filter((item) => ["accept", "accepted", "applied"].includes(item.current_state));
5624
+ const blockedItems = items.filter((item) => item.human_review_required);
5625
+ return {
5626
+ selector: definition.selector,
5627
+ kind: definition.kind,
5628
+ label: definition.label,
5629
+ itemCount: items.length,
5630
+ pendingItemCount: pendingItems.length,
5631
+ appliedItemCount: appliedItems.length,
5632
+ blockedItemCount: blockedItems.length,
5633
+ previewCommand: importAdoptCommand(projectRoot, definition.selector, false),
5634
+ writeCommand: importAdoptCommand(projectRoot, definition.selector, true)
5635
+ };
5636
+ }).filter((selector) => selector.itemCount > 0);
5637
+ }
5638
+
5585
5639
  function summarizeImportAdoption(adoptionPlan, adoptionStatus, projectRoot) {
5586
5640
  const surfaces = adoptionPlan.imported_proposal_surfaces || [];
5587
5641
  const slugs = [];
@@ -5693,6 +5747,7 @@ function printBrownfieldImportPlan(payload) {
5693
5747
  }
5694
5748
 
5695
5749
  function buildBrownfieldImportAdoptListPayload(inputPath) {
5750
+ const artifacts = readImportAdoptionArtifacts(inputPath);
5696
5751
  const plan = buildBrownfieldImportPlanPayload(inputPath);
5697
5752
  const selectors = plan.bundles.map((bundle) => ({
5698
5753
  selector: `bundle:${bundle.bundle}`,
@@ -5706,12 +5761,15 @@ function buildBrownfieldImportAdoptListPayload(inputPath) {
5706
5761
  previewCommand: importAdoptCommand(plan.projectRoot, `bundle:${bundle.bundle}`, false),
5707
5762
  writeCommand: importAdoptCommand(plan.projectRoot, `bundle:${bundle.bundle}`, true)
5708
5763
  }));
5764
+ const broadSelectors = buildBrownfieldBroadAdoptSelectors(plan.projectRoot, artifacts.adoptionPlan);
5709
5765
  return {
5710
5766
  ok: true,
5711
5767
  projectRoot: plan.projectRoot,
5712
5768
  topogramRoot: plan.topogramRoot,
5713
5769
  selectorCount: selectors.length,
5714
5770
  selectors,
5771
+ broadSelectorCount: broadSelectors.length,
5772
+ broadSelectors,
5715
5773
  nextCommand: selectors.find((selector) => !selector.complete)?.previewCommand || plan.commands.status
5716
5774
  };
5717
5775
  }
@@ -5727,6 +5785,15 @@ function printBrownfieldImportAdoptList(payload) {
5727
5785
  console.log(` Preview: ${selector.previewCommand}`);
5728
5786
  console.log(` Write: ${selector.writeCommand}`);
5729
5787
  }
5788
+ if (payload.broadSelectors.length > 0) {
5789
+ console.log("");
5790
+ console.log("Broad selectors:");
5791
+ for (const selector of payload.broadSelectors) {
5792
+ console.log(`- ${selector.selector}: ${selector.itemCount} ${selector.label}`);
5793
+ console.log(` Preview: ${selector.previewCommand}`);
5794
+ console.log(` Write: ${selector.writeCommand}`);
5795
+ }
5796
+ }
5730
5797
  console.log("");
5731
5798
  console.log(`Next: ${payload.nextCommand}`);
5732
5799
  }
@@ -10,6 +10,10 @@ import {
10
10
  resolveGeneratorManifestForBinding,
11
11
  validateGeneratorManifest
12
12
  } from "./registry.js";
13
+ import {
14
+ generatorPolicyDiagnosticsForBindings,
15
+ loadGeneratorPolicy
16
+ } from "../generator-policy.js";
13
17
  import { generateDbContractGraph } from "./surfaces/databases/contract.js";
14
18
  import { generateDbLifecyclePlan } from "./surfaces/databases/lifecycle-shared.js";
15
19
  import {
@@ -252,6 +256,26 @@ function loadPackageGeneratorAdapter(manifest, component, options = {}) {
252
256
  throw new Error(`Component '${component?.id || "unknown"}' generator '${manifest.id}@${manifest.version}' is package-backed but does not declare a package.`);
253
257
  }
254
258
  const rootDir = options.configDir || options.rootDir || process.cwd();
259
+ const diagnostics = generatorPolicyDiagnosticsForBindings(
260
+ loadGeneratorPolicy(rootDir),
261
+ [{
262
+ componentId: String(component?.id || "unknown"),
263
+ componentType: String(component?.type || manifest.surface || "unknown"),
264
+ projection: String(component?.projection?.id || component?.projection || "unknown"),
265
+ generatorId: String(component?.generator?.id || manifest.id),
266
+ version: String(component?.generator?.version || manifest.version),
267
+ packageName
268
+ }],
269
+ "generator-adapter"
270
+ );
271
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
272
+ if (errors.length > 0) {
273
+ throw new Error(errors.map((diagnostic) =>
274
+ diagnostic.suggestedFix
275
+ ? `${diagnostic.message} Suggested fix: ${diagnostic.suggestedFix}`
276
+ : diagnostic.message
277
+ ).join("\n"));
278
+ }
255
279
  let moduleValue;
256
280
  try {
257
281
  moduleValue = requireFromProject(rootDir)(packageName);
@@ -25,6 +25,7 @@ import {
25
25
  * @property {Array<{ name: string, ok: boolean, message: string }>} checks
26
26
  * @property {string[]} errors
27
27
  * @property {{ files: number, artifacts: number, diagnostics: number }|null} smoke
28
+ * @property {boolean} executesPackageCode
28
29
  */
29
30
 
30
31
  /**
@@ -238,7 +239,8 @@ export function checkGeneratorPack(sourceSpec, options = {}) {
238
239
  manifest: null,
239
240
  checks: [],
240
241
  errors: [],
241
- smoke: null
242
+ smoke: null,
243
+ executesPackageCode: true
242
244
  };
243
245
 
244
246
  /** @type {any|null} */
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  import { getProjection } from "../shared.js";
4
+ import { generateServerContract } from "./server-contract.js";
4
5
 
5
6
  function renderPackageJson(profile) {
6
7
  const dependencies = profile === "express"
@@ -41,10 +42,10 @@ function routePath(path) {
41
42
  return String(path || "/").replace(/:([A-Za-z0-9_]+)/g, ":$1");
42
43
  }
43
44
 
44
- function renderHonoIndex(projection) {
45
- const routes = (projection.http || []).map((route) => {
45
+ function renderHonoIndex(projection, contract) {
46
+ const routes = (contract.routes || []).map((route) => {
46
47
  const method = String(route.method || "GET").toLowerCase();
47
- return `app.${method}("${routePath(route.path)}", (c) => c.json({ ok: true, capability: "${route.capabilityId}", input: { params: c.req.param(), query: c.req.query() } }, ${route.success || 200} as any));`;
48
+ return `app.${method}("${routePath(route.path)}", (c) => c.json({ ok: true, capability: "${route.capabilityId}", input: { params: c.req.param(), query: c.req.query() } }, ${route.successStatus || 200} as any));`;
48
49
  }).join("\n");
49
50
  return `import { serve } from "@hono/node-server";
50
51
  import { Hono } from "hono";
@@ -65,10 +66,10 @@ function expressPath(path) {
65
66
  return routePath(path);
66
67
  }
67
68
 
68
- function renderExpressIndex(projection) {
69
- const routes = (projection.http || []).map((route) => {
69
+ function renderExpressIndex(projection, contract) {
70
+ const routes = (contract.routes || []).map((route) => {
70
71
  const method = String(route.method || "GET").toLowerCase();
71
- return `app.${method}("${expressPath(route.path)}", (req, res) => res.status(${route.success || 200}).json({ ok: true, capability: "${route.capabilityId}", input: { params: req.params, query: req.query } }));`;
72
+ return `app.${method}("${expressPath(route.path)}", (req, res) => res.status(${route.successStatus || 200}).json({ ok: true, capability: "${route.capabilityId}", input: { params: req.params, query: req.query } }));`;
72
73
  }).join("\n");
73
74
  return `import express from "express";
74
75
 
@@ -88,10 +89,11 @@ app.listen(port, () => {
88
89
 
89
90
  export function generateStatelessServer(graph, options = {}) {
90
91
  const projection = getProjection(graph, options.projectionId);
92
+ const contract = generateServerContract(graph, { ...options, projectionId: projection.id });
91
93
  const profile = options.profile === "express" ? "express" : "hono";
92
94
  return {
93
95
  "package.json": renderPackageJson(profile),
94
96
  "tsconfig.json": renderTsconfig(),
95
- "src/index.ts": profile === "express" ? renderExpressIndex(projection) : renderHonoIndex(projection)
97
+ "src/index.ts": profile === "express" ? renderExpressIndex(projection, contract) : renderHonoIndex(projection, contract)
96
98
  };
97
99
  }
package/src/workflows.js CHANGED
@@ -1679,7 +1679,8 @@ function summarizeBundleParticipants(bundle) {
1679
1679
  }
1680
1680
 
1681
1681
  function summarizeBundleSurface(bundle, values, empty = "_none_") {
1682
- return values.length ? values.map((item) => `\`${item}\``).join(", ") : empty;
1682
+ const list = Array.isArray(values) ? values : [];
1683
+ return list.length ? list.map((item) => `\`${item}\``).join(", ") : empty;
1683
1684
  }
1684
1685
 
1685
1686
  function buildBundleOperatorSummary(bundle) {
@@ -1693,6 +1694,7 @@ function buildBundleOperatorSummary(bundle) {
1693
1694
  bundle.id;
1694
1695
  const participants = summarizeBundleParticipants(bundle);
1695
1696
  const capabilityIds = [...new Set((bundle.capabilities || []).map((entry) => entry.id_hint))].slice(0, 4);
1697
+ const componentIds = [...new Set((bundle.components || []).map((entry) => entry.id_hint))].slice(0, 4);
1696
1698
  const screenIds = [...new Set((bundle.screens || []).map((entry) => entry.id_hint))].slice(0, 4);
1697
1699
  const routePaths = [...new Set((bundle.uiRoutes || []).map((entry) => entry.path).filter(Boolean))].slice(0, 4);
1698
1700
  const workflowIds = [...new Set((bundle.workflows || []).map((entry) => entry.id_hint))].slice(0, 4);
@@ -1708,6 +1710,7 @@ function buildBundleOperatorSummary(bundle) {
1708
1710
  const evidenceKinds = [
1709
1711
  (bundle.entities || []).length > 0 ? "entity evidence" : null,
1710
1712
  (bundle.capabilities || []).length > 0 ? "API capability evidence" : null,
1713
+ (bundle.components || []).length > 0 ? "UI component evidence" : null,
1711
1714
  (bundle.screens || []).length > 0 || (bundle.uiRoutes || []).length > 0 ? "UI screen/route evidence" : null,
1712
1715
  (bundle.workflows || []).length > 0 ? "workflow evidence" : null,
1713
1716
  (bundle.docs || []).length > 0 ? "doc evidence" : null,
@@ -1723,6 +1726,7 @@ function buildBundleOperatorSummary(bundle) {
1723
1726
  primaryEntityId,
1724
1727
  participants,
1725
1728
  capabilityIds,
1729
+ componentIds,
1726
1730
  screenIds,
1727
1731
  routePaths,
1728
1732
  workflowIds,
@@ -2711,6 +2715,7 @@ function renderCandidateBundleReadme(bundle, proposalSurfaces = []) {
2711
2715
  `Enums: ${bundle.enums.length}`,
2712
2716
  `Capabilities: ${bundle.capabilities.length}`,
2713
2717
  `Shapes: ${bundle.shapes.length}`,
2718
+ `Components: ${bundle.components.length}`,
2714
2719
  `Screens: ${bundle.screens.length}`,
2715
2720
  `UI routes: ${bundle.uiRoutes.length}`,
2716
2721
  `UI actions: ${bundle.uiActions.length}`,
@@ -2728,6 +2733,7 @@ function renderCandidateBundleReadme(bundle, proposalSurfaces = []) {
2728
2733
  `- Primary entity: ${summary.primaryEntityId ? `\`${summary.primaryEntityId}\`` : "_none_"}`,
2729
2734
  `- Participants: ${summary.participants.label}`,
2730
2735
  `- Main capabilities: ${summarizeBundleSurface(bundle, summary.capabilityIds)}`,
2736
+ `- Main components: ${summarizeBundleSurface(bundle, summary.componentIds)}`,
2731
2737
  `- Main screens: ${summarizeBundleSurface(bundle, summary.screenIds)}`,
2732
2738
  `- Main routes: ${summarizeBundleSurface(bundle, summary.routePaths)}`,
2733
2739
  `- Main workflows: ${summarizeBundleSurface(bundle, summary.workflowIds)}`,
@@ -7581,6 +7587,7 @@ function reconcileWorkflow(inputPath, options = {}) {
7581
7587
  enums: bundle.enums.map((entry) => entry.id_hint),
7582
7588
  capabilities: bundle.capabilities.map((entry) => entry.id_hint),
7583
7589
  shapes: bundle.shapes.map((entry) => entry.id),
7590
+ components: bundle.components.map((entry) => entry.id_hint),
7584
7591
  screens: bundle.screens.map((entry) => entry.id_hint),
7585
7592
  workflows: bundle.workflows.map((entry) => entry.id_hint),
7586
7593
  docs: bundle.docs.map((entry) => entry.id),
@@ -7604,10 +7611,11 @@ function reconcileWorkflow(inputPath, options = {}) {
7604
7611
  : "## Promoted Canonical Items";
7605
7612
  files["candidates/reconcile/report.json"] = `${stableStringify(report)}\n`;
7606
7613
  const candidateModelBundlesMarkdown = report.candidate_model_bundles.length
7607
- ? report.candidate_model_bundles.map((bundle) => `- \`${bundle.slug}\` (${bundle.actors.length} actors, ${bundle.roles.length} roles, ${bundle.entities.length} entities, ${bundle.enums.length} enums, ${bundle.capabilities.length} capabilities, ${bundle.shapes.length} shapes, ${bundle.screens.length} screens, ${bundle.workflows.length} workflows, ${bundle.docs.length} docs)
7614
+ ? report.candidate_model_bundles.map((bundle) => `- \`${bundle.slug}\` (${bundle.actors.length} actors, ${bundle.roles.length} roles, ${bundle.entities.length} entities, ${bundle.enums.length} enums, ${bundle.capabilities.length} capabilities, ${bundle.shapes.length} shapes, ${bundle.components.length} components, ${bundle.screens.length} screens, ${bundle.workflows.length} workflows, ${bundle.docs.length} docs)
7608
7615
  - primary concept \`${bundle.operator_summary.primaryConcept}\`${bundle.operator_summary.primaryEntityId ? `, primary entity \`${bundle.operator_summary.primaryEntityId}\`` : ""}
7609
7616
  - participants ${bundle.operator_summary.participants.label}
7610
7617
  - main capabilities ${summarizeBundleSurface(bundle, bundle.operator_summary.capabilityIds)}
7618
+ - main components ${summarizeBundleSurface(bundle, bundle.operator_summary.componentIds)}
7611
7619
  - main routes ${summarizeBundleSurface(bundle, bundle.operator_summary.routePaths)}
7612
7620
  - candidate maintained seam mappings ${renderMaintainedSeamCandidatesInline(bundle)}
7613
7621
  - permission hints ${bundle.auth_permission_hints?.length ? bundle.auth_permission_hints.map((entry) => formatAuthPermissionHintInline(entry)).join(", ") : "_none_"}