@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +2 -2
  3. package/src/agent-brief.js +29 -23
  4. package/src/agent-ops/query-builders/change-risk/{import-plan.js → extract-plan.js} +1 -1
  5. package/src/agent-ops/query-builders/change-risk/review-packets.js +5 -5
  6. package/src/agent-ops/query-builders/change-risk.js +1 -1
  7. package/src/agent-ops/query-builders/common.js +2 -2
  8. package/src/agent-ops/query-builders/multi-agent.js +1 -1
  9. package/src/agent-ops/query-builders/workflow-context-shared.js +4 -4
  10. package/src/catalog/provenance.js +1 -1
  11. package/src/cli/catalog-alias.d.ts +2 -0
  12. package/src/cli/catalog-alias.js +2 -2
  13. package/src/cli/command-parser.js +2 -0
  14. package/src/cli/command-parsers/core.js +9 -5
  15. package/src/cli/command-parsers/extractor.js +40 -0
  16. package/src/cli/command-parsers/import.js +11 -17
  17. package/src/cli/command-parsers/project.js +0 -3
  18. package/src/cli/commands/catalog/copy.js +3 -3
  19. package/src/cli/commands/catalog/help.js +1 -2
  20. package/src/cli/commands/catalog/list.js +7 -4
  21. package/src/cli/commands/catalog/show.js +4 -4
  22. package/src/cli/commands/copy.js +356 -0
  23. package/src/cli/commands/doctor.js +1 -1
  24. package/src/cli/commands/extractor.js +451 -0
  25. package/src/cli/commands/import/adopt.js +9 -9
  26. package/src/cli/commands/import/check.js +15 -15
  27. package/src/cli/commands/import/diff.js +6 -6
  28. package/src/cli/commands/import/help.js +45 -34
  29. package/src/cli/commands/import/paths.js +3 -3
  30. package/src/cli/commands/import/plan.js +8 -8
  31. package/src/cli/commands/import/refresh.js +25 -24
  32. package/src/cli/commands/import/status-history.js +4 -4
  33. package/src/cli/commands/import/workspace.js +24 -18
  34. package/src/cli/commands/import-runner.js +10 -7
  35. package/src/cli/commands/import.js +4 -1
  36. package/src/cli/commands/init.js +67 -0
  37. package/src/cli/commands/query/{import-adopt.js → extract-adopt.js} +2 -2
  38. package/src/cli/commands/query/runner/change.js +2 -2
  39. package/src/cli/commands/query/runner/{import-adopt.js → extract-adopt.js} +9 -9
  40. package/src/cli/commands/query/runner/index.js +1 -1
  41. package/src/cli/commands/query/runner/workflow.js +7 -7
  42. package/src/cli/commands/query/workspace.js +4 -4
  43. package/src/cli/commands/release-status.js +2 -2
  44. package/src/cli/commands/source.js +2 -2
  45. package/src/cli/commands/template/check.js +2 -2
  46. package/src/cli/commands/template/list-show.js +4 -4
  47. package/src/cli/dispatcher.js +32 -3
  48. package/src/cli/help-dispatch.js +33 -8
  49. package/src/cli/help.js +79 -52
  50. package/src/cli/migration-guidance.js +9 -0
  51. package/src/cli/options.js +17 -0
  52. package/src/extractor/check.js +155 -0
  53. package/src/extractor/packages.js +295 -0
  54. package/src/extractor/registry.js +196 -0
  55. package/src/extractor-policy.js +249 -0
  56. package/src/generator/check.js +24 -87
  57. package/src/generator/context/bundle.js +14 -7
  58. package/src/generator/context/diff.js +8 -1
  59. package/src/generator/context/digest.js +10 -1
  60. package/src/generator/context/shared/domain-sdlc.js +5 -1
  61. package/src/generator/context/shared/relationships.js +20 -5
  62. package/src/generator/context/shared/summaries.js +26 -0
  63. package/src/generator/context/shared.d.ts +1 -0
  64. package/src/generator/context/shared.js +1 -0
  65. package/src/generator/context/slice/core.js +9 -5
  66. package/src/generator/context/slice/sdlc.js +31 -2
  67. package/src/generator/context/task-mode.js +3 -3
  68. package/src/generator/registry/index.js +16 -75
  69. package/src/generator-policy.js +9 -57
  70. package/src/import/core/registry.d.ts +3 -0
  71. package/src/import/core/registry.js +82 -8
  72. package/src/import/core/runner/reports.js +4 -4
  73. package/src/import/core/runner/run.js +2 -0
  74. package/src/import/core/runner/tracks.js +66 -4
  75. package/src/import/provenance.js +18 -17
  76. package/src/init-project.js +215 -0
  77. package/src/new-project/constants.js +1 -1
  78. package/src/new-project/create.js +2 -2
  79. package/src/new-project/project-files.js +7 -7
  80. package/src/package-adapters/adapter.js +64 -0
  81. package/src/package-adapters/file-map.js +30 -0
  82. package/src/package-adapters/index.js +27 -0
  83. package/src/package-adapters/manifest.js +108 -0
  84. package/src/package-adapters/policy.js +81 -0
  85. package/src/package-adapters/spec.js +51 -0
  86. package/src/reconcile/journeys.js +8 -3
  87. package/src/record-blocks.js +125 -0
  88. package/src/resolver/index.js +3 -0
  89. package/src/resolver/journeys.js +74 -0
  90. package/src/resolver/normalize.js +25 -0
  91. package/src/sdlc/adopt.js +1 -1
  92. package/src/validator/common.js +34 -1
  93. package/src/validator/index.js +4 -0
  94. package/src/validator/kinds.d.ts +2 -0
  95. package/src/validator/kinds.js +34 -1
  96. package/src/validator/per-kind/journey.js +233 -0
  97. package/src/workflows/docs-generate.js +4 -1
  98. package/src/workflows/reconcile/bundle-core/index.js +4 -2
  99. package/src/workflows/reconcile/canonical-surface.js +4 -1
  100. 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
