@topogram/cli 0.3.78 → 0.3.80
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/CHANGELOG.md +20 -0
- package/package.json +2 -2
- package/src/agent-brief.js +29 -23
- package/src/agent-ops/query-builders/change-risk/{import-plan.js → extract-plan.js} +1 -1
- package/src/agent-ops/query-builders/change-risk/review-packets.js +5 -5
- package/src/agent-ops/query-builders/change-risk.js +1 -1
- package/src/agent-ops/query-builders/common.js +2 -2
- package/src/agent-ops/query-builders/multi-agent.js +1 -1
- package/src/agent-ops/query-builders/workflow-context-shared.js +4 -4
- package/src/catalog/provenance.js +1 -1
- package/src/cli/catalog-alias.d.ts +2 -0
- package/src/cli/catalog-alias.js +2 -2
- package/src/cli/command-parser.js +2 -0
- package/src/cli/command-parsers/core.js +9 -5
- package/src/cli/command-parsers/extractor.js +40 -0
- package/src/cli/command-parsers/import.js +11 -17
- package/src/cli/command-parsers/project.js +0 -3
- package/src/cli/commands/catalog/copy.js +3 -3
- package/src/cli/commands/catalog/help.js +1 -2
- package/src/cli/commands/catalog/list.js +7 -4
- package/src/cli/commands/catalog/show.js +4 -4
- package/src/cli/commands/copy.js +356 -0
- package/src/cli/commands/doctor.js +1 -1
- package/src/cli/commands/extractor.js +451 -0
- package/src/cli/commands/import/adopt.js +9 -9
- package/src/cli/commands/import/check.js +15 -15
- package/src/cli/commands/import/diff.js +6 -6
- package/src/cli/commands/import/help.js +45 -34
- package/src/cli/commands/import/paths.js +3 -3
- package/src/cli/commands/import/plan.js +8 -8
- package/src/cli/commands/import/refresh.js +25 -24
- package/src/cli/commands/import/status-history.js +4 -4
- package/src/cli/commands/import/workspace.js +24 -18
- package/src/cli/commands/import-runner.js +10 -7
- package/src/cli/commands/import.js +4 -1
- package/src/cli/commands/init.js +67 -0
- package/src/cli/commands/query/{import-adopt.js → extract-adopt.js} +2 -2
- package/src/cli/commands/query/runner/change.js +2 -2
- package/src/cli/commands/query/runner/{import-adopt.js → extract-adopt.js} +9 -9
- package/src/cli/commands/query/runner/index.js +1 -1
- package/src/cli/commands/query/runner/workflow.js +7 -7
- package/src/cli/commands/query/workspace.js +4 -4
- package/src/cli/commands/release-status.js +2 -2
- package/src/cli/commands/source.js +2 -2
- package/src/cli/commands/template/check.js +2 -2
- package/src/cli/commands/template/list-show.js +4 -4
- package/src/cli/dispatcher.js +32 -3
- package/src/cli/help-dispatch.js +33 -8
- package/src/cli/help.js +79 -52
- package/src/cli/migration-guidance.js +9 -0
- package/src/cli/options.js +17 -0
- package/src/extractor/check.js +155 -0
- package/src/extractor/packages.js +295 -0
- package/src/extractor/registry.js +196 -0
- package/src/extractor-policy.js +249 -0
- package/src/generator/check.js +24 -87
- package/src/generator/context/bundle.js +14 -7
- package/src/generator/context/diff.js +8 -1
- package/src/generator/context/digest.js +10 -1
- package/src/generator/context/shared/domain-sdlc.js +5 -1
- package/src/generator/context/shared/relationships.js +20 -5
- package/src/generator/context/shared/summaries.js +26 -0
- package/src/generator/context/shared.d.ts +1 -0
- package/src/generator/context/shared.js +1 -0
- package/src/generator/context/slice/core.js +9 -5
- package/src/generator/context/slice/sdlc.js +31 -2
- package/src/generator/context/task-mode.js +3 -3
- package/src/generator/registry/index.js +16 -75
- package/src/generator-policy.js +9 -57
- package/src/import/core/registry.d.ts +3 -0
- package/src/import/core/registry.js +82 -8
- package/src/import/core/runner/reports.js +4 -4
- package/src/import/core/runner/run.js +2 -0
- package/src/import/core/runner/tracks.js +66 -4
- package/src/import/provenance.js +18 -17
- package/src/init-project.js +215 -0
- package/src/new-project/constants.js +1 -1
- package/src/new-project/create.js +2 -2
- package/src/new-project/project-files.js +7 -7
- package/src/package-adapters/adapter.js +64 -0
- package/src/package-adapters/file-map.js +30 -0
- package/src/package-adapters/index.js +27 -0
- package/src/package-adapters/manifest.js +108 -0
- package/src/package-adapters/policy.js +81 -0
- package/src/package-adapters/spec.js +51 -0
- package/src/reconcile/journeys.js +8 -3
- package/src/record-blocks.js +125 -0
- package/src/resolver/index.js +3 -0
- package/src/resolver/journeys.js +74 -0
- package/src/resolver/normalize.js +25 -0
- package/src/sdlc/adopt.js +1 -1
- package/src/validator/common.js +34 -1
- package/src/validator/index.js +4 -0
- package/src/validator/kinds.d.ts +2 -0
- package/src/validator/kinds.js +34 -1
- package/src/validator/per-kind/journey.js +233 -0
- package/src/workflows/docs-generate.js +4 -1
- package/src/workflows/reconcile/bundle-core/index.js +4 -2
- package/src/workflows/reconcile/canonical-surface.js +4 -1
- package/src/cli/commands/new.js +0 -94
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { stableStringify } from "./format.js";
|
|
7
|
+
import {
|
|
8
|
+
optionalStringArray,
|
|
9
|
+
optionalStringRecord,
|
|
10
|
+
packageAllowedByPolicy,
|
|
11
|
+
packageScopeFromName as sharedPackageScopeFromName
|
|
12
|
+
} from "./package-adapters/index.js";
|
|
13
|
+
|
|
14
|
+
export const EXTRACTOR_POLICY_FILE = "topogram.extractor-policy.json";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ExtractorPolicy
|
|
18
|
+
* @property {string} version
|
|
19
|
+
* @property {string[]} allowedPackageScopes
|
|
20
|
+
* @property {string[]} allowedPackages
|
|
21
|
+
* @property {Record<string, string>} pinnedVersions
|
|
22
|
+
* @property {string[]} enabledPackages
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} PackageExtractorBinding
|
|
27
|
+
* @property {string} packageName
|
|
28
|
+
* @property {string} version
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} ExtractorPolicyInfo
|
|
33
|
+
* @property {string} path
|
|
34
|
+
* @property {ExtractorPolicy|null} policy
|
|
35
|
+
* @property {boolean} exists
|
|
36
|
+
* @property {ExtractorPolicyDiagnostic[]} diagnostics
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} ExtractorPolicyDiagnostic
|
|
41
|
+
* @property {string} code
|
|
42
|
+
* @property {"error"|"warning"} severity
|
|
43
|
+
* @property {string} message
|
|
44
|
+
* @property {string|null} path
|
|
45
|
+
* @property {string|null} suggestedFix
|
|
46
|
+
* @property {string|null} step
|
|
47
|
+
* @property {string|null} [packageName]
|
|
48
|
+
* @property {string|null} [version]
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Record<string, any>} input
|
|
53
|
+
* @returns {ExtractorPolicyDiagnostic}
|
|
54
|
+
*/
|
|
55
|
+
function extractorPolicyDiagnostic(input) {
|
|
56
|
+
return {
|
|
57
|
+
code: String(input.code || "extractor_policy_failed"),
|
|
58
|
+
severity: input.severity === "warning" ? "warning" : "error",
|
|
59
|
+
message: String(input.message || "Extractor policy check failed."),
|
|
60
|
+
path: typeof input.path === "string" ? input.path : null,
|
|
61
|
+
suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
|
|
62
|
+
step: typeof input.step === "string" ? input.step : null,
|
|
63
|
+
packageName: typeof input.packageName === "string" ? input.packageName : null,
|
|
64
|
+
version: typeof input.version === "string" ? input.version : null
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @returns {ExtractorPolicy}
|
|
70
|
+
*/
|
|
71
|
+
export function defaultExtractorPolicy() {
|
|
72
|
+
return {
|
|
73
|
+
version: "0.1",
|
|
74
|
+
allowedPackageScopes: [],
|
|
75
|
+
allowedPackages: [],
|
|
76
|
+
pinnedVersions: {},
|
|
77
|
+
enabledPackages: []
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {unknown} value
|
|
83
|
+
* @param {string} policyPath
|
|
84
|
+
* @returns {ExtractorPolicy}
|
|
85
|
+
*/
|
|
86
|
+
export function validateExtractorPolicy(value, policyPath) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
88
|
+
throw new Error(`${EXTRACTOR_POLICY_FILE} must contain a JSON object.`);
|
|
89
|
+
}
|
|
90
|
+
const raw = /** @type {Record<string, unknown>} */ (value);
|
|
91
|
+
const defaults = defaultExtractorPolicy();
|
|
92
|
+
return {
|
|
93
|
+
version: typeof raw.version === "string" && raw.version ? raw.version : defaults.version,
|
|
94
|
+
allowedPackageScopes: raw.allowedPackageScopes == null
|
|
95
|
+
? defaults.allowedPackageScopes
|
|
96
|
+
: optionalStringArray(raw.allowedPackageScopes, "allowedPackageScopes", policyPath),
|
|
97
|
+
allowedPackages: optionalStringArray(raw.allowedPackages, "allowedPackages", policyPath),
|
|
98
|
+
pinnedVersions: optionalStringRecord(raw.pinnedVersions, policyPath, "package-or-extractor ids"),
|
|
99
|
+
enabledPackages: optionalStringArray(raw.enabledPackages, "enabledPackages", policyPath)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} packageName
|
|
105
|
+
* @returns {string|null}
|
|
106
|
+
*/
|
|
107
|
+
export function packageScopeFromName(packageName) {
|
|
108
|
+
return sharedPackageScopeFromName(packageName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {ExtractorPolicy} policy
|
|
113
|
+
* @param {string} packageName
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
export function extractorPackageAllowed(policy, packageName) {
|
|
117
|
+
return packageName.startsWith("@topogram/extractor-") || packageAllowedByPolicy(policy, packageName);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {string} projectRoot
|
|
122
|
+
* @param {string|null|undefined} policyPath
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
function resolvePolicyPath(projectRoot, policyPath) {
|
|
126
|
+
if (policyPath) {
|
|
127
|
+
const resolved = path.resolve(projectRoot, policyPath);
|
|
128
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
129
|
+
return path.join(resolved, EXTRACTOR_POLICY_FILE);
|
|
130
|
+
}
|
|
131
|
+
return resolved;
|
|
132
|
+
}
|
|
133
|
+
return path.join(projectRoot, EXTRACTOR_POLICY_FILE);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} projectRoot
|
|
138
|
+
* @param {string|null|undefined} [policyPath]
|
|
139
|
+
* @returns {ExtractorPolicyInfo}
|
|
140
|
+
*/
|
|
141
|
+
export function loadExtractorPolicy(projectRoot, policyPath = null) {
|
|
142
|
+
const resolvedPolicyPath = resolvePolicyPath(projectRoot, policyPath);
|
|
143
|
+
if (!fs.existsSync(resolvedPolicyPath)) {
|
|
144
|
+
return {
|
|
145
|
+
path: resolvedPolicyPath,
|
|
146
|
+
policy: null,
|
|
147
|
+
exists: false,
|
|
148
|
+
diagnostics: []
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
return {
|
|
153
|
+
path: resolvedPolicyPath,
|
|
154
|
+
policy: validateExtractorPolicy(JSON.parse(fs.readFileSync(resolvedPolicyPath, "utf8")), resolvedPolicyPath),
|
|
155
|
+
exists: true,
|
|
156
|
+
diagnostics: []
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
path: resolvedPolicyPath,
|
|
161
|
+
policy: null,
|
|
162
|
+
exists: true,
|
|
163
|
+
diagnostics: [extractorPolicyDiagnostic({
|
|
164
|
+
code: "extractor_policy_invalid",
|
|
165
|
+
message: error instanceof Error ? error.message : String(error),
|
|
166
|
+
path: resolvedPolicyPath,
|
|
167
|
+
suggestedFix: `Fix ${EXTRACTOR_POLICY_FILE} or regenerate it with \`topogram extractor policy init\`.`,
|
|
168
|
+
step: "extractor-policy"
|
|
169
|
+
})]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {ExtractorPolicyInfo} policyInfo
|
|
176
|
+
* @returns {ExtractorPolicy}
|
|
177
|
+
*/
|
|
178
|
+
export function effectiveExtractorPolicy(policyInfo) {
|
|
179
|
+
return policyInfo.policy || defaultExtractorPolicy();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {string} projectRoot
|
|
184
|
+
* @param {ExtractorPolicy} policy
|
|
185
|
+
* @param {string|null|undefined} [policyPath]
|
|
186
|
+
* @returns {ExtractorPolicy}
|
|
187
|
+
*/
|
|
188
|
+
export function writeExtractorPolicy(projectRoot, policy, policyPath = null) {
|
|
189
|
+
const resolvedPolicyPath = resolvePolicyPath(projectRoot, policyPath);
|
|
190
|
+
fs.writeFileSync(resolvedPolicyPath, `${stableStringify(policy)}\n`, "utf8");
|
|
191
|
+
return policy;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {ExtractorPolicyInfo} policyInfo
|
|
196
|
+
* @param {PackageExtractorBinding[]} bindings
|
|
197
|
+
* @param {string} [step]
|
|
198
|
+
* @returns {ExtractorPolicyDiagnostic[]}
|
|
199
|
+
*/
|
|
200
|
+
export function extractorPolicyDiagnosticsForPackages(policyInfo, bindings, step = "extractor-policy") {
|
|
201
|
+
if (policyInfo.diagnostics.length > 0) {
|
|
202
|
+
return policyInfo.diagnostics;
|
|
203
|
+
}
|
|
204
|
+
const policy = effectiveExtractorPolicy(policyInfo);
|
|
205
|
+
/** @type {ExtractorPolicyDiagnostic[]} */
|
|
206
|
+
const diagnostics = [];
|
|
207
|
+
for (const binding of bindings) {
|
|
208
|
+
if (!extractorPackageAllowed(policy, binding.packageName)) {
|
|
209
|
+
const scope = packageScopeFromName(binding.packageName);
|
|
210
|
+
diagnostics.push(extractorPolicyDiagnostic({
|
|
211
|
+
code: "extractor_package_denied",
|
|
212
|
+
message: `Extractor package '${binding.packageName}' is not allowed by ${EXTRACTOR_POLICY_FILE}.`,
|
|
213
|
+
path: policyInfo.path,
|
|
214
|
+
suggestedFix: `Review '${binding.packageName}', then run \`topogram extractor policy pin ${binding.packageName}@${binding.version}\` or add '${scope || binding.packageName}' to ${EXTRACTOR_POLICY_FILE}.`,
|
|
215
|
+
step,
|
|
216
|
+
packageName: binding.packageName,
|
|
217
|
+
version: binding.version
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
const pinnedVersion = policy.pinnedVersions[binding.packageName] || null;
|
|
221
|
+
if (pinnedVersion && pinnedVersion !== binding.version) {
|
|
222
|
+
diagnostics.push(extractorPolicyDiagnostic({
|
|
223
|
+
code: "extractor_version_mismatch",
|
|
224
|
+
message: `Extractor package '${binding.packageName}' uses version '${binding.version}', but ${EXTRACTOR_POLICY_FILE} pins it to '${pinnedVersion}'.`,
|
|
225
|
+
path: policyInfo.path,
|
|
226
|
+
suggestedFix: `Use extractor version '${pinnedVersion}', or run \`topogram extractor policy pin ${binding.packageName}@${binding.version}\` after review.`,
|
|
227
|
+
step,
|
|
228
|
+
packageName: binding.packageName,
|
|
229
|
+
version: binding.version
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return diagnostics;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @param {string} spec
|
|
238
|
+
* @returns {{ packageName: string, version: string }}
|
|
239
|
+
*/
|
|
240
|
+
export function parseExtractorPolicyPin(spec) {
|
|
241
|
+
const separator = spec.lastIndexOf("@");
|
|
242
|
+
if (separator <= 0 || separator === spec.length - 1) {
|
|
243
|
+
throw new Error("Extractor policy pin requires a package name and extractor version, for example @topogram/extractor-node-cli@1.");
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
packageName: spec.slice(0, separator),
|
|
247
|
+
version: spec.slice(separator + 1)
|
|
248
|
+
};
|
|
249
|
+
}
|
package/src/generator/check.js
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
5
|
|
|
7
6
|
import {
|
|
8
7
|
loadPackageGeneratorManifest,
|
|
9
8
|
validateGeneratorManifest
|
|
10
9
|
} from "./registry.js";
|
|
10
|
+
import {
|
|
11
|
+
isPathSpec,
|
|
12
|
+
loadInstalledPackageAdapter,
|
|
13
|
+
loadLocalPackageAdapter,
|
|
14
|
+
packageNameFromSpec,
|
|
15
|
+
validateRelativeStringFileMap
|
|
16
|
+
} from "../package-adapters/index.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* @typedef {import("./registry.js").GeneratorManifest} GeneratorManifest
|
|
@@ -28,82 +34,6 @@ import {
|
|
|
28
34
|
* @property {boolean} executesPackageCode
|
|
29
35
|
*/
|
|
30
36
|
|
|
31
|
-
/**
|
|
32
|
-
* @param {string} spec
|
|
33
|
-
* @param {string} cwd
|
|
34
|
-
* @returns {boolean}
|
|
35
|
-
*/
|
|
36
|
-
function isPathSpec(spec, cwd) {
|
|
37
|
-
return spec.startsWith(".") || spec.startsWith("/") || fs.existsSync(path.resolve(cwd, spec));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {string} spec
|
|
42
|
-
* @returns {string}
|
|
43
|
-
*/
|
|
44
|
-
function packageNameFromSpec(spec) {
|
|
45
|
-
if (spec.startsWith("@")) {
|
|
46
|
-
const versionIndex = spec.indexOf("@", 1);
|
|
47
|
-
return versionIndex > 0 ? spec.slice(0, versionIndex) : spec;
|
|
48
|
-
}
|
|
49
|
-
const versionIndex = spec.indexOf("@");
|
|
50
|
-
return versionIndex > 0 ? spec.slice(0, versionIndex) : spec;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @param {any} moduleValue
|
|
55
|
-
* @param {string|null|undefined} exportName
|
|
56
|
-
* @returns {any}
|
|
57
|
-
*/
|
|
58
|
-
function selectPackageExport(moduleValue, exportName) {
|
|
59
|
-
if (exportName) {
|
|
60
|
-
return moduleValue?.[exportName] || moduleValue?.default?.[exportName] || null;
|
|
61
|
-
}
|
|
62
|
-
return moduleValue?.default || moduleValue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {string} root
|
|
67
|
-
* @param {GeneratorManifest} manifest
|
|
68
|
-
* @returns {{ adapter: any|null, error: string|null }}
|
|
69
|
-
*/
|
|
70
|
-
function loadLocalAdapter(root, manifest) {
|
|
71
|
-
try {
|
|
72
|
-
const packageJsonPath = path.join(root, "package.json");
|
|
73
|
-
const requireFromPackage = createRequire(packageJsonPath);
|
|
74
|
-
return {
|
|
75
|
-
adapter: selectPackageExport(requireFromPackage(root), manifest.export),
|
|
76
|
-
error: null
|
|
77
|
-
};
|
|
78
|
-
} catch (error) {
|
|
79
|
-
return {
|
|
80
|
-
adapter: null,
|
|
81
|
-
error: `Generator package export could not be loaded from '${root}': ${error instanceof Error ? error.message : String(error)}`
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @param {string} packageName
|
|
88
|
-
* @param {string} rootDir
|
|
89
|
-
* @param {GeneratorManifest} manifest
|
|
90
|
-
* @returns {{ adapter: any|null, error: string|null }}
|
|
91
|
-
*/
|
|
92
|
-
function loadInstalledAdapter(packageName, rootDir, manifest) {
|
|
93
|
-
try {
|
|
94
|
-
const requireFromRoot = createRequire(path.join(rootDir, "package.json"));
|
|
95
|
-
return {
|
|
96
|
-
adapter: selectPackageExport(requireFromRoot(packageName), manifest.export),
|
|
97
|
-
error: null
|
|
98
|
-
};
|
|
99
|
-
} catch (error) {
|
|
100
|
-
return {
|
|
101
|
-
adapter: null,
|
|
102
|
-
error: `Generator package '${packageName}' export could not be loaded from '${rootDir}': ${error instanceof Error ? error.message : String(error)}`
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
37
|
/**
|
|
108
38
|
* @param {GeneratorManifest} manifest
|
|
109
39
|
* @returns {Record<string, any>}
|
|
@@ -208,14 +138,12 @@ function validateSmokeResult(result) {
|
|
|
208
138
|
if (!result.files || typeof result.files !== "object" || Array.isArray(result.files)) {
|
|
209
139
|
return { ok: false, message: "generate(context) result must include a files object", smoke: null };
|
|
210
140
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return { ok: false, message: `generated file '${filePath}' content must be a string`, smoke: null };
|
|
218
|
-
}
|
|
141
|
+
const fileMapValidation = validateRelativeStringFileMap(result.files, {
|
|
142
|
+
filePathMessage: "generated file paths must be non-empty relative paths",
|
|
143
|
+
contentMessage: (filePath) => `generated file '${filePath}' content must be a string`
|
|
144
|
+
});
|
|
145
|
+
if (!fileMapValidation.ok) {
|
|
146
|
+
return { ok: false, message: fileMapValidation.message, smoke: null };
|
|
219
147
|
}
|
|
220
148
|
return {
|
|
221
149
|
ok: true,
|
|
@@ -304,7 +232,11 @@ export function checkGeneratorPack(sourceSpec, options = {}) {
|
|
|
304
232
|
}
|
|
305
233
|
|
|
306
234
|
if (payload.source === "path") {
|
|
307
|
-
const loaded =
|
|
235
|
+
const loaded = loadLocalPackageAdapter({
|
|
236
|
+
packageRoot: payload.packageRoot || cwd,
|
|
237
|
+
exportName: payload.manifest.export,
|
|
238
|
+
packageLabel: "Generator package"
|
|
239
|
+
});
|
|
308
240
|
adapter = loaded.adapter;
|
|
309
241
|
if (loaded.error) {
|
|
310
242
|
payload.errors.push(loaded.error);
|
|
@@ -312,7 +244,12 @@ export function checkGeneratorPack(sourceSpec, options = {}) {
|
|
|
312
244
|
return payload;
|
|
313
245
|
}
|
|
314
246
|
} else if (payload.packageName) {
|
|
315
|
-
const loaded =
|
|
247
|
+
const loaded = loadInstalledPackageAdapter({
|
|
248
|
+
packageName: payload.packageName,
|
|
249
|
+
rootDir: cwd,
|
|
250
|
+
exportName: payload.manifest.export,
|
|
251
|
+
packageLabel: "Generator package"
|
|
252
|
+
});
|
|
316
253
|
adapter = loaded.adapter;
|
|
317
254
|
if (loaded.error) {
|
|
318
255
|
payload.errors.push(loaded.error);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stableSortedStrings,
|
|
13
13
|
summarizeById,
|
|
14
14
|
summarizeDocsByIds,
|
|
15
|
+
summarizeJourneyLikeByIds,
|
|
15
16
|
summarizeStatementsByIds,
|
|
16
17
|
verificationIdsForTarget
|
|
17
18
|
} from "./shared.js";
|
|
@@ -89,9 +90,12 @@ function uiBundle(graph) {
|
|
|
89
90
|
.map((doc) => doc.id)
|
|
90
91
|
);
|
|
91
92
|
const journeys = stableSortedStrings(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.
|
|
93
|
+
[
|
|
94
|
+
...(graph.byKind.journey || []),
|
|
95
|
+
...(graph.docs || []).filter((doc) => doc.kind === "journey")
|
|
96
|
+
]
|
|
97
|
+
.filter((journey) => (journey.relatedProjections || []).some((id) => projections.includes(id)))
|
|
98
|
+
.map((journey) => journey.id)
|
|
95
99
|
);
|
|
96
100
|
const rules = stableSortedStrings(capabilities.flatMap((capabilityId) => {
|
|
97
101
|
return (graph.byKind.rule || [])
|
|
@@ -112,7 +116,7 @@ function uiBundle(graph) {
|
|
|
112
116
|
projections: summarizeStatementsByIds(graph, projections),
|
|
113
117
|
capabilities: summarizeStatementsByIds(graph, capabilities),
|
|
114
118
|
workflows: summarizeDocsByIds(graph, workflows),
|
|
115
|
-
journeys:
|
|
119
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys),
|
|
116
120
|
rules: summarizeStatementsByIds(graph, rules)
|
|
117
121
|
},
|
|
118
122
|
dependencies: {
|
|
@@ -131,7 +135,7 @@ function uiBundle(graph) {
|
|
|
131
135
|
review_boundaries: {
|
|
132
136
|
projections: projections.map((id) => ({ id, review_boundary: summarizeById(graph, id)?.reviewBoundary || null })),
|
|
133
137
|
workflows: workflows.map((id) => ({ id, review_boundary: reviewBoundaryForWorkflowDoc((graph.docs || []).find((doc) => doc.id === id)) })),
|
|
134
|
-
journeys: journeys.map((id) => ({ id, review_boundary: reviewBoundaryForJourneyDoc((graph
|
|
138
|
+
journeys: journeys.map((id) => ({ id, review_boundary: reviewBoundaryForJourneyDoc(summarizeById(graph, id) || null) }))
|
|
135
139
|
}
|
|
136
140
|
};
|
|
137
141
|
}
|
|
@@ -186,7 +190,10 @@ function dbBundle(graph) {
|
|
|
186
190
|
function maintainedAppBundle(graph) {
|
|
187
191
|
const proofStories = maintainedProofMetadata(graph);
|
|
188
192
|
const projections = stableSortedStrings((graph.byKind.projection || []).map((item) => item.id));
|
|
189
|
-
const journeys = stableSortedStrings(
|
|
193
|
+
const journeys = stableSortedStrings([
|
|
194
|
+
...(graph.byKind.journey || []).map((item) => item.id),
|
|
195
|
+
...(graph.docs || []).filter((doc) => doc.kind === "journey").map((doc) => doc.id)
|
|
196
|
+
]);
|
|
190
197
|
const verifications = stableSortedStrings((graph.byKind.verification || []).map((item) => item.id));
|
|
191
198
|
const maintainedFiles = stableSortedStrings(proofStories.flatMap((item) => item.maintainedFiles || []));
|
|
192
199
|
const emittedDependencies = stableSortedStrings(proofStories.flatMap((item) => item.emittedDependencies || []));
|
|
@@ -215,7 +222,7 @@ function maintainedAppBundle(graph) {
|
|
|
215
222
|
},
|
|
216
223
|
included_surfaces: {
|
|
217
224
|
projections: summarizeStatementsByIds(graph, projections),
|
|
218
|
-
journeys:
|
|
225
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys),
|
|
219
226
|
maintained_files_in_scope: maintainedFiles,
|
|
220
227
|
emitted_artifact_dependencies: emittedDependencies,
|
|
221
228
|
human_owned_seams: humanOwnedSeams,
|
|
@@ -16,10 +16,17 @@ import {
|
|
|
16
16
|
import { defaultOwnershipBoundary } from "../../policy/review-boundaries.js";
|
|
17
17
|
|
|
18
18
|
function normalizeTargetMap(graph, kind) {
|
|
19
|
-
if (kind === "workflow"
|
|
19
|
+
if (kind === "workflow") {
|
|
20
20
|
const docs = (graph.docs || []).filter((doc) => doc.kind === kind);
|
|
21
21
|
return new Map(docs.map((doc) => [doc.id, summarizeDoc(doc)]));
|
|
22
22
|
}
|
|
23
|
+
if (kind === "journey") {
|
|
24
|
+
const journeys = [
|
|
25
|
+
...(graph.byKind?.journey || []).map((statement) => summarizeStatement(statement)),
|
|
26
|
+
...(graph.docs || []).filter((doc) => doc.kind === kind).map((doc) => summarizeDoc(doc))
|
|
27
|
+
];
|
|
28
|
+
return new Map(journeys.map((journey) => [journey.id, journey]));
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
return new Map(((graph.byKind[kind] || []).map((statement) => [statement.id, summarizeStatement(statement)])));
|
|
25
32
|
}
|
|
@@ -138,6 +138,11 @@ function journeyDigest(graph, journey) {
|
|
|
138
138
|
id: journey.id
|
|
139
139
|
},
|
|
140
140
|
summary: summarizeById(graph, journey.id),
|
|
141
|
+
steps: (journey.steps || []).map((step) => ({
|
|
142
|
+
id: step.id,
|
|
143
|
+
intent: step.intent || null,
|
|
144
|
+
after: [...(step.after || [])]
|
|
145
|
+
})),
|
|
141
146
|
verifications: verificationIdsForTarget(graph, [journey.id, ...(journey.relatedCapabilities || [])]),
|
|
142
147
|
review_boundary: reviewBoundaryForJourneyDoc(journey),
|
|
143
148
|
ownership_boundary: defaultOwnershipBoundary()
|
|
@@ -174,7 +179,11 @@ export function generateContextDigest(graph) {
|
|
|
174
179
|
files[`projections/${projection.id}.context-digest.json`] = projectionDigest(graph, projection);
|
|
175
180
|
}
|
|
176
181
|
|
|
177
|
-
|
|
182
|
+
const journeys = [
|
|
183
|
+
...(graph.byKind.journey || []),
|
|
184
|
+
...(graph.docs || []).filter((doc) => doc.kind === "journey")
|
|
185
|
+
].sort((a, b) => a.id.localeCompare(b.id));
|
|
186
|
+
for (const journey of journeys) {
|
|
178
187
|
files[`journeys/${journey.id}.context-digest.json`] = journeyDigest(graph, journey);
|
|
179
188
|
}
|
|
180
189
|
|
|
@@ -287,9 +287,13 @@ export function getWorkflowDoc(graph, workflowId) {
|
|
|
287
287
|
* @returns {any}
|
|
288
288
|
*/
|
|
289
289
|
export function getJourneyDoc(graph, journeyId) {
|
|
290
|
+
const journey = (graph.byKind?.journey || []).find(/** @param {any} item */ (item) => item.id === journeyId);
|
|
291
|
+
if (journey) {
|
|
292
|
+
return journey;
|
|
293
|
+
}
|
|
290
294
|
const doc = (graph.docs || []).find(/** @param {any} item */ (item) => item.kind === "journey" && item.id === journeyId);
|
|
291
295
|
if (!doc) {
|
|
292
|
-
throw new Error(`No journey
|
|
296
|
+
throw new Error(`No journey found with id '${journeyId}'`);
|
|
293
297
|
}
|
|
294
298
|
return doc;
|
|
295
299
|
}
|
|
@@ -45,10 +45,13 @@ export function verificationsFor(graph, predicate) {
|
|
|
45
45
|
* @returns {any}
|
|
46
46
|
*/
|
|
47
47
|
export function relatedJourneysForCapability(graph, capabilityId) {
|
|
48
|
-
return
|
|
49
|
-
graph,
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
return [
|
|
49
|
+
...(graph.byKind?.journey || []).filter(/** @param {any} journey */ (journey) => (journey.relatedCapabilities || []).includes(capabilityId)),
|
|
50
|
+
...relatedDocs(
|
|
51
|
+
graph,
|
|
52
|
+
/** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.kind === "journey" && (doc.relatedCapabilities || []).includes(capabilityId)
|
|
53
|
+
)
|
|
54
|
+
];
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/**
|
|
@@ -395,6 +398,15 @@ export function summarizeDocsByIds(graph, ids) {
|
|
|
395
398
|
.map(summarizeDoc);
|
|
396
399
|
}
|
|
397
400
|
|
|
401
|
+
/**
|
|
402
|
+
* @param {import("./types.d.ts").ContextGraph} graph
|
|
403
|
+
* @param {Iterable<string>} ids
|
|
404
|
+
* @returns {any}
|
|
405
|
+
*/
|
|
406
|
+
export function summarizeJourneyLikeByIds(graph, ids) {
|
|
407
|
+
return stableSortedStrings(ids).map(/** @param {string} id */ (id) => summarizeById(graph, id)).filter(Boolean);
|
|
408
|
+
}
|
|
409
|
+
|
|
398
410
|
/**
|
|
399
411
|
* @param {import("./types.d.ts").ContextGraph} graph
|
|
400
412
|
* @returns {any}
|
|
@@ -403,7 +415,10 @@ export function workspaceInventory(graph) {
|
|
|
403
415
|
return {
|
|
404
416
|
capabilities: stableSortedStrings((graph.byKind.capability || []).map(/** @param {any} item */ (item) => item.id)),
|
|
405
417
|
workflows: stableSortedStrings((graph.docs || []).filter(/** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.kind === "workflow").map(/** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.id)),
|
|
406
|
-
journeys: stableSortedStrings(
|
|
418
|
+
journeys: stableSortedStrings([
|
|
419
|
+
...(graph.byKind.journey || []).map(/** @param {any} item */ (item) => item.id),
|
|
420
|
+
...(graph.docs || []).filter(/** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.kind === "journey").map(/** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.id)
|
|
421
|
+
]),
|
|
407
422
|
entities: stableSortedStrings((graph.byKind.entity || []).map(/** @param {any} item */ (item) => item.id)),
|
|
408
423
|
projections: stableSortedStrings((graph.byKind.projection || []).map(/** @param {any} item */ (item) => item.id)),
|
|
409
424
|
widgets: stableSortedStrings((graph.byKind.widget || []).map(/** @param {any} item */ (item) => item.id)),
|
|
@@ -97,6 +97,30 @@ export function summarizeJourneyDoc(doc) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {import("./types.d.ts").ContextStatement} journey
|
|
102
|
+
* @returns {any}
|
|
103
|
+
*/
|
|
104
|
+
export function summarizeJourney(journey) {
|
|
105
|
+
return {
|
|
106
|
+
id: journey.id,
|
|
107
|
+
kind: journey.kind,
|
|
108
|
+
name: journey.name || journey.id,
|
|
109
|
+
description: journey.description || null,
|
|
110
|
+
status: journey.status || null,
|
|
111
|
+
goal: journey.goal || null,
|
|
112
|
+
actors: stableSortedStrings(journey.actors || []),
|
|
113
|
+
relatedCapabilities: stableSortedStrings(journey.relatedCapabilities || []),
|
|
114
|
+
relatedWorkflows: stableSortedStrings(journey.relatedWorkflows || []),
|
|
115
|
+
relatedProjections: stableSortedStrings(journey.relatedProjections || []),
|
|
116
|
+
relatedWidgets: stableSortedStrings(journey.relatedWidgets || []),
|
|
117
|
+
stepCount: (journey.steps || []).length,
|
|
118
|
+
alternateCount: (journey.alternates || []).length,
|
|
119
|
+
reviewBoundary: reviewBoundaryForJourneyDocPolicy(journey),
|
|
120
|
+
ownership_boundary: defaultOwnershipBoundary()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
/**
|
|
101
125
|
* @param {import("./types.d.ts").ContextStatement} statement
|
|
102
126
|
* @returns {any}
|
|
@@ -163,6 +187,8 @@ export function summarizeStatement(statement) {
|
|
|
163
187
|
};
|
|
164
188
|
case "widget":
|
|
165
189
|
return summarizeComponent(statement);
|
|
190
|
+
case "journey":
|
|
191
|
+
return summarizeJourney(statement);
|
|
166
192
|
case "shape":
|
|
167
193
|
return {
|
|
168
194
|
id: statement.id,
|
|
@@ -34,6 +34,7 @@ export function summarizeById(...args: any[]): any;
|
|
|
34
34
|
export function summarizeDocsByIds(...args: any[]): any;
|
|
35
35
|
export function summarizeDocument(...args: any[]): any;
|
|
36
36
|
export function summarizeDomain(...args: any[]): any;
|
|
37
|
+
export function summarizeJourneyLikeByIds(...args: any[]): any;
|
|
37
38
|
export function summarizePitch(...args: any[]): any;
|
|
38
39
|
export function summarizePlan(...args: any[]): any;
|
|
39
40
|
export function summarizeProjection(...args: any[]): any;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
relatedWorkflowDocsForCapability,
|
|
21
21
|
summarizeById,
|
|
22
22
|
summarizeDocsByIds,
|
|
23
|
+
summarizeJourneyLikeByIds,
|
|
23
24
|
summarizeProjection,
|
|
24
25
|
summarizeStatementsByIds,
|
|
25
26
|
verificationIdsForTarget,
|
|
@@ -87,7 +88,7 @@ function capabilitySlice(graph, capabilityId) {
|
|
|
87
88
|
entities: summarizeStatementsByIds(graph, entities),
|
|
88
89
|
rules: summarizeStatementsByIds(graph, rules),
|
|
89
90
|
workflows: summarizeDocsByIds(graph, workflows),
|
|
90
|
-
journeys:
|
|
91
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys),
|
|
91
92
|
projections: summarizeStatementsByIds(graph, projections)
|
|
92
93
|
},
|
|
93
94
|
verification: summarizeStatementsByIds(graph, verifications),
|
|
@@ -118,9 +119,12 @@ function workflowSlice(graph, workflowId) {
|
|
|
118
119
|
];
|
|
119
120
|
}))].sort();
|
|
120
121
|
const rules = [...new Set(capabilities.flatMap(/** @param {string} capabilityId */ (capabilityId) => relatedRulesForTarget(graph, capabilityId)))].sort();
|
|
121
|
-
const journeys =
|
|
122
|
-
|
|
123
|
-
.
|
|
122
|
+
const journeys = [
|
|
123
|
+
...(graph.byKind?.journey || []),
|
|
124
|
+
...(graph.docs || []).filter(/** @param {any} doc */ (doc) => doc.kind === "journey")
|
|
125
|
+
]
|
|
126
|
+
.filter(/** @param {any} journey */ (journey) => (journey.relatedWorkflows || []).includes(workflowId))
|
|
127
|
+
.map(/** @param {any} journey */ (journey) => journey.id)
|
|
124
128
|
.sort();
|
|
125
129
|
const verifications = verificationIdsForTarget(graph, [...capabilities, ...entities, workflowId]);
|
|
126
130
|
|
|
@@ -143,7 +147,7 @@ function workflowSlice(graph, workflowId) {
|
|
|
143
147
|
capabilities: summarizeStatementsByIds(graph, capabilities),
|
|
144
148
|
entities: summarizeStatementsByIds(graph, entities),
|
|
145
149
|
rules: summarizeStatementsByIds(graph, rules),
|
|
146
|
-
journeys:
|
|
150
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys)
|
|
147
151
|
},
|
|
148
152
|
verification: summarizeStatementsByIds(graph, verifications),
|
|
149
153
|
verification_targets: recommendedVerificationTargets(graph, [...capabilities, ...entities, workflowId], {
|