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,114 @@
1
+ export const SKS_SSOT_AUTHORITY_INVENTORY = Object.freeze([
2
+ Object.freeze({
3
+ id: 'route_contract',
4
+ title: 'Route contract',
5
+ authorityKind: 'runtime_owner',
6
+ canonicalSources: Object.freeze([
7
+ 'decision-contract.json, route prompt, and pipeline-plan.json'
8
+ ]),
9
+ allowedWriters: Object.freeze(['pipeline plan writer', 'request intake']),
10
+ allowedReaders: Object.freeze(['naruto', 'workers', 'stop gate']),
11
+ derivedArtifacts: Object.freeze([
12
+ 'subagent-plan.json',
13
+ 'subagent-events.jsonl',
14
+ 'worker inboxes'
15
+ ]),
16
+ protected: true,
17
+ description: 'The sealed user objective, constraints, non-goals, and acceptance criteria define what code may be created.',
18
+ authority: 'The sealed user objective, constraints, non-goals, and acceptance criteria define what code may be created.',
19
+ rule: 'Do not implement behavior outside the sealed route contract; block with evidence if the requested path cannot be honored.'
20
+ }),
21
+ Object.freeze({
22
+ id: 'triwiki_context',
23
+ title: 'TriWiki context pack',
24
+ authorityKind: 'file',
25
+ canonicalSources: Object.freeze(['.sneakoscope/wiki/context-pack.json']),
26
+ allowedWriters: Object.freeze(['sks wiki pack', 'sks align run', 'triwiki refresh']),
27
+ allowedReaders: Object.freeze(['pipeline stages', 'naruto parent', 'honest mode']),
28
+ derivedArtifacts: Object.freeze([
29
+ 'subagent-parent-summary.json',
30
+ 'subagent-evidence.json',
31
+ 'reflection.md'
32
+ ]),
33
+ protected: true,
34
+ description: 'TriWiki is the bounded mission context SSOT and must be refreshed or packed, then validated before risky handoffs and final claims.',
35
+ authority: 'TriWiki is the bounded mission context SSOT and must be refreshed or packed, then validated before risky handoffs and final claims.',
36
+ rule: 'Use the latest coordinate+voxel overlay pack; coordinate-only legacy packs are invalid for pipeline decisions.'
37
+ }),
38
+ Object.freeze({
39
+ id: 'runtime_source',
40
+ title: 'TypeScript runtime source',
41
+ authorityKind: 'file',
42
+ canonicalSources: Object.freeze(['src/**/*.ts']),
43
+ allowedWriters: Object.freeze(['implementation workers under sealed route']),
44
+ allowedReaders: Object.freeze(['build', 'tests', 'release gates']),
45
+ derivedArtifacts: Object.freeze(['dist/**', 'dist/bin/sks.js']),
46
+ protected: true,
47
+ description: 'TypeScript source is the runtime SSOT.',
48
+ authority: 'TypeScript source is the runtime SSOT.',
49
+ rule: 'Edit source, rebuild derived output, and rely on runtime:ts-source-of-truth plus runtime:dist-parity.'
50
+ }),
51
+ Object.freeze({
52
+ id: 'generated_outputs',
53
+ title: 'Generated outputs',
54
+ authorityKind: 'generated_projection',
55
+ canonicalSources: Object.freeze([
56
+ 'source generators, build scripts, and schema definitions'
57
+ ]),
58
+ allowedWriters: Object.freeze(['generators', 'build scripts']),
59
+ allowedReaders: Object.freeze(['release', 'doctor', 'install']),
60
+ derivedArtifacts: Object.freeze([
61
+ 'release-gates.v2.json',
62
+ 'infra-harness-gates.json',
63
+ '.sneakoscope/reports/**',
64
+ 'dist/build-manifest.json'
65
+ ]),
66
+ protected: true,
67
+ description: 'Generated files are derived from their generator or schema owner.',
68
+ authority: 'Generated files are derived from their generator or schema owner.',
69
+ rule: 'Regenerate derived artifacts instead of hand-editing them as independent truth.'
70
+ }),
71
+ Object.freeze({
72
+ id: 'stack_current_docs',
73
+ title: 'Stack current docs',
74
+ authorityKind: 'file',
75
+ canonicalSources: Object.freeze(['.sneakoscope/memory/q2_facts/stack-current-docs.md']),
76
+ allowedWriters: Object.freeze(['Context7 hydration', 'docs refresh']),
77
+ allowedReaders: Object.freeze(['implementation', 'research']),
78
+ derivedArtifacts: Object.freeze(['implementation notes', 'route evidence']),
79
+ protected: false,
80
+ description: 'Current vendor or Context7 docs override model-memory defaults when stack versions or APIs change.',
81
+ authority: 'Current vendor or Context7 docs override model-memory defaults when stack versions or APIs change.',
82
+ rule: 'Fetch and record current docs before relying on external package, SDK, API, MCP, or generated-doc behavior that may have changed.'
83
+ }),
84
+ Object.freeze({
85
+ id: 'release_gate_manifest',
86
+ title: 'Release gate manifest',
87
+ authorityKind: 'manifest',
88
+ canonicalSources: Object.freeze([
89
+ 'src/core/release/gate-manifest.ts and src/scripts/release-parallel-check.ts'
90
+ ]),
91
+ allowedWriters: Object.freeze(['release maintainers via source edit']),
92
+ allowedReaders: Object.freeze(['release DAG', 'publish preflight']),
93
+ derivedArtifacts: Object.freeze([
94
+ '.sneakoscope/reports/release-gate-plan.json',
95
+ '.sneakoscope/reports/gate-policy-audit.json'
96
+ ]),
97
+ protected: true,
98
+ description: 'Release gate selection and publish-required status live in the manifest plus the release DAG.',
99
+ authority: 'Release gate selection and publish-required status live in the manifest plus the release DAG.',
100
+ rule: 'Publish-blocking guard gates must appear in the DAG, manifest, and package scripts.'
101
+ })
102
+ ]);
103
+ export function canonicalSsotAuthorityInventory() {
104
+ return SKS_SSOT_AUTHORITY_INVENTORY;
105
+ }
106
+ export function inventoryAsSsotGuardSources() {
107
+ return SKS_SSOT_AUTHORITY_INVENTORY.map((domain) => ({
108
+ id: domain.id,
109
+ source: domain.canonicalSources.join(', '),
110
+ authority: domain.authority,
111
+ derived: [...domain.derivedArtifacts],
112
+ rule: domain.rule
113
+ }));
114
+ }
@@ -1,5 +1,6 @@
1
1
  import { nowIso } from '../fsx.js';
