@rkarim08/sia 1.0.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/.claude-plugin/marketplace.json +35 -0
- package/.claude-plugin/plugin.json +27 -0
- package/.mcp.json +13 -0
- package/CLAUDE.md +226 -0
- package/LICENSE +202 -0
- package/PLUGIN_README.md +253 -0
- package/README.md +1013 -0
- package/agents/sia-changelog-writer.md +89 -0
- package/agents/sia-code-reviewer.md +86 -0
- package/agents/sia-conflict-resolver.md +100 -0
- package/agents/sia-convention-enforcer.md +69 -0
- package/agents/sia-debug.md +106 -0
- package/agents/sia-decision-reviewer.md +101 -0
- package/agents/sia-dependency-tracker.md +80 -0
- package/agents/sia-explain.md +126 -0
- package/agents/sia-feature.md +116 -0
- package/agents/sia-knowledge-capture.md +117 -0
- package/agents/sia-lead-architecture-advisor.md +93 -0
- package/agents/sia-lead-team-health.md +107 -0
- package/agents/sia-migration.md +100 -0
- package/agents/sia-onboarding.md +115 -0
- package/agents/sia-orientation.md +99 -0
- package/agents/sia-pm-briefing.md +106 -0
- package/agents/sia-pm-risk-advisor.md +82 -0
- package/agents/sia-qa-analyst.md +116 -0
- package/agents/sia-qa-regression-map.md +94 -0
- package/agents/sia-refactor.md +115 -0
- package/agents/sia-regression.md +112 -0
- package/agents/sia-security-audit.md +125 -0
- package/agents/sia-test-advisor.md +91 -0
- package/hooks/hooks.json +98 -0
- package/migrations/bridge/001_initial.sql +34 -0
- package/migrations/episodic/001_initial.sql +35 -0
- package/migrations/meta/001_initial.sql +68 -0
- package/migrations/semantic/001_initial.sql +292 -0
- package/migrations/semantic/002_ontology.sql +89 -0
- package/migrations/semantic/003_freshness.sql +63 -0
- package/migrations/semantic/004_v5_unified_schema.sql +194 -0
- package/migrations/semantic/005_backfill_event_kinds.sql +8 -0
- package/migrations/semantic/006_tree_sitter.sql +6 -0
- package/migrations/semantic/007_branch_snapshots.sql +22 -0
- package/package.json +110 -0
- package/scripts/branch-switch.sh +13 -0
- package/scripts/build-wasm-grammars.sh +81 -0
- package/scripts/post-compact.sh +8 -0
- package/scripts/post-tool-use.sh +10 -0
- package/scripts/pre-compact.sh +8 -0
- package/scripts/session-end.sh +8 -0
- package/scripts/session-start.sh +8 -0
- package/scripts/start-mcp.ts +45 -0
- package/scripts/stop-hook.sh +8 -0
- package/scripts/user-prompt-submit.sh +8 -0
- package/scripts/viz-server.ts +152 -0
- package/skills/sia-brainstorm/SKILL.md +156 -0
- package/skills/sia-brainstorm/scripts/frame-template.html +214 -0
- package/skills/sia-brainstorm/scripts/helper.js +95 -0
- package/skills/sia-brainstorm/scripts/server.cjs +338 -0
- package/skills/sia-brainstorm/scripts/start-server.sh +153 -0
- package/skills/sia-brainstorm/scripts/stop-server.sh +55 -0
- package/skills/sia-brainstorm/spec-document-reviewer-prompt.md +49 -0
- package/skills/sia-brainstorm/visual-companion.md +286 -0
- package/skills/sia-capture/SKILL.md +64 -0
- package/skills/sia-compare/SKILL.md +33 -0
- package/skills/sia-conflicts/SKILL.md +38 -0
- package/skills/sia-debug-workflow/SKILL.md +120 -0
- package/skills/sia-debug-workflow/root-cause-tracing.md +70 -0
- package/skills/sia-debug-workflow/scripts/find-polluter.sh +64 -0
- package/skills/sia-debug-workflow/temporal-investigation.md +72 -0
- package/skills/sia-digest/SKILL.md +23 -0
- package/skills/sia-dispatch/SKILL.md +69 -0
- package/skills/sia-dispatch/agent-task-template.md +99 -0
- package/skills/sia-doctor/SKILL.md +39 -0
- package/skills/sia-execute/SKILL.md +70 -0
- package/skills/sia-execute-plan/SKILL.md +85 -0
- package/skills/sia-export-import/SKILL.md +49 -0
- package/skills/sia-export-knowledge/SKILL.md +46 -0
- package/skills/sia-finish/SKILL.md +100 -0
- package/skills/sia-finish/pr-summary-template.md +54 -0
- package/skills/sia-freshness/SKILL.md +38 -0
- package/skills/sia-history/SKILL.md +42 -0
- package/skills/sia-impact/SKILL.md +70 -0
- package/skills/sia-index/SKILL.md +54 -0
- package/skills/sia-install/SKILL.md +39 -0
- package/skills/sia-lead-compliance/SKILL.md +16 -0
- package/skills/sia-lead-drift-report/SKILL.md +16 -0
- package/skills/sia-lead-knowledge-map/SKILL.md +16 -0
- package/skills/sia-learn/SKILL.md +58 -0
- package/skills/sia-plan/SKILL.md +68 -0
- package/skills/sia-plan/plan-reviewer-prompt.md +63 -0
- package/skills/sia-playbooks/SKILL.md +29 -0
- package/skills/sia-playbooks/reference-feature.md +100 -0
- package/skills/sia-playbooks/reference-flagging.md +50 -0
- package/skills/sia-playbooks/reference-orientation.md +92 -0
- package/skills/sia-playbooks/reference-regression.md +115 -0
- package/skills/sia-playbooks/reference-review.md +64 -0
- package/skills/sia-playbooks/reference-tools.md +239 -0
- package/skills/sia-pm-decision-log/SKILL.md +28 -0
- package/skills/sia-pm-risk-dashboard/SKILL.md +24 -0
- package/skills/sia-pm-sprint-summary/SKILL.md +27 -0
- package/skills/sia-prune/SKILL.md +45 -0
- package/skills/sia-qa-coverage/SKILL.md +28 -0
- package/skills/sia-qa-flaky/SKILL.md +20 -0
- package/skills/sia-qa-report/SKILL.md +26 -0
- package/skills/sia-reindex/SKILL.md +30 -0
- package/skills/sia-review-respond/SKILL.md +88 -0
- package/skills/sia-review-respond/pushback-patterns.md +90 -0
- package/skills/sia-search/SKILL.md +47 -0
- package/skills/sia-setup/SKILL.md +82 -0
- package/skills/sia-setup/setup-checklist.md +97 -0
- package/skills/sia-stats/SKILL.md +36 -0
- package/skills/sia-status/SKILL.md +44 -0
- package/skills/sia-sync/SKILL.md +46 -0
- package/skills/sia-team/SKILL.md +64 -0
- package/skills/sia-test/SKILL.md +92 -0
- package/skills/sia-test/testing-anti-patterns.md +104 -0
- package/skills/sia-tour/SKILL.md +29 -0
- package/skills/sia-upgrade/SKILL.md +43 -0
- package/skills/sia-verify/SKILL.md +81 -0
- package/skills/sia-visualize/SKILL.md +28 -0
- package/skills/sia-visualize-live/SKILL.md +55 -0
- package/skills/sia-visualize-live/scripts/graph-template.html +389 -0
- package/skills/sia-visualize-live/scripts/start-visualizer.sh +161 -0
- package/skills/sia-visualize-live/scripts/stop-visualizer.sh +55 -0
- package/skills/sia-visualize-live/scripts/visualizer-server.cjs +264 -0
- package/skills/sia-workspace/SKILL.md +57 -0
- package/src/agent/claude-md-template-flagging.md +219 -0
- package/src/agent/claude-md-template.md +213 -0
- package/src/agent/modules/sia-feature.md +100 -0
- package/src/agent/modules/sia-flagging.md +50 -0
- package/src/agent/modules/sia-orientation.md +92 -0
- package/src/agent/modules/sia-regression.md +115 -0
- package/src/agent/modules/sia-review.md +64 -0
- package/src/agent/modules/sia-tools.md +239 -0
- package/src/ast/extractors/c-include.ts +189 -0
- package/src/ast/extractors/csharp-project.ts +260 -0
- package/src/ast/extractors/prisma-schema.ts +44 -0
- package/src/ast/extractors/project-manifest.ts +111 -0
- package/src/ast/extractors/sql-schema.ts +67 -0
- package/src/ast/extractors/tier-a.ts +423 -0
- package/src/ast/extractors/tier-b.ts +289 -0
- package/src/ast/extractors/tier-dispatch.ts +247 -0
- package/src/ast/index-worker.ts +108 -0
- package/src/ast/indexer.ts +484 -0
- package/src/ast/languages.ts +408 -0
- package/src/ast/pagerank-builder.ts +125 -0
- package/src/ast/path-utils.ts +137 -0
- package/src/ast/tree-sitter/backends/native.ts +57 -0
- package/src/ast/tree-sitter/backends/wasm.ts +39 -0
- package/src/ast/tree-sitter/call-walker.ts +44 -0
- package/src/ast/tree-sitter/edit-computer.ts +55 -0
- package/src/ast/tree-sitter/query-runner.ts +46 -0
- package/src/ast/tree-sitter/service.ts +174 -0
- package/src/ast/tree-sitter/tree-cache.ts +39 -0
- package/src/ast/tree-sitter/types.ts +79 -0
- package/src/ast/watcher.ts +322 -0
- package/src/capture/chunker.ts +169 -0
- package/src/capture/consolidate.ts +127 -0
- package/src/capture/edge-inferrer.ts +161 -0
- package/src/capture/embedder.ts +166 -0
- package/src/capture/embedding-cache.ts +73 -0
- package/src/capture/flag-processor.ts +64 -0
- package/src/capture/hook.ts +67 -0
- package/src/capture/pipeline.ts +450 -0
- package/src/capture/prompts/consolidate.ts +25 -0
- package/src/capture/prompts/edge-infer.ts +29 -0
- package/src/capture/prompts/extract-flagged.ts +36 -0
- package/src/capture/prompts/extract.ts +42 -0
- package/src/capture/tokenizer.ts +147 -0
- package/src/capture/track-a-ast.ts +93 -0
- package/src/capture/track-b-llm.ts +149 -0
- package/src/capture/types.ts +64 -0
- package/src/cli/commands/community.ts +137 -0
- package/src/cli/commands/compare.ts +123 -0
- package/src/cli/commands/conflicts.ts +41 -0
- package/src/cli/commands/digest.ts +197 -0
- package/src/cli/commands/disable-flagging.ts +34 -0
- package/src/cli/commands/doctor.ts +240 -0
- package/src/cli/commands/download-model.ts +161 -0
- package/src/cli/commands/enable-flagging.ts +34 -0
- package/src/cli/commands/export-knowledge.ts +208 -0
- package/src/cli/commands/export.ts +85 -0
- package/src/cli/commands/freshness.ts +164 -0
- package/src/cli/commands/graph.ts +51 -0
- package/src/cli/commands/history.ts +139 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/install.ts +156 -0
- package/src/cli/commands/lead-report.ts +241 -0
- package/src/cli/commands/learn.ts +321 -0
- package/src/cli/commands/pm-report.ts +413 -0
- package/src/cli/commands/prune.ts +75 -0
- package/src/cli/commands/qa-report.ts +278 -0
- package/src/cli/commands/reindex.ts +104 -0
- package/src/cli/commands/rollback.ts +70 -0
- package/src/cli/commands/search.ts +103 -0
- package/src/cli/commands/server.ts +91 -0
- package/src/cli/commands/share.ts +33 -0
- package/src/cli/commands/stats.ts +79 -0
- package/src/cli/commands/status.ts +176 -0
- package/src/cli/commands/sync.ts +96 -0
- package/src/cli/commands/team.ts +118 -0
- package/src/cli/commands/tour.ts +157 -0
- package/src/cli/commands/visualize-live.ts +162 -0
- package/src/cli/commands/workspace.ts +117 -0
- package/src/cli/index.ts +424 -0
- package/src/cli/learn-progress.ts +87 -0
- package/src/community/detection-bridge.ts +344 -0
- package/src/community/leiden.ts +462 -0
- package/src/community/raptor.ts +210 -0
- package/src/community/scheduler.ts +74 -0
- package/src/community/summarize.ts +115 -0
- package/src/decay/archiver.ts +73 -0
- package/src/decay/bridge-orphan-cleanup.ts +212 -0
- package/src/decay/consolidation-sweep.ts +112 -0
- package/src/decay/decay.ts +116 -0
- package/src/decay/deep-validator.ts +62 -0
- package/src/decay/episodic-promoter.ts +132 -0
- package/src/decay/maintenance-scheduler.ts +326 -0
- package/src/decay/scheduler.ts +6 -0
- package/src/decay/session-sweeper.ts +79 -0
- package/src/decay/types.ts +17 -0
- package/src/freshness/confidence-decay.ts +122 -0
- package/src/freshness/cuckoo-filter.ts +176 -0
- package/src/freshness/deep-validation.ts +345 -0
- package/src/freshness/dirty-tracker.ts +237 -0
- package/src/freshness/file-watcher-layer.ts +119 -0
- package/src/freshness/firewall.ts +64 -0
- package/src/freshness/git-reconcile-layer.ts +161 -0
- package/src/freshness/inverted-index.ts +158 -0
- package/src/freshness/stale-read-layer.ts +222 -0
- package/src/graph/audit.ts +69 -0
- package/src/graph/bridge-db.ts +141 -0
- package/src/graph/communities.ts +195 -0
- package/src/graph/db-interface.ts +259 -0
- package/src/graph/edges.ts +163 -0
- package/src/graph/entities.ts +327 -0
- package/src/graph/episodic-db.ts +113 -0
- package/src/graph/flags.ts +31 -0
- package/src/graph/meta-db.ts +200 -0
- package/src/graph/semantic-db.ts +101 -0
- package/src/graph/session-resume.ts +56 -0
- package/src/graph/snapshots.ts +342 -0
- package/src/graph/staging.ts +151 -0
- package/src/graph/types.ts +128 -0
- package/src/hooks/adapters/claude-code.ts +21 -0
- package/src/hooks/adapters/cline.ts +43 -0
- package/src/hooks/adapters/cursor.ts +65 -0
- package/src/hooks/adapters/generic.ts +12 -0
- package/src/hooks/agent-detect.ts +34 -0
- package/src/hooks/claude-md-directives.ts +32 -0
- package/src/hooks/event-router.ts +182 -0
- package/src/hooks/extractors/pattern-detector.ts +111 -0
- package/src/hooks/handlers/post-compact.ts +30 -0
- package/src/hooks/handlers/post-tool-use.ts +403 -0
- package/src/hooks/handlers/pre-compact.ts +100 -0
- package/src/hooks/handlers/session-end.ts +47 -0
- package/src/hooks/handlers/session-start.ts +154 -0
- package/src/hooks/handlers/stop.ts +128 -0
- package/src/hooks/handlers/user-prompt-submit.ts +68 -0
- package/src/hooks/plugin-branch-switch.ts +68 -0
- package/src/hooks/plugin-common.ts +47 -0
- package/src/hooks/plugin-post-compact.ts +28 -0
- package/src/hooks/plugin-post-tool-use.ts +38 -0
- package/src/hooks/plugin-pre-compact.ts +37 -0
- package/src/hooks/plugin-session-end.ts +37 -0
- package/src/hooks/plugin-session-start.ts +75 -0
- package/src/hooks/plugin-stop.ts +61 -0
- package/src/hooks/plugin-user-prompt-submit.ts +47 -0
- package/src/hooks/types.ts +43 -0
- package/src/knowledge/discovery.ts +238 -0
- package/src/knowledge/external-refs.ts +98 -0
- package/src/knowledge/freshness.ts +221 -0
- package/src/knowledge/ingest.ts +330 -0
- package/src/knowledge/markdown-export.ts +229 -0
- package/src/knowledge/markdown-import.ts +359 -0
- package/src/knowledge/patterns.ts +74 -0
- package/src/knowledge/templates.ts +307 -0
- package/src/llm/ai-sdk-adapter.ts +46 -0
- package/src/llm/config.ts +88 -0
- package/src/llm/cost-tracker.ts +110 -0
- package/src/llm/prompts/extraction.ts +55 -0
- package/src/llm/prompts/summarization.ts +36 -0
- package/src/llm/prompts/validation.ts +37 -0
- package/src/llm/provider-registry.ts +68 -0
- package/src/llm/reliability.ts +179 -0
- package/src/llm/schemas.ts +52 -0
- package/src/mcp/freshness-annotator.ts +69 -0
- package/src/mcp/server.ts +949 -0
- package/src/mcp/tools/sia-ast-query.ts +225 -0
- package/src/mcp/tools/sia-at-time.ts +151 -0
- package/src/mcp/tools/sia-backlinks.ts +87 -0
- package/src/mcp/tools/sia-batch-execute.ts +169 -0
- package/src/mcp/tools/sia-by-file.ts +89 -0
- package/src/mcp/tools/sia-community.ts +113 -0
- package/src/mcp/tools/sia-doctor.ts +73 -0
- package/src/mcp/tools/sia-execute-file.ts +122 -0
- package/src/mcp/tools/sia-execute.ts +104 -0
- package/src/mcp/tools/sia-expand.ts +158 -0
- package/src/mcp/tools/sia-fetch-and-index.ts +241 -0
- package/src/mcp/tools/sia-flag.ts +65 -0
- package/src/mcp/tools/sia-index.ts +111 -0
- package/src/mcp/tools/sia-note.ts +134 -0
- package/src/mcp/tools/sia-search.ts +105 -0
- package/src/mcp/tools/sia-stats.ts +63 -0
- package/src/mcp/tools/sia-sync-status.ts +44 -0
- package/src/mcp/tools/sia-upgrade.ts +247 -0
- package/src/mcp/truncate.ts +231 -0
- package/src/native/bridge.ts +167 -0
- package/src/native/fallback-ast-diff.ts +144 -0
- package/src/native/fallback-graph.ts +325 -0
- package/src/ontology/constraints.ts +56 -0
- package/src/ontology/errors.ts +8 -0
- package/src/ontology/middleware.ts +266 -0
- package/src/retrieval/bm25-search.ts +151 -0
- package/src/retrieval/context-assembly.ts +76 -0
- package/src/retrieval/graph-traversal.ts +168 -0
- package/src/retrieval/pagerank.ts +40 -0
- package/src/retrieval/query-classifier.ts +106 -0
- package/src/retrieval/reranker.ts +156 -0
- package/src/retrieval/search.ts +236 -0
- package/src/retrieval/throttle.ts +102 -0
- package/src/retrieval/vector-search.ts +203 -0
- package/src/retrieval/workspace-search.ts +130 -0
- package/src/sandbox/context-mode.ts +285 -0
- package/src/sandbox/credential-pass.ts +55 -0
- package/src/sandbox/executor.ts +235 -0
- package/src/security/pattern-detector.ts +127 -0
- package/src/security/rule-of-two.ts +50 -0
- package/src/security/sanitize.ts +46 -0
- package/src/security/semantic-consistency.ts +93 -0
- package/src/security/staging-promoter.ts +154 -0
- package/src/shared/config.ts +302 -0
- package/src/shared/diagnostics.ts +210 -0
- package/src/shared/errors.ts +48 -0
- package/src/shared/git-utils.ts +143 -0
- package/src/shared/llm-client.ts +120 -0
- package/src/shared/logger.ts +99 -0
- package/src/shared/types.ts +79 -0
- package/src/sync/client.ts +43 -0
- package/src/sync/conflict.ts +106 -0
- package/src/sync/dedup.ts +183 -0
- package/src/sync/hlc.ts +117 -0
- package/src/sync/keychain.ts +144 -0
- package/src/sync/pull.ts +232 -0
- package/src/sync/push.ts +131 -0
- package/src/types/chokidar.d.ts +23 -0
- package/src/visualization/graph-renderer.ts +312 -0
- package/src/visualization/subgraph-extract.ts +208 -0
- package/src/visualization/views/community-clusters.ts +246 -0
- package/src/visualization/views/dependency-map.ts +189 -0
- package/src/visualization/views/graph-explorer.ts +364 -0
- package/src/visualization/views/timeline.ts +247 -0
- package/src/workspace/api-contracts.ts +226 -0
- package/src/workspace/cross-repo.ts +61 -0
- package/src/workspace/detector.ts +190 -0
- package/src/workspace/manifest.ts +141 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// Module: graph-renderer — Generate self-contained HTML visualization with D3.js
|
|
2
|
+
|
|
3
|
+
import type { SubgraphData } from "@/visualization/subgraph-extract";
|
|
4
|
+
|
|
5
|
+
/** Color palette by entity type category. */
|
|
6
|
+
const TYPE_COLORS: Record<string, string> = {
|
|
7
|
+
FileNode: "#4A90D9",
|
|
8
|
+
CodeEntity: "#4A90D9",
|
|
9
|
+
PackageNode: "#4A90D9",
|
|
10
|
+
Decision: "#5DB85D",
|
|
11
|
+
Convention: "#5DB85D",
|
|
12
|
+
Bug: "#5DB85D",
|
|
13
|
+
Solution: "#5DB85D",
|
|
14
|
+
Concept: "#5DB85D",
|
|
15
|
+
Community: "#9B59B6",
|
|
16
|
+
ContentChunk: "#E67E22",
|
|
17
|
+
Dependency: "#95A5A6",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const CATEGORY_LABELS: Array<{ label: string; color: string; types: string[] }> = [
|
|
21
|
+
{ label: "Structural", color: "#4A90D9", types: ["FileNode", "CodeEntity", "PackageNode"] },
|
|
22
|
+
{
|
|
23
|
+
label: "Semantic",
|
|
24
|
+
color: "#5DB85D",
|
|
25
|
+
types: ["Decision", "Convention", "Bug", "Solution", "Concept"],
|
|
26
|
+
},
|
|
27
|
+
{ label: "Community", color: "#9B59B6", types: ["Community"] },
|
|
28
|
+
{ label: "Content", color: "#E67E22", types: ["ContentChunk"] },
|
|
29
|
+
{ label: "Other", color: "#95A5A6", types: ["Dependency"] },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const DEFAULT_COLOR = "#95A5A6";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a self-contained HTML file with D3.js force-directed graph visualization.
|
|
36
|
+
* All CSS is inlined. D3 v7 is loaded from CDN.
|
|
37
|
+
*/
|
|
38
|
+
export function renderGraphHtml(data: SubgraphData, title?: string): string {
|
|
39
|
+
const pageTitle = title ?? "Sia Knowledge Graph";
|
|
40
|
+
const nodesJson = JSON.stringify(data.nodes);
|
|
41
|
+
const edgesJson = JSON.stringify(data.edges);
|
|
42
|
+
const typeColorsJson = JSON.stringify(TYPE_COLORS);
|
|
43
|
+
const defaultColor = DEFAULT_COLOR;
|
|
44
|
+
|
|
45
|
+
// Build legend HTML
|
|
46
|
+
const legendHtml = CATEGORY_LABELS.map(
|
|
47
|
+
(cat) =>
|
|
48
|
+
`<div class="legend-item">
|
|
49
|
+
<span class="legend-dot" style="background:${cat.color}"></span>
|
|
50
|
+
<span class="legend-label">${cat.label} (${cat.types.join(", ")})</span>
|
|
51
|
+
</div>`,
|
|
52
|
+
).join("\n");
|
|
53
|
+
|
|
54
|
+
// Build type checkboxes for filter panel
|
|
55
|
+
const allTypes = [...new Set(data.nodes.map((n) => n.type))].sort();
|
|
56
|
+
const filterCheckboxes = allTypes
|
|
57
|
+
.map(
|
|
58
|
+
(t) =>
|
|
59
|
+
`<label class="filter-checkbox">
|
|
60
|
+
<input type="checkbox" value="${t}" checked onchange="applyFilters()"> ${t}
|
|
61
|
+
</label>`,
|
|
62
|
+
)
|
|
63
|
+
.join("\n");
|
|
64
|
+
|
|
65
|
+
return `<!DOCTYPE html>
|
|
66
|
+
<html lang="en">
|
|
67
|
+
<head>
|
|
68
|
+
<meta charset="UTF-8">
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
70
|
+
<title>${escapeHtml(pageTitle)}</title>
|
|
71
|
+
<style>
|
|
72
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
73
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; overflow: hidden; }
|
|
74
|
+
#graph-container { width: 100vw; height: 100vh; }
|
|
75
|
+
svg { width: 100%; height: 100%; }
|
|
76
|
+
.controls { position: fixed; top: 16px; left: 16px; z-index: 10; display: flex; flex-direction: column; gap: 8px; }
|
|
77
|
+
.search-box { padding: 8px 12px; border-radius: 6px; border: 1px solid #444; background: #2a2a4a; color: #e0e0e0; font-size: 14px; width: 240px; }
|
|
78
|
+
.search-box::placeholder { color: #888; }
|
|
79
|
+
.panel { position: fixed; top: 16px; right: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 16px; max-width: 320px; max-height: 80vh; overflow-y: auto; }
|
|
80
|
+
.panel h2 { font-size: 16px; margin-bottom: 8px; color: #fff; }
|
|
81
|
+
.panel h3 { font-size: 14px; margin-bottom: 4px; color: #ccc; }
|
|
82
|
+
.panel p { font-size: 12px; color: #aaa; margin-bottom: 4px; }
|
|
83
|
+
.panel .node-type { font-size: 11px; padding: 2px 8px; border-radius: 10px; display: inline-block; margin-bottom: 8px; }
|
|
84
|
+
.info-panel { display: none; }
|
|
85
|
+
.info-panel.visible { display: block; }
|
|
86
|
+
.legend { position: fixed; bottom: 16px; left: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 12px 16px; }
|
|
87
|
+
.legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
88
|
+
.legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
|
89
|
+
.legend-label { font-size: 12px; color: #ccc; }
|
|
90
|
+
.filter-panel { position: fixed; bottom: 16px; right: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 12px 16px; }
|
|
91
|
+
.filter-panel h3 { font-size: 13px; margin-bottom: 8px; color: #fff; }
|
|
92
|
+
.filter-checkbox { display: block; font-size: 12px; color: #ccc; margin-bottom: 4px; cursor: pointer; }
|
|
93
|
+
.filter-checkbox input { margin-right: 6px; }
|
|
94
|
+
.title-bar { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 10; font-size: 18px; font-weight: 600; color: #fff; text-shadow: 0 2px 4px rgba(0,0,0,0.5); }
|
|
95
|
+
.stats { font-size: 11px; color: #888; text-align: center; margin-top: 2px; }
|
|
96
|
+
line.edge { stroke-opacity: 0.4; }
|
|
97
|
+
line.edge:hover { stroke-opacity: 0.8; }
|
|
98
|
+
circle.node { cursor: pointer; stroke: #fff; stroke-width: 1.5; }
|
|
99
|
+
circle.node:hover { stroke-width: 3; }
|
|
100
|
+
circle.node.highlighted { stroke: #FFD700; stroke-width: 3; }
|
|
101
|
+
circle.node.dimmed { opacity: 0.15; }
|
|
102
|
+
line.edge.dimmed { opacity: 0.05; }
|
|
103
|
+
text.node-label { font-size: 10px; fill: #ccc; pointer-events: none; text-anchor: middle; }
|
|
104
|
+
text.node-label.dimmed { opacity: 0.1; }
|
|
105
|
+
</style>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div class="title-bar">
|
|
109
|
+
<div>${escapeHtml(pageTitle)}</div>
|
|
110
|
+
<div class="stats" id="stats"></div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="controls">
|
|
113
|
+
<input type="text" class="search-box" id="search" placeholder="Search nodes by name..." oninput="onSearch(this.value)">
|
|
114
|
+
</div>
|
|
115
|
+
<div class="panel info-panel" id="info-panel">
|
|
116
|
+
<h2 id="info-name"></h2>
|
|
117
|
+
<span class="node-type" id="info-type"></span>
|
|
118
|
+
<p id="info-summary"></p>
|
|
119
|
+
<p><strong>Importance:</strong> <span id="info-importance"></span></p>
|
|
120
|
+
<p><strong>Trust Tier:</strong> <span id="info-trust"></span></p>
|
|
121
|
+
<p><strong>ID:</strong> <span id="info-id" style="font-size:10px;word-break:break-all"></span></p>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="legend">
|
|
124
|
+
${legendHtml}
|
|
125
|
+
</div>
|
|
126
|
+
<div class="filter-panel" id="filter-panel">
|
|
127
|
+
<h3>Filter by Type</h3>
|
|
128
|
+
${filterCheckboxes}
|
|
129
|
+
</div>
|
|
130
|
+
<div id="graph-container"></div>
|
|
131
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
132
|
+
<script>
|
|
133
|
+
(function() {
|
|
134
|
+
const rawNodes = ${nodesJson};
|
|
135
|
+
const rawEdges = ${edgesJson};
|
|
136
|
+
const typeColors = ${typeColorsJson};
|
|
137
|
+
const defaultColor = "${defaultColor}";
|
|
138
|
+
|
|
139
|
+
document.getElementById("stats").textContent =
|
|
140
|
+
rawNodes.length + " nodes, " + rawEdges.length + " edges";
|
|
141
|
+
|
|
142
|
+
// Build working copies
|
|
143
|
+
let nodes = rawNodes.map(function(n) { return Object.assign({}, n); });
|
|
144
|
+
let edges = rawEdges.map(function(e) { return Object.assign({}, e, { source: e.from_id, target: e.to_id }); });
|
|
145
|
+
|
|
146
|
+
const nodeById = new Map();
|
|
147
|
+
nodes.forEach(function(n) { nodeById.set(n.id, n); });
|
|
148
|
+
|
|
149
|
+
// Filter edges to only include those whose endpoints exist
|
|
150
|
+
edges = edges.filter(function(e) { return nodeById.has(e.source) && nodeById.has(e.target); });
|
|
151
|
+
|
|
152
|
+
const width = window.innerWidth;
|
|
153
|
+
const height = window.innerHeight;
|
|
154
|
+
|
|
155
|
+
const svg = d3.select("#graph-container")
|
|
156
|
+
.append("svg")
|
|
157
|
+
.attr("width", width)
|
|
158
|
+
.attr("height", height);
|
|
159
|
+
|
|
160
|
+
const g = svg.append("g");
|
|
161
|
+
|
|
162
|
+
// Zoom
|
|
163
|
+
const zoom = d3.zoom()
|
|
164
|
+
.scaleExtent([0.1, 8])
|
|
165
|
+
.on("zoom", function(event) { g.attr("transform", event.transform); });
|
|
166
|
+
svg.call(zoom);
|
|
167
|
+
|
|
168
|
+
// Simulation
|
|
169
|
+
const simulation = d3.forceSimulation(nodes)
|
|
170
|
+
.force("link", d3.forceLink(edges).id(function(d) { return d.id; }).distance(80))
|
|
171
|
+
.force("charge", d3.forceManyBody().strength(-200))
|
|
172
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
173
|
+
.force("collision", d3.forceCollide().radius(function(d) { return nodeRadius(d) + 2; }));
|
|
174
|
+
|
|
175
|
+
function nodeRadius(d) {
|
|
176
|
+
return 5 + (d.importance || 0.5) * 15;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function nodeColor(d) {
|
|
180
|
+
return typeColors[d.type] || defaultColor;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Draw edges
|
|
184
|
+
const edgeElements = g.append("g")
|
|
185
|
+
.selectAll("line")
|
|
186
|
+
.data(edges)
|
|
187
|
+
.join("line")
|
|
188
|
+
.attr("class", "edge")
|
|
189
|
+
.attr("stroke", "#556")
|
|
190
|
+
.attr("stroke-width", function(d) { return Math.max(1, (d.weight || 1) * 2); });
|
|
191
|
+
|
|
192
|
+
// Draw nodes
|
|
193
|
+
const nodeElements = g.append("g")
|
|
194
|
+
.selectAll("circle")
|
|
195
|
+
.data(nodes)
|
|
196
|
+
.join("circle")
|
|
197
|
+
.attr("class", "node")
|
|
198
|
+
.attr("r", nodeRadius)
|
|
199
|
+
.attr("fill", nodeColor)
|
|
200
|
+
.on("click", function(event, d) { showInfo(d); })
|
|
201
|
+
.call(d3.drag()
|
|
202
|
+
.on("start", dragStarted)
|
|
203
|
+
.on("drag", dragged)
|
|
204
|
+
.on("end", dragEnded));
|
|
205
|
+
|
|
206
|
+
// Draw labels
|
|
207
|
+
const labelElements = g.append("g")
|
|
208
|
+
.selectAll("text")
|
|
209
|
+
.data(nodes)
|
|
210
|
+
.join("text")
|
|
211
|
+
.attr("class", "node-label")
|
|
212
|
+
.text(function(d) { return d.name.length > 20 ? d.name.slice(0, 18) + "..." : d.name; })
|
|
213
|
+
.attr("dy", function(d) { return nodeRadius(d) + 12; });
|
|
214
|
+
|
|
215
|
+
simulation.on("tick", function() {
|
|
216
|
+
edgeElements
|
|
217
|
+
.attr("x1", function(d) { return d.source.x; })
|
|
218
|
+
.attr("y1", function(d) { return d.source.y; })
|
|
219
|
+
.attr("x2", function(d) { return d.target.x; })
|
|
220
|
+
.attr("y2", function(d) { return d.target.y; });
|
|
221
|
+
nodeElements
|
|
222
|
+
.attr("cx", function(d) { return d.x; })
|
|
223
|
+
.attr("cy", function(d) { return d.y; });
|
|
224
|
+
labelElements
|
|
225
|
+
.attr("x", function(d) { return d.x; })
|
|
226
|
+
.attr("y", function(d) { return d.y; });
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Drag handlers
|
|
230
|
+
function dragStarted(event, d) {
|
|
231
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
232
|
+
d.fx = d.x;
|
|
233
|
+
d.fy = d.y;
|
|
234
|
+
}
|
|
235
|
+
function dragged(event, d) {
|
|
236
|
+
d.fx = event.x;
|
|
237
|
+
d.fy = event.y;
|
|
238
|
+
}
|
|
239
|
+
function dragEnded(event, d) {
|
|
240
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
241
|
+
d.fx = null;
|
|
242
|
+
d.fy = null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Info panel
|
|
246
|
+
function showInfo(d) {
|
|
247
|
+
var panel = document.getElementById("info-panel");
|
|
248
|
+
panel.classList.add("visible");
|
|
249
|
+
document.getElementById("info-name").textContent = d.name;
|
|
250
|
+
var typeEl = document.getElementById("info-type");
|
|
251
|
+
typeEl.textContent = d.type;
|
|
252
|
+
typeEl.style.background = nodeColor(d);
|
|
253
|
+
typeEl.style.color = "#fff";
|
|
254
|
+
document.getElementById("info-summary").textContent = d.summary || "(no summary)";
|
|
255
|
+
document.getElementById("info-importance").textContent = d.importance.toFixed(2);
|
|
256
|
+
document.getElementById("info-trust").textContent = d.trustTier;
|
|
257
|
+
document.getElementById("info-id").textContent = d.id;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Search
|
|
261
|
+
window.onSearch = function(query) {
|
|
262
|
+
var q = query.toLowerCase().trim();
|
|
263
|
+
if (!q) {
|
|
264
|
+
nodeElements.classed("highlighted", false).classed("dimmed", false);
|
|
265
|
+
edgeElements.classed("dimmed", false);
|
|
266
|
+
labelElements.classed("dimmed", false);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
var matchIds = new Set();
|
|
270
|
+
nodes.forEach(function(n) {
|
|
271
|
+
if (n.name.toLowerCase().includes(q)) matchIds.add(n.id);
|
|
272
|
+
});
|
|
273
|
+
nodeElements.classed("highlighted", function(d) { return matchIds.has(d.id); });
|
|
274
|
+
nodeElements.classed("dimmed", function(d) { return !matchIds.has(d.id); });
|
|
275
|
+
edgeElements.classed("dimmed", function(d) {
|
|
276
|
+
var sid = typeof d.source === "object" ? d.source.id : d.source;
|
|
277
|
+
var tid = typeof d.target === "object" ? d.target.id : d.target;
|
|
278
|
+
return !matchIds.has(sid) && !matchIds.has(tid);
|
|
279
|
+
});
|
|
280
|
+
labelElements.classed("dimmed", function(d) { return !matchIds.has(d.id); });
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Filter by type
|
|
284
|
+
window.applyFilters = function() {
|
|
285
|
+
var checkboxes = document.querySelectorAll(".filter-checkbox input");
|
|
286
|
+
var activeTypes = new Set();
|
|
287
|
+
checkboxes.forEach(function(cb) { if (cb.checked) activeTypes.add(cb.value); });
|
|
288
|
+
|
|
289
|
+
nodeElements.style("display", function(d) { return activeTypes.has(d.type) ? null : "none"; });
|
|
290
|
+
labelElements.style("display", function(d) { return activeTypes.has(d.type) ? null : "none"; });
|
|
291
|
+
edgeElements.style("display", function(d) {
|
|
292
|
+
var sid = typeof d.source === "object" ? d.source.id : d.source;
|
|
293
|
+
var tid = typeof d.target === "object" ? d.target.id : d.target;
|
|
294
|
+
var sn = nodeById.get(sid);
|
|
295
|
+
var tn = nodeById.get(tid);
|
|
296
|
+
return (sn && activeTypes.has(sn.type) && tn && activeTypes.has(tn.type)) ? null : "none";
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
})();
|
|
300
|
+
</script>
|
|
301
|
+
</body>
|
|
302
|
+
</html>`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Escape HTML special characters. */
|
|
306
|
+
function escapeHtml(s: string): string {
|
|
307
|
+
return s
|
|
308
|
+
.replace(/&/g, "&")
|
|
309
|
+
.replace(/</g, "<")
|
|
310
|
+
.replace(/>/g, ">")
|
|
311
|
+
.replace(/"/g, """);
|
|
312
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Module: subgraph-extract — Extract relevant subgraph for visualization
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
|
|
5
|
+
export interface VisNode {
|
|
6
|
+
id: string;
|
|
7
|
+
type: string;
|
|
8
|
+
name: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
importance: number;
|
|
11
|
+
trustTier: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface VisEdge {
|
|
15
|
+
id: string;
|
|
16
|
+
from_id: string;
|
|
17
|
+
to_id: string;
|
|
18
|
+
type: string;
|
|
19
|
+
weight: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SubgraphData {
|
|
23
|
+
nodes: VisNode[];
|
|
24
|
+
edges: VisEdge[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExtractOpts {
|
|
28
|
+
scope?: string;
|
|
29
|
+
nodeType?: string;
|
|
30
|
+
maxNodes?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_MAX_NODES = 200;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a safe SQL IN clause from an array of hex-UUID strings.
|
|
37
|
+
* UUIDs contain only [0-9a-f-] so they are safe to inline without parameterisation,
|
|
38
|
+
* which avoids SQLite's host-parameter limit (SQLITE_MAX_VARIABLE_NUMBER = 999).
|
|
39
|
+
*/
|
|
40
|
+
function inClause(ids: string[]): string {
|
|
41
|
+
return ids.map((id) => `'${id}'`).join(",");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Map a raw entity row to a VisNode. */
|
|
45
|
+
function toVisNode(row: Record<string, unknown>): VisNode {
|
|
46
|
+
return {
|
|
47
|
+
id: row.id as string,
|
|
48
|
+
type: row.type as string,
|
|
49
|
+
name: row.name as string,
|
|
50
|
+
summary: (row.summary as string) ?? "",
|
|
51
|
+
importance: (row.importance as number) ?? 0.5,
|
|
52
|
+
trustTier: (row.trust_tier as number) ?? 3,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Map a raw edge row to a VisEdge. */
|
|
57
|
+
function toVisEdge(row: Record<string, unknown>): VisEdge {
|
|
58
|
+
return {
|
|
59
|
+
id: row.id as string,
|
|
60
|
+
from_id: row.from_id as string,
|
|
61
|
+
to_id: row.to_id as string,
|
|
62
|
+
type: row.type as string,
|
|
63
|
+
weight: (row.weight as number) ?? 1.0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fetch all active edges where both endpoints are in the given id set.
|
|
69
|
+
*/
|
|
70
|
+
async function edgesBetween(db: SiaDb, ids: string[]): Promise<VisEdge[]> {
|
|
71
|
+
if (ids.length === 0) return [];
|
|
72
|
+
const list = inClause(ids);
|
|
73
|
+
const { rows } = await db.execute(
|
|
74
|
+
`SELECT id, from_id, to_id, type, weight FROM graph_edges
|
|
75
|
+
WHERE from_id IN (${list}) AND to_id IN (${list})
|
|
76
|
+
AND t_valid_until IS NULL`,
|
|
77
|
+
);
|
|
78
|
+
return rows.map(toVisEdge);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get 1-hop neighbor entity IDs for a set of seed IDs (via active edges).
|
|
83
|
+
*/
|
|
84
|
+
async function neighborIds(db: SiaDb, seedIds: string[]): Promise<string[]> {
|
|
85
|
+
if (seedIds.length === 0) return [];
|
|
86
|
+
const list = inClause(seedIds);
|
|
87
|
+
const { rows } = await db.execute(
|
|
88
|
+
`SELECT DISTINCT from_id AS nid FROM graph_edges
|
|
89
|
+
WHERE to_id IN (${list}) AND t_valid_until IS NULL
|
|
90
|
+
UNION
|
|
91
|
+
SELECT DISTINCT to_id AS nid FROM graph_edges
|
|
92
|
+
WHERE from_id IN (${list}) AND t_valid_until IS NULL`,
|
|
93
|
+
);
|
|
94
|
+
return rows.map((r) => r.nid as string);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fetch entity rows by a list of IDs (active only).
|
|
99
|
+
*/
|
|
100
|
+
async function fetchEntitiesById(db: SiaDb, ids: string[]): Promise<VisNode[]> {
|
|
101
|
+
if (ids.length === 0) return [];
|
|
102
|
+
const list = inClause(ids);
|
|
103
|
+
const { rows } = await db.execute(
|
|
104
|
+
`SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
|
|
105
|
+
WHERE id IN (${list})
|
|
106
|
+
AND t_valid_until IS NULL AND archived_at IS NULL`,
|
|
107
|
+
);
|
|
108
|
+
return rows.map(toVisNode);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Default extraction: top N nodes by importance with edges between them.
|
|
113
|
+
*/
|
|
114
|
+
async function extractDefault(db: SiaDb, maxNodes: number): Promise<SubgraphData> {
|
|
115
|
+
const { rows } = await db.execute(
|
|
116
|
+
`SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
|
|
117
|
+
WHERE t_valid_until IS NULL AND archived_at IS NULL
|
|
118
|
+
ORDER BY importance DESC LIMIT ?`,
|
|
119
|
+
[maxNodes],
|
|
120
|
+
);
|
|
121
|
+
const nodes = rows.map(toVisNode);
|
|
122
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
123
|
+
const edges = await edgesBetween(db, nodeIds);
|
|
124
|
+
return { nodes, edges };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Scope extraction: FileNode/CodeEntity under path + 2-hop neighbors.
|
|
129
|
+
*/
|
|
130
|
+
async function extractScoped(db: SiaDb, scope: string, maxNodes: number): Promise<SubgraphData> {
|
|
131
|
+
// Find seed entities whose file_paths contain the scope prefix
|
|
132
|
+
const { rows: seedRows } = await db.execute(
|
|
133
|
+
`SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
|
|
134
|
+
WHERE (type = 'FileNode' OR type = 'CodeEntity')
|
|
135
|
+
AND file_paths LIKE ?
|
|
136
|
+
AND t_valid_until IS NULL AND archived_at IS NULL`,
|
|
137
|
+
[`%${scope}%`],
|
|
138
|
+
);
|
|
139
|
+
const seedNodes = seedRows.map(toVisNode);
|
|
140
|
+
const seedIds = seedNodes.map((n) => n.id);
|
|
141
|
+
|
|
142
|
+
// 1-hop neighbors
|
|
143
|
+
const hop1Ids = await neighborIds(db, seedIds);
|
|
144
|
+
// 2-hop neighbors
|
|
145
|
+
const hop2Ids = await neighborIds(db, hop1Ids);
|
|
146
|
+
|
|
147
|
+
// Combine all unique IDs, prioritising seeds
|
|
148
|
+
const allIds = new Set<string>([...seedIds, ...hop1Ids, ...hop2Ids]);
|
|
149
|
+
|
|
150
|
+
// Fetch entities for non-seed IDs
|
|
151
|
+
const extraIds = [...allIds].filter((id) => !seedIds.includes(id));
|
|
152
|
+
const extraNodes = await fetchEntitiesById(db, extraIds);
|
|
153
|
+
|
|
154
|
+
// Merge and cap
|
|
155
|
+
const allNodes = [...seedNodes, ...extraNodes].slice(0, maxNodes);
|
|
156
|
+
const cappedIds = allNodes.map((n) => n.id);
|
|
157
|
+
const edges = await edgesBetween(db, cappedIds);
|
|
158
|
+
|
|
159
|
+
return { nodes: allNodes, edges };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* NodeType extraction: all nodes of that type + direct (1-hop) neighbors.
|
|
164
|
+
*/
|
|
165
|
+
async function extractByType(db: SiaDb, nodeType: string, maxNodes: number): Promise<SubgraphData> {
|
|
166
|
+
const { rows: typeRows } = await db.execute(
|
|
167
|
+
`SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
|
|
168
|
+
WHERE type = ?
|
|
169
|
+
AND t_valid_until IS NULL AND archived_at IS NULL`,
|
|
170
|
+
[nodeType],
|
|
171
|
+
);
|
|
172
|
+
const typeNodes = typeRows.map(toVisNode);
|
|
173
|
+
const typeIds = typeNodes.map((n) => n.id);
|
|
174
|
+
|
|
175
|
+
// 1-hop neighbors
|
|
176
|
+
const hop1Ids = await neighborIds(db, typeIds);
|
|
177
|
+
const extraIds = hop1Ids.filter((id) => !typeIds.includes(id));
|
|
178
|
+
const extraNodes = await fetchEntitiesById(db, extraIds);
|
|
179
|
+
|
|
180
|
+
// Merge and cap
|
|
181
|
+
const allNodes = [...typeNodes, ...extraNodes].slice(0, maxNodes);
|
|
182
|
+
const cappedIds = allNodes.map((n) => n.id);
|
|
183
|
+
const edges = await edgesBetween(db, cappedIds);
|
|
184
|
+
|
|
185
|
+
return { nodes: allNodes, edges };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Extract a relevant subgraph for visualization.
|
|
190
|
+
*
|
|
191
|
+
* Three modes:
|
|
192
|
+
* - Default (no scope/type): top N nodes by importance + edges between them
|
|
193
|
+
* - With scope: FileNode/CodeEntity under path + 2-hop neighbors, capped at maxNodes
|
|
194
|
+
* - With nodeType: all nodes of that type + direct neighbors, capped at maxNodes
|
|
195
|
+
*/
|
|
196
|
+
export async function extractSubgraph(db: SiaDb, opts?: ExtractOpts): Promise<SubgraphData> {
|
|
197
|
+
const maxNodes = opts?.maxNodes ?? DEFAULT_MAX_NODES;
|
|
198
|
+
|
|
199
|
+
if (opts?.scope) {
|
|
200
|
+
return extractScoped(db, opts.scope, maxNodes);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (opts?.nodeType) {
|
|
204
|
+
return extractByType(db, opts.nodeType, maxNodes);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return extractDefault(db, maxNodes);
|
|
208
|
+
}
|