@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,233 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ JOURNEY_IDENTIFIER_PATTERN
5
+ } from "../kinds.js";
6
+ import {
7
+ pushError
8
+ } from "../utils.js";
9
+ import {
10
+ parseRecordBlock,
11
+ recordField,
12
+ recordString,
13
+ recordStringList,
14
+ recordSymbol,
15
+ recordSymbolList
16
+ } from "../../record-blocks.js";
17
+
18
+ const STEP_FIELDS = new Set(["id", "intent", "commands", "expects", "after", "notes"]);
19
+ const ALTERNATE_FIELDS = new Set(["id", "from", "condition", "commands", "expects", "notes"]);
20
+
21
+ /**
22
+ * @param {ValidationErrors} errors
23
+ * @param {TopogramStatement} statement
24
+ * @returns {void}
25
+ */
26
+ function validateJourneyIdentifier(errors, statement) {
27
+ if (!JOURNEY_IDENTIFIER_PATTERN.test(statement.id)) {
28
+ pushError(
29
+ errors,
30
+ `Journey identifier '${statement.id}' must match ${JOURNEY_IDENTIFIER_PATTERN.source}`,
31
+ statement.loc
32
+ );
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {ValidationErrors} errors
38
+ * @param {TopogramStatement} statement
39
+ * @param {TopogramField} field
40
+ * @param {Set<string>} allowedFields
41
+ * @param {string[]} requiredFields
42
+ * @returns {import("../../record-blocks.js").TopogramRecordBlock | null}
43
+ */
44
+ function validateRecordBlock(errors, statement, field, allowedFields, requiredFields) {
45
+ if (field.value.type !== "block") {
46
+ pushError(errors, `Field '${field.key}' on journey ${statement.id} must be a block`, field.loc);
47
+ return null;
48
+ }
49
+
50
+ const record = parseRecordBlock(/** @type {import("../../parser.js").AstBlock} */ (/** @type {unknown} */ (field.value)));
51
+ for (const recordFieldEntry of record.fieldOrder) {
52
+ if (!recordFieldEntry.key) {
53
+ pushError(errors, `Each '${field.key}' record entry on journey ${statement.id} must start with a field name`, recordFieldEntry.loc);
54
+ continue;
55
+ }
56
+ if (!allowedFields.has(recordFieldEntry.key)) {
57
+ pushError(errors, `Unsupported '${field.key}' field '${recordFieldEntry.key}' on journey ${statement.id}`, recordFieldEntry.loc);
58
+ }
59
+ const entries = record.fields.get(recordFieldEntry.key) || [];
60
+ if (entries.length > 1 && entries[1] === recordFieldEntry) {
61
+ pushError(errors, `Duplicate '${field.key}' field '${recordFieldEntry.key}' on journey ${statement.id}`, recordFieldEntry.loc);
62
+ }
63
+ }
64
+
65
+ for (const required of requiredFields) {
66
+ if (!record.fields.has(required)) {
67
+ pushError(errors, `Journey ${statement.id} ${field.key} record requires '${required}'`, field.loc);
68
+ }
69
+ }
70
+
71
+ return record;
72
+ }
73
+
74
+ /**
75
+ * @param {ValidationErrors} errors
76
+ * @param {TopogramStatement} statement
77
+ * @param {import("../../record-blocks.js").TopogramRecordBlock} record
78
+ * @param {string} fieldName
79
+ * @param {"symbol" | "string" | "symbol_list" | "string_list"} expected
80
+ * @returns {void}
81
+ */
82
+ function validateRecordFieldShape(errors, statement, record, fieldName, expected) {
83
+ const field = recordField(record, fieldName);
84
+ if (!field || !field.value) return;
85
+ if (expected === "symbol" && field.value.type !== "symbol") {
86
+ pushError(errors, `Journey ${statement.id} record field '${fieldName}' must be a symbol`, field.loc);
87
+ }
88
+ if (expected === "string" && field.value.type !== "string") {
89
+ pushError(errors, `Journey ${statement.id} record field '${fieldName}' must be a string`, field.loc);
90
+ }
91
+ if (expected === "symbol_list" && field.value.type !== "list") {
92
+ pushError(errors, `Journey ${statement.id} record field '${fieldName}' must be a list of symbols`, field.loc);
93
+ }
94
+ if (expected === "string_list" && field.value.type !== "list") {
95
+ pushError(errors, `Journey ${statement.id} record field '${fieldName}' must be a list of strings`, field.loc);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @param {ValidationErrors} errors
101
+ * @param {TopogramStatement} statement
102
+ * @param {TopogramFieldMap} fieldMap
103
+ * @returns {Set<string>}
104
+ */
105
+ function validateStepRecords(errors, statement, fieldMap) {
106
+ const fields = fieldMap.get("step") || [];
107
+ /** @type {Set<string>} */
108
+ const stepIds = new Set();
109
+ if (fields.length === 0) {
110
+ pushError(errors, `Journey ${statement.id} must include at least one step`, statement.loc);
111
+ return stepIds;
112
+ }
113
+
114
+ for (const field of fields) {
115
+ const record = validateRecordBlock(errors, statement, field, STEP_FIELDS, ["id", "intent"]);
116
+ if (!record) continue;
117
+ validateRecordFieldShape(errors, statement, record, "id", "symbol");
118
+ validateRecordFieldShape(errors, statement, record, "intent", "string");
119
+ validateRecordFieldShape(errors, statement, record, "commands", "string_list");
120
+ validateRecordFieldShape(errors, statement, record, "expects", "string_list");
121
+ validateRecordFieldShape(errors, statement, record, "after", "symbol_list");
122
+ validateRecordFieldShape(errors, statement, record, "notes", "string");
123
+
124
+ const stepId = recordSymbol(record, "id");
125
+ if (!stepId) continue;
126
+ if (!/^[a-z][a-z0-9_]*$/.test(stepId)) {
127
+ pushError(errors, `Journey ${statement.id} step id must match /^[a-z][a-z0-9_]*$/`, recordField(record, "id")?.loc || field.loc);
128
+ }
129
+ if (stepIds.has(stepId)) {
130
+ pushError(errors, `Journey ${statement.id} has duplicate step '${stepId}'`, recordField(record, "id")?.loc || field.loc);
131
+ }
132
+ stepIds.add(stepId);
133
+ }
134
+
135
+ for (const field of fields) {
136
+ if (field.value.type !== "block") continue;
137
+ const record = parseRecordBlock(/** @type {import("../../parser.js").AstBlock} */ (/** @type {unknown} */ (field.value)));
138
+ const stepId = recordSymbol(record, "id") || "unknown";
139
+ for (const afterId of recordSymbolList(record, "after")) {
140
+ if (!stepIds.has(afterId)) {
141
+ pushError(errors, `Journey ${statement.id} step '${stepId}' after references missing step '${afterId}'`, recordField(record, "after")?.loc || field.loc);
142
+ }
143
+ }
144
+ }
145
+
146
+ return stepIds;
147
+ }
148
+
149
+ /**
150
+ * @param {ValidationErrors} errors
151
+ * @param {TopogramStatement} statement
152
+ * @param {TopogramFieldMap} fieldMap
153
+ * @param {Set<string>} stepIds
154
+ * @returns {void}
155
+ */
156
+ function validateAlternateRecords(errors, statement, fieldMap, stepIds) {
157
+ const fields = fieldMap.get("alternate") || [];
158
+ /** @type {Set<string>} */
159
+ const alternateIds = new Set();
160
+ for (const field of fields) {
161
+ const record = validateRecordBlock(errors, statement, field, ALTERNATE_FIELDS, ["id", "from", "condition"]);
162
+ if (!record) continue;
163
+ validateRecordFieldShape(errors, statement, record, "id", "symbol");
164
+ validateRecordFieldShape(errors, statement, record, "from", "symbol");
165
+ validateRecordFieldShape(errors, statement, record, "condition", "string");
166
+ validateRecordFieldShape(errors, statement, record, "commands", "string_list");
167
+ validateRecordFieldShape(errors, statement, record, "expects", "string_list");
168
+ validateRecordFieldShape(errors, statement, record, "notes", "string");
169
+
170
+ const alternateId = recordSymbol(record, "id");
171
+ if (alternateId) {
172
+ if (!/^[a-z][a-z0-9_]*$/.test(alternateId)) {
173
+ pushError(errors, `Journey ${statement.id} alternate id must match /^[a-z][a-z0-9_]*$/`, recordField(record, "id")?.loc || field.loc);
174
+ }
175
+ if (alternateIds.has(alternateId)) {
176
+ pushError(errors, `Journey ${statement.id} has duplicate alternate '${alternateId}'`, recordField(record, "id")?.loc || field.loc);
177
+ }
178
+ alternateIds.add(alternateId);
179
+ }
180
+
181
+ const fromStep = recordSymbol(record, "from");
182
+ if (fromStep && !stepIds.has(fromStep)) {
183
+ pushError(errors, `Journey ${statement.id} alternate '${alternateId || "unknown"}' from references missing step '${fromStep}'`, recordField(record, "from")?.loc || field.loc);
184
+ }
185
+ }
186
+ }
187
+
188
+ /**
189
+ * @param {ValidationErrors} errors
190
+ * @param {TopogramStatement} statement
191
+ * @param {TopogramFieldMap} fieldMap
192
+ * @returns {void}
193
+ */
194
+ function validateTopLevelListShapes(errors, statement, fieldMap) {
195
+ const stringListFields = ["success_signals", "failure_signals", "tags"];
196
+ for (const key of stringListFields) {
197
+ const field = fieldMap.get(key)?.[0];
198
+ if (!field || field.value.type !== "list") continue;
199
+ for (const item of field.value.items) {
200
+ if (item.type !== "string" && key !== "tags") {
201
+ pushError(errors, `Journey ${statement.id} field '${key}' must contain strings`, item.loc);
202
+ }
203
+ if (key === "tags" && item.type !== "symbol" && item.type !== "string") {
204
+ pushError(errors, `Journey ${statement.id} field '${key}' must contain symbols or strings`, item.loc);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Keep referenced imports used by checkJs while making the expected record
212
+ * helper vocabulary obvious to future validators.
213
+ */
214
+ void recordString;
215
+ void recordStringList;
216
+
217
+ /**
218
+ * @param {ValidationErrors} errors
219
+ * @param {TopogramStatement} statement
220
+ * @param {TopogramFieldMap} fieldMap
221
+ * @param {TopogramRegistry} registry
222
+ * @returns {void}
223
+ */
224
+ export function validateJourney(errors, statement, fieldMap, registry) {
225
+ void registry;
226
+ if (statement.kind !== "journey") {
227
+ return;
228
+ }
229
+ validateJourneyIdentifier(errors, statement);
230
+ validateTopLevelListShapes(errors, statement, fieldMap);
231
+ const stepIds = validateStepRecords(errors, statement, fieldMap);
232
+ validateAlternateRecords(errors, statement, fieldMap, stepIds);
233
+ }
@@ -215,7 +215,10 @@ export function generateDocsWorkflow(inputPath) {
215
215
  export function generateJourneyDraftsWorkflow(inputPath) {
216
216
  const paths = normalizeWorkspacePaths(inputPath);
217
217
  const graph = loadResolvedGraph(paths.topogramRoot);
218
- const canonicalJourneys = (graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey");
218
+ const canonicalJourneys = [
219
+ ...(graph.byKind?.journey || []),
220
+ ...(graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey")
221
+ ];
219
222
  const { drafts, skippedEntities } = buildJourneyDraftsReconcile(graph);
220
223
  /** @type {WorkflowFiles} */
221
224
  /** @type {WorkflowFiles} */
@@ -62,9 +62,11 @@ export function bundleLabelFromConceptId(conceptId) {
62
62
  /** @param {ResolvedGraph} graph @returns {any} */
63
63
  export function canonicalJourneyCoverage(graph) {
64
64
  const journeyDocs = (graph?.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey");
65
+ const journeyStatements = graph?.byKind?.journey || [];
66
+ const journeys = [...journeyStatements, ...journeyDocs];
65
67
  return {
66
- byEntityId: new Set(journeyDocs.flatMap((/** @type {any} */ doc) => doc.relatedEntities || [])),
67
- byCapabilityId: new Set(journeyDocs.flatMap((/** @type {any} */ doc) => doc.relatedCapabilities || []))
68
+ byEntityId: new Set(journeys.flatMap((/** @type {any} */ journey) => journey.relatedEntities || [])),
69
+ byCapabilityId: new Set(journeys.flatMap((/** @type {any} */ journey) => journey.relatedCapabilities || []))
68
70
  };
69
71
  }
70
72
 
@@ -133,7 +133,10 @@ export function collectCanonicalWorkflowSurface(graph) {
133
133
 
134
134
  /** @param {ResolvedGraph} graph @returns {any} */
135
135
  export function collectCanonicalActorRoleSurface(graph) {
136
- const journeyDocs = (graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey");
136
+ const journeyDocs = [
137
+ ...(graph.byKind?.journey || []),
138
+ ...(graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey")
139
+ ];
137
140
  const workflowDocs = (graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "workflow");
138
141
  return {
139
142
  actor_ids: ((graph.byKind.actor || []).map((/** @type {any} */ entry) => entry.id)).sort(),
@@ -1,94 +0,0 @@
1
- // @ts-check
2
-
3
- import path from "node:path";
4
-
5
- import { resolveCatalogTemplateAlias } from "../catalog-alias.js";
6
- import { GENERATOR_POLICY_FILE } from "../../generator-policy.js";
7
- import { createNewProject } from "../../new-project.js";
8
-
9
- const ENGINE_ROOT = path.resolve(decodeURIComponent(new URL("../../../", import.meta.url).pathname));
10
- const TEMPLATES_ROOT = path.join(ENGINE_ROOT, "templates");
11
-
12
- /**
13
- * @param {ReturnType<typeof createNewProject>} result
14
- * @param {string} cwd
15
- * @returns {string}
16
- */
17
- function displayProjectRootForNewProject(result, cwd) {
18
- const relativeProjectRoot = path.relative(cwd, result.projectRoot);
19
- return !relativeProjectRoot || relativeProjectRoot.startsWith("..")
20
- ? result.projectRoot
21
- : relativeProjectRoot;
22
- }
23
-
24
- /**
25
- * @param {ReturnType<typeof createNewProject>} result
26
- * @param {string} cwd
27
- * @returns {void}
28
- */
29
- export function printNewProjectResult(result, cwd) {
30
- const template = result.template || {};
31
- console.log(`Created Topogram project at ${result.projectRoot}.`);
32
- console.log(`Template: ${result.templateName}`);
33
- console.log(`Source: ${template.source || "unknown"}`);
34
- if (template.sourceSpec) {
35
- console.log(`Source spec: ${template.sourceSpec}`);
36
- }
37
- if (template.catalog) {
38
- console.log(`Catalog: ${template.catalog.id} from ${template.catalog.source}`);
39
- console.log(`Package: ${template.catalog.packageSpec}`);
40
- }
41
- console.log(`Executable implementation: ${template.includesExecutableImplementation ? "yes" : "no"}`);
42
- console.log("Policy: topogram.template-policy.json");
43
- console.log(`Generator policy: ${GENERATOR_POLICY_FILE}`);
44
- console.log("Template files: .topogram-template-files.json");
45
- if (template.includesExecutableImplementation) {
46
- console.log("Trust: .topogram-template-trust.json");
47
- }
48
- for (const warning of result.warnings) {
49
- console.warn(`Warning: ${warning}`);
50
- }
51
- console.log("");
52
- console.log("Next steps:");
53
- console.log(` cd ${displayProjectRootForNewProject(result, cwd)}`);
54
- console.log(" npm install");
55
- console.log(" npm run agent:brief");
56
- console.log(" npm run doctor");
57
- console.log(" npm run source:status");
58
- console.log(" npm run template:explain");
59
- console.log(" npm run check");
60
- console.log(" npm run generator:policy:status");
61
- console.log(" npm run generator:policy:check");
62
- if (template.includesExecutableImplementation) {
63
- console.log(" npm run template:policy:explain");
64
- console.log(" npm run trust:status");
65
- }
66
- console.log(" npm run generate");
67
- console.log(" npm run verify");
68
- }
69
-
70
- /**
71
- * @param {string} inputPath
72
- * @param {{ templateName: string, catalogSource?: string|null, cwd?: string }} options
73
- * @returns {number}
74
- */
75
- export function runNewProjectCommand(inputPath, options) {
76
- const cwd = options.cwd || process.cwd();
77
- const projectRoot = path.resolve(inputPath);
78
- const relativeToEngine = path.relative(ENGINE_ROOT, projectRoot);
79
- if (relativeToEngine === "" || (!relativeToEngine.startsWith("..") && !path.isAbsolute(relativeToEngine))) {
80
- throw new Error(
81
- `Refusing to create a generated project inside the engine directory. Use a path outside engine, for example '../${path.basename(projectRoot)}'.`
82
- );
83
- }
84
- const resolvedTemplate = resolveCatalogTemplateAlias(options.templateName, options.catalogSource || null);
85
- const result = createNewProject({
86
- targetPath: inputPath,
87
- templateName: resolvedTemplate.templateName,
88
- templateProvenance: resolvedTemplate.provenance,
89
- engineRoot: ENGINE_ROOT,
90
- templatesRoot: TEMPLATES_ROOT
91
- });
92
- printNewProjectResult(result, cwd);
93
- return 0;
94
- }