2
2
  import { isRecord } from '../json/records.js';
3
+ import { inventoryAsSsotGuardSources } from './ssot-authority-inventory.js';
3
4
  export const SSOT_GUARD_SCHEMA = 'sks.ssot-guard.v1';
4
5
  export const SSOT_GUARD_ARTIFACT = 'ssot-guard.json';
5
6
  export function buildSsotGuard(input = {}) {
@@ -34,50 +35,7 @@ export function buildSsotGuard(input = {}) {
34
35
  };
35
36
  }
36
37
  export function canonicalSsotSources() {
37
- return [
38
- {
39
- id: 'route_contract',
40
- source: 'decision-contract.json, route prompt, and pipeline-plan.json',
41
- authority: 'The sealed user objective, constraints, non-goals, and acceptance criteria define what code may be created.',
42
- derived: ['subagent-plan.json', 'subagent-events.jsonl', 'worker inboxes'],
43
- rule: 'Do not implement behavior outside the sealed route contract; block with evidence if the requested path cannot be honored.'
44
- },
45
- {
46
- id: 'triwiki_context',
47
- source: '.sneakoscope/wiki/context-pack.json',
48
- authority: 'TriWiki is the bounded mission context SSOT and must be refreshed or packed, then validated before risky handoffs and final claims.',
49
- derived: ['subagent-parent-summary.json', 'subagent-evidence.json', 'reflection.md'],
50
- rule: 'Use the latest coordinate+voxel overlay pack; coordinate-only legacy packs are invalid for pipeline decisions.'
51
- },
52
- {
53
- id: 'runtime_source',
54
- source: 'src/**/*.ts',
55
- authority: 'TypeScript source is the runtime SSOT.',
56
- derived: ['dist/**', 'dist/bin/sks.js'],
57
- rule: 'Edit source, rebuild derived output, and rely on runtime:ts-source-of-truth plus runtime:dist-parity.'
58
- },
59
- {
60
- id: 'generated_outputs',
61
- source: 'source generators, build scripts, and schema definitions',
62
- authority: 'Generated files are derived from their generator or schema owner.',
63
- derived: ['release-gates.v2.json', 'infra-harness-gates.json', '.sneakoscope/reports/**', 'dist/build-manifest.json'],
64
- rule: 'Regenerate derived artifacts instead of hand-editing them as independent truth.'
65
- },
66
- {
67
- id: 'stack_current_docs',
68
- source: '.sneakoscope/memory/q2_facts/stack-current-docs.md',
69
- authority: 'Current vendor or Context7 docs override model-memory defaults when stack versions or APIs change.',
70
- derived: ['implementation notes', 'route evidence'],
71
- rule: 'Fetch and record current docs before relying on external package, SDK, API, MCP, or generated-doc behavior that may have changed.'
72
- },
73
- {
74
- id: 'release_gate_manifest',
75
- source: 'src/core/release/gate-manifest.ts and src/scripts/release-parallel-check.ts',
76
- authority: 'Release gate selection and publish-required status live in the manifest plus the release DAG.',
77
- derived: ['.sneakoscope/reports/release-gate-plan.json', '.sneakoscope/reports/gate-policy-audit.json'],
78
- rule: 'Publish-blocking guard gates must appear in the DAG, manifest, and package scripts.'
79
- }
80
- ];
38
+ return inventoryAsSsotGuardSources().map((row) => ({ ...row }));
81
39
  }
