@topogram/cli 0.3.62 → 0.3.64
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.d.ts +6 -0
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog.d.ts +10 -0
- package/src/catalog.js +62 -66
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog.js +617 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy.js +785 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +1734 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package.js +815 -0
- package/src/cli/commands/query.js +1302 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +2145 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared.d.ts +2 -0
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/shared.js +20 -62
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project.js +100 -11
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +1 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/project-config.js +1 -0
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/text-helpers.d.ts +6 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/validator.d.ts +2 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api.js +799 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan.js +742 -0
- package/src/workflows/reconcile/auth.js +692 -0
- package/src/workflows/reconcile/bundle-core.js +600 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts.js +625 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @param {string} value @returns {any} */
|
|
4
|
+
export function provenanceList(value) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
12
|
+
export function collectBundleProvenance(bundle) {
|
|
13
|
+
const values = new Set();
|
|
14
|
+
for (const entry of bundle.docs || []) {
|
|
15
|
+
for (const provenance of provenanceList(entry.provenance)) values.add(provenance);
|
|
16
|
+
}
|
|
17
|
+
for (const entry of bundle.workflows || []) {
|
|
18
|
+
for (const provenance of provenanceList(entry.provenance)) values.add(provenance);
|
|
19
|
+
}
|
|
20
|
+
for (const entry of bundle.capabilities || []) {
|
|
21
|
+
for (const provenance of provenanceList(entry.provenance)) values.add(provenance);
|
|
22
|
+
}
|
|
23
|
+
for (const entry of bundle.screens || []) {
|
|
24
|
+
for (const provenance of provenanceList(entry.provenance)) values.add(provenance);
|
|
25
|
+
}
|
|
26
|
+
for (const entry of bundle.entities || []) {
|
|
27
|
+
for (const provenance of provenanceList(entry.provenance)) values.add(provenance);
|
|
28
|
+
}
|
|
29
|
+
return values;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
33
|
+
export function contextualEvidenceScore(bundle) {
|
|
34
|
+
return (
|
|
35
|
+
(bundle.docs || []).length * 4 +
|
|
36
|
+
(bundle.workflows || []).length * 4 +
|
|
37
|
+
(bundle.capabilities || []).length * 3 +
|
|
38
|
+
(bundle.screens || []).length * 2 +
|
|
39
|
+
(bundle.entities || []).length * 2
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
44
|
+
export function collectBundleDocIds(bundle) {
|
|
45
|
+
return new Set((bundle.docs || []).map((/** @type {any} */ entry) => entry.id));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
49
|
+
export function collectBundleCapabilityIds(bundle) {
|
|
50
|
+
return new Set([
|
|
51
|
+
...(bundle.capabilities || []).map((/** @type {any} */ entry) => entry.id_hint),
|
|
52
|
+
...(bundle.verifications || []).flatMap((/** @type {any} */ entry) => entry.related_capabilities || [])
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
57
|
+
export function collectBundleRuleIds(bundle) {
|
|
58
|
+
return [...new Set([
|
|
59
|
+
...(bundle.docs || []).flatMap((/** @type {any} */ entry) => entry.related_rules || []),
|
|
60
|
+
...(bundle.docLinkSuggestions || []).flatMap((/** @type {any} */ entry) => entry.add_related_rules || []),
|
|
61
|
+
...(bundle.docMetadataPatches || []).flatMap((/** @type {any} */ entry) => entry.related_rules || [])
|
|
62
|
+
])].sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
67
|
+
export function primaryEntityIdForBundle(bundle) {
|
|
68
|
+
const entityIds = [
|
|
69
|
+
...(bundle.entities || []).map((/** @type {any} */ entry) => entry.id_hint),
|
|
70
|
+
...(bundle.capabilities || []).map((/** @type {any} */ entry) => entry.entity_id).filter(Boolean),
|
|
71
|
+
...(bundle.screens || []).map((/** @type {any} */ entry) => entry.entity_id).filter(Boolean),
|
|
72
|
+
...(bundle.workflows || []).map((/** @type {any} */ entry) => entry.entity_id).filter(Boolean)
|
|
73
|
+
];
|
|
74
|
+
return entityIds.find(Boolean) || null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { buildBundleDocDriftSummaries as buildBundleDocDriftSummariesReconcile, buildBundleDocMetadataPatches as buildBundleDocMetadataPatchesReconcile } from "../../reconcile/docs.js";
|
|
3
|
+
import { canonicalCandidateTerm, idHintify, titleCase } from "../../text-helpers.js";
|
|
4
|
+
import { confidenceRank, docDirForKind, renderCandidateActor, renderCandidateRole } from "../docs.js";
|
|
5
|
+
import { inferCapabilityEntityId } from "../import-app/index.js";
|
|
6
|
+
import { readTextIfExists, renderMarkdownDoc } from "../shared.js";
|
|
7
|
+
import {
|
|
8
|
+
annotateDocLinkSuggestionsWithAuthRoleGuidance,
|
|
9
|
+
classifyBundleAuthRoleGuidance,
|
|
10
|
+
inferBundleAuthClaimHints,
|
|
11
|
+
inferBundleAuthOwnershipHints,
|
|
12
|
+
inferBundleAuthPermissionHints,
|
|
13
|
+
inferBundleAuthRoleGuidance
|
|
14
|
+
} from "./auth.js";
|
|
15
|
+
import { collectBundleCapabilityIds, collectBundleDocIds, collectBundleProvenance, contextualEvidenceScore, provenanceList } from "./bundle-shared.js";
|
|
16
|
+
import { addBundleJourneyDrafts, buildBundleMergeHints, bundleLabelFromConceptId, getOrCreateCandidateBundle, renderCandidateBundleReadme } from "./bundle-core.js";
|
|
17
|
+
import { buildTopogramApiCapabilityIndex, buildBundleDocLinkSuggestions, collectCanonicalUiSurface, collectCanonicalWorkflowSurface, matchImportedApiCapability } from "./canonical-surface.js";
|
|
18
|
+
import { buildBundleAdoptionPlan, buildCanonicalShapeIndex, buildProjectionEntityIndex, buildProjectionImpacts, buildProjectionPatchCandidates, buildUiImpacts, buildWorkflowImpacts } from "./impacts.js";
|
|
19
|
+
import {
|
|
20
|
+
renderCandidateCapability,
|
|
21
|
+
renderCandidateEntity,
|
|
22
|
+
renderCandidateEnum,
|
|
23
|
+
renderCandidateShape,
|
|
24
|
+
renderCandidateUiReportDoc,
|
|
25
|
+
renderCandidateVerification,
|
|
26
|
+
renderCandidateWidget,
|
|
27
|
+
renderCandidateWorkflowDecision,
|
|
28
|
+
renderCandidateWorkflowDoc,
|
|
29
|
+
renderDocLinkPatchDoc,
|
|
30
|
+
renderDocMetadataPatchDoc,
|
|
31
|
+
renderProjectionPatchDoc,
|
|
32
|
+
shapeIdForCapability
|
|
33
|
+
} from "./renderers.js";
|
|
34
|
+
|
|
35
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
36
|
+
export function bundleNoiseSuppressionReason(bundle) {
|
|
37
|
+
if ((bundle.actors || []).length > 0 || (bundle.roles || []).length > 0 || (bundle.capabilities || []).length > 0 || (bundle.workflows || []).length > 0 || (bundle.docs || []).length > 0 || (bundle.screens || []).length > 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const noiseEntities = (bundle.entities || []).filter((/** @type {any} */ entry) => entry.noise_candidate);
|
|
41
|
+
if (noiseEntities.length === 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (noiseEntities.length === (bundle.entities || []).length) {
|
|
45
|
+
return noiseEntities[0].noise_reason || "Rails implementation-noise bundle.";
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @param {Map<string, CandidateBundle>} bundles @param {WorkflowRecord} candidate @returns {any} */
|
|
51
|
+
export function bestContextBundleForCandidate(bundles, candidate) {
|
|
52
|
+
const candidateProvenance = new Set(provenanceList(candidate.provenance));
|
|
53
|
+
const relatedDocs = new Set(candidate.related_docs || []);
|
|
54
|
+
const relatedCapabilities = new Set(candidate.related_capabilities || []);
|
|
55
|
+
let best = null;
|
|
56
|
+
for (const bundle of bundles.values()) {
|
|
57
|
+
const evidenceScore = contextualEvidenceScore(bundle);
|
|
58
|
+
if (evidenceScore === 0) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const provenanceOverlap = candidateProvenance.size > 0
|
|
62
|
+
? [...candidateProvenance].filter((/** @type {any} */ item) => collectBundleProvenance(bundle).has(item)).length
|
|
63
|
+
: 0;
|
|
64
|
+
const docLinkOverlap = relatedDocs.size > 0
|
|
65
|
+
? [...relatedDocs].filter((/** @type {any} */ item) => collectBundleDocIds(bundle).has(item)).length
|
|
66
|
+
: 0;
|
|
67
|
+
const capabilityLinkOverlap = relatedCapabilities.size > 0
|
|
68
|
+
? [...relatedCapabilities].filter((/** @type {any} */ item) => collectBundleCapabilityIds(bundle).has(item)).length
|
|
69
|
+
: 0;
|
|
70
|
+
if (provenanceOverlap === 0 && docLinkOverlap === 0 && capabilityLinkOverlap === 0) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const score = docLinkOverlap * 1000 + capabilityLinkOverlap * 750 + provenanceOverlap * 100 + evidenceScore;
|
|
74
|
+
if (!best || score > best.score || (score === best.score && bundle.slug.localeCompare(best.bundle.slug) < 0)) {
|
|
75
|
+
best = { bundle, score };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best?.bundle || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @param {ResolvedGraph} graph @param {ImportArtifacts} appImport @param {any} topogramRoot @returns {any} */
|
|
82
|
+
export function buildCandidateModelBundles(graph, appImport, topogramRoot) {
|
|
83
|
+
const dbCandidates = appImport.candidates.db || { entities: [], enums: [] };
|
|
84
|
+
const apiCandidates = appImport.candidates.api || { capabilities: [] };
|
|
85
|
+
const uiCandidates = appImport.candidates.ui || { screens: [], routes: [], actions: [], widgets: [] };
|
|
86
|
+
const uiWidgetCandidates = uiCandidates.widgets || uiCandidates.components || [];
|
|
87
|
+
const workflowCandidates = appImport.candidates.workflows || { workflows: [], workflow_states: [], workflow_transitions: [] };
|
|
88
|
+
const verificationCandidates = appImport.candidates.verification || { verifications: [], scenarios: [], frameworks: [], scripts: [] };
|
|
89
|
+
const docCandidates = appImport.candidates.docs || [];
|
|
90
|
+
const actorCandidates = appImport.candidates.actors || [];
|
|
91
|
+
const roleCandidates = appImport.candidates.roles || [];
|
|
92
|
+
const knownEnums = new Set((dbCandidates.enums || []).map((/** @type {any} */ entry) => entry.id_hint));
|
|
93
|
+
const canonicalActorIds = new Set((graph?.byKind.actor || []).map((/** @type {any} */ entry) => entry.id));
|
|
94
|
+
const canonicalRoleIds = new Set((graph?.byKind.role || []).map((/** @type {any} */ entry) => entry.id));
|
|
95
|
+
const canonicalEntityIds = new Set((graph?.byKind.entity || []).map((/** @type {any} */ entry) => entry.id));
|
|
96
|
+
const canonicalEnumIds = new Set((graph?.byKind.enum || []).map((/** @type {any} */ entry) => entry.id));
|
|
97
|
+
const canonicalWidgetIds = new Set((graph?.byKind.widget || []).map((/** @type {any} */ entry) => entry.id));
|
|
98
|
+
const canonicalUi = collectCanonicalUiSurface(graph || { byKind: { projection: [] } });
|
|
99
|
+
const canonicalWorkflow = collectCanonicalWorkflowSurface(graph || { byKind: { decision: [] }, docs: [] });
|
|
100
|
+
const canonicalDocsByKind = new Map();
|
|
101
|
+
for (const doc of graph?.docs || []) {
|
|
102
|
+
if (!canonicalDocsByKind.has(doc.kind)) {
|
|
103
|
+
canonicalDocsByKind.set(doc.kind, new Set());
|
|
104
|
+
}
|
|
105
|
+
canonicalDocsByKind.get(doc.kind).add(doc.id);
|
|
106
|
+
}
|
|
107
|
+
const topogramApiCapabilities = graph ? buildTopogramApiCapabilityIndex(graph) : [];
|
|
108
|
+
const canonicalShapeIndex = buildCanonicalShapeIndex(graph);
|
|
109
|
+
const canonicalVerificationIds = new Set((graph?.byKind.verification || []).map((/** @type {any} */ entry) => entry.id));
|
|
110
|
+
const projectionIndex = buildProjectionEntityIndex(graph);
|
|
111
|
+
const bundles = new Map();
|
|
112
|
+
const enumCandidatesById = new Map((dbCandidates.enums || []).map((/** @type {any} */ entry) => [entry.id_hint, entry]));
|
|
113
|
+
const verificationScenariosByVerificationId = new Map();
|
|
114
|
+
for (const scenario of verificationCandidates.scenarios || []) {
|
|
115
|
+
const bucket = verificationScenariosByVerificationId.get(scenario.verification_id) || [];
|
|
116
|
+
bucket.push(scenario);
|
|
117
|
+
verificationScenariosByVerificationId.set(scenario.verification_id, bucket);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const entry of dbCandidates.enums || []) {
|
|
121
|
+
if (canonicalEnumIds.has(entry.id_hint)) continue;
|
|
122
|
+
getOrCreateCandidateBundle(bundles, `enum_${entry.id_hint}`, titleCase(entry.id_hint)).enums.push(entry);
|
|
123
|
+
}
|
|
124
|
+
for (const entry of dbCandidates.entities || []) {
|
|
125
|
+
const bundle = getOrCreateCandidateBundle(bundles, entry.id_hint, entry.label);
|
|
126
|
+
bundle.importedFieldEvidence = [
|
|
127
|
+
...(bundle.importedFieldEvidence || []),
|
|
128
|
+
...((entry.fields || []).map((/** @type {any} */ field) => ({
|
|
129
|
+
entity_id: entry.id_hint,
|
|
130
|
+
name: field.name,
|
|
131
|
+
field_type: field.field_type,
|
|
132
|
+
required: field.required
|
|
133
|
+
})))
|
|
134
|
+
];
|
|
135
|
+
if (!canonicalEntityIds.has(entry.id_hint)) {
|
|
136
|
+
bundle.entities.push(entry);
|
|
137
|
+
}
|
|
138
|
+
for (const field of entry.fields || []) {
|
|
139
|
+
const enumId = idHintify(field.field_type);
|
|
140
|
+
const enumEntry = enumCandidatesById.get(enumId);
|
|
141
|
+
if (!enumEntry || canonicalEnumIds.has(enumId)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (!bundle.enums.some((/** @type {any} */ candidate) => candidate.id_hint === enumId)) {
|
|
145
|
+
bundle.enums.push(enumEntry);
|
|
146
|
+
}
|
|
147
|
+
bundles.delete(`enum_${enumId}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const entry of apiCandidates.capabilities || []) {
|
|
151
|
+
const matchedCapability = graph ? matchImportedApiCapability(entry, topogramApiCapabilities) : null;
|
|
152
|
+
if (matchedCapability) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const conceptId = inferCapabilityEntityId(entry);
|
|
156
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, titleCase(conceptId.replace(/^entity_/, "")));
|
|
157
|
+
bundle.capabilities.push(entry);
|
|
158
|
+
const inputFields = entry.input_fields || [];
|
|
159
|
+
const outputFields = entry.output_fields || [];
|
|
160
|
+
if (inputFields.length > 0) {
|
|
161
|
+
bundle.shapes.push({
|
|
162
|
+
id: shapeIdForCapability(entry, "input"),
|
|
163
|
+
label: `${titleCase(entry.id_hint.replace(/^cap_/, ""))} Input`,
|
|
164
|
+
fields: inputFields
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (outputFields.length > 0) {
|
|
168
|
+
bundle.shapes.push({
|
|
169
|
+
id: shapeIdForCapability(entry, "output"),
|
|
170
|
+
label: `${titleCase(entry.id_hint.replace(/^cap_/, ""))} Output`,
|
|
171
|
+
fields: outputFields
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const entry of uiCandidates.screens || []) {
|
|
176
|
+
if (canonicalUi.screens.includes(entry.id_hint)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const conceptId = entry.entity_id || entry.concept_id || `entity_${canonicalCandidateTerm(entry.id_hint)}`;
|
|
180
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, bundleLabelFromConceptId(conceptId || entry.id_hint));
|
|
181
|
+
bundle.screens.push(entry);
|
|
182
|
+
}
|
|
183
|
+
for (const entry of uiCandidates.routes || []) {
|
|
184
|
+
if (canonicalUi.routes.includes(entry.path)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const conceptId = entry.entity_id || entry.concept_id || `entity_${canonicalCandidateTerm(entry.screen_id || entry.id_hint)}`;
|
|
188
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, bundleLabelFromConceptId(conceptId || entry.screen_id || entry.id_hint));
|
|
189
|
+
bundle.uiRoutes.push(entry);
|
|
190
|
+
}
|
|
191
|
+
for (const entry of uiCandidates.actions || []) {
|
|
192
|
+
const conceptId = entry.entity_id || entry.concept_id || `entity_${canonicalCandidateTerm(entry.screen_id || entry.id_hint)}`;
|
|
193
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, bundleLabelFromConceptId(conceptId || entry.screen_id || entry.id_hint));
|
|
194
|
+
bundle.uiActions.push(entry);
|
|
195
|
+
}
|
|
196
|
+
/** @param {WorkflowRecord} entry @returns {any} */
|
|
197
|
+
function widgetConceptId(entry) {
|
|
198
|
+
if (entry.entity_id || entry.concept_id) {
|
|
199
|
+
return entry.entity_id || entry.concept_id;
|
|
200
|
+
}
|
|
201
|
+
const screenStem = String(entry.screen_id || entry.id_hint || "")
|
|
202
|
+
.replace(/_(list|index|table|grid|results)$/, "")
|
|
203
|
+
.replace(/^list_/, "");
|
|
204
|
+
return `entity_${canonicalCandidateTerm(screenStem || entry.id_hint)}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const entry of uiWidgetCandidates) {
|
|
208
|
+
if (canonicalWidgetIds.has(entry.id_hint)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const conceptId = widgetConceptId(entry);
|
|
212
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, bundleLabelFromConceptId(conceptId || entry.screen_id || entry.id_hint));
|
|
213
|
+
bundle.widgets.push(entry);
|
|
214
|
+
}
|
|
215
|
+
for (const entry of workflowCandidates.workflows || []) {
|
|
216
|
+
if (canonicalWorkflow.workflow_docs.includes(entry.id_hint)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const bundle = getOrCreateCandidateBundle(bundles, entry.entity_id || `entity_${canonicalCandidateTerm(entry.id_hint)}`, titleCase((entry.entity_id || entry.id_hint).replace(/^entity_/, "")));
|
|
220
|
+
bundle.workflows.push(entry);
|
|
221
|
+
}
|
|
222
|
+
for (const entry of workflowCandidates.workflow_states || []) {
|
|
223
|
+
if (canonicalWorkflow.workflow_docs.includes(entry.workflow_id)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const bundle = getOrCreateCandidateBundle(bundles, entry.entity_id || `entity_${canonicalCandidateTerm(entry.workflow_id || entry.id_hint)}`, titleCase((entry.entity_id || entry.workflow_id || entry.id_hint).replace(/^entity_/, "")));
|
|
227
|
+
bundle.workflowStates.push(entry);
|
|
228
|
+
}
|
|
229
|
+
for (const entry of workflowCandidates.workflow_transitions || []) {
|
|
230
|
+
if (canonicalWorkflow.workflow_docs.includes(entry.workflow_id)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const bundle = getOrCreateCandidateBundle(bundles, entry.entity_id || `entity_${canonicalCandidateTerm(entry.workflow_id || entry.id_hint)}`, titleCase((entry.entity_id || entry.workflow_id || entry.id_hint).replace(/^entity_/, "")));
|
|
234
|
+
bundle.workflowTransitions.push(entry);
|
|
235
|
+
}
|
|
236
|
+
for (const entry of verificationCandidates.verifications || []) {
|
|
237
|
+
if (canonicalVerificationIds.has(entry.id_hint)) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const relatedCapabilityId =
|
|
241
|
+
entry.related_capabilities?.[0] ||
|
|
242
|
+
verificationScenariosByVerificationId.get(entry.id_hint)?.flatMap((/** @type {any} */ scenario) => scenario.related_capabilities || [])[0] ||
|
|
243
|
+
null;
|
|
244
|
+
const conceptId = relatedCapabilityId
|
|
245
|
+
? inferCapabilityEntityId({
|
|
246
|
+
id_hint: relatedCapabilityId,
|
|
247
|
+
endpoint: { path: `/${relatedCapabilityId.replace(/^cap_(create|get|update|delete|list|complete|close|approve|reject|request_revision|export|download)_/, "").replace(/_/g, "-")}` }
|
|
248
|
+
})
|
|
249
|
+
: `surface_${canonicalCandidateTerm(entry.id_hint)}`;
|
|
250
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, bundleLabelFromConceptId(conceptId || entry.id_hint));
|
|
251
|
+
bundle.verifications.push({
|
|
252
|
+
...entry,
|
|
253
|
+
scenarios: verificationScenariosByVerificationId.get(entry.id_hint) || []
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
for (const entry of docCandidates || []) {
|
|
257
|
+
const hasCanonicalDoc = (canonicalDocsByKind.get(entry.kind) || new Set()).has(entry.id);
|
|
258
|
+
const canonicalEntityHint = `entity_${canonicalCandidateTerm(entry.id)}`;
|
|
259
|
+
const semanticallyAnchored =
|
|
260
|
+
(entry.related_entities || []).length > 0 ||
|
|
261
|
+
(entry.related_capabilities || []).length > 0 ||
|
|
262
|
+
canonicalEntityIds.has(canonicalEntityHint);
|
|
263
|
+
if (!semanticallyAnchored && entry.kind !== "workflow") {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const conceptId = semanticallyAnchored
|
|
267
|
+
? (
|
|
268
|
+
entry.related_entities?.[0] ||
|
|
269
|
+
(entry.related_capabilities?.[0] ? inferCapabilityEntityId({ id_hint: entry.related_capabilities[0], endpoint: { path: `/${entry.related_capabilities[0].replace(/^cap_(create|get|update|delete|list)_/, "").replace(/_/g, "-")}` } }) : `entity_${canonicalCandidateTerm(entry.id)}`)
|
|
270
|
+
)
|
|
271
|
+
: `flow_${canonicalCandidateTerm(entry.id)}`;
|
|
272
|
+
const bundle = getOrCreateCandidateBundle(bundles, conceptId, titleCase(conceptId.replace(/^(entity|flow)_/, "")));
|
|
273
|
+
bundle.docs.push({
|
|
274
|
+
...entry,
|
|
275
|
+
existing_canonical: hasCanonicalDoc
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
for (const entry of actorCandidates || []) {
|
|
279
|
+
if (canonicalActorIds.has(entry.id_hint)) continue;
|
|
280
|
+
const contextualBundle = bestContextBundleForCandidate(bundles, entry);
|
|
281
|
+
(contextualBundle || getOrCreateCandidateBundle(bundles, entry.id_hint, entry.label)).actors.push(entry);
|
|
282
|
+
}
|
|
283
|
+
for (const entry of roleCandidates || []) {
|
|
284
|
+
if (canonicalRoleIds.has(entry.id_hint)) continue;
|
|
285
|
+
const contextualBundle = bestContextBundleForCandidate(bundles, entry);
|
|
286
|
+
(contextualBundle || getOrCreateCandidateBundle(bundles, entry.id_hint, entry.label)).roles.push(entry);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
addBundleJourneyDrafts(bundles, graph);
|
|
290
|
+
|
|
291
|
+
/** @type {any[]} */
|
|
292
|
+
|
|
293
|
+
const suppressedNoiseBundles = [];
|
|
294
|
+
const finalizedBundles = [...bundles.values()]
|
|
295
|
+
.filter((/** @type {any} */ bundle) =>
|
|
296
|
+
bundle.actors.length > 0 ||
|
|
297
|
+
bundle.roles.length > 0 ||
|
|
298
|
+
bundle.entities.length > 0 ||
|
|
299
|
+
bundle.enums.length > 0 ||
|
|
300
|
+
bundle.capabilities.length > 0 ||
|
|
301
|
+
bundle.shapes.length > 0 ||
|
|
302
|
+
bundle.widgets.length > 0 ||
|
|
303
|
+
bundle.screens.length > 0 ||
|
|
304
|
+
bundle.uiRoutes.length > 0 ||
|
|
305
|
+
bundle.uiActions.length > 0 ||
|
|
306
|
+
bundle.workflows.length > 0 ||
|
|
307
|
+
bundle.verifications.length > 0 ||
|
|
308
|
+
bundle.workflowStates.length > 0 ||
|
|
309
|
+
bundle.workflowTransitions.length > 0
|
|
310
|
+
)
|
|
311
|
+
.map((/** @type {any} */ bundle) => {
|
|
312
|
+
const sortedBundle = {
|
|
313
|
+
...bundle,
|
|
314
|
+
actors: bundle.actors.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
315
|
+
roles: bundle.roles.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
316
|
+
entities: bundle.entities.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
317
|
+
enums: bundle.enums.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
318
|
+
capabilities: bundle.capabilities.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
319
|
+
shapes: bundle.shapes.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id.localeCompare(b.id)),
|
|
320
|
+
widgets: bundle.widgets.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
321
|
+
screens: bundle.screens.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
322
|
+
uiRoutes: bundle.uiRoutes.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
323
|
+
uiActions: bundle.uiActions.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
324
|
+
workflows: bundle.workflows.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
325
|
+
verifications: bundle.verifications.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
326
|
+
workflowStates: bundle.workflowStates.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
327
|
+
workflowTransitions: bundle.workflowTransitions.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id_hint.localeCompare(b.id_hint)),
|
|
328
|
+
docs: bundle.docs.sort((/** @type {any} */ a, /** @type {any} */ b) => a.id.localeCompare(b.id))
|
|
329
|
+
};
|
|
330
|
+
const mergeHints = buildBundleMergeHints(sortedBundle, canonicalEntityIds);
|
|
331
|
+
const projectionImpacts = buildProjectionImpacts({ ...sortedBundle, mergeHints }, projectionIndex);
|
|
332
|
+
const uiImpacts = buildUiImpacts(sortedBundle, graph);
|
|
333
|
+
const workflowImpacts = buildWorkflowImpacts(sortedBundle, graph);
|
|
334
|
+
const authPermissionHints = inferBundleAuthPermissionHints(sortedBundle);
|
|
335
|
+
const authClaimHints = inferBundleAuthClaimHints(sortedBundle);
|
|
336
|
+
const authOwnershipHints = inferBundleAuthOwnershipHints(sortedBundle);
|
|
337
|
+
const authRoleGuidance = inferBundleAuthRoleGuidance({
|
|
338
|
+
...sortedBundle,
|
|
339
|
+
authPermissionHints,
|
|
340
|
+
authClaimHints,
|
|
341
|
+
authOwnershipHints
|
|
342
|
+
});
|
|
343
|
+
const docLinkSuggestions = buildBundleDocLinkSuggestions(sortedBundle, graph);
|
|
344
|
+
const enrichedBundle = {
|
|
345
|
+
...sortedBundle,
|
|
346
|
+
mergeHints,
|
|
347
|
+
projectionImpacts,
|
|
348
|
+
uiImpacts,
|
|
349
|
+
workflowImpacts,
|
|
350
|
+
authPermissionHints,
|
|
351
|
+
authClaimHints,
|
|
352
|
+
authOwnershipHints,
|
|
353
|
+
authRoleGuidance,
|
|
354
|
+
docLinkSuggestions,
|
|
355
|
+
docDriftSummaries: buildBundleDocDriftSummariesReconcile(sortedBundle, graph, topogramRoot, confidenceRank, readTextIfExists),
|
|
356
|
+
docMetadataPatches: []
|
|
357
|
+
};
|
|
358
|
+
const projectionPatches = buildProjectionPatchCandidates(enrichedBundle);
|
|
359
|
+
const adoptionPlan = buildBundleAdoptionPlan({ ...enrichedBundle, projectionPatches }, canonicalShapeIndex);
|
|
360
|
+
const classifiedAuthRoleGuidance = classifyBundleAuthRoleGuidance({ ...enrichedBundle, projectionPatches, adoptionPlan });
|
|
361
|
+
return {
|
|
362
|
+
...enrichedBundle,
|
|
363
|
+
authRoleGuidance: classifiedAuthRoleGuidance,
|
|
364
|
+
docLinkSuggestions: annotateDocLinkSuggestionsWithAuthRoleGuidance(docLinkSuggestions, classifiedAuthRoleGuidance),
|
|
365
|
+
projectionPatches,
|
|
366
|
+
adoptionPlan
|
|
367
|
+
};
|
|
368
|
+
})
|
|
369
|
+
.map((/** @type {any} */ bundle) => ({
|
|
370
|
+
...bundle,
|
|
371
|
+
docMetadataPatches: buildBundleDocMetadataPatchesReconcile(bundle, confidenceRank)
|
|
372
|
+
}))
|
|
373
|
+
.filter((/** @type {any} */ bundle) => {
|
|
374
|
+
const reason = bundleNoiseSuppressionReason(bundle);
|
|
375
|
+
if (!reason) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
suppressedNoiseBundles.push({
|
|
379
|
+
slug: bundle.slug,
|
|
380
|
+
id: bundle.id,
|
|
381
|
+
reason
|
|
382
|
+
});
|
|
383
|
+
return false;
|
|
384
|
+
})
|
|
385
|
+
.sort((/** @type {any} */ a, /** @type {any} */ b) => a.slug.localeCompare(b.slug));
|
|
386
|
+
return { bundles: finalizedBundles, suppressedNoiseBundles };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** @param {ResolvedGraph} graph @param {ImportArtifacts} appImport @param {any} topogramRoot @returns {any} */
|
|
390
|
+
export function buildCandidateModelFiles(graph, appImport, topogramRoot) {
|
|
391
|
+
/** @type {WorkflowFiles} */
|
|
392
|
+
/** @type {WorkflowFiles} */
|
|
393
|
+
const files = {};
|
|
394
|
+
const { bundles, suppressedNoiseBundles } = buildCandidateModelBundles(graph, appImport, topogramRoot);
|
|
395
|
+
const knownEnums = new Set(
|
|
396
|
+
bundles.flatMap((/** @type {any} */ bundle) => bundle.enums.map((/** @type {any} */ entry) => entry.id_hint))
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
for (const bundle of bundles) {
|
|
400
|
+
const bundleRoot = `candidates/reconcile/model/bundles/${bundle.slug}`;
|
|
401
|
+
files[`${bundleRoot}/README.md`] = renderCandidateBundleReadme(bundle);
|
|
402
|
+
for (const entry of bundle.actors) {
|
|
403
|
+
files[`${bundleRoot}/actors/${entry.id_hint}.tg`] = renderCandidateActor(entry);
|
|
404
|
+
}
|
|
405
|
+
for (const entry of bundle.roles) {
|
|
406
|
+
files[`${bundleRoot}/roles/${entry.id_hint}.tg`] = renderCandidateRole(entry);
|
|
407
|
+
}
|
|
408
|
+
for (const entry of bundle.enums) {
|
|
409
|
+
files[`${bundleRoot}/enums/${entry.id_hint}.tg`] = renderCandidateEnum(entry);
|
|
410
|
+
}
|
|
411
|
+
for (const entry of bundle.entities) {
|
|
412
|
+
files[`${bundleRoot}/entities/${entry.id_hint}.tg`] = renderCandidateEntity(entry, knownEnums);
|
|
413
|
+
}
|
|
414
|
+
for (const shape of bundle.shapes) {
|
|
415
|
+
files[`${bundleRoot}/shapes/${shape.id}.tg`] = renderCandidateShape(shape.id, shape.label, shape.fields);
|
|
416
|
+
}
|
|
417
|
+
for (const entry of bundle.capabilities) {
|
|
418
|
+
const inputShapeId = bundle.shapes.find((/** @type {any} */ shape) => shape.id === shapeIdForCapability(entry, "input")) ? shapeIdForCapability(entry, "input") : null;
|
|
419
|
+
const outputShapeId = bundle.shapes.find((/** @type {any} */ shape) => shape.id === shapeIdForCapability(entry, "output")) ? shapeIdForCapability(entry, "output") : null;
|
|
420
|
+
files[`${bundleRoot}/capabilities/${entry.id_hint}.tg`] = renderCandidateCapability(entry, inputShapeId, outputShapeId);
|
|
421
|
+
}
|
|
422
|
+
for (const entry of bundle.verifications || []) {
|
|
423
|
+
files[`${bundleRoot}/verifications/${entry.id_hint}.tg`] = renderCandidateVerification(entry, entry.scenarios || []);
|
|
424
|
+
}
|
|
425
|
+
for (const entry of bundle.widgets || []) {
|
|
426
|
+
files[`${bundleRoot}/widgets/${entry.id_hint}.tg`] = renderCandidateWidget(entry);
|
|
427
|
+
}
|
|
428
|
+
for (const entry of bundle.docs) {
|
|
429
|
+
if (entry.existing_canonical) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const docDir = docDirForKind(entry.kind);
|
|
433
|
+
files[`${bundleRoot}/docs/${docDir}/${entry.id}.md`] = renderMarkdownDoc(
|
|
434
|
+
entry.metadata || {
|
|
435
|
+
id: entry.id,
|
|
436
|
+
kind: entry.kind,
|
|
437
|
+
title: entry.title,
|
|
438
|
+
status: "inferred",
|
|
439
|
+
source_of_truth: entry.source_of_truth || "imported",
|
|
440
|
+
confidence: entry.confidence || "low",
|
|
441
|
+
review_required: true,
|
|
442
|
+
related_entities: entry.related_entities || [],
|
|
443
|
+
related_capabilities: entry.related_capabilities || [],
|
|
444
|
+
related_actors: entry.related_actors || [],
|
|
445
|
+
related_roles: entry.related_roles || [],
|
|
446
|
+
related_rules: entry.related_rules || [],
|
|
447
|
+
related_workflows: entry.related_workflows || [],
|
|
448
|
+
provenance: entry.provenance || [],
|
|
449
|
+
tags: entry.tags || ["import", entry.kind]
|
|
450
|
+
},
|
|
451
|
+
entry.body || "Candidate imported doc."
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
for (const entry of bundle.workflows) {
|
|
455
|
+
const states = bundle.workflowStates.filter((/** @type {any} */ state) => state.workflow_id === entry.id_hint);
|
|
456
|
+
const transitions = bundle.workflowTransitions.filter((/** @type {any} */ transition) => transition.workflow_id === entry.id_hint);
|
|
457
|
+
files[`${bundleRoot}/docs/workflows/${entry.id_hint}.md`] = renderCandidateWorkflowDoc(entry, states, transitions);
|
|
458
|
+
files[`${bundleRoot}/decisions/dec_${entry.id_hint.replace(/^workflow_/, "")}.tg`] = renderCandidateWorkflowDecision(entry, states, transitions);
|
|
459
|
+
}
|
|
460
|
+
for (const screen of bundle.screens) {
|
|
461
|
+
const routes = bundle.uiRoutes.filter((/** @type {any} */ route) => route.screen_id === screen.id_hint);
|
|
462
|
+
const actions = bundle.uiActions.filter((/** @type {any} */ action) => action.screen_id === screen.id_hint);
|
|
463
|
+
files[`${bundleRoot}/docs/reports/ui-${screen.id_hint}.md`] = renderCandidateUiReportDoc(screen, routes, actions);
|
|
464
|
+
}
|
|
465
|
+
for (const patch of bundle.projectionPatches || []) {
|
|
466
|
+
files[`${bundleRoot}/${patch.patch_rel_path}`] = renderProjectionPatchDoc(patch);
|
|
467
|
+
}
|
|
468
|
+
for (const patch of bundle.docLinkSuggestions || []) {
|
|
469
|
+
files[`${bundleRoot}/${patch.patch_rel_path}`] = renderDocLinkPatchDoc(patch);
|
|
470
|
+
}
|
|
471
|
+
for (const patch of bundle.docMetadataPatches || []) {
|
|
472
|
+
files[`${bundleRoot}/${patch.patch_rel_path}`] = renderDocMetadataPatchDoc(patch);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return { files, bundles, suppressedNoiseBundles };
|
|
477
|
+
}
|