@agentskit/doc-bridge 1.6.4 → 1.7.45
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/CHANGELOG.md +249 -0
- package/CONTRIBUTING.md +6 -4
- package/action.yml +1 -1
- package/dist/cli/program.js +1139 -294
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +43 -5
- package/dist/config/index.js.map +1 -1
- package/dist/index-BUL0q7s8.d.ts +660 -0
- package/dist/index.d.ts +817 -2134
- package/dist/index.js +1154 -244
- package/dist/index.js.map +1 -1
- package/docs/PRD-enterprise-hardening.md +288 -0
- package/docs/RELEASE.md +22 -8
- package/docs/adr/0001-enterprise-verification-contract.md +35 -0
- package/docs/agent-corpus/INDEX.md +2 -2
- package/docs/agent-corpus/chat.md +2 -2
- package/docs/agent-corpus/cli.md +2 -2
- package/docs/agent-corpus/conformance.md +2 -2
- package/docs/agent-corpus/doc-bridge.md +1 -1
- package/docs/agent-corpus/doctor.md +2 -2
- package/docs/agent-corpus/gates.md +2 -2
- package/docs/agent-corpus/mcp.md +2 -2
- package/docs/agent-corpus/memory.md +2 -2
- package/docs/agent-corpus/query.md +2 -2
- package/docs/knowledge-engine-runbook.md +30 -2
- package/docs/spec/analyzer-plugin-v1.md +24 -0
- package/docs/spec/benchmark-v1.md +36 -0
- package/docs/spec/config-v1.md +156 -0
- package/docs/validation-cycle-plan.md +255 -0
- package/docs/verification-harness.md +37 -4
- package/mcpb/manifest.json +1 -1
- package/package.json +68 -70
- package/scripts/check-ecosystem-upstream.mjs +3 -2
- package/scripts/report-visual-check.mjs +64 -12
- package/scripts/verification-harness.mjs +216 -14
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +31 -7
- package/src/cli/demo.ts +2 -2
- package/src/cli/program.ts +59 -16
- package/src/config/index.ts +2 -0
- package/src/config/load-config.ts +7 -1
- package/src/config/schema.ts +60 -2
- package/src/conformance/documentation-standard-v1.ts +14 -8
- package/src/discovery/documentation.ts +90 -23
- package/src/discovery/repository.ts +147 -19
- package/src/doctor/run-doctor.ts +2 -15
- package/src/federation/llms.ts +72 -20
- package/src/fixes/proposals.ts +4 -3
- package/src/index-builder/human-adapters/fumadocs.ts +1 -1
- package/src/index-builder/watch-index.ts +1 -1
- package/src/index.ts +29 -0
- package/src/lib/bounded-text.ts +15 -10
- package/src/metrics/benchmark.ts +176 -0
- package/src/plugins/contract.ts +89 -0
- package/src/reconciliation/reconcile.ts +181 -5
- package/src/report/html.ts +318 -88
- package/src/rules/engine.ts +15 -2
- package/src/safety/repository.ts +1 -1
- package/src/schemas/knowledge.ts +21 -3
- package/src/validate.ts +7 -1
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +65 -9
- package/dist/index-DudNuwI5.d.ts +0 -2060
package/src/report/html.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type OfflineReportInput = {
|
|
|
9
9
|
|
|
10
10
|
export type OfflineReportOptions = {
|
|
11
11
|
readonly includeSnippets?: boolean
|
|
12
|
+
readonly privacy?: 'private' | 'anonymized'
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export type OfflineReportArtifact = {
|
|
@@ -33,8 +34,15 @@ type ReportViewModel = {
|
|
|
33
34
|
readonly nodes: readonly ReportViewNode[]
|
|
34
35
|
readonly edges: readonly { from: string; to: string; count: number; kinds: readonly string[]; relationIds: readonly string[] }[]
|
|
35
36
|
}
|
|
36
|
-
readonly groups: Readonly<Record<string, { kind: string; name: string; path: string | undefined; members: readonly string[] }>>
|
|
37
|
+
readonly groups: Readonly<Record<string, { kind: string; name: string; path: string | undefined; members: readonly string[]; moduleCount: number }>>
|
|
37
38
|
readonly entityGroup: Readonly<Record<string, string>>
|
|
39
|
+
readonly diagnosticGroup?: Readonly<Record<string, readonly string[]>>
|
|
40
|
+
readonly diagnosticRelationFindings?: Readonly<Record<string, readonly { status: string; severity: string }[]>>
|
|
41
|
+
readonly levelChunks: {
|
|
42
|
+
readonly default: string
|
|
43
|
+
readonly groups: Readonly<Record<string, string>>
|
|
44
|
+
readonly packages: readonly string[]
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
const pathParts = (value: string | undefined): string[] => (value ?? '').split('/').filter(Boolean)
|
|
@@ -56,6 +64,8 @@ const reportGroupKey = (entity: KnowledgeEntity, appLayout: boolean): string =>
|
|
|
56
64
|
|
|
57
65
|
const titleCase = (value: string): string => value.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ')
|
|
58
66
|
|
|
67
|
+
const levelChunkName = (scope: 'group' | 'package', id: string): string => `chunks/levels-${scope}-${sha256NormalizedV1(id).slice(0, 12)}.js`
|
|
68
|
+
|
|
59
69
|
const buildReportViewModel = (snapshot: DiscoverySnapshotV1): ReportViewModel => {
|
|
60
70
|
const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]))
|
|
61
71
|
const packages = snapshot.entities.filter((entity) => entity.kind === 'package')
|
|
@@ -71,6 +81,7 @@ const buildReportViewModel = (snapshot: DiscoverySnapshotV1): ReportViewModel =>
|
|
|
71
81
|
const appLayout = packages.some((entity) => pathParts(entity.path)[0] === 'apps')
|
|
72
82
|
const groups = new Map<string, { kind: string; name: string; path: string | undefined; members: string[] }>()
|
|
73
83
|
const entityGroup: Record<string, string> = {}
|
|
84
|
+
const groupModuleCounts = new Map<string, number>()
|
|
74
85
|
|
|
75
86
|
for (const entity of packages) {
|
|
76
87
|
const family = reportGroupKey(entity, appLayout)
|
|
@@ -89,6 +100,7 @@ const buildReportViewModel = (snapshot: DiscoverySnapshotV1): ReportViewModel =>
|
|
|
89
100
|
for (const [entityId, packageId] of packageForEntity) {
|
|
90
101
|
const groupId = entityGroup[packageId]
|
|
91
102
|
if (groupId) entityGroup[entityId] = groupId
|
|
103
|
+
if (groupId && entities.get(entityId)?.kind === 'module') groupModuleCounts.set(groupId, (groupModuleCounts.get(groupId) ?? 0) + 1)
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
if (externalIds.size) {
|
|
@@ -136,8 +148,13 @@ const buildReportViewModel = (snapshot: DiscoverySnapshotV1): ReportViewModel =>
|
|
|
136
148
|
.map((edge) => ({ ...edge, kinds: [...edge.kinds].sort(), relationIds: [...edge.relationIds].sort() }))
|
|
137
149
|
.sort((left, right) => right.count - left.count || left.from.localeCompare(right.from) || left.to.localeCompare(right.to)),
|
|
138
150
|
},
|
|
139
|
-
groups: Object.fromEntries([...groups.entries()].map(([id, group]) => [id, { ...group, members: [...group.members].sort() }])),
|
|
151
|
+
groups: Object.fromEntries([...groups.entries()].map(([id, group]) => [id, { ...group, members: [...group.members].sort(), moduleCount: groupModuleCounts.get(id) ?? 0 }])),
|
|
140
152
|
entityGroup,
|
|
153
|
+
levelChunks: {
|
|
154
|
+
default: 'chunks/levels-packages.js',
|
|
155
|
+
groups: Object.fromEntries([...groups.keys()].map((id) => [id, levelChunkName('group', id)])),
|
|
156
|
+
packages: [...packages].sort((left, right) => left.id.localeCompare(right.id)).map((entity) => levelChunkName('package', entity.id)),
|
|
157
|
+
},
|
|
141
158
|
}
|
|
142
159
|
}
|
|
143
160
|
|
|
@@ -154,54 +171,173 @@ const embeddedJson = (value: unknown): string => JSON.stringify(value).replaceAl
|
|
|
154
171
|
|
|
155
172
|
const errorPage = (message: string): string => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Doc Bridge report error</title><style>body{font:16px system-ui;margin:3rem;color:#311}main{max-width:60rem;margin:auto;border:1px solid #d99;padding:2rem;border-radius:8px;background:#fff8f8}code{white-space:pre-wrap}</style></head><body><main><h1>Doc Bridge report unavailable</h1><p>The saved snapshot/report could not be rendered.</p><code>${escapeHtml(message)}</code><p>Run <code>ak-docs check</code> to regenerate valid artifacts.</p></main></body></html>`
|
|
156
173
|
|
|
157
|
-
const reportData = (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV1, includeSnippets: boolean) =>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
174
|
+
const reportData = (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV1, includeSnippets: boolean, privacy: OfflineReportOptions['privacy'] = 'private') => {
|
|
175
|
+
const evidence = (items: readonly { source: string; path: string; lineStart?: number | undefined; lineEnd?: number | undefined; context?: string | undefined }[]) => items.map(({ context, ...item }) => includeSnippets && context ? { ...item, context: redactSecrets(context) } : item)
|
|
176
|
+
const base = {
|
|
177
|
+
project: snapshot.project,
|
|
178
|
+
revision: snapshot.sourceRevision,
|
|
179
|
+
revisionKind: snapshot.sourceRevisionKind,
|
|
180
|
+
snapshotHash: snapshot.contentHash,
|
|
181
|
+
reportHash: report.contentHash,
|
|
182
|
+
configurationHash: snapshot.configurationHash,
|
|
183
|
+
pipelineVersion: snapshot.pipelineVersion,
|
|
184
|
+
analyzerVersions: snapshot.analyzerVersions,
|
|
185
|
+
diagnosticCount: report.diagnostics.length,
|
|
186
|
+
actionableCount: report.diagnostics.filter((diagnostic) => diagnostic.status !== 'confirmed').length,
|
|
187
|
+
confirmedCount: report.diagnostics.filter((diagnostic) => diagnostic.status === 'confirmed').length,
|
|
188
|
+
requiredRelationKinds: report.summary.requiredRelationKinds,
|
|
189
|
+
requiredRelationTargets: report.summary.requiredRelationTargets,
|
|
190
|
+
documentation: report.summary.documentation,
|
|
191
|
+
diagnosticSummary: {
|
|
192
|
+
errors: report.diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length,
|
|
193
|
+
warnings: report.diagnostics.filter((diagnostic) => diagnostic.severity === 'warn').length,
|
|
194
|
+
undocumented: report.diagnostics.filter((diagnostic) => diagnostic.status === 'undocumented').length,
|
|
195
|
+
drift: report.diagnostics.filter((diagnostic) => diagnostic.status === 'stale-or-unverified' || diagnostic.status === 'conflict').length,
|
|
196
|
+
},
|
|
197
|
+
entities: snapshot.entities.map((entity, index) => ({
|
|
198
|
+
id: entity.id,
|
|
199
|
+
anchor: entity.id.length > 64 ? `entity-n${index}` : anchor('entity', entity.id),
|
|
200
|
+
kind: entity.kind,
|
|
201
|
+
name: entity.name,
|
|
202
|
+
path: entity.path,
|
|
203
|
+
provenance: entity.provenance,
|
|
204
|
+
evidence: evidence(entity.evidence),
|
|
205
|
+
})),
|
|
206
|
+
relations: snapshot.relations.map((relation) => ({
|
|
207
|
+
id: relation.id,
|
|
208
|
+
kind: relation.kind,
|
|
209
|
+
from: relation.from,
|
|
210
|
+
to: relation.to,
|
|
211
|
+
provenance: relation.provenance,
|
|
212
|
+
evidence: evidence(relation.evidence),
|
|
213
|
+
})),
|
|
214
|
+
diagnostics: report.diagnostics.map((diagnostic) => ({
|
|
215
|
+
id: diagnostic.id,
|
|
216
|
+
code: diagnostic.code,
|
|
217
|
+
status: diagnostic.status,
|
|
218
|
+
severity: diagnostic.severity,
|
|
219
|
+
message: diagnostic.message,
|
|
220
|
+
entityIds: diagnostic.entityIds ?? [],
|
|
221
|
+
relationIds: diagnostic.relationIds ?? [],
|
|
222
|
+
remediation: diagnostic.remediation,
|
|
223
|
+
evidence: evidence(diagnostic.evidence),
|
|
224
|
+
})),
|
|
225
|
+
coverage: snapshot.coverage,
|
|
226
|
+
view: buildReportViewModel(snapshot),
|
|
227
|
+
}
|
|
228
|
+
if (privacy !== 'anonymized') return base
|
|
229
|
+
|
|
230
|
+
const pad = (value: number): string => String(value).padStart(3, '0')
|
|
231
|
+
const sortedEntities = [...base.entities].sort((left, right) => left.id.localeCompare(right.id))
|
|
232
|
+
const entityOrdinals = new Map<string, number>()
|
|
233
|
+
const entityIds = new Map(sortedEntities.map((entity) => {
|
|
234
|
+
const next = (entityOrdinals.get(entity.kind) ?? 0) + 1
|
|
235
|
+
entityOrdinals.set(entity.kind, next)
|
|
236
|
+
return [entity.id, `entity:${entity.kind}-${pad(next)}`] as const
|
|
237
|
+
}))
|
|
238
|
+
const entityNames = new Map<string, number>()
|
|
239
|
+
const nameFor = (kind: string): string => {
|
|
240
|
+
const prefix = kind === 'external' ? 'dependency' : kind
|
|
241
|
+
const next = (entityNames.get(prefix) ?? 0) + 1
|
|
242
|
+
entityNames.set(prefix, next)
|
|
243
|
+
return `${prefix}-${pad(next)}`
|
|
244
|
+
}
|
|
245
|
+
const sortedRelations = [...base.relations].sort((left, right) => left.id.localeCompare(right.id))
|
|
246
|
+
const relationIds = new Map(sortedRelations.map((relation, index) => [relation.id, `relation:${pad(index + 1)}`]))
|
|
247
|
+
const sortedDiagnostics = [...base.diagnostics].sort((left, right) => left.id.localeCompare(right.id))
|
|
248
|
+
const diagnosticIds = new Map(sortedDiagnostics.map((diagnostic, index) => [diagnostic.id, `finding:${pad(index + 1)}`]))
|
|
249
|
+
const genericEvidence = (items: readonly { source: string; lineStart?: number | undefined; lineEnd?: number | undefined }[]) => items.map((item, index) => ({
|
|
250
|
+
source: item.source,
|
|
251
|
+
path: `evidence-${pad(index + 1)}`,
|
|
252
|
+
...(item.lineStart === undefined ? {} : { lineStart: item.lineStart }),
|
|
253
|
+
...(item.lineEnd === undefined ? {} : { lineEnd: item.lineEnd }),
|
|
254
|
+
}))
|
|
255
|
+
const groups = Object.entries(base.view.groups).sort(([left], [right]) => left.localeCompare(right))
|
|
256
|
+
const groupIds = new Map(groups.map(([id], index) => [id, `group:${pad(index + 1)}`]))
|
|
257
|
+
const groupNames = new Map<string, number>()
|
|
258
|
+
const groupName = (kind: string): string => {
|
|
259
|
+
if (kind === 'external') return 'External dependencies'
|
|
260
|
+
if (kind === 'shared') return 'Shared packages'
|
|
261
|
+
if (kind === 'workspace') return 'Workspace'
|
|
262
|
+
const next = (groupNames.get(kind) ?? 0) + 1
|
|
263
|
+
groupNames.set(kind, next)
|
|
264
|
+
return `${kind.charAt(0).toUpperCase()}${kind.slice(1)} ${next}`
|
|
265
|
+
}
|
|
266
|
+
const sanitizedGroups = Object.fromEntries(groups.map(([id, group]) => [groupIds.get(id) as string, {
|
|
267
|
+
kind: group.kind,
|
|
268
|
+
name: groupName(group.kind),
|
|
269
|
+
path: undefined,
|
|
270
|
+
members: group.members.map((member) => entityIds.get(member) as string),
|
|
271
|
+
moduleCount: group.moduleCount,
|
|
272
|
+
}]))
|
|
273
|
+
const sanitizedOverviewNodes = base.view.overview.nodes.map((node) => ({
|
|
274
|
+
id: groupIds.get(node.id) as string,
|
|
275
|
+
kind: node.kind,
|
|
276
|
+
name: sanitizedGroups[groupIds.get(node.id) as string]?.name ?? 'Scope',
|
|
277
|
+
path: undefined,
|
|
278
|
+
memberCount: node.memberCount,
|
|
279
|
+
}))
|
|
280
|
+
const sanitizedView = {
|
|
281
|
+
overview: {
|
|
282
|
+
nodes: sanitizedOverviewNodes,
|
|
283
|
+
edges: base.view.overview.edges.map((edge) => ({
|
|
284
|
+
from: groupIds.get(edge.from) as string,
|
|
285
|
+
to: groupIds.get(edge.to) as string,
|
|
286
|
+
count: edge.count,
|
|
287
|
+
kinds: edge.kinds,
|
|
288
|
+
relationIds: edge.relationIds.map((id) => relationIds.get(id) as string),
|
|
289
|
+
})),
|
|
290
|
+
},
|
|
291
|
+
groups: sanitizedGroups,
|
|
292
|
+
entityGroup: Object.fromEntries(Object.entries(base.view.entityGroup).map(([id, group]) => [entityIds.get(id) as string, groupIds.get(group) as string])),
|
|
293
|
+
levelChunks: {
|
|
294
|
+
default: base.view.levelChunks.default,
|
|
295
|
+
groups: Object.fromEntries([...groupIds.values()].map((id) => [id, levelChunkName('group', id)])),
|
|
296
|
+
packages: [...entityIds.values()].filter((id) => id.startsWith('entity:package-')).sort().map((id) => levelChunkName('package', id)),
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
...base,
|
|
301
|
+
project: { name: 'Anonymized repository' },
|
|
302
|
+
revision: 'redacted',
|
|
303
|
+
entities: base.entities.map((entity) => ({
|
|
304
|
+
id: entityIds.get(entity.id) as string,
|
|
305
|
+
anchor: anchor('entity', entityIds.get(entity.id) as string),
|
|
306
|
+
kind: entity.kind,
|
|
307
|
+
name: nameFor(entity.kind),
|
|
308
|
+
path: undefined,
|
|
309
|
+
provenance: entity.provenance,
|
|
310
|
+
evidence: genericEvidence(entity.evidence),
|
|
311
|
+
})),
|
|
312
|
+
relations: base.relations.map((relation) => ({
|
|
313
|
+
id: relationIds.get(relation.id) as string,
|
|
314
|
+
kind: relation.kind,
|
|
315
|
+
from: entityIds.get(relation.from) as string,
|
|
316
|
+
to: entityIds.get(relation.to) as string,
|
|
317
|
+
provenance: relation.provenance,
|
|
318
|
+
evidence: genericEvidence(relation.evidence),
|
|
319
|
+
})),
|
|
320
|
+
diagnostics: base.diagnostics.map((diagnostic) => ({
|
|
321
|
+
id: diagnosticIds.get(diagnostic.id) as string,
|
|
322
|
+
code: diagnostic.code,
|
|
323
|
+
status: diagnostic.status,
|
|
324
|
+
severity: diagnostic.severity,
|
|
325
|
+
message: `Finding ${diagnostic.code}`,
|
|
326
|
+
entityIds: diagnostic.entityIds?.map((id) => entityIds.get(id) as string) ?? [],
|
|
327
|
+
relationIds: diagnostic.relationIds?.map((id) => relationIds.get(id) as string) ?? [],
|
|
328
|
+
evidence: genericEvidence(diagnostic.evidence),
|
|
329
|
+
})),
|
|
330
|
+
coverage: base.coverage.map((entry, index) => ({
|
|
331
|
+
analyzer: entry.analyzer,
|
|
332
|
+
analyzerVersion: entry.analyzerVersion,
|
|
333
|
+
scope: `scope-${pad(index + 1)}`,
|
|
334
|
+
status: entry.status,
|
|
335
|
+
...(entry.reason ? { reason: 'Coverage boundary reported by analyzer.' } : {}),
|
|
336
|
+
...(entry.evidence ? { evidence: genericEvidence(entry.evidence) } : {}),
|
|
337
|
+
})),
|
|
338
|
+
view: sanitizedView,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
205
341
|
|
|
206
342
|
let styles = String.raw`
|
|
207
343
|
:root{color-scheme:light;--ink:#17251f;--muted:#60706a;--paper:#f5f7f3;--panel:#fff;--line:#d9e0da;--green:#18794e;--blue:#236b83;--amber:#a9640b;--red:#b33c35;--shadow:0 12px 32px rgba(23,37,31,.08)}
|
|
@@ -213,9 +349,9 @@ styles += String.raw`
|
|
|
213
349
|
*{box-sizing:border-box}body{background:var(--paper);font-family:"Avenir Next","Segoe UI",ui-sans-serif,system-ui,sans-serif;font-size:14px;line-height:1.5}.shell{max-width:1720px;padding:30px clamp(20px,4vw,64px) 56px}.masthead{align-items:flex-start;border-bottom:2px solid var(--ink);padding:4px 0 28px}.masthead>div:first-child{max-width:840px}.eyebrow{font-size:10px;letter-spacing:.16em}.masthead h1{font-family:"Iowan Old Style",Georgia,serif;font-size:clamp(3rem,7vw,7.2rem);letter-spacing:-.075em;line-height:.9;margin:16px 0 20px}.lede{font-size:17px;line-height:1.45;max-width:650px}.run-meta{padding-top:3px;min-width:210px}.read-only{border:0;border-radius:0;padding:0;background:none;color:var(--green);font-size:10px;letter-spacing:.1em;text-transform:uppercase}.run-meta strong{color:var(--ink);font-size:12px;overflow-wrap:anywhere;margin-top:16px}.summary{grid-template-columns:repeat(5,minmax(0,1fr));gap:0;margin:0;padding:18px 0 22px;border-bottom:1px solid var(--line)}.metric{border:0;border-left:1px solid var(--line);border-radius:0;background:none;box-shadow:none;padding:0 18px}.metric:first-child{border-left:0;padding-left:0}.metric b{font-family:"Iowan Old Style",Georgia,serif;font-size:38px;letter-spacing:-.05em}.metric span{font-size:11px;letter-spacing:.07em;text-transform:uppercase}.lens-bar{align-items:flex-end;margin:42px 0 6px}.tabs{gap:0}.tab,.level{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--muted);padding:8px 14px;min-height:38px;font-weight:700}.tab:first-child,.level:first-child{padding-left:0}.tab:hover,.level:hover{background:none;color:var(--ink);border-color:var(--line)}.tab[aria-selected=true],.level[aria-pressed=true]{background:none;color:var(--ink);border-color:var(--green)}.lens-bar>.subtle{max-width:430px;text-align:right}.filters{border-block:1px solid var(--line);padding:14px 0;margin:12px 0 24px}.filters input,.filters select,.filters button{border:1px solid var(--line);border-radius:3px;background:var(--panel);min-height:38px}.filters button{font-weight:700}.insights{grid-template-columns:repeat(4,minmax(0,1fr));gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:18px 0 30px}.insight{border:0;border-left:1px solid var(--line);border-radius:0;background:none;box-shadow:none;padding:14px 16px 16px}.insight:first-child{border-left:0;padding-left:0}.insight h3{font-size:12px;letter-spacing:.02em}.insight p{font-size:13px}.tag{border-radius:2px;padding:2px 5px;font-size:9px}.workspace{grid-template-columns:minmax(0,1fr) 350px;gap:24px}.panel{border:1px solid var(--line);border-radius:3px;background:var(--panel);box-shadow:none;padding:24px}.panel h2{font-family:"Iowan Old Style",Georgia,serif;font-size:30px;letter-spacing:-.04em}.map-wrap{border:1px solid #aeb9ae;border-radius:2px;background-color:#e8eee7;background-image:linear-gradient(rgba(24,43,43,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(24,43,43,.07) 1px,transparent 1px);background-size:24px 24px}.map-wrap::-webkit-scrollbar{height:10px}.map-wrap::-webkit-scrollbar-thumb{background:var(--line)}#graph{height:620px}.graph-node rect{fill:#f8f5ee;stroke:#274744;stroke-width:1.4;rx:2}.graph-node:hover rect,.graph-node:focus rect{stroke:var(--blue);stroke-width:2.5}.graph-node.selected rect{fill:#dceee1;stroke:var(--green);stroke-width:3}.graph-node.issue rect{fill:#fff0d4;stroke:var(--amber)}.edge{stroke:#69857d;stroke-width:1.5;opacity:.8}.edge.alert{stroke:var(--amber);stroke-width:2.5}.edge-label{fill:var(--ink);font-size:10px;font-weight:700}.node-label{font-size:12px;font-weight:800}.node-kind{font-size:10px;fill:var(--muted)}.node-count{font-size:10px;fill:var(--green);font-weight:800}.side{top:20px}.side .panel-head{border-bottom:1px solid var(--line);padding-bottom:14px}.detail-title{font-family:"Iowan Old Style",Georgia,serif;font-size:28px}.detail-grid{gap:0}.detail-grid>div{border-left:1px solid var(--line);padding-left:10px}.detail-grid>div:nth-child(odd){border-left:0;padding-left:0}.detail-grid b{font-family:"Iowan Old Style",Georgia,serif;font-size:26px}.run{margin-top:24px}.run-summary{border-bottom:1px solid var(--line);padding-bottom:12px}.chip{border:0;border-left:1px solid var(--line);border-radius:0;padding:2px 10px}.chip:first-child{border-left:0;padding-left:0}.finding-groups{grid-template-columns:repeat(2,minmax(0,1fr));gap:0}.finding-group{border:0;border-top:1px solid var(--line);border-radius:0;background:none;padding:16px 14px 16px 0}.finding-group:nth-child(even){padding-left:14px;border-left:1px solid var(--line)}.finding-group h3{font-size:13px;letter-spacing:.02em}.finding-detail{border-top:2px solid var(--ink)}.finding{padding:16px 0}.metadata{gap:24px;margin-top:26px}.metadata div{border-top:1px solid var(--line);padding-top:9px}.map-wrap,.panel,.finding-group,.insight{transition:background-color .16s ease,border-color .16s ease}@media(max-width:900px){.masthead{display:block}.run-meta{padding-top:24px;text-align:left}.summary{grid-template-columns:repeat(3,minmax(0,1fr));row-gap:18px}.metric:nth-child(4){border-left:0;padding-left:0}.workspace{grid-template-columns:1fr}.side{position:static}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}@media(max-width:560px){.shell{padding:20px 16px 40px}.masthead h1{font-size:clamp(3rem,16vw,5rem)}.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.metric:nth-child(odd){border-left:0;padding-left:0}.metric:nth-child(even){border-left:1px solid var(--line);padding-left:12px}.insights{grid-template-columns:1fr}.insight,.insight:first-child{border-left:0;border-top:1px solid var(--line);padding-left:0}.insight:first-child{border-top:0}.panel{padding:16px}.panel-head{display:block}.panel-head>.tabs{margin-top:12px}.filters label{width:100%}.filters input,.filters select{width:100%}.metadata{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){.map-wrap,.panel,.finding-group,.insight{transition:none}}@media(prefers-color-scheme:dark){:root{--ink:#e8eee7;--muted:#b0beb3;--paper:#131c1a;--panel:#1b2824;--line:#42534b;--green:#72d09d;--blue:#86cfe1;--amber:#f0bc6b;--red:#ff9186}.map-wrap{background-color:#17231e;background-image:linear-gradient(rgba(232,238,231,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(232,238,231,.07) 1px,transparent 1px)}.graph-node rect{fill:#1c2b25;stroke:#90aa99}.graph-node.selected rect{fill:#294936}.graph-node.issue rect{fill:#40321e}.filters input,.filters select,.filters button{background:#202f29;color:var(--ink)}}
|
|
214
350
|
`
|
|
215
351
|
|
|
216
|
-
styles += String.raw`.graph-node .node-label,.graph-node text.node-label{fill:var(--ink)}.graph-node .node-kind{fill:var(--muted)}.edge-label{fill:var(--ink)}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.map-wrap #graph{min-width:0!important;width:100%!important}.panel-head>.tabs{margin-top:12px}}@media(max-width:700px){.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.filters label{width:100%}.filters input,.filters select{width:100%}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}`
|
|
352
|
+
styles += String.raw`.graph-node .node-label,.graph-node text.node-label{fill:var(--ink)}.graph-node .node-kind{fill:var(--muted)}.edge-label{fill:var(--ink)}.panel-head>div:first-child{min-width:0;flex:1 1 0}.panel-head>.tabs:not(.report-tabs){flex-wrap:nowrap;max-width:100%;overflow-x:auto}.panel-head>.tabs:not(.report-tabs) .level{flex:0 0 auto}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.map-wrap #graph{min-width:0!important;width:100%!important}.panel-head>.tabs{margin-top:12px}}@media(max-width:700px){.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.filters label{width:100%}.filters input,.filters select{width:100%}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}`
|
|
217
353
|
|
|
218
|
-
styles += String.raw`.
|
|
354
|
+
styles += String.raw`.report-tabs{display:flex;gap:0;align-items:flex-end;border-bottom:1px solid var(--line);margin:24px 0 0}.report-tabs .tab{font-size:13px}.breadcrumbs{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:0 0 14px;color:var(--muted);font-size:12px}.breadcrumbs button{border:0;background:none;color:var(--blue);padding:3px 0;font-weight:700}.breadcrumbs button:hover{text-decoration:underline}.breadcrumbs .current{color:var(--ink);font-weight:700}.scope-note{margin:0 0 12px}.report-dashboard{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,1fr);gap:24px}.report-table{width:100%;border-collapse:collapse;font-size:12px}.report-table th,.report-table td{border-top:1px solid var(--line);padding:10px 8px;text-align:left;vertical-align:top}.report-table th{color:var(--muted);font-size:10px;letter-spacing:.08em;text-transform:uppercase}.report-table td:first-child{font-weight:750}.bar-list{display:grid;gap:10px}.bar-item{display:grid;grid-template-columns:minmax(120px,1fr) 2fr auto;gap:10px;align-items:center;font-size:12px}.bar-item i{height:8px;background:var(--line);display:block;overflow:hidden}.bar-item i b{display:block;height:100%;background:var(--green)}.map-wrap{touch-action:none;cursor:grab}.map-wrap.dragging{cursor:grabbing}@media(max-width:700px){.report-tabs{overflow:auto}.report-tabs .tab{white-space:nowrap}.report-dashboard{grid-template-columns:1fr}.bar-item{grid-template-columns:minmax(100px,1fr) 1fr auto}}`
|
|
219
355
|
|
|
220
356
|
styles += String.raw`body[data-report-view="architecture"] #insights,body[data-report-view="architecture"] .filters,body[data-report-view="architecture"] .run,body[data-report-view="architecture"] .metadata{display:none}body[data-report-view="insights"] .workspace,body[data-report-view="insights"] .filters,body[data-report-view="insights"] .run,body[data-report-view="insights"] .metadata{display:none}body[data-report-view="findings"] .workspace,body[data-report-view="findings"] #insights,body[data-report-view="findings"] #report-dashboard,body[data-report-view="findings"] #coverage-title,body[data-report-view="findings"] #coverage-title~*{display:none}body[data-report-view="findings"] .run[aria-labelledby="coverage-title"]{display:none}body[data-report-view="coverage"] .workspace,body[data-report-view="coverage"] #insights,body[data-report-view="coverage"] #report-dashboard,body[data-report-view="coverage"] .filters,body[data-report-view="coverage"] .run[aria-labelledby="run-title"],body[data-report-view="coverage"] .metadata{display:none}body[data-report-view="insights"] #report-dashboard{display:block}body[data-report-view="map"] #report-dashboard{display:none}`
|
|
221
357
|
styles += String.raw`body[data-report-view="architecture"] #report-dashboard{display:none!important}.edge{fill:none}`
|
|
@@ -223,71 +359,81 @@ styles += String.raw`body[data-report-view="architecture"] #report-dashboard{dis
|
|
|
223
359
|
styles += String.raw`:root{--ink:#17251f;--muted:#586b63;--paper:#f2f5f1;--panel:#fbfcf9;--line:#c9d5cc;--green:#13734a;--blue:#155f78;--amber:#995d08;--red:#a52e2b}html,body{max-width:100%;overflow-x:hidden}body{font-size:15px;line-height:1.55}.shell{width:100%;max-width:1600px;margin:0 auto;padding:28px clamp(16px,3vw,48px) 56px}.masthead{gap:32px;padding:0 0 24px;align-items:flex-start}.masthead h1{font-size:clamp(2.8rem,5.5vw,5.4rem);line-height:.94;margin:12px 0 16px;max-width:100%;overflow-wrap:anywhere}.lede{max-width:60ch;font-size:16px}.run-meta{min-width:0;max-width:300px}.summary{margin:0;padding:18px 0 20px;grid-template-columns:repeat(5,minmax(0,1fr));gap:0}.metric{min-width:0;padding:0 16px;border-inline-start:1px solid var(--line)}.metric:first-child{padding-inline-start:0;border-inline-start:0}.metric b{font-size:clamp(1.8rem,3vw,3rem);max-width:100%;overflow-wrap:anywhere}.metric span{font-size:10px;letter-spacing:.06em;line-height:1.25}.lens-bar{margin:24px 0 4px;align-items:flex-end}.lens-bar>.subtle{max-width:52ch}.report-tabs{max-width:100%;overflow-x:auto}.tab,.level{min-height:40px;padding:8px 12px}.filters{max-width:100%;align-items:center}.filters label{min-width:0}.filters input,.filters select{max-width:100%;min-width:0}.workspace{min-width:0;grid-template-columns:minmax(0,1fr) minmax(280px,340px);gap:20px}.panel{min-width:0}.map-wrap{min-width:0;max-width:100%;width:100%;overflow:hidden;contain:layout paint}#graph{display:block;width:100%!important;min-width:0!important;max-width:100%;height:clamp(480px,60vh,680px)!important}.side{min-width:0}.detail-title,.path,.list,.finding,.finding-group,.report-table{min-width:0;overflow-wrap:anywhere}.report-dashboard{min-width:0}.report-table{table-layout:fixed}.report-table th,.report-table td{overflow-wrap:anywhere}.bar-item{min-width:0}.bar-item span{min-width:0;overflow-wrap:anywhere}.tab:focus-visible,.level:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--blue);outline-offset:2px}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.summary{grid-template-columns:repeat(3,minmax(0,1fr));row-gap:16px}.summary .metric:nth-child(4){border-inline-start:0;padding-inline-start:0}}@media(max-width:700px){.shell{padding:12px 16px 32px}.masthead{padding-bottom:14px}.masthead h1{font-size:clamp(2.2rem,11vw,3.4rem);margin:6px 0 10px}.lede{font-size:14px;line-height:1.35}.run-meta{display:none}.summary{padding:10px 0 12px;grid-template-columns:repeat(2,minmax(0,1fr));row-gap:8px}.summary .metric:nth-child(even){border-inline-start:1px solid var(--line);padding-inline-start:12px}.summary .metric:nth-child(odd){border-inline-start:0;padding-inline-start:0}.metric{padding-inline:10px}.metric b{font-size:1.65rem}.lens-bar{display:block;margin-top:14px}.lens-bar>.subtle{display:none}.filters{display:grid;grid-template-columns:1fr;gap:10px}.filters label{display:grid;grid-template-columns:1fr;gap:5px}.filters input,.filters select,.filters button{width:100%;min-height:44px}.panel{padding:16px}.panel-head{display:block}.panel-head>.tabs{margin-top:12px}.finding-groups{grid-template-columns:1fr}.report-dashboard{grid-template-columns:1fr}#graph{height:520px!important}.lens-bar + #lens-caption{margin:8px 0}.lens-bar + #lens-caption + #insights{margin-top:8px}}@media(prefers-color-scheme:dark){:root{--ink:#edf5ef;--muted:#b7c6ba;--paper:#111815;--panel:#1a2821;--line:#405248;--green:#73d19d;--blue:#8bd1e6;--amber:#f0bd70;--red:#ff9389}}`
|
|
224
360
|
|
|
225
361
|
styles += String.raw`@media(max-width:700px){.map-wrap{overflow:auto;contain:layout paint}#graph{width:720px!important;min-width:720px!important;max-width:none!important}}`
|
|
362
|
+
styles += String.raw`.map-wrap.dense{overflow:auto}.map-wrap.dense #graph{width:2000px!important;max-width:none!important}`
|
|
226
363
|
styles += String.raw`#lens-caption + p.subtle{display:none}`
|
|
227
364
|
|
|
228
365
|
const script = String.raw`
|
|
229
366
|
const data=${'${DATA}'};
|
|
230
367
|
data.entities=data.entities||[];data.relations=data.relations||[];data.diagnostics=data.diagnostics||[];
|
|
231
|
-
|
|
368
|
+
globalThis.__DOC_BRIDGE_DATA__=data;
|
|
369
|
+
const policySuffix=data.requiredRelationKinds===undefined?"Policy: all observed relation kinds require declarations.":data.requiredRelationKinds.length?"Policy: declarations required for "+data.requiredRelationKinds.join(", ")+(data.requiredRelationTargets==="internal"?" between internal entities":"")+".":"Policy: missing relation declarations disabled; stale, conflicting, and unresolved declarations remain checked.";
|
|
232
370
|
const ensurePolicyNote=()=>{const note=document.querySelector("#map-note");if(note&&!note.textContent.includes(policySuffix))note.textContent=(note.textContent+" · "+policySuffix).trim()};
|
|
233
371
|
new MutationObserver(ensurePolicyNote).observe(document.querySelector("#map-note"),{childList:true,characterData:true});
|
|
234
|
-
const lazyChunks=globalThis.__DOC_BRIDGE_LAZY_CHUNKS__||[],loadedChunks=new Set(),loadingChunks=new Map();
|
|
372
|
+
const lazyChunks=globalThis.__DOC_BRIDGE_LAZY_CHUNKS__||[],hasLevelChunks=globalThis.__DOC_BRIDGE_HAS_LEVEL_CHUNKS__===true,loadedChunks=new Set(),loadingChunks=new Map();
|
|
235
373
|
const esc=(value)=>String(value??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll("\"",""").replaceAll("'","'");
|
|
236
|
-
const byId=new Map(),parent=new Map(),relationById=new Map(),packageMembers=new Map(),findingIndex=new Map(),relationFindingIndex=new Map(),relationsByEntity=new Map(),groupEntityIds=new Map(),groupFindingCounts=new Map(),packageCache=new Map(),nodeIssueCache=new Map();
|
|
374
|
+
const byId=new Map(),parent=new Map(),relationById=new Map(),packageMembers=new Map(),findingIndex=new Map(),relationFindingIndex=new Map(),relationsByEntity=new Map(),groupEntityIds=new Map(),groupFindingIndex=new Map(),groupFindingCounts=new Map(),packageCache=new Map(),nodeIssueCache=new Map();let graphCacheKey="",graphCache;
|
|
375
|
+
let activeLevelChunk="";
|
|
237
376
|
const groupById=new Map(Object.entries(data.view?.groups||{}));
|
|
377
|
+
const packageChunkNameById=new Map([...groupById.values()].flatMap((group)=>group.members).filter((id)=>data.entities.find((entity)=>entity.id===id)?.kind==="package").sort().map((id,index)=>[id,data.view?.levelChunks?.packages?.[index]]));
|
|
238
378
|
const addFinding=(index,id,finding)=>{const values=index.get(id)||[];values.push(finding);index.set(id,values)};
|
|
239
379
|
const addRelation=(id,relation)=>{const values=relationsByEntity.get(id)||[];values.push(relation);relationsByEntity.set(id,values)};
|
|
240
|
-
const hydrate=()=>{byId.clear();parent.clear();relationById.clear();packageMembers.clear();findingIndex.clear();relationFindingIndex.clear();relationsByEntity.clear();groupEntityIds.clear();groupFindingCounts.clear();packageCache.clear();nodeIssueCache.clear();data.entities.forEach((entity)=>{byId.set(entity.id,entity);const groupId=data.view?.entityGroup?.[entity.id];if(groupId){const ids=groupEntityIds.get(groupId)||new Set();ids.add(entity.id);groupEntityIds.set(groupId,ids)}});data.relations.forEach((relation)=>{relationById.set(relation.id,relation);addRelation(relation.from,relation);addRelation(relation.to,relation);if(relation.kind==="contains"){if(!parent.has(relation.to))parent.set(relation.to,relation.from);const members=packageMembers.get(relation.from)||new Set();members.add(relation.to);packageMembers.set(relation.from,members)}});data.diagnostics.forEach((finding)=>{(finding.entityIds||[]).forEach((id)=>addFinding(findingIndex,id,finding));(finding.relationIds||[]).forEach((id)=>addFinding(relationFindingIndex,id,finding));const scopes=
|
|
241
|
-
const
|
|
242
|
-
const
|
|
380
|
+
const hydrate=()=>{byId.clear();parent.clear();relationById.clear();packageMembers.clear();findingIndex.clear();relationFindingIndex.clear();relationsByEntity.clear();groupEntityIds.clear();groupFindingIndex.clear();groupFindingCounts.clear();packageCache.clear();nodeIssueCache.clear();data.entities.forEach((entity)=>{byId.set(entity.id,entity);const groupId=data.view?.entityGroup?.[entity.id];if(groupId){const ids=groupEntityIds.get(groupId)||new Set();ids.add(entity.id);groupEntityIds.set(groupId,ids)}});data.relations.forEach((relation)=>{relationById.set(relation.id,relation);addRelation(relation.from,relation);addRelation(relation.to,relation);if(relation.kind==="contains"){if(!parent.has(relation.to))parent.set(relation.to,relation.from);const members=packageMembers.get(relation.from)||new Set();members.add(relation.to);packageMembers.set(relation.from,members)}});data.diagnostics.forEach((finding)=>{(finding.entityIds||[]).forEach((id)=>addFinding(findingIndex,id,finding));(finding.relationIds||[]).forEach((id)=>addFinding(relationFindingIndex,id,finding));const scopes=data.view?.diagnosticGroup?.[finding.id]||[];if(finding.status!=="confirmed")scopes.forEach((scope)=>{addFinding(groupFindingIndex,scope,finding);groupFindingCounts.set(scope,(groupFindingCounts.get(scope)||0)+1)})})};
|
|
381
|
+
const isLevelChunk=(name)=>name.startsWith("chunks/levels-");
|
|
382
|
+
const isDetailChunk=(name)=>name.startsWith("chunks/details-");
|
|
383
|
+
const hasChunk=(name)=>isLevelChunk(name)?activeLevelChunk===name:isDetailChunk(name)?loadedChunks.has(name):!lazyChunks.includes(name)||loadedChunks.has(name);
|
|
384
|
+
const loadChunk=(name)=>{if(hasChunk(name))return Promise.resolve();if(loadingChunks.has(name))return loadingChunks.get(name);const promise=new Promise((resolve,reject)=>{const element=document.createElement("script");element.src=name;element.onload=()=>{try{if(isLevelChunk(name)){const payload=globalThis.__DOC_BRIDGE_LEVEL_PAYLOAD__;if(!payload)throw new Error("Offline report level payload is missing: "+name);data.entities=payload.entities||[];data.relations=payload.relations||[];activeLevelChunk=name;delete globalThis.__DOC_BRIDGE_LEVEL_PAYLOAD__}else if(isDetailChunk(name)){const payload=globalThis.__DOC_BRIDGE_DETAIL_PAYLOAD__;if(!payload)throw new Error("Offline report detail payload is missing: "+name);const entities=new Map((payload.entities||[]).map((entity)=>[entity.id,entity]));const relations=new Map((payload.relations||[]).map((relation)=>[relation.id,relation]));data.entities=data.entities.map((entity)=>entities.get(entity.id)||entity);data.relations=data.relations.map((relation)=>relations.get(relation.id)||relation);delete globalThis.__DOC_BRIDGE_DETAIL_PAYLOAD__}loadedChunks.add(name);hydrate();loadingChunks.delete(name);resolve()}catch(error){loadingChunks.delete(name);reject(error)}};element.onerror=()=>{loadingChunks.delete(name);reject(new Error("Unable to load offline report chunk: "+name))};document.head.append(element)});loadingChunks.set(name,promise);return promise};
|
|
243
385
|
const readHash=()=>new URLSearchParams(location.hash.slice(1));
|
|
244
386
|
const hash=readHash();
|
|
245
387
|
const state={lens:hash.get("lens")||"architecture",level:hash.get("level")||"overview",selected:hash.get("selected"),query:hash.get("query")||"",status:hash.get("status")||"",severity:hash.get("severity")||"",findingGroup:hash.get("findingGroup"),findingPage:Number(hash.get("findingPage")||0)};
|
|
246
388
|
const syncHash=()=>{const params=new URLSearchParams();[["lens",state.lens,"architecture"],["level",state.level,"overview"],["selected",state.selected,""],["query",state.query,""],["status",state.status,""],["severity",state.severity,""],["findingGroup",state.findingGroup,""],["findingPage",state.findingPage,"0"]].forEach(([key,value,defaultValue])=>{if(value&&value!==defaultValue)params.set(key,String(value))});const next=params.toString();try{if(location.hash.slice(1)!==next)history.replaceState(null,"",next?"#"+next:"")}catch{}};
|
|
247
389
|
const rootOf=(id)=>{let current=id;const seen=new Set();while(parent.has(current)&&!seen.has(current)){seen.add(current);current=parent.get(current)}return current};
|
|
248
390
|
const packageOf=(id)=>{if(packageCache.has(id))return packageCache.get(id);let current=id;const seen=new Set();while(current&&!seen.has(current)){seen.add(current);if(byId.get(current)?.kind==="package"){packageCache.set(id,current);return current}current=parent.get(current)}packageCache.set(id,id);return id};
|
|
249
|
-
const groupFor=(id)=>data.view?.entityGroup?.[id]||id;
|
|
391
|
+
const groupFor=(id)=>{const direct=data.view?.entityGroup?.[id];if(direct)return direct;const entity=byId.get(id);return entity?.kind==="module"||entity?.kind==="file"?groupFor(packageOf(id)):id};
|
|
250
392
|
const packageNodes=()=>data.entities.filter((entity)=>entity.kind==="package");
|
|
251
393
|
const viewLabels={architecture:"Architecture",drift:"Insights",risks:"Findings",evidence:"Coverage"};
|
|
252
394
|
const ensureReportChrome=()=>{document.querySelectorAll(".tab[data-lens]").forEach((tab)=>{tab.textContent=viewLabels[tab.dataset.lens]||tab.dataset.lens;tab.closest(".tabs")?.classList.add("report-tabs")});let breadcrumbs=document.querySelector("#breadcrumbs");if(!breadcrumbs){breadcrumbs=document.createElement("nav");breadcrumbs.id="breadcrumbs";breadcrumbs.className="breadcrumbs";breadcrumbs.setAttribute("aria-label","Report location");document.querySelector(".workspace")?.before(breadcrumbs)}return breadcrumbs};
|
|
253
395
|
const label=(entity)=>entity?.name||entity?.id||"Unknown";
|
|
254
396
|
const short=(value,max)=>{const text=String(value??""),limit=text==="External dependencies"?16:max;return text.length>limit?text.slice(0,limit-1)+"…":text};
|
|
255
|
-
const renderBreadcrumbs=()=>{const breadcrumbs=ensureReportChrome(),items=[{label:"Repository",selected:"",level:"overview"}];if(state.selected){const group=groupById.get(state.selected),entity=byId.get(state.selected);if(group)items.push({label:group.name,selected:group.id,level:"package"});else if(entity){const packageId=packageOf(entity.id),packageEntity=byId.get(packageId),groupId=groupFor(packageId);if(
|
|
397
|
+
const renderBreadcrumbs=()=>{const breadcrumbs=ensureReportChrome(),items=[{label:"Repository",selected:"",level:"overview"}];if(state.selected){const group=groupById.get(state.selected),entity=byId.get(state.selected);if(group)items.push({label:group.name,selected:group.id,level:"package"});else if(entity){const packageId=packageOf(entity.id),packageEntity=byId.get(packageId),groupId=groupFor(packageId),group=groupById.get(groupId);if(group&&groupId!==packageId&&group.name!==label(packageEntity))items.push({label:group.name,selected:groupId,level:"package"});if(packageEntity&&packageEntity.id!==entity.id)items.push({label:label(packageEntity),selected:packageEntity.id,level:"module"});items.push({label:label(entity),selected:entity.id,level:state.level})}}breadcrumbs.innerHTML=items.map((item,index)=>{const separator="<span aria-hidden=\"true\">"+(index?" / ":"")+"</span>";if(index===items.length-1)return separator+"<span class=\"current\">"+esc(item.label)+"</span>";return separator+"<button type=\"button\" data-breadcrumb-selected=\""+esc(item.selected)+"\" data-breadcrumb-level=\""+esc(item.level)+"\">"+esc(item.label)+"</button>"}).join("")};
|
|
256
398
|
const degreeMap=(relations)=>{const degrees=new Map();relations.forEach((relation)=>{degrees.set(relation.from,(degrees.get(relation.from)||0)+1);degrees.set(relation.to,(degrees.get(relation.to)||0)+1)});return degrees};
|
|
257
|
-
const relationHealth=(relation)=>{if(!relation)return "";const findings=
|
|
258
|
-
const nodeIssues=(nodeId)=>{if(nodeIssueCache.has(nodeId))return nodeIssueCache.get(nodeId);const ids=groupEntityIds.get(nodeId)||new Set([nodeId]),findings=new Set(),relations=new Set();for(const id of ids){(findingIndex.get(id)||[]).forEach((finding)=>findings.add(finding));(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation))}relations.forEach((relation)=>(relationFindingIndex.get(relation.id)||[]).forEach((finding)=>findings.add(finding)));const result=[...findings];nodeIssueCache.set(nodeId,result);return result};
|
|
399
|
+
const relationHealth=(relation)=>{if(!relation)return "";const findings=relationFindings(relation.id);return findings.some((finding)=>finding.severity==="error")?"error":findings.length?"warn":""};
|
|
400
|
+
const nodeIssues=(nodeId)=>{if(nodeIssueCache.has(nodeId))return nodeIssueCache.get(nodeId);const ids=groupEntityIds.get(nodeId)||new Set([nodeId]),findings=new Set(groupFindingIndex.get(nodeId)||[]),relations=new Set();for(const id of ids){(findingIndex.get(id)||[]).filter((finding)=>finding.status!=="confirmed").forEach((finding)=>findings.add(finding));(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation))}relations.forEach((relation)=>(relationFindingIndex.get(relation.id)||[]).filter((finding)=>finding.status!=="confirmed").forEach((finding)=>findings.add(finding)));const result=[...findings];nodeIssueCache.set(nodeId,result);return result};
|
|
259
401
|
const findingInLens=(finding)=>state.lens==="architecture"||(state.lens==="drift"&&finding.status!=="confirmed")||(state.lens==="risks"&&(finding.severity==="error"||finding.severity==="warn"))||(state.lens==="evidence"&&finding.evidence.length>0);
|
|
260
|
-
const
|
|
402
|
+
const relationFindings=(id)=>relationFindingIndex.get(id)||data.view?.diagnosticRelationFindings?.[id]||[];
|
|
403
|
+
const relationInLens=(id)=>relationFindings(id).some(findingInLens);
|
|
261
404
|
const externalFor=(ids)=>{const externalIds=new Set();for(const id of ids)for(const relation of relationsByEntity.get(id)||[]){const other=relation.from===id?relation.to:relation.from;if(byId.get(other)?.kind==="external")externalIds.add(other)}return [...externalIds].map((id)=>byId.get(id)).filter(Boolean)};
|
|
405
|
+
const levelChunkName=()=>{if(state.selected&&groupById.has(state.selected))return data.view?.levelChunks?.groups?.[state.selected]||data.view?.levelChunks?.default;const selected=state.selected?byId.get(state.selected):null;const packageId=selected?.kind==="package"?selected.id:selected?packageOf(selected.id):state.selected?.startsWith("package:")?state.selected:null;const packageChunk=packageId&&packageChunkNameById.get(packageId);if(packageChunk)return packageChunk;const selectedGroup=state.selected?data.view?.entityGroup?.[state.selected]:null;if(selectedGroup&&data.view?.levelChunks?.groups?.[selectedGroup])return data.view.levelChunks.groups[selectedGroup];return data.view?.levelChunks?.default};
|
|
406
|
+
const detailChunkName=()=>{const entity=state.selected?byId.get(state.selected):null;const packageId=entity?.kind==="package"?entity.id:entity?packageOf(entity.id):null;const levelName=packageId&&packageChunkNameById.get(packageId);return levelName?.replace("chunks/levels-package-","chunks/details-")||null};
|
|
262
407
|
const nodesFor=()=>{if(state.level==="overview")return data.view?.overview?.nodes||[];const packages=packageNodes(),selectedEntity=state.selected?byId.get(state.selected):null,groupScope=state.selected&&groupById.has(state.selected)?state.selected:null,packageScope=selectedEntity?.kind==="package"?selectedEntity.id:state.selected&&!groupScope?packageOf(state.selected):null;if(state.level==="package"&&!state.selected){const packageIds=new Set(packages.map((node)=>node.id));return packages.concat(state.lens==="architecture"?[]:externalFor(packageIds))}if((state.level==="module"||state.level==="file")&&!state.selected)return[];let nodes=data.entities.filter((entity)=>{if(state.level==="package")return entity.kind==="package"&&(!groupScope||groupFor(entity.id)===groupScope)&&(!packageScope||entity.id===packageScope);if(state.level==="module")return entity.kind==="module"&&(groupScope?groupFor(entity.id)===groupScope:packageOf(entity.id)===packageScope);return Boolean(entity.path)&&(groupScope?groupFor(entity.id)===groupScope:packageOf(entity.id)===packageScope)});if(state.level==="module"&&selectedEntity?.kind==="module"){const neighborhood=new Set([selectedEntity.id]);(relationsByEntity.get(selectedEntity.id)||[]).forEach((relation)=>{if(!["imports","re-exports"].includes(relation.kind))return;if(relation.from===selectedEntity.id)neighborhood.add(relation.to);if(relation.to===selectedEntity.id)neighborhood.add(relation.from)});nodes=data.entities.filter((entity)=>entity.kind==="module"&&neighborhood.has(entity.id))}const scopedIds=new Set(nodes.map((node)=>node.id)),external=state.lens==="architecture"?[]:externalFor(scopedIds),degrees=degreeMap(data.relations);return nodes.concat(external).sort((a,b)=>(degrees.get(b.id)||0)-(degrees.get(a.id)||0)||a.id.localeCompare(b.id)).slice(0,state.level==="file"?80:60)};
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const
|
|
408
|
+
const graphModelUncached=()=>{if(state.level==="overview"){const overviewNodes=data.view?.overview?.nodes||[],overviewNodeIds=new Set(overviewNodes.map((node)=>node.id)),base=(data.view?.overview?.edges||[]).filter((edge)=>overviewNodeIds.has(edge.from)&&overviewNodeIds.has(edge.to)),edges=state.lens==="architecture"?base:base.filter((edge)=>edge.relationIds.some(relationInLens)),visible=new Set(edges.flatMap((edge)=>[edge.from,edge.to]));return{nodes:state.lens==="architecture"?overviewNodes:overviewNodes.filter((node)=>visible.has(node.id)||nodeIssues(node.id).some((finding)=>findingInLens(finding))),edges:edges.map((edge)=>({...edge,health:edge.relationIds.some((id)=>relationHealth(relationById.get(id)))?"error":""}))}}const nodes=nodesFor(),ids=new Set(nodes.map((node)=>node.id)),aggregate=state.level==="package"&&(!state.selected||state.selected.startsWith("group:")),edges=new Map();data.relations.forEach((relation)=>{const essential=state.level==="package"?relation.kind==="depends-on":relation.kind==="imports"||relation.kind==="re-exports";if(relation.kind==="contains"|| (state.lens==="architecture"&&!essential)||(state.lens!=="architecture"&&!relationInLens(relation.id)))return;const from=aggregate?packageOf(relation.from):relation.from,to=aggregate?packageOf(relation.to):relation.to;if(from===to||!ids.has(from)||!ids.has(to))return;const key=from+"→"+to,current=edges.get(key)||{from,to,count:0,kinds:new Set(),relationIds:new Set(),health:""};current.count++;current.kinds.add(relation.kind);current.relationIds.add(relation.id);current.health=current.health==="error"||relationHealth(relation)==="error"?"error":current.health||relationHealth(relation);edges.set(key,current)});const visibleEdges=[...edges.values()].sort((a,b)=>b.count-a.count||a.from.localeCompare(b.from)||a.to.localeCompare(b.to));const visible=new Set(visibleEdges.flatMap((edge)=>[edge.from,edge.to]));return{nodes:state.lens==="architecture"?nodes:nodes.filter((node)=>visible.has(node.id)||nodeIssues(node.id).some((finding)=>findingInLens(finding))),edges:visibleEdges}};
|
|
409
|
+
const graphModel=()=>{const key=state.lens+"|"+state.level+"|"+(state.selected||"");if(graphCacheKey===key&&graphCache)return graphCache;graphCacheKey=key;return graphCache=graphModelUncached()};
|
|
410
|
+
const renderInsights=()=>{const summary=data.diagnosticSummary||{},documentation=data.documentation||{},classificationCounts=documentation.documentClassificationCounts||{},documentedCounts=documentation.documentedDocumentClassificationCounts||{},agentDocs=classificationCounts.agent??0,documentedAgentDocs=documentedCounts.agent??0,undocumented=summary.undocumented??data.diagnostics.filter((finding)=>finding.status==="undocumented").length,drift=summary.drift??data.diagnostics.filter((finding)=>finding.status==="stale-or-unverified"||finding.status==="conflict").length,unsupported=(data.coverage||[]).filter((entry)=>entry.status==="not-analyzed"||entry.status==="partial").length,model=graphModel(),degrees=degreeMap(model.edges),values=[...degrees.values()].sort((a,b)=>a-b),median=values.length?values[Math.floor(values.length/2)]:0,hot=[...degrees.values()].filter((value)=>value>=Math.max(4,median*2)).length,isolated=model.nodes.filter((node)=>!model.edges.some((edge)=>edge.from===node.id||edge.to===node.id)).length;document.querySelector("#insights").innerHTML=[["Documentation drift",undocumented+drift,"Relations or docs needing comparison.",""],["Agent documentation",documentedAgentDocs+"/"+agentDocs,"Configured agent-corpus documents linked to knowledge.",""],["Unanalyzed scope",unsupported,"Coverage gaps are explicit.",""],["Connectivity hotspots",hot,"Heuristic: unusually connected nodes.","heuristic"],["Disconnected nodes",isolated,"Heuristic: no visible edge at this level.","heuristic"]].map(([title,count,copy,tag])=>"<article class=\"insight\"><h3>"+esc(title)+" <span class=\"tag "+tag+"\">"+(tag?"heuristic":"signal")+"</span></h3><p><strong>"+count+"</strong> · "+esc(copy)+"</p></article>").join("")};
|
|
411
|
+
const renderInsightsDashboard=()=>{let dashboard=document.querySelector("#report-dashboard");if(!dashboard){dashboard=document.createElement("section");dashboard.id="report-dashboard";dashboard.className="report-dashboard";document.querySelector(".filters")?.before(dashboard)}const actionable=data.diagnostics.filter((finding)=>finding.status!=="confirmed");if(!actionable.length){dashboard.innerHTML="<p class=\"empty\">No actionable findings. Confirmed checks are available in Evidence.</p>";return}const degrees=degreeMap(data.relations),groups=[...groupById.entries()].filter(([,group])=>group.kind!=="external"),rows=groups.map(([id,group])=>{const members=groupEntityIds.get(id)||new Set(),modules=group.moduleCount||[...members].filter((entityId)=>byId.get(entityId)?.kind==="module").length;return{name:group.name,packages:group.members.length,modules,findings:groupFindingCounts.get(id)||0}}).sort((a,b)=>b.findings-a.findings||a.name.localeCompare(b.name)),topPackages=[...data.entities].filter((entity)=>entity.kind==="package").map((entity)=>({name:label(entity),degree:degrees.get(entity.id)||0})).sort((a,b)=>b.degree-a.degree||a.name.localeCompare(b.name)).slice(0,10),max=Math.max(1,...topPackages.map((row)=>row.degree));dashboard.innerHTML="<section><div class=\"eyebrow\">Attention by scope</div><h2>Where the map needs a closer look</h2><table class=\"report-table\"><thead><tr><th>Scope</th><th>Packages</th><th>Modules</th><th>Findings</th></tr></thead><tbody>"+rows.map((row)=>"<tr><td>"+esc(row.name)+"</td><td>"+row.packages+"</td><td>"+row.modules+"</td><td><strong>"+row.findings+"</strong></td></tr>").join("")+"</tbody></table></section><section><div class=\"eyebrow\">Connectivity</div><h2>Most connected packages</h2><div class=\"bar-list\">"+topPackages.map((row)=>"<div class=\"bar-item\"><span>"+esc(row.name)+"</span><i><b style=\"width:"+Math.round(row.degree/max*100)+"%\"></b></i><strong>"+row.degree+"</strong></div>").join("")+"</div></section>"};
|
|
266
412
|
const mapState={scale:1,dragging:false,x:0,y:0};
|
|
267
|
-
const applyMapTransform=()=>{const svg=document.querySelector("#graph");if(!svg)return;svg.style.transform="translate("+mapState.x+"px,"+mapState.y+"px) scale("+mapState.scale+")";svg.style.transformOrigin="center center"};
|
|
413
|
+
const applyMapTransform=()=>{const svg=document.querySelector("#graph");if(!svg)return;const dense=svg.closest(".map-wrap")?.classList.contains("dense");svg.setAttribute("preserveAspectRatio",dense?"xMinYMin meet":"xMidYMid meet");svg.style.transform="translate("+mapState.x+"px,"+mapState.y+"px) scale("+mapState.scale+")";svg.style.transformOrigin="center center"};
|
|
268
414
|
const routeGraphEdges=()=>{const svg=document.querySelector("#graph");if(!svg)return;svg.querySelectorAll("line.edge").forEach((line,index)=>{const x1=Number(line.getAttribute("x1")),y1=Number(line.getAttribute("y1")),x2=Number(line.getAttribute("x2")),y2=Number(line.getAttribute("y2")),midX=(x1+x2)/2+((index%5)-2)*14,path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("class",line.getAttribute("class")||"edge");path.setAttribute("fill","none");path.setAttribute("marker-end",line.getAttribute("marker-end")||"url(#arrow)");path.setAttribute("d","M "+x1+" "+y1+" H "+midX+" V "+y2+" H "+x2);const label=line.parentElement?.querySelector("text.edge-label");if(label){label.setAttribute("x",String(midX+3));label.setAttribute("y",String((y1+y2)/2))}line.replaceWith(path)})};
|
|
269
415
|
const enterNode=(id)=>{const group=groupById.get(id),entity=byId.get(id);if(group){state.selected=id;state.level="package"}else if(entity?.kind==="package"){state.selected=id;state.level="module"}else if(entity?.kind==="module"){state.selected=id;state.level="file"}else return;mapState.scale=1;mapState.x=0;mapState.y=0;render()};
|
|
270
416
|
let nodeClickTimer;
|
|
271
417
|
document.addEventListener("dblclick",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);enterNode(node.dataset.node)},true);
|
|
272
|
-
document.addEventListener("click",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);
|
|
418
|
+
document.addEventListener("click",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);nodeClickTimer=setTimeout(async()=>{state.selected=node.dataset.node;render();const chunk=detailChunkName();if(chunk&&!hasChunk(chunk)){await loadChunk(chunk);render()}},380)},true);
|
|
273
419
|
document.addEventListener("click",(event)=>{const crumb=event.target.closest?.("[data-breadcrumb-level]");if(!crumb)return;event.preventDefault();event.stopImmediatePropagation();state.level=crumb.dataset.breadcrumbLevel;state.selected=crumb.dataset.breadcrumbSelected||null;mapState.scale=1;mapState.x=0;mapState.y=0;render()},true);
|
|
274
|
-
const diagnosticsFor=(id)=>findingIndex.get(id)||[];
|
|
420
|
+
const diagnosticsFor=(id)=>(findingIndex.get(id)||[]).filter((finding)=>finding.status!=="confirmed");
|
|
275
421
|
const renderDetails=()=>{const panel=document.querySelector("#details"),entity=state.selected?byId.get(state.selected):null,group=state.selected?groupById.get(state.selected):null;if(group){const members=group.members.map((id)=>byId.get(id)).filter(Boolean),relations=new Set(),ids=groupEntityIds.get(state.selected)||new Set();ids.forEach((id)=>(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation)));const findings=nodeIssues(state.selected),evidence=members.flatMap((member)=>member.evidence||[]).slice(0,8),unit=group.kind==="external"?"dependencies":"packages";panel.innerHTML="<div class=\"eyebrow\">Selected group</div><h2 class=\"detail-title\">"+esc(group.name)+"</h2><span class=\"tag\">"+esc(group.kind)+"</span><p class=\"path\">"+esc(group.path||"Derived from stable package identity and repository structure")+"</p><div class=\"detail-grid\"><div><b>"+members.length+"</b><span>"+unit+"</span></div><div><b>"+relations.size+"</b><span>relations</span></div><div><b>"+findings.length+"</b><span>findings</span></div><div><b>"+evidence.length+"</b><span>evidence items</span></div></div><h3>Members</h3><ul class=\"list\">"+(members.length?members.slice(0,8).map((member)=>"<li>"+esc(label(member))+"</li>").join(""):"<li>No members recorded.</li>")+(members.length>8?"<li class=\"subtle\">+"+(members.length-8)+" more — choose Package level to inspect all.</li>":"")+"</ul>"+(findings.length?"<h3 style=\"margin-top:16px\">Attention</h3><ul class=\"list\">"+findings.slice(0,5).map((finding)=>"<li><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> "+esc(finding.code)+"</li>").join("")+"</ul>":"");return}if(!entity){panel.innerHTML="<p class=\"subtle\">Select a node in the map to inspect its evidence, connectivity, and findings.</p>";return}const relations=relationsByEntity.get(entity.id)||[],incoming=relations.filter((relation)=>relation.to===entity.id),outgoing=relations.filter((relation)=>relation.from===entity.id),findings=diagnosticsFor(entity.id),evidence=[...(entity.evidence||[]),...incoming.flatMap((relation)=>relation.evidence||[]),...outgoing.flatMap((relation)=>relation.evidence||[])].slice(0,8);panel.innerHTML="<div class=\"eyebrow\">Selected entity</div><h2 class=\"detail-title\">"+esc(label(entity))+"</h2><span class=\"tag\">"+esc(entity.kind)+"</span><p class=\"path\">"+esc(entity.path||entity.id)+"</p><div class=\"detail-grid\"><div><b>"+incoming.length+"</b><span>incoming</span></div><div><b>"+outgoing.length+"</b><span>outgoing</span></div><div><b>"+findings.length+"</b><span>findings</span></div><div><b>"+evidence.length+"</b><span>evidence items</span></div></div><h3>Evidence</h3><ul class=\"list\">"+(evidence.length?evidence.map((item)=>"<li>"+esc(item.path+(item.lineStart?":"+item.lineStart:"")+(item.context?" — "+item.context:""))+"</li>").join(""):"<li>No evidence recorded.</li>")+"</ul>"+(findings.length?"<h3 style=\"margin-top:16px\">Attention</h3><ul class=\"list\">"+findings.slice(0,5).map((finding)=>"<li><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> "+esc(finding.code)+"</li>").join("")+"</ul>":"")};
|
|
276
|
-
const renderGraph=()=>{const model=graphModel(),svg=document.querySelector("#graph");if(!model.nodes.length){svg.innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>";return}const ids=new Set(model.nodes.map((node)=>node.id)),incoming=new Map(model.nodes.map((node)=>[node.id,0])),outgoing=new Map(model.nodes.map((node)=>[node.id,[]]));
|
|
422
|
+
const renderGraph=()=>{const model=graphModel(),svg=document.querySelector("#graph"),dense=model.edges.length>64,edges=dense?[...model.edges].sort((a,b)=>(b.health?1:0)-(a.health?1:0)||b.count-a.count||a.from.localeCompare(b.from)||a.to.localeCompare(b.to)).slice(0,64):model.edges;if(!model.nodes.length){svg.innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>";return}const ids=new Set(model.nodes.map((node)=>node.id)),incoming=new Map(model.nodes.map((node)=>[node.id,0])),outgoing=new Map(model.nodes.map((node)=>[node.id,[]]));edges.forEach((edge)=>{if(!ids.has(edge.from)||!ids.has(edge.to))return;incoming.set(edge.to,(incoming.get(edge.to)||0)+1);outgoing.get(edge.from).push(edge.to)});const rank=new Map(),work=[];model.nodes.filter((node)=>(incoming.get(node.id)||0)===0).sort((a,b)=>label(a).localeCompare(label(b))||a.id.localeCompare(b.id)).forEach((node)=>{rank.set(node.id,0);work.push(node)});for(let index=0;index<work.length;index++){const node=work[index];for(const next of outgoing.get(node.id)||[]){const nextRank=Math.max(rank.get(next)||0,(rank.get(node.id)||0)+1);rank.set(next,nextRank);if(!work.some((item)=>item.id===next))work.push(byId.get(next)||{id:next,name:next})}}model.nodes.forEach((node)=>{if(!rank.has(node.id))rank.set(node.id,0)});const columns=new Map();model.nodes.forEach((node)=>{const items=columns.get(rank.get(node.id))||[];items.push(node);columns.set(rank.get(node.id),items)});for(const items of columns.values())items.sort((a,b)=>label(a).localeCompare(label(b))||a.id.localeCompare(b.id));if(dense){columns.clear();model.nodes.forEach((node,index)=>{const column=Math.floor(index/8),items=columns.get(column)||[];items.push(node);columns.set(column,items)})}const maxRows=Math.max(1,...[...columns.values()].map((items)=>items.length)),cellWidth=190,cellHeight=82,pad=28,columnCount=Math.max(1,...columns.keys())+1,width=Math.max(620,columnCount*cellWidth+pad*2,Math.min(1000,svg.clientWidth*1.05)),height=Math.max(520,maxRows*cellHeight+pad*2);svg.setAttribute("viewBox","0 0 "+width+" "+height);svg.style.width=dense?Math.max(1000,width)+"px":"100%";svg.style.minWidth="0";svg.style.height="100%";document.querySelector(".map-wrap")?.classList.toggle("dense",dense);const positions=new Map();for(const [column,items] of columns)items.forEach((node,row)=>positions.set(node.id,{x:pad+column*cellWidth+78,y:pad+row*cellHeight+27}));const defs="<defs><marker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"7\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L0,6 L7,3 z\" fill=\"#789087\"/></marker></defs>",edgeMarkup=edges.map((edge)=>{const from=positions.get(edge.from),to=positions.get(edge.to);if(!from||!to)return "";const midX=(from.x+to.x)/2,midY=(from.y+to.y)/2;return "<g><line class=\"edge "+(edge.health?"alert":"")+"\" x1=\""+from.x+"\" y1=\""+from.y+"\" x2=\""+to.x+"\" y2=\""+to.y+"\" marker-end=\"url(#arrow)\"/><text class=\"edge-label\" x=\""+midX+"\" y=\""+midY+"\">"+esc(edge.count>1?edge.count+"×":"")+"</text></g>"}).join(""),nodes=model.nodes.map((node)=>{const pos=positions.get(node.id),issues=nodeIssues(node.id),degree=degreeMap(edges).get(node.id)||0,selected=state.selected===node.id?" selected":"",issue=issues.length?" issue":"",nodeLabel=label(node),nodeKind=node.kind==="domain"?(node.memberCount||0)+" packages":node.kind;return "<g class=\"graph-node"+selected+issue+"\" transform=\"translate("+(pos.x-78)+","+(pos.y-27)+")\"><title>"+esc(nodeLabel)+" · "+esc(nodeKind)+"</title><rect width=\"156\" height=\"54\" rx=\"8\" role=\"button\" tabindex=\"0\" aria-label=\""+esc(nodeLabel+" "+nodeKind)+"\" data-node=\""+esc(node.id)+"\"></rect><text class=\"node-label\" x=\"10\" y=\"19\">"+esc(short(nodeLabel,22))+"</text><text class=\"node-kind\" x=\"10\" y=\"34\">"+esc(short(nodeKind,22))+"</text><text class=\"node-count\" x=\"144\" y=\"19\" text-anchor=\"end\">"+(degree||"")+"</text></g>"}).join("");svg.innerHTML=defs+edgeMarkup+nodes;routeGraphEdges();applyMapTransform();document.querySelector("#map-note").textContent=(state.level==="overview"?"Bounded domain view. ":state.level+" view. ")+"Directional edges are aggregated from canonical relations; "+model.edges.length+" visible connection groups"+(dense?" (showing "+edges.length+" prioritized of "+model.edges.length+").":".");};
|
|
277
423
|
const findingMatches=(finding)=>{const query=state.query.toLowerCase();return(!query||[finding.id,finding.code,finding.message,...finding.entityIds,...finding.relationIds].join(" ").toLowerCase().includes(query))&&(!state.status||finding.status===state.status)&&(!state.severity||finding.severity===state.severity)};
|
|
278
|
-
const findingScope=(finding)=>{const ids=[...(finding.entityIds||[])];(finding.relationIds||[]).forEach((id)=>{const relation=relationById.get(id);if(relation)ids.push(relation.from,relation.to)});return ids.map(groupFor).sort()[0]||"repository"};
|
|
424
|
+
const findingScope=(finding)=>data.view?.diagnosticGroup?.[finding.id]?.[0]||(()=>{const ids=[...(finding.entityIds||[])];(finding.relationIds||[]).forEach((id)=>{const relation=relationById.get(id);if(relation)ids.push(relation.from,relation.to)});return ids.map(groupFor).sort()[0]||"repository"})();
|
|
279
425
|
const findingGroups=(findings)=>{const groups=new Map();findings.forEach((finding)=>{const scope=findingScope(finding),key=[scope,finding.code,finding.status,finding.severity].join("|"),group=groups.get(key)||{key,scope,code:finding.code,status:finding.status,severity:finding.severity,findings:[]};group.findings.push(finding);groups.set(key,group)});return[...groups.values()].sort((left,right)=>right.findings.length-left.findings.length||left.code.localeCompare(right.code)||left.key.localeCompare(right.key))};
|
|
280
426
|
const renderFinding=(finding)=>"<article class=\"finding\" id=\""+esc("diagnostic-"+finding.id.replace(/[^A-Za-z0-9_-]+/g,"-"))+"\"><div class=\"finding-head\"><h3>"+esc(finding.code)+"</h3><span><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> <span class=\"tag\">"+esc(finding.status)+"</span></span></div><p>"+esc(finding.message)+"</p>"+(finding.evidence.length?"<ul>"+finding.evidence.slice(0,4).map((item)=>"<li>"+esc(item.path+(item.lineStart?":"+item.lineStart:"")+(item.context?" — "+item.context:""))+"</li>").join("")+"</ul>":"")+(finding.remediation?"<p><strong>Next check:</strong> "+esc(finding.remediation)+"</p>":"")+"</article>";
|
|
281
|
-
const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+"
|
|
427
|
+
const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const itemLabel=state.lens==="evidence"?"evidence checks":"findings",groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+" "+itemLabel+" · "+groups.length+" groups";document.querySelector("#findings").innerHTML=findings.length?"<div class=\"finding-groups\">"+groups.map((group)=>"<article class=\"finding-group\"><div class=\"finding-head\"><h3>"+esc(group.code)+"</h3><span><span class=\"tag "+esc(group.severity)+"\">"+esc(group.severity)+"</span> <span class=\"tag\">"+esc(group.status)+"</span></span></div><p><strong>"+group.findings.length+"</strong> "+itemLabel+" · "+esc(groupById.get(group.scope)?.name||group.scope)+"</p><p>"+esc(group.findings[0].message)+"</p><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(group.key)+"\" aria-expanded=\""+String(selected?.key===group.key)+"\">Inspect group</button></article>").join("")+"</div>"+(selected?"<div class=\"finding-detail\"><div class=\"finding-head\"><h3>"+esc(selected.code)+" · "+esc(groupById.get(selected.scope)?.name||selected.scope)+"</h3><span class=\"subtle\">"+selected.findings.length+" "+itemLabel+"</span></div>"+selected.findings.slice(start,start+pageSize).map(renderFinding).join("")+"<div class=\"finding-actions\"><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page-1)+"\" "+(page===0?"disabled":"")+">Previous</button><span class=\"subtle\">Showing "+(start+1)+"–"+Math.min(start+pageSize,selected.findings.length)+" of "+selected.findings.length+" · page "+(page+1)+"/"+pageCount+"</span><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page+1)+"\" "+(page+1>=pageCount?"disabled":"")+">Next</button></div></div>":""):"<p class=\"empty\">No "+itemLabel+" match the current lens and filters.</p>"};
|
|
282
428
|
const renderCoverage=()=>{document.querySelector("#coverage-list").innerHTML=(data.coverage||[]).map((entry)=>{const width=entry.status==="complete"?100:entry.status==="partial"?55:12;return"<div class=\"coverage-row\"><div><b>"+esc(entry.analyzer)+"</b><br><span class=\"subtle\">"+esc(entry.scope)+"</span></div><div class=\"bar\"><i class=\""+(entry.status==="complete"?"":entry.status==="partial"?"partial":"none")+"\" style=\"width:"+width+"%\"></i></div><div>"+esc(entry.status)+"</div></div>"}).join("")||"<p class=\"empty\">No coverage metadata.</p>"};
|
|
283
|
-
const deferFindings=()=>{if(findingsLoaded())return;const
|
|
429
|
+
const deferFindings=()=>{if(findingsLoaded())return;const actionable=data.actionableCount??data.diagnosticCount??0,confirmed=data.confirmedCount??0;document.querySelector("#finding-count").textContent=actionable+" actionable findings · "+confirmed+" confirmed checks available on demand";document.querySelector("#findings").innerHTML="<div class=\"empty\"><p>Findings stay out of the first paint so large repositories remain responsive. Confirmed checks are evidence, not issues.</p><button id=\"load-findings\" class=\"tab\" type=\"button\">Load findings</button></div>"};
|
|
284
430
|
const scopePrompt=()=>{if((state.level==="module"||state.level==="file")&&!state.selected){document.querySelector("#map-note").textContent="Select an app or package to inspect this level.";document.querySelector("#graph").innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>"}};
|
|
285
431
|
const findingsLoaded=()=>!lazyChunks.includes("chunks/findings.js")||loadedChunks.has("chunks/findings.js");
|
|
286
|
-
const levelsLoaded=()=>!
|
|
432
|
+
const levelsLoaded=()=>!hasLevelChunks||state.level==="overview"||activeLevelChunk===levelChunkName();
|
|
287
433
|
const setReportView=()=>{document.body.dataset.reportView={architecture:"architecture",drift:"insights",risks:"findings",evidence:"coverage"}[state.lens]||"architecture";ensureReportChrome()};
|
|
288
|
-
const
|
|
289
|
-
document.
|
|
290
|
-
document.addEventListener("click",(event)=>{const
|
|
434
|
+
const measureRender=(name,renderFn)=>{const started=performance.now(),value=renderFn(),timings=JSON.parse(document.documentElement.dataset.docBridgeRenderTimings||"{}");timings[name]=Math.round(performance.now()-started);document.documentElement.dataset.docBridgeRenderTimings=JSON.stringify(timings);return value};
|
|
435
|
+
const render=()=>{if(state.level!=="overview"&&!levelsLoaded()){document.querySelector("#map-note").textContent="Loading canonical entities…";loadChunk(levelChunkName()).then(render).catch((error)=>{document.querySelector("#map-note").textContent=error.message});return}if(state.lens!=="architecture"&&!findingsLoaded()){document.querySelector("#lens-caption").textContent="Loading findings for this lens…";loadChunk("chunks/findings.js").then(render).catch((error)=>{document.querySelector("#lens-caption").textContent=error.message});return}syncHash();setReportView();renderBreadcrumbs();document.querySelectorAll(".tab[data-lens]").forEach((tab)=>tab.setAttribute("aria-selected",String(tab.dataset.lens===state.lens)));document.querySelectorAll(".level").forEach((button)=>button.setAttribute("aria-pressed",String(button.dataset.level===state.level)));document.querySelector("#lens-caption").textContent=state.lens==="architecture"?"The repository topology at the selected level.":state.lens==="drift"?"Where canonical code relations and documentation declarations need attention.":state.lens==="risks"?"Signals that deserve human review; connectivity warnings are heuristics, not architectural proof.":"What the analyzers observed, declared, or could not analyze.";document.documentElement.dataset.docBridgeRenderTimings="{}";measureRender("insights",renderInsights);if(state.lens!=="architecture")measureRender("insightsDashboard",renderInsightsDashboard);measureRender("graph",renderGraph);measureRender("details",renderDetails);if(state.lens!=="architecture")measureRender("findings",renderFindings);else document.querySelector("#findings")?.replaceChildren();deferFindings();measureRender("coverage",renderCoverage)};
|
|
436
|
+
document.addEventListener("click",async(event)=>{const target=event.target.closest?.("[data-level],[data-lens],#load-findings");if(!target)return;if(target.id==="load-findings"){event.preventDefault();event.stopImmediatePropagation();await loadChunk("chunks/findings.js");render();return}if(target.dataset.level&&!levelsLoaded()){event.preventDefault();event.stopImmediatePropagation();state.level=target.dataset.level;await loadChunk(levelChunkName());render();return}if(target.dataset.lens&&target.dataset.lens!=="architecture"&&!findingsLoaded()){event.preventDefault();event.stopImmediatePropagation();state.lens=target.dataset.lens;state.findingGroup=null;await loadChunk("chunks/findings.js");render()}},true);
|
|
291
437
|
document.addEventListener("click",(event)=>{const crumb=event.target.closest?.("[data-breadcrumb-level]");if(!crumb)return;event.preventDefault();event.stopImmediatePropagation();state.level=crumb.dataset.breadcrumbLevel;state.selected=crumb.dataset.breadcrumbSelected||null;mapState.scale=1;mapState.x=0;mapState.y=0;render()},true);
|
|
292
438
|
document.addEventListener("input",async(event)=>{const target=event.target;if(target?.id!=="search"||findingsLoaded())return;event.stopImmediatePropagation();state.query=target.value;state.findingGroup=null;await loadChunk("chunks/findings.js");render()},true);
|
|
293
439
|
document.addEventListener("change",async(event)=>{const target=event.target;if(!["status","severity"].includes(target?.id)||findingsLoaded())return;event.stopImmediatePropagation();state[target.id]=target.value;state.findingGroup=null;await loadChunk("chunks/findings.js");render()},true);
|
|
@@ -319,20 +465,21 @@ type InternalOfflineReportOptions = OfflineReportOptions & {
|
|
|
319
465
|
const render = (input: OfflineReportInput, options: InternalOfflineReportOptions): string => {
|
|
320
466
|
const { snapshot, report } = input
|
|
321
467
|
const includeSnippets = options.includeSnippets === true
|
|
322
|
-
const data = reportData(snapshot, report, includeSnippets)
|
|
468
|
+
const data = reportData(snapshot, report, includeSnippets, options.privacy)
|
|
323
469
|
const scriptBody = script.replace('${' + 'DATA}', options.dataExpression ?? embeddedJson(data))
|
|
324
470
|
const largeNote = snapshot.entities.length > 500 || report.diagnostics.length > 1_000
|
|
325
471
|
? 'Large snapshots are rendered from compact canonical data with progressive graph levels.'
|
|
326
472
|
: 'The viewer starts with a grouped topology and expands into canonical entities on demand.'
|
|
327
|
-
const
|
|
473
|
+
const actionableCount = report.diagnostics.filter((diagnostic) => diagnostic.status !== 'confirmed').length
|
|
474
|
+
const confirmedCount = report.diagnostics.filter((diagnostic) => diagnostic.status === 'confirmed').length
|
|
328
475
|
const unsupportedCount = snapshot.coverage.filter((entry) => entry.status !== 'complete').length
|
|
329
476
|
const policyNote = report.summary.requiredRelationKinds === undefined
|
|
330
477
|
? 'Missing relation declarations are checked for every observed relation kind.'
|
|
331
478
|
: report.summary.requiredRelationKinds.length
|
|
332
|
-
? `Missing relation declarations are checked for: ${report.summary.requiredRelationKinds.join(', ')}.`
|
|
479
|
+
? `Missing relation declarations are checked for: ${report.summary.requiredRelationKinds.join(', ')}${report.summary.requiredRelationTargets === 'internal' ? ' between internal entities' : ''}.`
|
|
333
480
|
: 'Missing relation declarations are disabled by configuration; stale, conflicting, and unresolved declarations remain checked.'
|
|
334
481
|
const statusOptions = ['confirmed', 'undocumented', 'stale-or-unverified', 'conflict', 'unresolved', 'not-analyzed']
|
|
335
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Doc Bridge — ${escapeHtml(
|
|
482
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Doc Bridge — ${escapeHtml(data.project.name)}</title><style>${styles}</style></head><body>${options.dataScripts ?? ''}<main class="shell"><header class="masthead"><div><div class="eyebrow">Doc Bridge / Knowledge report</div><h1>${escapeHtml(data.project.name)}</h1><p class="lede">A read-only architecture and documentation map. Start broad, then follow evidence to the exact entity, relation, or finding.</p></div><div class="run-meta"><span class="read-only">Read-only snapshot</span><strong>${escapeHtml(data.revision)}</strong><span>${escapeHtml(data.revisionKind)} · pipeline ${escapeHtml(data.pipelineVersion)}</span></div></header><section class="summary" aria-label="Snapshot summary"><div class="metric"><b>${snapshot.entities.length}</b><span>entities</span></div><div class="metric"><b>${snapshot.relations.length}</b><span>canonical relations</span></div><div class="metric ${actionableCount?'warn':''}"><b>${actionableCount}</b><span>actionable findings</span></div><div class="metric"><b>${confirmedCount}</b><span>confirmed checks</span></div><div class="metric ${unsupportedCount?'bad':''}"><b>${unsupportedCount}</b><span>partial / unanalyzed scopes</span></div></section><div class="lens-bar"><nav class="tabs" aria-label="Report lenses"><button class="tab" data-lens="architecture" aria-selected="true">Architecture</button><button class="tab" data-lens="drift" aria-selected="false">Documentation drift</button><button class="tab" data-lens="risks" aria-selected="false">Risks & hotspots</button><button class="tab" data-lens="evidence" aria-selected="false">Evidence</button></nav><span class="subtle">${escapeHtml(largeNote)}</span></div><p id="lens-caption" class="subtle">The repository topology at the selected level.</p><p class="subtle">${escapeHtml(policyNote)}</p><section id="insights" class="insights" aria-label="Attention signals"></section><section class="filters" aria-label="Finding filters"><label>Search <input id="search" type="search" placeholder="Press / to search findings"></label><label>Status <select id="status"><option value="">Any status</option>${statusOptions.map((value) => `<option>${escapeHtml(value)}</option>`).join('')}</select></label><label>Severity <select id="severity"><option value="">Any severity</option><option>error</option><option>warn</option><option>info</option></select></label><button id="reset" type="button">Reset filters</button></section><div class="workspace"><section class="panel" aria-labelledby="architecture-title"><div class="panel-head"><div><h2 id="architecture-title">Architecture map</h2><p id="map-note" class="subtle">Grouped package/external view.</p></div><div class="tabs" aria-label="Graph level"><button class="level" data-level="overview" aria-pressed="true">Overview</button><button class="level" data-level="package" aria-pressed="false">Package</button><button class="level" data-level="module" aria-pressed="false">Module</button><button class="level" data-level="file" aria-pressed="false">File</button></div></div><div class="map-wrap"><svg id="graph" viewBox="0 0 1000 560" role="img" aria-label="Interactive architecture graph"></svg></div><p class="subtle">Node number = visible connection degree. Amber nodes/edges have actionable findings. Click or focus a node to inspect its evidence, connectivity, and findings. Grouped edges preserve canonical relation direction and count.</p></section><aside class="panel side" aria-labelledby="details-title"><div class="panel-head"><div><div class="eyebrow">Evidence trail</div><h2 id="details-title">Details</h2></div><button id="clear-selection" class="tab" type="button">Clear</button></div><div id="details"><p class="subtle">Select a node in the map to inspect its evidence, connectivity, and findings.</p></div></aside></div><section class="panel run" aria-labelledby="run-title"><div class="panel-head"><div><div class="eyebrow">Jest-like diagnostics</div><h2 id="run-title">Run report</h2></div><span id="finding-count" class="subtle">${actionableCount} actionable findings · ${confirmedCount} confirmed checks</span></div><div class="run-summary"><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === 'error').length}</b> errors</span><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === 'warn').length}</b> warnings</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === 'undocumented').length}</b> undocumented</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === 'stale-or-unverified' || item.status === 'conflict').length}</b> drift/conflict</span><span class="chip"><b>${confirmedCount}</b> confirmed checks</span></div><div id="findings"></div></section><section class="panel run" aria-labelledby="coverage-title"><div class="panel-head"><div><div class="eyebrow">Analyzer boundaries</div><h2 id="coverage-title">Coverage & unsupported areas</h2></div><span class="subtle">Explicit limits are part of the evidence</span></div><div id="coverage-list"></div></section><section class="metadata" aria-label="Run metadata"><div><b>Snapshot</b>${escapeHtml(data.snapshotHash)}</div><div><b>Report</b>${escapeHtml(data.reportHash)}</div><div><b>Configuration</b>${escapeHtml(data.configurationHash)}</div><div><b>Analyzers</b>${escapeHtml(Object.entries(data.analyzerVersions).map(([name, version]) => `${name} ${version}`).join(', '))}</div><div><b>Source revision</b>${escapeHtml(data.revision)} (${escapeHtml(data.revisionKind)})</div><div><b>Mode</b>${options.privacy === 'anonymized' ? 'Anonymized read-only browser viewer; evidence paths and project identity are redacted.' : 'Read-only browser viewer; approvals and fixes remain outside this artifact.'}</div></section></main><script>${scriptBody}</script></body></html>`
|
|
336
483
|
}
|
|
337
484
|
|
|
338
485
|
const parseReportInput = (input: unknown): OfflineReportInput => {
|
|
@@ -345,6 +492,8 @@ const parseReportInput = (input: unknown): OfflineReportInput => {
|
|
|
345
492
|
}
|
|
346
493
|
|
|
347
494
|
const chunkScript = (payload: unknown): string => `globalThis.__DOC_BRIDGE_DATA__=Object.assign(globalThis.__DOC_BRIDGE_DATA__||{},${embeddedJson(payload)});`
|
|
495
|
+
const levelChunkScript = (payload: unknown): string => `globalThis.__DOC_BRIDGE_LEVEL_PAYLOAD__=${embeddedJson(payload)};`
|
|
496
|
+
const detailChunkScript = (payload: unknown): string => `globalThis.__DOC_BRIDGE_DETAIL_PAYLOAD__=${embeddedJson(payload)};`
|
|
348
497
|
const byteLength = (value: string): number => new TextEncoder().encode(value).byteLength
|
|
349
498
|
|
|
350
499
|
export const renderOfflineReport = (input: unknown, options: OfflineReportOptions = {}): string => {
|
|
@@ -358,24 +507,105 @@ export const renderOfflineReport = (input: unknown, options: OfflineReportOption
|
|
|
358
507
|
export const renderOfflineReportArtifact = (input: unknown, options: OfflineReportOptions & { readonly thresholdBytes?: number } = {}): OfflineReportArtifact => {
|
|
359
508
|
const parsed = parseReportInput(input)
|
|
360
509
|
const thresholdBytes = options.thresholdBytes ?? DEFAULT_LARGE_REPORT_THRESHOLD_BYTES
|
|
361
|
-
const renderOptions = { includeSnippets: options.includeSnippets === true, thresholdBytes }
|
|
510
|
+
const renderOptions = { includeSnippets: options.includeSnippets === true, privacy: options.privacy ?? 'private', thresholdBytes }
|
|
362
511
|
const singleFile = render(parsed, options)
|
|
363
512
|
if (byteLength(singleFile) <= thresholdBytes) {
|
|
364
513
|
const files = { 'index.html': sha256NormalizedV1(singleFile) }
|
|
365
|
-
const manifestPayload = { schemaVersion: 1, mode: 'single-file', generatedBy: '@agentskit/doc-bridge', snapshotHash: parsed.snapshot.contentHash, reportHash: parsed.report.contentHash, configurationHash: parsed.snapshot.configurationHash, sourceRevision: parsed.snapshot.sourceRevision, renderOptions, files }
|
|
514
|
+
const manifestPayload = { schemaVersion: 1, mode: 'single-file', generatedBy: '@agentskit/doc-bridge', snapshotHash: parsed.snapshot.contentHash, reportHash: parsed.report.contentHash, configurationHash: parsed.snapshot.configurationHash, sourceRevision: options.privacy === 'anonymized' ? 'redacted' : parsed.snapshot.sourceRevision, renderOptions, files }
|
|
366
515
|
const manifest = JSON.stringify({ ...manifestPayload, artifactHash: sha256NormalizedV1(manifestPayload) }, null, 2)
|
|
367
516
|
return { mode: 'single-file', indexHtml: singleFile, files: { 'index.html': singleFile }, manifest }
|
|
368
517
|
}
|
|
369
518
|
|
|
370
|
-
const data = reportData(parsed.snapshot, parsed.report, options.includeSnippets === true)
|
|
519
|
+
const data = reportData(parsed.snapshot, parsed.report, options.includeSnippets === true, options.privacy)
|
|
520
|
+
const entitiesById = new Map(data.entities.map((entity) => [entity.id, entity]))
|
|
521
|
+
const packageEntities = data.entities.filter((entity) => entity.kind === 'package')
|
|
522
|
+
const packageIds = new Set(packageEntities.map((entity) => entity.id))
|
|
523
|
+
const packageRelations = data.relations.filter((relation) => packageIds.has(relation.from) && packageIds.has(relation.to))
|
|
524
|
+
const diagnosticGroup = Object.fromEntries(data.diagnostics.map((diagnostic) => {
|
|
525
|
+
const groups = new Set<string>()
|
|
526
|
+
for (const entityId of diagnostic.entityIds ?? []) {
|
|
527
|
+
const groupId = data.view.entityGroup[entityId]
|
|
528
|
+
if (groupId) groups.add(groupId)
|
|
529
|
+
}
|
|
530
|
+
for (const relationId of diagnostic.relationIds ?? []) {
|
|
531
|
+
const relation = data.relations.find((candidate) => candidate.id === relationId)
|
|
532
|
+
if (!relation) continue
|
|
533
|
+
for (const endpoint of [relation.from, relation.to]) {
|
|
534
|
+
const groupId = data.view.entityGroup[endpoint]
|
|
535
|
+
if (groupId) groups.add(groupId)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return [diagnostic.id, [...groups].sort()] as const
|
|
539
|
+
}))
|
|
540
|
+
const diagnosticRelationFindings = new Map<string, { status: string; severity: string }[]>()
|
|
541
|
+
for (const diagnostic of data.diagnostics) {
|
|
542
|
+
for (const relationId of diagnostic.relationIds ?? []) {
|
|
543
|
+
const findings = diagnosticRelationFindings.get(relationId) ?? []
|
|
544
|
+
findings.push({ status: diagnostic.status, severity: diagnostic.severity })
|
|
545
|
+
diagnosticRelationFindings.set(relationId, findings)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const containsRelations = data.relations.filter((relation) => relation.kind === 'contains')
|
|
549
|
+
const collectScope = (roots: readonly string[]): Set<string> => {
|
|
550
|
+
const ids = new Set(roots)
|
|
551
|
+
let changed = true
|
|
552
|
+
while (changed) {
|
|
553
|
+
changed = false
|
|
554
|
+
for (const relation of containsRelations) {
|
|
555
|
+
if (ids.has(relation.from) && !ids.has(relation.to)) {
|
|
556
|
+
ids.add(relation.to)
|
|
557
|
+
changed = true
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return ids
|
|
562
|
+
}
|
|
563
|
+
const scopedPayload = (ids: ReadonlySet<string>) => ({
|
|
564
|
+
entities: data.entities.filter((entity) => ids.has(entity.id)),
|
|
565
|
+
relations: data.relations.filter((relation) => ids.has(relation.from) && ids.has(relation.to)),
|
|
566
|
+
})
|
|
567
|
+
const packagePayload = (ids: ReadonlySet<string>) => ({
|
|
568
|
+
entities: data.entities.filter((entity) => ids.has(entity.id) && entity.kind === 'package'),
|
|
569
|
+
relations: data.relations.filter((relation) => ids.has(relation.from) && ids.has(relation.to) && entitiesById.get(relation.from)?.kind === 'package' && entitiesById.get(relation.to)?.kind === 'package'),
|
|
570
|
+
})
|
|
571
|
+
const compactPayload = (payload: ReturnType<typeof scopedPayload>) => ({
|
|
572
|
+
entities: payload.entities.map(({ evidence: _evidence, anchor: _anchor, provenance: _provenance, ...entity }) => entity),
|
|
573
|
+
relations: payload.relations.map(({ evidence: _evidence, provenance: _provenance, ...relation }) => relation),
|
|
574
|
+
})
|
|
575
|
+
const levelFiles: Record<string, string> = {}
|
|
576
|
+
const detailFiles: Record<string, string> = {}
|
|
577
|
+
for (const [groupId, group] of Object.entries(data.view.groups)) {
|
|
578
|
+
const ids = new Set(group.members.filter((id) => entitiesById.get(id)?.kind === 'package'))
|
|
579
|
+
const chunkName = data.view.levelChunks.groups[groupId]
|
|
580
|
+
if (chunkName) levelFiles[chunkName] = levelChunkScript(packagePayload(ids))
|
|
581
|
+
}
|
|
582
|
+
const packageIdList = packageEntities.map((entity) => entity.id)
|
|
583
|
+
const packageIdSet = new Set(packageIdList)
|
|
584
|
+
levelFiles[data.view.levelChunks.default] = levelChunkScript(scopedPayload(packageIdSet))
|
|
585
|
+
const sortedPackageIds = [...packageIds].sort()
|
|
586
|
+
for (const [index, packageId] of sortedPackageIds.entries()) {
|
|
587
|
+
const chunkName = data.view.levelChunks.packages[index]
|
|
588
|
+
const packageScope = scopedPayload(collectScope([packageId]))
|
|
589
|
+
if (chunkName) levelFiles[chunkName] = levelChunkScript(compactPayload(packageScope))
|
|
590
|
+
const detailName = chunkName?.replace('chunks/levels-package-', 'chunks/details-')
|
|
591
|
+
if (detailName) detailFiles[detailName] = detailChunkScript(packageScope)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const overviewView = {
|
|
595
|
+
...data.view,
|
|
596
|
+
entityGroup: Object.fromEntries(Object.entries(data.view.entityGroup).filter(([id]) => data.entities.find((entity) => entity.id === id)?.kind === 'package')),
|
|
597
|
+
diagnosticGroup,
|
|
598
|
+
diagnosticRelationFindings: Object.fromEntries(diagnosticRelationFindings),
|
|
599
|
+
}
|
|
371
600
|
const files = {
|
|
372
|
-
'chunks/overview.js': chunkScript({ project: data.project, revision: data.revision, revisionKind: data.revisionKind, snapshotHash: data.snapshotHash, reportHash: data.reportHash, configurationHash: data.configurationHash, pipelineVersion: data.pipelineVersion, analyzerVersions: data.analyzerVersions, coverage: data.coverage, diagnosticCount: data.diagnosticCount, diagnosticSummary: data.diagnosticSummary, requiredRelationKinds: data.requiredRelationKinds,
|
|
373
|
-
|
|
601
|
+
'chunks/overview.js': chunkScript({ project: data.project, revision: data.revision, revisionKind: data.revisionKind, snapshotHash: data.snapshotHash, reportHash: data.reportHash, configurationHash: data.configurationHash, pipelineVersion: data.pipelineVersion, analyzerVersions: data.analyzerVersions, coverage: data.coverage, diagnosticCount: data.diagnosticCount, actionableCount: data.actionableCount, confirmedCount: data.confirmedCount, diagnosticSummary: data.diagnosticSummary, documentation: data.documentation, requiredRelationKinds: data.requiredRelationKinds, entities: packageEntities, relations: packageRelations, view: overviewView }),
|
|
602
|
+
...levelFiles,
|
|
603
|
+
...detailFiles,
|
|
374
604
|
'chunks/findings.js': chunkScript({ diagnostics: data.diagnostics }),
|
|
375
605
|
}
|
|
376
|
-
const indexHtml = render(parsed, { ...options, dataExpression: 'globalThis.__DOC_BRIDGE_DATA__', dataScripts: `<script>globalThis.__DOC_BRIDGE_LAZY_CHUNKS__
|
|
606
|
+
const indexHtml = render(parsed, { ...options, dataExpression: 'globalThis.__DOC_BRIDGE_DATA__', dataScripts: `<script>globalThis.__DOC_BRIDGE_HAS_LEVEL_CHUNKS__=true;globalThis.__DOC_BRIDGE_LAZY_CHUNKS__=["chunks/findings.js"]</script><script src="chunks/overview.js"></script>` })
|
|
377
607
|
const fileHashes = Object.fromEntries(Object.entries({ ...files, 'index.html': indexHtml }).map(([file, content]) => [file, sha256NormalizedV1(content)]))
|
|
378
|
-
const manifestPayload = { schemaVersion: 1, mode: 'directory', generatedBy: '@agentskit/doc-bridge', snapshotHash: parsed.snapshot.contentHash, reportHash: parsed.report.contentHash, configurationHash: parsed.snapshot.configurationHash, sourceRevision: parsed.snapshot.sourceRevision, renderOptions, files: fileHashes }
|
|
608
|
+
const manifestPayload = { schemaVersion: 1, mode: 'directory', generatedBy: '@agentskit/doc-bridge', snapshotHash: parsed.snapshot.contentHash, reportHash: parsed.report.contentHash, configurationHash: parsed.snapshot.configurationHash, sourceRevision: options.privacy === 'anonymized' ? 'redacted' : parsed.snapshot.sourceRevision, renderOptions, files: fileHashes }
|
|
379
609
|
const manifest = JSON.stringify({ ...manifestPayload, artifactHash: sha256NormalizedV1(manifestPayload) }, null, 2)
|
|
380
610
|
return { mode: 'directory', indexHtml, files: { ...files, 'index.html': indexHtml }, manifest }
|
|
381
611
|
}
|