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
package/README.md CHANGED
@@ -22,7 +22,7 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
22
22
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
23
23
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
24
24
 
25
- This README documents package **SKS 8.3.2** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
25
+ This README documents package **SKS 8.4.0** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
26
26
 
27
27
  Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/CHANGELOG.md).
28
28
 
@@ -0,0 +1,168 @@
1
+ {
2
+ "schema": "sks.architecture-map-policy.v1",
3
+ "version": 1,
4
+ "module_id_source": "context-graph module node id, as produced by src/core/triwiki/context-graph/extractors/code/modules.ts: the owning directory, capped at depth 3 under src|source|lib and depth 2 elsewhere. Entries below are exact module ids, never globs; a module not named by any layer is unconstrained.",
5
+ "layers": [
6
+ {
7
+ "id": "entrypoint",
8
+ "purpose": "Process entry. Nothing in the repository imports these modules, so argv arrives here and only leaves downward.",
9
+ "modules": ["src/bin"]
10
+ },
11
+ {
12
+ "id": "cli_surface",
13
+ "purpose": "Argument parsing, terminal output, the command registry and the command manifest.",
14
+ "modules": ["src/cli"]
15
+ },
16
+ {
17
+ "id": "command",
18
+ "purpose": "One handler per user-facing command, split across a thin dispatch root and the heavier implementations.",
19
+ "modules": ["src/commands", "src/core/commands"]
20
+ },
21
+ {
22
+ "id": "orchestration",
23
+ "purpose": "Route classification, pipeline plans, gate profiles and the stop gate.",
24
+ "modules": [
25
+ "src/core/pipeline",
26
+ "src/core/pipeline-internals",
27
+ "src/core/router",
28
+ "src/core/routes",
29
+ "src/core/runtime",
30
+ "src/core/stop-gate"
31
+ ]
32
+ },
33
+ {
34
+ "id": "release",
35
+ "purpose": "Release gate manifest, gate DAG, scheduling and publish contracts.",
36
+ "modules": ["src/core/release"]
37
+ },
38
+ {
39
+ "id": "knowledge",
40
+ "purpose": "TriWiki: the context graph, code packs, the proof bank and the wrongness ledger.",
41
+ "modules": ["src/core/triwiki", "src/core/triwiki-wrongness"]
42
+ },
43
+ {
44
+ "id": "foundation",
45
+ "purpose": "Domain-free primitives: error shapes, JSON records, file locks, regex helpers.",
46
+ "modules": ["src/core/errors", "src/core/json", "src/core/locks", "src/core/text"]
47
+ },
48
+ {
49
+ "id": "gate_script",
50
+ "purpose": "Standalone check executables invoked by npm scripts and the release DAG.",
51
+ "modules": ["src/scripts", "src/scripts/lib"]
52
+ }
53
+ ],
54
+ "boundaries": {
55
+ "mode": "allow-list",
56
+ "allow": [
57
+ { "from": "entrypoint", "to": ["cli_surface", "command", "orchestration"] },
58
+ {
59
+ "from": "cli_surface",
60
+ "to": ["command", "orchestration", "release", "knowledge", "foundation"]
61
+ },
62
+ { "from": "command", "to": ["orchestration", "release", "knowledge", "foundation"] },
63
+ { "from": "orchestration", "to": ["release", "knowledge", "foundation"] },
64
+ { "from": "release", "to": ["knowledge", "foundation"] },
65
+ { "from": "knowledge", "to": ["foundation"] },
66
+ { "from": "foundation", "to": [] },
67
+ {
68
+ "from": "gate_script",
69
+ "to": ["cli_surface", "command", "orchestration", "release", "knowledge", "foundation"]
70
+ }
71
+ ]
72
+ },
73
+ "thresholds": {
74
+ "max_diagram_nodes": 240,
75
+ "max_diagram_edges": 900,
76
+ "max_diagram_bytes": 45000,
77
+ "max_module_fan_out": 56,
78
+ "max_module_fan_in": 96
79
+ },
80
+ "profiles": {
81
+ "global": { "maxNodes": 240, "maxEdges": 360, "maxLabelChars": 56, "tokenBudget": 0 },
82
+ "planning": { "maxNodes": 48, "maxEdges": 72, "maxLabelChars": 56, "tokenBudget": 900 },
83
+ "implementation": { "maxNodes": 64, "maxEdges": 96, "maxLabelChars": 56, "tokenBudget": 1200 },
84
+ "review": { "maxNodes": 72, "maxEdges": 108, "maxLabelChars": 56, "tokenBudget": 1600 },
85
+ "protected": { "maxNodes": 96, "maxEdges": 144, "maxLabelChars": 56, "tokenBudget": 2400 }
86
+ },
87
+ "absoluteTokenCeiling": 3200,
88
+ "globalAtlasMaxBytes": 1572864,
89
+ "missionArtifactsMaxBytes": 358400,
90
+ "accuracyFloors": {
91
+ "changedPathAccounting": 1,
92
+ "projectionAccounting": 1,
93
+ "protectedRelationRecall": 1,
94
+ "eligibleExtractionSuccess": 0.995,
95
+ "deterministicHashRuns": 3
96
+ },
97
+ "dependencyRules": [
98
+ {
99
+ "id": "boundary-allow-list",
100
+ "effect": "deny",
101
+ "severity": "blocking",
102
+ "description": "Module edges that cross layers outside boundaries.allow (and outside shrink-only exceptions) are forbidden_dependency findings."
103
+ }
104
+ ],
105
+ "viewBudgets": {
106
+ "project-topology": { "maxNodes": 240, "maxEdges": 360 },
107
+ "module-dependency": { "maxNodes": 240, "maxEdges": 900 },
108
+ "public-surface": { "maxNodes": 120, "maxEdges": 180 },
109
+ "ssot-provenance": { "maxNodes": 96, "maxEdges": 144 },
110
+ "runtime-control": { "maxNodes": 120, "maxEdges": 180 },
111
+ "verification-coverage": { "maxNodes": 160, "maxEdges": 240 },
112
+ "risk-domains": { "maxNodes": 96, "maxEdges": 144 }
113
+ },
114
+ "blockingCodes": [
115
+ "baseline_missing",
116
+ "baseline_late",
117
+ "baseline_stale",
118
+ "baseline_tampered",
119
+ "graph_stale",
120
+ "map_parity_failure",
121
+ "unaccounted_changed_file",
122
+ "new_cycle",
123
+ "cycle_expansion",
124
+ "forbidden_dependency",
125
+ "layer_skip",
126
+ "ssot_collision",
127
+ "authority_bypass",
128
+ "protected_verification_gap",
129
+ "insufficient_graph"
130
+ ],
131
+ "exceptions": {
132
+ "mode": "shrink-only",
133
+ "entries": [
134
+ {
135
+ "id": "cli-layer-hosts-shared-surfaces",
136
+ "reason": "src/cli owns dispatch surface and terminal presentation toolkit; handlers reach up for formatting.",
137
+ "edges": [
138
+ "command -> cli_surface",
139
+ "orchestration -> cli_surface",
140
+ "knowledge -> cli_surface"
141
+ ],
142
+ "baseline_module_edges": 4,
143
+ "ceiling_module_edges": 4
144
+ },
145
+ {
146
+ "id": "command-layer-hosts-shared-contracts",
147
+ "reason": "Shared shapes hosted in the command layer rather than below it.",
148
+ "edges": ["knowledge -> command", "release -> command"],
149
+ "baseline_module_edges": 2,
150
+ "ceiling_module_edges": 2
151
+ },
152
+ {
153
+ "id": "release-knowledge-cycle",
154
+ "reason": "release and triwiki form a real two-module cycle; only the upward half is excepted.",
155
+ "edges": ["knowledge -> release"],
156
+ "baseline_module_edges": 1,
157
+ "ceiling_module_edges": 1
158
+ },
159
+ {
160
+ "id": "script-root-hosts-libraries",
161
+ "reason": "src/scripts is an entry root but some files are imported as libraries.",
162
+ "edges": ["cli_surface -> gate_script", "release -> gate_script"],
163
+ "baseline_module_edges": 2,
164
+ "ceiling_module_edges": 2
165
+ }
166
+ ]
167
+ }
168
+ }
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "8.3.2"
262
+ version = "8.4.0"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "8.3.2"
3
+ version = "8.4.0"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -23,6 +23,8 @@ export function usage(command = 'bridge') {
23
23
  ` sks ${command} provider validate|enable|disable <codex-lb|openrouter> [--json]`,
24
24
  ` sks ${command} provider remove-credential <codex-lb|openrouter> --confirm [--json]`,
25
25
  ` sks ${command} catalog sync|status [--json]`,
26
+ ` sks ${command} models list [--json]`,
27
+ ` sks ${command} models select --set <public-id,...> [--json]`,
26
28
  ` sks ${command} route list [--json]`,
27
29
  ` sks ${command} route set-default <codex-lb|openrouter> [--json]`,
28
30
  ` sks ${command} route explain <model> [--json]`,
@@ -204,6 +206,22 @@ async function parseInvocation(args, io) {
204
206
  label: `Combined catalog ${action}`
205
207
  };
206
208
  }
