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,124 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export const TOPOLOGY_EXCLUDED_DIR_NAMES = new Set([
4
+ '.git',
5
+ '.cache',
6
+ '.claude',
7
+ '.next',
8
+ '.sneakoscope',
9
+ '.turbo',
10
+ '.venv',
11
+ 'build',
12
+ 'coverage',
13
+ 'dist',
14
+ 'node_modules',
15
+ 'target',
16
+ 'tmp',
17
+ 'vendor'
18
+ ]);
19
+ const MAX_WALK_DEPTH = 24;
20
+ export function buildFileInventory(root, maxFiles) {
21
+ const limit = Number.isFinite(maxFiles) && maxFiles > 0 ? Math.trunc(maxFiles) : 0;
22
+ const files = [];
23
+ let truncated = false;
24
+ const walk = (absoluteDir, relativeDir, depth) => {
25
+ if (truncated || depth > MAX_WALK_DEPTH)
26
+ return;
27
+ let entries;
28
+ try {
29
+ entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
35
+ for (const entry of entries) {
36
+ if (truncated)
37
+ return;
38
+ const name = entry.name;
39
+ if (!name || name === '.' || name === '..' || name === '.DS_Store')
40
+ continue;
41
+ if (TOPOLOGY_EXCLUDED_DIR_NAMES.has(name))
42
+ continue;
43
+ if (entry.isSymbolicLink())
44
+ continue;
45
+ const relative = relativeDir ? `${relativeDir}/${name}` : name;
46
+ if (entry.isDirectory()) {
47
+ walk(path.join(absoluteDir, name), relative, depth + 1);
48
+ continue;
49
+ }
50
+ if (!entry.isFile())
51
+ continue;
52
+ if (files.length >= limit) {
53
+ truncated = true;
54
+ return;
55
+ }
56
+ files.push(relative);
57
+ }
58
+ };
59
+ walk(path.resolve(root), '', 0);
60
+ files.sort();
61
+ return { files, set: new Set(files), truncated };
62
+ }
63
+ export function isGlobPattern(value) {
64
+ return value.includes('*') || value.includes('?');
65
+ }
66
+ const regexCache = new Map();
67
+ const REGEX_CACHE_LIMIT = 512;
68
+ const REGEX_SPECIAL = /[.+^${}()|[\]\\]/;
69
+ export function globToRegExp(glob) {
70
+ const cached = regexCache.get(glob);
71
+ if (cached)
72
+ return cached;
73
+ let body = '';
74
+ for (let index = 0; index < glob.length; index += 1) {
75
+ const char = glob[index] ?? '';
76
+ if (char === '*') {
77
+ if (glob[index + 1] === '*') {
78
+ index += 1;
79
+ if (glob[index + 1] === '/') {
80
+ index += 1;
81
+ body += '(?:.*/)?';
82
+ }
83
+ else {
84
+ body += '.*';
85
+ }
86
+ continue;
87
+ }
88
+ body += '[^/]*';
89
+ continue;
90
+ }
91
+ if (char === '?') {
92
+ body += '[^/]';
93
+ continue;
94
+ }
95
+ body += REGEX_SPECIAL.test(char) ? `\\${char}` : char;
96
+ }
97
+ const compiled = new RegExp(`^${body}$`);
98
+ if (regexCache.size >= REGEX_CACHE_LIMIT)
99
+ regexCache.clear();
100
+ regexCache.set(glob, compiled);
101
+ return compiled;
102
+ }
103
+ export function expandGlob(inventory, pattern, cap) {
104
+ const raw = String(pattern ?? '').trim();
105
+ if (!raw)
106
+ return { matches: [], total: 0, capped: false };
107
+ if (!isGlobPattern(raw)) {
108
+ const hit = inventory.set.has(raw);
109
+ return { matches: hit ? [raw] : [], total: hit ? 1 : 0, capped: false };
110
+ }
111
+ const expression = globToRegExp(raw);
112
+ const matches = [];
113
+ let total = 0;
114
+ for (const file of inventory.files) {
115
+ if (!expression.test(file))
116
+ continue;
117
+ total += 1;
118
+ if (total <= cap)
119
+ matches.push(file);
120
+ }
121
+ if (total > cap)
122
+ return { matches: [], total, capped: true };
123
+ return { matches, total, capped: false };
124
+ }
@@ -0,0 +1,92 @@
1
+ import { emptyContextGraphFragment } from '../../contracts.js';
2
+ import { compareContextGraphIds } from '../../ids.js';
3
+ import { buildCommandGraph, collectRuntimeManifest } from './commands.js';
4
+ import { buildGateAffectedEdges, buildGateDependencyEdges, buildGatePresetPipelines, buildGateVerificationEdges, reportUnbackedProtectedGates } from './gate-edges.js';
5
+ import { buildGateNodes, collectTopologyGates } from './gates.js';
6
+ import { buildFileInventory } from './globs.js';
7
+ import { buildRouteGraph } from './routes.js';
8
+ import { TOPOLOGY_EXTRACTOR_ID, TOPOLOGY_EXTRACTOR_REVISION, createTopologyContext, recordSkip, topologyExpired } from './shared.js';
9
+ const INVENTORY_SKIP_PATH = 'release-gates.v2.json';
10
+ function sortIssues(issues) {
11
+ return [...issues].sort((left, right) => {
12
+ const byCode = left.code.localeCompare(right.code);
13
+ if (byCode !== 0)
14
+ return byCode;
15
+ return left.message.localeCompare(right.message);
16
+ });
17
+ }
18
+ function sortSkips(skips) {
19
+ return [...skips].sort((left, right) => {
20
+ const byPath = left.path.localeCompare(right.path);
21
+ if (byPath !== 0)
22
+ return byPath;
23
+ const byReason = left.reason.localeCompare(right.reason);
24
+ if (byReason !== 0)
25
+ return byReason;
26
+ return String(left.detail ?? '').localeCompare(String(right.detail ?? ''));
27
+ });
28
+ }
29
+ function sortedHashes(hashes) {
30
+ const out = {};
31
+ for (const key of Object.keys(hashes).sort()) {
32
+ const value = hashes[key];
33
+ if (value !== undefined)
34
+ out[key] = value;
35
+ }
36
+ return out;
37
+ }
38
+ function finalize(ctx) {
39
+ const fragment = emptyContextGraphFragment(TOPOLOGY_EXTRACTOR_ID, TOPOLOGY_EXTRACTOR_REVISION);
40
+ fragment.nodes = [...ctx.nodes.values()].sort((left, right) => compareContextGraphIds(left.id, right.id));
41
+ fragment.edges = [...ctx.edges.values()].sort((left, right) => compareContextGraphIds(left.id, right.id));
42
+ fragment.issues = sortIssues(ctx.issues);
43
+ fragment.skipped = sortSkips(ctx.skipped);
44
+ fragment.inputHashes = sortedHashes(ctx.inputHashes);
45
+ return fragment;
46
+ }
47
+ export class TopologyGraphExtractor {
48
+ id = TOPOLOGY_EXTRACTOR_ID;
49
+ revision = TOPOLOGY_EXTRACTOR_REVISION;
50
+ async extract(input) {
51
+ const startedAt = Date.now();
52
+ const files = buildFileInventory(input.root, input.limits.maxFiles);
53
+ const ctx = createTopologyContext({
54
+ root: input.root,
55
+ observedAt: input.observedAt,
56
+ limits: input.limits,
57
+ files,
58
+ startedAt
59
+ });
60
+ if (files.truncated) {
61
+ recordSkip(ctx, INVENTORY_SKIP_PATH, 'cap_reached', `file inventory stopped at ${input.limits.maxFiles} files`);
62
+ }
63
+ const runtime = collectRuntimeManifest(ctx);
64
+ const gates = collectTopologyGates(ctx);
65
+ buildGateNodes(ctx, gates);
66
+ buildGateDependencyEdges(ctx, gates);
67
+ let presets = [];
68
+ if (topologyExpired(ctx)) {
69
+ recordSkip(ctx, INVENTORY_SKIP_PATH, 'cap_reached', 'extraction time budget reached before gate file relations');
70
+ }
71
+ else {
72
+ const verified = buildGateVerificationEdges(ctx, gates);
73
+ const affected = buildGateAffectedEdges(ctx, gates);
74
+ reportUnbackedProtectedGates(ctx, gates, verified, affected);
75
+ presets = buildGatePresetPipelines(ctx, gates);
76
+ }
77
+ if (topologyExpired(ctx)) {
78
+ recordSkip(ctx, INVENTORY_SKIP_PATH, 'cap_reached', 'extraction time budget reached before command topology');
79
+ return finalize(ctx);
80
+ }
81
+ buildCommandGraph(ctx, runtime, presets);
82
+ buildRouteGraph(ctx);
83
+ return finalize(ctx);
84
+ }
85
+ }
86
+ export function createTopologyGraphExtractor() {
87
+ return new TopologyGraphExtractor();
88
+ }
89
+ export { TOPOLOGY_EXTRACTOR_ID, TOPOLOGY_EXTRACTOR_REVISION } from './shared.js';
90
+ export { TOPOLOGY_GATE_MANIFESTS } from './gates.js';
91
+ export { TOPOLOGY_COMMAND_MANIFEST_PATH, TOPOLOGY_RUNTIME_SCRIPTS_PATH } from './commands.js';
92
+ export { TOPOLOGY_ROUTES_PATH } from './routes.js';
@@ -0,0 +1,135 @@
1
+ import { ROUTES } from '../../../../routes.js';
2
+ import { contextGraphNodeId } from '../../ids.js';
3
+ import { canonicalCommandName, commandNodeIdFor } from './commands.js';
4
+ import { addEdge, addNode, asRecord, estimateTokenCost, isCanonicalId, mapIdentifierLines, readStringArrayField, readStringField, readWorkspaceText, recordSkip, topologyLintError } from './shared.js';
5
+ export const TOPOLOGY_ROUTES_PATH = 'src/core/routes.ts';
6
+ const ROUTE_ID_LINE = /^\s*id:\s*'([^']+)'/;
7
+ const CLI_VERB = /^sks\s+([a-z][a-z0-9-]*)/;
8
+ export function routeCliCommandName(cliEntrypoint) {
9
+ const match = CLI_VERB.exec(String(cliEntrypoint ?? '').trim());
10
+ const verb = match?.[1];
11
+ return verb ? canonicalCommandName(verb) : null;
12
+ }
13
+ export function buildRouteGraph(ctx) {
14
+ const read = readWorkspaceText(ctx, TOPOLOGY_ROUTES_PATH);
15
+ if (!read)
16
+ return;
17
+ const lines = mapIdentifierLines(read.text, ROUTE_ID_LINE);
18
+ const entries = ROUTES;
19
+ if (!entries.length) {
20
+ topologyLintError(ctx, 'invalid_node_field', `${TOPOLOGY_ROUTES_PATH} declares no routes`, { path: TOPOLOGY_ROUTES_PATH });
21
+ return;
22
+ }
23
+ for (const raw of entries) {
24
+ const route = asRecord(raw);
25
+ if (!route) {
26
+ topologyLintError(ctx, 'invalid_node_field', `${TOPOLOGY_ROUTES_PATH} contains a non-object route`, {
27
+ path: TOPOLOGY_ROUTES_PATH
28
+ });
29
+ continue;
30
+ }
31
+ const id = readStringField(route, 'id').trim();
32
+ if (!id) {
33
+ topologyLintError(ctx, 'invalid_node_field', `${TOPOLOGY_ROUTES_PATH} contains a route with no id`, {
34
+ path: TOPOLOGY_ROUTES_PATH
35
+ });
36
+ continue;
37
+ }
38
+ const routeNodeId = contextGraphNodeId({ kind: 'route', name: id });
39
+ if (!isCanonicalId('route', id, routeNodeId)) {
40
+ topologyLintError(ctx, 'invalid_node_field', `route "${id}" is not a canonical route id`, { path: TOPOLOGY_ROUTES_PATH });
41
+ continue;
42
+ }
43
+ const line = lines.get(id);
44
+ const lifecycle = readStringArrayField(route, 'lifecycle');
45
+ const routeLabel = readStringField(route, 'route') || id;
46
+ const stopGate = readStringField(route, 'stopGate');
47
+ const codexAppOnly = route.codexAppOnly === true;
48
+ const cliEntrypoint = readStringField(route, 'cliEntrypoint');
49
+ const routeMetadata = {
50
+ source: 'routes',
51
+ routeId: id,
52
+ command: readStringField(route, 'command'),
53
+ mode: readStringField(route, 'mode'),
54
+ routeName: routeLabel,
55
+ stopGate,
56
+ context7Policy: readStringField(route, 'context7Policy'),
57
+ reasoningPolicy: readStringField(route, 'reasoningPolicy'),
58
+ requiredSkills: readStringArrayField(route, 'requiredSkills'),
59
+ dollarAliases: readStringArrayField(route, 'dollarAliases'),
60
+ hidden: route.hidden === true,
61
+ codexAppOnly,
62
+ cliEntrypointDeclared: Boolean(cliEntrypoint)
63
+ };
64
+ const routeAdded = addNode(ctx, {
65
+ id: routeNodeId,
66
+ kind: 'route',
67
+ label: id,
68
+ path: TOPOLOGY_ROUTES_PATH,
69
+ line,
70
+ contentHash: read.hash,
71
+ trust: 0.9,
72
+ risk: route.mutatesRouteState === true ? 'high' : 'medium',
73
+ tokenCost: estimateTokenCost([id, routeLabel, lifecycle.join(' ')]),
74
+ metadata: routeMetadata,
75
+ sourcePath: TOPOLOGY_ROUTES_PATH
76
+ });
77
+ if (!routeAdded)
78
+ continue;
79
+ if (!lifecycle.length) {
80
+ recordSkip(ctx, TOPOLOGY_ROUTES_PATH, 'excluded', `route ${id} declares no lifecycle pipeline`);
81
+ continue;
82
+ }
83
+ const pipelineNodeId = contextGraphNodeId({ kind: 'pipeline', pipelineId: `route:${id}` });
84
+ const pipelineMetadata = {
85
+ source: 'routes',
86
+ pipelineKind: 'route_lifecycle',
87
+ routeId: id,
88
+ stages: lifecycle,
89
+ stageCount: lifecycle.length,
90
+ stopGate,
91
+ codexAppOnly
92
+ };
93
+ const pipelineAdded = addNode(ctx, {
94
+ id: pipelineNodeId,
95
+ kind: 'pipeline',
96
+ label: routeLabel,
97
+ path: TOPOLOGY_ROUTES_PATH,
98
+ line,
99
+ contentHash: read.hash,
100
+ trust: 0.9,
101
+ risk: 'medium',
102
+ tokenCost: estimateTokenCost([routeLabel, lifecycle.join(' ')]),
103
+ metadata: pipelineMetadata,
104
+ sourcePath: TOPOLOGY_ROUTES_PATH
105
+ });
106
+ if (!pipelineAdded)
107
+ continue;
108
+ addEdge(ctx, {
109
+ from: routeNodeId,
110
+ to: pipelineNodeId,
111
+ type: 'routes_to',
112
+ confidence: 'manifest',
113
+ path: TOPOLOGY_ROUTES_PATH,
114
+ hash: read.hash,
115
+ line
116
+ });
117
+ if (codexAppOnly)
118
+ continue;
119
+ const commandName = routeCliCommandName(cliEntrypoint);
120
+ if (!commandName)
121
+ continue;
122
+ const commandId = commandNodeIdFor(commandName);
123
+ if (!ctx.nodes.has(commandId))
124
+ continue;
125
+ addEdge(ctx, {
126
+ from: commandId,
127
+ to: pipelineNodeId,
128
+ type: 'routes_to',
129
+ confidence: 'manifest',
130
+ path: TOPOLOGY_ROUTES_PATH,
131
+ hash: read.hash,
132
+ line
133
+ });
134
+ }
135
+ }
@@ -0,0 +1,215 @@
1
+ import fs from 'node:fs';
2
+ import { sha256 } from '../../../../fsx.js';
3
+ import { lintError } from '../../contracts.js';
4
+ import { contextGraphEdgeId, contextGraphNodeId } from '../../ids.js';
5
+ import { ContextGraphPathError, isWorkspaceRelativePosixPath, resolveInsideWorkspace } from '../../paths.js';
6
+ export const TOPOLOGY_EXTRACTOR_ID = 'topology';
7
+ export const TOPOLOGY_EXTRACTOR_REVISION = '1.0.0';
8
+ export const TOPOLOGY_GLOB_MATCH_CAP = 48;
9
+ export const TOPOLOGY_GATE_AFFECTED_CAP = 96;
10
+ export const TOPOLOGY_GATE_VERIFIED_CAP = 24;
11
+ export function createTopologyContext(params) {
12
+ const timeout = Number.isFinite(params.limits.timeoutMs) && params.limits.timeoutMs > 0 ? params.limits.timeoutMs : 0;
13
+ return {
14
+ root: params.root,
15
+ observedAt: params.observedAt,
16
+ limits: params.limits,
17
+ deadline: params.startedAt + timeout,
18
+ files: params.files,
19
+ nodes: new Map(),
20
+ edges: new Map(),
21
+ issues: [],
22
+ skipped: [],
23
+ inputHashes: {},
24
+ fileClassification: new Map(),
25
+ skipKeys: new Set()
26
+ };
27
+ }
28
+ export function topologyExpired(ctx) {
29
+ return Date.now() > ctx.deadline;
30
+ }
31
+ export function estimateTokenCost(parts) {
32
+ let total = 0;
33
+ for (const part of parts)
34
+ total += part.length;
35
+ return Math.max(1, Math.ceil(total / 4));
36
+ }
37
+ export function recordSkip(ctx, skipPath, reason, detail) {
38
+ const key = `${skipPath}${reason}${detail ?? ''}`;
39
+ if (ctx.skipKeys.has(key))
40
+ return;
41
+ ctx.skipKeys.add(key);
42
+ ctx.skipped.push({ path: skipPath, reason, ...(detail === undefined ? {} : { detail }) });
43
+ }
44
+ export function topologyLintError(ctx, code, message, extra = {}) {
45
+ ctx.issues.push(lintError(code, message, { ...extra, extractor: TOPOLOGY_EXTRACTOR_ID }));
46
+ }
47
+ export function addNode(ctx, input) {
48
+ const existing = ctx.nodes.get(input.id);
49
+ if (existing) {
50
+ if (existing.kind !== input.kind) {
51
+ topologyLintError(ctx, 'duplicate_node_conflict', `node ${input.id} is claimed as both ${existing.kind} and ${input.kind}`, { nodeId: input.id, path: input.sourcePath });
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+ if (ctx.nodes.size >= ctx.limits.maxNodes) {
57
+ recordSkip(ctx, input.sourcePath, 'cap_reached', 'node budget reached');
58
+ return false;
59
+ }
60
+ if (input.path !== undefined && !isWorkspaceRelativePosixPath(input.path)) {
61
+ topologyLintError(ctx, 'absolute_or_escaping_path', `node ${input.id} carries a non workspace-relative path`, {
62
+ nodeId: input.id,
63
+ path: input.sourcePath
64
+ });
65
+ return false;
66
+ }
67
+ ctx.nodes.set(input.id, {
68
+ id: input.id,
69
+ kind: input.kind,
70
+ label: input.label,
71
+ ...(input.path === undefined ? {} : { path: input.path }),
72
+ ...(input.line === undefined ? {} : { locator: { line: input.line } }),
73
+ ...(input.contentHash === undefined ? {} : { contentHash: input.contentHash }),
74
+ trust: input.trust,
75
+ freshness: 'fresh',
76
+ risk: input.risk,
77
+ tokenCost: input.tokenCost,
78
+ metadata: input.metadata
79
+ });
80
+ return true;
81
+ }
82
+ export function addEdge(ctx, input) {
83
+ if (!ctx.nodes.has(input.from) || !ctx.nodes.has(input.to)) {
84
+ topologyLintError(ctx, 'dangling_edge', `${input.type} edge ${input.from} -> ${input.to} has an unresolved endpoint`, { path: input.path });
85
+ return false;
86
+ }
87
+ if (!input.path || !input.hash || !isWorkspaceRelativePosixPath(input.path)) {
88
+ topologyLintError(ctx, 'edge_without_provenance', `${input.type} edge ${input.from} -> ${input.to} has no usable provenance`);
89
+ return false;
90
+ }
91
+ const id = contextGraphEdgeId({ from: input.from, to: input.to, type: input.type });
92
+ if (ctx.edges.has(id))
93
+ return true;
94
+ if (ctx.edges.size >= ctx.limits.maxEdges) {
95
+ recordSkip(ctx, input.path, 'cap_reached', 'edge budget reached');
96
+ return false;
97
+ }
98
+ ctx.edges.set(id, {
99
+ id,
100
+ from: input.from,
101
+ to: input.to,
102
+ type: input.type,
103
+ confidence: input.confidence,
104
+ provenance: {
105
+ path: input.path,
106
+ ...(input.line === undefined ? {} : { line: input.line }),
107
+ hash: input.hash,
108
+ extractor: TOPOLOGY_EXTRACTOR_ID
109
+ },
110
+ observedAt: ctx.observedAt
111
+ });
112
+ return true;
113
+ }
114
+ export function ensureFileNode(ctx, relativePath, role, sourcePath) {
115
+ if (!isWorkspaceRelativePosixPath(relativePath))
116
+ return null;
117
+ const id = contextGraphNodeId({ kind: 'file', path: relativePath });
118
+ const classification = ctx.fileClassification.get(relativePath);
119
+ const metadata = {
120
+ source: TOPOLOGY_EXTRACTOR_ID,
121
+ role,
122
+ ...(classification === undefined ? {} : { runtimeClassification: classification })
123
+ };
124
+ const added = addNode(ctx, {
125
+ id,
126
+ kind: 'file',
127
+ label: relativePath.slice(relativePath.lastIndexOf('/') + 1) || relativePath,
128
+ path: relativePath,
129
+ trust: 0.9,
130
+ risk: 'low',
131
+ tokenCost: estimateTokenCost([relativePath]),
132
+ metadata,
133
+ sourcePath
134
+ });
135
+ return added ? id : null;
136
+ }
137
+ export function readWorkspaceText(ctx, relativePath) {
138
+ let absolute = null;
139
+ try {
140
+ absolute = resolveInsideWorkspace(ctx.root, relativePath);
141
+ }
142
+ catch (error) {
143
+ if (error instanceof ContextGraphPathError) {
144
+ recordSkip(ctx, relativePath, 'symlink_escape', 'manifest resolves outside the workspace');
145
+ topologyLintError(ctx, 'symlink_escape', `${relativePath} resolves outside the workspace`, { path: relativePath });
146
+ return null;
147
+ }
148
+ throw error;
149
+ }
150
+ if (!absolute) {
151
+ recordSkip(ctx, relativePath, 'excluded', 'not present in this workspace');
152
+ return null;
153
+ }
154
+ try {
155
+ const stat = fs.statSync(absolute);
156
+ if (!stat.isFile()) {
157
+ recordSkip(ctx, relativePath, 'unreadable', 'not a regular file');
158
+ topologyLintError(ctx, 'invalid_node_field', `${relativePath} is not a regular file`, { path: relativePath });
159
+ return null;
160
+ }
161
+ if (stat.size > ctx.limits.maxFileBytes) {
162
+ recordSkip(ctx, relativePath, 'oversized', 'manifest exceeds the extraction byte limit');
163
+ topologyLintError(ctx, 'invalid_node_field', `${relativePath} exceeds the extraction byte limit`, { path: relativePath });
164
+ return null;
165
+ }
166
+ const buffer = fs.readFileSync(absolute);
167
+ const hash = sha256(buffer);
168
+ ctx.inputHashes[relativePath] = hash;
169
+ return { text: buffer.toString('utf8'), hash };
170
+ }
171
+ catch {
172
+ recordSkip(ctx, relativePath, 'unreadable', 'manifest could not be read');
173
+ topologyLintError(ctx, 'invalid_node_field', `${relativePath} could not be read`, { path: relativePath });
174
+ return null;
175
+ }
176
+ }
177
+ export function mapIdentifierLines(text, pattern) {
178
+ const out = new Map();
179
+ const lines = text.split('\n');
180
+ for (let index = 0; index < lines.length; index += 1) {
181
+ const line = lines[index];
182
+ if (line === undefined)
183
+ continue;
184
+ const match = pattern.exec(line);
185
+ const captured = match?.[1];
186
+ if (!captured || out.has(captured))
187
+ continue;
188
+ out.set(captured, index + 1);
189
+ }
190
+ return out;
191
+ }
192
+ export function asRecord(value) {
193
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
194
+ }
195
+ export function readStringField(source, key) {
196
+ const value = source[key];
197
+ return typeof value === 'string' ? value : '';
198
+ }
199
+ export function readStringArrayField(source, key) {
200
+ const value = source[key];
201
+ if (!Array.isArray(value))
202
+ return [];
203
+ const out = [];
204
+ for (const entry of value)
205
+ if (typeof entry === 'string' && entry)
206
+ out.push(entry);
207
+ return out;
208
+ }
209
+ export function readNumberField(source, key) {
210
+ const value = source[key];
211
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
212
+ }
213
+ export function isCanonicalId(prefix, rawId, generatedId) {
214
+ return generatedId === `${prefix}:${rawId}`;
215
+ }