82
40
  export function validateSsotGuardArtifact(value) {
83
41
  const issues = [];
@@ -0,0 +1,81 @@
1
+ import { sha256 } from '../../../fsx.js';
2
+ import { ARCHITECTURE_BASELINE_SCHEMA } from './contracts.js';
3
+ import { hashCanonical } from './fingerprint.js';
4
+ import { analyzeArchitectureFindings } from './findings.js';
5
+ import { computeArchitectureMetrics, computeSccs, buildModuleDependencyGraph } from './metrics.js';
6
+ import { mergeGraphWithTopology } from './topology-overlay.js';
7
+ export function sealArchitectureBaseline(canonicalPayloadHash) {
8
+ return sha256(`${ARCHITECTURE_BASELINE_SCHEMA}\n${canonicalPayloadHash}`);
9
+ }
10
+ export function buildArchitectureBaseline(input) {
11
+ const merged = mergeGraphWithTopology(input.bundle.graph, input.bundle.topology);
12
+ const moduleGraph = buildModuleDependencyGraph(merged.nodes, merged.edges);
13
+ const sccs = computeSccs(moduleGraph);
14
+ const findings = input.findings ?? analyzeArchitectureFindings({
15
+ bundle: input.bundle,
16
+ policy: input.policy,
17
+ baselineSccs: []
18
+ });
19
+ const metrics = input.metrics ?? computeArchitectureMetrics({
20
+ graph: input.bundle.graph,
21
+ topology: input.bundle.topology,
22
+ layerViolationCount: findings.filter((finding) => finding.code === 'forbidden_dependency').length,
23
+ ssotCollisionCount: findings.filter((finding) => finding.code === 'ssot_collision').length,
24
+ authorityBypassCount: findings.filter((finding) => finding.code === 'authority_bypass').length,
25
+ protectedVerificationGapCount: findings.filter((finding) => finding.code === 'protected_verification_gap').length
26
+ });
27
+ const viewHashes = Object.freeze({ ...(input.viewHashes ?? {}) });
28
+ const payload = {
29
+ schema: ARCHITECTURE_BASELINE_SCHEMA,
30
+ missionId: input.missionId,
31
+ routeId: input.routeId,
32
+ taskProfile: input.taskProfile,
33
+ required: true,
34
+ capturedBeforeMutation: true,
35
+ repositoryHead: input.bundle.worktree.repositoryHead,
36
+ worktreeFingerprintHash: input.bundle.worktree.hash,
37
+ preexistingDirtyPaths: input.bundle.worktree.dirtyPaths,
38
+ graphHash: input.bundle.graphHash,
39
+ topologyHash: input.bundle.topologyHash,
40
+ ssotInventoryHash: input.bundle.ssotInventoryHash,
41
+ voxelContextHash: input.bundle.voxelContextHash,
42
+ policyHash: input.bundle.policyHash,
43
+ analyzerVersion: input.bundle.analyzerVersion,
44
+ serializerVersion: input.bundle.serializerVersion,
45
+ scope: input.scope,
46
+ metrics,
47
+ findings,
48
+ viewHashes,
49
+ sccKeys: sccs.map((members) => members.join('|'))
50
+ };
51
+ const canonicalPayloadHash = hashCanonical(payload);
52
+ const seal = sealArchitectureBaseline(canonicalPayloadHash);
53
+ return Object.freeze({
54
+ schema: ARCHITECTURE_BASELINE_SCHEMA,
55
+ missionId: input.missionId,
56
+ routeId: input.routeId,
57
+ taskProfile: input.taskProfile,
58
+ required: true,
59
+ capturedAt: input.capturedAt,
60
+ capturedBeforeMutation: true,
61
+ repositoryHead: input.bundle.worktree.repositoryHead,
62
+ worktreeFingerprintHash: input.bundle.worktree.hash,
63
+ preexistingDirtyPaths: Object.freeze([...input.bundle.worktree.dirtyPaths]),
64
+ graphHash: input.bundle.graphHash,
65
+ topologyHash: input.bundle.topologyHash,
66
+ ssotInventoryHash: input.bundle.ssotInventoryHash,
67
+ voxelContextHash: input.bundle.voxelContextHash,
68
+ policyHash: input.bundle.policyHash,
69
+ analyzerVersion: input.bundle.analyzerVersion,
70
+ serializerVersion: input.bundle.serializerVersion,
71
+ scope: input.scope,
72
+ metrics,
73
+ findings: Object.freeze([...findings]),
74
+ viewHashes,
75
+ canonicalPayloadHash,
76
+ seal
77
+ });
78
+ }
79
+ export function verifyArchitectureBaselineSeal(baseline) {
80
+ return baseline.seal === sealArchitectureBaseline(baseline.canonicalPayloadHash);
81
+ }
@@ -0,0 +1,34 @@
1
+ import { byCodePoint } from './contracts.js';
2
+ import { hashCanonical } from './fingerprint.js';
3
+ export function renderArchitectureCapsule(input) {
4
+ const blocking = input.findings
5
+ .filter((finding) => finding.severity === 'blocking')
6
+ .sort((left, right) => byCodePoint(left.id, right.id));
7
+ const baselineShort = input.baseline ? input.baseline.seal.slice(0, 12) : 'none';
8
+ const lines = [
9
+ `schema=sks.architecture-capsule.v1`,
10
+ `profile=${input.profile}`,
11
+ `mission=${input.missionId}`,
12
+ `route=${input.routeId ?? 'unknown'}`,
13
+ `baseline=${baselineShort}`,
14
+ `modules=${input.metrics.moduleCount}`,
15
+ `scc=${input.metrics.sccCount}`,
16
+ `fan_in_max=${input.metrics.maxFanIn}`,
17
+ `fan_out_max=${input.metrics.maxFanOut}`,
18
+ `layer_violations=${input.metrics.layerViolationCount}`,
19
+ `ssot_collisions=${input.metrics.ssotCollisionCount}`,
20
+ `verification_gaps=${input.metrics.protectedVerificationGapCount}`,
21
+ `affected_gates=${(input.affectedGates ?? []).slice().sort(byCodePoint).join(',') || 'none'}`,
22
+ `blocking_findings=${blocking.map((finding) => `${finding.id}:${finding.code}`).join(',') || 'none'}`,
23
+ `seeds=${(input.seedPaths ?? []).slice().sort(byCodePoint).join(',') || 'none'}`,
24
+ `map=${input.mapArtifact ?? 'architecture-map-delta.mmd'}`
25
+ ];
26
+ const text = `${lines.join('\n')}\n`;
27
+ return Object.freeze({
28
+ schema: 'sks.architecture-capsule.v1',
29
+ text,
30
+ byteLength: Buffer.byteLength(text, 'utf8'),
31
+ profile: input.profile,
32
+ contentHash: hashCanonical(text)
33
+ });
34
+ }
@@ -0,0 +1,67 @@
1
+ export const ARCHITECTURE_INPUT_BUNDLE_SCHEMA = 'sks.architecture-input-bundle.v1';
2
+ export const ARCHITECTURE_BASELINE_SCHEMA = 'sks.architecture-baseline.v1';
3
+ export const ARCHITECTURE_REVIEW_SCHEMA = 'sks.architecture-review.v1';
4
+ export const ARCHITECTURE_MAP_MANIFEST_SCHEMA = 'sks.architecture-map-manifest.v1';
5
+ export const ARCHITECTURE_MAP_POLICY_SCHEMA = 'sks.architecture-map-policy.v1';
6
+ export const MERMAID_PROJECTION_SCHEMA = 'sks.mermaid-projection.v1';
7
+ export const ARCHITECTURE_ANALYZER_VERSION = 'architecture-map-analyzer.v1';
8
+ export const ARCHITECTURE_SERIALIZER_VERSION = 'mermaid-serializer.v1';
9
+ export const ARCHITECTURE_MAP_DIR_REL = '.sneakoscope/wiki/architecture-map';
10
+ export const ARCHITECTURE_MAP_POLICY_FILE = 'config/architecture-map-policy.v1.json';
11
+ export const ARCHITECTURE_MAP_VIEW_IDS = Object.freeze([
12
+ 'project-topology',
13
+ 'module-dependency',
14
+ 'public-surface',
15
+ 'ssot-provenance',
16
+ 'runtime-control',
17
+ 'verification-coverage',
18
+ 'risk-domains',
19
+ 'change-impact',
20
+ 'architecture-delta',
21
+ 'ownership-workstream'
22
+ ]);
23
+ export const GLOBAL_ARCHITECTURE_MAP_VIEW_IDS = Object.freeze([
24
+ 'project-topology',
25
+ 'module-dependency',
26
+ 'public-surface',
27
+ 'ssot-provenance',
28
+ 'runtime-control',
29
+ 'verification-coverage',
30
+ 'risk-domains'
31
+ ]);
32
+ export const ARCHITECTURE_FINDING_CODES = Object.freeze([
33
+ 'baseline_missing',
34
+ 'baseline_late',
35
+ 'baseline_stale',
36
+ 'baseline_tampered',
37
+ 'graph_stale',
38
+ 'map_parity_failure',
39
+ 'unaccounted_changed_file',
40
+ 'new_cycle',
41
+ 'cycle_expansion',
42
+ 'forbidden_dependency',
43
+ 'layer_skip',
44
+ 'ssot_collision',
45
+ 'authority_bypass',
46
+ 'protected_verification_gap',
47
+ 'fan_out_regression',
48
+ 'fan_in_unverified',
49
+ 'passthrough_chain',
50
+ 'redundant_parallel_path',
51
+ 'duplicate_facade',
52
+ 'orphan_entrypoint',
53
+ 'unowned_high_risk',
54
+ 'test_distance_increase',
55
+ 'architecture_depth_increase',
56
+ 'cross_workstream_write_overlap',
57
+ 'unknown_dynamic_relation',
58
+ 'insufficient_graph'
59
+ ]);
60
+ export function byCodePoint(left, right) {
61
+ if (left < right)
62
+ return -1;
63
+ return left > right ? 1 : 0;
64
+ }
65
+ export function sortStrings(values) {
66
+ return [...values].sort(byCodePoint);
67
+ }
@@ -0,0 +1,149 @@
1
+ import { ARCHITECTURE_REVIEW_SCHEMA, byCodePoint } from './contracts.js';
2
+ import { verifyArchitectureBaselineSeal } from './baseline.js';
3
+ import { diffWorktreePaths, hashCanonical } from './fingerprint.js';
4
+ import { analyzeArchitectureFindings } from './findings.js';
5
+ import { computeArchitectureMetrics } from './metrics.js';
6
+ export function metricDelta(before, after) {
7
+ return Object.freeze({
8
+ sccCountDelta: after.sccCount - before.sccCount,
9
+ cyclicNodeCountDelta: after.cyclicNodeCount - before.cyclicNodeCount,
10
+ layerViolationCountDelta: after.layerViolationCount - before.layerViolationCount,
11
+ ssotCollisionCountDelta: after.ssotCollisionCount - before.ssotCollisionCount,
12
+ protectedVerificationGapCountDelta: after.protectedVerificationGapCount - before.protectedVerificationGapCount
13
+ });
14
+ }
15
+ function partitionFindings(before, after) {
16
+ const beforeIds = new Set(before.map((finding) => finding.id));
17
+ const afterIds = new Set(after.map((finding) => finding.id));
18
+ const byId = (left, right) => byCodePoint(left.id, right.id);
19
+ return {
20
+ newFindings: after.filter((finding) => !beforeIds.has(finding.id)).sort(byId),
21
+ resolvedFindings: before.filter((finding) => !afterIds.has(finding.id)).sort(byId),
22
+ persistentFindings: after.filter((finding) => beforeIds.has(finding.id)).sort(byId)
23
+ };
24
+ }
25
+ function baselineSccsFromFindings(findings) {
26
+ return Object.freeze(findings
27
+ .filter((finding) => finding.code === 'new_cycle' || finding.code === 'cycle_expansion')
28
+ .map((finding) => finding.subjectIds));
29
+ }
30
+ function countCodes(findings, code) {
31
+ return findings.filter((finding) => finding.code === code).length;
32
+ }
33
+ export function buildArchitectureDelta(input) {
34
+ const afterFindings = analyzeArchitectureFindings({
35
+ bundle: input.afterBundle,
36
+ policy: input.policy,
37
+ baselineSccs: baselineSccsFromFindings(input.baseline.findings)
38
+ });
39
+ const partitioned = partitionFindings(input.baseline.findings, afterFindings);
40
+ const afterMetrics = computeArchitectureMetrics({
41
+ graph: input.afterBundle.graph,
42
+ topology: input.afterBundle.topology,
43
+ layerViolationCount: countCodes(afterFindings, 'forbidden_dependency'),
44
+ ssotCollisionCount: countCodes(afterFindings, 'ssot_collision'),
45
+ protectedVerificationGapCount: countCodes(afterFindings, 'protected_verification_gap')
46
+ });
47
+ return Object.freeze({
48
+ schema: 'sks.architecture-delta.v1',
49
+ missionId: input.missionId,
50
+ baselineSeal: input.baseline.seal,
51
+ addedNodeIds: Object.freeze([]),
52
+ removedNodeIds: Object.freeze([]),
53
+ addedEdgeIds: Object.freeze([]),
54
+ removedEdgeIds: Object.freeze([]),
55
+ newFindings: Object.freeze(partitioned.newFindings),
56
+ resolvedFindings: Object.freeze(partitioned.resolvedFindings),
57
+ metricDelta: metricDelta(input.baseline.metrics, afterMetrics)
58
+ });
59
+ }
60
+ export function buildArchitectureReview(input) {
61
+ const sealValid = verifyArchitectureBaselineSeal(input.baseline);
62
+ const changedPaths = [...diffWorktreePaths(input.baseline.preexistingDirtyPaths.map((path) => ({
63
+ path,
64
+ kind: 'file',
65
+ sha256: 'baseline',
66
+ size: 0,
67
+ executable: false
68
+ })), input.afterBundle.worktree.paths)];
69
+ const changedPathSet = new Set(changedPaths.map((entry) => entry.path));
70
+ const accounted = [...(input.accountedChangedPaths ?? [...changedPathSet])].sort(byCodePoint);
71
+ const accountedSet = new Set(accounted);
72
+ const unaccounted = [...changedPathSet].filter((path) => !accountedSet.has(path)).sort(byCodePoint);
73
+ const afterFindings = analyzeArchitectureFindings({
74
+ bundle: input.afterBundle,
75
+ policy: input.policy,
76
+ baselineSccs: baselineSccsFromFindings(input.baseline.findings)
77
+ });
78
+ const partitioned = partitionFindings(input.baseline.findings, afterFindings);
79
+ const afterMetrics = computeArchitectureMetrics({
80
+ graph: input.afterBundle.graph,
81
+ topology: input.afterBundle.topology,
82
+ layerViolationCount: countCodes(afterFindings, 'forbidden_dependency'),
83
+ ssotCollisionCount: countCodes(afterFindings, 'ssot_collision'),
84
+ authorityBypassCount: countCodes(afterFindings, 'authority_bypass'),
85
+ protectedVerificationGapCount: countCodes(afterFindings, 'protected_verification_gap')
86
+ });
87
+ const newBlocking = afterFindings
88
+ .filter((finding) => finding.severity === 'blocking' && finding.disposition === 'open')
89
+ .filter((finding) => !input.baseline.findings.some((prior) => prior.id === finding.id))
90
+ .map((finding) => finding.id)
91
+ .sort(byCodePoint);
92
+ const blockers = [];
93
+ if (!sealValid)
94
+ blockers.push('baseline_tampered');
95
+ if (input.baseline.policyHash !== input.afterBundle.policyHash ||
96
+ input.baseline.analyzerVersion !== input.afterBundle.analyzerVersion ||
97
+ input.baseline.serializerVersion !== input.afterBundle.serializerVersion) {
98
+ blockers.push('baseline_stale');
99
+ }
100
+ if (unaccounted.length)
101
+ blockers.push('unaccounted_changed_file');
102
+ if (newBlocking.length)
103
+ blockers.push('blocking_findings');
104
+ const verification = Object.freeze({
105
+ baselineValid: input.baseline.schema === 'sks.architecture-baseline.v1',
106
+ baselineTimely: input.baseline.capturedBeforeMutation === true,
107
+ baselineFresh: !blockers.includes('baseline_stale'),
108
+ sealValid,
109
+ changedPathAccounting: unaccounted.length === 0 ? 1 : 0,
110
+ projectionAccounting: 1,
111
+ blockers: Object.freeze([...blockers].sort(byCodePoint))
112
+ });
113
+ const blockingFindingIds = Object.freeze([...newBlocking, ...(unaccounted.length ? ['unaccounted_changed_file'] : [])].sort(byCodePoint));
114
+ const verdict = verification.baselineValid &&
115
+ verification.baselineTimely &&
116
+ verification.baselineFresh &&
117
+ verification.sealValid &&
118
+ verification.changedPathAccounting === 1 &&
119
+ newBlocking.length === 0 &&
120
+ unaccounted.length === 0
121
+ ? 'pass'
122
+ : 'block';
123
+ const payload = {
124
+ schema: ARCHITECTURE_REVIEW_SCHEMA,
125
+ missionId: input.missionId,
126
+ baselineSeal: input.baseline.seal,
127
+ baselineHash: input.baseline.canonicalPayloadHash,
128
+ afterInputHash: input.afterBundle.canonicalHash,
129
+ changedPaths,
130
+ accountedChangedPaths: accounted,
131
+ unaccountedChangedPaths: unaccounted,
132
+ beforeMetrics: input.baseline.metrics,
133
+ afterMetrics,
134
+ metricDelta: metricDelta(input.baseline.metrics, afterMetrics),
135
+ newFindings: partitioned.newFindings,
136
+ resolvedFindings: partitioned.resolvedFindings,
137
+ persistentFindings: partitioned.persistentFindings,
138
+ acceptedExceptions: [],
139
+ projectionAccounting: input.projectionAccounting ?? {},
140
+ verification,
141
+ verdict,
142
+ blockingFindingIds
143
+ };
144
+ return Object.freeze({
145
+ ...payload,
146
+ canonicalPayloadHash: hashCanonical(payload),
147
+ generatedAt: input.generatedAt
148
+ });
149
+ }
@@ -0,0 +1,28 @@
1
+ import { sha256 } from '../../../fsx.js';
2
+ import { sortStrings } from './contracts.js';
3
+ const FINDING_SCHEMA = 'sks.architecture-finding.v1';
4
+ export function findingId(input) {
5
+ const payload = [
6
+ FINDING_SCHEMA,
7
+ input.code,
8
+ ...sortStrings(input.subjectIds),
9
+ ...sortStrings(input.evidenceIds),
10
+ input.ruleId
11
+ ].join('\n');
12
+ return `af_${sha256(payload).slice(0, 24)}`;
13
+ }
14
+ export function makeFinding(input) {
15
+ const subjectIds = Object.freeze(sortStrings(input.subjectIds));
16
+ const evidenceIds = Object.freeze(sortStrings(input.evidenceIds ?? []));
17
+ const ruleId = input.ruleId ?? '';
18
+ return Object.freeze({
19
+ id: findingId({ code: input.code, subjectIds, evidenceIds, ruleId }),
20
+ code: input.code,
21
+ severity: input.severity,
22
+ subjectIds,
23
+ evidenceIds,
24
+ ruleId,
25
+ message: input.message,
26
+ disposition: 'open'
27
+ });
28
+ }