sneakoscope 8.3.2 → 8.4.0
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/README.md +1 -1
- package/config/architecture-map-policy.v1.json +168 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +19 -1
- package/dist/commands/doctor-profile.js +1 -1
- package/dist/commands/doctor.js +33 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/align/align-route.js +8 -2
- package/dist/core/align/code-navigation-align.js +88 -32
- package/dist/core/architecture-map-pipeline.js +172 -0
- package/dist/core/architecture-map-review.js +180 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +25 -3
- package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
- package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -1
- package/dist/core/fsx.js +14 -0
- package/dist/core/git-hygiene/git-policy.js +4 -0
- package/dist/core/git-hygiene/gitignore-writer.js +2 -0
- package/dist/core/gx-renderer.js +2 -1
- package/dist/core/managed-paths.js +1 -0
- package/dist/core/perf/release-latency-slo.js +57 -11
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
- package/dist/core/pipeline-internals/runtime-core.js +13 -172
- package/dist/core/pipeline-internals/runtime-gates.js +12 -1
- package/dist/core/release/content-digest-cache.js +70 -0
- package/dist/core/release/gate-manifest.js +9 -0
- package/dist/core/release/main-push-guard.js +4 -3
- package/dist/core/release/package-dist-snapshot.js +18 -2
- package/dist/core/release/physical-release-gates.js +26 -3
- package/dist/core/release/release-authorization-snapshot.js +4 -0
- package/dist/core/release/release-gate-cache-v2.js +27 -3
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/release/release-gate-dag.js +3 -0
- package/dist/core/release/release-inspection-memo.js +69 -0
- package/dist/core/release/release-pack-content-scanner.js +5 -0
- package/dist/core/release/release-pack-receipt.js +28 -18
- package/dist/core/release/release-pack-tarball.js +10 -0
- package/dist/core/release/release-upgrade-baseline.js +4 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/ssot-authority-inventory.js +114 -0
- package/dist/core/safety/ssot-guard.js +2 -44
- package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
- package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
- package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
- package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
- package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
- package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
- package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
- package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
- package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
- package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
- package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
- package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
- package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
- package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
- package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
- package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
- package/dist/core/triwiki/context-graph/projections/index.js +2 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
- package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
- package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
- package/dist/scripts/architecture-map-check.js +299 -0
- package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
- package/dist/scripts/release-metadata-check.js +2 -0
- package/package.json +6 -3
- package/release-gates.v2.json +204 -0
- package/schemas/architecture-baseline.v1.schema.json +47 -0
- package/schemas/architecture-map-manifest.v1.schema.json +40 -0
- package/schemas/architecture-map-policy.v1.schema.json +147 -0
- package/schemas/architecture-review.v1.schema.json +46 -0
- package/schemas/mermaid-projection.v1.schema.json +47 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ARCHITECTURE_BASELINE_SCHEMA, ARCHITECTURE_INPUT_BUNDLE_SCHEMA, ARCHITECTURE_MAP_MANIFEST_SCHEMA, ARCHITECTURE_REVIEW_SCHEMA, MERMAID_PROJECTION_SCHEMA } from './contracts.js';
|
|
2
|
+
import { verifyArchitectureBaselineSeal } from './baseline.js';
|
|
3
|
+
function asRecord(value) {
|
|
4
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
5
|
+
return null;
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
export function validateArchitectureInputBundle(value) {
|
|
9
|
+
const blockers = [];
|
|
10
|
+
const record = asRecord(value);
|
|
11
|
+
if (!record)
|
|
12
|
+
return { ok: false, blockers: ['not_an_object'] };
|
|
13
|
+
if (record.schema !== ARCHITECTURE_INPUT_BUNDLE_SCHEMA)
|
|
14
|
+
blockers.push('schema');
|
|
15
|
+
for (const key of [
|
|
16
|
+
'rootId',
|
|
17
|
+
'graphHash',
|
|
18
|
+
'topologyHash',
|
|
19
|
+
'ssotInventoryHash',
|
|
20
|
+
'voxelContextHash',
|
|
21
|
+
'policyHash',
|
|
22
|
+
'analyzerVersion',
|
|
23
|
+
'serializerVersion',
|
|
24
|
+
'canonicalHash'
|
|
25
|
+
]) {
|
|
26
|
+
if (typeof record[key] !== 'string' || !record[key].length)
|
|
27
|
+
blockers.push(key);
|
|
28
|
+
}
|
|
29
|
+
if (!record.graph || typeof record.graph !== 'object')
|
|
30
|
+
blockers.push('graph');
|
|
31
|
+
if (!record.worktree || typeof record.worktree !== 'object')
|
|
32
|
+
blockers.push('worktree');
|
|
33
|
+
return { ok: blockers.length === 0, blockers: Object.freeze(blockers) };
|
|
34
|
+
}
|
|
35
|
+
export function validateArchitectureBaseline(value) {
|
|
36
|
+
const blockers = [];
|
|
37
|
+
const record = asRecord(value);
|
|
38
|
+
if (!record)
|
|
39
|
+
return { ok: false, blockers: ['not_an_object'] };
|
|
40
|
+
if (record.schema !== ARCHITECTURE_BASELINE_SCHEMA)
|
|
41
|
+
blockers.push('schema');
|
|
42
|
+
if (record.required !== true)
|
|
43
|
+
blockers.push('required');
|
|
44
|
+
if (record.capturedBeforeMutation !== true)
|
|
45
|
+
blockers.push('capturedBeforeMutation');
|
|
46
|
+
for (const key of [
|
|
47
|
+
'missionId',
|
|
48
|
+
'worktreeFingerprintHash',
|
|
49
|
+
'graphHash',
|
|
50
|
+
'policyHash',
|
|
51
|
+
'analyzerVersion',
|
|
52
|
+
'serializerVersion',
|
|
53
|
+
'canonicalPayloadHash',
|
|
54
|
+
'seal'
|
|
55
|
+
]) {
|
|
56
|
+
if (typeof record[key] !== 'string' || !record[key].length)
|
|
57
|
+
blockers.push(key);
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(record.findings))
|
|
60
|
+
blockers.push('findings');
|
|
61
|
+
if (!record.metrics || typeof record.metrics !== 'object')
|
|
62
|
+
blockers.push('metrics');
|
|
63
|
+
if (blockers.length === 0) {
|
|
64
|
+
const baseline = value;
|
|
65
|
+
if (!verifyArchitectureBaselineSeal(baseline))
|
|
66
|
+
blockers.push('seal_mismatch');
|
|
67
|
+
}
|
|
68
|
+
return { ok: blockers.length === 0, blockers: Object.freeze(blockers) };
|
|
69
|
+
}
|
|
70
|
+
export function validateArchitectureReview(value) {
|
|
71
|
+
const blockers = [];
|
|
72
|
+
const record = asRecord(value);
|
|
73
|
+
if (!record)
|
|
74
|
+
return { ok: false, blockers: ['not_an_object'] };
|
|
75
|
+
if (record.schema !== ARCHITECTURE_REVIEW_SCHEMA)
|
|
76
|
+
blockers.push('schema');
|
|
77
|
+
for (const key of [
|
|
78
|
+
'missionId',
|
|
79
|
+
'baselineSeal',
|
|
80
|
+
'baselineHash',
|
|
81
|
+
'afterInputHash',
|
|
82
|
+
'canonicalPayloadHash'
|
|
83
|
+
]) {
|
|
84
|
+
if (typeof record[key] !== 'string' || !record[key].length)
|
|
85
|
+
blockers.push(key);
|
|
86
|
+
}
|
|
87
|
+
if (record.verdict !== 'pass' && record.verdict !== 'block')
|
|
88
|
+
blockers.push('verdict');
|
|
89
|
+
for (const key of [
|
|
90
|
+
'changedPaths',
|
|
91
|
+
'accountedChangedPaths',
|
|
92
|
+
'unaccountedChangedPaths',
|
|
93
|
+
'newFindings',
|
|
94
|
+
'blockingFindingIds'
|
|
95
|
+
]) {
|
|
96
|
+
if (!Array.isArray(record[key]))
|
|
97
|
+
blockers.push(key);
|
|
98
|
+
}
|
|
99
|
+
return { ok: blockers.length === 0, blockers: Object.freeze(blockers) };
|
|
100
|
+
}
|
|
101
|
+
export function validateMermaidProjection(value) {
|
|
102
|
+
const blockers = [];
|
|
103
|
+
const record = asRecord(value);
|
|
104
|
+
if (!record)
|
|
105
|
+
return { ok: false, blockers: ['not_an_object'] };
|
|
106
|
+
if (record.schema !== MERMAID_PROJECTION_SCHEMA)
|
|
107
|
+
blockers.push('schema');
|
|
108
|
+
for (const key of ['viewId', 'title', 'source', 'contentHash']) {
|
|
109
|
+
if (typeof record[key] !== 'string' || !record[key].length)
|
|
110
|
+
blockers.push(key);
|
|
111
|
+
}
|
|
112
|
+
if (record.direction !== 'LR' && record.direction !== 'TD')
|
|
113
|
+
blockers.push('direction');
|
|
114
|
+
if (!record.accounting || typeof record.accounting !== 'object')
|
|
115
|
+
blockers.push('accounting');
|
|
116
|
+
return { ok: blockers.length === 0, blockers: Object.freeze(blockers) };
|
|
117
|
+
}
|
|
118
|
+
export function validateArchitectureMapManifest(value) {
|
|
119
|
+
const blockers = [];
|
|
120
|
+
const record = asRecord(value);
|
|
121
|
+
if (!record)
|
|
122
|
+
return { ok: false, blockers: ['not_an_object'] };
|
|
123
|
+
if (record.schema !== ARCHITECTURE_MAP_MANIFEST_SCHEMA)
|
|
124
|
+
blockers.push('schema');
|
|
125
|
+
for (const key of ['graphHash', 'policyHash', 'serializerVersion']) {
|
|
126
|
+
if (typeof record[key] !== 'string' || !record[key].length)
|
|
127
|
+
blockers.push(key);
|
|
128
|
+
}
|
|
129
|
+
if (!Array.isArray(record.views) || record.views.length === 0)
|
|
130
|
+
blockers.push('views');
|
|
131
|
+
if (!record.projectionAccounting || typeof record.projectionAccounting !== 'object') {
|
|
132
|
+
blockers.push('projectionAccounting');
|
|
133
|
+
}
|
|
134
|
+
return { ok: blockers.length === 0, blockers: Object.freeze(blockers) };
|
|
135
|
+
}
|
|
136
|
+
export function assertValidInputBundle(bundle) {
|
|
137
|
+
const result = validateArchitectureInputBundle(bundle);
|
|
138
|
+
if (!result.ok)
|
|
139
|
+
throw new Error(`architecture_input_bundle_invalid: ${result.blockers.join(',')}`);
|
|
140
|
+
}
|
|
141
|
+
export function assertValidReview(review) {
|
|
142
|
+
const result = validateArchitectureReview(review);
|
|
143
|
+
if (!result.ok)
|
|
144
|
+
throw new Error(`architecture_review_invalid: ${result.blockers.join(',')}`);
|
|
145
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hashCanonical } from './fingerprint.js';
|
|
2
|
+
export function emptyVoxelContext(profile = 'global') {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
provider: 'none',
|
|
5
|
+
version: '0',
|
|
6
|
+
seedNodeIds: Object.freeze([]),
|
|
7
|
+
seedPaths: Object.freeze([]),
|
|
8
|
+
riskDomains: Object.freeze([]),
|
|
9
|
+
profile,
|
|
10
|
+
workstreamHints: Object.freeze([]),
|
|
11
|
+
tokenBudgetHint: null,
|
|
12
|
+
protectedAreaIds: Object.freeze([])
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export class NoneVoxelArchitectureContextProvider {
|
|
16
|
+
id = 'none';
|
|
17
|
+
version = '0';
|
|
18
|
+
async load(input) {
|
|
19
|
+
void input.root;
|
|
20
|
+
void input.missionId;
|
|
21
|
+
void input.taskText;
|
|
22
|
+
return emptyVoxelContext(input.profile ?? 'global');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const DEFAULT_PROVIDER = new NoneVoxelArchitectureContextProvider();
|
|
26
|
+
export async function loadVoxelArchitectureContext(input) {
|
|
27
|
+
const provider = input.provider ?? DEFAULT_PROVIDER;
|
|
28
|
+
const loadInput = { root: input.root };
|
|
29
|
+
if (input.missionId !== undefined)
|
|
30
|
+
loadInput.missionId = input.missionId;
|
|
31
|
+
if (input.taskText !== undefined)
|
|
32
|
+
loadInput.taskText = input.taskText;
|
|
33
|
+
if (input.profile !== undefined)
|
|
34
|
+
loadInput.profile = input.profile;
|
|
35
|
+
return provider.load(loadInput);
|
|
36
|
+
}
|
|
37
|
+
export function voxelContextHash(context) {
|
|
38
|
+
return hashCanonical(context);
|
|
39
|
+
}
|
|
@@ -7,4 +7,8 @@ export function contextGraphExtractors() {
|
|
|
7
7
|
export function codeNavigationGraphExtractors(options = {}) {
|
|
8
8
|
return [createCodeGraphExtractor(options)];
|
|
9
9
|
}
|
|
10
|
+
export function architectureMapGraphExtractors(options = {}) {
|
|
11
|
+
return [createCodeGraphExtractor(options), createTopologyGraphExtractor(), createEvidenceGraphExtractor()];
|
|
12
|
+
}
|
|
13
|
+
export const ARCHITECTURE_MAP_EXTRACTOR_IDS = Object.freeze(['code', 'topology', 'triwiki-evidence']);
|
|
10
14
|
export { createCodeGraphExtractor, createEvidenceGraphExtractor, createTopologyGraphExtractor };
|
|
@@ -5,3 +5,5 @@ export { rankModuleCandidates, sortCandidates } from './module-view.js';
|
|
|
5
5
|
export { codePackEntryId, describeContextGraphNode, estimateEntryTokenCost, projectedFreshness, projectedTrustScore } from './node-summary.js';
|
|
6
6
|
export { ATTENTION_FACT_TRUST_FLOOR, attentionHydrateHint, isFactGradeAnchor, projectContextGraphAnchors, projectContextPackAnchors, resolveContextGraphAnchorNode } from './anchors.js';
|
|
7
7
|
export { CONTEXT_GRAPH_ATTENTION_TOKEN_BUDGET, readContextGraphAttention } from './attention.js';
|
|
8
|
+
export * as mermaid from './mermaid/index.js';
|
|
9
|
+
export { buildArchitectureMapViews, buildArchitectureMapManifest, buildMermaidView, buildModuleDependencyView, serializeMermaidDocument, GENERATED_HEADER } from './mermaid/index.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { byCodePoint } from '../../architecture/contracts.js';
|
|
2
|
+
import * as ast from './ast.js';
|
|
3
|
+
import { assertInjective, mermaidSubgraphId } from './ids.js';
|
|
4
|
+
import { emptyAccounting, toMermaidProjection } from './serializer.js';
|
|
5
|
+
import { emptyProjection } from './view-builder.js';
|
|
6
|
+
export function buildArchitectureDeltaView(input) {
|
|
7
|
+
void input.policy;
|
|
8
|
+
const beforeNodes = new Set(input.delta.beforeNodeIds);
|
|
9
|
+
const afterNodes = new Set(input.delta.afterNodeIds);
|
|
10
|
+
const added = [...afterNodes].filter((id) => !beforeNodes.has(id)).sort(byCodePoint);
|
|
11
|
+
const removed = [...beforeNodes].filter((id) => !afterNodes.has(id)).sort(byCodePoint);
|
|
12
|
+
const retained = [...afterNodes].filter((id) => beforeNodes.has(id)).sort(byCodePoint);
|
|
13
|
+
if (!added.length && !removed.length && !retained.length) {
|
|
14
|
+
return emptyProjection('architecture-delta', 'Architecture delta', 'TD', 'empty_delta: no before/after node ids');
|
|
15
|
+
}
|
|
16
|
+
const labels = new Map(input.snapshot.nodes.map((node) => [node.id, node.label || node.path || node.id]));
|
|
17
|
+
const allIds = [...added, ...removed, ...retained];
|
|
18
|
+
const idMap = assertInjective(allIds);
|
|
19
|
+
const statements = [
|
|
20
|
+
ast.comment('atlas-view: architecture-delta'),
|
|
21
|
+
ast.subgraph({
|
|
22
|
+
id: mermaidSubgraphId('delta:added'),
|
|
23
|
+
label: 'added',
|
|
24
|
+
canonicalId: 'delta:added',
|
|
25
|
+
statements: added.map((id) => ast.node({
|
|
26
|
+
id: idMap.get(id),
|
|
27
|
+
label: `+ ${labels.get(id) ?? id}`,
|
|
28
|
+
canonicalNodeIds: [id]
|
|
29
|
+
}))
|
|
30
|
+
}),
|
|
31
|
+
ast.subgraph({
|
|
32
|
+
id: mermaidSubgraphId('delta:removed'),
|
|
33
|
+
label: 'removed',
|
|
34
|
+
canonicalId: 'delta:removed',
|
|
35
|
+
statements: removed.map((id) => ast.node({
|
|
36
|
+
id: idMap.get(id),
|
|
37
|
+
label: `- ${labels.get(id) ?? id}`,
|
|
38
|
+
canonicalNodeIds: [id]
|
|
39
|
+
}))
|
|
40
|
+
}),
|
|
41
|
+
ast.subgraph({
|
|
42
|
+
id: mermaidSubgraphId('delta:retained'),
|
|
43
|
+
label: 'retained',
|
|
44
|
+
canonicalId: 'delta:retained',
|
|
45
|
+
statements: retained.map((id) => ast.node({
|
|
46
|
+
id: idMap.get(id),
|
|
47
|
+
label: labels.get(id) ?? id,
|
|
48
|
+
canonicalNodeIds: [id]
|
|
49
|
+
}))
|
|
50
|
+
})
|
|
51
|
+
];
|
|
52
|
+
const beforeEdges = new Set(input.delta.beforeEdgeIds ?? []);
|
|
53
|
+
const afterEdges = new Set(input.delta.afterEdgeIds ?? []);
|
|
54
|
+
if (beforeEdges.size || afterEdges.size) {
|
|
55
|
+
const addedEdges = [...afterEdges].filter((id) => !beforeEdges.has(id)).sort(byCodePoint);
|
|
56
|
+
const removedEdges = [...beforeEdges].filter((id) => !afterEdges.has(id)).sort(byCodePoint);
|
|
57
|
+
statements.push(ast.comment(`edges +${addedEdges.length}/-${removedEdges.length}`));
|
|
58
|
+
}
|
|
59
|
+
return toMermaidProjection({
|
|
60
|
+
viewId: 'architecture-delta',
|
|
61
|
+
doc: ast.document({
|
|
62
|
+
direction: 'TD',
|
|
63
|
+
title: 'Architecture delta',
|
|
64
|
+
statements
|
|
65
|
+
}),
|
|
66
|
+
accounting: emptyAccounting()
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { byCodePoint } from '../../architecture/contracts.js';
|
|
2
|
+
import { asRelationLabel, mermaidLabel } from './escape.js';
|
|
3
|
+
function freezeIds(ids) {
|
|
4
|
+
return Object.freeze([...ids].sort(byCodePoint));
|
|
5
|
+
}
|
|
6
|
+
export function comment(text) {
|
|
7
|
+
return Object.freeze({ kind: 'comment', text: mermaidLabel(text, 200) });
|
|
8
|
+
}
|
|
9
|
+
export function node(input) {
|
|
10
|
+
if (!input.canonicalNodeIds.length)
|
|
11
|
+
throw new Error('mermaid_node_without_canonical_id');
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
kind: 'node',
|
|
14
|
+
id: input.id,
|
|
15
|
+
label: mermaidLabel(input.label),
|
|
16
|
+
canonicalNodeIds: freezeIds(input.canonicalNodeIds)
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function edge(input) {
|
|
20
|
+
if (!input.canonicalEdgeIds.length)
|
|
21
|
+
throw new Error('mermaid_edge_without_canonical_id');
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
kind: 'edge',
|
|
24
|
+
from: input.from,
|
|
25
|
+
to: input.to,
|
|
26
|
+
relation: asRelationLabel(input.relation),
|
|
27
|
+
style: input.style ?? 'solid',
|
|
28
|
+
canonicalEdgeIds: freezeIds(input.canonicalEdgeIds),
|
|
29
|
+
fromCanonicalId: input.fromCanonicalId,
|
|
30
|
+
toCanonicalId: input.toCanonicalId
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function subgraph(input) {
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
kind: 'subgraph',
|
|
36
|
+
id: input.id,
|
|
37
|
+
label: mermaidLabel(input.label),
|
|
38
|
+
canonicalId: input.canonicalId,
|
|
39
|
+
statements: Object.freeze([...input.statements])
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function document(input) {
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
kind: 'flowchart',
|
|
45
|
+
direction: input.direction,
|
|
46
|
+
title: mermaidLabel(input.title, 120),
|
|
47
|
+
statements: Object.freeze([...input.statements])
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { byCodePoint } from '../../architecture/contracts.js';
|
|
2
|
+
import { buildArchitectureScope, sliceArchitectureGraph } from '../../architecture/slice.js';
|
|
3
|
+
import { emptyProjection, projectFilteredView } from './view-builder.js';
|
|
4
|
+
export function buildChangeImpactView(input) {
|
|
5
|
+
const seedPaths = input.request?.seedPaths ?? [];
|
|
6
|
+
const seedNodeIds = input.request?.seedNodeIds ?? [];
|
|
7
|
+
if (!seedPaths.length && !seedNodeIds.length) {
|
|
8
|
+
return emptyProjection('change-impact', 'Change impact', 'LR', 'mission_helper: seed paths/node ids required');
|
|
9
|
+
}
|
|
10
|
+
const scope = buildArchitectureScope({
|
|
11
|
+
profile: input.request?.profile ?? 'implementation',
|
|
12
|
+
seedNodeIds,
|
|
13
|
+
seedPaths
|
|
14
|
+
});
|
|
15
|
+
const slice = sliceArchitectureGraph(input.snapshot, input.policy, scope);
|
|
16
|
+
const nodeIdSet = new Set(slice.nodeIds);
|
|
17
|
+
const edgeIdSet = new Set(slice.edgeIds);
|
|
18
|
+
const sliced = {
|
|
19
|
+
...input.snapshot,
|
|
20
|
+
nodes: input.snapshot.nodes
|
|
21
|
+
.filter((node) => nodeIdSet.has(node.id))
|
|
22
|
+
.sort((left, right) => byCodePoint(left.id, right.id)),
|
|
23
|
+
edges: input.snapshot.edges
|
|
24
|
+
.filter((edge) => edgeIdSet.has(edge.id))
|
|
25
|
+
.sort((left, right) => byCodePoint(left.id, right.id)),
|
|
26
|
+
nodeCount: nodeIdSet.size,
|
|
27
|
+
edgeCount: edgeIdSet.size
|
|
28
|
+
};
|
|
29
|
+
return projectFilteredView({
|
|
30
|
+
viewId: 'change-impact',
|
|
31
|
+
snapshot: sliced,
|
|
32
|
+
policy: input.policy,
|
|
33
|
+
request: {
|
|
34
|
+
...(input.request?.profile ? { profile: input.request.profile } : { profile: 'implementation' }),
|
|
35
|
+
seedNodeIds: slice.protectedNodeIds,
|
|
36
|
+
...(seedPaths.length ? { seedPaths } : {})
|
|
37
|
+
},
|
|
38
|
+
filter: {
|
|
39
|
+
kinds: ['module', 'file', 'symbol', 'test', 'gate', 'command', 'route'],
|
|
40
|
+
edgeTypes: ['imports', 'calls', 'tests', 'gated_by', 'routes_to', 'depends_on', 'contains']
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { MERMAID_PROJECTION_SCHEMA } from '../../architecture/contracts.js';
|
|
2
|
+
export { MERMAID_PROJECTION_SCHEMA };
|
|
3
|
+
export const GENERATED_HEADER = '%% GENERATED BY SKS. DO NOT EDIT. SOURCE: TriWiki Context Graph + machine-readable control manifests. %%';
|
|
4
|
+
export const MERMAID_TEXT_LIMIT = 50_000;
|
|
5
|
+
export const MERMAID_LABEL_DISPLAY_LIMIT = 96;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MERMAID_LABEL_DISPLAY_LIMIT } from './contracts.js';
|
|
2
|
+
const MERMAID_EMPTY_LABEL = '(unlabeled)';
|
|
3
|
+
const ELLIPSIS = '...';
|
|
4
|
+
const INVISIBLE_FORMAT = /[\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/g;
|
|
5
|
+
const CONTROL_RANGE = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
6
|
+
const WHITESPACE_RUN = /\s+/g;
|
|
7
|
+
const HOME_PATH = /(?:^|[\s"'])(?:\/Users|\/home)\/[^\s"']+/g;
|
|
8
|
+
const SECRETISH = /\b(?:sk-[A-Za-z0-9_-]{8,}|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g;
|
|
9
|
+
const MERMAID_ESCAPES = [
|
|
10
|
+
[/#/g, '#35;'],
|
|
11
|
+
[/"/g, '#34;'],
|
|
12
|
+
[/</g, '#60;'],
|
|
13
|
+
[/>/g, '#62;'],
|
|
14
|
+
[/\|/g, '#124;'],
|
|
15
|
+
[/`/g, "'"]
|
|
16
|
+
];
|
|
17
|
+
function boundCodePoints(text, limit) {
|
|
18
|
+
const points = Array.from(text);
|
|
19
|
+
if (points.length <= limit)
|
|
20
|
+
return text;
|
|
21
|
+
return `${points.slice(0, Math.max(1, limit - ELLIPSIS.length)).join('')}${ELLIPSIS}`;
|
|
22
|
+
}
|
|
23
|
+
export function mermaidLabel(text, displayLimit = MERMAID_LABEL_DISPLAY_LIMIT) {
|
|
24
|
+
if (!Number.isInteger(displayLimit) || displayLimit < 8) {
|
|
25
|
+
throw new Error('mermaid_display_limit_out_of_range');
|
|
26
|
+
}
|
|
27
|
+
const normalized = String(text ?? '')
|
|
28
|
+
.normalize('NFC')
|
|
29
|
+
.replace(INVISIBLE_FORMAT, '')
|
|
30
|
+
.replace(CONTROL_RANGE, ' ')
|
|
31
|
+
.replace(HOME_PATH, '[redacted-path]')
|
|
32
|
+
.replace(SECRETISH, '[redacted-secret]')
|
|
33
|
+
.replace(WHITESPACE_RUN, ' ')
|
|
34
|
+
.trim();
|
|
35
|
+
const bounded = boundCodePoints(normalized, displayLimit);
|
|
36
|
+
if (!bounded)
|
|
37
|
+
return MERMAID_EMPTY_LABEL;
|
|
38
|
+
let escaped = bounded;
|
|
39
|
+
for (const [pattern, replacement] of MERMAID_ESCAPES)
|
|
40
|
+
escaped = escaped.replace(pattern, replacement);
|
|
41
|
+
return escaped;
|
|
42
|
+
}
|
|
43
|
+
export function asRelationLabel(text) {
|
|
44
|
+
return mermaidLabel(text, 48);
|
|
45
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { sha256 } from '../../../../fsx.js';
|
|
2
|
+
const BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
|
|
3
|
+
const DIGEST_CHARS = 12;
|
|
4
|
+
function base32Lower(bytes) {
|
|
5
|
+
let out = '';
|
|
6
|
+
let value = 0;
|
|
7
|
+
let bits = 0;
|
|
8
|
+
for (const byte of bytes) {
|
|
9
|
+
value = (value << 8) | byte;
|
|
10
|
+
bits += 8;
|
|
11
|
+
while (bits >= 5) {
|
|
12
|
+
bits -= 5;
|
|
13
|
+
out += BASE32_ALPHABET.charAt((value >>> bits) & 31);
|
|
14
|
+
}
|
|
15
|
+
value &= (1 << bits) - 1;
|
|
16
|
+
}
|
|
17
|
+
if (bits > 0)
|
|
18
|
+
out += BASE32_ALPHABET.charAt((value << (5 - bits)) & 31);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function digestToken(value, chars) {
|
|
22
|
+
return base32Lower(Buffer.from(sha256(value), 'hex')).slice(0, chars);
|
|
23
|
+
}
|
|
24
|
+
function joinInjective(parts) {
|
|
25
|
+
return parts.map((part) => `${part.length}:${part}`).join('');
|
|
26
|
+
}
|
|
27
|
+
export function mermaidNodeId(sourceId) {
|
|
28
|
+
if (typeof sourceId !== 'string' || sourceId.length === 0) {
|
|
29
|
+
throw new Error('mermaid_empty_source_id');
|
|
30
|
+
}
|
|
31
|
+
for (const length of [DIGEST_CHARS, 16, 24, 32, 64]) {
|
|
32
|
+
const id = `n_${digestToken(sourceId, length)}`;
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
throw new Error('MERMAID_NODE_ID_COLLISION');
|
|
36
|
+
}
|
|
37
|
+
export function mermaidSubgraphId(sourceId) {
|
|
38
|
+
if (typeof sourceId !== 'string' || sourceId.length === 0) {
|
|
39
|
+
throw new Error('mermaid_empty_subgraph_source_id');
|
|
40
|
+
}
|
|
41
|
+
return `sg_${digestToken(sourceId, DIGEST_CHARS)}`;
|
|
42
|
+
}
|
|
43
|
+
export function mermaidEdgeId(fromSourceId, toSourceId, edgeType) {
|
|
44
|
+
if (!fromSourceId || !toSourceId || !edgeType)
|
|
45
|
+
throw new Error('mermaid_empty_edge_endpoint');
|
|
46
|
+
return `e_${digestToken(joinInjective([fromSourceId, toSourceId, edgeType]), DIGEST_CHARS)}`;
|
|
47
|
+
}
|
|
48
|
+
export function assertInjective(sourceIds) {
|
|
49
|
+
const bySourceId = new Map();
|
|
50
|
+
const owners = new Map();
|
|
51
|
+
for (const sourceId of sourceIds) {
|
|
52
|
+
if (bySourceId.has(sourceId))
|
|
53
|
+
throw new Error(`mermaid_duplicate_source_id: ${sourceId}`);
|
|
54
|
+
let assigned = null;
|
|
55
|
+
for (const length of [DIGEST_CHARS, 16, 24, 32, 64]) {
|
|
56
|
+
const candidate = `n_${digestToken(sourceId, length)}`;
|
|
57
|
+
const owner = owners.get(candidate);
|
|
58
|
+
if (owner === undefined) {
|
|
59
|
+
owners.set(candidate, sourceId);
|
|
60
|
+
assigned = candidate;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (owner === sourceId) {
|
|
64
|
+
assigned = candidate;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!assigned)
|
|
69
|
+
throw new Error(`mermaid_node_id_collision: ${sourceId}`);
|
|
70
|
+
bySourceId.set(sourceId, assigned);
|
|
71
|
+
}
|
|
72
|
+
return bySourceId;
|
|
73
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { GLOBAL_ARCHITECTURE_MAP_VIEW_IDS } from '../../architecture/contracts.js';
|
|
2
|
+
import { analyzeArchitectureFindings } from '../../architecture/findings.js';
|
|
3
|
+
import { buildArchitectureInputBundle, policyContentHash } from '../../architecture/input-bundle.js';
|
|
4
|
+
import { computeArchitectureMetrics } from '../../architecture/metrics.js';
|
|
5
|
+
import { mergeGraphWithTopology } from '../../architecture/topology-overlay.js';
|
|
6
|
+
import { buildArchitectureMapManifest } from './manifest.js';
|
|
7
|
+
import { buildModuleDependencyView } from './module-dependency.js';
|
|
8
|
+
import { buildProjectTopologyView } from './project-topology.js';
|
|
9
|
+
import { buildPublicSurfaceView } from './public-surface.js';
|
|
10
|
+
import { buildRiskDomainsView } from './risk-domains.js';
|
|
11
|
+
import { buildRuntimeControlView } from './runtime-control.js';
|
|
12
|
+
import { buildSsotProvenanceView } from './ssot-provenance.js';
|
|
13
|
+
import { buildVerificationCoverageView } from './verification-coverage.js';
|
|
14
|
+
import { viewSpecFor } from './view-specs.js';
|
|
15
|
+
export * from './contracts.js';
|
|
16
|
+
export * from './ast.js';
|
|
17
|
+
export * from './ids.js';
|
|
18
|
+
export * from './escape.js';
|
|
19
|
+
export * from './serializer.js';
|
|
20
|
+
export * from './selection-ledger.js';
|
|
21
|
+
export * from './reduce.js';
|
|
22
|
+
export * from './view-specs.js';
|
|
23
|
+
export * from './view-builder.js';
|
|
24
|
+
export * from './view-builder-common.js';
|
|
25
|
+
export * from './project-topology.js';
|
|
26
|
+
export * from './module-dependency.js';
|
|
27
|
+
export * from './public-surface.js';
|
|
28
|
+
export * from './ssot-provenance.js';
|
|
29
|
+
export * from './runtime-control.js';
|
|
30
|
+
export * from './verification-coverage.js';
|
|
31
|
+
export * from './risk-domains.js';
|
|
32
|
+
export * from './change-impact.js';
|
|
33
|
+
export * from './architecture-delta.js';
|
|
34
|
+
export * from './ownership-workstream.js';
|
|
35
|
+
export * from './manifest.js';
|
|
36
|
+
const GLOBAL_BUILDERS = {
|
|
37
|
+
'project-topology': buildProjectTopologyView,
|
|
38
|
+
'module-dependency': buildModuleDependencyView,
|
|
39
|
+
'public-surface': buildPublicSurfaceView,
|
|
40
|
+
'ssot-provenance': buildSsotProvenanceView,
|
|
41
|
+
'runtime-control': buildRuntimeControlView,
|
|
42
|
+
'verification-coverage': buildVerificationCoverageView,
|
|
43
|
+
'risk-domains': buildRiskDomainsView
|
|
44
|
+
};
|
|
45
|
+
function asProjection(built) {
|
|
46
|
+
return Object.freeze({
|
|
47
|
+
schema: built.schema,
|
|
48
|
+
viewId: built.viewId,
|
|
49
|
+
direction: built.direction,
|
|
50
|
+
title: built.title,
|
|
51
|
+
source: built.source,
|
|
52
|
+
accounting: built.accounting,
|
|
53
|
+
contentHash: built.contentHash,
|
|
54
|
+
byteLength: built.byteLength
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function buildArchitectureMapViews(snapshot, policy, options = {}) {
|
|
58
|
+
const rootId = options.rootId ?? 'workspace';
|
|
59
|
+
const bundle = buildArchitectureInputBundle({
|
|
60
|
+
rootId,
|
|
61
|
+
graph: snapshot,
|
|
62
|
+
policy,
|
|
63
|
+
topologyMode: 'from-snapshot'
|
|
64
|
+
});
|
|
65
|
+
const merged = mergeGraphWithTopology(bundle.graph, bundle.topology);
|
|
66
|
+
const workingSnapshot = {
|
|
67
|
+
...snapshot,
|
|
68
|
+
nodes: [...merged.nodes],
|
|
69
|
+
edges: [...merged.edges],
|
|
70
|
+
nodeCount: merged.nodes.length,
|
|
71
|
+
edgeCount: merged.edges.length
|
|
72
|
+
};
|
|
73
|
+
const views = [];
|
|
74
|
+
for (const viewId of GLOBAL_ARCHITECTURE_MAP_VIEW_IDS) {
|
|
75
|
+
const built = GLOBAL_BUILDERS[viewId]({ snapshot: workingSnapshot, policy });
|
|
76
|
+
const spec = viewSpecFor(viewId);
|
|
77
|
+
views.push(Object.freeze({
|
|
78
|
+
viewId,
|
|
79
|
+
filename: spec.filename,
|
|
80
|
+
text: built.text,
|
|
81
|
+
projection: asProjection(built)
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
const manifest = buildArchitectureMapManifest({
|
|
85
|
+
views: views.map((view) => ({
|
|
86
|
+
viewId: view.viewId,
|
|
87
|
+
projection: view.projection,
|
|
88
|
+
text: view.text
|
|
89
|
+
})),
|
|
90
|
+
graphHash: bundle.graphHash,
|
|
91
|
+
policyHash: bundle.policyHash.length ? bundle.policyHash : policyContentHash(policy),
|
|
92
|
+
topologyHash: bundle.topologyHash,
|
|
93
|
+
inputBundleHash: bundle.canonicalHash,
|
|
94
|
+
analyzerVersion: bundle.analyzerVersion,
|
|
95
|
+
serializerVersion: bundle.serializerVersion,
|
|
96
|
+
missionId: options.missionId ?? null
|
|
97
|
+
});
|
|
98
|
+
const findings = analyzeArchitectureFindings({ bundle, policy });
|
|
99
|
+
const metrics = computeArchitectureMetrics({
|
|
100
|
+
graph: workingSnapshot,
|
|
101
|
+
topology: bundle.topology,
|
|
102
|
+
layerViolationCount: findings.filter((finding) => finding.code === 'forbidden_dependency').length,
|
|
103
|
+
ssotCollisionCount: findings.filter((finding) => finding.code === 'ssot_collision').length,
|
|
104
|
+
authorityBypassCount: findings.filter((finding) => finding.code === 'authority_bypass').length,
|
|
105
|
+
protectedVerificationGapCount: findings.filter((finding) => finding.code === 'protected_verification_gap').length
|
|
106
|
+
});
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
views: Object.freeze(views),
|
|
109
|
+
manifest,
|
|
110
|
+
metrics,
|
|
111
|
+
findings
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ARCHITECTURE_ANALYZER_VERSION, ARCHITECTURE_MAP_MANIFEST_SCHEMA, ARCHITECTURE_SERIALIZER_VERSION, byCodePoint } from '../../architecture/contracts.js';
|
|
2
|
+
import { hashCanonical } from '../../architecture/fingerprint.js';
|
|
3
|
+
export function buildArchitectureMapManifest(input) {
|
|
4
|
+
const sorted = [...input.views].sort((left, right) => byCodePoint(left.viewId, right.viewId));
|
|
5
|
+
const viewRows = Object.freeze(sorted.map((entry) => Object.freeze({
|
|
6
|
+
viewId: entry.viewId,
|
|
7
|
+
contentHash: entry.projection.contentHash,
|
|
8
|
+
byteLength: entry.projection.byteLength
|
|
9
|
+
})));
|
|
10
|
+
const projectionAccounting = Object.freeze(Object.fromEntries(sorted.map((entry) => [entry.viewId, entry.projection.accounting])));
|
|
11
|
+
const topologyHash = input.topologyHash ?? '';
|
|
12
|
+
const inputBundleHash = input.inputBundleHash ?? '';
|
|
13
|
+
const analyzerVersion = input.analyzerVersion ?? ARCHITECTURE_ANALYZER_VERSION;
|
|
14
|
+
const serializerVersion = input.serializerVersion ?? ARCHITECTURE_SERIALIZER_VERSION;
|
|
15
|
+
const sourceBinding = Object.freeze({
|
|
16
|
+
graphHash: input.graphHash,
|
|
17
|
+
topologyHash,
|
|
18
|
+
policyHash: input.policyHash
|
|
19
|
+
});
|
|
20
|
+
const payload = {
|
|
21
|
+
schema: ARCHITECTURE_MAP_MANIFEST_SCHEMA,
|
|
22
|
+
missionId: input.missionId ?? null,
|
|
23
|
+
graphHash: input.graphHash,
|
|
24
|
+
topologyHash,
|
|
25
|
+
policyHash: input.policyHash,
|
|
26
|
+
inputBundleHash,
|
|
27
|
+
analyzerVersion,
|
|
28
|
+
serializerVersion,
|
|
29
|
+
views: viewRows,
|
|
30
|
+
projectionAccounting,
|
|
31
|
+
sourceBinding
|
|
32
|
+
};
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
...payload,
|
|
35
|
+
canonicalHash: hashCanonical(payload)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { layerForModule } from '../../architecture/policy.js';
|
|
2
|
+
import { projectFilteredView } from './view-builder.js';
|
|
3
|
+
function modulePath(nodeId, path) {
|
|
4
|
+
if (path)
|
|
5
|
+
return path;
|
|
6
|
+
return nodeId.startsWith('module:') ? nodeId.slice('module:'.length) : nodeId;
|
|
7
|
+
}
|
|
8
|
+
export function buildModuleDependencyView(input) {
|
|
9
|
+
return projectFilteredView({
|
|
10
|
+
viewId: 'module-dependency',
|
|
11
|
+
snapshot: input.snapshot,
|
|
12
|
+
policy: input.policy,
|
|
13
|
+
...(input.request ? { request: input.request } : {}),
|
|
14
|
+
filter: {
|
|
15
|
+
kinds: ['module'],
|
|
16
|
+
edgeTypes: ['imports', 'reexports']
|
|
17
|
+
},
|
|
18
|
+
groupByLayer: true,
|
|
19
|
+
layerOf: (node, policy) => layerForModule(policy, modulePath(node.id, node.path))
|
|
20
|
+
});
|
|
21
|
+
}
|