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.
Files changed (105) hide show
  1. package/README.md +1 -1
  2. package/config/architecture-map-policy.v1.json +168 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/bridge.js +19 -1
  6. package/dist/commands/doctor-profile.js +1 -1
  7. package/dist/commands/doctor.js +33 -1
  8. package/dist/config/skills-manifest.json +1 -1
  9. package/dist/core/align/align-route.js +8 -2
  10. package/dist/core/align/code-navigation-align.js +88 -32
  11. package/dist/core/architecture-map-pipeline.js +172 -0
  12. package/dist/core/architecture-map-review.js +180 -0
  13. package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
  14. package/dist/core/codex-app/menubar/constants.js +1 -0
  15. package/dist/core/codex-lb/codex-lb-env.js +25 -3
  16. package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
  17. package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
  18. package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
  19. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
  20. package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
  21. package/dist/core/commands/triwiki-atlas-command.js +372 -0
  22. package/dist/core/commands/triwiki-command.js +12 -1
  23. package/dist/core/fsx.js +14 -0
  24. package/dist/core/git-hygiene/git-policy.js +4 -0
  25. package/dist/core/git-hygiene/gitignore-writer.js +2 -0
  26. package/dist/core/gx-renderer.js +2 -1
  27. package/dist/core/managed-paths.js +1 -0
  28. package/dist/core/perf/release-latency-slo.js +57 -11
  29. package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
  30. package/dist/core/pipeline-internals/runtime-core.js +13 -172
  31. package/dist/core/pipeline-internals/runtime-gates.js +12 -1
  32. package/dist/core/release/content-digest-cache.js +70 -0
  33. package/dist/core/release/gate-manifest.js +9 -0
  34. package/dist/core/release/main-push-guard.js +4 -3
  35. package/dist/core/release/package-dist-snapshot.js +18 -2
  36. package/dist/core/release/physical-release-gates.js +26 -3
  37. package/dist/core/release/release-authorization-snapshot.js +4 -0
  38. package/dist/core/release/release-gate-cache-v2.js +27 -3
  39. package/dist/core/release/release-gate-contract.js +2 -0
  40. package/dist/core/release/release-gate-dag.js +3 -0
  41. package/dist/core/release/release-inspection-memo.js +69 -0
  42. package/dist/core/release/release-pack-content-scanner.js +5 -0
  43. package/dist/core/release/release-pack-receipt.js +28 -18
  44. package/dist/core/release/release-pack-tarball.js +10 -0
  45. package/dist/core/release/release-upgrade-baseline.js +4 -0
  46. package/dist/core/routes.js +1 -1
  47. package/dist/core/safety/ssot-authority-inventory.js +114 -0
  48. package/dist/core/safety/ssot-guard.js +2 -44
  49. package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
  50. package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
  51. package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
  52. package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
  53. package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
  54. package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
  55. package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
  56. package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
  57. package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
  58. package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
  59. package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
  60. package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
  61. package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
  62. package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
  63. package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
  64. package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
  65. package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
  66. package/dist/core/triwiki/context-graph/projections/index.js +2 -0
  67. package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
  68. package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
  69. package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
  70. package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
  71. package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
  72. package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
  73. package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
  74. package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
  75. package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
  76. package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
  77. package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
  78. package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
  79. package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
  80. package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
  81. package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
  82. package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
  83. package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
  84. package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
  85. package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
  86. package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
  87. package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
  88. package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
  89. package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
  90. package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
  91. package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
  92. package/dist/core/version.js +1 -1
  93. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
  94. package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
  95. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
  96. package/dist/scripts/architecture-map-check.js +299 -0
  97. package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
  98. package/dist/scripts/release-metadata-check.js +2 -0
  99. package/package.json +6 -3
  100. package/release-gates.v2.json +204 -0
  101. package/schemas/architecture-baseline.v1.schema.json +47 -0
  102. package/schemas/architecture-map-manifest.v1.schema.json +40 -0
  103. package/schemas/architecture-map-policy.v1.schema.json +147 -0
  104. package/schemas/architecture-review.v1.schema.json +46 -0
  105. package/schemas/mermaid-projection.v1.schema.json +47 -0