+ }
@@ -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
- for (const [filePath, content] of Object.entries(result.files)) {
212
- const normalizedPath = typeof filePath === "string" ? path.normalize(filePath) : "";
213
- if (typeof filePath !== "string" || filePath.length === 0 || path.isAbsolute(filePath) || normalizedPath === ".." || normalizedPath.startsWith(`..${path.sep}`)) {
214
- return { ok: false, message: "generated file paths must be non-empty relative paths", smoke: null };
215
- }
216
- if (typeof content !== "string") {
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 = loadLocalAdapter(payload.packageRoot || cwd, payload.manifest);
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 = loadInstalledAdapter(payload.packageName, cwd, payload.manifest);
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
- (graph.docs || [])
93
- .filter((doc) => doc.kind === "journey" && (doc.relatedProjections || []).some((id) => projections.includes(id)))
94
- .map((doc) => doc.id)
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: summarizeDocsByIds(graph, 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.docs || []).find((doc) => doc.id === id)) }))
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((graph.docs || []).filter((doc) => doc.kind === "journey").map((doc) => doc.id));
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: summarizeDocsByIds(graph, 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" || kind === "journey") {
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
- for (const journey of (graph.docs || []).filter((doc) => doc.kind === "journey").sort((a, b) => a.id.localeCompare(b.id))) {
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 doc found with id '${journeyId}'`);
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 relatedDocs(
49
- graph,
50
- /** @param {import("./types.d.ts").ContextDoc} doc */ (doc) => doc.kind === "journey" && (doc.relatedCapabilities || []).includes(capabilityId)
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((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)),
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 @@ export {
20
20
  summarizeById,
21
21
  summarizeStatementsByIds,
22
22
  summarizeDocsByIds,
23
+ summarizeJourneyLikeByIds,
23
24
  workspaceInventory
24
25
  } from "./shared/relationships.js";
25
26
  export {
@@ -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: summarizeDocsByIds(graph, 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 = (graph.docs || [])
122
- .filter(/** @param {any} doc */ (doc) => doc.kind === "journey" && (doc.relatedWorkflows || []).includes(workflowId))
123
- .map(/** @param {any} doc */ (doc) => doc.id)
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: summarizeDocsByIds(graph, journeys)
150
+ journeys: summarizeJourneyLikeByIds(graph, journeys)
147
151
  },
148
152
  verification: summarizeStatementsByIds(graph, verifications),
149
153
  verification_targets: recommendedVerificationTargets(graph, [...capabilities, ...entities, workflowId], {