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 crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isSymlinkEscape } from '../../paths.js';
5
+ const EXCLUDED_DIR_NAMES = new Set(['node_modules', 'dist', 'build', 'coverage', 'archive']);
6
+ const SUPPORTED_EXTENSIONS = new Map([
7
+ ['.ts', 'typescript'],
8
+ ['.tsx', 'typescript'],
9
+ ['.mts', 'typescript'],
10
+ ['.cts', 'typescript'],
11
+ ['.js', 'javascript'],
12
+ ['.jsx', 'javascript'],
13
+ ['.mjs', 'javascript'],
14
+ ['.cjs', 'javascript']
15
+ ]);
16
+ const FOREIGN_CODE_EXTENSIONS = new Set([
17
+ '.py', '.rb', '.go', '.rs', '.java', '.kt', '.kts', '.swift', '.php', '.c', '.h', '.cc', '.cpp',
18
+ '.hpp', '.cs', '.scala', '.sh', '.bash', '.zsh', '.vue', '.svelte', '.dart', '.m', '.mm', '.pl',
19
+ '.lua', '.ex', '.exs', '.clj', '.hs', '.ml', '.jl', '.sql', '.r'
20
+ ]);
21
+ const MAX_EXCLUDED_SKIPS = 64;
22
+ const MAX_CAP_SKIPS = 32;
23
+ const BINARY_PROBE_BYTES = 8192;
24
+ const MIN_BINARY_CONTROL_BYTES = 4;
25
+ const MIN_BINARY_CONTROL_PERCENT = 1;
26
+ export function isSupportedCodePath(relativePath) {
27
+ return SUPPORTED_EXTENSIONS.has(path.posix.extname(relativePath).toLowerCase());
28
+ }
29
+ export function isTestPath(relativePath) {
30
+ const parts = relativePath.split('/');
31
+ if (parts.includes('__tests__') || parts.includes('__test__'))
32
+ return true;
33
+ const head = parts[0];
34
+ if (parts.length > 1 && (head === 'test' || head === 'tests'))
35
+ return true;
36
+ const base = parts[parts.length - 1] ?? '';
37
+ return /\.(?:test|spec)\.[cm]?[jt]sx?$/i.test(base);
38
+ }
39
+ function isExcludedDirName(name) {
40
+ return name.startsWith('.') || EXCLUDED_DIR_NAMES.has(name);
41
+ }
42
+ function hashBytes(bytes) {
43
+ return crypto.createHash('sha256').update(bytes).digest('hex');
44
+ }
45
+ function countLines(text) {
46
+ if (!text)
47
+ return 0;
48
+ let lines = 1;
49
+ for (let index = 0; index < text.length; index += 1)
50
+ if (text.charCodeAt(index) === 10)
51
+ lines += 1;
52
+ return text.endsWith('\n') ? lines - 1 : lines;
53
+ }
54
+ function decodeUtf8(bytes) {
55
+ try {
56
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ function controlByteDensity(bytes) {
63
+ const probe = Math.min(bytes.length, BINARY_PROBE_BYTES);
64
+ if (!probe)
65
+ return { count: 0, percent: 0 };
66
+ let count = 0;
67
+ for (let index = 0; index < probe; index += 1) {
68
+ const byte = bytes[index];
69
+ if (byte === undefined)
70
+ continue;
71
+ if (byte === 0 || byte < 9 || (byte > 13 && byte < 32) || byte === 127)
72
+ count += 1;
73
+ }
74
+ return { count, percent: (count / probe) * 100 };
75
+ }
76
+ function looksBinary(bytes) {
77
+ const density = controlByteDensity(bytes);
78
+ return density.count >= MIN_BINARY_CONTROL_BYTES && density.percent >= MIN_BINARY_CONTROL_PERCENT;
79
+ }
80
+ function addSkip(state, skip) {
81
+ if (skip.reason === 'excluded') {
82
+ if (state.excludedSkips >= MAX_EXCLUDED_SKIPS)
83
+ return;
84
+ state.excludedSkips += 1;
85
+ }
86
+ if (skip.reason === 'cap_reached') {
87
+ if (state.capSkips >= MAX_CAP_SKIPS) {
88
+ state.stopped = true;
89
+ return;
90
+ }
91
+ state.capSkips += 1;
92
+ }
93
+ state.skipped.push(skip);
94
+ }
95
+ function readEntries(absolute) {
96
+ try {
97
+ return fs.readdirSync(absolute, { withFileTypes: true }).sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
103
+ function ingestFile(state, rel, absolute) {
104
+ const extension = path.posix.extname(rel).toLowerCase();
105
+ const language = SUPPORTED_EXTENSIONS.get(extension);
106
+ if (!language) {
107
+ if (FOREIGN_CODE_EXTENSIONS.has(extension)) {
108
+ addSkip(state, { path: rel, reason: 'unsupported_language', detail: `no extractor for ${extension} sources` });
109
+ }
110
+ return;
111
+ }
112
+ if (isSymlinkEscape(state.root, rel)) {
113
+ addSkip(state, { path: rel, reason: 'symlink_escape', detail: 'symlink resolves outside the workspace' });
114
+ return;
115
+ }
116
+ if (state.files.length >= state.limits.maxFiles) {
117
+ addSkip(state, { path: rel, reason: 'cap_reached', detail: `maxFiles=${state.limits.maxFiles} reached` });
118
+ return;
119
+ }
120
+ let stat;
121
+ try {
122
+ stat = fs.statSync(absolute);
123
+ }
124
+ catch {
125
+ addSkip(state, { path: rel, reason: 'unreadable', detail: 'stat failed' });
126
+ return;
127
+ }
128
+ if (stat.size > state.limits.maxFileBytes) {
129
+ addSkip(state, { path: rel, reason: 'oversized', detail: `${stat.size} bytes exceeds maxFileBytes=${state.limits.maxFileBytes}` });
130
+ return;
131
+ }
132
+ let bytes;
133
+ try {
134
+ bytes = fs.readFileSync(absolute);
135
+ }
136
+ catch {
137
+ addSkip(state, { path: rel, reason: 'unreadable', detail: 'read failed' });
138
+ return;
139
+ }
140
+ const text = decodeUtf8(bytes);
141
+ if (text === null) {
142
+ addSkip(state, { path: rel, reason: 'binary', detail: 'not valid UTF-8 despite a source extension' });
143
+ return;
144
+ }
145
+ if (looksBinary(bytes)) {
146
+ addSkip(state, { path: rel, reason: 'binary', detail: 'dense control bytes in a source-extension file' });
147
+ return;
148
+ }
149
+ state.files.push({
150
+ rel,
151
+ abs: absolute,
152
+ hash: hashBytes(bytes),
153
+ text,
154
+ bytes: bytes.length,
155
+ lines: countLines(text),
156
+ isTest: isTestPath(rel),
157
+ extension,
158
+ language
159
+ });
160
+ }
161
+ function walkDirectory(state, relDir) {
162
+ if (state.stopped)
163
+ return;
164
+ const absolute = relDir ? path.join(state.root, relDir) : state.root;
165
+ let real;
166
+ try {
167
+ real = fs.realpathSync(absolute);
168
+ }
169
+ catch {
170
+ addSkip(state, { path: relDir || '.', reason: 'unreadable', detail: 'directory realpath failed' });
171
+ return;
172
+ }
173
+ if (state.visitedDirs.has(real))
174
+ return;
175
+ state.visitedDirs.add(real);
176
+ const entries = readEntries(absolute);
177
+ if (!entries) {
178
+ addSkip(state, { path: relDir || '.', reason: 'unreadable', detail: 'directory listing failed' });
179
+ return;
180
+ }
181
+ for (const entry of entries) {
182
+ if (state.stopped)
183
+ return;
184
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
185
+ if (entry.isDirectory()) {
186
+ if (isExcludedDirName(entry.name)) {
187
+ addSkip(state, { path: rel, reason: 'excluded', detail: 'directory is not scanned by the code extractor' });
188
+ continue;
189
+ }
190
+ walkDirectory(state, rel);
191
+ continue;
192
+ }
193
+ if (entry.isSymbolicLink()) {
194
+ let target;
195
+ try {
196
+ target = fs.statSync(path.join(state.root, rel));
197
+ }
198
+ catch {
199
+ addSkip(state, { path: rel, reason: 'unreadable', detail: 'broken symlink' });
200
+ continue;
201
+ }
202
+ if (target.isDirectory()) {
203
+ if (isSymlinkEscape(state.root, rel)) {
204
+ addSkip(state, { path: rel, reason: 'symlink_escape', detail: 'symlinked directory resolves outside the workspace' });
205
+ continue;
206
+ }
207
+ if (isExcludedDirName(entry.name))
208
+ continue;
209
+ walkDirectory(state, rel);
210
+ continue;
211
+ }
212
+ }
213
+ if (!entry.isFile() && !entry.isSymbolicLink())
214
+ continue;
215
+ ingestFile(state, rel, path.join(state.root, rel));
216
+ }
217
+ }
218
+ export function walkCodeInventory(root, limits) {
219
+ const absoluteRoot = path.resolve(root);
220
+ const state = {
221
+ root: absoluteRoot,
222
+ limits,
223
+ files: [],
224
+ skipped: [],
225
+ excludedSkips: 0,
226
+ capSkips: 0,
227
+ stopped: false,
228
+ visitedDirs: new Set()
229
+ };
230
+ if (fs.existsSync(absoluteRoot))
231
+ walkDirectory(state, '');
232
+ state.files.sort((left, right) => (left.rel < right.rel ? -1 : left.rel > right.rel ? 1 : 0));
233
+ const byRel = new Map();
234
+ for (const file of state.files)
235
+ byRel.set(file.rel, file);
236
+ return { files: state.files, byRel, skipped: state.skipped };
237
+ }
@@ -0,0 +1,252 @@
1
+ import ts from 'typescript';
2
+ import { tryNormalizeGraphPath } from '../../paths.js';
3
+ import { isBareSpecifier, modeForSpecifier, resolveSpecifier } from './ts-config.js';
4
+ import { MAX_REEXPORT_DEPTH } from './types.js';
5
+ function lineOf(sourceFile, node) {
6
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
7
+ }
8
+ function resolveTargetRel(builder, literal, line) {
9
+ const specifier = literal.text;
10
+ if (!specifier)
11
+ return null;
12
+ const mode = modeForSpecifier(builder.context, builder.sourceFile, literal);
13
+ const resolved = resolveSpecifier(builder.context, specifier, builder.record.abs, mode);
14
+ if (!resolved) {
15
+ if (!isBareSpecifier(specifier))
16
+ builder.facts.unresolved.push({ specifier, line });
17
+ return null;
18
+ }
19
+ const rel = tryNormalizeGraphPath(builder.context.root, resolved.fileName);
20
+ if (rel === null) {
21
+ if (!resolved.external && !isBareSpecifier(specifier)) {
22
+ builder.facts.skips.push({
23
+ path: builder.facts.rel,
24
+ reason: 'excluded',
25
+ detail: `import '${specifier}' resolves outside the workspace`
26
+ });
27
+ }
28
+ return null;
29
+ }
30
+ if (!builder.isKnownFile(rel))
31
+ return null;
32
+ return rel;
33
+ }
34
+ function noteImport(builder, targetRel, line) {
35
+ if (builder.importLines.has(targetRel))
36
+ return;
37
+ builder.importLines.set(targetRel, line);
38
+ builder.facts.imports.push({ targetRel, line });
39
+ }
40
+ function collectImportDeclaration(builder, statement) {
41
+ const specifier = statement.moduleSpecifier;
42
+ if (!ts.isStringLiteralLike(specifier))
43
+ return;
44
+ const line = lineOf(builder.sourceFile, statement);
45
+ const targetRel = resolveTargetRel(builder, specifier, line);
46
+ if (targetRel)
47
+ noteImport(builder, targetRel, line);
48
+ const clause = statement.importClause;
49
+ if (!clause)
50
+ return;
51
+ const push = (local, exportName) => {
52
+ builder.facts.bindings.push({ local, exportName, specifier: specifier.text, targetRel, line });
53
+ };
54
+ if (clause.name)
55
+ push(clause.name.text, 'default');
56
+ const bindings = clause.namedBindings;
57
+ if (!bindings)
58
+ return;
59
+ if (ts.isNamespaceImport(bindings)) {
60
+ push(bindings.name.text, '*');
61
+ return;
62
+ }
63
+ for (const element of bindings.elements) {
64
+ push(element.name.text, (element.propertyName ?? element.name).text);
65
+ }
66
+ }
67
+ function collectImportEquals(builder, statement) {
68
+ const reference = statement.moduleReference;
69
+ if (!ts.isExternalModuleReference(reference))
70
+ return;
71
+ const specifier = reference.expression;
72
+ if (!ts.isStringLiteralLike(specifier))
73
+ return;
74
+ const line = lineOf(builder.sourceFile, statement);
75
+ const targetRel = resolveTargetRel(builder, specifier, line);
76
+ if (targetRel)
77
+ noteImport(builder, targetRel, line);
78
+ builder.facts.bindings.push({
79
+ local: statement.name.text,
80
+ exportName: 'export=',
81
+ specifier: specifier.text,
82
+ targetRel,
83
+ line
84
+ });
85
+ }
86
+ function collectExportDeclaration(builder, statement) {
87
+ const specifier = statement.moduleSpecifier;
88
+ if (!specifier || !ts.isStringLiteralLike(specifier))
89
+ return;
90
+ const line = lineOf(builder.sourceFile, statement);
91
+ const targetRel = resolveTargetRel(builder, specifier, line);
92
+ if (!targetRel)
93
+ return;
94
+ const clause = statement.exportClause;
95
+ const record = { targetRel, line, star: !clause, names: [] };
96
+ if (clause && ts.isNamedExports(clause)) {
97
+ for (const element of clause.elements) {
98
+ record.names.push({ exportName: element.name.text, sourceName: (element.propertyName ?? element.name).text });
99
+ }
100
+ }
101
+ builder.facts.reexports.push(record);
102
+ }
103
+ function collectCallSpecifiers(builder) {
104
+ const visit = (node) => {
105
+ if (ts.isCallExpression(node)) {
106
+ const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
107
+ const isRequire = ts.isIdentifier(node.expression) && node.expression.text === 'require';
108
+ if (isDynamicImport || isRequire) {
109
+ const argument = node.arguments[0];
110
+ if (argument && ts.isStringLiteralLike(argument)) {
111
+ const line = lineOf(builder.sourceFile, node);
112
+ const targetRel = resolveTargetRel(builder, argument, line);
113
+ if (targetRel)
114
+ noteImport(builder, targetRel, line);
115
+ }
116
+ }
117
+ }
118
+ ts.forEachChild(node, visit);
119
+ };
120
+ ts.forEachChild(builder.sourceFile, visit);
121
+ }
122
+ export function collectModuleFacts(context, parsed, isKnownFile) {
123
+ const facts = {
124
+ rel: parsed.record.rel,
125
+ imports: [],
126
+ reexports: [],
127
+ bindings: [],
128
+ skips: [],
129
+ unresolved: []
130
+ };
131
+ const builder = {
132
+ facts,
133
+ record: parsed.record,
134
+ sourceFile: parsed.sourceFile,
135
+ context,
136
+ isKnownFile,
137
+ importLines: new Map()
138
+ };
139
+ for (const statement of parsed.sourceFile.statements) {
140
+ if (ts.isImportDeclaration(statement))
141
+ collectImportDeclaration(builder, statement);
142
+ else if (ts.isImportEqualsDeclaration(statement))
143
+ collectImportEquals(builder, statement);
144
+ else if (ts.isExportDeclaration(statement))
145
+ collectExportDeclaration(builder, statement);
146
+ }
147
+ collectCallSpecifiers(builder);
148
+ return facts;
149
+ }
150
+ export function resolveExportedSymbol(tables, rel, exportName, depth = 0, seen = new Set()) {
151
+ if (depth > MAX_REEXPORT_DEPTH)
152
+ return null;
153
+ const key = `${rel}\u0000${exportName}`;
154
+ if (seen.has(key))
155
+ return null;
156
+ seen.add(key);
157
+ const direct = tables.exportsByRel.get(rel)?.get(exportName);
158
+ if (direct)
159
+ return { rel, symbol: direct, viaReexport: depth > 0 };
160
+ const facts = tables.factsByRel.get(rel);
161
+ if (!facts)
162
+ return null;
163
+ for (const record of facts.reexports) {
164
+ for (const name of record.names) {
165
+ if (name.exportName !== exportName)
166
+ continue;
167
+ const nested = resolveExportedSymbol(tables, record.targetRel, name.sourceName, depth + 1, seen);
168
+ if (nested)
169
+ return { rel: nested.rel, symbol: nested.symbol, viaReexport: true };
170
+ }
171
+ }
172
+ for (const record of facts.reexports) {
173
+ if (!record.star)
174
+ continue;
175
+ const nested = resolveExportedSymbol(tables, record.targetRel, exportName, depth + 1, seen);
176
+ if (nested)
177
+ return { rel: nested.rel, symbol: nested.symbol, viaReexport: true };
178
+ }
179
+ return null;
180
+ }
181
+ export function starReexportTargets(tables, rel) {
182
+ const found = new Set();
183
+ const walk = (current, depth) => {
184
+ if (depth > MAX_REEXPORT_DEPTH)
185
+ return;
186
+ for (const record of tables.factsByRel.get(current)?.reexports ?? []) {
187
+ if (!record.star)
188
+ continue;
189
+ if (record.targetRel === rel || found.has(record.targetRel))
190
+ continue;
191
+ found.add(record.targetRel);
192
+ walk(record.targetRel, depth + 1);
193
+ }
194
+ };
195
+ walk(rel, 0);
196
+ return [...found].sort();
197
+ }
198
+ export function buildImportAndReexportEdges(sink, tables, byRel, fileNodeIdByRel) {
199
+ for (const [rel, facts] of tables.factsByRel) {
200
+ const record = byRel.get(rel);
201
+ const from = fileNodeIdByRel.get(rel);
202
+ if (!record || !from)
203
+ continue;
204
+ for (const entry of facts.imports) {
205
+ const to = fileNodeIdByRel.get(entry.targetRel);
206
+ if (!to)
207
+ continue;
208
+ sink.addEdge({ from, to, type: 'imports', confidence: 'exact', path: rel, hash: record.hash, line: entry.line });
209
+ }
210
+ for (const reexport of facts.reexports) {
211
+ const to = fileNodeIdByRel.get(reexport.targetRel);
212
+ if (to) {
213
+ sink.addEdge({ from, to, type: 'reexports', confidence: 'exact', path: rel, hash: record.hash, line: reexport.line });
214
+ }
215
+ if (reexport.star) {
216
+ for (const transitive of starReexportTargets(tables, reexport.targetRel)) {
217
+ const target = fileNodeIdByRel.get(transitive);
218
+ if (!target || target === from)
219
+ continue;
220
+ sink.addEdge({ from, to: target, type: 'reexports', confidence: 'exact', path: rel, hash: record.hash, line: reexport.line });
221
+ }
222
+ continue;
223
+ }
224
+ for (const name of reexport.names) {
225
+ const resolution = resolveExportedSymbol(tables, reexport.targetRel, name.sourceName);
226
+ if (!resolution)
227
+ continue;
228
+ sink.addEdge({
229
+ from,
230
+ to: resolution.symbol.nodeId,
231
+ type: 'reexports',
232
+ confidence: 'exact',
233
+ path: rel,
234
+ hash: record.hash,
235
+ line: reexport.line
236
+ });
237
+ const definingFile = fileNodeIdByRel.get(resolution.rel);
238
+ if (definingFile && definingFile !== from) {
239
+ sink.addEdge({
240
+ from,
241
+ to: definingFile,
242
+ type: 'reexports',
243
+ confidence: 'exact',
244
+ path: rel,
245
+ hash: record.hash,
246
+ line: reexport.line
247
+ });
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
@@ -0,0 +1,79 @@
1
+ import { contextGraphNodeId } from '../../ids.js';
2
+ const SOURCE_ROOTS = new Set(['src', 'source', 'lib']);
3
+ const SOURCE_ROOT_DEPTH = 3;
4
+ const PLAIN_DEPTH = 2;
5
+ export const ROOT_MODULE_DIR = '.';
6
+ export function moduleDirForPath(relativePath) {
7
+ const parts = relativePath.split('/');
8
+ const dirParts = parts.slice(0, -1);
9
+ if (!dirParts.length)
10
+ return ROOT_MODULE_DIR;
11
+ const head = dirParts[0] ?? '';
12
+ const depth = SOURCE_ROOTS.has(head) ? SOURCE_ROOT_DEPTH : PLAIN_DEPTH;
13
+ return dirParts.slice(0, Math.min(depth, dirParts.length)).join('/');
14
+ }
15
+ function moduleLabel(dir) {
16
+ if (dir === ROOT_MODULE_DIR)
17
+ return 'root';
18
+ const parts = dir.split('/');
19
+ const head = parts[0] ?? '';
20
+ const trimmed = SOURCE_ROOTS.has(head) && parts.length > 1 ? parts.slice(1) : parts;
21
+ return trimmed.join('-') || dir;
22
+ }
23
+ export function inferModuleBoundaries(relativePaths) {
24
+ const byDir = new Map();
25
+ for (const rel of relativePaths) {
26
+ const dir = moduleDirForPath(rel);
27
+ const bucket = byDir.get(dir);
28
+ if (bucket)
29
+ bucket.push(rel);
30
+ else
31
+ byDir.set(dir, [rel]);
32
+ }
33
+ const boundaries = [];
34
+ for (const [dir, files] of byDir) {
35
+ boundaries.push({
36
+ moduleId: dir,
37
+ dir,
38
+ nodeId: contextGraphNodeId({ kind: 'module', moduleId: dir }),
39
+ files: files.sort()
40
+ });
41
+ }
42
+ return boundaries.sort((left, right) => (left.dir < right.dir ? -1 : left.dir > right.dir ? 1 : 0));
43
+ }
44
+ function moduleTokenCost(fileCount) {
45
+ return 8 + fileCount * 2;
46
+ }
47
+ export function buildModuleNodes(sink, boundaries) {
48
+ for (const boundary of boundaries) {
49
+ const capPath = boundary.files[0] ?? boundary.dir;
50
+ sink.addNode({
51
+ id: boundary.nodeId,
52
+ kind: 'module',
53
+ label: moduleLabel(boundary.dir),
54
+ ...(boundary.dir === ROOT_MODULE_DIR ? {} : { path: boundary.dir }),
55
+ trust: 0.8,
56
+ risk: 'low',
57
+ tokenCost: moduleTokenCost(boundary.files.length),
58
+ metadata: { dir: boundary.dir, fileCount: boundary.files.length }
59
+ }, capPath);
60
+ }
61
+ }
62
+ export function buildModuleContainsEdges(sink, boundaries, fileNodeIdByRel, byRel) {
63
+ for (const boundary of boundaries) {
64
+ for (const rel of boundary.files) {
65
+ const fileNodeId = fileNodeIdByRel.get(rel);
66
+ const record = byRel.get(rel);
67
+ if (!fileNodeId || !record)
68
+ continue;
69
+ sink.addEdge({
70
+ from: boundary.nodeId,
71
+ to: fileNodeId,
72
+ type: 'contains',
73
+ confidence: 'exact',
74
+ path: rel,
75
+ hash: record.hash
76
+ });
77
+ }
78
+ }
79
+ }