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.
- package/README.md +129 -10
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -1
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
- package/dist/cli/install-helpers-codex-lb-config.js +114 -9
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +788 -600
- package/dist/cli/router.js +30 -2
- package/dist/commands/codex-app.js +37 -1
- package/dist/commands/codex-lb.js +491 -300
- package/dist/config/skills-manifest.json +57 -71
- package/dist/core/align/align-route.js +127 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
- package/dist/core/codex-app/codex-app-restart.js +83 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
- package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
- package/dist/core/codex-app/menubar/constants.js +5 -0
- package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
- package/dist/core/codex-app/menubar/installer.js +18 -0
- package/dist/core/codex-app/multi-provider-router.js +22 -10
- package/dist/core/codex-app/openrouter-activate.js +26 -1
- package/dist/core/codex-app.js +118 -26
- package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
- package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/capability-runner.js +204 -0
- package/dist/core/codex-lb/capability-types.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +9 -13
- package/dist/core/codex-lb/codex-lb-setup.js +45 -8
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
- package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
- package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
- package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
- package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
- package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
- package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
- package/dist/core/codex-lb/desktop-controller.js +1410 -0
- package/dist/core/codex-lb/desktop-mode.js +32 -0
- package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
- package/dist/core/codex-lb/desktop-service.js +613 -0
- package/dist/core/codex-lb/legacy-migration.js +575 -0
- package/dist/core/codex-lb/migration-receipt.js +246 -0
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
- package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
- package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
- package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
- package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
- package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
- package/dist/core/codex-native/core-skill-manifest.js +22 -6
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
- package/dist/core/commands/align-command.js +220 -0
- package/dist/core/commands/image-ux-review-command.js +280 -68
- package/dist/core/commands/naruto-command.js +257 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +402 -34
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/doctor/skill-legacy-surface.js +1 -3
- package/dist/core/errors/message.js +3 -0
- package/dist/core/evidence/evidence-freshness.js +14 -0
- package/dist/core/feature-fixtures.js +9 -1
- package/dist/core/feature-registry.js +5 -3
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/hook-context.js +15 -2
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
- package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +33 -89
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +230 -12
- package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
- package/dist/core/imagegen/imagegen-capability.js +10 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +25 -13
- package/dist/core/init.js +1 -1
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/perf/release-latency-slo.js +12 -6
- package/dist/core/pipeline-internals/runtime-core.js +78 -0
- package/dist/core/pipeline-internals/runtime-gates.js +5 -2
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/preflight/parallel-preflight-engine.js +1 -1
- package/dist/core/proof/auto-finalize.js +98 -6
- package/dist/core/proof/route-proof-gate.js +12 -1
- package/dist/core/proof/route-proof-policy.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/release-proof-io.js +1 -3
- package/dist/core/release/release-real-contract.js +5 -4
- package/dist/core/release/release-stamp-proof.js +1 -3
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/remote/audit-idempotency.js +50 -18
- package/dist/core/remote/codex-public-activity.js +283 -0
- package/dist/core/remote/index.js +1 -0
- package/dist/core/remote/ssh-worker-client.js +4 -1
- package/dist/core/remote/worker.js +206 -27
- package/dist/core/research/research-adversarial-review.js +1 -3
- package/dist/core/research/research-claim-synthesizer.js +1 -3
- package/dist/core/research/research-falsification-runner.js +1 -3
- package/dist/core/research/research-source-evidence.js +1 -3
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +2 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +30 -10
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/secret-redaction.js +6 -0
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +85 -10
- package/dist/core/telegram/center-snapshot.js +718 -0
- package/dist/core/telegram/config.js +158 -31
- package/dist/core/telegram/hub.js +288 -33
- package/dist/core/telegram/index.js +2 -0
- package/dist/core/telegram/ingress-spool.js +419 -0
- package/dist/core/telegram/ledgers.js +188 -29
- package/dist/core/telegram/messages.js +26 -6
- package/dist/core/telegram/owner-lock.js +10 -7
- package/dist/core/telegram/runtime-projection.js +38 -3
- package/dist/core/telegram/runtime.js +298 -63
- package/dist/core/telegram/service.js +137 -8
- package/dist/core/telegram/setup.js +291 -73
- package/dist/core/text/strings.js +3 -0
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
- package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
- package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
- package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
- package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
- package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
- package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
- package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
- package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +1 -1
- package/dist/scripts/helper-dedup-check.js +0 -2
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +21 -4
- package/dist/scripts/release-real-check.js +7 -0
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +8 -3
- package/release-gates.v2.json +176 -1
- package/runtime-required-scripts.json +30 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
- package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { contextGraphPathFromId } from './ids.js';
|
|
2
|
+
function push(map, key, value) {
|
|
3
|
+
const bucket = map.get(key);
|
|
4
|
+
if (bucket)
|
|
5
|
+
bucket.push(value);
|
|
6
|
+
else
|
|
7
|
+
map.set(key, [value]);
|
|
8
|
+
}
|
|
9
|
+
function freeze(map) {
|
|
10
|
+
const out = new Map();
|
|
11
|
+
for (const [key, value] of map)
|
|
12
|
+
out.set(key, value.sort());
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
export function buildContextGraphIndex(snapshot) {
|
|
16
|
+
const nodesById = new Map();
|
|
17
|
+
const edgesById = new Map();
|
|
18
|
+
const outgoing = new Map();
|
|
19
|
+
const incoming = new Map();
|
|
20
|
+
const nodesByPath = new Map();
|
|
21
|
+
const nodesByLabel = new Map();
|
|
22
|
+
const cycleByNode = new Map();
|
|
23
|
+
for (const node of snapshot.nodes) {
|
|
24
|
+
nodesById.set(node.id, node);
|
|
25
|
+
const nodePath = node.path ?? contextGraphPathFromId(node.id);
|
|
26
|
+
if (nodePath)
|
|
27
|
+
push(nodesByPath, nodePath, node.id);
|
|
28
|
+
if (node.label)
|
|
29
|
+
push(nodesByLabel, node.label.toLowerCase(), node.id);
|
|
30
|
+
}
|
|
31
|
+
for (const edge of snapshot.edges) {
|
|
32
|
+
edgesById.set(edge.id, edge);
|
|
33
|
+
push(outgoing, edge.from, edge.id);
|
|
34
|
+
push(incoming, edge.to, edge.id);
|
|
35
|
+
}
|
|
36
|
+
for (const cycle of snapshot.cycles) {
|
|
37
|
+
for (const nodeId of cycle.nodes)
|
|
38
|
+
cycleByNode.set(nodeId, cycle.id);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
snapshot,
|
|
42
|
+
nodesById,
|
|
43
|
+
edgesById,
|
|
44
|
+
outgoing: freeze(outgoing),
|
|
45
|
+
incoming: freeze(incoming),
|
|
46
|
+
nodesByPath: freeze(nodesByPath),
|
|
47
|
+
nodesByLabel: freeze(nodesByLabel),
|
|
48
|
+
cycleByNode
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function outgoingEdges(index, nodeId) {
|
|
52
|
+
const ids = index.outgoing.get(nodeId) ?? [];
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const id of ids) {
|
|
55
|
+
const edge = index.edgesById.get(id);
|
|
56
|
+
if (edge)
|
|
57
|
+
out.push(edge);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
export function incomingEdges(index, nodeId) {
|
|
62
|
+
const ids = index.incoming.get(nodeId) ?? [];
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const id of ids) {
|
|
65
|
+
const edge = index.edgesById.get(id);
|
|
66
|
+
if (edge)
|
|
67
|
+
out.push(edge);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
export function nodesAtPath(index, relativePath) {
|
|
72
|
+
const ids = index.nodesByPath.get(relativePath) ?? [];
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const id of ids) {
|
|
75
|
+
const node = index.nodesById.get(id);
|
|
76
|
+
if (node)
|
|
77
|
+
out.push(node);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function computeStronglyConnectedComponents(nodeIds, outgoingOf) {
|
|
82
|
+
const indexOf = new Map();
|
|
83
|
+
const lowLink = new Map();
|
|
84
|
+
const onStack = new Set();
|
|
85
|
+
const stack = [];
|
|
86
|
+
const components = [];
|
|
87
|
+
let counter = 0;
|
|
88
|
+
for (const start of nodeIds) {
|
|
89
|
+
if (indexOf.has(start))
|
|
90
|
+
continue;
|
|
91
|
+
const work = [
|
|
92
|
+
{ node: start, edgeIndex: 0, edges: outgoingOf(start) }
|
|
93
|
+
];
|
|
94
|
+
indexOf.set(start, counter);
|
|
95
|
+
lowLink.set(start, counter);
|
|
96
|
+
counter += 1;
|
|
97
|
+
stack.push(start);
|
|
98
|
+
onStack.add(start);
|
|
99
|
+
while (work.length) {
|
|
100
|
+
const frame = work[work.length - 1];
|
|
101
|
+
if (!frame)
|
|
102
|
+
break;
|
|
103
|
+
if (frame.edgeIndex < frame.edges.length) {
|
|
104
|
+
const next = frame.edges[frame.edgeIndex]?.to;
|
|
105
|
+
frame.edgeIndex += 1;
|
|
106
|
+
if (!next)
|
|
107
|
+
continue;
|
|
108
|
+
if (!indexOf.has(next)) {
|
|
109
|
+
indexOf.set(next, counter);
|
|
110
|
+
lowLink.set(next, counter);
|
|
111
|
+
counter += 1;
|
|
112
|
+
stack.push(next);
|
|
113
|
+
onStack.add(next);
|
|
114
|
+
work.push({ node: next, edgeIndex: 0, edges: outgoingOf(next) });
|
|
115
|
+
}
|
|
116
|
+
else if (onStack.has(next)) {
|
|
117
|
+
lowLink.set(frame.node, Math.min(lowLink.get(frame.node) ?? 0, indexOf.get(next) ?? 0));
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
work.pop();
|
|
122
|
+
const parent = work[work.length - 1];
|
|
123
|
+
if (parent) {
|
|
124
|
+
lowLink.set(parent.node, Math.min(lowLink.get(parent.node) ?? 0, lowLink.get(frame.node) ?? 0));
|
|
125
|
+
}
|
|
126
|
+
if (lowLink.get(frame.node) === indexOf.get(frame.node)) {
|
|
127
|
+
const component = [];
|
|
128
|
+
for (;;) {
|
|
129
|
+
const popped = stack.pop();
|
|
130
|
+
if (popped === undefined)
|
|
131
|
+
break;
|
|
132
|
+
onStack.delete(popped);
|
|
133
|
+
component.push(popped);
|
|
134
|
+
if (popped === frame.node)
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
if (component.length > 1)
|
|
138
|
+
components.push(component.sort());
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return components.sort((left, right) => String(left[0]).localeCompare(String(right[0])));
|
|
143
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { lintError, validateContextGraphSnapshot } from '../contracts.js';
|
|
2
|
+
import { computeStronglyConnectedComponents } from '../graph-index.js';
|
|
3
|
+
import { isWorkspaceRelativePosixPath, isSymlinkEscape } from '../paths.js';
|
|
4
|
+
import { containsPlaintextSecret } from '../../../secret-redaction.js';
|
|
5
|
+
import { computeContextGraphSnapshotHash, orderedEdge, orderedNode, sortContextGraphEdges, sortContextGraphNodes } from '../compiler/serialize.js';
|
|
6
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
7
|
+
const MIN_ENV_VALUE_LENGTH = 12;
|
|
8
|
+
const SOURCE_RELATION_KINDS = new Set([
|
|
9
|
+
'file',
|
|
10
|
+
'config',
|
|
11
|
+
'schema',
|
|
12
|
+
'test',
|
|
13
|
+
'proof',
|
|
14
|
+
'source',
|
|
15
|
+
'module',
|
|
16
|
+
'symbol',
|
|
17
|
+
'pipeline',
|
|
18
|
+
'command',
|
|
19
|
+
'route'
|
|
20
|
+
]);
|
|
21
|
+
export function structuralIssues(snapshot) {
|
|
22
|
+
return validateContextGraphSnapshot(snapshot).issues;
|
|
23
|
+
}
|
|
24
|
+
export function pathSafetyIssues(root, snapshot) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
const checked = new Set();
|
|
27
|
+
const check = (value, label, extra) => {
|
|
28
|
+
if (!isWorkspaceRelativePosixPath(value)) {
|
|
29
|
+
issues.push(lintError('absolute_or_escaping_path', `${label} is not a workspace-relative POSIX path`, {
|
|
30
|
+
...extra,
|
|
31
|
+
path: value
|
|
32
|
+
}));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (checked.has(value))
|
|
36
|
+
return;
|
|
37
|
+
checked.add(value);
|
|
38
|
+
if (isSymlinkEscape(root, value)) {
|
|
39
|
+
issues.push(lintError('symlink_escape', `${label} resolves outside the workspace through a symlink`, {
|
|
40
|
+
...extra,
|
|
41
|
+
path: value
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
for (const node of snapshot.nodes) {
|
|
46
|
+
if (node.path !== undefined)
|
|
47
|
+
check(node.path, `node ${node.id} path`, { nodeId: node.id });
|
|
48
|
+
}
|
|
49
|
+
for (const edge of snapshot.edges) {
|
|
50
|
+
const provenancePath = edge.provenance?.path;
|
|
51
|
+
if (typeof provenancePath === 'string' && provenancePath) {
|
|
52
|
+
check(provenancePath, `edge ${edge.id} provenance path`, {
|
|
53
|
+
edgeId: edge.id,
|
|
54
|
+
extractor: edge.provenance.extractor
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
function nodeValues(node) {
|
|
61
|
+
const parts = [node.label ?? '', node.path ?? ''];
|
|
62
|
+
for (const [key, value] of Object.entries(node.metadata ?? {})) {
|
|
63
|
+
if (typeof value === 'string')
|
|
64
|
+
parts.push(`${key}=${value}`);
|
|
65
|
+
else if (Array.isArray(value))
|
|
66
|
+
for (const item of value)
|
|
67
|
+
parts.push(`${key}=${item}`);
|
|
68
|
+
}
|
|
69
|
+
return parts.filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
function edgeValues(edge) {
|
|
72
|
+
return [edge.provenance?.path ?? '', edge.provenance?.extractor ?? '', edge.provenance?.hash ?? ''].filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
const HIGH_ENTROPY_RUN_RE = /[A-Za-z0-9_+/=~-]{20,}/g;
|
|
75
|
+
function looksHighEntropy(token) {
|
|
76
|
+
if (/^[a-f0-9]{8,64}$/.test(token))
|
|
77
|
+
return false;
|
|
78
|
+
if (token.length >= 32)
|
|
79
|
+
return true;
|
|
80
|
+
return /[A-Z]/.test(token) && /[a-z]/.test(token) && /[0-9]/.test(token);
|
|
81
|
+
}
|
|
82
|
+
function isStructuralValue(value) {
|
|
83
|
+
if (value.length > 512)
|
|
84
|
+
return false;
|
|
85
|
+
for (const run of value.match(HIGH_ENTROPY_RUN_RE) ?? []) {
|
|
86
|
+
if (looksHighEntropy(run))
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
function environmentValues(env) {
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const value of Object.values(env)) {
|
|
94
|
+
if (typeof value === 'string' && value.length >= MIN_ENV_VALUE_LENGTH)
|
|
95
|
+
out.push(value);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
export function secretIssues(snapshot, env = process.env) {
|
|
100
|
+
const issues = [];
|
|
101
|
+
const envValues = environmentValues(env);
|
|
102
|
+
const scan = (values, where, label) => {
|
|
103
|
+
for (const text of values) {
|
|
104
|
+
if (!isStructuralValue(text) && containsPlaintextSecret(text, env)) {
|
|
105
|
+
issues.push(lintError('secret_like_value', `${label} carries a secret-like value`, where));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const value of envValues) {
|
|
109
|
+
if (text.includes(value)) {
|
|
110
|
+
issues.push(lintError('secret_like_value', `${label} carries a raw environment value`, where));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
for (const node of snapshot.nodes)
|
|
117
|
+
scan(nodeValues(node), { nodeId: node.id }, `node ${node.id}`);
|
|
118
|
+
for (const edge of snapshot.edges) {
|
|
119
|
+
scan(edgeValues(edge), { edgeId: edge.id, extractor: edge.provenance?.extractor }, `edge ${edge.id}`);
|
|
120
|
+
}
|
|
121
|
+
return issues;
|
|
122
|
+
}
|
|
123
|
+
export function determinismIssues(snapshot) {
|
|
124
|
+
const issues = [];
|
|
125
|
+
const canonicalNodes = JSON.stringify(sortContextGraphNodes(snapshot.nodes).map(orderedNode));
|
|
126
|
+
if (JSON.stringify(snapshot.nodes.map(orderedNode)) !== canonicalNodes) {
|
|
127
|
+
issues.push(lintError('non_deterministic_serialization', 'nodes are not in canonical order'));
|
|
128
|
+
}
|
|
129
|
+
if (JSON.stringify(snapshot.nodes) !== canonicalNodes) {
|
|
130
|
+
issues.push(lintError('non_deterministic_serialization', 'node objects do not use the canonical key order'));
|
|
131
|
+
}
|
|
132
|
+
const canonicalEdges = JSON.stringify(sortContextGraphEdges(snapshot.edges).map((edge) => orderedEdge(edge)));
|
|
133
|
+
if (JSON.stringify(snapshot.edges.map((edge) => orderedEdge(edge))) !== canonicalEdges) {
|
|
134
|
+
issues.push(lintError('non_deterministic_serialization', 'edges are not in canonical order'));
|
|
135
|
+
}
|
|
136
|
+
if (JSON.stringify(snapshot.edges) !== canonicalEdges) {
|
|
137
|
+
issues.push(lintError('non_deterministic_serialization', 'edge objects do not use the canonical key order'));
|
|
138
|
+
}
|
|
139
|
+
return issues;
|
|
140
|
+
}
|
|
141
|
+
export function hashIssues(snapshot, meta) {
|
|
142
|
+
const issues = [];
|
|
143
|
+
const recomputed = computeContextGraphSnapshotHash(snapshot);
|
|
144
|
+
if (recomputed !== snapshot.snapshotHash) {
|
|
145
|
+
issues.push(lintError('hash_mismatch', 'snapshotHash does not match the canonical serialization'));
|
|
146
|
+
}
|
|
147
|
+
for (const node of snapshot.nodes) {
|
|
148
|
+
if (node.contentHash !== undefined && !SHA256_RE.test(node.contentHash)) {
|
|
149
|
+
issues.push(lintError('hash_mismatch', `node ${node.id} contentHash is not a sha256 digest`, { nodeId: node.id }));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const edge of snapshot.edges) {
|
|
153
|
+
const hash = edge.provenance?.hash;
|
|
154
|
+
if (typeof hash !== 'string' || !SHA256_RE.test(hash)) {
|
|
155
|
+
issues.push(lintError('hash_mismatch', `edge ${edge.id} provenance hash is not a sha256 digest`, { edgeId: edge.id }));
|
|
156
|
+
}
|
|
157
|
+
if (typeof edge.provenance?.extractor !== 'string' || !edge.provenance.extractor) {
|
|
158
|
+
issues.push(lintError('edge_without_provenance', `edge ${edge.id} provenance names no extractor`, { edgeId: edge.id }));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!meta)
|
|
162
|
+
return issues;
|
|
163
|
+
if (meta.snapshotHash !== snapshot.snapshotHash) {
|
|
164
|
+
issues.push(lintError('snapshot_meta_mismatch', 'meta snapshotHash does not match the snapshot'));
|
|
165
|
+
}
|
|
166
|
+
if (meta.nodeCount !== snapshot.nodeCount || meta.edgeCount !== snapshot.edgeCount) {
|
|
167
|
+
issues.push(lintError('snapshot_meta_mismatch', 'meta counts do not match the snapshot'));
|
|
168
|
+
}
|
|
169
|
+
return issues;
|
|
170
|
+
}
|
|
171
|
+
export function protectedGateIssues(index) {
|
|
172
|
+
const issues = [];
|
|
173
|
+
for (const node of index.snapshot.nodes) {
|
|
174
|
+
if (node.kind !== 'gate' || node.risk !== 'protected')
|
|
175
|
+
continue;
|
|
176
|
+
const incident = [...(index.outgoing.get(node.id) ?? []), ...(index.incoming.get(node.id) ?? [])];
|
|
177
|
+
const grounded = incident.some((edgeId) => {
|
|
178
|
+
const edge = index.edgesById.get(edgeId);
|
|
179
|
+
if (!edge)
|
|
180
|
+
return false;
|
|
181
|
+
const otherId = edge.from === node.id ? edge.to : edge.from;
|
|
182
|
+
const other = index.nodesById.get(otherId);
|
|
183
|
+
return Boolean(other && SOURCE_RELATION_KINDS.has(other.kind));
|
|
184
|
+
});
|
|
185
|
+
if (!grounded) {
|
|
186
|
+
issues.push(lintError('protected_gate_without_source_relation', `protected gate ${node.id} has no relation to a repository source`, {
|
|
187
|
+
nodeId: node.id
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return issues;
|
|
192
|
+
}
|
|
193
|
+
export function manifestCycleIssues(snapshot) {
|
|
194
|
+
const manifest = new Map();
|
|
195
|
+
for (const edge of snapshot.edges) {
|
|
196
|
+
if (edge.confidence !== 'manifest')
|
|
197
|
+
continue;
|
|
198
|
+
const bucket = manifest.get(edge.from);
|
|
199
|
+
if (bucket)
|
|
200
|
+
bucket.push({ to: edge.to });
|
|
201
|
+
else
|
|
202
|
+
manifest.set(edge.from, [{ to: edge.to }]);
|
|
203
|
+
}
|
|
204
|
+
if (manifest.size === 0)
|
|
205
|
+
return [];
|
|
206
|
+
const nodeIds = snapshot.nodes.map((node) => node.id);
|
|
207
|
+
const components = computeStronglyConnectedComponents(nodeIds, (nodeId) => manifest.get(nodeId) ?? []);
|
|
208
|
+
return components.map((component) => lintError('manifest_dag_cycle', `manifest relations form a cycle across ${component.length} nodes`, {
|
|
209
|
+
nodeId: component[0] ?? ''
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
export function freshnessClaimIssues(snapshot, sourceHashes) {
|
|
213
|
+
if (!sourceHashes)
|
|
214
|
+
return [];
|
|
215
|
+
const issues = [];
|
|
216
|
+
for (const node of snapshot.nodes) {
|
|
217
|
+
if (node.freshness !== 'fresh' || node.path === undefined)
|
|
218
|
+
continue;
|
|
219
|
+
const current = sourceHashes[node.path];
|
|
220
|
+
if (current === undefined)
|
|
221
|
+
continue;
|
|
222
|
+
if (node.contentHash === undefined || node.contentHash === '')
|
|
223
|
+
continue;
|
|
224
|
+
if (current !== node.contentHash) {
|
|
225
|
+
issues.push(lintError('freshness_claim_mismatch', `node ${node.id} claims fresh but its source hash changed`, {
|
|
226
|
+
nodeId: node.id,
|
|
227
|
+
path: node.path
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return issues;
|
|
232
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { lintWarning } from '../contracts.js';
|
|
2
|
+
import { incomingEdges, outgoingEdges } from '../graph-index.js';
|
|
3
|
+
import { CONTEXT_GRAPH_QUERY_PROFILES } from '../profiles.js';
|
|
4
|
+
const CITATION_EDGE_TYPES = new Set([
|
|
5
|
+
'cites',
|
|
6
|
+
'derived_from',
|
|
7
|
+
'supports'
|
|
8
|
+
]);
|
|
9
|
+
const VERIFICATION_EDGE_TYPES = new Set([
|
|
10
|
+
'tests',
|
|
11
|
+
'verified_by',
|
|
12
|
+
'gated_by'
|
|
13
|
+
]);
|
|
14
|
+
const HIGH_FAN_IN = 8;
|
|
15
|
+
const LOW_TRUST = 0.5;
|
|
16
|
+
const PER_RULE_LIMIT = 50;
|
|
17
|
+
function traversableEdgeTypes() {
|
|
18
|
+
const out = new Set();
|
|
19
|
+
for (const profile of Object.values(CONTEXT_GRAPH_QUERY_PROFILES)) {
|
|
20
|
+
for (const edge of profile.edges)
|
|
21
|
+
out.add(edge);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function capped(issues, limit = PER_RULE_LIMIT) {
|
|
26
|
+
if (issues.length <= limit)
|
|
27
|
+
return [...issues];
|
|
28
|
+
const head = issues.slice(0, limit);
|
|
29
|
+
const first = issues[0];
|
|
30
|
+
if (first) {
|
|
31
|
+
head.push({
|
|
32
|
+
code: first.code,
|
|
33
|
+
severity: 'warning',
|
|
34
|
+
message: `${issues.length - limit} further ${first.code} warnings were omitted from this snapshot`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return head;
|
|
38
|
+
}
|
|
39
|
+
export function warningIssues(input) {
|
|
40
|
+
const { index } = input;
|
|
41
|
+
const traversable = traversableEdgeTypes();
|
|
42
|
+
const orphanClaims = [];
|
|
43
|
+
const lowTrust = [];
|
|
44
|
+
const unknownFreshness = [];
|
|
45
|
+
const highFanIn = [];
|
|
46
|
+
const unreachable = [];
|
|
47
|
+
for (const node of index.snapshot.nodes) {
|
|
48
|
+
const outgoing = outgoingEdges(index, node.id);
|
|
49
|
+
const incoming = incomingEdges(index, node.id);
|
|
50
|
+
if (node.kind === 'wiki_claim') {
|
|
51
|
+
const citations = outgoing.filter((edge) => CITATION_EDGE_TYPES.has(edge.type));
|
|
52
|
+
if (citations.length === 0) {
|
|
53
|
+
orphanClaims.push(lintWarning('orphan_wiki_claim', `wiki claim ${node.id} cites no source`, { nodeId: node.id }));
|
|
54
|
+
}
|
|
55
|
+
else if (citations.length === 1 && node.trust < LOW_TRUST) {
|
|
56
|
+
lowTrust.push(lintWarning('single_source_low_trust_synthesis', `wiki claim ${node.id} rests on one low-trust source`, {
|
|
57
|
+
nodeId: node.id
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (node.freshness === 'unknown') {
|
|
62
|
+
unknownFreshness.push(lintWarning('unknown_freshness', `node ${node.id} has unknown freshness`, { nodeId: node.id }));
|
|
63
|
+
}
|
|
64
|
+
if (incoming.length >= HIGH_FAN_IN) {
|
|
65
|
+
const verified = [...incoming, ...outgoing].some((edge) => VERIFICATION_EDGE_TYPES.has(edge.type));
|
|
66
|
+
if (!verified) {
|
|
67
|
+
highFanIn.push(lintWarning('high_fan_in_without_verification', `node ${node.id} has ${incoming.length} dependents and no test or gate relation`, { nodeId: node.id }));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const reachable = [...incoming, ...outgoing].some((edge) => traversable.has(edge.type));
|
|
71
|
+
if (!reachable) {
|
|
72
|
+
unreachable.push(lintWarning('unreachable_in_profile', `node ${node.id} is not reachable by any query profile`, {
|
|
73
|
+
nodeId: node.id
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
...capped(orphanClaims),
|
|
79
|
+
...capped(lowTrust),
|
|
80
|
+
...capped(unknownFreshness),
|
|
81
|
+
...capped(highFanIn),
|
|
82
|
+
...capped(unreachable),
|
|
83
|
+
...summarizeSkips(input.skipped ?? [])
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
export function summarizeSkips(skipped) {
|
|
87
|
+
const counts = new Map();
|
|
88
|
+
for (const skip of skipped)
|
|
89
|
+
counts.set(skip.reason, (counts.get(skip.reason) ?? 0) + 1);
|
|
90
|
+
return [...counts.entries()]
|
|
91
|
+
.sort((left, right) => (left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0))
|
|
92
|
+
.map(([reason, count]) => lintWarning('extractor_skipped_input', `${count} input(s) were skipped: ${reason}`));
|
|
93
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const CONTEXT_GRAPH_DIR_SEGMENTS = ['.sneakoscope', 'wiki'];
|
|
4
|
+
export class ContextGraphPathError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ContextGraphPathError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function toPosix(value) {
|
|
13
|
+
return value.replace(/\\/g, '/');
|
|
14
|
+
}
|
|
15
|
+
export function normalizeGraphPath(root, candidate) {
|
|
16
|
+
const raw = String(candidate ?? '').trim();
|
|
17
|
+
if (!raw)
|
|
18
|
+
throw new ContextGraphPathError('empty_path', 'graph path is empty');
|
|
19
|
+
const absoluteRoot = path.resolve(root);
|
|
20
|
+
const absolute = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(absoluteRoot, raw);
|
|
21
|
+
const relative = path.relative(absoluteRoot, absolute);
|
|
22
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
23
|
+
throw new ContextGraphPathError('absolute_or_escaping_path', `path escapes the workspace: ${toPosix(raw)}`);
|
|
24
|
+
}
|
|
25
|
+
return toPosix(relative);
|
|
26
|
+
}
|
|
27
|
+
export function tryNormalizeGraphPath(root, candidate) {
|
|
28
|
+
try {
|
|
29
|
+
return normalizeGraphPath(root, candidate);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function isWorkspaceRelativePosixPath(value) {
|
|
36
|
+
if (typeof value !== 'string' || !value)
|
|
37
|
+
return false;
|
|
38
|
+
if (value.includes('\\'))
|
|
39
|
+
return false;
|
|
40
|
+
if (path.posix.isAbsolute(value))
|
|
41
|
+
return false;
|
|
42
|
+
if (/^[A-Za-z]:\//.test(value))
|
|
43
|
+
return false;
|
|
44
|
+
if (value === '..' || value.startsWith('../') || value.includes('/../') || value.endsWith('/..'))
|
|
45
|
+
return false;
|
|
46
|
+
if (value.startsWith('~'))
|
|
47
|
+
return false;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
export function resolveInsideWorkspace(root, relative) {
|
|
51
|
+
const absoluteRoot = safeRealpath(path.resolve(root));
|
|
52
|
+
const absolute = path.resolve(absoluteRoot, relative);
|
|
53
|
+
if (!fs.existsSync(absolute))
|
|
54
|
+
return null;
|
|
55
|
+
const real = safeRealpath(absolute);
|
|
56
|
+
const rel = path.relative(absoluteRoot, real);
|
|
57
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
58
|
+
throw new ContextGraphPathError('symlink_escape', `symlink leaves the workspace: ${toPosix(relative)}`);
|
|
59
|
+
}
|
|
60
|
+
return real;
|
|
61
|
+
}
|
|
62
|
+
export function isSymlinkEscape(root, relative) {
|
|
63
|
+
try {
|
|
64
|
+
resolveInsideWorkspace(root, relative);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return error instanceof ContextGraphPathError && error.code === 'symlink_escape';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function safeRealpath(target) {
|
|
72
|
+
try {
|
|
73
|
+
return fs.realpathSync(target);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return target;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function contextGraphDir(root) {
|
|
80
|
+
return path.join(root, ...CONTEXT_GRAPH_DIR_SEGMENTS);
|
|
81
|
+
}
|
|
82
|
+
export function contextGraphSnapshotPath(root) {
|
|
83
|
+
return path.join(contextGraphDir(root), 'context-graph.json');
|
|
84
|
+
}
|
|
85
|
+
export function contextGraphMetaPath(root) {
|
|
86
|
+
return path.join(contextGraphDir(root), 'context-graph.meta.json');
|
|
87
|
+
}
|
|
88
|
+
export function contextGraphPrevSnapshotPath(root) {
|
|
89
|
+
return path.join(contextGraphDir(root), 'context-graph.prev.json');
|
|
90
|
+
}
|
|
91
|
+
export function contextGraphEventLogPath(root) {
|
|
92
|
+
return path.join(contextGraphDir(root), 'context-graph-events.jsonl');
|
|
93
|
+
}
|
|
94
|
+
export function contextGraphFragmentCacheDir(root) {
|
|
95
|
+
return path.join(root, '.sneakoscope', 'cache', 'context-graph', 'fragments');
|
|
96
|
+
}
|
|
97
|
+
export function contextGraphLockPath(root) {
|
|
98
|
+
return path.join(root, '.sneakoscope', 'cache', 'context-graph', 'compile.lock');
|
|
99
|
+
}
|
|
100
|
+
export function contextGraphBenchmarkReportPath(root) {
|
|
101
|
+
return path.join(root, '.sneakoscope', 'reports', 'context-graph-benchmark.json');
|
|
102
|
+
}
|
|
103
|
+
export function contextGraphExperimentLogPath(root) {
|
|
104
|
+
return path.join(root, '.sneakoscope', 'reports', 'context-graph-experiments.jsonl');
|
|
105
|
+
}
|
|
106
|
+
export function contextPackPath(root) {
|
|
107
|
+
return path.join(contextGraphDir(root), 'context-pack.json');
|
|
108
|
+
}
|
|
109
|
+
export function contextGraphArtifactPaths(root) {
|
|
110
|
+
return [
|
|
111
|
+
contextGraphSnapshotPath(root),
|
|
112
|
+
contextGraphMetaPath(root),
|
|
113
|
+
contextGraphPrevSnapshotPath(root),
|
|
114
|
+
contextGraphEventLogPath(root)
|
|
115
|
+
];
|
|
116
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const CONTEXT_GRAPH_QUERY_PROFILE_NAMES = ['implementation', 'review', 'planning', 'answer'];
|
|
2
|
+
export const CONTEXT_GRAPH_TRAVERSAL_CAPS = {
|
|
3
|
+
maxVisitedNodes: 4000,
|
|
4
|
+
maxVisitedEdges: 20000,
|
|
5
|
+
maxSeeds: 24,
|
|
6
|
+
maxSelectedNodes: 64,
|
|
7
|
+
defaultTokenBudget: 6000,
|
|
8
|
+
queryTimeoutMs: 1500
|
|
9
|
+
};
|
|
10
|
+
function weights(entries) {
|
|
11
|
+
const edges = [];
|
|
12
|
+
const edgeWeights = {};
|
|
13
|
+
for (const [edge, weight] of entries) {
|
|
14
|
+
edges.push(edge);
|
|
15
|
+
edgeWeights[edge] = weight;
|
|
16
|
+
}
|
|
17
|
+
return { edges, edgeWeights };
|
|
18
|
+
}
|
|
19
|
+
const IMPLEMENTATION = weights([
|
|
20
|
+
['defines', 3.0],
|
|
21
|
+
['contains', 2.2],
|
|
22
|
+
['imports', 2.0],
|
|
23
|
+
['routes_to', 2.0],
|
|
24
|
+
['reexports', 1.8],
|
|
25
|
+
['references', 1.6],
|
|
26
|
+
['calls', 1.6],
|
|
27
|
+
['tests', 1.4],
|
|
28
|
+
['verified_by', 1.2]
|
|
29
|
+
]);
|
|
30
|
+
const REVIEW = weights([
|
|
31
|
+
['affected_by', 3.0],
|
|
32
|
+
['tests', 2.4],
|
|
33
|
+
['gated_by', 2.4],
|
|
34
|
+
['verified_by', 2.0],
|
|
35
|
+
['invalidates', 1.8],
|
|
36
|
+
['conflicts_with', 1.6],
|
|
37
|
+
['routes_to', 1.4],
|
|
38
|
+
['contains', 1.2],
|
|
39
|
+
['defines', 1.0]
|
|
40
|
+
]);
|
|
41
|
+
const PLANNING = weights([
|
|
42
|
+
['depends_on', 2.8],
|
|
43
|
+
['owns', 2.2],
|
|
44
|
+
['affected_by', 2.0],
|
|
45
|
+
['conflicts_with', 1.8],
|
|
46
|
+
['routes_to', 1.6],
|
|
47
|
+
['cochanged_with', 1.4],
|
|
48
|
+
['gated_by', 1.4],
|
|
49
|
+
['contains', 1.0],
|
|
50
|
+
['imports', 1.0]
|
|
51
|
+
]);
|
|
52
|
+
const ANSWER = weights([
|
|
53
|
+
['derived_from', 2.8],
|
|
54
|
+
['cites', 2.6],
|
|
55
|
+
['supports', 2.0],
|
|
56
|
+
['contradicts', 1.8],
|
|
57
|
+
['supersedes', 1.6],
|
|
58
|
+
['contains', 0.8]
|
|
59
|
+
]);
|
|
60
|
+
export const CONTEXT_GRAPH_QUERY_PROFILES = {
|
|
61
|
+
implementation: { name: 'implementation', ...IMPLEMENTATION, maxDepth: 2, maxDepthHighRisk: 3 },
|
|
62
|
+
review: { name: 'review', ...REVIEW, maxDepth: 2, maxDepthHighRisk: 3 },
|
|
63
|
+
planning: { name: 'planning', ...PLANNING, maxDepth: 2, maxDepthHighRisk: 3 },
|
|
64
|
+
answer: { name: 'answer', ...ANSWER, maxDepth: 2, maxDepthHighRisk: 3 }
|
|
65
|
+
};
|
|
66
|
+
export const DEFAULT_CONTEXT_GRAPH_QUERY_PROFILE = 'implementation';
|
|
67
|
+
export function isContextGraphQueryProfileName(value) {
|
|
68
|
+
return typeof value === 'string' && CONTEXT_GRAPH_QUERY_PROFILE_NAMES.includes(value);
|
|
69
|
+
}
|
|
70
|
+
export function contextGraphQueryProfile(name) {
|
|
71
|
+
return CONTEXT_GRAPH_QUERY_PROFILES[isContextGraphQueryProfileName(name) ? name : DEFAULT_CONTEXT_GRAPH_QUERY_PROFILE];
|
|
72
|
+
}
|
|
73
|
+
export function profileEdgeWeight(profile, edge) {
|
|
74
|
+
return profile.edgeWeights[edge] ?? 0;
|
|
75
|
+
}
|
|
76
|
+
export function profileTraversesEdge(profile, edge) {
|
|
77
|
+
return profileEdgeWeight(profile, edge) > 0;
|
|
78
|
+
}
|