@topogram/cli 0.3.63 → 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,461 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { normalizeEndpointPathForMatch, SCALAR_FIELD_TYPES } from "../import-app/index.js";
|
|
3
|
+
import { canonicalCandidateTerm, ensureTrailingNewline, idHintify, titleCase } from "../../text-helpers.js";
|
|
4
|
+
import { renderMarkdownDoc } from "../shared.js";
|
|
5
|
+
import {
|
|
6
|
+
buildAuthClaimReviewGuidance,
|
|
7
|
+
buildAuthOwnershipReviewGuidance,
|
|
8
|
+
buildAuthPermissionReviewGuidance,
|
|
9
|
+
formatAuthClaimHintInline,
|
|
10
|
+
formatAuthOwnershipHintInline,
|
|
11
|
+
formatAuthPermissionHintInline
|
|
12
|
+
} from "./auth.js";
|
|
13
|
+
|
|
14
|
+
/** @param {string} fieldType @param {Set<any>} knownEnums @returns {any} */
|
|
15
|
+
export function normalizeCandidateFieldType(fieldType, knownEnums = new Set()) {
|
|
16
|
+
const normalized = idHintify(fieldType);
|
|
17
|
+
if (SCALAR_FIELD_TYPES.has(normalized)) {
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
if (knownEnums.has(normalized)) {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
return normalized || "string";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {WorkflowRecord} record @returns {any} */
|
|
27
|
+
export function renderCandidateMetadataComments(record) {
|
|
28
|
+
const lines = [
|
|
29
|
+
`# imported confidence: ${record.confidence || "unknown"}`,
|
|
30
|
+
`# imported source_kind: ${record.source_kind || "unknown"}`
|
|
31
|
+
];
|
|
32
|
+
if (record.inference_summary) {
|
|
33
|
+
lines.push(`# imported inference: ${record.inference_summary}`);
|
|
34
|
+
}
|
|
35
|
+
for (const provenance of (record.provenance || []).slice(0, 3)) {
|
|
36
|
+
lines.push(`# imported provenance: ${provenance}`);
|
|
37
|
+
}
|
|
38
|
+
if ((record.provenance || []).length > 3) {
|
|
39
|
+
lines.push(`# imported provenance_more: ${(record.provenance || []).length - 3}`);
|
|
40
|
+
}
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param {WorkflowRecord} record @param {Set<any>} knownEnums @returns {any} */
|
|
45
|
+
export function renderCandidateEntity(record, knownEnums = new Set()) {
|
|
46
|
+
const fields = record.fields || [];
|
|
47
|
+
const primaryKeys = fields.filter((/** @type {any} */ field) => field.primary_key).map((/** @type {any} */ field) => field.name);
|
|
48
|
+
const uniqueKeys = fields.filter((/** @type {any} */ field) => field.unique && !field.primary_key).map((/** @type {any} */ field) => field.name);
|
|
49
|
+
const fieldLines = fields.map((/** @type {any} */ field) => {
|
|
50
|
+
const fieldType = normalizeCandidateFieldType(field.field_type, knownEnums);
|
|
51
|
+
const requiredness = field.required ? "required" : "optional";
|
|
52
|
+
return ` ${field.name} ${fieldType} ${requiredness}`;
|
|
53
|
+
});
|
|
54
|
+
const lines = [
|
|
55
|
+
`entity ${record.id_hint} {`,
|
|
56
|
+
` name "${record.label}"`,
|
|
57
|
+
` description "Candidate entity imported from brownfield schema evidence"`,
|
|
58
|
+
"",
|
|
59
|
+
" fields {",
|
|
60
|
+
...fieldLines,
|
|
61
|
+
" }"
|
|
62
|
+
];
|
|
63
|
+
if (primaryKeys.length > 0 || uniqueKeys.length > 0) {
|
|
64
|
+
lines.push("", " keys {");
|
|
65
|
+
if (primaryKeys.length > 0) {
|
|
66
|
+
lines.push(` primary [${primaryKeys.join(", ")}]`);
|
|
67
|
+
}
|
|
68
|
+
if (uniqueKeys.length > 0) {
|
|
69
|
+
lines.push(` unique [${uniqueKeys.join(", ")}]`);
|
|
70
|
+
}
|
|
71
|
+
lines.push(" }");
|
|
72
|
+
}
|
|
73
|
+
lines.push("", " status active", "}");
|
|
74
|
+
return ensureTrailingNewline(`${renderCandidateMetadataComments(record)}\n${lines.join("\n")}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {WorkflowRecord} record @returns {any} */
|
|
78
|
+
export function renderCandidateEnum(record) {
|
|
79
|
+
return ensureTrailingNewline(
|
|
80
|
+
`${renderCandidateMetadataComments(record)}\n${[
|
|
81
|
+
`enum ${record.id_hint} {`,
|
|
82
|
+
` values [${(record.values || []).join(", ")}]`,
|
|
83
|
+
"}"
|
|
84
|
+
].join("\n")}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {WorkflowRecord} record @returns {any} */
|
|
89
|
+
export function inferCapabilityVerb(record) {
|
|
90
|
+
const id = record.id_hint || "";
|
|
91
|
+
if (id.startsWith("cap_create_")) return "creates";
|
|
92
|
+
if (id.startsWith("cap_update_") || (record.endpoint?.method || "").toUpperCase() === "PATCH") return "updates";
|
|
93
|
+
if (id.startsWith("cap_delete_") || (record.endpoint?.method || "").toUpperCase() === "DELETE") return "deletes";
|
|
94
|
+
return "reads";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @param {WorkflowRecord} record @returns {any} */
|
|
98
|
+
export function inferCapabilityEntityId(record) {
|
|
99
|
+
if (record.entity_id) {
|
|
100
|
+
return record.entity_id;
|
|
101
|
+
}
|
|
102
|
+
const pathSegments = normalizeEndpointPathForMatch(record.endpoint?.path || "")
|
|
103
|
+
.split("/")
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.filter((/** @type {any} */ segment) => segment !== "{}");
|
|
106
|
+
const resourceSegment = pathSegments[0] || record.id_hint.replace(/^cap_(create|update|delete|get|list)_/, "");
|
|
107
|
+
return `entity_${idHintify(canonicalCandidateTerm(resourceSegment))}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @param {WorkflowRecord} record @param {string} direction @returns {any} */
|
|
111
|
+
export function shapeIdForCapability(record, direction) {
|
|
112
|
+
const stem = record.id_hint.replace(/^cap_/, "");
|
|
113
|
+
return direction === "input" ? `shape_input_${stem}` : `shape_output_${stem}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** @param {string} shapeId @param {string} label @param {any[]} fields @returns {any} */
|
|
117
|
+
export function renderCandidateShape(shapeId, label, fields) {
|
|
118
|
+
const lines = [
|
|
119
|
+
`shape ${shapeId} {`,
|
|
120
|
+
` name "${label}"`,
|
|
121
|
+
` description "Candidate shape imported from brownfield API evidence"`,
|
|
122
|
+
"",
|
|
123
|
+
" fields {"
|
|
124
|
+
];
|
|
125
|
+
for (const field of fields) {
|
|
126
|
+
lines.push(` ${field} string optional`);
|
|
127
|
+
}
|
|
128
|
+
lines.push(" }", "", " status active", "}");
|
|
129
|
+
return ensureTrailingNewline(lines.join("\n"));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @param {WorkflowRecord} record @param {any} inputShapeId @param {any} outputShapeId @returns {any} */
|
|
133
|
+
export function renderCandidateCapability(record, inputShapeId, outputShapeId) {
|
|
134
|
+
const operationKind = inferCapabilityVerb(record);
|
|
135
|
+
const entityId = inferCapabilityEntityId(record);
|
|
136
|
+
const lines = [
|
|
137
|
+
`capability ${record.id_hint} {`,
|
|
138
|
+
` name "${record.label}"`,
|
|
139
|
+
` description "Candidate capability imported from brownfield API evidence"`,
|
|
140
|
+
""
|
|
141
|
+
];
|
|
142
|
+
if (record.auth_hint === "secured") {
|
|
143
|
+
lines.push(" actors [user]", "");
|
|
144
|
+
}
|
|
145
|
+
lines.push(` ${operationKind} [${entityId}]`);
|
|
146
|
+
if (inputShapeId) {
|
|
147
|
+
lines.push("", ` input [${inputShapeId}]`);
|
|
148
|
+
}
|
|
149
|
+
if (outputShapeId) {
|
|
150
|
+
lines.push(` output [${outputShapeId}]`);
|
|
151
|
+
}
|
|
152
|
+
lines.push("", " status active", "}");
|
|
153
|
+
return ensureTrailingNewline(`${renderCandidateMetadataComments(record)}\n${lines.join("\n")}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @param {WorkflowRecord} record @param {any[]} scenarios @returns {any} */
|
|
157
|
+
export function renderCandidateVerification(record, scenarios = []) {
|
|
158
|
+
const validates = [...new Set(record.related_capabilities || [])];
|
|
159
|
+
const scenarioSymbols = scenarios.length > 0
|
|
160
|
+
? scenarios.map((/** @type {any} */ entry) => entry.id_hint)
|
|
161
|
+
: (record.scenario_ids || []).map((/** @type {any} */ entry) => idHintify(entry));
|
|
162
|
+
const lines = [
|
|
163
|
+
`verification ${record.id_hint} {`,
|
|
164
|
+
` name "${record.label}"`,
|
|
165
|
+
` description "Candidate verification imported from brownfield test evidence"`,
|
|
166
|
+
"",
|
|
167
|
+
` validates [${validates.join(", ")}]`,
|
|
168
|
+
` method ${record.method || "runtime"}`,
|
|
169
|
+
"",
|
|
170
|
+
` scenarios [${scenarioSymbols.join(", ")}]`,
|
|
171
|
+
"",
|
|
172
|
+
" status active",
|
|
173
|
+
"}"
|
|
174
|
+
];
|
|
175
|
+
return ensureTrailingNewline(`${renderCandidateMetadataComments(record)}\n${lines.join("\n")}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** @param {WorkflowRecord} record @param {any[]} states @param {any[]} transitions @returns {any} */
|
|
179
|
+
export function renderCandidateWorkflowDecision(record, states, transitions) {
|
|
180
|
+
const context = [
|
|
181
|
+
...states.map((/** @type {any} */ state) => state.state_id),
|
|
182
|
+
...transitions.map((/** @type {any} */ transition) => transition.capability_id).filter(Boolean)
|
|
183
|
+
].filter(Boolean);
|
|
184
|
+
const consequences = transitions.map((/** @type {any} */ transition) => transition.to_state).filter(Boolean);
|
|
185
|
+
return ensureTrailingNewline(
|
|
186
|
+
`${renderCandidateMetadataComments(record)}\n${[
|
|
187
|
+
`decision dec_${record.id_hint.replace(/^workflow_/, "")} {`,
|
|
188
|
+
` name "${record.label}"`,
|
|
189
|
+
` description "Candidate workflow decision imported from brownfield evidence"`,
|
|
190
|
+
"",
|
|
191
|
+
` context [${[...new Set(context)].join(", ")}]`,
|
|
192
|
+
` consequences [${[...new Set(consequences)].join(", ")}]`,
|
|
193
|
+
"",
|
|
194
|
+
" status proposed",
|
|
195
|
+
"}"
|
|
196
|
+
].join("\n")}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @param {WorkflowRecord} record @param {any[]} states @param {any[]} transitions @returns {any} */
|
|
201
|
+
export function renderCandidateWorkflowDoc(record, states, transitions) {
|
|
202
|
+
/** @type {WorkflowRecord} */
|
|
203
|
+
const metadata = {
|
|
204
|
+
id: record.id_hint,
|
|
205
|
+
kind: "workflow",
|
|
206
|
+
title: record.label,
|
|
207
|
+
status: "inferred",
|
|
208
|
+
source_of_truth: "imported",
|
|
209
|
+
confidence: record.confidence || "medium",
|
|
210
|
+
review_required: true,
|
|
211
|
+
related_entities: [record.entity_id].filter(Boolean),
|
|
212
|
+
related_capabilities: record.related_capabilities || [],
|
|
213
|
+
provenance: record.provenance || [],
|
|
214
|
+
tags: ["import", "workflow"]
|
|
215
|
+
};
|
|
216
|
+
const body = [
|
|
217
|
+
"Candidate workflow imported from brownfield evidence.",
|
|
218
|
+
"",
|
|
219
|
+
`Entity: \`${record.entity_id}\``,
|
|
220
|
+
`States: ${states.length ? states.map((/** @type {any} */ state) => `\`${state.state_id}\``).join(", ") : "_none_"}`,
|
|
221
|
+
`Transitions: ${transitions.length ? transitions.map((/** @type {any} */ transition) => `\`${transition.capability_id || transition.id_hint}\` -> \`${transition.to_state}\``).join(", ") : "_none_"}`,
|
|
222
|
+
"",
|
|
223
|
+
"Review this workflow before promoting it as canonical."
|
|
224
|
+
].join("\n");
|
|
225
|
+
return renderMarkdownDoc(metadata, body);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** @param {WorkflowRecord} screen @param {any[]} routes @param {any[]} actions @returns {any} */
|
|
229
|
+
export function renderCandidateUiReportDoc(screen, routes, actions) {
|
|
230
|
+
/** @type {WorkflowRecord} */
|
|
231
|
+
const metadata = {
|
|
232
|
+
id: `ui_${screen.id_hint}`,
|
|
233
|
+
kind: "report",
|
|
234
|
+
title: `${screen.label} UI Surface`,
|
|
235
|
+
status: "inferred",
|
|
236
|
+
source_of_truth: "imported",
|
|
237
|
+
confidence: screen.confidence || "medium",
|
|
238
|
+
review_required: true,
|
|
239
|
+
related_entities: [screen.entity_id].filter(Boolean),
|
|
240
|
+
provenance: screen.provenance || [],
|
|
241
|
+
tags: ["import", "ui"]
|
|
242
|
+
};
|
|
243
|
+
const body = [
|
|
244
|
+
"Candidate UI surface imported from brownfield route evidence.",
|
|
245
|
+
"",
|
|
246
|
+
`Screen: \`${screen.id_hint}\` (${screen.screen_kind})`,
|
|
247
|
+
`Routes: ${routes.length ? routes.map((/** @type {any} */ route) => `\`${route.path}\``).join(", ") : "_none_"}`,
|
|
248
|
+
`Actions: ${actions.length ? actions.map((/** @type {any} */ action) => `\`${action.capability_hint}\``).join(", ") : "_none_"}`,
|
|
249
|
+
"",
|
|
250
|
+
"Review this UI surface before promoting it into canonical docs or projections."
|
|
251
|
+
].join("\n");
|
|
252
|
+
return renderMarkdownDoc(metadata, body);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** @param {WorkflowRecord} widget @returns {any} */
|
|
256
|
+
export function renderCandidateWidget(widget) {
|
|
257
|
+
const propName = widget.data_prop || "rows";
|
|
258
|
+
const pattern = widget.pattern || "search_results";
|
|
259
|
+
const region = widget.region || "results";
|
|
260
|
+
return ensureTrailingNewline(
|
|
261
|
+
[
|
|
262
|
+
`widget ${widget.id_hint} {`,
|
|
263
|
+
` name "${widget.label || widget.id_hint}"`,
|
|
264
|
+
' description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."',
|
|
265
|
+
" category collection",
|
|
266
|
+
" props {",
|
|
267
|
+
` ${propName} array required`,
|
|
268
|
+
" }",
|
|
269
|
+
` patterns [${pattern}]`,
|
|
270
|
+
` regions [${region}]`,
|
|
271
|
+
" status proposed",
|
|
272
|
+
"}"
|
|
273
|
+
].join("\n")
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** @param {WorkflowRecord} patch @returns {any} */
|
|
278
|
+
export function renderProjectionPatchDoc(patch) {
|
|
279
|
+
const lines = [
|
|
280
|
+
`# ${patch.projection_id} Patch Candidate`,
|
|
281
|
+
"",
|
|
282
|
+
`Projection: \`${patch.projection_id}\``,
|
|
283
|
+
`Kind: \`${patch.kind}\``,
|
|
284
|
+
patch.projection_type ? `Projection type: \`${patch.projection_type}\`` : null,
|
|
285
|
+
"",
|
|
286
|
+
patch.reason || "Candidate additive projection patch inferred during reconcile.",
|
|
287
|
+
""
|
|
288
|
+
].filter(Boolean);
|
|
289
|
+
|
|
290
|
+
if ((patch.missing_realizes || []).length > 0) {
|
|
291
|
+
lines.push("## Missing Realizes", "");
|
|
292
|
+
for (const item of patch.missing_realizes) {
|
|
293
|
+
lines.push(`- \`${item}\``);
|
|
294
|
+
}
|
|
295
|
+
lines.push("");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if ((patch.missing_http || []).length > 0) {
|
|
299
|
+
lines.push("## Missing HTTP Entries", "");
|
|
300
|
+
for (const entry of patch.missing_http) {
|
|
301
|
+
lines.push(`- \`${entry.capability_id}\` ${entry.method} \`${entry.path}\``);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if ((patch.missing_screens || []).length > 0) {
|
|
307
|
+
lines.push("## Missing UI Screens", "");
|
|
308
|
+
for (const item of patch.missing_screens) {
|
|
309
|
+
lines.push(`- \`${item}\``);
|
|
310
|
+
}
|
|
311
|
+
lines.push("");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if ((patch.missing_routes || []).length > 0) {
|
|
315
|
+
lines.push("## Missing UI Routes", "");
|
|
316
|
+
for (const entry of patch.missing_routes) {
|
|
317
|
+
lines.push(`- \`${entry.screen_id}\` -> \`${entry.path}\``);
|
|
318
|
+
}
|
|
319
|
+
lines.push("");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if ((patch.missing_actions || []).length > 0) {
|
|
323
|
+
lines.push("## Missing UI Actions", "");
|
|
324
|
+
for (const entry of patch.missing_actions) {
|
|
325
|
+
lines.push(`- \`${entry.capability_hint}\` on \`${entry.screen_id}\``);
|
|
326
|
+
}
|
|
327
|
+
lines.push("");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if ((patch.missing_auth_permissions || []).length > 0) {
|
|
331
|
+
lines.push("## Inferred Permission Rules", "");
|
|
332
|
+
for (const entry of patch.missing_auth_permissions) {
|
|
333
|
+
lines.push(`- ${formatAuthPermissionHintInline(entry)} on ${entry.projection_surface === "visibility_rules" ? "`visibility_rules`" : "`authorization`"} for ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}`);
|
|
334
|
+
lines.push(` - why inferred: ${entry.why_inferred || entry.explanation}`);
|
|
335
|
+
lines.push(` - review next: ${entry.review_guidance || buildAuthPermissionReviewGuidance(entry)}`);
|
|
336
|
+
}
|
|
337
|
+
lines.push("");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if ((patch.missing_auth_claims || []).length > 0) {
|
|
341
|
+
lines.push("## Inferred Auth Claim Rules", "");
|
|
342
|
+
for (const entry of patch.missing_auth_claims) {
|
|
343
|
+
lines.push(`- ${formatAuthClaimHintInline(entry)} on ${entry.projection_surface === "visibility_rules" ? "`visibility_rules`" : "`authorization`"} for ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}`);
|
|
344
|
+
lines.push(` - why inferred: ${entry.why_inferred || entry.explanation}`);
|
|
345
|
+
lines.push(` - review next: ${entry.review_guidance || buildAuthClaimReviewGuidance(entry)}`);
|
|
346
|
+
}
|
|
347
|
+
lines.push("");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if ((patch.missing_auth_ownerships || []).length > 0) {
|
|
351
|
+
lines.push("## Inferred Ownership Rules", "");
|
|
352
|
+
for (const entry of patch.missing_auth_ownerships) {
|
|
353
|
+
lines.push(`- ${formatAuthOwnershipHintInline(entry)} on \`authorization\` for ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}`);
|
|
354
|
+
lines.push(` - why inferred: ${entry.why_inferred || entry.explanation}`);
|
|
355
|
+
lines.push(` - review next: ${entry.review_guidance || buildAuthOwnershipReviewGuidance(entry)}`);
|
|
356
|
+
}
|
|
357
|
+
lines.push("");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
lines.push("## Review Notes", "", "- Review this patch before editing canonical projection files.", "- This artifact is additive guidance only and is not auto-applied.");
|
|
361
|
+
return ensureTrailingNewline(lines.join("\n"));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @param {WorkflowRecord} patch @returns {any} */
|
|
365
|
+
export function renderDocLinkPatchDoc(patch) {
|
|
366
|
+
/** @type {WorkflowRecord} */
|
|
367
|
+
const metadata = {
|
|
368
|
+
id: `doc-link-${patch.doc_id}`,
|
|
369
|
+
kind: "report",
|
|
370
|
+
title: `Doc Link Update for ${titleCase(String(patch.doc_id || "").replaceAll("_", " "))}`,
|
|
371
|
+
status: "inferred",
|
|
372
|
+
source_of_truth: "imported",
|
|
373
|
+
confidence: "medium",
|
|
374
|
+
review_required: true,
|
|
375
|
+
related_docs: [patch.doc_id],
|
|
376
|
+
related_actors: patch.add_related_actors || [],
|
|
377
|
+
related_roles: patch.add_related_roles || [],
|
|
378
|
+
related_capabilities: patch.add_related_capabilities || [],
|
|
379
|
+
related_rules: patch.add_related_rules || [],
|
|
380
|
+
related_workflows: patch.add_related_workflows || [],
|
|
381
|
+
tags: ["import", "doc-link-update"]
|
|
382
|
+
};
|
|
383
|
+
const lines = [
|
|
384
|
+
`Target doc: \`${patch.doc_id}\` (${patch.doc_kind})`,
|
|
385
|
+
"",
|
|
386
|
+
"Suggested metadata updates:"
|
|
387
|
+
];
|
|
388
|
+
if ((patch.add_related_actors || []).length > 0) {
|
|
389
|
+
lines.push("", "```yaml", "related_actors:");
|
|
390
|
+
for (const actorId of patch.add_related_actors || []) {
|
|
391
|
+
lines.push(` - ${actorId}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push("```");
|
|
394
|
+
}
|
|
395
|
+
if ((patch.add_related_roles || []).length > 0) {
|
|
396
|
+
lines.push("", "```yaml", "related_roles:");
|
|
397
|
+
for (const roleId of patch.add_related_roles || []) {
|
|
398
|
+
lines.push(` - ${roleId}`);
|
|
399
|
+
}
|
|
400
|
+
lines.push("```");
|
|
401
|
+
}
|
|
402
|
+
if ((patch.add_related_capabilities || []).length > 0) {
|
|
403
|
+
lines.push("", "```yaml", "related_capabilities:");
|
|
404
|
+
for (const capabilityId of patch.add_related_capabilities || []) {
|
|
405
|
+
lines.push(` - ${capabilityId}`);
|
|
406
|
+
}
|
|
407
|
+
lines.push("```");
|
|
408
|
+
}
|
|
409
|
+
if ((patch.add_related_rules || []).length > 0) {
|
|
410
|
+
lines.push("", "```yaml", "related_rules:");
|
|
411
|
+
for (const ruleId of patch.add_related_rules || []) {
|
|
412
|
+
lines.push(` - ${ruleId}`);
|
|
413
|
+
}
|
|
414
|
+
lines.push("```");
|
|
415
|
+
}
|
|
416
|
+
if ((patch.add_related_workflows || []).length > 0) {
|
|
417
|
+
lines.push("", "```yaml", "related_workflows:");
|
|
418
|
+
for (const workflowId of patch.add_related_workflows || []) {
|
|
419
|
+
lines.push(` - ${workflowId}`);
|
|
420
|
+
}
|
|
421
|
+
lines.push("```");
|
|
422
|
+
}
|
|
423
|
+
lines.push("", patch.recommendation, "", "Review this draft update before editing the canonical doc.");
|
|
424
|
+
return renderMarkdownDoc(metadata, lines.join("\n"));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** @param {WorkflowRecord} patch @returns {any} */
|
|
428
|
+
export function renderDocMetadataPatchDoc(patch) {
|
|
429
|
+
/** @type {WorkflowRecord} */
|
|
430
|
+
const metadata = {
|
|
431
|
+
id: `doc-metadata-${patch.doc_id}`,
|
|
432
|
+
kind: "report",
|
|
433
|
+
title: `Doc Metadata Update for ${titleCase(String(patch.doc_id || "").replaceAll("_", " "))}`,
|
|
434
|
+
status: "inferred",
|
|
435
|
+
source_of_truth: "imported",
|
|
436
|
+
confidence: patch.imported_confidence || "medium",
|
|
437
|
+
review_required: true,
|
|
438
|
+
related_docs: [patch.doc_id],
|
|
439
|
+
tags: ["import", "doc-metadata-update"]
|
|
440
|
+
};
|
|
441
|
+
const lines = [
|
|
442
|
+
`Target doc: \`${patch.doc_id}\` (${patch.doc_kind})`,
|
|
443
|
+
"",
|
|
444
|
+
"Suggested metadata updates:"
|
|
445
|
+
];
|
|
446
|
+
if (patch.summary) {
|
|
447
|
+
lines.push("", "```yaml", `summary: ${JSON.stringify(patch.summary)}`, "```");
|
|
448
|
+
}
|
|
449
|
+
if (patch.success_outcome) {
|
|
450
|
+
lines.push("", "```yaml", `success_outcome: ${JSON.stringify(patch.success_outcome)}`, "```");
|
|
451
|
+
}
|
|
452
|
+
if ((patch.actors || []).length > 0) {
|
|
453
|
+
lines.push("", "```yaml", "actors:");
|
|
454
|
+
for (const actor of patch.actors || []) {
|
|
455
|
+
lines.push(` - ${actor}`);
|
|
456
|
+
}
|
|
457
|
+
lines.push("```");
|
|
458
|
+
}
|
|
459
|
+
lines.push("", patch.recommendation, "", "Review this draft update before editing the canonical doc.");
|
|
460
|
+
return renderMarkdownDoc(metadata, lines.join("\n"));
|
|
461
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import {
|
|
3
|
+
buildAuthHintClosureSummary,
|
|
4
|
+
inferBundleAuthClaimHints,
|
|
5
|
+
inferBundleAuthOwnershipHints,
|
|
6
|
+
inferBundleAuthPermissionHints,
|
|
7
|
+
inferBundleAuthRoleGuidance
|
|
8
|
+
} from "./auth.js";
|
|
9
|
+
import { primaryEntityIdForBundle } from "./bundle-shared.js";
|
|
10
|
+
|
|
11
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
12
|
+
export function summarizeBundleParticipants(bundle) {
|
|
13
|
+
const actors = [...new Set((bundle.actors || []).map((/** @type {any} */ entry) => entry.id_hint))];
|
|
14
|
+
const roles = [...new Set((bundle.roles || []).map((/** @type {any} */ entry) => entry.id_hint))];
|
|
15
|
+
return {
|
|
16
|
+
actors,
|
|
17
|
+
roles,
|
|
18
|
+
label: [...actors, ...roles].length
|
|
19
|
+
? [...actors, ...roles].map((/** @type {any} */ item) => `\`${item}\``).join(", ")
|
|
20
|
+
: "_none_"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @param {CandidateBundle} bundle @param {any[]} values @param {string} empty @returns {any} */
|
|
25
|
+
export function summarizeBundleSurface(bundle, values, empty = "_none_") {
|
|
26
|
+
const list = Array.isArray(values) ? values : [];
|
|
27
|
+
return list.length ? list.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : empty;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {CandidateBundle} bundle @returns {any} */
|
|
31
|
+
export function buildBundleOperatorSummary(bundle) {
|
|
32
|
+
const primaryEntityId = primaryEntityIdForBundle(bundle);
|
|
33
|
+
const primaryConcept =
|
|
34
|
+
primaryEntityId ||
|
|
35
|
+
bundle.capabilities?.[0]?.id_hint ||
|
|
36
|
+
bundle.workflows?.[0]?.id_hint ||
|
|
37
|
+
bundle.screens?.[0]?.id_hint ||
|
|
38
|
+
bundle.enums?.[0]?.id_hint ||
|
|
39
|
+
bundle.id;
|
|
40
|
+
const participants = summarizeBundleParticipants(bundle);
|
|
41
|
+
const capabilityIds = [...new Set((bundle.capabilities || []).map((/** @type {any} */ entry) => entry.id_hint))].slice(0, 4);
|
|
42
|
+
const widgetIds = [...new Set((bundle.widgets || []).map((/** @type {any} */ entry) => entry.id_hint))].slice(0, 4);
|
|
43
|
+
const screenIds = [...new Set((bundle.screens || []).map((/** @type {any} */ entry) => entry.id_hint))].slice(0, 4);
|
|
44
|
+
const routePaths = [...new Set((bundle.uiRoutes || []).map((/** @type {any} */ entry) => entry.path).filter(Boolean))].slice(0, 4);
|
|
45
|
+
const workflowIds = [...new Set((bundle.workflows || []).map((/** @type {any} */ entry) => entry.id_hint))].slice(0, 4);
|
|
46
|
+
const authPermissionHints = bundle.authPermissionHints || inferBundleAuthPermissionHints(bundle);
|
|
47
|
+
const authClaimHints = bundle.authClaimHints || inferBundleAuthClaimHints(bundle);
|
|
48
|
+
const authOwnershipHints = bundle.authOwnershipHints || inferBundleAuthOwnershipHints(bundle);
|
|
49
|
+
const authRoleGuidance = bundle.authRoleGuidance || inferBundleAuthRoleGuidance({
|
|
50
|
+
...bundle,
|
|
51
|
+
authPermissionHints,
|
|
52
|
+
authClaimHints,
|
|
53
|
+
authOwnershipHints
|
|
54
|
+
});
|
|
55
|
+
const evidenceKinds = [
|
|
56
|
+
(bundle.entities || []).length > 0 ? "entity evidence" : null,
|
|
57
|
+
(bundle.capabilities || []).length > 0 ? "API capability evidence" : null,
|
|
58
|
+
(bundle.widgets || []).length > 0 ? "UI widget evidence" : null,
|
|
59
|
+
(bundle.screens || []).length > 0 || (bundle.uiRoutes || []).length > 0 ? "UI screen/route evidence" : null,
|
|
60
|
+
(bundle.workflows || []).length > 0 ? "workflow evidence" : null,
|
|
61
|
+
(bundle.docs || []).length > 0 ? "doc evidence" : null,
|
|
62
|
+
(bundle.actors || []).length > 0 || (bundle.roles || []).length > 0 ? "actor/role evidence" : null
|
|
63
|
+
].filter(Boolean);
|
|
64
|
+
const whyThisBundle =
|
|
65
|
+
evidenceKinds.length > 0
|
|
66
|
+
? `This bundle exists because ${evidenceKinds.join(", ")} converges on the same ${bundle.label.toLowerCase()} concept.`
|
|
67
|
+
: `This bundle exists because multiple imported signals point at the same ${bundle.label.toLowerCase()} concept.`;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
primaryConcept,
|
|
71
|
+
primaryEntityId,
|
|
72
|
+
participants,
|
|
73
|
+
capabilityIds,
|
|
74
|
+
widgetIds,
|
|
75
|
+
screenIds,
|
|
76
|
+
routePaths,
|
|
77
|
+
workflowIds,
|
|
78
|
+
authPermissionHints,
|
|
79
|
+
authClaimHints,
|
|
80
|
+
authOwnershipHints,
|
|
81
|
+
authRoleGuidance,
|
|
82
|
+
authAging: bundle.operatorSummary?.authAging || null,
|
|
83
|
+
authClosureSummary: buildAuthHintClosureSummary({
|
|
84
|
+
authPermissionHints,
|
|
85
|
+
authClaimHints,
|
|
86
|
+
authOwnershipHints
|
|
87
|
+
}),
|
|
88
|
+
whyThisBundle
|
|
89
|
+
};
|
|
90
|
+
}
|