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,266 @@
1
+ import { lintWarning } from '../../contracts.js';
2
+ import { contextGraphNodeId, shortDigest } from '../../ids.js';
3
+ import { tryNormalizeGraphPath } from '../../paths.js';
4
+ import { classifyTriWikiProofCardSchema, isReusableTriWikiProofCard } from '../../../triwiki-proof-card.js';
5
+ import { boundedList, boundedText, safeText } from './redaction.js';
6
+ import { EvidenceFragmentBuilder, PROOF_BANK_REL, RiskDomainRegistry, asRecord, asString, asStringList, readWorkspaceFile, statWorkspaceEntry, tokenEstimate } from './shared.js';
7
+ import { discoverProofRecords } from './proof-index.js';
8
+ const MAX_DERIVED_INPUTS = 12;
9
+ const PLACEHOLDER_HASHES = new Set(['', 'unknown', 'legacy-missing', 'none', 'null']);
10
+ const HASH_PINNED_INPUTS = [
11
+ { field: 'package_lock_hash', rel: 'package-lock.json' },
12
+ { field: 'release_gates_hash', rel: 'release-gates.v2.json' }
13
+ ];
14
+ export function extractProofEvidence(builder, ctx, risks) {
15
+ const discovery = discoverProofRecords(ctx);
16
+ for (const skip of discovery.skipped)
17
+ builder.addSkip(skip);
18
+ for (const [rel, hash] of Object.entries(discovery.inputHashes))
19
+ builder.addInputHash(rel, hash);
20
+ if (discovery.mode === 'absent') {
21
+ builder.addIssue(lintWarning('extractor_skipped_input', 'TriWiki proof bank is absent; no proof evidence was extracted', {
22
+ path: PROOF_BANK_REL,
23
+ extractor: builder.fragment.extractor
24
+ }));
25
+ return { mode: discovery.mode, proofCount: 0, healthyCount: 0 };
26
+ }
27
+ if (discovery.mode === 'scan') {
28
+ builder.addIssue(lintWarning('extractor_skipped_input', 'proof index manifest is absent; fell back to a bounded proof-bank directory read', {
29
+ path: PROOF_BANK_REL,
30
+ extractor: builder.fragment.extractor
31
+ }));
32
+ }
33
+ let healthyCount = 0;
34
+ for (const record of discovery.records) {
35
+ const health = proofHealth(record, ctx, discovery.mode);
36
+ if (health.healthy)
37
+ healthyCount += 1;
38
+ const nodeId = addProofNode(builder, record, health, discovery.mode);
39
+ if (!nodeId)
40
+ continue;
41
+ linkSubject(builder, record, health, nodeId, discovery.mode);
42
+ linkDerivedInputs(builder, ctx, record, nodeId);
43
+ const domainId = risks.note(`proof-subject/${record.subjectType}`, record.subjectType === 'gate' || record.subjectType === 'gate-pack' ? 'high' : 'medium');
44
+ if (domainId) {
45
+ builder.addEdge({
46
+ from: nodeId,
47
+ to: domainId,
48
+ type: 'affected_by',
49
+ confidence: 'manifest',
50
+ provenancePath: record.rel,
51
+ provenanceHash: record.hash
52
+ });
53
+ }
54
+ }
55
+ return { mode: discovery.mode, proofCount: discovery.records.length, healthyCount };
56
+ }
57
+ function proofHealth(record, ctx, mode) {
58
+ const invalidated = record.invalidationReasons.length > 0 || record.reusable === false;
59
+ if (record.corrupt) {
60
+ return { healthy: false, expired: null, invalidated: true, schemaClass: 'invalid', result: 'unknown', reason: 'corrupt_proof_card' };
61
+ }
62
+ const expired = expiryState(record.expiresAt, ctx);
63
+ if (mode === 'index') {
64
+ const healthy = record.reusable === true && !invalidated && expired === false && record.cardPresent;
65
+ return {
66
+ healthy,
67
+ expired,
68
+ invalidated,
69
+ schemaClass: 'unread',
70
+ result: 'unknown',
71
+ reason: healthy
72
+ ? 'index_reusable'
73
+ : !record.cardPresent
74
+ ? 'proof_card_missing'
75
+ : invalidated
76
+ ? 'invalidated'
77
+ : expired === true
78
+ ? 'expired'
79
+ : 'not_reusable'
80
+ };
81
+ }
82
+ const card = record.card;
83
+ if (!card) {
84
+ return { healthy: false, expired, invalidated: true, schemaClass: 'invalid', result: 'unknown', reason: 'unreadable_proof_card' };
85
+ }
86
+ const schemaClass = classifyTriWikiProofCardSchema(card);
87
+ const result = asString(card.result) ?? 'unknown';
88
+ if (schemaClass !== 'current') {
89
+ return { healthy: false, expired, invalidated, schemaClass, result, reason: schemaClass };
90
+ }
91
+ if (ctx.now === null) {
92
+ return { healthy: false, expired: null, invalidated, schemaClass, result, reason: 'observed_at_unparseable' };
93
+ }
94
+ const reusable = isReusableTriWikiProofCard(card, ctx.now);
95
+ return {
96
+ healthy: reusable,
97
+ expired,
98
+ invalidated,
99
+ schemaClass,
100
+ result,
101
+ reason: reusable ? 'reusable' : invalidated ? 'invalidated' : expired === true ? 'expired' : `not_reusable:${result}`
102
+ };
103
+ }
104
+ function expiryState(expiresAt, ctx) {
105
+ if (!expiresAt)
106
+ return false;
107
+ const parsed = Date.parse(expiresAt);
108
+ if (!Number.isFinite(parsed))
109
+ return null;
110
+ if (ctx.now === null)
111
+ return null;
112
+ return parsed <= ctx.now.getTime();
113
+ }
114
+ function addProofNode(builder, record, health, mode) {
115
+ const id = contextGraphNodeId({ kind: 'proof', proofId: record.proofId });
116
+ const freshness = health.healthy
117
+ ? 'fresh'
118
+ : health.invalidated || health.expired === true || record.corrupt
119
+ ? 'stale'
120
+ : 'unknown';
121
+ const metadata = {
122
+ proof_id: boundedText(record.proofId, 80),
123
+ subject_type: boundedText(record.subjectType, 40),
124
+ subject_id: boundedText(record.subjectId, 120),
125
+ discovery: mode,
126
+ card_present: record.cardPresent,
127
+ corrupt: record.corrupt,
128
+ reusable: health.healthy,
129
+ declared_reusable: record.reusable === null ? null : record.reusable,
130
+ expired: health.expired,
131
+ schema_class: health.schemaClass,
132
+ result: boundedText(health.result, 40),
133
+ health_reason: boundedText(health.reason, 80),
134
+ invalidation_reason_count: record.invalidationReasons.length,
135
+ ...(record.invalidationReasons.length ? { invalidation_reasons: boundedList(record.invalidationReasons) } : {}),
136
+ ...(record.cacheKey ? { cache_key_digest: shortDigest(record.cacheKey) } : {}),
137
+ ...proofCardProjection(record)
138
+ };
139
+ const added = builder.addNode({
140
+ id,
141
+ kind: 'proof',
142
+ label: safeText(record.proofId, 80) || id,
143
+ path: record.rel,
144
+ contentHash: record.hash,
145
+ trust: health.healthy ? 0.9 : record.corrupt ? 0.05 : 0.15,
146
+ freshness,
147
+ risk: record.subjectType === 'gate' || record.subjectType === 'gate-pack' ? 'high' : 'medium',
148
+ tokenCost: tokenEstimate(record.proofId.length + record.subjectId.length, 96),
149
+ metadata
150
+ }, record.rel);
151
+ return added ? id : null;
152
+ }
153
+ function proofCardProjection(record) {
154
+ const card = record.card;
155
+ if (!card)
156
+ return {};
157
+ const raw = card;
158
+ const evidence = asRecord(raw.evidence);
159
+ const inputHash = asString(raw.input_hash);
160
+ const implementationHash = asString(raw.gate_impl_hash) ?? asString(raw.implementation_hash);
161
+ const toolVersions = asRecord(raw.tool_versions);
162
+ return {
163
+ evidence_key_count: evidence ? Object.keys(evidence).length : 0,
164
+ tool_version_count: toolVersions ? Object.keys(toolVersions).length : 0,
165
+ ...(inputHash && !PLACEHOLDER_HASHES.has(inputHash) ? { input_hash: inputHash.slice(0, 24) } : {}),
166
+ ...(implementationHash && !PLACEHOLDER_HASHES.has(implementationHash)
167
+ ? { implementation_hash: implementationHash.slice(0, 24) }
168
+ : {}),
169
+ ...(asString(raw.fixture_version) ? { fixture_version: boundedText(raw.fixture_version, 40) } : {})
170
+ };
171
+ }
172
+ function subjectNodeId(record) {
173
+ if (!record.subjectId || record.subjectId === 'unknown')
174
+ return null;
175
+ switch (record.subjectType) {
176
+ case 'gate':
177
+ case 'gate-pack':
178
+ return { id: contextGraphNodeId({ kind: 'gate', gateId: record.subjectId }), kind: 'gate' };
179
+ case 'module':
180
+ return { id: contextGraphNodeId({ kind: 'module', moduleId: record.subjectId }), kind: 'module' };
181
+ case 'pipeline':
182
+ return { id: contextGraphNodeId({ kind: 'pipeline', pipelineId: record.subjectId }), kind: 'pipeline' };
183
+ default:
184
+ return null;
185
+ }
186
+ }
187
+ function linkSubject(builder, record, health, proofNodeId, mode) {
188
+ const subject = subjectNodeId(record);
189
+ if (!subject)
190
+ return;
191
+ builder.addNode({
192
+ id: subject.id,
193
+ kind: subject.kind,
194
+ label: safeText(record.subjectId, 120) || subject.id,
195
+ trust: 0.7,
196
+ freshness: 'unknown',
197
+ risk: subject.kind === 'gate' ? 'high' : 'medium',
198
+ tokenCost: tokenEstimate(record.subjectId.length, 64),
199
+ metadata: { evidence_stub: true, subject_type: boundedText(record.subjectType, 40) }
200
+ }, record.rel);
201
+ if (health.healthy) {
202
+ builder.addEdge({
203
+ from: proofNodeId,
204
+ to: subject.id,
205
+ type: 'verified_by',
206
+ confidence: mode === 'index' ? 'manifest' : 'exact',
207
+ provenancePath: record.rel,
208
+ provenanceHash: record.hash
209
+ });
210
+ return;
211
+ }
212
+ builder.addEdge({
213
+ from: proofNodeId,
214
+ to: subject.id,
215
+ type: 'invalidates',
216
+ confidence: health.invalidated ? 'manifest' : 'observed',
217
+ provenancePath: record.rel,
218
+ provenanceHash: record.hash
219
+ });
220
+ }
221
+ function linkDerivedInputs(builder, ctx, record, proofNodeId) {
222
+ const raw = record.card;
223
+ const candidates = [];
224
+ if (raw) {
225
+ for (const pinned of HASH_PINNED_INPUTS) {
226
+ const hash = asString(raw[pinned.field]);
227
+ if (hash && !PLACEHOLDER_HASHES.has(hash))
228
+ candidates.push(pinned.rel);
229
+ }
230
+ candidates.push(...asStringList(raw.input_paths), ...asStringList(raw.source_paths));
231
+ }
232
+ const seen = new Set();
233
+ for (const candidate of candidates) {
234
+ if (seen.size >= MAX_DERIVED_INPUTS)
235
+ break;
236
+ const rel = tryNormalizeGraphPath(ctx.root, candidate);
237
+ if (!rel || seen.has(rel))
238
+ continue;
239
+ const stat = statWorkspaceEntry(ctx.root, rel);
240
+ if (!stat || !stat.isFile())
241
+ continue;
242
+ seen.add(rel);
243
+ const fileId = contextGraphNodeId({ kind: 'file', path: rel });
244
+ const read = readWorkspaceFile(ctx.root, rel, ctx.limits.maxFileBytes);
245
+ builder.addNode({
246
+ id: fileId,
247
+ kind: 'file',
248
+ label: safeText(rel, 120) || rel,
249
+ path: rel,
250
+ ...(read.ok ? { contentHash: read.value.hash } : {}),
251
+ trust: read.ok ? 0.9 : 0.4,
252
+ freshness: read.ok ? 'fresh' : 'unknown',
253
+ risk: 'low',
254
+ tokenCost: tokenEstimate(stat.size),
255
+ metadata: { evidence_stub: true, source_path: rel }
256
+ }, record.rel);
257
+ builder.addEdge({
258
+ from: proofNodeId,
259
+ to: fileId,
260
+ type: 'derived_from',
261
+ confidence: 'manifest',
262
+ provenancePath: record.rel,
263
+ provenanceHash: record.hash
264
+ });
265
+ }
266
+ }
@@ -0,0 +1,177 @@
1
+ import { REDACTION_MARKER, containsPlaintextSecret, redactString } from '../../../../secret-redaction.js';
2
+ import { lintError } from '../../contracts.js';
3
+ import { isWorkspaceRelativePosixPath } from '../../paths.js';
4
+ export const EVIDENCE_REDACTED_PATH = '[redacted-path]';
5
+ export const EVIDENCE_MAX_META_STRING = 160;
6
+ export const EVIDENCE_MAX_META_LIST = 12;
7
+ const UNSAFE_PATH_PATTERNS = [
8
+ /(?:^|[\s"'(=:,])~\//,
9
+ /(?:^|[\s"'(=:,])\/(?:Users|home|root|var|etc|tmp|private|opt|usr|Volumes)\//i,
10
+ /(?:^|[\s"'(=:,])[A-Za-z]:[\\/]/,
11
+ /(?:^|[\s"'(=:,])\\\\[^\\/\s]+\\/,
12
+ /\.\.\//
13
+ ];
14
+ export function looksLikeUnsafePath(value) {
15
+ return UNSAFE_PATH_PATTERNS.some((pattern) => pattern.test(value));
16
+ }
17
+ export function safeText(value, maxLength = EVIDENCE_MAX_META_STRING) {
18
+ if (value === null || value === undefined)
19
+ return '';
20
+ const raw = typeof value === 'string' ? value : String(value);
21
+ const collapsed = raw.replace(/[\r\n\t]+/g, ' ').replace(/\s{2,}/g, ' ').trim();
22
+ if (!collapsed)
23
+ return '';
24
+ const redacted = redactString(collapsed);
25
+ const guarded = looksLikeUnsafePath(redacted) ? EVIDENCE_REDACTED_PATH : redacted;
26
+ return guarded.length > maxLength ? `${guarded.slice(0, Math.max(1, maxLength - 1))}…` : guarded;
27
+ }
28
+ export function boundedText(value, maxLength = EVIDENCE_MAX_META_STRING) {
29
+ if (value === null || value === undefined)
30
+ return '';
31
+ const raw = typeof value === 'string' ? value : String(value);
32
+ const collapsed = raw.replace(/[\r\n\t]+/g, ' ').replace(/\s{2,}/g, ' ').trim();
33
+ if (!collapsed)
34
+ return '';
35
+ return collapsed.length > maxLength ? `${collapsed.slice(0, Math.max(1, maxLength - 1))}…` : collapsed;
36
+ }
37
+ export function boundedList(values, maxItems = EVIDENCE_MAX_META_LIST, maxLength = EVIDENCE_MAX_META_STRING) {
38
+ const out = [];
39
+ const seen = new Set();
40
+ for (const value of values) {
41
+ if (out.length >= maxItems)
42
+ break;
43
+ const text = boundedText(value, maxLength);
44
+ if (!text || seen.has(text))
45
+ continue;
46
+ seen.add(text);
47
+ out.push(text);
48
+ }
49
+ return out.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
50
+ }
51
+ export function safeTextList(values, maxItems = EVIDENCE_MAX_META_LIST, maxLength = EVIDENCE_MAX_META_STRING) {
52
+ const out = [];
53
+ const seen = new Set();
54
+ for (const value of values) {
55
+ if (out.length >= maxItems)
56
+ break;
57
+ const text = safeText(value, maxLength);
58
+ if (!text || seen.has(text))
59
+ continue;
60
+ seen.add(text);
61
+ out.push(text);
62
+ }
63
+ return out.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
64
+ }
65
+ function valueIsClean(value) {
66
+ if (typeof value === 'string')
67
+ return !containsPlaintextSecret(value);
68
+ if (Array.isArray(value))
69
+ return value.every((entry) => !containsPlaintextSecret(entry));
70
+ return true;
71
+ }
72
+ function sanitizeMetadataValue(value) {
73
+ if (typeof value === 'string') {
74
+ const next = safeText(value);
75
+ return { value: next, changed: next !== value };
76
+ }
77
+ if (Array.isArray(value)) {
78
+ const next = safeTextList(value);
79
+ const changed = next.length !== value.length || next.some((entry, index) => entry !== value[index]);
80
+ return { value: next, changed };
81
+ }
82
+ return { value, changed: false };
83
+ }
84
+ function guardNode(node, extractor) {
85
+ if (containsPlaintextSecret(node.id) || containsPlaintextSecret(node.label)) {
86
+ return {
87
+ node: null,
88
+ issue: lintError('secret_like_value', 'evidence node identity looks secret-like and was refused', {
89
+ nodeId: node.id.slice(0, 24),
90
+ extractor
91
+ })
92
+ };
93
+ }
94
+ if (node.path !== undefined && containsPlaintextSecret(node.path)) {
95
+ return {
96
+ node: null,
97
+ issue: lintError('secret_like_value', 'evidence node path looks secret-like and was refused', {
98
+ nodeId: node.id,
99
+ extractor
100
+ })
101
+ };
102
+ }
103
+ if (node.path !== undefined && !isWorkspaceRelativePosixPath(node.path)) {
104
+ return {
105
+ node: null,
106
+ issue: lintError('absolute_or_escaping_path', 'evidence node path is not workspace-relative and was refused', {
107
+ nodeId: node.id,
108
+ extractor
109
+ })
110
+ };
111
+ }
112
+ const metadata = {};
113
+ let redactedFields = 0;
114
+ for (const key of Object.keys(node.metadata).sort()) {
115
+ const raw = node.metadata[key];
116
+ if (raw === undefined)
117
+ continue;
118
+ const sanitized = sanitizeMetadataValue(raw);
119
+ if (!valueIsClean(sanitized.value)) {
120
+ return {
121
+ node: null,
122
+ issue: lintError('secret_like_value', `evidence node metadata "${key}" stayed secret-like after redaction`, {
123
+ nodeId: node.id,
124
+ extractor
125
+ })
126
+ };
127
+ }
128
+ if (sanitized.changed)
129
+ redactedFields += 1;
130
+ if (typeof sanitized.value === 'string' && !sanitized.value)
131
+ continue;
132
+ if (Array.isArray(sanitized.value) && !sanitized.value.length)
133
+ continue;
134
+ metadata[key] = sanitized.value;
135
+ }
136
+ if (redactedFields > 0) {
137
+ metadata.redacted = true;
138
+ metadata.redacted_field_count = redactedFields;
139
+ metadata.redaction_marker = REDACTION_MARKER;
140
+ }
141
+ return { node: { ...node, metadata }, issue: null };
142
+ }
143
+ function edgeIsSafe(edge, nodeIds) {
144
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to))
145
+ return false;
146
+ if (!edge.provenance.path || !isWorkspaceRelativePosixPath(edge.provenance.path))
147
+ return false;
148
+ if (containsPlaintextSecret(edge.provenance.path) || containsPlaintextSecret(edge.provenance.hash))
149
+ return false;
150
+ return true;
151
+ }
152
+ export function sanitizeEvidenceFragment(fragment) {
153
+ const issues = [...fragment.issues];
154
+ const nodes = [];
155
+ const keptIds = new Set();
156
+ for (const node of fragment.nodes) {
157
+ const guarded = guardNode(node, fragment.extractor);
158
+ if (guarded.issue)
159
+ issues.push(guarded.issue);
160
+ if (!guarded.node)
161
+ continue;
162
+ nodes.push(guarded.node);
163
+ keptIds.add(guarded.node.id);
164
+ }
165
+ const edges = fragment.edges.filter((edge) => edgeIsSafe(edge, keptIds));
166
+ const inputHashes = {};
167
+ for (const key of Object.keys(fragment.inputHashes).sort()) {
168
+ const value = fragment.inputHashes[key];
169
+ if (value === undefined)
170
+ continue;
171
+ if (!isWorkspaceRelativePosixPath(key) || containsPlaintextSecret(key))
172
+ continue;
173
+ inputHashes[key] = value;
174
+ }
175
+ const skipped = fragment.skipped.filter((entry) => isWorkspaceRelativePosixPath(entry.path) && !containsPlaintextSecret(entry.path) && !containsPlaintextSecret(entry.detail ?? ''));
176
+ return { ...fragment, nodes, edges, issues, skipped, inputHashes };
177
+ }
@@ -0,0 +1,154 @@
1
+ import { contextGraphNodeId, shortDigest } from '../../ids.js';
2
+ import { safeText } from './redaction.js';
3
+ import { CONTEXT_PACK_REL, EvidenceFragmentBuilder, asArray, asRecord, asString, readWorkspaceFile, statWorkspaceEntry, tokenEstimate } from './shared.js';
4
+ const MAX_DIRECTORY_PROBE = 24;
5
+ export function manifestHashes(pack) {
6
+ const out = new Map();
7
+ const provenance = asRecord(pack.provenance);
8
+ const manifest = provenance ? asRecord(provenance.source_manifest) : null;
9
+ for (const entry of asArray(manifest?.entries)) {
10
+ const row = asRecord(entry);
11
+ const rel = row ? asString(row.path) : null;
12
+ const hash = row ? asString(row.sha256) : null;
13
+ if (rel && hash)
14
+ out.set(rel, hash);
15
+ }
16
+ return out;
17
+ }
18
+ export function hasManifestPrefix(manifest, rel) {
19
+ const prefix = `${rel}/`;
20
+ for (const key of manifest.keys()) {
21
+ if (key.startsWith(prefix))
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ class SourceHasher {
27
+ ctx;
28
+ builder;
29
+ cache = new Map();
30
+ budget;
31
+ constructor(ctx, builder) {
32
+ this.ctx = ctx;
33
+ this.builder = builder;
34
+ this.budget = Math.max(0, ctx.limits.maxFiles);
35
+ }
36
+ hash(rel) {
37
+ const cached = this.cache.get(rel);
38
+ if (cached !== undefined)
39
+ return cached;
40
+ if (this.budget <= 0) {
41
+ this.builder.noteCap(CONTEXT_PACK_REL, 'source_hash_budget_exhausted');
42
+ this.cache.set(rel, null);
43
+ return null;
44
+ }
45
+ this.budget -= 1;
46
+ const read = readWorkspaceFile(this.ctx.root, rel, this.ctx.limits.maxFileBytes);
47
+ const value = read.ok ? read.value.hash : null;
48
+ if (!read.ok && read.skip.reason !== 'unreadable')
49
+ this.builder.addSkip(read.skip);
50
+ this.cache.set(rel, value);
51
+ return value;
52
+ }
53
+ }
54
+ export class EvidenceSourceGraph {
55
+ builder;
56
+ ctx;
57
+ manifest;
58
+ packHash;
59
+ hasher;
60
+ states = new Map();
61
+ constructor(builder, ctx, manifest, packHash) {
62
+ this.builder = builder;
63
+ this.ctx = ctx;
64
+ this.manifest = manifest;
65
+ this.packHash = packHash;
66
+ this.hasher = new SourceHasher(ctx, builder);
67
+ }
68
+ ensure(rel) {
69
+ const existing = this.states.get(rel);
70
+ if (existing)
71
+ return existing;
72
+ const id = contextGraphNodeId({ kind: 'source', sourceHash: shortDigest(`path:${rel}`) });
73
+ const stat = statWorkspaceEntry(this.ctx.root, rel);
74
+ const manifestHash = this.manifest.get(rel) ?? null;
75
+ const isDirectory = Boolean(stat?.isDirectory());
76
+ const diskHash = !stat || isDirectory ? null : this.hasher.hash(rel);
77
+ const freshness = this.freshness({ stat, isDirectory, manifestHash, diskHash, rel });
78
+ const metadata = {
79
+ source_path: rel,
80
+ entry_kind: isDirectory ? 'directory' : stat ? 'file' : 'missing',
81
+ manifest_hash_present: manifestHash !== null,
82
+ ...(manifestHash ? { manifest_hash: manifestHash.slice(0, 16) } : {}),
83
+ ...(diskHash ? { disk_hash: diskHash.slice(0, 16) } : {}),
84
+ ...(manifestHash && diskHash ? { hash_match: manifestHash === diskHash } : {})
85
+ };
86
+ this.builder.addNode({
87
+ id,
88
+ kind: 'source',
89
+ label: safeText(rel, 120) || rel,
90
+ path: rel,
91
+ ...(diskHash ? { contentHash: diskHash } : manifestHash ? { contentHash: manifestHash } : {}),
92
+ trust: freshness === 'fresh' ? 0.85 : freshness === 'stale' ? 0.3 : 0.5,
93
+ freshness,
94
+ risk: 'low',
95
+ tokenCost: tokenEstimate(rel.length, 64),
96
+ metadata
97
+ }, CONTEXT_PACK_REL);
98
+ if (!isDirectory && (diskHash || manifestHash))
99
+ this.linkBackingFile(id, rel, stat, diskHash, manifestHash);
100
+ const state = { id, freshness };
101
+ this.states.set(rel, state);
102
+ return state;
103
+ }
104
+ linkBackingFile(sourceId, rel, stat, diskHash, manifestHash) {
105
+ const fileId = contextGraphNodeId({ kind: 'file', path: rel });
106
+ this.builder.addNode({
107
+ id: fileId,
108
+ kind: 'file',
109
+ label: safeText(rel, 120) || rel,
110
+ path: rel,
111
+ ...(diskHash ? { contentHash: diskHash } : {}),
112
+ trust: diskHash ? 0.9 : 0.4,
113
+ freshness: diskHash ? 'fresh' : 'stale',
114
+ risk: 'low',
115
+ tokenCost: tokenEstimate(stat?.size ?? rel.length),
116
+ metadata: { evidence_stub: true, source_path: rel }
117
+ }, CONTEXT_PACK_REL);
118
+ this.builder.addEdge({
119
+ from: sourceId,
120
+ to: fileId,
121
+ type: 'derived_from',
122
+ confidence: diskHash ? 'exact' : 'manifest',
123
+ provenancePath: rel,
124
+ provenanceHash: diskHash ?? manifestHash ?? this.packHash
125
+ });
126
+ }
127
+ freshness(input) {
128
+ if (!input.stat)
129
+ return input.manifestHash || hasManifestPrefix(this.manifest, input.rel) ? 'stale' : 'unknown';
130
+ if (input.isDirectory)
131
+ return this.directoryFreshness(input.rel);
132
+ if (!input.manifestHash || !input.diskHash)
133
+ return 'unknown';
134
+ return input.diskHash === input.manifestHash ? 'fresh' : 'stale';
135
+ }
136
+ directoryFreshness(rel) {
137
+ const prefix = `${rel}/`;
138
+ const members = [...this.manifest.keys()]
139
+ .filter((key) => key.startsWith(prefix))
140
+ .sort()
141
+ .slice(0, MAX_DIRECTORY_PROBE);
142
+ if (!members.length)
143
+ return 'unknown';
144
+ for (const member of members) {
145
+ const expected = this.manifest.get(member);
146
+ const actual = this.hasher.hash(member);
147
+ if (!expected || !actual)
148
+ return 'unknown';
149
+ if (actual !== expected)
150
+ return 'stale';
151
+ }
152
+ return 'fresh';
153
+ }
154
+ }
@@ -0,0 +1,7 @@
1
+ import { createCodeGraphExtractor } from './code/index.js';
2
+ import { createEvidenceGraphExtractor } from './evidence/index.js';
3
+ import { createTopologyGraphExtractor } from './topology/index.js';
4
+ export function contextGraphExtractors() {
5
+ return [createCodeGraphExtractor(), createTopologyGraphExtractor(), createEvidenceGraphExtractor()];
6
+ }
7
+ export { createCodeGraphExtractor, createEvidenceGraphExtractor, createTopologyGraphExtractor };