@topogram/cli 0.3.64 → 0.3.66
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 +716 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.js +1 -1
- package/src/agent-brief.js +7 -21
- 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 +677 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/archive/jsonl.js +2 -2
- package/src/archive/resolver-bridge.js +1 -1
- package/src/archive/unarchive.js +2 -1
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +65 -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 +123 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/command-parsers/project.js +3 -0
- package/src/cli/command-parsers/shared.js +1 -1
- package/src/cli/commands/agent.js +2 -2
- 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 +30 -615
- package/src/cli/commands/check.js +3 -3
- package/src/cli/commands/doctor.js +2 -9
- 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 +15 -783
- 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 +269 -0
- package/src/cli/commands/import/plan.js +292 -0
- package/src/cli/commands/import/refresh.js +471 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +233 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/migrate.js +153 -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 +29 -813
- 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 +270 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/source.js +3 -12
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +467 -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 +6 -6
- package/src/cli/commands/template.js +41 -2143
- package/src/cli/commands/trust.js +1 -1
- package/src/cli/commands/workflow.js +6 -1
- package/src/cli/dispatcher.js +6 -1
- package/src/cli/help.js +15 -14
- package/src/cli/migration-guidance.js +1 -1
- package/src/cli/output-safety.js +2 -1
- package/src/cli/path-normalization.js +3 -13
- 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/context/domain-page.js +1 -1
- 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 +42 -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/context/task-mode.js +2 -2
- 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/sdlc/doc-page.js +1 -1
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
- package/src/generator/surfaces/shared.d.ts +3 -0
- 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/import/core/context.d.ts +3 -0
- package/src/import/core/context.js +5 -7
- 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 +337 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +51 -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 +393 -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 +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +90 -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 +351 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +370 -0
- package/src/new-project/template-snapshots.js +442 -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 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +591 -0
- package/src/project-config.js +19 -561
- 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/sdlc/adopt.js +6 -5
- package/src/sdlc/paths.js +3 -5
- package/src/sdlc/scaffold.js +2 -1
- 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 +1 -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/widget-behavior.d.ts +1 -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 +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +212 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +153 -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 +30 -740
- 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 +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/candidate-model.js +18 -2
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
- package/src/workflows/reconcile/impacts/indexes.js +105 -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 +14 -623
- package/src/workflows/reconcile/renderers.js +41 -6
- package/src/workflows/shared.js +5 -11
- package/src/workspace-docs.d.ts +29 -0
- package/src/workspace-paths.js +328 -0
|
@@ -113,17 +113,31 @@ export function shapeIdForCapability(record, direction) {
|
|
|
113
113
|
return direction === "input" ? `shape_input_${stem}` : `shape_output_${stem}`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/** @param {string} shapeId @param {string} label @param {any[]} fields @returns {any} */
|
|
117
|
-
export function renderCandidateShape(shapeId, label, fields) {
|
|
116
|
+
/** @param {string} shapeId @param {string} label @param {any[]} fields @param {string|null} [sourceKind] @returns {any} */
|
|
117
|
+
export function renderCandidateShape(shapeId, label, fields, sourceKind = null) {
|
|
118
|
+
/** @param {any} field @returns {{ name: string, fieldType: string, requiredness: string }} */
|
|
119
|
+
function normalizeField(field) {
|
|
120
|
+
if (typeof field === "string") {
|
|
121
|
+
return { name: field, fieldType: "string", requiredness: "optional" };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
name: field?.name || "id",
|
|
125
|
+
fieldType: field?.field_type || field?.type || "string",
|
|
126
|
+
requiredness: field?.required ? "required" : "optional"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
118
129
|
const lines = [
|
|
119
130
|
`shape ${shapeId} {`,
|
|
120
131
|
` name "${label}"`,
|
|
121
|
-
|
|
132
|
+
sourceKind === "ui_widget_event"
|
|
133
|
+
? ` description "Candidate event payload shape imported from brownfield UI interaction evidence"`
|
|
134
|
+
: ` description "Candidate shape imported from brownfield API evidence"`,
|
|
122
135
|
"",
|
|
123
136
|
" fields {"
|
|
124
137
|
];
|
|
125
138
|
for (const field of fields) {
|
|
126
|
-
|
|
139
|
+
const normalized = normalizeField(field);
|
|
140
|
+
lines.push(` ${normalized.name} ${normalized.fieldType} ${normalized.requiredness}`);
|
|
127
141
|
}
|
|
128
142
|
lines.push(" }", "", " status active", "}");
|
|
129
143
|
return ensureTrailingNewline(lines.join("\n"));
|
|
@@ -236,7 +250,6 @@ export function renderCandidateUiReportDoc(screen, routes, actions) {
|
|
|
236
250
|
source_of_truth: "imported",
|
|
237
251
|
confidence: screen.confidence || "medium",
|
|
238
252
|
review_required: true,
|
|
239
|
-
related_entities: [screen.entity_id].filter(Boolean),
|
|
240
253
|
provenance: screen.provenance || [],
|
|
241
254
|
tags: ["import", "ui"]
|
|
242
255
|
};
|
|
@@ -244,11 +257,12 @@ export function renderCandidateUiReportDoc(screen, routes, actions) {
|
|
|
244
257
|
"Candidate UI surface imported from brownfield route evidence.",
|
|
245
258
|
"",
|
|
246
259
|
`Screen: \`${screen.id_hint}\` (${screen.screen_kind})`,
|
|
260
|
+
screen.entity_id ? `Inferred entity: \`${screen.entity_id}\`` : null,
|
|
247
261
|
`Routes: ${routes.length ? routes.map((/** @type {any} */ route) => `\`${route.path}\``).join(", ") : "_none_"}`,
|
|
248
262
|
`Actions: ${actions.length ? actions.map((/** @type {any} */ action) => `\`${action.capability_hint}\``).join(", ") : "_none_"}`,
|
|
249
263
|
"",
|
|
250
264
|
"Review this UI surface before promoting it into canonical docs or projections."
|
|
251
|
-
].join("\n");
|
|
265
|
+
].filter(Boolean).join("\n");
|
|
252
266
|
return renderMarkdownDoc(metadata, body);
|
|
253
267
|
}
|
|
254
268
|
|
|
@@ -257,15 +271,36 @@ export function renderCandidateWidget(widget) {
|
|
|
257
271
|
const propName = widget.data_prop || "rows";
|
|
258
272
|
const pattern = widget.pattern || "search_results";
|
|
259
273
|
const region = widget.region || "results";
|
|
274
|
+
const inferredEvents = Array.isArray(widget.inferred_events) ? widget.inferred_events : [];
|
|
275
|
+
const eventLines = inferredEvents
|
|
276
|
+
.filter((/** @type {any} */ event) => event.name && event.payload_shape)
|
|
277
|
+
.map((/** @type {any} */ event) => ` ${event.name} ${event.payload_shape}`);
|
|
278
|
+
const selectionEvent = inferredEvents.find((/** @type {any} */ event) => event.name);
|
|
279
|
+
const eventComments = inferredEvents.map((/** @type {any} */ event) =>
|
|
280
|
+
` # Inferred event: ${event.name || "event"} ${event.action || "action"} ${event.target_screen || event.target || "target"}.`
|
|
281
|
+
);
|
|
282
|
+
const behaviorLines = eventLines.length > 0
|
|
283
|
+
? [
|
|
284
|
+
" events {",
|
|
285
|
+
...eventLines,
|
|
286
|
+
" }",
|
|
287
|
+
" behavior [selection]",
|
|
288
|
+
" behaviors {",
|
|
289
|
+
` selection mode single emits ${selectionEvent?.name || "row_select"}`,
|
|
290
|
+
" }"
|
|
291
|
+
]
|
|
292
|
+
: [];
|
|
260
293
|
return ensureTrailingNewline(
|
|
261
294
|
[
|
|
262
295
|
`widget ${widget.id_hint} {`,
|
|
263
296
|
` name "${widget.label || widget.id_hint}"`,
|
|
264
297
|
' description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."',
|
|
265
298
|
" category collection",
|
|
299
|
+
...eventComments,
|
|
266
300
|
" props {",
|
|
267
301
|
` ${propName} array required`,
|
|
268
302
|
" }",
|
|
303
|
+
...behaviorLines,
|
|
269
304
|
` patterns [${pattern}]`,
|
|
270
305
|
` regions [${region}]`,
|
|
271
306
|
" status proposed",
|
package/src/workflows/shared.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { ensureTrailingNewline, titleCase } from "../text-helpers.js";
|
|
6
|
+
import { resolveWorkspaceContext } from "../workspace-paths.js";
|
|
6
7
|
|
|
7
8
|
/** @param {string} startDir @returns {any} */
|
|
8
9
|
export function findNearestGitRoot(startDir) {
|
|
@@ -21,17 +22,10 @@ export function findNearestGitRoot(startDir) {
|
|
|
21
22
|
|
|
22
23
|
/** @param {string} inputPath @returns {any} */
|
|
23
24
|
export function normalizeWorkspacePaths(inputPath) {
|
|
25
|
+
const context = resolveWorkspaceContext(inputPath);
|
|
24
26
|
const absolute = path.resolve(inputPath);
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const isTopogramDir = path.basename(absolute) === "topogram" && inputExists;
|
|
28
|
-
const bootstrapWorkspaceRoot = !isTopogramDir && !hasTopogramChild;
|
|
29
|
-
const topogramRoot = isTopogramDir
|
|
30
|
-
? absolute
|
|
31
|
-
: hasTopogramChild
|
|
32
|
-
? path.join(absolute, "topogram")
|
|
33
|
-
: path.join(absolute, "topogram");
|
|
34
|
-
const workspaceRoot = isTopogramDir ? path.dirname(topogramRoot) : absolute;
|
|
27
|
+
const topogramRoot = context.topoRoot;
|
|
28
|
+
const workspaceRoot = context.projectRoot;
|
|
35
29
|
const repoRoot = findNearestGitRoot(workspaceRoot);
|
|
36
30
|
return {
|
|
37
31
|
inputRoot: absolute,
|
|
@@ -39,7 +33,7 @@ export function normalizeWorkspacePaths(inputPath) {
|
|
|
39
33
|
workspaceRoot,
|
|
40
34
|
exampleRoot: workspaceRoot,
|
|
41
35
|
repoRoot,
|
|
42
|
-
bootstrappedTopogramRoot:
|
|
36
|
+
bootstrappedTopogramRoot: context.bootstrappedTopoRoot
|
|
43
37
|
};
|
|
44
38
|
}
|
|
45
39
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TopogramLocation } from "./topogram-types.js";
|
|
2
|
+
|
|
3
|
+
export const DOC_KINDS: Set<string>;
|
|
4
|
+
export const DOC_STATUSES: Set<string>;
|
|
5
|
+
export const DOC_CONFIDENCE: Set<string>;
|
|
6
|
+
export const DOC_AUDIENCES: Set<string>;
|
|
7
|
+
export const DOC_PRIORITIES: Set<string>;
|
|
8
|
+
export const DOC_ARRAY_FIELDS: Set<string>;
|
|
9
|
+
export const DOC_REFERENCE_FIELDS: Record<string, string>;
|
|
10
|
+
export const DOC_SCALAR_FIELDS: Set<string>;
|
|
11
|
+
|
|
12
|
+
export interface TopogramDocParseError {
|
|
13
|
+
message: string;
|
|
14
|
+
loc: TopogramLocation;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TopogramDoc {
|
|
18
|
+
type: "doc";
|
|
19
|
+
file: string;
|
|
20
|
+
relativePath: string;
|
|
21
|
+
metadata: Record<string, any>;
|
|
22
|
+
body: string;
|
|
23
|
+
loc: TopogramLocation;
|
|
24
|
+
parseError: TopogramDocParseError | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function collectTopogramDocFiles(inputPath: string): string[];
|
|
28
|
+
export function parseDocFile(filePath: string, workspaceRoot: string): TopogramDoc;
|
|
29
|
+
export function parseDocsPath(inputPath: string): TopogramDoc[];
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_TOPO_FOLDER_NAME = "topo";
|
|
7
|
+
export const LEGACY_TOPOGRAM_FOLDER_NAME = "topogram";
|
|
8
|
+
export const DEFAULT_WORKSPACE_PATH = `./${DEFAULT_TOPO_FOLDER_NAME}`;
|
|
9
|
+
export const PROJECT_CONFIG_FILE = "topogram.project.json";
|
|
10
|
+
|
|
11
|
+
const SIGNAL_SCAN_IGNORED_DIRS = new Set([
|
|
12
|
+
".git",
|
|
13
|
+
".next",
|
|
14
|
+
".tmp",
|
|
15
|
+
".turbo",
|
|
16
|
+
".yarn",
|
|
17
|
+
"app",
|
|
18
|
+
"build",
|
|
19
|
+
"coverage",
|
|
20
|
+
"dist",
|
|
21
|
+
"expected",
|
|
22
|
+
"node_modules",
|
|
23
|
+
"tmp"
|
|
24
|
+
]);
|
|
25
|
+
const WORKSPACE_SIGNAL_DIRS = new Set([
|
|
26
|
+
"_archive",
|
|
27
|
+
"acceptance_criteria",
|
|
28
|
+
"actors",
|
|
29
|
+
"bugs",
|
|
30
|
+
"capabilities",
|
|
31
|
+
"decisions",
|
|
32
|
+
"domains",
|
|
33
|
+
"entities",
|
|
34
|
+
"enums",
|
|
35
|
+
"operations",
|
|
36
|
+
"pitches",
|
|
37
|
+
"projections",
|
|
38
|
+
"requirements",
|
|
39
|
+
"rules",
|
|
40
|
+
"shapes",
|
|
41
|
+
"tasks",
|
|
42
|
+
"terms",
|
|
43
|
+
"verifications",
|
|
44
|
+
"widgets",
|
|
45
|
+
"workflows"
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} WorkspaceResolution
|
|
50
|
+
* @property {string} inputRoot
|
|
51
|
+
* @property {string} topoRoot
|
|
52
|
+
* @property {string} projectRoot
|
|
53
|
+
* @property {string|null} configPath
|
|
54
|
+
* @property {boolean} fromConfig
|
|
55
|
+
* @property {boolean} fromSignal
|
|
56
|
+
* @property {boolean} bootstrappedTopoRoot
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} candidatePath
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
function isDirectory(candidatePath) {
|
|
64
|
+
try {
|
|
65
|
+
return fs.existsSync(candidatePath) && fs.statSync(candidatePath).isDirectory();
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} filePath
|
|
73
|
+
* @returns {any}
|
|
74
|
+
*/
|
|
75
|
+
function readJson(filePath) {
|
|
76
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} startPath
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function searchStartDirectory(startPath) {
|
|
84
|
+
const absolute = path.resolve(startPath || ".");
|
|
85
|
+
if (fs.existsSync(absolute) && fs.statSync(absolute).isFile()) {
|
|
86
|
+
return path.dirname(absolute);
|
|
87
|
+
}
|
|
88
|
+
if (!fs.existsSync(absolute) && path.basename(absolute) === DEFAULT_TOPO_FOLDER_NAME) {
|
|
89
|
+
return path.dirname(absolute);
|
|
90
|
+
}
|
|
91
|
+
return absolute;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} startPath
|
|
96
|
+
* @returns {{ config: any, configPath: string, configDir: string }|null}
|
|
97
|
+
*/
|
|
98
|
+
export function findProjectRoot(startPath) {
|
|
99
|
+
let current = searchStartDirectory(startPath);
|
|
100
|
+
while (current && current !== path.dirname(current)) {
|
|
101
|
+
const candidate = path.join(current, PROJECT_CONFIG_FILE);
|
|
102
|
+
if (fs.existsSync(candidate)) {
|
|
103
|
+
return {
|
|
104
|
+
config: readJson(candidate),
|
|
105
|
+
configPath: candidate,
|
|
106
|
+
configDir: current
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
current = path.dirname(current);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} workspacePath
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
export function normalizeWorkspaceConfigPath(workspacePath) {
|
|
119
|
+
const value = String(workspacePath || "").trim();
|
|
120
|
+
if (!value) {
|
|
121
|
+
throw new Error("topogram.project.json workspace must be a non-empty relative path.");
|
|
122
|
+
}
|
|
123
|
+
if (path.isAbsolute(value)) {
|
|
124
|
+
throw new Error("topogram.project.json workspace must be relative to the project root.");
|
|
125
|
+
}
|
|
126
|
+
const normalized = value.replace(/\\/g, "/");
|
|
127
|
+
const resolved = path.posix.normalize(normalized);
|
|
128
|
+
if (resolved === ".." || resolved.startsWith("../")) {
|
|
129
|
+
throw new Error("topogram.project.json workspace must not escape the project root.");
|
|
130
|
+
}
|
|
131
|
+
return normalized;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {any} config
|
|
136
|
+
* @param {string} configDir
|
|
137
|
+
* @returns {string}
|
|
138
|
+
*/
|
|
139
|
+
export function resolveProjectWorkspace(config, configDir) {
|
|
140
|
+
if (config && Object.prototype.hasOwnProperty.call(config, "workspaces")) {
|
|
141
|
+
throw new Error("topogram.project.json workspaces[] is not supported yet; use single workspace instead.");
|
|
142
|
+
}
|
|
143
|
+
const configured = config?.workspace == null ? DEFAULT_WORKSPACE_PATH : config.workspace;
|
|
144
|
+
const normalized = normalizeWorkspaceConfigPath(configured);
|
|
145
|
+
return path.resolve(configDir, normalized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} root
|
|
150
|
+
* @param {number} [maxDepth]
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
export function workspaceHasTgFiles(root, maxDepth = 3) {
|
|
154
|
+
if (!isDirectory(root)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const walk = (/** @type {string} */ current, /** @type {number} */ depth) => {
|
|
158
|
+
if (depth > maxDepth) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
162
|
+
if (SIGNAL_SCAN_IGNORED_DIRS.has(entry.name)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const child = path.join(current, entry.name);
|
|
166
|
+
if (entry.isFile() && entry.name.endsWith(".tg")) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (entry.isDirectory() && walk(child, depth + 1)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
};
|
|
175
|
+
return walk(root, 0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {string} root
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
*/
|
|
182
|
+
function isWorkspaceSignalRoot(root) {
|
|
183
|
+
if (!isDirectory(root)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
187
|
+
if (entry.isFile() && entry.name.endsWith(".tg")) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (entry.isDirectory() && WORKSPACE_SIGNAL_DIRS.has(entry.name) && workspaceHasTgFiles(path.join(root, entry.name), 2)) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {string} root
|
|
199
|
+
* @returns {string[]}
|
|
200
|
+
*/
|
|
201
|
+
function signalWorkspaceCandidates(root) {
|
|
202
|
+
if (!isDirectory(root)) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
/** @type {string[]} */
|
|
206
|
+
const candidates = [];
|
|
207
|
+
if (isWorkspaceSignalRoot(root)) {
|
|
208
|
+
candidates.push(root);
|
|
209
|
+
}
|
|
210
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
211
|
+
if (!entry.isDirectory() || SIGNAL_SCAN_IGNORED_DIRS.has(entry.name)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const child = path.join(root, entry.name);
|
|
215
|
+
if (isWorkspaceSignalRoot(child)) {
|
|
216
|
+
candidates.push(child);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return [...new Set(candidates.map((candidate) => path.resolve(candidate)))].sort();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {string} inputPath
|
|
224
|
+
* @returns {WorkspaceResolution}
|
|
225
|
+
*/
|
|
226
|
+
export function resolveWorkspaceContext(inputPath = ".") {
|
|
227
|
+
const absolute = path.resolve(inputPath || ".");
|
|
228
|
+
if (
|
|
229
|
+
isDirectory(absolute) &&
|
|
230
|
+
(
|
|
231
|
+
path.basename(absolute) === DEFAULT_TOPO_FOLDER_NAME ||
|
|
232
|
+
(isWorkspaceSignalRoot(absolute) && !isDirectory(path.join(absolute, DEFAULT_TOPO_FOLDER_NAME)))
|
|
233
|
+
)
|
|
234
|
+
) {
|
|
235
|
+
return {
|
|
236
|
+
inputRoot: absolute,
|
|
237
|
+
topoRoot: absolute,
|
|
238
|
+
projectRoot: path.basename(absolute) === DEFAULT_TOPO_FOLDER_NAME ? path.dirname(absolute) : absolute,
|
|
239
|
+
configPath: null,
|
|
240
|
+
fromConfig: false,
|
|
241
|
+
fromSignal: false,
|
|
242
|
+
bootstrappedTopoRoot: false
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const configInfo = findProjectRoot(absolute);
|
|
247
|
+
if (configInfo) {
|
|
248
|
+
const topoRoot = resolveProjectWorkspace(configInfo.config, configInfo.configDir);
|
|
249
|
+
return {
|
|
250
|
+
inputRoot: absolute,
|
|
251
|
+
topoRoot,
|
|
252
|
+
projectRoot: configInfo.configDir,
|
|
253
|
+
configPath: configInfo.configPath,
|
|
254
|
+
fromConfig: true,
|
|
255
|
+
fromSignal: false,
|
|
256
|
+
bootstrappedTopoRoot: !fs.existsSync(topoRoot)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const searchBase = !fs.existsSync(absolute) && path.basename(absolute) === DEFAULT_TOPO_FOLDER_NAME
|
|
261
|
+
? path.dirname(absolute)
|
|
262
|
+
: absolute;
|
|
263
|
+
const defaultCandidate = path.join(searchBase, DEFAULT_TOPO_FOLDER_NAME);
|
|
264
|
+
if (isDirectory(defaultCandidate)) {
|
|
265
|
+
return {
|
|
266
|
+
inputRoot: absolute,
|
|
267
|
+
topoRoot: defaultCandidate,
|
|
268
|
+
projectRoot: searchBase,
|
|
269
|
+
configPath: null,
|
|
270
|
+
fromConfig: false,
|
|
271
|
+
fromSignal: false,
|
|
272
|
+
bootstrappedTopoRoot: false
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const signalCandidates = signalWorkspaceCandidates(searchBase);
|
|
277
|
+
if (signalCandidates.length === 1) {
|
|
278
|
+
const topoRoot = signalCandidates[0];
|
|
279
|
+
return {
|
|
280
|
+
inputRoot: absolute,
|
|
281
|
+
topoRoot,
|
|
282
|
+
projectRoot: topoRoot,
|
|
283
|
+
configPath: null,
|
|
284
|
+
fromConfig: false,
|
|
285
|
+
fromSignal: true,
|
|
286
|
+
bootstrappedTopoRoot: false
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (signalCandidates.length > 1) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Multiple Topogram workspace candidates found. Pass one explicitly: ${signalCandidates.join(", ")}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
inputRoot: absolute,
|
|
297
|
+
topoRoot: defaultCandidate,
|
|
298
|
+
projectRoot: searchBase,
|
|
299
|
+
configPath: null,
|
|
300
|
+
fromConfig: false,
|
|
301
|
+
fromSignal: false,
|
|
302
|
+
bootstrappedTopoRoot: true
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param {string} inputPath
|
|
308
|
+
* @returns {string}
|
|
309
|
+
*/
|
|
310
|
+
export function resolveTopoRoot(inputPath = ".") {
|
|
311
|
+
return resolveWorkspaceContext(inputPath).topoRoot;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* @param {string} packageRoot
|
|
316
|
+
* @returns {{ root: string, legacy: boolean }}
|
|
317
|
+
*/
|
|
318
|
+
export function resolvePackageWorkspace(packageRoot) {
|
|
319
|
+
const topoRoot = path.join(packageRoot, DEFAULT_TOPO_FOLDER_NAME);
|
|
320
|
+
if (isDirectory(topoRoot)) {
|
|
321
|
+
return { root: topoRoot, legacy: false };
|
|
322
|
+
}
|
|
323
|
+
const legacyRoot = path.join(packageRoot, LEGACY_TOPOGRAM_FOLDER_NAME);
|
|
324
|
+
if (isDirectory(legacyRoot)) {
|
|
325
|
+
return { root: legacyRoot, legacy: true };
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`Package is missing ${DEFAULT_TOPO_FOLDER_NAME}/.`);
|
|
328
|
+
}
|