209
+ if (area === 'models') {
210
+ if (action === 'list' && target === undefined) {
211
+ allowOnly(parsed, ['--json'], []);
212
+ return { ...base, request: { operation: 'models.list' }, label: 'Selectable bridge models' };
213
+ }
214
+ if (action === 'select') {
215
+ allowOnly(parsed, ['--json'], ['--set']);
216
+ const raw = parsed.values.get('--set') || '';
217
+ const publicIds = raw.split(',').map((id) => id.trim()).filter(Boolean);
218
+ return {
219
+ ...base,
220
+ request: { operation: 'models.select', public_ids: publicIds },
221
+ label: 'Select exposed bridge models'
222
+ };
223
+ }
224
+ }
207
225
  if (area === 'route') {
208
226
  if (action === 'list' && target === undefined) {
209
227
  allowOnly(parsed, ['--json'], []);
@@ -254,7 +272,7 @@ function parseArgs(args) {
254
272
  const booleanOptions = new Set([
255
273
  '--json', '--strict', '--require-ready', '--api-key-stdin', '--confirm'
256
274
  ]);
257
- const valueOptions = new Set(['--level', '--host', '--settings']);
275
+ const valueOptions = new Set(['--level', '--host', '--settings', '--set']);
258
276
  for (let index = 0; index < args.length; index += 1) {
259
277
  const value = String(args[index] || '');
260
278
  if (!value.startsWith('--')) {
@@ -43,7 +43,7 @@ export function doctorPhaseIdsForProfile(profile) {
43
43
  ];
44
44
  if (profile === 'migration')
45
45
  return required;
46
- const optional = ['supabase_mcp_repair', 'native_capability_repair', 'sks_menubar'];
46
+ const optional = ['supabase_mcp_repair', 'native_capability_repair', 'sks_menubar', 'desktop_bridge_catalog_repair'];
47
47
  if (profile === 'full' || profile === 'capabilities')
48
48
  return ['setup', ...required, ...optional];
49
49
  return [...required, ...optional];
@@ -7,7 +7,7 @@ import { ui as cliUi } from '../cli/cli-theme.js';
7
7
  import { getCodexInfo } from '../core/codex-adapter.js';
8
8
  import { rustInfo } from '../core/rust-accelerator.js';
9
9
  import { codexAppIntegrationStatus } from '../core/codex-app.js';
10
- import { desktopBridgeStatusV3 } from '../core/codex-lb/desktop-controller-v3.js';
10
+ import { desktopBridgeStatusV3, executeDesktopBridgeCommandV3 } from '../core/codex-lb/desktop-controller-v3.js';
11
11
  import { inspectCodexConfigReadability } from '../core/codex/codex-config-readability.js';
12
12
  import { inspectOAuthCallbackPortConflict, oauthCallbackDoctorGuidance } from '../core/codex/oauth-callback-port-diagnostic.js';
13
13
  import { inventoryCodexPermissionProfiles } from '../core/codex/codex-permission-profiles.js';
@@ -984,6 +984,38 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
984
984
  route_blockers: doctorNativeCapabilityRepair?.route_blockers || {},
985
985
  rollback_evidence: doctorNativeCapabilityRepair?.secret_preservation_guard || 'native_capability_repair_report'
986
986
  })
987
+ },
988
+ {
989
+ id: 'desktop_bridge_catalog_repair',
990
+ required_for_ready: false,
991
+ run: async () => {
992
+ const base = {
993
+ id: 'desktop_bridge_catalog_repair',
994
+ required_for_ready: false,
995
+ manual_required: false,
996
+ warnings: [],
997
+ rollback_evidence: 'combined_catalog_previous_generation_preserved'
998
+ };
999
+ try {
1000
+ const status = await desktopBridgeStatusV3({ home: root, env: process.env });
1001
+ const blockers = (status?.readiness?.blockers || []).map(String);
1002
+ const stale = blockers.filter((blocker) => blocker.endsWith('_catalog_stale'));
1003
+ if (!status?.management?.managed || stale.length === 0) {
1004
+ return { ...base, ok: true, repaired: false, blockers: [] };
1005
+ }
1006
+ const sync = await executeDesktopBridgeCommandV3({ operation: 'catalog.sync' }, { home: root, env: process.env });
1007
+ const synced = sync?.ok === true && sync?.execution?.ok === true;
1008
+ return {
1009
+ ...base,
1010
+ ok: synced,
1011
+ repaired: synced,
1012
+ blockers: synced ? [] : (sync?.execution?.blockers || ['desktop_bridge_catalog_sync_failed']).map(String)
1013
+ };
1014
+ }
1015
+ catch (error) {
1016
+ return { ...base, ok: false, repaired: false, blockers: [String(error?.message || 'desktop_bridge_catalog_sync_failed')] };
1017
+ }
1018
+ }
987
1019
  }
988
1020
  ].filter((phase) => doctorPhaseIds.includes(phase.id))
989
1021
  }).catch((err) => ({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "8.3.2",
3
+ "package_version": "8.4.0",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import fsp from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
6
+ import { ARCHITECTURE_MAP_ARTIFACT_RELS } from '../triwiki/context-graph/store/architecture-map-store.js';
6
7
  import { CODE_NAVIGATION_LIMITS } from '../triwiki/code-navigation-policy.js';
7
8
  export const ALIGN_PLAN_ARTIFACT = 'align-plan.json';
8
9
  export const ALIGN_LEDGER_ARTIFACT = 'align-ledger.json';
@@ -21,7 +22,8 @@ export const ALIGN_OUTPUT_ARTIFACTS = Object.freeze([
21
22
  '.sneakoscope/wiki/context-graph.meta.json',
22
23
  '.sneakoscope/wiki/code-navigation-manifest.json',
23
24
  '.sneakoscope/wiki/code-pack.json',
24
- '.sneakoscope/wiki/context-pack.json'
25
+ '.sneakoscope/wiki/context-pack.json',
26
+ ...ARCHITECTURE_MAP_ARTIFACT_RELS
25
27
  ]);
26
28
  export function buildAlignPlan(missionId, task) {
27
29
  return {
@@ -143,7 +145,11 @@ export function evaluateAlignGate(plan, ledger, missionId, activeVerification =
143
145
  skips: (ledger?.scan.fatal_skips.length ?? 1) === 0,
144
146
  coverage: ledger?.graph.exact_file_coverage === true && ledger.graph.missing_files.length === 0 && ledger.graph.unexpected_files.length === 0,
145
147
  cas: ledger?.scan.source_cas_verified === true,
146
- extractor: ledger?.graph.extractor_ids.length === 1 && ledger.graph.extractor_ids[0] === 'code',
148
+ extractor: Array.isArray(ledger?.graph.extractor_ids)
149
+ && ledger.graph.extractor_ids.length === 3
150
+ && ledger.graph.extractor_ids[0] === 'code'
151
+ && ledger.graph.extractor_ids[1] === 'topology'
152
+ && ledger.graph.extractor_ids[2] === 'triwiki-evidence',
147
153
  publication: ledger?.publication.staged === true && ledger.publication.transactional_directory_replaced === true,
148
154
  artifacts: ledger?.validation.artifact_hashes === true && activeVerification.artifacts,
149
155
  projection: ledger?.validation.projection === true && activeVerification.projections,
@@ -7,15 +7,40 @@ import { guardedRename, guardedRm, guardContextForRoute } from '../safety/mutati
7
7
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
8
8
  import { projectTriwikiToAgentsMdTransactional } from '../triwiki/agents-md-projector.js';
9
9
  import { buildCodeNavigationContextPack, validateCodeNavigationContextPack } from '../triwiki/code-navigation-context-pack.js';
10
- import { CODE_NAVIGATION_FATAL_SKIP_REASONS, CODE_NAVIGATION_LIMITS, inspectCodeNavigationSources } from '../triwiki/code-navigation-policy.js';
10
+ import { CODE_NAVIGATION_FATAL_SKIP_REASONS, CODE_NAVIGATION_LIMITS } from '../triwiki/code-navigation-policy.js';
11
11
  import { isCodePackProjectionBoundToSnapshot, projectCodePackFromGraph, validateCodePack } from '../triwiki/code-pack.js';
12
12
  import { validateContextGraphSnapshot } from '../triwiki/context-graph/contracts.js';
13
13
  import { compileContextGraph } from '../triwiki/context-graph/compiler/index.js';
14
14
  import { readSourceHashes } from '../triwiki/context-graph/compiler/freshness.js';
15
- import { codeNavigationGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
15
+ import { ARCHITECTURE_MAP_EXTRACTOR_IDS, architectureMapGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
16
+ import { computeContextGraphCacheKey } from '../triwiki/context-graph/compiler/cache-key.js';
17
+ import { walkCodeInventory } from '../triwiki/context-graph/extractors/code/inventory.js';
18
+ import { codeInventoryInputHashes } from '../triwiki/code-navigation-policy.js';
16
19
  import { clearContextGraphSnapshotCache } from '../triwiki/context-graph/query/snapshot-cache.js';
17
20
  import { withTriWikiStateLock } from '../triwiki/triwiki-cleanup.js';
21
+ import { publishArchitectureMapToStage } from '../triwiki/context-graph/store/architecture-map-store.js';
18
22
  import { ALIGN_GATE_ARTIFACT, ALIGN_LEDGER_ARTIFACT, ALIGN_OUTPUT_ARTIFACTS, ALIGN_PLAN_ARTIFACT, ALIGN_STAGING_ROOT_REL, buildInitialAlignLedger, refreshAlignGate } from './align-route.js';
23
+ const WIKI_PREFIX = '.sneakoscope/wiki/';
24
+ function isArchitectureMapOptionalSkip(skip) {
25
+ const rel = skip.path.replace(/\\/g, '/');
26
+ if (skip.reason !== 'unreadable' && skip.reason !== 'excluded')
27
+ return false;
28
+ if (rel === '.sneakoscope/wiki/context-pack.json')
29
+ return true;
30
+ if (rel.startsWith('.sneakoscope/triwiki/proof-bank'))
31
+ return true;
32
+ if (rel.startsWith('.sneakoscope/wiki/'))
33
+ return true;
34
+ return false;
35
+ }
36
+ function isAlignFatalSkip(skip) {
37
+ if (!CODE_NAVIGATION_FATAL_SKIP_REASONS.has(skip.reason))
38
+ return false;
39
+ return !isArchitectureMapOptionalSkip(skip);
40
+ }
41
+ if (new Set(ALIGN_OUTPUT_ARTIFACTS).size !== ALIGN_OUTPUT_ARTIFACTS.length) {
42
+ throw new Error('align_output_artifacts_not_unique');
43
+ }
19
44
  const ALIGN_REPLACED_SURFACES = Object.freeze([
20
45
  { key: 'wiki', rel: '.sneakoscope/wiki' },
21
46
  { key: 'memory', rel: '.sneakoscope/memory' },
@@ -116,19 +141,19 @@ async function buildPack(root, snapshot, meta, generatedAt) {
116
141
  observedSourceHashes: observed
117
142
  }).pack;
118
143
  }
119
- function buildManifest(generatedAt, inspection, snapshot) {
144
+ function buildManifest(generatedAt, inventory, inventoryDigest, snapshot) {
120
145
  return {
121
146
  schema: 'sks.code-navigation-manifest.v1',
122
147
  generated_at: generatedAt,
123
148
  source_policy: 'repository_code_only',
124
149
  exhaustive_graph: 'context-graph.json',
125
- inventory_digest: inspection.inventoryDigest,
150
+ inventory_digest: inventoryDigest,
126
151
  snapshot_hash: snapshot.snapshotHash,
127
- source_file_count: inspection.inventory.files.length,
152
+ source_file_count: inventory.files.length,
128
153
  symbol_count: snapshot.nodes.filter((node) => node.kind === 'symbol').length,
129
154
  edge_count: snapshot.edgeCount,
130
155
  extractors: snapshot.extractors.map(({ id, revision }) => ({ id, revision })),
131
- source_files: inspection.inventory.files.map((file) => ({
156
+ source_files: inventory.files.map((file) => ({
132
157
  path: file.rel,
133
158
  sha256: file.hash,
134
159
  language: file.language,
@@ -141,13 +166,15 @@ function buildManifest(generatedAt, inspection, snapshot) {
141
166
  }
142
167
  async function validateStaging(input) {
143
168
  for (const artifact of ALIGN_OUTPUT_ARTIFACTS) {
144
- const stageFile = path.join(input.stageWiki, path.basename(artifact));
169
+ if (!artifact.startsWith(WIKI_PREFIX))
170
+ return false;
171
+ const stageFile = path.join(input.stageWiki, artifact.slice(WIKI_PREFIX.length));
145
172
  const stat = await fsp.stat(stageFile).catch(() => null);
146
173
  if (!stat?.isFile() || stat.size <= 0)
147
174
  return false;
148
175
  }
149
176
  const meta = await readJson(path.join(input.stageWiki, 'context-graph.meta.json'), null);
150
- if (meta?.snapshotHash !== input.snapshot.snapshotHash || meta?.cacheKeyParts?.sourcePolicy !== 'repository_code_only')
177
+ if (meta?.snapshotHash !== input.snapshot.snapshotHash || meta?.cacheKeyParts?.sourcePolicy !== 'workspace')
151
178
  return false;
152
179
  const manifest = await readJson(path.join(input.stageWiki, 'code-navigation-manifest.json'), null);
153
180
  if (manifest?.inventory_digest !== input.manifest.inventory_digest || manifest?.source_file_count !== input.manifest.source_file_count)
@@ -174,21 +201,31 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
174
201
  ledger.validation.absent_or_existing_input_supported = plan.requires_cleanup_receipt === false
175
202
  && plan.acceptance.absent_or_existing_triwiki_supported === true;
176
203
  ledger.validation.prior_state_ignored = true;
177
- const extractorIdentities = codeNavigationGraphExtractors();
178
- const inspection = await inspectCodeNavigationSources(root, extractorIdentities, CODE_NAVIGATION_LIMITS);
179
- const extractors = codeNavigationGraphExtractors({ preparedInventory: inspection.inventory });
180
- ledger.scan.inventory_digest = inspection.inventoryDigest;
181
- ledger.scan.source_file_count = inspection.inventory.files.length;
182
- ledger.scan.source_bytes = inspection.inventory.files.reduce((sum, file) => sum + file.bytes, 0);
183
- ledger.scan.source_lines = inspection.inventory.files.reduce((sum, file) => sum + file.lines, 0);
184
- ledger.scan.languages = countsBy(inspection.inventory.files, (file) => file.language);
185
- ledger.scan.allowed_exclusions = inspection.inventory.skipped
186
- .filter((skip) => !CODE_NAVIGATION_FATAL_SKIP_REASONS.has(skip.reason))
204
+ const extractorIdentities = architectureMapGraphExtractors();
205
+ const inventory = walkCodeInventory(root, CODE_NAVIGATION_LIMITS);
206
+ const inputHashes = codeInventoryInputHashes(inventory);
207
+ const codeInventoryDigest = crypto
208
+ .createHash('sha256')
209
+ .update(Object.entries(inputHashes)
210
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
211
+ .map(([relative, hash]) => `${relative}\0${hash}`)
212
+ .join('\n'))
213
+ .digest('hex');
214
+ const cacheKey = await computeContextGraphCacheKey({ root, extractors: extractorIdentities });
215
+ const fatalSkips = inventory.skipped.filter((skip) => isAlignFatalSkip(skip));
216
+ ledger.scan.inventory_digest = codeInventoryDigest;
217
+ ledger.scan.source_file_count = inventory.files.length;
218
+ ledger.scan.source_bytes = inventory.files.reduce((sum, file) => sum + file.bytes, 0);
219
+ ledger.scan.source_lines = inventory.files.reduce((sum, file) => sum + file.lines, 0);
220
+ ledger.scan.languages = countsBy(inventory.files, (file) => file.language);
221
+ ledger.scan.allowed_exclusions = inventory.skipped
222
+ .filter((skip) => !isAlignFatalSkip(skip))
187
223
  .map((skip) => ({ path: skip.path, reason: skip.reason }));
188
- ledger.scan.fatal_skips = inspection.fatalSkips.map((skip) => ({ ...skip }));
189
- ledger.validation.source_inventory_complete = inspection.fatalSkips.length === 0;
190
- if (inspection.fatalSkips.length)
191
- throw new Error(`code_navigation_fatal_skips:${inspection.fatalSkips.map((skip) => `${skip.reason}:${skip.path}`).join('|')}`);
224
+ ledger.scan.fatal_skips = fatalSkips.map((skip) => ({ ...skip }));
225
+ ledger.validation.source_inventory_complete = fatalSkips.length === 0;
226
+ if (fatalSkips.length)
227
+ throw new Error(`code_navigation_fatal_skips:${fatalSkips.map((skip) => `${skip.reason}:${skip.path}`).join('|')}`);
228
+ const extractors = architectureMapGraphExtractors({ preparedInventory: inventory });
192
229
  const generatedAt = nowIso();
193
230
  const compiled = await compileContextGraph({
194
231
  root,
@@ -197,12 +234,12 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
197
234
  limits: CODE_NAVIGATION_LIMITS,
198
235
  observedAt: generatedAt,
199
236
  useFragmentCache: false,
200
- cacheKey: inspection.cacheKey,
237
+ cacheKey,
201
238
  persistArtifacts: false,
202
239
  useCompileLock: false
203
240
  });
204
241
  ledger.validation.graph_compile = compiled.ok;
205
- const compileFatal = compiled.skipped.filter((skip) => CODE_NAVIGATION_FATAL_SKIP_REASONS.has(skip.reason));
242
+ const compileFatal = compiled.skipped.filter((skip) => isAlignFatalSkip(skip));
206
243
  if (compileFatal.length)
207
244
  ledger.scan.fatal_skips.push(...compileFatal.map((skip) => ({ ...skip })));
208
245
  if (!compiled.ok || !compiled.snapshot || !compiled.meta || compileFatal.length) {
@@ -218,7 +255,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
218
255
  ledger.graph.test_nodes = snapshot.nodes.filter((node) => node.kind === 'test').length;
219
256
  ledger.graph.edge_count = snapshot.edgeCount;
220
257
  ledger.graph.edges_by_type = countsBy(snapshot.edges, (edge) => edge.type);
221
- const inventoryPaths = inspection.inventory.files.map((file) => file.rel).sort();
258
+ const inventoryPaths = inventory.files.map((file) => file.rel).sort();
222
259
  const fileNodePaths = snapshot.nodes.filter((node) => node.kind === 'file').map((node) => String(node.path || '')).filter(Boolean).sort();
223
260
  const inventorySet = new Set(inventoryPaths);
224
261
  const fileNodeSet = new Set(fileNodePaths);
@@ -229,12 +266,16 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
229
266
  && fileNodePaths.length === inventoryPaths.length;
230
267
  if (!ledger.graph.exact_file_coverage)
231
268
  throw new Error('code_navigation_exact_file_coverage_failed');
232
- if (ledger.graph.extractor_ids.length !== 1 || ledger.graph.extractor_ids[0] !== 'code')
233
- throw new Error('code_navigation_non_code_extractor_present');
269
+ const expectedExtractors = [...ARCHITECTURE_MAP_EXTRACTOR_IDS];
270
+ if (ledger.graph.extractor_ids.length !== expectedExtractors.length
271
+ || expectedExtractors.some((id, index) => ledger.graph.extractor_ids[index] !== id)) {
272
+ throw new Error(`code_navigation_architecture_map_extractors_mismatch:${ledger.graph.extractor_ids.join(',')}`);
273
+ }
234
274
  const graphValidation = validateContextGraphSnapshot(snapshot);
235
275
  ledger.validation.graph_schema = graphValidation.ok;
236
- if (!graphValidation.ok || meta.cacheKeyParts.sourcePolicy !== 'repository_code_only')
276
+ if (!graphValidation.ok || meta.cacheKeyParts.sourcePolicy !== 'workspace') {
237
277
  throw new Error('code_navigation_graph_validation_failed');
278
+ }
238
279
  const pack = await buildPack(root, snapshot, meta, generatedAt);
239
280
  const packValidation = await validateCodePack(pack, root);
240
281
  ledger.validation.code_pack = packValidation.ok
@@ -255,7 +296,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
255
296
  ledger.validation.context_pack = contextValidation.ok;
256
297
  if (!contextValidation.ok)
257
298
  throw new Error('code_navigation_context_pack_invalid');
258
- const manifest = buildManifest(generatedAt, inspection, snapshot);
299
+ const manifest = buildManifest(generatedAt, inventory, codeInventoryDigest, snapshot);
259
300
  const stageRoot = path.join(stageBase, `${missionId}-${randomUUID().slice(0, 8)}`);
260
301
  const stageWiki = path.join(stageRoot, 'new-wiki');
261
302
  const previousRoot = path.join(stageRoot, 'previous');
@@ -271,6 +312,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
271
312
  await writeJsonAtomic(path.join(stageWiki, 'code-navigation-manifest.json'), manifest);
272
313
  await writeJsonAtomic(path.join(stageWiki, 'code-pack.json'), pack);
273
314
  await writeJsonAtomic(path.join(stageWiki, 'context-pack.json'), contextPack);
315
+ await publishArchitectureMapToStage(stageWiki, snapshot, { root, missionId });
274
316
  ledger.publication.staged = true;
275
317
  ledger.validation.staged_readback = await validateStaging({ stageWiki, root, snapshot, pack, contextPack, manifest });
276
318
  if (!ledger.validation.staged_readback)
@@ -308,9 +350,23 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
308
350
  }
309
351
  ledger.validation.artifact_hashes = Object.keys(ledger.publication.artifact_sha256).length === ALIGN_OUTPUT_ARTIFACTS.length;
310
352
  await beforeFinalSourceCas?.();
311
- const finalInspection = await inspectCodeNavigationSources(root, extractorIdentities, CODE_NAVIGATION_LIMITS);
312
- ledger.scan.source_cas_verified = finalInspection.fatalSkips.length === 0
313
- && finalInspection.cacheKey.key === inspection.cacheKey.key;
353
+ const finalInventory = walkCodeInventory(root, CODE_NAVIGATION_LIMITS);
354
+ const finalFatal = finalInventory.skipped.filter((skip) => isAlignFatalSkip(skip));
355
+ const finalCacheKey = await computeContextGraphCacheKey({ root, extractors: extractorIdentities });
356
+ const finalCodeDigest = crypto
357
+ .createHash('sha256')
358
+ .update(Object.entries(codeInventoryInputHashes(finalInventory))
359
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
360
+ .map(([relative, hash]) => `${relative}\0${hash}`)
361
+ .join('\n'))
362
+ .digest('hex');
363
+ const casStable = finalCacheKey.parts.tsconfigHash === cacheKey.parts.tsconfigHash
364
+ && finalCacheKey.parts.commandManifestHash === cacheKey.parts.commandManifestHash
365
+ && finalCacheKey.parts.gateManifestHash === cacheKey.parts.gateManifestHash
366
+ && finalCacheKey.parts.schemaRevision === cacheKey.parts.schemaRevision
367
+ && finalCacheKey.parts.proofIndexHash === cacheKey.parts.proofIndexHash
368
+ && finalCodeDigest === codeInventoryDigest;
369
+ ledger.scan.source_cas_verified = finalFatal.length === 0 && casStable;
314
370
  if (!ledger.scan.source_cas_verified)
315
371
  throw new Error('code_navigation_source_changed_during_scan');
316
372
  commitStarted = true;