@@ -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 buildOwnershipWorkstreamView(input) {
9
+ return projectFilteredView({
10
+ viewId: 'ownership-workstream',
11
+ snapshot: input.snapshot,
12
+ policy: input.policy,
13
+ ...(input.request ? { request: input.request } : {}),
14
+ filter: {
15
+ kinds: ['module'],
16
+ edgeTypes: ['owns', 'depends_on', 'imports']
17
+ },
18
+ groupByLayer: true,
19
+ layerOf: (node, policy) => layerForModule(policy, modulePath(node.id, node.path))
20
+ });
21
+ }
@@ -0,0 +1,13 @@
1
+ import { projectFilteredView } from './view-builder.js';
2
+ export function buildProjectTopologyView(input) {
3
+ return projectFilteredView({
4
+ viewId: 'project-topology',
5
+ snapshot: input.snapshot,
6
+ policy: input.policy,
7
+ ...(input.request ? { request: input.request } : {}),
8
+ filter: {
9
+ kinds: ['module', 'file', 'command', 'route'],
10
+ edgeTypes: ['contains', 'depends_on', 'routes_to']
11
+ }
12
+ });
13
+ }
@@ -0,0 +1,17 @@
1
+ import { projectFilteredView } from './view-builder.js';
2
+ export function buildPublicSurfaceView(input) {
3
+ const edgeTypes = ['reexports', 'owns', 'routes_to'];
4
+ if (input.snapshot.edges.some((edge) => edge.type === 'exports')) {
5
+ edgeTypes.push('exports');
6
+ }
7
+ return projectFilteredView({
8
+ viewId: 'public-surface',
9
+ snapshot: input.snapshot,
10
+ policy: input.policy,
11
+ ...(input.request ? { request: input.request } : {}),
12
+ filter: {
13
+ kinds: ['command', 'route', 'schema', 'config'],
14
+ edgeTypes
15
+ }
16
+ });
17
+ }
@@ -0,0 +1,44 @@
1
+ import { byCodePoint } from '../../architecture/contracts.js';
2
+ import { SelectionLedger } from './selection-ledger.js';
3
+ export function reduceForBudget(input) {
4
+ const ledger = new SelectionLedger();
5
+ const protectedIds = input.protectedNodeIds ?? new Set();
6
+ const sortedNodes = [...input.nodes].sort((left, right) => byCodePoint(left.id, right.id));
7
+ for (const node of sortedNodes)
8
+ ledger.selectNode(node.id);
9
+ const admitted = [];
10
+ for (const node of sortedNodes) {
11
+ if (protectedIds.has(node.id)) {
12
+ admitted.push(node);
13
+ ledger.emitNode(node.id);
14
+ continue;
15
+ }
16
+ if (admitted.length >= input.budget.maxNodes) {
17
+ ledger.omitNode(node.id, 'budget_exhausted_after_protected_set');
18
+ continue;
19
+ }
20
+ admitted.push(node);
21
+ ledger.emitNode(node.id);
22
+ }
23
+ const admittedIds = new Set(admitted.map((node) => node.id));
24
+ const sortedEdges = [...input.edges].sort((left, right) => byCodePoint(left.id, right.id));
25
+ const admittedEdges = [];
26
+ for (const edge of sortedEdges) {
27
+ ledger.selectEdge(edge.id);
28
+ if (!admittedIds.has(edge.from) || !admittedIds.has(edge.to)) {
29
+ ledger.omitEdge(edge.id, 'outside_profile_scope', 'endpoint omitted');
30
+ continue;
31
+ }
32
+ if (admittedEdges.length >= input.budget.maxEdges) {
33
+ ledger.omitEdge(edge.id, 'budget_exhausted_after_protected_set');
34
+ continue;
35
+ }
36
+ admittedEdges.push(edge);
37
+ ledger.emitEdge(edge.id);
38
+ }
39
+ return {
40
+ nodes: Object.freeze(admitted),
41
+ edges: Object.freeze(admittedEdges),
42
+ ledger
43
+ };
44
+ }
@@ -0,0 +1,23 @@
1
+ import { projectFilteredView } from './view-builder.js';
2
+ export function buildRiskDomainsView(input) {
3
+ const highRiskIds = input.snapshot.nodes
4
+ .filter((node) => node.kind === 'module' && (node.risk === 'high' || node.risk === 'protected'))
5
+ .map((node) => node.id);
6
+ const findingSubjectIds = [...(input.request?.findingSubjectIds ?? []), ...highRiskIds];
7
+ const request = {
8
+ ...(input.request?.profile ? { profile: input.request.profile } : {}),
9
+ ...(input.request?.seedNodeIds ? { seedNodeIds: input.request.seedNodeIds } : {}),
10
+ ...(input.request?.seedPaths ? { seedPaths: input.request.seedPaths } : {}),
11
+ ...(findingSubjectIds.length ? { findingSubjectIds } : {})
12
+ };
13
+ return projectFilteredView({
14
+ viewId: 'risk-domains',
15
+ snapshot: input.snapshot,
16
+ policy: input.policy,
17
+ request,
18
+ filter: {
19
+ kinds: ['risk_domain', 'module'],
20
+ edgeTypes: ['affected_by', 'owns']
21
+ }
22
+ });
23
+ }
@@ -0,0 +1,20 @@
1
+ import { emptyProjection, projectFilteredView } from './view-builder.js';
2
+ export function buildRuntimeControlView(input) {
3
+ const hasControl = input.snapshot.nodes.some((node) => node.kind === 'route' ||
4
+ node.kind === 'pipeline' ||
5
+ node.kind === 'gate' ||
6
+ node.kind === 'command');
7
+ if (!hasControl) {
8
+ return emptyProjection('runtime-control', 'Runtime control', 'TD', 'insufficient_graph: no route/pipeline/gate/command nodes');
9
+ }
10
+ return projectFilteredView({
11
+ viewId: 'runtime-control',
12
+ snapshot: input.snapshot,
13
+ policy: input.policy,
14
+ ...(input.request ? { request: input.request } : {}),
15
+ filter: {
16
+ kinds: ['route', 'pipeline', 'gate', 'command'],
17
+ edgeTypes: ['routes_to', 'gated_by', 'owns']
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,94 @@
1
+ import { byCodePoint } from '../../architecture/contracts.js';
2
+ export class SelectionLedger {
3
+ selectedNodes = new Set();
4
+ emittedNodes = new Set();
5
+ omittedNodes = [];
6
+ aggregatedNodes = new Map();
7
+ selectedEdges = new Set();
8
+ emittedEdges = new Set();
9
+ omittedEdges = [];
10
+ aggregatedEdges = new Map();
11
+ selectNode(id) {
12
+ this.selectedNodes.add(id);
13
+ }
14
+ emitNode(id) {
15
+ this.selectedNodes.add(id);
16
+ this.emittedNodes.add(id);
17
+ }
18
+ omitNode(id, reason, detail) {
19
+ this.selectedNodes.add(id);
20
+ const omission = detail === undefined
21
+ ? { id, reason }
22
+ : { id, reason, detail };
23
+ this.omittedNodes.push(Object.freeze(omission));
24
+ }
25
+ aggregateNodes(aggregateId, memberIds) {
26
+ this.selectedNodes.add(aggregateId);
27
+ for (const id of memberIds)
28
+ this.selectedNodes.add(id);
29
+ this.emittedNodes.add(aggregateId);
30
+ this.aggregatedNodes.set(aggregateId, [...memberIds].sort(byCodePoint));
31
+ }
32
+ selectEdge(id) {
33
+ this.selectedEdges.add(id);
34
+ }
35
+ emitEdge(id) {
36
+ this.selectedEdges.add(id);
37
+ this.emittedEdges.add(id);
38
+ }
39
+ omitEdge(id, reason, detail) {
40
+ this.selectedEdges.add(id);
41
+ const omission = detail === undefined
42
+ ? { id, reason }
43
+ : { id, reason, detail };
44
+ this.omittedEdges.push(Object.freeze(omission));
45
+ }
46
+ aggregateEdges(aggregateId, memberIds) {
47
+ this.selectedEdges.add(aggregateId);
48
+ for (const id of memberIds)
49
+ this.selectedEdges.add(id);
50
+ this.emittedEdges.add(aggregateId);
51
+ this.aggregatedEdges.set(aggregateId, [...memberIds].sort(byCodePoint));
52
+ }
53
+ toAccounting() {
54
+ return Object.freeze({
55
+ selectedNodeIds: Object.freeze([...this.selectedNodes].sort(byCodePoint)),
56
+ emittedNodeIds: Object.freeze([...this.emittedNodes].sort(byCodePoint)),
57
+ aggregatedNodeMembers: Object.freeze(Object.fromEntries([...this.aggregatedNodes.entries()]
58
+ .sort(([left], [right]) => byCodePoint(left, right))
59
+ .map(([key, value]) => [key, Object.freeze(value)]))),
60
+ omittedNodes: Object.freeze([...this.omittedNodes].sort((left, right) => byCodePoint(left.id, right.id))),
61
+ selectedEdgeIds: Object.freeze([...this.selectedEdges].sort(byCodePoint)),
62
+ emittedEdgeIds: Object.freeze([...this.emittedEdges].sort(byCodePoint)),
63
+ aggregatedEdgeMembers: Object.freeze(Object.fromEntries([...this.aggregatedEdges.entries()]
64
+ .sort(([left], [right]) => byCodePoint(left, right))
65
+ .map(([key, value]) => [key, Object.freeze(value)]))),
66
+ omittedEdges: Object.freeze([...this.omittedEdges].sort((left, right) => byCodePoint(left.id, right.id)))
67
+ });
68
+ }
69
+ assertBalanced() {
70
+ const accounting = this.toAccounting();
71
+ const nodeCovered = new Set([
72
+ ...accounting.emittedNodeIds,
73
+ ...Object.values(accounting.aggregatedNodeMembers).flat(),
74
+ ...accounting.omittedNodes.map((entry) => entry.id)
75
+ ]);
76
+ for (const id of accounting.selectedNodeIds) {
77
+ if (!nodeCovered.has(id) && !accounting.emittedNodeIds.includes(id)) {
78
+ if (!Object.prototype.hasOwnProperty.call(accounting.aggregatedNodeMembers, id)) {
79
+ throw new Error(`projection_node_unaccounted: ${id}`);
80
+ }
81
+ }
82
+ }
83
+ const edgeCovered = new Set([
84
+ ...accounting.emittedEdgeIds,
85
+ ...Object.values(accounting.aggregatedEdgeMembers).flat(),
86
+ ...accounting.omittedEdges.map((entry) => entry.id)
87
+ ]);
88
+ for (const id of accounting.selectedEdgeIds) {
89
+ if (!edgeCovered.has(id) && !Object.prototype.hasOwnProperty.call(accounting.aggregatedEdgeMembers, id)) {
90
+ throw new Error(`projection_edge_unaccounted: ${id}`);
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,81 @@
1
+ import { byCodePoint } from '../../architecture/contracts.js';
2
+ import { hashCanonical } from '../../architecture/fingerprint.js';
3
+ import { GENERATED_HEADER, MERMAID_TEXT_LIMIT } from './contracts.js';
4
+ import { MERMAID_PROJECTION_SCHEMA } from './contracts.js';
5
+ function nodePrimaryCanonical(node) {
6
+ return node.canonicalNodeIds[0] ?? node.id;
7
+ }
8
+ function edgeSortKey(edge) {
9
+ return [edge.fromCanonicalId, edge.relation, edge.toCanonicalId, edge.canonicalEdgeIds[0] ?? ''].join('\0');
10
+ }
11
+ function sortStatements(statements) {
12
+ const comments = statements.filter((entry) => entry.kind === 'comment');
13
+ const subgraphs = statements
14
+ .filter((entry) => entry.kind === 'subgraph')
15
+ .sort((left, right) => byCodePoint(left.canonicalId, right.canonicalId));
16
+ const nodes = statements
17
+ .filter((entry) => entry.kind === 'node')
18
+ .sort((left, right) => byCodePoint(nodePrimaryCanonical(left), nodePrimaryCanonical(right)));
19
+ const edges = statements
20
+ .filter((entry) => entry.kind === 'edge')
21
+ .sort((left, right) => byCodePoint(edgeSortKey(left), edgeSortKey(right)));
22
+ return [...comments, ...subgraphs, ...nodes, ...edges];
23
+ }
24
+ function renderStatement(statement, indent) {
25
+ if (statement.kind === 'comment')
26
+ return [`${indent}%% ${statement.text}`];
27
+ if (statement.kind === 'node')
28
+ return [`${indent}${statement.id}["${statement.label}"]`];
29
+ if (statement.kind === 'edge') {
30
+ const arrow = statement.style === 'dotted' ? '-.->' : '-->';
31
+ return [`${indent}${statement.from} ${arrow}|"${statement.relation}"| ${statement.to}`];
32
+ }
33
+ const lines = [`${indent}subgraph ${statement.id}["${statement.label}"]`];
34
+ for (const child of sortStatements(statement.statements)) {
35
+ lines.push(...renderStatement(child, `${indent} `));
36
+ }
37
+ lines.push(`${indent}end`);
38
+ return lines;
39
+ }
40
+ export function serializeMermaidDocument(doc) {
41
+ const lines = [
42
+ GENERATED_HEADER,
43
+ `%% title: ${doc.title}`,
44
+ `flowchart ${doc.direction}`
45
+ ];
46
+ for (const statement of sortStatements(doc.statements)) {
47
+ lines.push(...renderStatement(statement, ''));
48
+ }
49
+ const text = `${lines.join('\n')}\n`;
50
+ const bytes = Buffer.byteLength(text, 'utf8');
51
+ if (bytes > MERMAID_TEXT_LIMIT) {
52
+ throw new Error(`mermaid_budget_overrun: ${bytes}>${MERMAID_TEXT_LIMIT}`);
53
+ }
54
+ return text;
55
+ }
56
+ export function emptyAccounting() {
57
+ return Object.freeze({
58
+ selectedNodeIds: Object.freeze([]),
59
+ emittedNodeIds: Object.freeze([]),
60
+ aggregatedNodeMembers: Object.freeze({}),
61
+ omittedNodes: Object.freeze([]),
62
+ selectedEdgeIds: Object.freeze([]),
63
+ emittedEdgeIds: Object.freeze([]),
64
+ aggregatedEdgeMembers: Object.freeze({}),
65
+ omittedEdges: Object.freeze([])
66
+ });
67
+ }
68
+ export function toMermaidProjection(input) {
69
+ const text = serializeMermaidDocument(input.doc);
70
+ return Object.freeze({
71
+ schema: MERMAID_PROJECTION_SCHEMA,
72
+ viewId: input.viewId,
73
+ direction: input.doc.direction,
74
+ title: input.doc.title,
75
+ source: text,
76
+ accounting: input.accounting,
77
+ contentHash: hashCanonical(text),
78
+ byteLength: Buffer.byteLength(text, 'utf8'),
79
+ text
80
+ });
81
+ }
@@ -0,0 +1,17 @@
1
+ import { emptyProjection, projectFilteredView } from './view-builder.js';
2
+ export function buildSsotProvenanceView(input) {
3
+ const hasSsotKinds = input.snapshot.nodes.some((node) => node.kind === 'source' || node.kind === 'wiki_claim' || node.kind === 'proof');
4
+ if (!hasSsotKinds) {
5
+ return emptyProjection('ssot-provenance', 'SSOT provenance', 'TD', 'insufficient_graph: no source/wiki_claim/proof nodes');
6
+ }
7
+ return projectFilteredView({
8
+ viewId: 'ssot-provenance',
9
+ snapshot: input.snapshot,
10
+ policy: input.policy,
11
+ ...(input.request ? { request: input.request } : {}),
12
+ filter: {
13
+ kinds: ['source', 'wiki_claim', 'proof'],
14
+ edgeTypes: ['cites', 'derived_from', 'verified_by']
15
+ }
16
+ });
17
+ }
@@ -0,0 +1,17 @@
1
+ import { emptyProjection, projectFilteredView } from './view-builder.js';
2
+ export function buildVerificationCoverageView(input) {
3
+ const hasVerification = input.snapshot.nodes.some((node) => node.kind === 'test' || node.kind === 'gate' || node.kind === 'proof');
4
+ if (!hasVerification) {
5
+ return emptyProjection('verification-coverage', 'Verification coverage', 'LR', 'insufficient_graph: no test/gate/proof nodes');
6
+ }
7
+ return projectFilteredView({
8
+ viewId: 'verification-coverage',
9
+ snapshot: input.snapshot,
10
+ policy: input.policy,
11
+ ...(input.request ? { request: input.request } : {}),
12
+ filter: {
13
+ kinds: ['test', 'gate', 'proof', 'module'],
14
+ edgeTypes: ['tests', 'verified_by', 'gated_by']
15
+ }
16
+ });
17
+ }
@@ -0,0 +1 @@
1
+ export { buildMermaidView, emptyMermaidView, emptyProjection, projectFilteredView } from './view-builder.js';
@@ -0,0 +1,173 @@
1
+ import { byCodePoint } from '../../architecture/contracts.js';
2
+ import * as ast from './ast.js';
3
+ import { assertInjective, mermaidEdgeId, mermaidNodeId, mermaidSubgraphId } from './ids.js';
4
+ import { reduceForBudget } from './reduce.js';
5
+ import { serializeMermaidDocument, toMermaidProjection } from './serializer.js';
6
+ import { viewSpecFor } from './view-specs.js';
7
+ function labelFor(node) {
8
+ return node.label || node.path || node.id;
9
+ }
10
+ function filterGraph(snapshot, filter) {
11
+ const kindSet = new Set(filter.kinds);
12
+ const edgeSet = new Set(filter.edgeTypes);
13
+ const nodes = snapshot.nodes
14
+ .filter((node) => kindSet.has(node.kind))
15
+ .sort((left, right) => byCodePoint(left.id, right.id));
16
+ const candidateIds = new Set(nodes.map((node) => node.id));
17
+ const edges = snapshot.edges
18
+ .filter((edge) => edgeSet.has(edge.type) && candidateIds.has(edge.from) && candidateIds.has(edge.to))
19
+ .sort((left, right) => byCodePoint(left.id, right.id));
20
+ return { nodes, edges };
21
+ }
22
+ export function buildMermaidView(input) {
23
+ const spec = viewSpecFor(input.viewId);
24
+ const { nodes: candidateNodes, edges: candidateEdges } = filterGraph(input.snapshot, input.filter);
25
+ const protectedIds = new Set(input.protectedNodeIds ?? []);
26
+ const profileKey = input.profile ?? 'global';
27
+ const budget = input.policy.profiles[profileKey];
28
+ const reduced = reduceForBudget({
29
+ nodes: candidateNodes,
30
+ edges: candidateEdges,
31
+ budget,
32
+ protectedNodeIds: protectedIds
33
+ });
34
+ const idMap = assertInjective(reduced.nodes.map((node) => node.id));
35
+ const statements = [ast.comment(`atlas-view: ${input.viewId}`)];
36
+ if (input.groupByLayer && input.layerOf) {
37
+ const byLayer = new Map();
38
+ for (const node of reduced.nodes) {
39
+ const layer = input.layerOf(node, input.policy) ?? 'unassigned';
40
+ const list = byLayer.get(layer) ?? [];
41
+ list.push(node);
42
+ byLayer.set(layer, list);
43
+ }
44
+ for (const layer of [...byLayer.keys()].sort(byCodePoint)) {
45
+ const nodes = byLayer.get(layer) ?? [];
46
+ const subgraphNodes = nodes.map((node) => ast.node({
47
+ id: idMap.get(node.id),
48
+ label: labelFor(node),
49
+ canonicalNodeIds: [node.id]
50
+ }));
51
+ statements.push(ast.subgraph({
52
+ id: mermaidSubgraphId(`layer:${layer}`),
53
+ label: layer,
54
+ canonicalId: `layer:${layer}`,
55
+ statements: subgraphNodes
56
+ }));
57
+ }
58
+ }
59
+ else {
60
+ for (const node of reduced.nodes) {
61
+ statements.push(ast.node({
62
+ id: idMap.get(node.id),
63
+ label: labelFor(node),
64
+ canonicalNodeIds: [node.id]
65
+ }));
66
+ }
67
+ }
68
+ for (const edge of reduced.edges) {
69
+ const fromId = idMap.get(edge.from);
70
+ const toId = idMap.get(edge.to);
71
+ if (!fromId || !toId)
72
+ continue;
73
+ const style = protectedIds.has(edge.from) || protectedIds.has(edge.to)
74
+ ? 'dotted'
75
+ : 'solid';
76
+ statements.push(ast.edge({
77
+ from: fromId,
78
+ to: toId,
79
+ relation: edge.type,
80
+ style,
81
+ canonicalEdgeIds: [edge.id],
82
+ fromCanonicalId: edge.from,
83
+ toCanonicalId: edge.to
84
+ }));
85
+ void mermaidEdgeId(edge.from, edge.to, edge.type);
86
+ }
87
+ for (const seed of protectedIds) {
88
+ if (!idMap.has(seed))
89
+ void mermaidNodeId(seed);
90
+ }
91
+ reduced.ledger.assertBalanced();
92
+ const doc = ast.document({
93
+ direction: spec.direction,
94
+ title: spec.title,
95
+ statements
96
+ });
97
+ void serializeMermaidDocument(doc);
98
+ const built = toMermaidProjection({
99
+ viewId: input.viewId,
100
+ doc,
101
+ accounting: reduced.ledger.toAccounting()
102
+ });
103
+ return Object.freeze({
104
+ text: built.text,
105
+ projection: Object.freeze({
106
+ schema: built.schema,
107
+ viewId: built.viewId,
108
+ direction: built.direction,
109
+ title: built.title,
110
+ source: built.source,
111
+ accounting: built.accounting,
112
+ contentHash: built.contentHash,
113
+ byteLength: built.byteLength
114
+ })
115
+ });
116
+ }
117
+ export function emptyMermaidView(input) {
118
+ const statements = [ast.comment(input.reason)];
119
+ const reduced = reduceForBudget({
120
+ nodes: [],
121
+ edges: [],
122
+ budget: { maxNodes: 1, maxEdges: 1, maxLabelChars: 56, tokenBudget: 0 }
123
+ });
124
+ const built = toMermaidProjection({
125
+ viewId: input.viewId,
126
+ doc: ast.document({
127
+ direction: input.direction,
128
+ title: input.title,
129
+ statements
130
+ }),
131
+ accounting: reduced.ledger.toAccounting()
132
+ });
133
+ return Object.freeze({
134
+ text: built.text,
135
+ projection: Object.freeze({
136
+ schema: built.schema,
137
+ viewId: built.viewId,
138
+ direction: built.direction,
139
+ title: built.title,
140
+ source: built.source,
141
+ accounting: built.accounting,
142
+ contentHash: built.contentHash,
143
+ byteLength: built.byteLength
144
+ })
145
+ });
146
+ }
147
+ export function projectFilteredView(input) {
148
+ const protectedNodeIds = [
149
+ ...(input.request?.seedNodeIds ?? []),
150
+ ...(input.request?.findingSubjectIds ?? [])
151
+ ];
152
+ const built = buildMermaidView({
153
+ snapshot: input.snapshot,
154
+ policy: input.policy,
155
+ viewId: input.viewId,
156
+ filter: input.filter,
157
+ ...(protectedNodeIds.length ? { protectedNodeIds } : {}),
158
+ ...(input.groupByLayer === true ? { groupByLayer: true } : {}),
159
+ ...(input.layerOf ? { layerOf: input.layerOf } : {}),
160
+ ...(input.request?.profile ? { profile: input.request.profile } : {})
161
+ });
162
+ return Object.freeze({
163
+ ...built.projection,
164
+ text: built.text
165
+ });
166
+ }
167
+ export function emptyProjection(viewId, title, direction, reason) {
168
+ const built = emptyMermaidView({ viewId, title, direction, reason });
169
+ return Object.freeze({
170
+ ...built.projection,
171
+ text: built.text
172
+ });
173
+ }
@@ -0,0 +1,33 @@
1
+ import { ARCHITECTURE_MAP_VIEW_IDS } from '../../architecture/contracts.js';
2
+ const SPECS = Object.freeze([
3
+ { viewId: 'project-topology', title: 'Project topology', direction: 'LR', filename: 'project-topology.mmd' },
4
+ { viewId: 'module-dependency', title: 'Module dependency', direction: 'TD', filename: 'module-dependency.mmd' },
5
+ { viewId: 'public-surface', title: 'Public surface', direction: 'LR', filename: 'public-surface.mmd' },
6
+ { viewId: 'ssot-provenance', title: 'SSOT provenance', direction: 'TD', filename: 'ssot-provenance.mmd' },
7
+ { viewId: 'runtime-control', title: 'Runtime control', direction: 'TD', filename: 'runtime-control.mmd' },
8
+ { viewId: 'verification-coverage', title: 'Verification coverage', direction: 'LR', filename: 'verification-coverage.mmd' },
9
+ { viewId: 'risk-domains', title: 'Risk domains', direction: 'TD', filename: 'risk-domains.mmd' },
10
+ { viewId: 'change-impact', title: 'Change impact', direction: 'LR', filename: 'change-impact.mmd' },
11
+ { viewId: 'architecture-delta', title: 'Architecture delta', direction: 'TD', filename: 'architecture-delta.mmd' },
12
+ { viewId: 'ownership-workstream', title: 'Ownership workstream', direction: 'LR', filename: 'ownership-workstream.mmd' }
13
+ ]);
14
+ export function architectureMapViewSpecs() {
15
+ return SPECS;
16
+ }
17
+ export function viewSpecFor(viewId) {
18
+ const spec = SPECS.find((entry) => entry.viewId === viewId);
19
+ if (!spec)
20
+ throw new Error(`unknown_architecture_map_view: ${viewId}`);
21
+ return spec;
22
+ }
23
+ export function assertViewIdsMatchWo() {
24
+ const fromSpecs = SPECS.map((spec) => spec.viewId);
25
+ if (fromSpecs.length !== ARCHITECTURE_MAP_VIEW_IDS.length) {
26
+ throw new Error('architecture_map_view_id_count_mismatch');
27
+ }
28
+ for (let index = 0; index < ARCHITECTURE_MAP_VIEW_IDS.length; index += 1) {
29
+ if (fromSpecs[index] !== ARCHITECTURE_MAP_VIEW_IDS[index]) {
30
+ throw new Error(`architecture_map_view_id_mismatch: ${fromSpecs[index]}`);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,71 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { loadArchitectureMapPolicy } from '../architecture/policy.js';
4
+ import { buildArchitectureMapViews } from '../projections/mermaid/index.js';
5
+ const WIKI_PREFIX = '.sneakoscope/wiki/';
6
+ export const ARCHITECTURE_MAP_DIR_REL = '.sneakoscope/wiki/architecture-map';
7
+ export const ARCHITECTURE_MAP_GLOBAL_VIEW_FILES = Object.freeze([
8
+ 'project-topology.mmd',
9
+ 'module-dependency.mmd',
10
+ 'public-surface.mmd',
11
+ 'ssot-provenance.mmd',
12
+ 'runtime-control.mmd',
13
+ 'verification-coverage.mmd',
14
+ 'risk-domains.mmd'
15
+ ]);
16
+ export const ARCHITECTURE_MAP_ARTIFACT_RELS = Object.freeze([
17
+ `${ARCHITECTURE_MAP_DIR_REL}/manifest.json`,
18
+ `${ARCHITECTURE_MAP_DIR_REL}/metrics.json`,
19
+ `${ARCHITECTURE_MAP_DIR_REL}/findings.json`,
20
+ ...ARCHITECTURE_MAP_GLOBAL_VIEW_FILES.map((name) => `${ARCHITECTURE_MAP_DIR_REL}/views/${name}`)
21
+ ]);
22
+ function byCodePoint(left, right) {
23
+ if (left < right)
24
+ return -1;
25
+ return left > right ? 1 : 0;
26
+ }
27
+ function wikiRelative(artifactRel) {
28
+ if (!artifactRel.startsWith(WIKI_PREFIX)) {
29
+ throw new Error(`architecture_map_publish_artifact_outside_wiki:${artifactRel}`);
30
+ }
31
+ return artifactRel.slice(WIKI_PREFIX.length);
32
+ }
33
+ export async function publishArchitectureMapToStage(stageWikiAbs, snapshot, options) {
34
+ const policy = options.policy ?? loadArchitectureMapPolicy(options.root);
35
+ const built = buildArchitectureMapViews(snapshot, policy, {
36
+ rootId: options.root,
37
+ missionId: options.missionId ?? null
38
+ });
39
+ const written = [];
40
+ const writeJson = async (rel, value) => {
41
+ const dest = path.join(stageWikiAbs, wikiRelative(rel));
42
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
43
+ await fsp.writeFile(dest, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
44
+ written.push(rel);
45
+ };
46
+ const writeText = async (rel, text) => {
47
+ const dest = path.join(stageWikiAbs, wikiRelative(rel));
48
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
49
+ await fsp.writeFile(dest, text.endsWith('\n') ? text : `${text}\n`, 'utf8');
50
+ written.push(rel);
51
+ };
52
+ await writeJson(`${ARCHITECTURE_MAP_DIR_REL}/manifest.json`, built.manifest);
53
+ await writeJson(`${ARCHITECTURE_MAP_DIR_REL}/metrics.json`, built.metrics);
54
+ await writeJson(`${ARCHITECTURE_MAP_DIR_REL}/findings.json`, { findings: built.findings });
55
+ for (const view of built.views) {
56
+ await writeText(`${ARCHITECTURE_MAP_DIR_REL}/views/${view.filename}`, view.text);
57
+ }
58
+ const expected = new Set(ARCHITECTURE_MAP_ARTIFACT_RELS);
59
+ for (const rel of written) {
60
+ if (!expected.has(rel))
61
+ throw new Error(`architecture_map_publish_unexpected_artifact:${rel}`);
62
+ }
63
+ if (written.length !== ARCHITECTURE_MAP_ARTIFACT_RELS.length) {
64
+ throw new Error(`architecture_map_publish_incomplete:${written.length}/${ARCHITECTURE_MAP_ARTIFACT_RELS.length}`);
65
+ }
66
+ return {
67
+ written: [...written].sort(byCodePoint),
68
+ stageMapDir: path.join(stageWikiAbs, wikiRelative(ARCHITECTURE_MAP_DIR_REL)),
69
+ built
70
+ };
71
+ }