sneakoscope 7.3.0 → 7.6.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 (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,237 @@
1
+ import path from 'node:path';
2
+ import { CONTEXT_GRAPH_META_SCHEMA, CONTEXT_GRAPH_SCHEMA_REVISION } from '../contracts.js';
3
+ import { buildContextGraphIndex, computeStronglyConnectedComponents, outgoingEdges } from '../graph-index.js';
4
+ import { tryNormalizeGraphPath } from '../paths.js';
5
+ import { runContextGraphLint } from '../lint/index.js';
6
+ import { withContextGraphCompileLock } from '../store/compile-lock.js';
7
+ import { appendContextGraphEvent } from '../store/event-log.js';
8
+ import { readContextGraphMeta, readContextGraphSnapshot, readContextGraphSnapshotHash, writeContextGraphSnapshot } from '../store/snapshot-store.js';
9
+ import { computeContextGraphCacheKey } from './cache-key.js';
10
+ import { DEFAULT_CONTEXT_GRAPH_LIMITS, runContextGraphExtractors } from './extract.js';
11
+ import { applyContextGraphFreshness } from './freshness.js';
12
+ import { applyContextGraphCaps, carryForwardFromSnapshot, mergeContextGraphFragments } from './merge.js';
13
+ import { buildContextGraphSnapshot, contextGraphCycleId } from './serialize.js';
14
+ const MAX_META_SKIPPED = 200;
15
+ function resolveLimits(partial) {
16
+ return { ...DEFAULT_CONTEXT_GRAPH_LIMITS, ...(partial ?? {}) };
17
+ }
18
+ function normalizeChangedPaths(root, changedPaths) {
19
+ if (changedPaths === null || changedPaths === undefined)
20
+ return null;
21
+ const out = new Set();
22
+ for (const candidate of changedPaths) {
23
+ const normalized = tryNormalizeGraphPath(root, candidate);
24
+ if (normalized)
25
+ out.add(normalized);
26
+ }
27
+ return [...out].sort();
28
+ }
29
+ function failure(reason, blockers, durationMs, extra = {}) {
30
+ return {
31
+ ok: false,
32
+ snapshot: null,
33
+ meta: null,
34
+ issues: [],
35
+ wrote: false,
36
+ snapshotHash: null,
37
+ previousSnapshotHash: null,
38
+ durationMs,
39
+ skipped: [],
40
+ blockers,
41
+ reason,
42
+ cacheKey: null,
43
+ cacheReusable: false,
44
+ incremental: false,
45
+ droppedEdges: [],
46
+ ...extra
47
+ };
48
+ }
49
+ function cyclesOf(snapshot) {
50
+ const index = buildContextGraphIndex(snapshot);
51
+ const components = computeStronglyConnectedComponents(snapshot.nodes.map((node) => node.id), (nodeId) => outgoingEdges(index, nodeId).map((edge) => ({ to: edge.to })));
52
+ return components.map((nodes) => ({ id: contextGraphCycleId(nodes), nodes }));
53
+ }
54
+ async function compileLocked(input, root, startedAt) {
55
+ const observedAt = input.observedAt ?? new Date().toISOString();
56
+ const limits = resolveLimits(input.limits);
57
+ const changedPaths = normalizeChangedPaths(root, input.changedPaths);
58
+ const cacheKey = await computeContextGraphCacheKey({
59
+ root,
60
+ extractors: input.extractors
61
+ });
62
+ await appendContextGraphEvent(root, {
63
+ type: 'compile.started',
64
+ at: observedAt,
65
+ cacheKey: cacheKey.key,
66
+ incremental: changedPaths !== null
67
+ });
68
+ const extraction = await runContextGraphExtractors({
69
+ root,
70
+ extractors: input.extractors,
71
+ changedPaths,
72
+ limits,
73
+ observedAt,
74
+ cacheKey: cacheKey.key,
75
+ useFragmentCache: (input.useFragmentCache ?? true) && cacheKey.reusable
76
+ });
77
+ if (extraction.blockers.length > 0) {
78
+ await appendContextGraphEvent(root, {
79
+ type: 'compile.blocked',
80
+ at: observedAt,
81
+ cacheKey: cacheKey.key,
82
+ reason: extraction.blockers[0] ?? 'extractor_failed'
83
+ });
84
+ return failure('extractor_failed', extraction.blockers, Date.now() - startedAt, {
85
+ cacheKey: cacheKey.key,
86
+ cacheReusable: cacheKey.reusable,
87
+ issues: extraction.issues
88
+ });
89
+ }
90
+ const carryForward = changedPaths === null ? null : await previousGeneration(root, cacheKey, changedPaths);
91
+ const merged = mergeContextGraphFragments({
92
+ fragments: extraction.fragments,
93
+ ...(carryForward === null ? {} : { carryForward })
94
+ });
95
+ const freshness = await applyContextGraphFreshness({
96
+ root,
97
+ nodes: merged.nodes,
98
+ edges: merged.edges
99
+ });
100
+ const capped = applyContextGraphCaps(freshness.nodes, merged.edges, limits);
101
+ const skipped = [...merged.skipped, ...capped.skipped];
102
+ const provisional = buildContextGraphSnapshot({
103
+ nodes: capped.nodes,
104
+ edges: capped.edges,
105
+ cycles: [],
106
+ extractors: merged.extractors
107
+ });
108
+ const snapshot = buildContextGraphSnapshot({
109
+ nodes: provisional.nodes,
110
+ edges: provisional.edges,
111
+ cycles: cyclesOf(provisional),
112
+ extractors: provisional.extractors
113
+ });
114
+ const inputHashes = { ...merged.inputHashes };
115
+ for (const [file, hash] of Object.entries(freshness.sourceHashes)) {
116
+ if (inputHashes[file] === undefined)
117
+ inputHashes[file] = hash;
118
+ }
119
+ const lint = runContextGraphLint({
120
+ root,
121
+ snapshot,
122
+ sourceHashes: freshness.sourceHashes,
123
+ skipped,
124
+ env: input.env ?? process.env
125
+ });
126
+ const issues = [...extraction.issues, ...merged.issues, ...lint.issues];
127
+ const errors = issues.filter((issue) => issue.severity === 'error');
128
+ if (errors.length > 0) {
129
+ await appendContextGraphEvent(root, {
130
+ type: 'compile.blocked',
131
+ at: observedAt,
132
+ cacheKey: cacheKey.key,
133
+ snapshotHash: snapshot.snapshotHash,
134
+ errorCount: errors.length,
135
+ warningCount: issues.length - errors.length,
136
+ reason: errors[0]?.code ?? 'lint_error'
137
+ });
138
+ return {
139
+ ok: false,
140
+ snapshot,
141
+ meta: null,
142
+ issues,
143
+ wrote: false,
144
+ snapshotHash: snapshot.snapshotHash,
145
+ previousSnapshotHash: null,
146
+ durationMs: Date.now() - startedAt,
147
+ skipped,
148
+ blockers: errors.map((issue) => `lint:${issue.code}`),
149
+ reason: 'lint_error',
150
+ cacheKey: cacheKey.key,
151
+ cacheReusable: cacheKey.reusable,
152
+ incremental: carryForward !== null,
153
+ droppedEdges: merged.dropped
154
+ };
155
+ }
156
+ const durationMs = Date.now() - startedAt;
157
+ const previousSnapshotHash = await readContextGraphSnapshotHash(root);
158
+ const meta = {
159
+ schema: CONTEXT_GRAPH_META_SCHEMA,
160
+ schemaRevision: CONTEXT_GRAPH_SCHEMA_REVISION,
161
+ snapshotHash: snapshot.snapshotHash,
162
+ previousSnapshotHash,
163
+ generatedAt: observedAt,
164
+ cacheKey: cacheKey.key,
165
+ cacheKeyParts: cacheKey.parts,
166
+ inputHashes,
167
+ nodeCount: snapshot.nodeCount,
168
+ edgeCount: snapshot.edgeCount,
169
+ lint: { ok: true, errors: 0, warnings: issues.length },
170
+ skipped: skipped.slice(0, MAX_META_SKIPPED),
171
+ durationMs
172
+ };
173
+ await writeContextGraphSnapshot({ root, snapshot, meta });
174
+ await appendContextGraphEvent(root, {
175
+ type: 'compile.committed',
176
+ at: observedAt,
177
+ cacheKey: cacheKey.key,
178
+ snapshotHash: snapshot.snapshotHash,
179
+ previousSnapshotHash,
180
+ nodeCount: snapshot.nodeCount,
181
+ edgeCount: snapshot.edgeCount,
182
+ errorCount: 0,
183
+ warningCount: issues.length,
184
+ durationMs,
185
+ incremental: carryForward !== null
186
+ });
187
+ return {
188
+ ok: true,
189
+ snapshot,
190
+ meta,
191
+ issues,
192
+ wrote: true,
193
+ snapshotHash: snapshot.snapshotHash,
194
+ previousSnapshotHash,
195
+ durationMs,
196
+ skipped,
197
+ blockers: [],
198
+ reason: null,
199
+ cacheKey: cacheKey.key,
200
+ cacheReusable: cacheKey.reusable,
201
+ incremental: carryForward !== null,
202
+ droppedEdges: merged.dropped
203
+ };
204
+ }
205
+ async function previousGeneration(root, cacheKey, changedPaths) {
206
+ if (!cacheKey.reusable)
207
+ return null;
208
+ const [snapshotLoad, metaLoad] = await Promise.all([
209
+ readContextGraphSnapshot(root),
210
+ readContextGraphMeta(root)
211
+ ]);
212
+ if (snapshotLoad.status !== 'ok' || !snapshotLoad.snapshot)
213
+ return null;
214
+ if (metaLoad.status !== 'ok' || !metaLoad.meta)
215
+ return null;
216
+ if (metaLoad.meta.snapshotHash !== snapshotLoad.snapshot.snapshotHash)
217
+ return null;
218
+ if (metaLoad.meta.schemaRevision !== CONTEXT_GRAPH_SCHEMA_REVISION)
219
+ return null;
220
+ if (metaLoad.meta.cacheKeyParts.schemaRevision !== cacheKey.parts.schemaRevision)
221
+ return null;
222
+ return carryForwardFromSnapshot(snapshotLoad.snapshot.nodes, snapshotLoad.snapshot.edges, changedPaths);
223
+ }
224
+ export async function compileContextGraph(input) {
225
+ const startedAt = Date.now();
226
+ const root = path.resolve(input.root);
227
+ const outcome = await withContextGraphCompileLock(root, () => compileLocked(input, root, startedAt));
228
+ if (outcome.acquired)
229
+ return outcome.value;
230
+ await appendContextGraphEvent(root, {
231
+ type: 'compile.lock_contended',
232
+ at: input.observedAt ?? new Date().toISOString(),
233
+ reason: 'lock_held'
234
+ });
235
+ return failure('lock_held', ['lock_held'], Date.now() - startedAt);
236
+ }
237
+ export { DEFAULT_CONTEXT_GRAPH_LIMITS } from './extract.js';
@@ -0,0 +1,115 @@
1
+ import { sha256 } from '../../../fsx.js';
2
+ import { CONTEXT_GRAPH_SCHEMA, CONTEXT_GRAPH_SCHEMA_REVISION } from '../contracts.js';
3
+ import { compareContextGraphIds, shortDigest } from '../ids.js';
4
+ function orderedMetadata(metadata) {
5
+ const out = {};
6
+ if (!metadata)
7
+ return out;
8
+ for (const key of Object.keys(metadata).sort()) {
9
+ const value = metadata[key];
10
+ if (value === undefined)
11
+ continue;
12
+ out[key] = Array.isArray(value) ? [...value] : value;
13
+ }
14
+ return out;
15
+ }
16
+ function orderedLocator(locator) {
17
+ return {
18
+ ...(locator.line === undefined ? {} : { line: locator.line }),
19
+ ...(locator.column === undefined ? {} : { column: locator.column }),
20
+ ...(locator.endLine === undefined ? {} : { endLine: locator.endLine }),
21
+ ...(locator.endColumn === undefined ? {} : { endColumn: locator.endColumn })
22
+ };
23
+ }
24
+ export function orderedNode(node) {
25
+ return {
26
+ id: node.id,
27
+ kind: node.kind,
28
+ label: node.label,
29
+ ...(node.path === undefined ? {} : { path: node.path }),
30
+ ...(node.locator === undefined ? {} : { locator: orderedLocator(node.locator) }),
31
+ ...(node.contentHash === undefined ? {} : { contentHash: node.contentHash }),
32
+ trust: node.trust,
33
+ freshness: node.freshness,
34
+ risk: node.risk,
35
+ tokenCost: node.tokenCost,
36
+ metadata: orderedMetadata(node.metadata)
37
+ };
38
+ }
39
+ export function orderedEdge(edge, includeObservedAt = true) {
40
+ const provenance = edge.provenance;
41
+ return {
42
+ id: edge.id,
43
+ from: edge.from,
44
+ to: edge.to,
45
+ type: edge.type,
46
+ confidence: edge.confidence,
47
+ provenance: {
48
+ path: provenance.path,
49
+ ...(provenance.line === undefined ? {} : { line: provenance.line }),
50
+ hash: provenance.hash,
51
+ extractor: provenance.extractor
52
+ },
53
+ observedAt: includeObservedAt ? edge.observedAt : ''
54
+ };
55
+ }
56
+ export function sortContextGraphNodes(nodes) {
57
+ return [...nodes].sort((left, right) => compareContextGraphIds(left.id, right.id));
58
+ }
59
+ export function sortContextGraphEdges(edges) {
60
+ return [...edges].sort((left, right) => compareContextGraphIds(left.id, right.id));
61
+ }
62
+ export function contextGraphCycleId(nodes) {
63
+ return `cycle:${shortDigest([...nodes].sort(compareContextGraphIds).join('\n'))}`;
64
+ }
65
+ export function sortContextGraphCycles(cycles) {
66
+ return cycles
67
+ .map((cycle) => ({ id: cycle.id, nodes: [...cycle.nodes].sort(compareContextGraphIds) }))
68
+ .sort((left, right) => compareContextGraphIds(left.id, right.id));
69
+ }
70
+ export function sortExtractorStats(stats) {
71
+ return [...stats]
72
+ .map((stat) => ({
73
+ id: stat.id,
74
+ revision: stat.revision,
75
+ nodeCount: stat.nodeCount,
76
+ edgeCount: stat.edgeCount,
77
+ issueCount: stat.issueCount,
78
+ skippedCount: stat.skippedCount
79
+ }))
80
+ .sort((left, right) => compareContextGraphIds(left.id, right.id));
81
+ }
82
+ export function canonicalContextGraphHashInput(draft) {
83
+ return JSON.stringify({
84
+ schema: CONTEXT_GRAPH_SCHEMA,
85
+ schemaRevision: CONTEXT_GRAPH_SCHEMA_REVISION,
86
+ nodes: sortContextGraphNodes(draft.nodes).map(orderedNode),
87
+ edges: sortContextGraphEdges(draft.edges).map((edge) => orderedEdge(edge, false)),
88
+ cycles: sortContextGraphCycles(draft.cycles),
89
+ extractors: sortExtractorStats(draft.extractors)
90
+ });
91
+ }
92
+ export function computeContextGraphSnapshotHash(draft) {
93
+ return sha256(canonicalContextGraphHashInput(draft));
94
+ }
95
+ export function buildContextGraphSnapshot(draft) {
96
+ const nodes = sortContextGraphNodes(draft.nodes).map(orderedNode);
97
+ const edges = sortContextGraphEdges(draft.edges).map((edge) => orderedEdge(edge));
98
+ const cycles = sortContextGraphCycles(draft.cycles);
99
+ const extractors = sortExtractorStats(draft.extractors);
100
+ const snapshotHash = computeContextGraphSnapshotHash({ nodes, edges, cycles, extractors });
101
+ return {
102
+ schema: CONTEXT_GRAPH_SCHEMA,
103
+ schemaRevision: CONTEXT_GRAPH_SCHEMA_REVISION,
104
+ snapshotHash,
105
+ nodes,
106
+ edges,
107
+ cycles,
108
+ extractors,
109
+ nodeCount: nodes.length,
110
+ edgeCount: edges.length
111
+ };
112
+ }
113
+ export function serializeContextGraphSnapshot(snapshot) {
114
+ return `${JSON.stringify(snapshot, null, 2)}\n`;
115
+ }
@@ -0,0 +1,236 @@
1
+ import { compareContextGraphIds } from './ids.js';
2
+ export const CONTEXT_GRAPH_SCHEMA = 'sks.context-graph.v1';
3
+ export const CONTEXT_GRAPH_FRAGMENT_SCHEMA = 'sks.context-graph-fragment.v1';
4
+ export const CONTEXT_GRAPH_META_SCHEMA = 'sks.context-graph-meta.v1';
5
+ export const CONTEXT_GRAPH_SCHEMA_REVISION = '1.0.0';
6
+ export const CONTEXT_GRAPH_MISSING_ERROR = 'context_graph_missing';
7
+ export const CONTEXT_GRAPH_STALE_ERROR = 'context_graph_stale';
8
+ export const CONTEXT_GRAPH_CORRUPT_ERROR = 'context_graph_corrupt';
9
+ export const CONTEXT_GRAPH_REPAIR_COMMAND = 'sks wiki refresh --code';
10
+ export const CONTEXT_GRAPH_NODE_KINDS = [
11
+ 'file',
12
+ 'symbol',
13
+ 'module',
14
+ 'command',
15
+ 'route',
16
+ 'pipeline',
17
+ 'test',
18
+ 'gate',
19
+ 'schema',
20
+ 'config',
21
+ 'wiki_claim',
22
+ 'source',
23
+ 'proof',
24
+ 'risk_domain'
25
+ ];
26
+ export const CONTEXT_GRAPH_EDGE_TYPES = [
27
+ 'contains',
28
+ 'defines',
29
+ 'imports',
30
+ 'reexports',
31
+ 'references',
32
+ 'calls',
33
+ 'depends_on',
34
+ 'routes_to',
35
+ 'owns',
36
+ 'tests',
37
+ 'verified_by',
38
+ 'gated_by',
39
+ 'affected_by',
40
+ 'cites',
41
+ 'derived_from',
42
+ 'supports',
43
+ 'contradicts',
44
+ 'supersedes',
45
+ 'invalidates',
46
+ 'cochanged_with',
47
+ 'conflicts_with'
48
+ ];
49
+ export const CONTEXT_GRAPH_LINT_ERROR_CODES = [
50
+ 'duplicate_node_conflict',
51
+ 'dangling_edge',
52
+ 'edge_without_provenance',
53
+ 'absolute_or_escaping_path',
54
+ 'symlink_escape',
55
+ 'secret_like_value',
56
+ 'unsupported_node_kind',
57
+ 'unsupported_edge_type',
58
+ 'non_deterministic_serialization',
59
+ 'protected_gate_without_source_relation',
60
+ 'manifest_dag_cycle',
61
+ 'hash_mismatch',
62
+ 'snapshot_meta_mismatch',
63
+ 'freshness_claim_mismatch',
64
+ 'invalid_node_field',
65
+ 'invalid_edge_field'
66
+ ];
67
+ export const CONTEXT_GRAPH_LINT_WARNING_CODES = [
68
+ 'orphan_wiki_claim',
69
+ 'single_source_low_trust_synthesis',
70
+ 'unknown_freshness',
71
+ 'high_fan_in_without_verification',
72
+ 'unreachable_in_profile',
73
+ 'extractor_skipped_input'
74
+ ];
75
+ export const CONTEXT_GRAPH_STALE_REASONS = [
76
+ 'head_changed',
77
+ 'dirty_fingerprint_changed',
78
+ 'schema_revision_changed',
79
+ 'tsconfig_changed',
80
+ 'command_manifest_changed',
81
+ 'gate_manifest_changed',
82
+ 'proof_index_changed',
83
+ 'wiki_context_changed',
84
+ 'git_state_unknown',
85
+ 'source_hash_mismatch',
86
+ 'meta_mismatch',
87
+ 'cache_key_changed'
88
+ ];
89
+ const NODE_KIND_SET = new Set(CONTEXT_GRAPH_NODE_KINDS);
90
+ const EDGE_TYPE_SET = new Set(CONTEXT_GRAPH_EDGE_TYPES);
91
+ const EDGE_CONFIDENCE_SET = new Set(['exact', 'syntactic', 'manifest', 'observed', 'derived']);
92
+ const FRESHNESS_SET = new Set(['fresh', 'stale', 'unknown']);
93
+ const RISK_SET = new Set(['low', 'medium', 'high', 'protected']);
94
+ export function isContextGraphNodeKind(value) {
95
+ return typeof value === 'string' && NODE_KIND_SET.has(value);
96
+ }
97
+ export function isContextGraphEdgeType(value) {
98
+ return typeof value === 'string' && EDGE_TYPE_SET.has(value);
99
+ }
100
+ export function lintError(code, message, extra = {}) {
101
+ return { ...extra, code, severity: 'error', message };
102
+ }
103
+ export function lintWarning(code, message, extra = {}) {
104
+ return { ...extra, code, severity: 'warning', message };
105
+ }
106
+ export function emptyContextGraphFragment(extractor, revision) {
107
+ return {
108
+ schema: CONTEXT_GRAPH_FRAGMENT_SCHEMA,
109
+ extractor,
110
+ extractorRevision: revision,
111
+ nodes: [],
112
+ edges: [],
113
+ issues: [],
114
+ skipped: [],
115
+ inputHashes: {}
116
+ };
117
+ }
118
+ function record(value) {
119
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
120
+ }
121
+ function validateNode(value, index, issues) {
122
+ const node = record(value);
123
+ if (!node) {
124
+ issues.push(lintError('invalid_node_field', `node[${index}] is not an object`));
125
+ return null;
126
+ }
127
+ const id = typeof node.id === 'string' ? node.id : '';
128
+ if (!id) {
129
+ issues.push(lintError('invalid_node_field', `node[${index}] has no id`));
130
+ return null;
131
+ }
132
+ if (!isContextGraphNodeKind(node.kind)) {
133
+ issues.push(lintError('unsupported_node_kind', `node ${id} has unsupported kind`, { nodeId: id }));
134
+ }
135
+ if (typeof node.label !== 'string' || !node.label) {
136
+ issues.push(lintError('invalid_node_field', `node ${id} has no label`, { nodeId: id }));
137
+ }
138
+ if (typeof node.trust !== 'number' || node.trust < 0 || node.trust > 1) {
139
+ issues.push(lintError('invalid_node_field', `node ${id} trust must be 0..1`, { nodeId: id }));
140
+ }
141
+ if (!FRESHNESS_SET.has(String(node.freshness))) {
142
+ issues.push(lintError('invalid_node_field', `node ${id} has invalid freshness`, { nodeId: id }));
143
+ }
144
+ if (!RISK_SET.has(String(node.risk))) {
145
+ issues.push(lintError('invalid_node_field', `node ${id} has invalid risk`, { nodeId: id }));
146
+ }
147
+ if (typeof node.tokenCost !== 'number' || node.tokenCost < 0) {
148
+ issues.push(lintError('invalid_node_field', `node ${id} tokenCost must be >= 0`, { nodeId: id }));
149
+ }
150
+ if (!record(node.metadata)) {
151
+ issues.push(lintError('invalid_node_field', `node ${id} metadata must be an object`, { nodeId: id }));
152
+ }
153
+ return id;
154
+ }
155
+ function validateEdge(value, index, nodeIds, issues) {
156
+ const edge = record(value);
157
+ if (!edge) {
158
+ issues.push(lintError('invalid_edge_field', `edge[${index}] is not an object`));
159
+ return;
160
+ }
161
+ const id = typeof edge.id === 'string' ? edge.id : '';
162
+ if (!id) {
163
+ issues.push(lintError('invalid_edge_field', `edge[${index}] has no id`));
164
+ return;
165
+ }
166
+ if (!isContextGraphEdgeType(edge.type)) {
167
+ issues.push(lintError('unsupported_edge_type', `edge ${id} has unsupported type`, { edgeId: id }));
168
+ }
169
+ if (!EDGE_CONFIDENCE_SET.has(String(edge.confidence))) {
170
+ issues.push(lintError('invalid_edge_field', `edge ${id} has invalid confidence`, { edgeId: id }));
171
+ }
172
+ const from = typeof edge.from === 'string' ? edge.from : '';
173
+ const to = typeof edge.to === 'string' ? edge.to : '';
174
+ if (!from || !nodeIds.has(from)) {
175
+ issues.push(lintError('dangling_edge', `edge ${id} has no resolvable from node`, { edgeId: id }));
176
+ }
177
+ if (!to || !nodeIds.has(to)) {
178
+ issues.push(lintError('dangling_edge', `edge ${id} has no resolvable to node`, { edgeId: id }));
179
+ }
180
+ const provenance = record(edge.provenance);
181
+ if (!provenance || typeof provenance.path !== 'string' || !provenance.path || typeof provenance.hash !== 'string' || !provenance.hash) {
182
+ issues.push(lintError('edge_without_provenance', `edge ${id} has no usable provenance`, { edgeId: id }));
183
+ }
184
+ if (typeof edge.observedAt !== 'string' || !edge.observedAt) {
185
+ issues.push(lintError('invalid_edge_field', `edge ${id} has no observedAt`, { edgeId: id }));
186
+ }
187
+ }
188
+ export function validateContextGraphSnapshot(value) {
189
+ const issues = [];
190
+ const snapshot = record(value);
191
+ if (!snapshot) {
192
+ issues.push(lintError('invalid_node_field', 'snapshot is not an object'));
193
+ return { ok: false, issues };
194
+ }
195
+ if (snapshot.schema !== CONTEXT_GRAPH_SCHEMA) {
196
+ issues.push(lintError('snapshot_meta_mismatch', `snapshot schema must be ${CONTEXT_GRAPH_SCHEMA}`));
197
+ }
198
+ if (typeof snapshot.snapshotHash !== 'string' || !snapshot.snapshotHash) {
199
+ issues.push(lintError('hash_mismatch', 'snapshot has no snapshotHash'));
200
+ }
201
+ const nodes = Array.isArray(snapshot.nodes) ? snapshot.nodes : null;
202
+ const edges = Array.isArray(snapshot.edges) ? snapshot.edges : null;
203
+ if (!nodes || !edges) {
204
+ issues.push(lintError('invalid_node_field', 'snapshot must carry nodes and edges arrays'));
205
+ return { ok: false, issues };
206
+ }
207
+ const nodeIds = new Set();
208
+ let previousNodeId = null;
209
+ for (const [index, raw] of nodes.entries()) {
210
+ const id = validateNode(raw, index, issues);
211
+ if (!id)
212
+ continue;
213
+ if (nodeIds.has(id))
214
+ issues.push(lintError('duplicate_node_conflict', `duplicate node id ${id}`, { nodeId: id }));
215
+ nodeIds.add(id);
216
+ if (previousNodeId !== null && compareContextGraphIds(previousNodeId, id) > 0) {
217
+ issues.push(lintError('non_deterministic_serialization', `nodes are not sorted by id at ${id}`, { nodeId: id }));
218
+ }
219
+ previousNodeId = id;
220
+ }
221
+ let previousEdgeId = null;
222
+ for (const [index, raw] of edges.entries()) {
223
+ validateEdge(raw, index, nodeIds, issues);
224
+ const edgeId = record(raw)?.id;
225
+ if (typeof edgeId !== 'string')
226
+ continue;
227
+ if (previousEdgeId !== null && compareContextGraphIds(previousEdgeId, edgeId) > 0) {
228
+ issues.push(lintError('non_deterministic_serialization', `edges are not sorted by id at ${edgeId}`, { edgeId }));
229
+ }
230
+ previousEdgeId = edgeId;
231
+ }
232
+ if (snapshot.nodeCount !== nodes.length || snapshot.edgeCount !== edges.length) {
233
+ issues.push(lintError('snapshot_meta_mismatch', 'snapshot counts do not match the serialized arrays'));
234
+ }
235
+ return { ok: issues.every((issue) => issue.severity !== 'error'), issues };
236
+ }