@topogram/cli 0.3.63 → 0.3.65
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 +1 -1
- package/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.d.ts +6 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +12 -0
- package/src/catalog.js +18 -750
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +32 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +17 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +35 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +31 -0
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +11 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +43 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +44 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +67 -910
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2188
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +87 -0
- package/src/parser.js +118 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -560
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +7 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/validator.d.ts +2 -0
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +4 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +32 -0
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +37 -0
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +14 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +16 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { githubRepoSlug } from "../topogram-config.js";
|
|
7
|
+
import { cliDependencyForProject, generatorDependenciesForTemplate, isSameOrInside, packageNameFromPath, writeProjectNpmConfig } from "./package-spec.js";
|
|
8
|
+
|
|
9
|
+
/** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
|
|
10
|
+
/** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
|
|
11
|
+
/** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
|
|
12
|
+
/** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
|
|
13
|
+
/** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
|
|
14
|
+
/** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
|
|
15
|
+
/** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
|
|
16
|
+
/** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
|
|
17
|
+
/** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
|
|
18
|
+
/** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
|
|
19
|
+
/** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} projectRoot
|
|
23
|
+
* @param {string} engineRoot
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
export function assertProjectOutsideEngine(projectRoot, engineRoot) {
|
|
27
|
+
if (isSameOrInside(path.resolve(engineRoot), path.resolve(projectRoot))) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Refusing to create a generated project inside the engine directory. Use a path outside engine, for example '../${path.basename(projectRoot)}'.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} projectRoot
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
export function ensureCreatableProjectRoot(projectRoot) {
|
|
39
|
+
if (!fs.existsSync(projectRoot)) {
|
|
40
|
+
fs.mkdirSync(projectRoot, { recursive: true });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!fs.statSync(projectRoot).isDirectory()) {
|
|
44
|
+
throw new Error(`Cannot create project at '${projectRoot}' because it is not a directory.`);
|
|
45
|
+
}
|
|
46
|
+
/** @type {string[]} */
|
|
47
|
+
const dirEntries = fs.readdirSync(projectRoot);
|
|
48
|
+
const entries = dirEntries.filter((entry) => entry !== ".DS_Store");
|
|
49
|
+
if (entries.length > 0) {
|
|
50
|
+
throw new Error(`Refusing to create a Topogram project in non-empty directory '${projectRoot}'.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} templateRoot
|
|
56
|
+
* @param {string} projectRoot
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
export function copyTopogramWorkspace(templateRoot, projectRoot) {
|
|
60
|
+
const topogramRoot = path.join(projectRoot, "topogram");
|
|
61
|
+
fs.cpSync(path.join(templateRoot, "topogram"), topogramRoot, { recursive: true });
|
|
62
|
+
|
|
63
|
+
fs.cpSync(
|
|
64
|
+
path.join(templateRoot, "topogram.project.json"),
|
|
65
|
+
path.join(projectRoot, "topogram.project.json")
|
|
66
|
+
);
|
|
67
|
+
const implementationRoot = path.join(templateRoot, "implementation");
|
|
68
|
+
if (fs.existsSync(implementationRoot)) {
|
|
69
|
+
fs.cpSync(
|
|
70
|
+
implementationRoot,
|
|
71
|
+
path.join(projectRoot, "implementation"),
|
|
72
|
+
{ recursive: true }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} projectRoot
|
|
79
|
+
* @param {string} engineRoot
|
|
80
|
+
* @param {ResolvedTemplate} template
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
export function writeProjectPackage(projectRoot, engineRoot, template) {
|
|
84
|
+
const cliDependency = cliDependencyForProject(projectRoot, engineRoot);
|
|
85
|
+
const generatorDependencies = generatorDependenciesForTemplate(template.root);
|
|
86
|
+
const starterScripts = template.manifest.starterScripts || {};
|
|
87
|
+
const pkg = {
|
|
88
|
+
name: packageNameFromPath(projectRoot),
|
|
89
|
+
private: true,
|
|
90
|
+
type: "module",
|
|
91
|
+
scripts: {
|
|
92
|
+
explain: "node ./scripts/explain.mjs",
|
|
93
|
+
doctor: "topogram doctor",
|
|
94
|
+
"agent:brief": "topogram agent brief --json",
|
|
95
|
+
"source:status": "topogram source status --local",
|
|
96
|
+
"source:status:remote": "topogram source status --remote",
|
|
97
|
+
check: "topogram check",
|
|
98
|
+
"check:json": "topogram check --json",
|
|
99
|
+
"query:list": "topogram query list --json",
|
|
100
|
+
"query:show": "topogram query show",
|
|
101
|
+
generate: "topogram generate",
|
|
102
|
+
"template:explain": "topogram template explain",
|
|
103
|
+
"template:status": "topogram template status",
|
|
104
|
+
"template:detach": "topogram template detach",
|
|
105
|
+
"template:detach:dry-run": "topogram template detach --dry-run",
|
|
106
|
+
"template:policy:check": "topogram template policy check",
|
|
107
|
+
"template:policy:explain": "topogram template policy explain",
|
|
108
|
+
"generator:policy:status": "topogram generator policy status",
|
|
109
|
+
"generator:policy:check": "topogram generator policy check",
|
|
110
|
+
"generator:policy:explain": "topogram generator policy explain",
|
|
111
|
+
"template:update:status": "topogram template update --status",
|
|
112
|
+
"template:update:recommend": "topogram template update --recommend",
|
|
113
|
+
"template:update:plan": "topogram template update --plan",
|
|
114
|
+
"template:update:check": "topogram template update --check",
|
|
115
|
+
"template:update:apply": "topogram template update --apply",
|
|
116
|
+
"trust:status": "topogram trust status",
|
|
117
|
+
"trust:diff": "topogram trust diff",
|
|
118
|
+
verify: "npm run app:compile",
|
|
119
|
+
bootstrap: "npm run app:bootstrap",
|
|
120
|
+
dev: "npm run app:dev",
|
|
121
|
+
"app:bootstrap": "npm --prefix ./app run bootstrap",
|
|
122
|
+
"app:dev": "npm --prefix ./app run dev",
|
|
123
|
+
"app:compile": "npm --prefix ./app run compile",
|
|
124
|
+
"app:smoke": "npm --prefix ./app run smoke",
|
|
125
|
+
"app:runtime-check": "npm --prefix ./app run runtime-check",
|
|
126
|
+
"app:check": "npm run app:compile",
|
|
127
|
+
"app:probe": "npm run app:smoke && npm run app:runtime-check",
|
|
128
|
+
"app:runtime": "npm --prefix ./app run runtime",
|
|
129
|
+
...starterScripts
|
|
130
|
+
},
|
|
131
|
+
devDependencies: {
|
|
132
|
+
[cliDependency.name]: cliDependency.spec,
|
|
133
|
+
...generatorDependencies
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
fs.writeFileSync(path.join(projectRoot, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
|
137
|
+
writeProjectNpmConfig(projectRoot, cliDependency);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {string} projectRoot
|
|
142
|
+
* @returns {void}
|
|
143
|
+
*/
|
|
144
|
+
export function writeExplainScript(projectRoot) {
|
|
145
|
+
const scriptDir = path.join(projectRoot, "scripts");
|
|
146
|
+
fs.mkdirSync(scriptDir, { recursive: true });
|
|
147
|
+
const script = `const message = \`
|
|
148
|
+
Topogram app workflow
|
|
149
|
+
|
|
150
|
+
1. Edit:
|
|
151
|
+
topogram/
|
|
152
|
+
topogram.project.json
|
|
153
|
+
|
|
154
|
+
2. Start with project guidance:
|
|
155
|
+
npm run agent:brief
|
|
156
|
+
|
|
157
|
+
3. Validate:
|
|
158
|
+
npm run doctor
|
|
159
|
+
npm run source:status
|
|
160
|
+
npm run template:explain
|
|
161
|
+
npm run check
|
|
162
|
+
|
|
163
|
+
4. Regenerate:
|
|
164
|
+
npm run generate
|
|
165
|
+
|
|
166
|
+
5. Verify generated app:
|
|
167
|
+
npm run verify
|
|
168
|
+
|
|
169
|
+
6. Run locally:
|
|
170
|
+
npm run bootstrap
|
|
171
|
+
npm run dev
|
|
172
|
+
|
|
173
|
+
7. Probe the running app from another terminal:
|
|
174
|
+
npm run app:probe
|
|
175
|
+
|
|
176
|
+
Or run self-contained local runtime verification:
|
|
177
|
+
npm run app:runtime
|
|
178
|
+
|
|
179
|
+
Useful inspection:
|
|
180
|
+
npm run agent:brief
|
|
181
|
+
npm run check:json
|
|
182
|
+
topogram emit ui-widget-contract ./topogram --json
|
|
183
|
+
topogram emit widget-conformance-report ./topogram --json
|
|
184
|
+
npm run doctor
|
|
185
|
+
npm run source:status
|
|
186
|
+
npm run source:status:remote
|
|
187
|
+
npm run template:explain
|
|
188
|
+
npm run template:status
|
|
189
|
+
npm run template:detach:dry-run
|
|
190
|
+
npm run template:policy:check
|
|
191
|
+
npm run template:policy:explain
|
|
192
|
+
npm run generator:policy:status
|
|
193
|
+
npm run generator:policy:check
|
|
194
|
+
npm run generator:policy:explain
|
|
195
|
+
npm run template:update:status
|
|
196
|
+
npm run template:update:recommend
|
|
197
|
+
npm run template:update:plan
|
|
198
|
+
npm run template:update:check
|
|
199
|
+
npm run template:update:apply
|
|
200
|
+
npm run trust:status
|
|
201
|
+
npm run trust:diff
|
|
202
|
+
\`;
|
|
203
|
+
|
|
204
|
+
console.log(message.trimEnd());
|
|
205
|
+
`;
|
|
206
|
+
fs.writeFileSync(path.join(scriptDir, "explain.mjs"), script, "utf8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} projectRoot
|
|
211
|
+
* @param {Record<string, any>} projectConfig
|
|
212
|
+
* @returns {void}
|
|
213
|
+
*/
|
|
214
|
+
export function writeProjectReadme(projectRoot, projectConfig) {
|
|
215
|
+
const template = projectConfig.template || {};
|
|
216
|
+
const templateName = template.id || "unknown";
|
|
217
|
+
const workflowCommands = [
|
|
218
|
+
"npm install",
|
|
219
|
+
"npm run explain",
|
|
220
|
+
"npm run agent:brief",
|
|
221
|
+
"npm run doctor",
|
|
222
|
+
"npm run source:status",
|
|
223
|
+
"npm run template:explain",
|
|
224
|
+
"npm run check",
|
|
225
|
+
"npm run template:policy:check",
|
|
226
|
+
"npm run generator:policy:status",
|
|
227
|
+
"npm run generator:policy:check",
|
|
228
|
+
...(template.includesExecutableImplementation ? [
|
|
229
|
+
"npm run template:policy:explain",
|
|
230
|
+
"npm run trust:status"
|
|
231
|
+
] : []),
|
|
232
|
+
"npm run generate",
|
|
233
|
+
"npm run verify"
|
|
234
|
+
];
|
|
235
|
+
const provenanceLines = [];
|
|
236
|
+
provenanceLines.push(`- Template: \`${templateName}@${template.version || "unknown"}\``);
|
|
237
|
+
provenanceLines.push(`- Source: \`${template.source || "unknown"}\``);
|
|
238
|
+
if (template.sourceSpec) {
|
|
239
|
+
provenanceLines.push(`- Source spec: \`${template.sourceSpec}\``);
|
|
240
|
+
}
|
|
241
|
+
if (template.catalog) {
|
|
242
|
+
provenanceLines.push(`- Catalog: \`${template.catalog.id}\` from \`${template.catalog.source}\``);
|
|
243
|
+
provenanceLines.push(`- Package: \`${template.catalog.packageSpec}\``);
|
|
244
|
+
}
|
|
245
|
+
provenanceLines.push(`- Executable implementation: \`${template.includesExecutableImplementation ? "yes" : "no"}\``);
|
|
246
|
+
const readme = `# ${packageNameFromPath(projectRoot)}
|
|
247
|
+
|
|
248
|
+
Generated by \`topogram new\`.
|
|
249
|
+
|
|
250
|
+
## Template
|
|
251
|
+
|
|
252
|
+
${provenanceLines.join("\n")}
|
|
253
|
+
|
|
254
|
+
## Workflow
|
|
255
|
+
|
|
256
|
+
\`\`\`bash
|
|
257
|
+
${workflowCommands.join("\n")}
|
|
258
|
+
\`\`\`
|
|
259
|
+
|
|
260
|
+
Edit \`topogram/\` and \`topogram.project.json\`, then regenerate with \`npm run generate\`.
|
|
261
|
+
Generated app code is written to \`app/\`.
|
|
262
|
+
Use \`topogram emit <target>\` to inspect contracts, reports, snapshots, and other artifacts without regenerating the app.
|
|
263
|
+
Agents should start with \`AGENTS.md\` and \`npm run agent:brief\`. The direct \`topogram agent brief --json\` command is the canonical machine-readable first-run guidance.
|
|
264
|
+
${template.includesExecutableImplementation ? "\nThis template copied `implementation/` code. `topogram new` did not execute it; review `implementation/`, `topogram.template-policy.json`, and `.topogram-template-trust.json` before regenerating after edits.\n" : ""}
|
|
265
|
+
`;
|
|
266
|
+
fs.writeFileSync(path.join(projectRoot, "README.md"), readme, "utf8");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @param {string} projectRoot
|
|
271
|
+
* @param {Record<string, any>} projectConfig
|
|
272
|
+
* @returns {void}
|
|
273
|
+
*/
|
|
274
|
+
export function writeAgentsGuide(projectRoot, projectConfig) {
|
|
275
|
+
const template = projectConfig.template || {};
|
|
276
|
+
const hasImplementation = Boolean(projectConfig.implementation || template.includesExecutableImplementation);
|
|
277
|
+
const guide = `# Agent Guide
|
|
278
|
+
|
|
279
|
+
Start here before editing this Topogram project.
|
|
280
|
+
|
|
281
|
+
## First Read
|
|
282
|
+
|
|
283
|
+
1. \`AGENTS.md\`
|
|
284
|
+
2. \`README.md\`
|
|
285
|
+
3. \`topogram.project.json\`
|
|
286
|
+
4. \`topogram.template-policy.json\`
|
|
287
|
+
5. \`topogram.generator-policy.json\`
|
|
288
|
+
${hasImplementation ? "6. `.topogram-template-trust.json`\n7. `implementation/`\n8. Focused `topogram query ...` output\n" : "6. Focused `topogram query ...` output\n"}
|
|
289
|
+
Machine-readable source:
|
|
290
|
+
|
|
291
|
+
\`\`\`bash
|
|
292
|
+
topogram agent brief --json
|
|
293
|
+
\`\`\`
|
|
294
|
+
|
|
295
|
+
Local shortcut:
|
|
296
|
+
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
npm run agent:brief
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
Reference: https://github.com/${githubRepoSlug(null)}/blob/main/docs/agent-first-run.md
|
|
302
|
+
|
|
303
|
+
## First Commands
|
|
304
|
+
|
|
305
|
+
\`\`\`bash
|
|
306
|
+
npm run agent:brief
|
|
307
|
+
npm run doctor
|
|
308
|
+
npm run source:status
|
|
309
|
+
npm run template:explain
|
|
310
|
+
npm run generator:policy:check
|
|
311
|
+
${hasImplementation ? "npm run trust:status\n" : ""}npm run check
|
|
312
|
+
npm run query:list
|
|
313
|
+
npm run query:show -- widget-behavior
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
## Edit Rules
|
|
317
|
+
|
|
318
|
+
- Edit \`topogram/**\` and \`topogram.project.json\` first.
|
|
319
|
+
- Review policy files before editing \`topogram.template-policy.json\` or \`topogram.generator-policy.json\`.
|
|
320
|
+
- Do not make lasting edits under generated-owned \`app/**\`; use \`npm run generate\` to replace generated output.
|
|
321
|
+
- If an output is changed to maintained ownership, agents may edit that app code directly after reading focused query packets.
|
|
322
|
+
|
|
323
|
+
## UI And Widgets
|
|
324
|
+
|
|
325
|
+
- \`ui_contract\` owns screens, regions, widget bindings, behavior, visibility, and semantic design tokens.
|
|
326
|
+
- Web/iOS/Android surfaces realize the shared UI contract; they do not own widget placement.
|
|
327
|
+
- Use \`topogram widget check --json\`, \`topogram widget behavior --json\`, and focused \`topogram query ...\` packets after UI edits.
|
|
328
|
+
|
|
329
|
+
## Template And Trust
|
|
330
|
+
|
|
331
|
+
- Local edits to template-derived Topogram files are project-owned.
|
|
332
|
+
- Use \`npm run source:status\` and \`npm run template:update:recommend\` before applying template updates.
|
|
333
|
+
${hasImplementation ? "- This project has executable `implementation/` code. `topogram new` did not execute it. Do not refresh trust until the implementation has been reviewed.\n" : "- This template does not declare executable implementation code.\n"}
|
|
334
|
+
## Import And Adoption
|
|
335
|
+
|
|
336
|
+
- If \`.topogram-import.json\` exists, run \`topogram import check .\`, \`topogram import plan .\`, \`topogram import adopt --list .\`, and \`topogram import history . --verify\`.
|
|
337
|
+
- Imported Topogram files are project-owned after adoption; source hashes record trusted import evidence at the time of import.
|
|
338
|
+
|
|
339
|
+
## Verification Gates
|
|
340
|
+
|
|
341
|
+
\`\`\`bash
|
|
342
|
+
npm run check
|
|
343
|
+
npm run generate
|
|
344
|
+
npm run verify
|
|
345
|
+
\`\`\`
|
|
346
|
+
`;
|
|
347
|
+
fs.writeFileSync(path.join(projectRoot, "AGENTS.md"), guide, "utf8");
|
|
348
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { TEMPLATE_POLICY_FILE } from "./constants.js";
|
|
7
|
+
import { stableJsonStringify } from "./json.js";
|
|
8
|
+
import { currentTemplateMetadata } from "./metadata.js";
|
|
9
|
+
import { packageScopeFromSpec } from "./package-spec.js";
|
|
10
|
+
|
|
11
|
+
/** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
|
|
12
|
+
/** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
|
|
13
|
+
/** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
|
|
14
|
+
/** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
|
|
15
|
+
/** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
|
|
16
|
+
/** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
|
|
17
|
+
/** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
|
|
18
|
+
/** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
|
|
19
|
+
/** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
|
|
20
|
+
/** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
|
|
21
|
+
/** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Record<string, any>} input
|
|
25
|
+
* @returns {TemplateUpdateDiagnostic}
|
|
26
|
+
*/
|
|
27
|
+
export function templateUpdateDiagnostic(input) {
|
|
28
|
+
return {
|
|
29
|
+
code: String(input.code || "template_update_failed"),
|
|
30
|
+
severity: input.severity === "warning" ? "warning" : "error",
|
|
31
|
+
message: String(input.message || "Template update failed."),
|
|
32
|
+
path: typeof input.path === "string" ? input.path : null,
|
|
33
|
+
suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
|
|
34
|
+
step: typeof input.step === "string" ? input.step : null
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {unknown} value
|
|
40
|
+
* @param {string} policyPath
|
|
41
|
+
* @returns {TemplatePolicy}
|
|
42
|
+
*/
|
|
43
|
+
function validateTemplatePolicy(value, policyPath) {
|
|
44
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
throw new Error(`${TEMPLATE_POLICY_FILE} must contain a JSON object.`);
|
|
46
|
+
}
|
|
47
|
+
const policy = /** @type {Record<string, unknown>} */ (value);
|
|
48
|
+
const version = typeof policy.version === "string" && policy.version ? policy.version : "0.1";
|
|
49
|
+
const allowedSources = Array.isArray(policy.allowedSources) ? policy.allowedSources : ["local", "package"];
|
|
50
|
+
const invalidSource = allowedSources.find((source) => !["local", "package"].includes(String(source)));
|
|
51
|
+
if (invalidSource) {
|
|
52
|
+
throw new Error(`${policyPath} has invalid allowedSources value '${String(invalidSource)}'.`);
|
|
53
|
+
}
|
|
54
|
+
const allowedTemplateIds = Array.isArray(policy.allowedTemplateIds)
|
|
55
|
+
? policy.allowedTemplateIds.map(String).filter(Boolean)
|
|
56
|
+
: [];
|
|
57
|
+
const allowedPackageScopes = Array.isArray(policy.allowedPackageScopes)
|
|
58
|
+
? policy.allowedPackageScopes.map(String).filter(Boolean)
|
|
59
|
+
: [];
|
|
60
|
+
const executableImplementation = policy.executableImplementation === "deny" || policy.executableImplementation === "warn"
|
|
61
|
+
? policy.executableImplementation
|
|
62
|
+
: "allow";
|
|
63
|
+
const pinnedVersions = policy.pinnedVersions && typeof policy.pinnedVersions === "object" && !Array.isArray(policy.pinnedVersions)
|
|
64
|
+
? Object.fromEntries(Object.entries(policy.pinnedVersions).filter(([, pin]) => typeof pin === "string"))
|
|
65
|
+
: {};
|
|
66
|
+
return {
|
|
67
|
+
version,
|
|
68
|
+
allowedSources: /** @type {Array<"local"|"package">} */ (allowedSources),
|
|
69
|
+
allowedTemplateIds,
|
|
70
|
+
allowedPackageScopes,
|
|
71
|
+
executableImplementation,
|
|
72
|
+
pinnedVersions
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} projectRoot
|
|
78
|
+
* @returns {TemplatePolicyInfo}
|
|
79
|
+
*/
|
|
80
|
+
export function loadTemplatePolicy(projectRoot) {
|
|
81
|
+
const policyPath = path.join(projectRoot, TEMPLATE_POLICY_FILE);
|
|
82
|
+
if (!fs.existsSync(policyPath)) {
|
|
83
|
+
return {
|
|
84
|
+
path: policyPath,
|
|
85
|
+
policy: null,
|
|
86
|
+
exists: false,
|
|
87
|
+
diagnostics: []
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return {
|
|
92
|
+
path: policyPath,
|
|
93
|
+
policy: validateTemplatePolicy(JSON.parse(fs.readFileSync(policyPath, "utf8")), policyPath),
|
|
94
|
+
exists: true,
|
|
95
|
+
diagnostics: []
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
path: policyPath,
|
|
100
|
+
policy: null,
|
|
101
|
+
exists: true,
|
|
102
|
+
diagnostics: [templateUpdateDiagnostic({
|
|
103
|
+
code: "template_policy_invalid",
|
|
104
|
+
message: error instanceof Error ? error.message : String(error),
|
|
105
|
+
path: policyPath,
|
|
106
|
+
suggestedFix: "Fix topogram.template-policy.json or regenerate it with `topogram template policy init`.",
|
|
107
|
+
step: "policy"
|
|
108
|
+
})]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {ResolvedTemplate} template
|
|
115
|
+
* @returns {TemplatePolicy}
|
|
116
|
+
*/
|
|
117
|
+
export function defaultTemplatePolicyForTemplate(template) {
|
|
118
|
+
const allowedPackageScopes = [];
|
|
119
|
+
const idScope = template.source === "package"
|
|
120
|
+
? packageScopeFromSpec(template.packageSpec || template.requested)
|
|
121
|
+
: null;
|
|
122
|
+
if (template.source === "package" && idScope) {
|
|
123
|
+
allowedPackageScopes.push(idScope);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
version: "0.1",
|
|
127
|
+
allowedSources: ["local", "package"],
|
|
128
|
+
allowedTemplateIds: [template.manifest.id],
|
|
129
|
+
allowedPackageScopes,
|
|
130
|
+
executableImplementation: "allow",
|
|
131
|
+
pinnedVersions: {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} projectRoot
|
|
137
|
+
* @param {TemplatePolicy} policy
|
|
138
|
+
* @returns {TemplatePolicy}
|
|
139
|
+
*/
|
|
140
|
+
export function writeTemplatePolicy(projectRoot, policy) {
|
|
141
|
+
fs.writeFileSync(path.join(projectRoot, TEMPLATE_POLICY_FILE), `${stableJsonStringify(policy)}\n`, "utf8");
|
|
142
|
+
return policy;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {string} projectRoot
|
|
147
|
+
* @param {Record<string, any>} projectConfig
|
|
148
|
+
* @returns {TemplatePolicy}
|
|
149
|
+
*/
|
|
150
|
+
export function writeTemplatePolicyForProject(projectRoot, projectConfig) {
|
|
151
|
+
const current = currentTemplateMetadata(projectConfig);
|
|
152
|
+
/** @type {string[]} */
|
|
153
|
+
const allowedPackageScopes = [];
|
|
154
|
+
if (current.source === "package") {
|
|
155
|
+
const currentScope = packageScopeFromSpec(current.sourceSpec) ||
|
|
156
|
+
(current.id?.startsWith("@") ? current.id.split("/")[0] : null);
|
|
157
|
+
if (currentScope) {
|
|
158
|
+
allowedPackageScopes.push(currentScope);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return writeTemplatePolicy(projectRoot, {
|
|
162
|
+
version: "0.1",
|
|
163
|
+
allowedSources: ["local", "package"],
|
|
164
|
+
allowedTemplateIds: current.id ? [current.id] : [],
|
|
165
|
+
allowedPackageScopes,
|
|
166
|
+
executableImplementation: "allow",
|
|
167
|
+
pinnedVersions: {}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {TemplatePolicyInfo} policyInfo
|
|
173
|
+
* @param {ResolvedTemplate} template
|
|
174
|
+
* @param {string} step
|
|
175
|
+
* @returns {TemplateUpdateDiagnostic[]}
|
|
176
|
+
*/
|
|
177
|
+
export function templatePolicyDiagnosticsForTemplate(policyInfo, template, step) {
|
|
178
|
+
if (policyInfo.diagnostics.length > 0) {
|
|
179
|
+
return policyInfo.diagnostics;
|
|
180
|
+
}
|
|
181
|
+
if (!policyInfo.policy) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const policy = policyInfo.policy;
|
|
185
|
+
/** @type {TemplateUpdateDiagnostic[]} */
|
|
186
|
+
const diagnostics = [];
|
|
187
|
+
if (policy.allowedSources.length > 0 && !policy.allowedSources.includes(template.source)) {
|
|
188
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
189
|
+
code: "template_source_denied",
|
|
190
|
+
message: `Template source '${template.source}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
|
|
191
|
+
path: policyInfo.path,
|
|
192
|
+
suggestedFix: `Run \`topogram template policy init\` to reset from the current project, or add '${template.source}' to allowedSources after review.`,
|
|
193
|
+
step
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
if (policy.allowedTemplateIds.length > 0 && !policy.allowedTemplateIds.includes(template.manifest.id)) {
|
|
197
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
198
|
+
code: "template_id_denied",
|
|
199
|
+
message: `Template '${template.manifest.id}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
|
|
200
|
+
path: policyInfo.path,
|
|
201
|
+
suggestedFix: `Run \`topogram template policy pin ${template.manifest.id}@${template.manifest.version}\` after review, or choose an allowed template.`,
|
|
202
|
+
step
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
if (template.source === "package" && policy.allowedPackageScopes && policy.allowedPackageScopes.length > 0) {
|
|
206
|
+
const scope = packageScopeFromSpec(template.packageSpec || template.requested) ||
|
|
207
|
+
(template.manifest.id.startsWith("@") ? template.manifest.id.split("/")[0] : null);
|
|
208
|
+
if (!scope || !policy.allowedPackageScopes.includes(scope)) {
|
|
209
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
210
|
+
code: "template_package_scope_denied",
|
|
211
|
+
message: `Template package scope '${scope || "(unscoped)"}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
|
|
212
|
+
path: policyInfo.path,
|
|
213
|
+
suggestedFix: `Add '${scope || "(unscoped)"}' to allowedPackageScopes after review, or choose a package from an allowed scope.`,
|
|
214
|
+
step
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const pinnedVersion = policy.pinnedVersions?.[template.manifest.id];
|
|
219
|
+
if (pinnedVersion && pinnedVersion !== template.manifest.version) {
|
|
220
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
221
|
+
code: "template_version_mismatch",
|
|
222
|
+
message: `Template '${template.manifest.id}' is pinned to version '${pinnedVersion}', but candidate version is '${template.manifest.version}'.`,
|
|
223
|
+
path: policyInfo.path,
|
|
224
|
+
suggestedFix: `Run \`topogram template policy pin ${template.manifest.id}@${template.manifest.version}\` after review, or use version '${pinnedVersion}'.`,
|
|
225
|
+
step
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
if (template.manifest.includesExecutableImplementation) {
|
|
229
|
+
if (policy.executableImplementation === "deny") {
|
|
230
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
231
|
+
code: "template_executable_denied",
|
|
232
|
+
message: `Template '${template.manifest.id}' includes executable implementation code, which is denied by ${TEMPLATE_POLICY_FILE}.`,
|
|
233
|
+
path: policyInfo.path,
|
|
234
|
+
suggestedFix: "Use a non-executable template, or set executableImplementation to 'allow' after reviewing implementation/.",
|
|
235
|
+
step
|
|
236
|
+
}));
|
|
237
|
+
} else if (policy.executableImplementation === "warn") {
|
|
238
|
+
diagnostics.push(templateUpdateDiagnostic({
|
|
239
|
+
code: "template_executable_warning",
|
|
240
|
+
severity: "warning",
|
|
241
|
+
message: `Template '${template.manifest.id}' includes executable implementation code.`,
|
|
242
|
+
path: policyInfo.path,
|
|
243
|
+
suggestedFix: "Review implementation/ before running topogram generate.",
|
|
244
|
+
step
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return diagnostics;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {string} projectRoot
|
|
253
|
+
* @param {ResolvedTemplate} template
|
|
254
|
+
* @param {string} step
|
|
255
|
+
* @returns {TemplateUpdateDiagnostic[]}
|
|
256
|
+
*/
|
|
257
|
+
export function templatePolicyDiagnosticsForProject(projectRoot, template, step) {
|
|
258
|
+
return templatePolicyDiagnosticsForTemplate(loadTemplatePolicy(projectRoot), template, step);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @param {TemplateUpdateDiagnostic[]} diagnostics
|
|
263
|
+
* @returns {string[]}
|
|
264
|
+
*/
|
|
265
|
+
export function issueMessagesFromDiagnostics(diagnostics) {
|
|
266
|
+
return diagnostics
|
|
267
|
+
.filter((diagnostic) => diagnostic.severity === "error")
|
|
268
|
+
.map((diagnostic) => diagnostic.message);
|
|
269
|
+
}
|