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
@@ -1,9 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { spawnSync } from 'node:child_process';
4
- import { ensureDir, exists, nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
5
- export const CODE_PACK_SCHEMA = 'sks.code-pack.v1';
6
- export const DEFAULT_CODE_PACK_TOKEN_BUDGET = 8000;
3
+ import { ensureDir, exists, readJson, writeJsonAtomic } from '../fsx.js';
4
+ import { buildCodePackFromGraph } from './context-graph/projections/code-pack.js';
5
+ import { CODE_PACK_SCHEMA } from './context-graph/projections/pack-contract.js';
6
+ export { CODE_PACK_SCHEMA, DEFAULT_CODE_PACK_TOKEN_BUDGET } from './context-graph/projections/pack-contract.js';
7
+ export { buildCodePackFromGraph, computeCodePackIndexDigest, isCodePackProjectionBoundToSnapshot, projectCodePackFromGraph } from './context-graph/projections/code-pack.js';
8
+ export { buildWorkspaceCodePack } from './context-graph/projections/code-pack-workspace.js';
7
9
  export function codePackDir(root) {
8
10
  return path.join(root, '.sneakoscope', 'wiki');
9
11
  }
@@ -13,126 +15,16 @@ export function codePackPath(root) {
13
15
  export function codePackPrevPath(root) {
14
16
  return path.join(codePackDir(root), 'code-pack.prev.json');
15
17
  }
16
- export function buildCodePack(root, index, tokenBudget = DEFAULT_CODE_PACK_TOKEN_BUDGET) {
17
- const normalizedBudget = Number.isFinite(tokenBudget) && tokenBudget >= 0
18
- ? Math.floor(tokenBudget)
19
- : DEFAULT_CODE_PACK_TOKEN_BUDGET;
20
- const entries = [];
21
- let totalTokenCost = 0;
22
- for (const card of index.modules) {
23
- const entry = buildEntryForModule(card);
24
- if (!entry)
25
- continue;
26
- if (totalTokenCost + entry.token_cost > normalizedBudget)
27
- continue;
28
- entries.push(entry);
29
- totalTokenCost += entry.token_cost;
30
- }
31
- return {
32
- schema: CODE_PACK_SCHEMA,
33
- generated_at: nowIso(),
34
- git_head_sha: readGitHeadSha(root),
35
- source_file_count: index.scanned_file_count,
36
- index_digest: computeIndexDigest(index),
37
- entries,
38
- token_budget: normalizedBudget,
39
- total_token_cost: totalTokenCost
40
- };
41
- }
42
- function buildEntryForModule(card) {
43
- const citations = collectCitations(card);
44
- if (!citations.length)
45
- return null;
46
- const text = summarizeModule(card);
47
- return {
48
- id: `code:${card.module_id}`,
49
- text,
50
- citations,
51
- trust_score: computeTrustScore(card),
52
- freshness: 'unknown',
53
- token_cost: Math.ceil(text.length / 4)
54
- };
55
- }
56
- function collectCitations(card) {
57
- const paths = [...card.paths, ...card.entry_points].filter(Boolean);
58
- const seen = new Set();
59
- const citations = [];
60
- for (const p of paths) {
61
- if (seen.has(p))
62
- continue;
63
- seen.add(p);
64
- citations.push({ path: p });
65
- }
66
- return citations;
67
- }
68
- function summarizeModule(card) {
69
- const role = inferRole(card);
70
- const primaryPath = card.paths[0] || card.module_id;
71
- const exportsPart = summarizeExports(card.exports_summary);
72
- const depsPart = summarizeDependencies(card.dependency_edges);
73
- const sizePart = `${card.file_count} file${card.file_count === 1 ? '' : 's'}, ${card.loc} loc, ${card.risk} risk`;
74
- return `${card.module_id} is ${role} at ${primaryPath} (${sizePart}). ${exportsPart} ${depsPart}`.trim().replace(/\s+/g, ' ');
75
- }
76
- function inferRole(card) {
77
- const idLower = card.module_id.toLowerCase();
78
- const pathLower = card.paths.join(' ').toLowerCase();
79
- if (idLower.includes('test') || pathLower.includes('__tests__'))
80
- return 'a test module';
81
- if (idLower.includes('cli') || pathLower.includes('cli'))
82
- return 'a CLI module';
83
- if (idLower.includes('command'))
84
- return 'a command module';
85
- if (idLower.includes('core'))
86
- return 'a core module';
87
- return 'a module';
88
- }
89
- function summarizeExports(exportsSummary) {
90
- if (!exportsSummary.length)
91
- return 'It has no detected top-level exports.';
92
- const preview = exportsSummary.slice(0, 5).map((line) => line.replace(/^export\s+/, '').replace(/\s*\{\s*$/, '').trim());
93
- const suffix = exportsSummary.length > preview.length ? `, and ${exportsSummary.length - preview.length} more` : '';
94
- return `Key exports: ${preview.join('; ')}${suffix}.`;
95
- }
96
- function summarizeDependencies(dependencyEdges) {
97
- if (!dependencyEdges.length)
98
- return 'It has no detected cross-module dependencies.';
99
- const preview = dependencyEdges.slice(0, 5);
100
- const suffix = dependencyEdges.length > preview.length ? `, and ${dependencyEdges.length - preview.length} more` : '';
101
- return `Depends on: ${preview.join(', ')}${suffix}.`;
102
- }
103
- function computeTrustScore(card) {
104
- let score = 0.5;
105
- if (card.entry_points.length > 0)
106
- score += 0.2;
107
- if (card.exports_summary.length > 0)
108
- score += 0.2;
109
- if (card.risk === 'high')
110
- score -= 0.15;
111
- else if (card.risk === 'low')
112
- score += 0.1;
113
- return Math.max(0, Math.min(1, Number(score.toFixed(2))));
114
- }
115
- function readGitHeadSha(root) {
116
- try {
117
- const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
118
- if (result.status !== 0 || result.error)
119
- return null;
120
- const sha = String(result.stdout || '').trim();
121
- return sha || null;
122
- }
123
- catch {
124
- return null;
125
- }
126
- }
127
- function computeIndexDigest(index) {
128
- const stable = index.modules
129
- .map((card) => ({ module_id: card.module_id, paths: [...card.paths].sort() }))
130
- .sort((a, b) => a.module_id.localeCompare(b.module_id));
131
- return sha256(JSON.stringify(stable));
18
+ export function buildCodePack(root, source, options = {}) {
19
+ return buildCodePackFromGraph(root, source, options);
132
20
  }
133
21
  export async function validateCodePack(pack, root) {
134
22
  const issues = [];
135
23
  const seenIds = new Set();
24
+ if (pack.schema !== CODE_PACK_SCHEMA)
25
+ issues.push(`unexpected pack schema: ${String(pack.schema)}`);
26
+ if (!pack.index_digest)
27
+ issues.push('pack has no index_digest');
136
28
  for (const entry of pack.entries) {
137
29
  if (seenIds.has(entry.id)) {
138
30
  issues.push(`duplicate entry id: ${entry.id}`);
@@ -0,0 +1,277 @@
1
+ import fs from 'node:fs';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { runProcess, sha256 } from '../../../fsx.js';
5
+ import { CONTEXT_GRAPH_SCHEMA_REVISION } from '../contracts.js';
6
+ import { shortDigest } from '../ids.js';
7
+ import { isWorkspaceRelativePosixPath } from '../paths.js';
8
+ const GIT_TIMEOUT_MS = 10_000;
9
+ const MAX_FINGERPRINT_FILES = 2000;
10
+ const MAX_FINGERPRINT_FILE_BYTES = 8 * 1024 * 1024;
11
+ const MAX_DIR_FINGERPRINT_ENTRIES = 4000;
12
+ const TSCONFIG_INPUTS = ['tsconfig.json', 'tsconfig.build.json'];
13
+ const COMMAND_MANIFEST_INPUTS = [
14
+ 'src/cli/command-registry.ts',
15
+ 'src/cli/command-manifest-lite.ts',
16
+ 'runtime-required-scripts.json'
17
+ ];
18
+ const GATE_MANIFEST_INPUTS = [
19
+ 'release-gates.v2.json',
20
+ 'infra-harness-gates.json',
21
+ 'config/architecture-budgets.v1.json'
22
+ ];
23
+ const PROOF_INDEX_DIR = '.sneakoscope/triwiki/proof-bank';
24
+ const WIKI_CONTEXT_DIR = '.sneakoscope/wiki';
25
+ const WIKI_CONTEXT_EXCLUDED = new Set([
26
+ 'context-graph.json',
27
+ 'context-graph.meta.json',
28
+ 'context-graph.prev.json',
29
+ 'context-graph-events.jsonl',
30
+ 'context-pack.json',
31
+ 'code-pack.json',
32
+ 'code-pack.prev.json'
33
+ ]);
34
+ const WIKI_CONTEXT_GIT_EXCLUDED = new Set([...WIKI_CONTEXT_EXCLUDED].map((name) => `${WIKI_CONTEXT_DIR}/${name}`));
35
+ const RELEVANT_EXTENSIONS = new Set([
36
+ '.ts',
37
+ '.tsx',
38
+ '.js',
39
+ '.mjs',
40
+ '.cjs',
41
+ '.json',
42
+ '.jsonc',
43
+ '.md',
44
+ '.toml',
45
+ '.yml',
46
+ '.yaml',
47
+ '.swift',
48
+ '.rs',
49
+ '.py'
50
+ ]);
51
+ function toPosix(value) {
52
+ return value.replace(/\\/g, '/');
53
+ }
54
+ async function hashFile(root, relative) {
55
+ if (!isWorkspaceRelativePosixPath(relative))
56
+ return null;
57
+ const absolute = path.resolve(root, relative);
58
+ let stat;
59
+ try {
60
+ stat = await fsp.lstat(absolute);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ if (stat.isSymbolicLink())
66
+ return `symlink:${shortDigest(relative)}`;
67
+ if (!stat.isFile())
68
+ return null;
69
+ if (stat.size > MAX_FINGERPRINT_FILE_BYTES)
70
+ return `oversize:${stat.size}`;
71
+ try {
72
+ return sha256(await fsp.readFile(absolute));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ async function fingerprintFiles(root, relatives) {
79
+ const rows = [];
80
+ const ordered = [...new Set(relatives)].sort();
81
+ let count = 0;
82
+ for (const relative of ordered) {
83
+ if (count >= MAX_FINGERPRINT_FILES) {
84
+ rows.push(`truncated:${ordered.length}`);
85
+ break;
86
+ }
87
+ count += 1;
88
+ const hash = await hashFile(root, relative);
89
+ rows.push(`${relative}:${hash ?? 'missing'}`);
90
+ }
91
+ return sha256(rows.join('\n'));
92
+ }
93
+ async function listDirectoryFiles(root, relativeDir) {
94
+ const absolute = path.resolve(root, relativeDir);
95
+ const out = [];
96
+ const stack = [absolute];
97
+ while (stack.length) {
98
+ const dir = stack.pop();
99
+ if (dir === undefined)
100
+ break;
101
+ let entries;
102
+ try {
103
+ entries = await fsp.readdir(dir, { withFileTypes: true });
104
+ }
105
+ catch {
106
+ continue;
107
+ }
108
+ for (const entry of entries) {
109
+ if (out.length >= MAX_DIR_FINGERPRINT_ENTRIES)
110
+ return out.sort();
111
+ const child = path.join(dir, entry.name);
112
+ if (entry.isDirectory()) {
113
+ stack.push(child);
114
+ continue;
115
+ }
116
+ if (!entry.isFile())
117
+ continue;
118
+ const relative = toPosix(path.relative(root, child));
119
+ if (!isWorkspaceRelativePosixPath(relative))
120
+ continue;
121
+ out.push(relative);
122
+ }
123
+ }
124
+ return out.sort();
125
+ }
126
+ async function fingerprintDirectory(root, relativeDir, exclude = new Set()) {
127
+ const files = (await listDirectoryFiles(root, relativeDir)).filter((relative) => !exclude.has(path.posix.basename(relative)));
128
+ return fingerprintFiles(root, files);
129
+ }
130
+ function parsePorcelain(stdout) {
131
+ const tracked = [];
132
+ const untracked = [];
133
+ for (const record of stdout.split('\0')) {
134
+ if (record.length < 4)
135
+ continue;
136
+ const status = record.slice(0, 2);
137
+ const file = record.slice(3);
138
+ if (!file)
139
+ continue;
140
+ if (status === '??')
141
+ untracked.push(toPosix(file));
142
+ else
143
+ tracked.push(toPosix(file));
144
+ }
145
+ return { tracked: tracked.sort(), untracked: untracked.sort() };
146
+ }
147
+ function isRelevant(relative) {
148
+ if (relative.startsWith('.sneakoscope/'))
149
+ return false;
150
+ if (relative.startsWith('node_modules/') || relative.includes('/node_modules/'))
151
+ return false;
152
+ if (relative.startsWith('dist/'))
153
+ return false;
154
+ return RELEVANT_EXTENSIONS.has(path.posix.extname(relative));
155
+ }
156
+ export async function readContextGraphGitState(root) {
157
+ const unknown = {
158
+ state: 'unknown',
159
+ head: null,
160
+ trackedDirtyFingerprint: 'unknown',
161
+ untrackedFingerprint: 'unknown',
162
+ dirtyPaths: []
163
+ };
164
+ const head = await runProcess('git', ['rev-parse', '--verify', 'HEAD'], {
165
+ cwd: root,
166
+ timeoutMs: GIT_TIMEOUT_MS
167
+ }).catch(() => null);
168
+ if (!head || head.code !== 0 || head.timedOut)
169
+ return unknown;
170
+ const headSha = head.stdout.trim();
171
+ if (!/^[0-9a-f]{7,64}$/.test(headSha))
172
+ return unknown;
173
+ const status = await runProcess('git', ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--no-renames'], { cwd: root, timeoutMs: GIT_TIMEOUT_MS }).catch(() => null);
174
+ if (!status || status.code !== 0 || status.timedOut || status.truncated)
175
+ return unknown;
176
+ const { tracked, untracked } = parsePorcelain(status.stdout);
177
+ const relevantTracked = tracked.filter((relative) => !WIKI_CONTEXT_GIT_EXCLUDED.has(relative));
178
+ const relevantUntracked = untracked.filter(isRelevant);
179
+ return {
180
+ state: relevantTracked.length === 0 && relevantUntracked.length === 0 ? 'clean' : 'dirty',
181
+ head: headSha,
182
+ trackedDirtyFingerprint: await fingerprintFiles(root, relevantTracked),
183
+ untrackedFingerprint: await fingerprintFiles(root, relevantUntracked),
184
+ dirtyPaths: [...new Set([...relevantTracked, ...relevantUntracked])].sort()
185
+ };
186
+ }
187
+ async function workspaceIdentity(root) {
188
+ let name = '';
189
+ try {
190
+ const parsed = JSON.parse(await fsp.readFile(path.join(root, 'package.json'), 'utf8'));
191
+ if (parsed && typeof parsed === 'object' && typeof parsed.name === 'string') {
192
+ name = parsed.name;
193
+ }
194
+ }
195
+ catch {
196
+ name = '';
197
+ }
198
+ return sha256(JSON.stringify({ name, dir: path.basename(path.resolve(root)) }));
199
+ }
200
+ function extractorSchemaRevision(extractors) {
201
+ const rows = [...extractors]
202
+ .map((extractor) => `${extractor.id}@${extractor.revision}`)
203
+ .sort();
204
+ return `${CONTEXT_GRAPH_SCHEMA_REVISION}+${shortDigest(rows.join('\n'))}`;
205
+ }
206
+ export function contextGraphCacheKey(parts) {
207
+ return sha256(JSON.stringify([
208
+ parts.workspaceIdentity,
209
+ parts.head ?? 'no-head',
210
+ parts.gitState,
211
+ parts.trackedDirtyFingerprint,
212
+ parts.untrackedFingerprint,
213
+ parts.schemaRevision,
214
+ parts.tsconfigHash,
215
+ parts.commandManifestHash,
216
+ parts.gateManifestHash,
217
+ parts.proofIndexHash,
218
+ parts.wikiContextHash
219
+ ]));
220
+ }
221
+ export async function computeContextGraphCacheKey(input) {
222
+ const root = path.resolve(input.root);
223
+ const git = await readContextGraphGitState(root);
224
+ const [tsconfigHash, commandManifestHash, gateManifestHash, proofIndexHash, wikiContextHash, identity] = await Promise.all([
225
+ fingerprintFiles(root, TSCONFIG_INPUTS),
226
+ fingerprintFiles(root, COMMAND_MANIFEST_INPUTS),
227
+ fingerprintFiles(root, GATE_MANIFEST_INPUTS),
228
+ fingerprintDirectory(root, PROOF_INDEX_DIR),
229
+ fingerprintDirectory(root, WIKI_CONTEXT_DIR, WIKI_CONTEXT_EXCLUDED),
230
+ workspaceIdentity(root)
231
+ ]);
232
+ const parts = {
233
+ workspaceIdentity: identity,
234
+ head: git.head,
235
+ gitState: git.state,
236
+ trackedDirtyFingerprint: git.trackedDirtyFingerprint,
237
+ untrackedFingerprint: git.untrackedFingerprint,
238
+ schemaRevision: extractorSchemaRevision(input.extractors),
239
+ tsconfigHash,
240
+ commandManifestHash,
241
+ gateManifestHash,
242
+ proofIndexHash,
243
+ wikiContextHash
244
+ };
245
+ const reusable = git.state !== 'unknown';
246
+ return {
247
+ key: contextGraphCacheKey(parts),
248
+ parts,
249
+ reusable,
250
+ reasons: reusable ? [] : ['git_state_unknown'],
251
+ dirtyPaths: git.dirtyPaths
252
+ };
253
+ }
254
+ const PART_REASONS = [
255
+ ['head', 'head_changed'],
256
+ ['trackedDirtyFingerprint', 'dirty_fingerprint_changed'],
257
+ ['untrackedFingerprint', 'dirty_fingerprint_changed'],
258
+ ['schemaRevision', 'schema_revision_changed'],
259
+ ['tsconfigHash', 'tsconfig_changed'],
260
+ ['commandManifestHash', 'command_manifest_changed'],
261
+ ['gateManifestHash', 'gate_manifest_changed'],
262
+ ['proofIndexHash', 'proof_index_changed'],
263
+ ['wikiContextHash', 'wiki_context_changed'],
264
+ ['workspaceIdentity', 'cache_key_changed']
265
+ ];
266
+ export function compareCacheKeyParts(previous, current) {
267
+ const reasons = [];
268
+ if (!previous)
269
+ return ['cache_key_changed'];
270
+ if (current.gitState === 'unknown' || previous.gitState === 'unknown')
271
+ reasons.push('git_state_unknown');
272
+ for (const [field, reason] of PART_REASONS) {
273
+ if (previous[field] !== current[field] && !reasons.includes(reason))
274
+ reasons.push(reason);
275
+ }
276
+ return reasons;
277
+ }
@@ -0,0 +1,104 @@
1
+ import { CONTEXT_GRAPH_FRAGMENT_SCHEMA, lintWarning } from '../contracts.js';
2
+ import { fragmentCacheKey, pruneFragmentCache, readCachedFragment, writeCachedFragment } from '../store/fragment-cache.js';
3
+ export const DEFAULT_CONTEXT_GRAPH_LIMITS = {
4
+ maxFiles: 5000,
5
+ maxFileBytes: 512 * 1024,
6
+ maxNodes: 50_000,
7
+ maxEdges: 200_000,
8
+ timeoutMs: 60_000
9
+ };
10
+ async function settle(promise) {
11
+ try {
12
+ return { ok: true, value: await promise };
13
+ }
14
+ catch (error) {
15
+ return { ok: false, error };
16
+ }
17
+ }
18
+ async function withTimeout(promise, timeoutMs) {
19
+ let timer;
20
+ const timeout = new Promise((resolve) => {
21
+ timer = setTimeout(() => resolve('timeout'), Math.max(1, timeoutMs));
22
+ if (typeof timer.unref === 'function')
23
+ timer.unref();
24
+ });
25
+ try {
26
+ return await Promise.race([settle(promise), timeout]);
27
+ }
28
+ finally {
29
+ if (timer)
30
+ clearTimeout(timer);
31
+ }
32
+ }
33
+ function isFragment(value, extractorId) {
34
+ if (!value || typeof value !== 'object')
35
+ return false;
36
+ const fragment = value;
37
+ return (fragment.schema === CONTEXT_GRAPH_FRAGMENT_SCHEMA
38
+ && fragment.extractor === extractorId
39
+ && Array.isArray(fragment.nodes)
40
+ && Array.isArray(fragment.edges));
41
+ }
42
+ function sortExtractors(extractors) {
43
+ return [...extractors].sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
44
+ }
45
+ export async function runContextGraphExtractors(input) {
46
+ const fragments = [];
47
+ const issues = [];
48
+ const cacheHits = [];
49
+ const blockers = [];
50
+ const seen = new Set();
51
+ for (const extractor of sortExtractors(input.extractors)) {
52
+ if (seen.has(extractor.id)) {
53
+ blockers.push(`extractor_duplicate_id:${extractor.id}`);
54
+ continue;
55
+ }
56
+ seen.add(extractor.id);
57
+ const key = fragmentCacheKey({
58
+ extractorId: extractor.id,
59
+ extractorRevision: extractor.revision,
60
+ cacheKey: input.cacheKey,
61
+ changedPaths: input.changedPaths
62
+ });
63
+ if (input.useFragmentCache) {
64
+ const cached = await readCachedFragment(input.root, key, extractor.id);
65
+ if (cached) {
66
+ fragments.push(cached);
67
+ cacheHits.push(extractor.id);
68
+ continue;
69
+ }
70
+ }
71
+ const outcome = await withTimeout(extractor.extract({
72
+ root: input.root,
73
+ changedPaths: input.changedPaths,
74
+ limits: input.limits,
75
+ observedAt: input.observedAt
76
+ }), input.limits.timeoutMs);
77
+ if (outcome === 'timeout') {
78
+ blockers.push(`extractor_timeout:${extractor.id}`);
79
+ continue;
80
+ }
81
+ if (!outcome.ok) {
82
+ blockers.push(`extractor_failed:${extractor.id}`);
83
+ continue;
84
+ }
85
+ if (!isFragment(outcome.value, extractor.id)) {
86
+ blockers.push(`extractor_invalid_fragment:${extractor.id}`);
87
+ continue;
88
+ }
89
+ fragments.push(outcome.value);
90
+ if (outcome.value.skipped.length > 0) {
91
+ issues.push(lintWarning('extractor_skipped_input', `${extractor.id} skipped ${outcome.value.skipped.length} input(s)`, { extractor: extractor.id }));
92
+ }
93
+ if (input.useFragmentCache) {
94
+ try {
95
+ await writeCachedFragment(input.root, key, outcome.value);
96
+ }
97
+ catch {
98
+ }
99
+ }
100
+ }
101
+ if (input.useFragmentCache)
102
+ await pruneFragmentCache(input.root);
103
+ return { fragments, issues, cacheHits, blockers };
104
+ }
@@ -0,0 +1,108 @@
1
+ import fsp from 'node:fs/promises';
2
+ import { sha256 } from '../../../fsx.js';
3
+ import { resolveInsideWorkspace } from '../paths.js';
4
+ import { compareContextGraphIds } from '../ids.js';
5
+ export const CONTEXT_GRAPH_MISSING_SOURCE_HASH = 'missing';
6
+ const DERIVATION_EDGE_TYPES = new Set([
7
+ 'cites',
8
+ 'derived_from',
9
+ 'supports',
10
+ 'verified_by'
11
+ ]);
12
+ const DERIVED_KINDS = new Set([
13
+ 'wiki_claim',
14
+ 'proof',
15
+ 'source',
16
+ 'risk_domain'
17
+ ]);
18
+ export async function readSourceHashes(root, paths) {
19
+ const out = {};
20
+ for (const relative of [...new Set(paths)].sort()) {
21
+ let absolute = null;
22
+ try {
23
+ absolute = resolveInsideWorkspace(root, relative);
24
+ }
25
+ catch {
26
+ out[relative] = CONTEXT_GRAPH_MISSING_SOURCE_HASH;
27
+ continue;
28
+ }
29
+ if (!absolute) {
30
+ out[relative] = CONTEXT_GRAPH_MISSING_SOURCE_HASH;
31
+ continue;
32
+ }
33
+ try {
34
+ out[relative] = sha256(await fsp.readFile(absolute));
35
+ }
36
+ catch {
37
+ out[relative] = CONTEXT_GRAPH_MISSING_SOURCE_HASH;
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+ function withFreshness(node, freshness) {
43
+ return node.freshness === freshness ? node : { ...node, freshness };
44
+ }
45
+ export async function applyContextGraphFreshness(input) {
46
+ const wanted = [];
47
+ for (const node of input.nodes) {
48
+ if (node.path !== undefined && node.path !== '')
49
+ wanted.push(node.path);
50
+ }
51
+ const provided = input.sourceHashes ?? {};
52
+ const missingFromCache = wanted.filter((relative) => provided[relative] === undefined);
53
+ const sourceHashes = {
54
+ ...provided,
55
+ ...(await readSourceHashes(input.root, missingFromCache))
56
+ };
57
+ const byId = new Map();
58
+ const stale = new Set();
59
+ const missingPaths = new Set();
60
+ for (const node of input.nodes) {
61
+ let resolved = node;
62
+ if (node.path !== undefined && node.path !== '') {
63
+ const current = sourceHashes[node.path];
64
+ if (current === undefined || current === CONTEXT_GRAPH_MISSING_SOURCE_HASH) {
65
+ missingPaths.add(node.path);
66
+ resolved = withFreshness(node, 'stale');
67
+ }
68
+ else if (node.contentHash !== undefined && node.contentHash !== '') {
69
+ resolved = withFreshness(node, current === node.contentHash ? 'fresh' : 'stale');
70
+ }
71
+ }
72
+ if (resolved.freshness === 'stale')
73
+ stale.add(resolved.id);
74
+ byId.set(resolved.id, resolved);
75
+ }
76
+ const incoming = new Map();
77
+ for (const edge of input.edges) {
78
+ if (!DERIVATION_EDGE_TYPES.has(edge.type))
79
+ continue;
80
+ const bucket = incoming.get(edge.to);
81
+ if (bucket)
82
+ bucket.push(edge);
83
+ else
84
+ incoming.set(edge.to, [edge]);
85
+ }
86
+ const queue = [...stale];
87
+ while (queue.length) {
88
+ const current = queue.pop();
89
+ if (current === undefined)
90
+ break;
91
+ for (const edge of incoming.get(current) ?? []) {
92
+ if (stale.has(edge.from))
93
+ continue;
94
+ const source = byId.get(edge.from);
95
+ if (!source || !DERIVED_KINDS.has(source.kind))
96
+ continue;
97
+ stale.add(edge.from);
98
+ byId.set(edge.from, withFreshness(source, 'stale'));
99
+ queue.push(edge.from);
100
+ }
101
+ }
102
+ return {
103
+ nodes: [...byId.values()],
104
+ sourceHashes,
105
+ staleNodeIds: [...stale].sort(compareContextGraphIds),
106
+ missingPaths: [...missingPaths].sort()
107
+ };
108
+ }