@agentskit/doc-bridge 1.6.2 → 1.6.3
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 +6 -0
- package/action.yml +1 -1
- package/bin/ak-verify.js +9 -0
- package/dist/cli/program.js +303 -55
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -1
- package/dist/{index-Di7PkJuf.d.ts → index-DudNuwI5.d.ts} +24 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.js +249 -43
- package/dist/index.js.map +1 -1
- package/docs/knowledge-engine-runbook.md +14 -0
- package/docs/verification-harness.md +36 -0
- package/mcpb/manifest.json +1 -1
- package/package.json +52 -44
- package/scripts/report-visual-check.mjs +239 -0
- package/scripts/verification-harness.mjs +280 -0
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/cli/program.ts +64 -7
- package/src/config/index.ts +2 -0
- package/src/config/schema.ts +9 -0
- package/src/index.ts +8 -1
- package/src/lib/markdown.ts +5 -0
- package/src/reconciliation/reconcile.ts +9 -1
- package/src/report/html.ts +292 -40
- package/src/schemas/knowledge.ts +1 -0
- package/src/version.ts +1 -1
package/src/report/html.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DiscoverySnapshotV1Schema, ReconciliationReportV1Schema, type DiscoverySnapshotV1, type ReconciliationReportV1 } from '../schemas/knowledge.js'
|
|
1
|
+
import { DiscoverySnapshotV1Schema, ReconciliationReportV1Schema, type DiscoverySnapshotV1, type KnowledgeEntity, type ReconciliationReportV1 } from '../schemas/knowledge.js'
|
|
2
|
+
import { sha256NormalizedV1 } from '../index-builder/content-hash.js'
|
|
2
3
|
import { redactSecrets } from '../safety/repository.js'
|
|
3
4
|
|
|
4
5
|
export type OfflineReportInput = {
|
|
@@ -10,6 +11,136 @@ export type OfflineReportOptions = {
|
|
|
10
11
|
readonly includeSnippets?: boolean
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
export type OfflineReportArtifact = {
|
|
15
|
+
readonly mode: 'single-file' | 'directory'
|
|
16
|
+
readonly indexHtml: string
|
|
17
|
+
readonly files: Readonly<Record<string, string>>
|
|
18
|
+
readonly manifest: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_LARGE_REPORT_THRESHOLD_BYTES = 5_000_000
|
|
22
|
+
|
|
23
|
+
type ReportViewNode = {
|
|
24
|
+
readonly id: string
|
|
25
|
+
readonly kind: string
|
|
26
|
+
readonly name: string
|
|
27
|
+
readonly path: string | undefined
|
|
28
|
+
readonly memberCount?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ReportViewModel = {
|
|
32
|
+
readonly overview: {
|
|
33
|
+
readonly nodes: readonly ReportViewNode[]
|
|
34
|
+
readonly edges: readonly { from: string; to: string; count: number; kinds: readonly string[]; relationIds: readonly string[] }[]
|
|
35
|
+
}
|
|
36
|
+
readonly groups: Readonly<Record<string, { kind: string; name: string; path: string | undefined; members: readonly string[] }>>
|
|
37
|
+
readonly entityGroup: Readonly<Record<string, string>>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const pathParts = (value: string | undefined): string[] => (value ?? '').split('/').filter(Boolean)
|
|
41
|
+
|
|
42
|
+
const packageFamily = (entity: KnowledgeEntity): string => {
|
|
43
|
+
const packageName = entity.name.replace(/^@[^/]+\//, '')
|
|
44
|
+
const nameFamily = packageName.split(/[-_./]/)[0]
|
|
45
|
+
if (nameFamily) return nameFamily.toLowerCase()
|
|
46
|
+
return pathParts(entity.path)[1] ?? pathParts(entity.path)[0] ?? 'other'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const reportGroupKey = (entity: KnowledgeEntity, appLayout: boolean): string => {
|
|
50
|
+
const parts = pathParts(entity.path)
|
|
51
|
+
if (appLayout && parts[0] === 'apps' && parts[1]) return `app:${parts[1]}`
|
|
52
|
+
if (appLayout && parts[0] === 'packages') return 'shared-packages'
|
|
53
|
+
if (appLayout && (parts.length === 0 || parts[0] === '.')) return 'workspace'
|
|
54
|
+
return packageFamily(entity)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const titleCase = (value: string): string => value.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ')
|
|
58
|
+
|
|
59
|
+
const buildReportViewModel = (snapshot: DiscoverySnapshotV1): ReportViewModel => {
|
|
60
|
+
const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]))
|
|
61
|
+
const packages = snapshot.entities.filter((entity) => entity.kind === 'package')
|
|
62
|
+
const packageForEntity = new Map<string, string>()
|
|
63
|
+
const externalIds = new Set<string>()
|
|
64
|
+
|
|
65
|
+
for (const relation of snapshot.relations) {
|
|
66
|
+
if (relation.kind === 'contains' && entities.get(relation.from)?.kind === 'package') packageForEntity.set(relation.to, relation.from)
|
|
67
|
+
if (entities.get(relation.from)?.kind === 'external') externalIds.add(relation.from)
|
|
68
|
+
if (entities.get(relation.to)?.kind === 'external') externalIds.add(relation.to)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const appLayout = packages.some((entity) => pathParts(entity.path)[0] === 'apps')
|
|
72
|
+
const groups = new Map<string, { kind: string; name: string; path: string | undefined; members: string[] }>()
|
|
73
|
+
const entityGroup: Record<string, string> = {}
|
|
74
|
+
|
|
75
|
+
for (const entity of packages) {
|
|
76
|
+
const family = reportGroupKey(entity, appLayout)
|
|
77
|
+
const id = `group:${family}`
|
|
78
|
+
const current = groups.get(id) ?? {
|
|
79
|
+
kind: family.startsWith('app:') ? 'app' : family === 'shared-packages' ? 'shared' : family === 'workspace' ? 'workspace' : 'domain',
|
|
80
|
+
name: family.startsWith('app:') ? `${titleCase(family.slice(4))} app` : family === 'shared-packages' ? 'Shared packages' : family === 'workspace' ? 'Workspace root' : family,
|
|
81
|
+
path: family.startsWith('app:') ? `apps/${family.slice(4)}` : family === 'shared-packages' ? 'packages' : family === 'workspace' ? '.' : undefined,
|
|
82
|
+
members: [] as string[],
|
|
83
|
+
}
|
|
84
|
+
current.members.push(entity.id)
|
|
85
|
+
groups.set(id, current)
|
|
86
|
+
entityGroup[entity.id] = id
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [entityId, packageId] of packageForEntity) {
|
|
90
|
+
const groupId = entityGroup[packageId]
|
|
91
|
+
if (groupId) entityGroup[entityId] = groupId
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (externalIds.size) {
|
|
95
|
+
const externalGroupId = 'group:external-dependencies'
|
|
96
|
+
groups.set(externalGroupId, {
|
|
97
|
+
kind: 'external',
|
|
98
|
+
name: 'External dependencies',
|
|
99
|
+
path: undefined,
|
|
100
|
+
members: [...externalIds].sort(),
|
|
101
|
+
})
|
|
102
|
+
for (const entityId of externalIds) entityGroup[entityId] = externalGroupId
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const allOverviewNodes: ReportViewNode[] = [...groups.entries()].map(([id, group]) => ({
|
|
106
|
+
id,
|
|
107
|
+
kind: group.kind,
|
|
108
|
+
name: group.name,
|
|
109
|
+
path: group.path,
|
|
110
|
+
memberCount: group.members.length,
|
|
111
|
+
}))
|
|
112
|
+
const hasApplications = allOverviewNodes.some((node) => node.kind === 'app')
|
|
113
|
+
const overviewNodes = allOverviewNodes.filter((node) =>
|
|
114
|
+
node.kind !== 'external' && (!hasApplications || node.kind !== 'workspace'),
|
|
115
|
+
)
|
|
116
|
+
const overviewNodeIds = new Set(overviewNodes.map((node) => node.id))
|
|
117
|
+
const edgeMap = new Map<string, { from: string; to: string; count: number; kinds: Set<string>; relationIds: Set<string> }>()
|
|
118
|
+
for (const relation of snapshot.relations) {
|
|
119
|
+
if (relation.kind === 'contains') continue
|
|
120
|
+
const from = entityGroup[relation.from]
|
|
121
|
+
const to = entityGroup[relation.to]
|
|
122
|
+
if (!from || !to || from === to) continue
|
|
123
|
+
const key = `${from}→${to}`
|
|
124
|
+
const edge = edgeMap.get(key) ?? { from, to, count: 0, kinds: new Set<string>(), relationIds: new Set<string>() }
|
|
125
|
+
edge.count += 1
|
|
126
|
+
edge.kinds.add(relation.kind)
|
|
127
|
+
edge.relationIds.add(relation.id)
|
|
128
|
+
edgeMap.set(key, edge)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
overview: {
|
|
133
|
+
nodes: overviewNodes.sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id)),
|
|
134
|
+
edges: [...edgeMap.values()]
|
|
135
|
+
.filter((edge) => overviewNodeIds.has(edge.from) && overviewNodeIds.has(edge.to))
|
|
136
|
+
.map((edge) => ({ ...edge, kinds: [...edge.kinds].sort(), relationIds: [...edge.relationIds].sort() }))
|
|
137
|
+
.sort((left, right) => right.count - left.count || left.from.localeCompare(right.from) || left.to.localeCompare(right.to)),
|
|
138
|
+
},
|
|
139
|
+
groups: Object.fromEntries([...groups.entries()].map(([id, group]) => [id, { ...group, members: [...group.members].sort() }])),
|
|
140
|
+
entityGroup,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
13
144
|
const escapeHtml = (value: unknown): string => String(value)
|
|
14
145
|
.replaceAll('&', '&')
|
|
15
146
|
.replaceAll('<', '<')
|
|
@@ -32,6 +163,14 @@ const reportData = (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV
|
|
|
32
163
|
configurationHash: snapshot.configurationHash,
|
|
33
164
|
pipelineVersion: snapshot.pipelineVersion,
|
|
34
165
|
analyzerVersions: snapshot.analyzerVersions,
|
|
166
|
+
diagnosticCount: report.diagnostics.length,
|
|
167
|
+
requiredRelationKinds: report.summary.requiredRelationKinds,
|
|
168
|
+
diagnosticSummary: {
|
|
169
|
+
errors: report.diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length,
|
|
170
|
+
warnings: report.diagnostics.filter((diagnostic) => diagnostic.severity === 'warn').length,
|
|
171
|
+
undocumented: report.diagnostics.filter((diagnostic) => diagnostic.status === 'undocumented').length,
|
|
172
|
+
drift: report.diagnostics.filter((diagnostic) => diagnostic.status === 'stale-or-unverified' || diagnostic.status === 'conflict').length,
|
|
173
|
+
},
|
|
35
174
|
entities: snapshot.entities.map((entity, index) => ({
|
|
36
175
|
id: entity.id,
|
|
37
176
|
anchor: entity.id.length > 64 ? `entity-n${index}` : anchor('entity', entity.id),
|
|
@@ -61,69 +200,182 @@ const reportData = (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV
|
|
|
61
200
|
evidence: diagnostic.evidence.map(({ context, ...item }) => includeSnippets && context ? { ...item, context: redactSecrets(context) } : item),
|
|
62
201
|
})),
|
|
63
202
|
coverage: snapshot.coverage,
|
|
203
|
+
view: buildReportViewModel(snapshot),
|
|
64
204
|
})
|
|
65
205
|
|
|
66
|
-
|
|
206
|
+
let styles = String.raw`
|
|
67
207
|
: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)}
|
|
68
|
-
*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:14px/1.5 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input,select{font:inherit}button{cursor:pointer}a{color:var(--blue)}.shell{max-width:1500px;margin:auto;padding:24px}.masthead{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;border-bottom:1px solid var(--line);padding:8px 0 24px}.eyebrow{color:var(--green);font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase}.masthead h1{font:700 clamp(28px,4vw,52px)/1.03 Georgia,serif;letter-spacing:-.04em;margin:8px 0}.lede{color:var(--muted);font-size:16px;max-width:720px;margin:0}.run-meta{text-align:right;color:var(--muted);font-size:12px}.run-meta strong{display:block;color:var(--green);font-size:14px}.read-only{border:1px solid #b9d7c6;background:#edf8f0;color:#185b3b;border-radius:999px;display:inline-flex;padding:4px 10px;font-weight:700;font-size:12px}.summary{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin:20px 0}.metric,.insight,.panel{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:14px}.metric{padding:16px}.metric b{font-size:27px;display:block;line-height:1}.metric span{color:var(--muted);display:block;margin-top:7px}.metric.warn b{color:var(--amber)}.metric.bad b{color:var(--red)}.lens-bar{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:22px 0 12px}.tabs{display:flex;gap:4px;flex-wrap:wrap}.tab,.level{border:1px solid transparent;background:transparent;border-radius:8px;padding:8px 11px;color:var(--muted)}.tab:hover,.level:hover{background:#e8eee9;color:var(--ink)}.tab[aria-selected=true],.level[aria-pressed=true]{background:var(--ink);color:#fff}.filters{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}.filters label{color:var(--muted);font-size:12px;display:flex;align-items:center;gap:6px}.filters input,.filters select,.filters button{border:1px solid var(--line);background:#fff;border-radius:8px;padding:8px 10px;color:var(--ink)}.filters input{min-width:220px}.filters button{color:var(--blue)}.insights{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:14px 0 20px}.insight{padding:14px}.insight h3{font-size:13px;margin:0 0 5px}.insight p{color:var(--muted);margin:0;font-size:12px}.tag{display:inline-block;border-radius:999px;padding:2px 7px;font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;background:#edf0ee;color:var(--muted)}.tag.heuristic{background:#fff3dc;color:#8a570a}.tag.error{background:#fde8e6;color:var(--red)}.tag.warn{background:#fff2d9;color:var(--amber)}.tag.info{background:#e6f1f5;color:var(--blue)}.workspace{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:14px}.panel{padding:18px}.panel h2{font:700 23px Georgia,serif;margin:0}.panel h3{font-size:14px;margin:0}.panel-head{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:14px}.subtle{color:var(--muted);font-size:12px}.map-wrap{border:1px solid var(--line);border-radius:10px;overflow:auto;background:linear-gradient(135deg,#fbfcfa,#f0f5f0)}#graph{display:block;width:100%;min-width:760px;height:560px}.edge{stroke:#b8c6bd;stroke-width:1.3;opacity:.65}.edge.alert{stroke:#d08a35;stroke-width:2}.edge-label{fill:var(--muted);font-size:10px}.graph-node{cursor:pointer}.graph-node rect{fill:#fff;stroke:#9eb0a4;stroke-width:1.2}.graph-node:hover rect,.graph-node:focus rect{stroke:var(--blue);stroke-width:2}.graph-node.selected rect{stroke:var(--green);stroke-width:3}.graph-node.issue rect{fill:#fffaf0;stroke:#c98a2c}.graph-node text{pointer-events:none}.node-label{font-size:12px;font-weight:750}.node-kind{font-size:10px;fill:var(--muted)}.node-count{font-size:10px;fill:var(--green);font-weight:800}.empty{padding:32px;text-align:center;color:var(--muted)}.side{position:sticky;top:14px;align-self:start}.detail-title{font:700 22px Georgia,serif;margin:4px 0}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:12px 0;margin:14px 0}.detail-grid b{display:block;font-size:19px}.detail-grid span{color:var(--muted);font-size:11px}.path{overflow-wrap:anywhere;color:var(--muted);font-size:12px}.list{list-style:none;margin:10px 0 0;padding:0}.list li{border-top:1px solid var(--line);padding:8px 0;font-size:12px}.run{margin-top:14px}.run-summary{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}.chip{border:1px solid var(--line);border-radius:999px;padding:5px 9px;color:var(--muted);font-size:12px}.chip b{color:var(--ink)}.finding{border-top:1px solid var(--line);padding:14px 0}.finding-head{display:flex;justify-content:space-between;gap:10px}.finding h3{margin:0;font-size:14px}.finding p{margin:5px 0;color:var(--muted)}.finding ul{margin:8px 0 0;padding-left:18px;color:var(--muted);font-size:12px}.coverage-row{display:grid;grid-template-columns:170px 1fr 90px;gap:10px;align-items:center;border-top:1px solid var(--line);padding:10px 0;font-size:12px}.bar{height:7px;background:#e7ece8;border-radius:99px;overflow:hidden}.bar i{display:block;height:100%;background:var(--green)}.bar i.partial{background:var(--amber)}.bar i.none{background:var(--red)}.metadata{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:14px}.metadata div{border-top:1px solid var(--line);padding-top:8px;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metadata b{display:block;color:var(--ink);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin-bottom:3px}.hidden{display:none!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:900px){.summary{grid-template-columns:repeat(3,minmax(0,1fr))}.insights{grid-template-columns:repeat(2,minmax(0,1fr))}.workspace{grid-template-columns:1fr}.side{position:static}.run-meta{display:none}}@media(max-width:560px){.shell{padding:14px}.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.insights{grid-template-columns:1fr}.masthead{display:block}.filters input{min-width:0;width:100%}.filters label:first-child{width:100%}.metadata{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
|
|
208
|
+
*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:14px/1.5 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input,select{font:inherit}button{cursor:pointer}a{color:var(--blue)}.shell{max-width:1500px;margin:auto;padding:24px}.masthead{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;border-bottom:1px solid var(--line);padding:8px 0 24px}.eyebrow{color:var(--green);font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase}.masthead h1{font:700 clamp(28px,4vw,52px)/1.03 Georgia,serif;letter-spacing:-.04em;margin:8px 0}.lede{color:var(--muted);font-size:16px;max-width:720px;margin:0}.run-meta{text-align:right;color:var(--muted);font-size:12px}.run-meta strong{display:block;color:var(--green);font-size:14px}.read-only{border:1px solid #b9d7c6;background:#edf8f0;color:#185b3b;border-radius:999px;display:inline-flex;padding:4px 10px;font-weight:700;font-size:12px}.summary{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin:20px 0}.metric,.insight,.panel{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:14px}.metric{padding:16px}.metric b{font-size:27px;display:block;line-height:1}.metric span{color:var(--muted);display:block;margin-top:7px}.metric.warn b{color:var(--amber)}.metric.bad b{color:var(--red)}.lens-bar{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:22px 0 12px}.tabs{display:flex;gap:4px;flex-wrap:wrap}.tab,.level{border:1px solid transparent;background:transparent;border-radius:8px;padding:8px 11px;color:var(--muted)}.tab:hover,.level:hover{background:#e8eee9;color:var(--ink)}.tab[aria-selected=true],.level[aria-pressed=true]{background:var(--ink);color:#fff}.filters{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}.filters label{color:var(--muted);font-size:12px;display:flex;align-items:center;gap:6px}.filters input,.filters select,.filters button{border:1px solid var(--line);background:#fff;border-radius:8px;padding:8px 10px;color:var(--ink)}.filters input{min-width:220px}.filters button{color:var(--blue)}.insights{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:14px 0 20px}.insight{padding:14px}.insight h3{font-size:13px;margin:0 0 5px}.insight p{color:var(--muted);margin:0;font-size:12px}.tag{display:inline-block;border-radius:999px;padding:2px 7px;font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;background:#edf0ee;color:var(--muted)}.tag.heuristic{background:#fff3dc;color:#8a570a}.tag.error{background:#fde8e6;color:var(--red)}.tag.warn{background:#fff2d9;color:var(--amber)}.tag.info{background:#e6f1f5;color:var(--blue)}.workspace{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:14px}.panel{padding:18px}.panel h2{font:700 23px Georgia,serif;margin:0}.panel h3{font-size:14px;margin:0}.panel-head{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:14px}.subtle{color:var(--muted);font-size:12px}.map-wrap{border:1px solid var(--line);border-radius:10px;overflow:auto;background:linear-gradient(135deg,#fbfcfa,#f0f5f0)}#graph{display:block;width:100%;min-width:760px;height:560px}.edge{stroke:#b8c6bd;stroke-width:1.3;opacity:.65}.edge.alert{stroke:#d08a35;stroke-width:2}.edge-label{fill:var(--muted);font-size:10px}.graph-node{cursor:pointer}.graph-node rect{fill:#fff;stroke:#9eb0a4;stroke-width:1.2}.graph-node:hover rect,.graph-node:focus rect{stroke:var(--blue);stroke-width:2}.graph-node.selected rect{stroke:var(--green);stroke-width:3}.graph-node.issue rect{fill:#fffaf0;stroke:#c98a2c}.graph-node text{pointer-events:none}.node-label{font-size:12px;font-weight:750}.node-kind{font-size:10px;fill:var(--muted)}.node-count{font-size:10px;fill:var(--green);font-weight:800}.empty{padding:32px;text-align:center;color:var(--muted)}.side{position:sticky;top:14px;align-self:start}.detail-title{font:700 22px Georgia,serif;margin:4px 0}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:12px 0;margin:14px 0}.detail-grid b{display:block;font-size:19px}.detail-grid span{color:var(--muted);font-size:11px}.path{overflow-wrap:anywhere;color:var(--muted);font-size:12px}.list{list-style:none;margin:10px 0 0;padding:0}.list li{border-top:1px solid var(--line);padding:8px 0;font-size:12px}.run{margin-top:14px}.run-summary{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}.chip{border:1px solid var(--line);border-radius:999px;padding:5px 9px;color:var(--muted);font-size:12px}.chip b{color:var(--ink)}.finding{border-top:1px solid var(--line);padding:14px 0}.finding-head{display:flex;justify-content:space-between;gap:10px}.finding h3{margin:0;font-size:14px}.finding p{margin:5px 0;color:var(--muted)}.finding ul{margin:8px 0 0;padding-left:18px;color:var(--muted);font-size:12px}.coverage-row{display:grid;grid-template-columns:170px 1fr 90px;gap:10px;align-items:center;border-top:1px solid var(--line);padding:10px 0;font-size:12px}.bar{height:7px;background:#e7ece8;border-radius:99px;overflow:hidden}.bar i{display:block;height:100%;background:var(--green)}.bar i.partial{background:var(--amber)}.bar i.none{background:var(--red)}.metadata{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:14px}.metadata div{border-top:1px solid var(--line);padding-top:8px;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metadata b{display:block;color:var(--ink);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin-bottom:3px}.hidden{display:none!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:900px){.summary{grid-template-columns:repeat(3,minmax(0,1fr))}.insights{grid-template-columns:repeat(2,minmax(0,1fr))}.workspace{grid-template-columns:1fr}.side{position:static}.run-meta{display:none}}@media(max-width:560px){.shell{padding:14px}.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.insights{grid-template-columns:1fr}.masthead{display:block}.filters input{min-width:0;width:100%}.filters label:first-child{width:100%}.metadata{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}@media(prefers-color-scheme:dark){:root{color-scheme:dark;--ink:#edf5ef;--muted:#a8b8ae;--paper:#111815;--panel:#18221d;--line:#304037;--green:#6dd19c;--blue:#7dc4dc;--amber:#efb866;--red:#f08a80;--shadow:0 12px 32px rgba(0,0,0,.24)}.map-wrap{background:linear-gradient(135deg,#19231d,#101611)}.filters input,.filters select,.filters button{background:#1d2922;color:var(--ink)}.graph-node rect{fill:#1b2820;stroke:#719080}.graph-node.issue rect{fill:#332c1d}}
|
|
209
|
+
`
|
|
210
|
+
|
|
211
|
+
styles += String.raw`
|
|
212
|
+
:root{--ink:#182b2b;--muted:#536664;--paper:#efe9df;--panel:#f8f5ee;--line:#c8c8b9;--green:#176b51;--blue:#155a68;--amber:#93530b;--red:#9b302b;--shadow:none}
|
|
213
|
+
*{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)}}
|
|
69
214
|
`
|
|
70
215
|
|
|
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}}`
|
|
217
|
+
|
|
218
|
+
styles += String.raw`.level{display:none!important}.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
|
+
|
|
220
|
+
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
|
+
styles += String.raw`body[data-report-view="architecture"] #report-dashboard{display:none!important}.edge{fill:none}`
|
|
222
|
+
|
|
223
|
+
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
|
+
|
|
225
|
+
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}}`
|
|
226
|
+
styles += String.raw`#lens-caption + p.subtle{display:none}`
|
|
227
|
+
|
|
71
228
|
const script = String.raw`
|
|
72
229
|
const data=${'${DATA}'};
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
230
|
+
data.entities=data.entities||[];data.relations=data.relations||[];data.diagnostics=data.diagnostics||[];
|
|
231
|
+
const policySuffix=data.requiredRelationKinds===undefined?"Policy: all observed relation kinds require declarations.":data.requiredRelationKinds.length?"Policy: declarations required for "+data.requiredRelationKinds.join(", ")+".":"Policy: missing relation declarations disabled; stale, conflicting, and unresolved declarations remain checked.";
|
|
232
|
+
const ensurePolicyNote=()=>{const note=document.querySelector("#map-note");if(note&&!note.textContent.includes(policySuffix))note.textContent=(note.textContent+" · "+policySuffix).trim()};
|
|
233
|
+
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();
|
|
235
|
+
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();
|
|
237
|
+
const groupById=new Map(Object.entries(data.view?.groups||{}));
|
|
238
|
+
const addFinding=(index,id,finding)=>{const values=index.get(id)||[];values.push(finding);index.set(id,values)};
|
|
239
|
+
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=new Set();(finding.entityIds||[]).forEach((id)=>{const scope=data.view?.entityGroup?.[id];if(scope)scopes.add(scope)});(finding.relationIds||[]).forEach((id)=>{const relation=relationById.get(id);if(!relation)return;[relation.from,relation.to].forEach((endpoint)=>{const scope=data.view?.entityGroup?.[endpoint];if(scope)scopes.add(scope)})});scopes.forEach((scope)=>groupFindingCounts.set(scope,(groupFindingCounts.get(scope)||0)+1))})};
|
|
241
|
+
const hasChunk=(name)=>!lazyChunks.includes(name)||loadedChunks.has(name);
|
|
242
|
+
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=()=>{loadedChunks.add(name);hydrate();resolve()};element.onerror=()=>reject(new Error("Unable to load offline report chunk: "+name));document.head.append(element)});loadingChunks.set(name,promise);return promise};
|
|
243
|
+
const readHash=()=>new URLSearchParams(location.hash.slice(1));
|
|
244
|
+
const hash=readHash();
|
|
245
|
+
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
|
+
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{}};
|
|
82
247
|
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};
|
|
83
|
-
const
|
|
84
|
-
const groupFor=(id)=>
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
248
|
+
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;
|
|
250
|
+
const packageNodes=()=>data.entities.filter((entity)=>entity.kind==="package");
|
|
251
|
+
const viewLabels={architecture:"Architecture",drift:"Insights",risks:"Findings",evidence:"Coverage"};
|
|
252
|
+
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
|
+
const label=(entity)=>entity?.name||entity?.id||"Unknown";
|
|
254
|
+
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(groupById.has(groupId))items.push({label:groupById.get(groupId).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("")};
|
|
89
256
|
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};
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
257
|
+
const relationHealth=(relation)=>{if(!relation)return "";const findings=relationFindingIndex.get(relation.id)||[];return findings.some((finding)=>finding.severity==="error")?"error":findings.length?"warn":""};
|
|
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};
|
|
259
|
+
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 relationInLens=(id)=>{const findings=relationFindingIndex.get(id)||[];return findings.some(findingInLens)};
|
|
261
|
+
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)};
|
|
262
|
+
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 graphModel=()=>{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}};
|
|
264
|
+
const renderInsights=()=>{const summary=data.diagnosticSummary||{},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.",""],["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("")};
|
|
265
|
+
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)}if(!data.diagnostics.length){dashboard.innerHTML="<p class=\"empty\">Open Findings or load the findings data to calculate insights.</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=[...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
|
+
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"};
|
|
268
|
+
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
|
+
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
|
+
let nodeClickTimer;
|
|
271
|
+
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);if(event.detail>=2){enterNode(node.dataset.node);return}nodeClickTimer=setTimeout(()=>{state.selected=node.dataset.node;render()},380)},true);
|
|
273
|
+
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)||[];
|
|
275
|
+
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,[]]));model.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));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="100%";svg.style.minWidth="0";svg.style.height="100%";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>",edges=model.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(model.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+edges+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."};
|
|
277
|
+
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"};
|
|
279
|
+
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
|
+
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+" findings · "+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> finding"+(group.findings.length===1?"":"s")+" · "+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+" total</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 findings match the current lens and filters.</p>"};
|
|
282
|
+
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 count=data.diagnosticCount||0;document.querySelector("#finding-count").textContent=count+" findings · available on demand";document.querySelector("#findings").innerHTML="<div class=\"empty\"><p>Findings stay out of the first paint so large repositories remain responsive.</p><button id=\"load-findings\" class=\"tab\" type=\"button\">Load findings</button></div>"};
|
|
284
|
+
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
|
+
const findingsLoaded=()=>!lazyChunks.includes("chunks/findings.js")||loadedChunks.has("chunks/findings.js");
|
|
286
|
+
const levelsLoaded=()=>!lazyChunks.includes("chunks/levels.js")||loadedChunks.has("chunks/levels.js");
|
|
287
|
+
const setReportView=()=>{document.body.dataset.reportView={architecture:"architecture",drift:"insights",risks:"findings",evidence:"coverage"}[state.lens]||"architecture";ensureReportChrome()};
|
|
288
|
+
const render=()=>{if(state.level!=="overview"&&!levelsLoaded()){document.querySelector("#map-note").textContent="Loading canonical entities…";loadChunk("chunks/levels.js").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.";renderInsights();renderInsightsDashboard();renderGraph();renderDetails();renderFindings();deferFindings();renderCoverage()};
|
|
289
|
+
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("chunks/levels.js");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);
|
|
290
|
+
document.addEventListener("click",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);if(event.detail>=2){enterNode(node.dataset.node);return}nodeClickTimer=setTimeout(()=>{state.selected=node.dataset.node;render()},380)},true);
|
|
291
|
+
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
|
+
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
|
+
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);
|
|
294
|
+
document.addEventListener("wheel",(event)=>{const wrap=event.target.closest?.(".map-wrap");if(!wrap)return;event.preventDefault();mapState.scale=Math.max(.65,Math.min(2.5,mapState.scale*(event.deltaY<0?1.12:.89)));applyMapTransform()},{passive:false});
|
|
295
|
+
document.addEventListener("pointerdown",(event)=>{const wrap=event.target.closest?.(".map-wrap");if(!wrap)return;mapState.dragging=true;mapState.lastX=event.clientX;mapState.lastY=event.clientY;wrap.classList.add("dragging")},true);
|
|
296
|
+
document.addEventListener("pointermove",(event)=>{if(!mapState.dragging)return;mapState.x+=event.clientX-mapState.lastX;mapState.y+=event.clientY-mapState.lastY;mapState.lastX=event.clientX;mapState.lastY=event.clientY;applyMapTransform()});
|
|
297
|
+
document.addEventListener("pointerup",()=>{mapState.dragging=false;document.querySelector(".map-wrap")?.classList.remove("dragging")},true);
|
|
298
|
+
document.addEventListener("keydown",(event)=>{if(event.target.matches?.("input,select,textarea"))return;if(event.key==="+"||event.key==="="){mapState.scale=Math.min(2.5,mapState.scale*1.15);applyMapTransform()}if(event.key==="-"){mapState.scale=Math.max(.65,mapState.scale*.87);applyMapTransform()}if(event.key==="0"){mapState.scale=1;mapState.x=0;mapState.y=0;applyMapTransform()}if(event.key==="Escape"){if(state.selected){state.selected=null}else if(state.level==="file"){state.level="module"}else if(state.level==="module"){state.level="package"}else if(state.level==="package"){state.level="overview"}render()}},true);
|
|
299
|
+
document.querySelectorAll(".tab[data-lens]").forEach((tab)=>tab.addEventListener("click",()=>{state.lens=tab.dataset.lens;state.findingGroup=null;render()}));
|
|
300
|
+
document.querySelectorAll(".level").forEach((button)=>button.addEventListener("click",()=>{state.level=button.dataset.level;render()}));
|
|
301
|
+
document.querySelector("#search").addEventListener("input",(event)=>{state.query=event.target.value;state.findingGroup=null;renderFindings()});
|
|
302
|
+
document.querySelector("#status").addEventListener("change",(event)=>{state.status=event.target.value;state.findingGroup=null;renderFindings()});
|
|
303
|
+
document.querySelector("#severity").addEventListener("change",(event)=>{state.severity=event.target.value;state.findingGroup=null;renderFindings()});
|
|
304
|
+
document.querySelector("#findings").addEventListener("click",(event)=>{const button=event.target.closest("[data-finding-group]");if(!button)return;state.findingGroup=button.dataset.findingGroup;state.findingPage=Number(button.dataset.findingPage||0);renderFindings()});
|
|
305
|
+
document.querySelector("#reset").addEventListener("click",()=>{state.query="";state.status="";state.severity="";state.findingGroup=null;state.findingPage=0;renderFindings()});
|
|
306
|
+
document.querySelector("#clear-selection").addEventListener("click",()=>{state.selected=null;render()});
|
|
307
|
+
const keepMobileGraphReadable=()=>{const svg=document.querySelector("#graph");if(!svg)return;if(window.innerWidth<=700){svg.style.setProperty("width","720px","important");svg.style.setProperty("min-width","720px","important");svg.style.setProperty("max-width","none","important")}else{svg.style.removeProperty("width");svg.style.removeProperty("min-width");svg.style.removeProperty("max-width")}};
|
|
308
|
+
new MutationObserver(keepMobileGraphReadable).observe(document.querySelector("#graph"),{attributes:true,attributeFilter:["style"]});
|
|
309
|
+
window.addEventListener("resize",keepMobileGraphReadable);
|
|
310
|
+
window.addEventListener("hashchange",()=>{const next=readHash();state.lens=next.get("lens")||"architecture";state.level=next.get("level")||"overview";state.selected=next.get("selected");state.query=next.get("query")||"";state.status=next.get("status")||"";state.severity=next.get("severity")||"";state.findingGroup=next.get("findingGroup");state.findingPage=Number(next.get("findingPage")||0);render()});
|
|
311
|
+
hydrate();render();keepMobileGraphReadable();ensurePolicyNote();
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
type InternalOfflineReportOptions = OfflineReportOptions & {
|
|
315
|
+
readonly dataExpression?: string
|
|
316
|
+
readonly dataScripts?: string
|
|
317
|
+
}
|
|
103
318
|
|
|
104
|
-
const render = (input: OfflineReportInput, options:
|
|
319
|
+
const render = (input: OfflineReportInput, options: InternalOfflineReportOptions): string => {
|
|
105
320
|
const { snapshot, report } = input
|
|
106
321
|
const includeSnippets = options.includeSnippets === true
|
|
107
322
|
const data = reportData(snapshot, report, includeSnippets)
|
|
108
|
-
const scriptBody = script.replace('${' + 'DATA}', embeddedJson(data))
|
|
323
|
+
const scriptBody = script.replace('${' + 'DATA}', options.dataExpression ?? embeddedJson(data))
|
|
109
324
|
const largeNote = snapshot.entities.length > 500 || report.diagnostics.length > 1_000
|
|
110
325
|
? 'Large snapshots are rendered from compact canonical data with progressive graph levels.'
|
|
111
326
|
: 'The viewer starts with a grouped topology and expands into canonical entities on demand.'
|
|
112
327
|
const issueCount = report.diagnostics.filter((diagnostic) => diagnostic.severity === 'error' || diagnostic.severity === 'warn').length
|
|
113
328
|
const unsupportedCount = snapshot.coverage.filter((entry) => entry.status !== 'complete').length
|
|
329
|
+
const policyNote = report.summary.requiredRelationKinds === undefined
|
|
330
|
+
? 'Missing relation declarations are checked for every observed relation kind.'
|
|
331
|
+
: report.summary.requiredRelationKinds.length
|
|
332
|
+
? `Missing relation declarations are checked for: ${report.summary.requiredRelationKinds.join(', ')}.`
|
|
333
|
+
: 'Missing relation declarations are disabled by configuration; stale, conflicting, and unresolved declarations remain checked.'
|
|
114
334
|
const statusOptions = ['confirmed', 'undocumented', 'stale-or-unverified', 'conflict', 'unresolved', 'not-analyzed']
|
|
115
|
-
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(snapshot.project.name)}</title><style>${styles}</style></head><body
|
|
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(snapshot.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(snapshot.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(snapshot.sourceRevision)}</strong><span>${escapeHtml(snapshot.sourceRevisionKind)} · pipeline ${escapeHtml(snapshot.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 ${issueCount?'warn':''}"><b>${issueCount}</b><span>warn/error findings</span></div><div class="metric ${report.diagnostics.length?'warn':''}"><b>${report.diagnostics.length}</b><span>total findings</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 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">${report.diagnostics.length} shown</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></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(snapshot.contentHash)}</div><div><b>Report</b>${escapeHtml(report.contentHash)}</div><div><b>Configuration</b>${escapeHtml(snapshot.configurationHash)}</div><div><b>Analyzers</b>${escapeHtml(Object.entries(snapshot.analyzerVersions).map(([name, version]) => `${name} ${version}`).join(', '))}</div><div><b>Source revision</b>${escapeHtml(snapshot.sourceRevision)} (${escapeHtml(snapshot.sourceRevisionKind)})</div><div><b>Mode</b>Read-only browser viewer; approvals and fixes remain outside this artifact.</div></section></main><script>${scriptBody}</script></body></html>`
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const parseReportInput = (input: unknown): OfflineReportInput => {
|
|
339
|
+
if (!input || typeof input !== 'object') throw new Error('Input must contain snapshot and report artifacts.')
|
|
340
|
+
const value = input as Partial<OfflineReportInput>
|
|
341
|
+
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot)
|
|
342
|
+
const report = ReconciliationReportV1Schema.parse(value.report)
|
|
343
|
+
if (report.snapshotHash !== snapshot.contentHash) throw new Error('Report snapshotHash does not match snapshot contentHash.')
|
|
344
|
+
return { snapshot, report }
|
|
116
345
|
}
|
|
117
346
|
|
|
347
|
+
const chunkScript = (payload: unknown): string => `globalThis.__DOC_BRIDGE_DATA__=Object.assign(globalThis.__DOC_BRIDGE_DATA__||{},${embeddedJson(payload)});`
|
|
348
|
+
const byteLength = (value: string): number => new TextEncoder().encode(value).byteLength
|
|
349
|
+
|
|
118
350
|
export const renderOfflineReport = (input: unknown, options: OfflineReportOptions = {}): string => {
|
|
119
351
|
try {
|
|
120
|
-
|
|
121
|
-
const value = input as Partial<OfflineReportInput>
|
|
122
|
-
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot)
|
|
123
|
-
const report = ReconciliationReportV1Schema.parse(value.report)
|
|
124
|
-
if (report.snapshotHash !== snapshot.contentHash) throw new Error('Report snapshotHash does not match snapshot contentHash.')
|
|
125
|
-
return render({ snapshot, report }, options)
|
|
352
|
+
return render(parseReportInput(input), options)
|
|
126
353
|
} catch (error) {
|
|
127
354
|
return errorPage(error instanceof Error ? error.message : String(error))
|
|
128
355
|
}
|
|
129
356
|
}
|
|
357
|
+
|
|
358
|
+
export const renderOfflineReportArtifact = (input: unknown, options: OfflineReportOptions & { readonly thresholdBytes?: number } = {}): OfflineReportArtifact => {
|
|
359
|
+
const parsed = parseReportInput(input)
|
|
360
|
+
const thresholdBytes = options.thresholdBytes ?? DEFAULT_LARGE_REPORT_THRESHOLD_BYTES
|
|
361
|
+
const renderOptions = { includeSnippets: options.includeSnippets === true, thresholdBytes }
|
|
362
|
+
const singleFile = render(parsed, options)
|
|
363
|
+
if (byteLength(singleFile) <= thresholdBytes) {
|
|
364
|
+
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 }
|
|
366
|
+
const manifest = JSON.stringify({ ...manifestPayload, artifactHash: sha256NormalizedV1(manifestPayload) }, null, 2)
|
|
367
|
+
return { mode: 'single-file', indexHtml: singleFile, files: { 'index.html': singleFile }, manifest }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const data = reportData(parsed.snapshot, parsed.report, options.includeSnippets === true)
|
|
371
|
+
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, view: data.view }),
|
|
373
|
+
'chunks/levels.js': chunkScript({ entities: data.entities, relations: data.relations }),
|
|
374
|
+
'chunks/findings.js': chunkScript({ diagnostics: data.diagnostics }),
|
|
375
|
+
}
|
|
376
|
+
const indexHtml = render(parsed, { ...options, dataExpression: 'globalThis.__DOC_BRIDGE_DATA__', dataScripts: `<script>globalThis.__DOC_BRIDGE_LAZY_CHUNKS__=${embeddedJson(['chunks/levels.js', 'chunks/findings.js'])}</script><script src="chunks/overview.js"></script>` })
|
|
377
|
+
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 }
|
|
379
|
+
const manifest = JSON.stringify({ ...manifestPayload, artifactHash: sha256NormalizedV1(manifestPayload) }, null, 2)
|
|
380
|
+
return { mode: 'directory', indexHtml, files: { ...files, 'index.html': indexHtml }, manifest }
|
|
381
|
+
}
|