@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,161 @@
|
|
|
1
|
+
// Module: edge-inferrer — Infer edges between new and existing entities
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
import { insertEdge } from "@/graph/edges";
|
|
5
|
+
import { getActiveEntities, getEntity } from "@/graph/entities";
|
|
6
|
+
|
|
7
|
+
/** Maximum edges created per new entity. */
|
|
8
|
+
const MAX_EDGES_PER_ENTITY = 5;
|
|
9
|
+
|
|
10
|
+
/** Minimum weight threshold for creating an edge. */
|
|
11
|
+
const MIN_WEIGHT = 0.3;
|
|
12
|
+
|
|
13
|
+
/** Type affinity pairs: source type -> target type -> edge label. */
|
|
14
|
+
const TYPE_AFFINITY: Record<string, { target: string; label: string }> = {
|
|
15
|
+
Solution: { target: "Bug", label: "solves" },
|
|
16
|
+
Bug: { target: "Solution", label: "solved_by" },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compute tag overlap weight between two tag sets.
|
|
21
|
+
* Returns |intersection| / |union| (Jaccard similarity on tags).
|
|
22
|
+
*/
|
|
23
|
+
function tagOverlap(tagsA: string[], tagsB: string[]): number {
|
|
24
|
+
const setA = new Set(tagsA);
|
|
25
|
+
const setB = new Set(tagsB);
|
|
26
|
+
|
|
27
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
28
|
+
|
|
29
|
+
let intersectionSize = 0;
|
|
30
|
+
for (const tag of setA) {
|
|
31
|
+
if (setB.has(tag)) intersectionSize++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const unionSize = setA.size + setB.size - intersectionSize;
|
|
35
|
+
if (unionSize === 0) return 0;
|
|
36
|
+
|
|
37
|
+
return intersectionSize / unionSize;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Safely parse a JSON tags string into a string array.
|
|
42
|
+
* Returns empty array on parse failure.
|
|
43
|
+
*/
|
|
44
|
+
function parseTags(raw: string): string[] {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (Array.isArray(parsed)) return parsed;
|
|
48
|
+
return [];
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface EdgeCandidate {
|
|
55
|
+
targetId: string;
|
|
56
|
+
edgeType: string;
|
|
57
|
+
weight: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Infer edges between newly created entities and existing entities in the graph.
|
|
62
|
+
*
|
|
63
|
+
* For each new entity ID:
|
|
64
|
+
* 1. Get the entity via getEntity.
|
|
65
|
+
* 2. If entity has no tags and type is not 'Solution' or 'Bug', skip.
|
|
66
|
+
* 3. Query for related entities by same package_path, matching tags, or type affinity.
|
|
67
|
+
* 4. For type affinity: Solution entities look for Bug entities with overlapping tags -> 'solves' edge.
|
|
68
|
+
* 5. Create edges via insertEdge for matches with weight >= 0.3.
|
|
69
|
+
* 6. Cap at 5 edges per new entity.
|
|
70
|
+
*
|
|
71
|
+
* Returns total edges created.
|
|
72
|
+
*/
|
|
73
|
+
export async function inferEdges(db: SiaDb, newEntityIds: string[]): Promise<number> {
|
|
74
|
+
let totalCreated = 0;
|
|
75
|
+
|
|
76
|
+
for (const entityId of newEntityIds) {
|
|
77
|
+
const entity = await getEntity(db, entityId);
|
|
78
|
+
if (!entity) continue;
|
|
79
|
+
|
|
80
|
+
const tags = parseTags(entity.tags);
|
|
81
|
+
const hasTypeAffinity = entity.type in TYPE_AFFINITY;
|
|
82
|
+
|
|
83
|
+
// Skip entities with no tags and no type affinity
|
|
84
|
+
if (tags.length === 0 && !hasTypeAffinity) continue;
|
|
85
|
+
|
|
86
|
+
// Gather all active entities to find candidates
|
|
87
|
+
const allActive = await getActiveEntities(db);
|
|
88
|
+
|
|
89
|
+
const candidates: EdgeCandidate[] = [];
|
|
90
|
+
|
|
91
|
+
for (const other of allActive) {
|
|
92
|
+
// Skip self-edges
|
|
93
|
+
if (other.id === entityId) continue;
|
|
94
|
+
|
|
95
|
+
let bestWeight = 0;
|
|
96
|
+
let bestEdgeType = "relates_to";
|
|
97
|
+
|
|
98
|
+
// 1. Same package_path
|
|
99
|
+
if (entity.package_path && other.package_path && entity.package_path === other.package_path) {
|
|
100
|
+
const otherTags = parseTags(other.tags);
|
|
101
|
+
const overlap = tagOverlap(tags, otherTags);
|
|
102
|
+
// Package path match gives a base weight of 0.3, boosted by tag overlap
|
|
103
|
+
const w = 0.3 + overlap * 0.4;
|
|
104
|
+
if (w > bestWeight) {
|
|
105
|
+
bestWeight = w;
|
|
106
|
+
bestEdgeType = "relates_to";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. Tag overlap (regardless of package path)
|
|
111
|
+
if (tags.length > 0) {
|
|
112
|
+
const otherTags = parseTags(other.tags);
|
|
113
|
+
const overlap = tagOverlap(tags, otherTags);
|
|
114
|
+
if (overlap > bestWeight) {
|
|
115
|
+
bestWeight = overlap;
|
|
116
|
+
bestEdgeType = "relates_to";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Type affinity (Solution<->Bug)
|
|
121
|
+
if (hasTypeAffinity) {
|
|
122
|
+
const affinity = TYPE_AFFINITY[entity.type];
|
|
123
|
+
if (other.type === affinity.target) {
|
|
124
|
+
const otherTags = parseTags(other.tags);
|
|
125
|
+
const overlap = tagOverlap(tags, otherTags);
|
|
126
|
+
// Type affinity gives a base boost of 0.35 + tag overlap contribution
|
|
127
|
+
const w = 0.35 + overlap * 0.55;
|
|
128
|
+
if (w > bestWeight) {
|
|
129
|
+
bestWeight = w;
|
|
130
|
+
bestEdgeType = affinity.label;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (bestWeight >= MIN_WEIGHT) {
|
|
136
|
+
candidates.push({
|
|
137
|
+
targetId: other.id,
|
|
138
|
+
edgeType: bestEdgeType,
|
|
139
|
+
weight: bestWeight,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Sort by weight descending and cap at MAX_EDGES_PER_ENTITY
|
|
145
|
+
candidates.sort((a, b) => b.weight - a.weight);
|
|
146
|
+
const toCreate = candidates.slice(0, MAX_EDGES_PER_ENTITY);
|
|
147
|
+
|
|
148
|
+
for (const candidate of toCreate) {
|
|
149
|
+
await insertEdge(db, {
|
|
150
|
+
from_id: entityId,
|
|
151
|
+
to_id: candidate.targetId,
|
|
152
|
+
type: candidate.edgeType,
|
|
153
|
+
weight: candidate.weight,
|
|
154
|
+
extraction_method: "edge-inferrer",
|
|
155
|
+
});
|
|
156
|
+
totalCreated++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return totalCreated;
|
|
161
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Module: embedder — ONNX-based text embedding with lazy session initialization
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import type { Tokenizer } from "@/capture/tokenizer";
|
|
5
|
+
import { loadTokenizer, tokenize } from "@/capture/tokenizer";
|
|
6
|
+
|
|
7
|
+
/** Embedding vector dimension for all-MiniLM-L6-v2. */
|
|
8
|
+
const EMBEDDING_DIM = 384;
|
|
9
|
+
|
|
10
|
+
/** Maximum sequence length for tokenization. */
|
|
11
|
+
const MAX_SEQ_LENGTH = 128;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The Embedder interface: embed text into a float vector, and clean up resources.
|
|
15
|
+
*/
|
|
16
|
+
export interface Embedder {
|
|
17
|
+
embed(text: string, trustTier?: number): Promise<Float32Array | null>;
|
|
18
|
+
close(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Try to load the onnxruntime-node module.
|
|
23
|
+
* Returns null if the module is unavailable (e.g., unsupported platform).
|
|
24
|
+
*/
|
|
25
|
+
async function loadOnnxRuntime(): Promise<typeof import("onnxruntime-node") | null> {
|
|
26
|
+
try {
|
|
27
|
+
return await import("onnxruntime-node");
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create an Embedder backed by an ONNX InferenceSession.
|
|
35
|
+
*
|
|
36
|
+
* The session is lazily initialized on the first call to embed().
|
|
37
|
+
* If the model file does not exist or ONNX runtime is unavailable, embed() returns null.
|
|
38
|
+
*/
|
|
39
|
+
export function createEmbedder(modelPath: string, tokenizerPath: string): Embedder {
|
|
40
|
+
let session: { run(feeds: Record<string, unknown>): Promise<Record<string, unknown>> } | null =
|
|
41
|
+
null;
|
|
42
|
+
let tokenizer: Tokenizer | null = null;
|
|
43
|
+
let initialized = false;
|
|
44
|
+
let ort: typeof import("onnxruntime-node") | null = null;
|
|
45
|
+
|
|
46
|
+
async function ensureSession(): Promise<boolean> {
|
|
47
|
+
if (initialized) return session !== null;
|
|
48
|
+
initialized = true;
|
|
49
|
+
|
|
50
|
+
if (!existsSync(modelPath)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!existsSync(tokenizerPath)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
tokenizer = loadTokenizer(tokenizerPath);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
ort = await loadOnnxRuntime();
|
|
66
|
+
if (!ort) return false;
|
|
67
|
+
|
|
68
|
+
session = (await ort.InferenceSession.create(modelPath, {
|
|
69
|
+
executionProviders: ["cpu"],
|
|
70
|
+
})) as unknown as typeof session;
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
session = null;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
async embed(text: string): Promise<Float32Array | null> {
|
|
80
|
+
const ready = await ensureSession();
|
|
81
|
+
if (!ready || !session || !tokenizer || !ort) return null;
|
|
82
|
+
|
|
83
|
+
const { inputIds, attentionMask } = tokenize(tokenizer, text, MAX_SEQ_LENGTH);
|
|
84
|
+
|
|
85
|
+
// Build token_type_ids (all zeros for single-sentence tasks)
|
|
86
|
+
const tokenTypeIds = new BigInt64Array(MAX_SEQ_LENGTH);
|
|
87
|
+
|
|
88
|
+
// Create ONNX tensors — shape [1, MAX_SEQ_LENGTH]
|
|
89
|
+
const shape = [1, MAX_SEQ_LENGTH] as const;
|
|
90
|
+
const feeds = {
|
|
91
|
+
input_ids: new ort.Tensor("int64", inputIds, shape),
|
|
92
|
+
attention_mask: new ort.Tensor("int64", attentionMask, shape),
|
|
93
|
+
token_type_ids: new ort.Tensor("int64", tokenTypeIds, shape),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const results = await session.run(feeds);
|
|
97
|
+
|
|
98
|
+
// last_hidden_state has shape [1, seq_len, 384]
|
|
99
|
+
const lastHiddenState = results.last_hidden_state as {
|
|
100
|
+
data: Float32Array;
|
|
101
|
+
dims: readonly number[];
|
|
102
|
+
};
|
|
103
|
+
if (!lastHiddenState?.data) return null;
|
|
104
|
+
|
|
105
|
+
const hiddenData = lastHiddenState.data;
|
|
106
|
+
const seqLen = lastHiddenState.dims[1] ?? MAX_SEQ_LENGTH;
|
|
107
|
+
|
|
108
|
+
// Mean pooling: average only non-padding token vectors
|
|
109
|
+
return meanPoolAndNormalize(hiddenData, attentionMask, seqLen, EMBEDDING_DIM);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
close(): void {
|
|
113
|
+
if (session && "release" in session) {
|
|
114
|
+
(session as { release(): void }).release();
|
|
115
|
+
}
|
|
116
|
+
session = null;
|
|
117
|
+
tokenizer = null;
|
|
118
|
+
ort = null;
|
|
119
|
+
initialized = false;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Mean-pool the hidden states for non-padding tokens, then L2-normalize.
|
|
126
|
+
*/
|
|
127
|
+
function meanPoolAndNormalize(
|
|
128
|
+
hiddenData: Float32Array,
|
|
129
|
+
attentionMask: BigInt64Array,
|
|
130
|
+
seqLen: number,
|
|
131
|
+
dim: number,
|
|
132
|
+
): Float32Array {
|
|
133
|
+
const pooled = new Float32Array(dim);
|
|
134
|
+
let tokenCount = 0;
|
|
135
|
+
|
|
136
|
+
for (let t = 0; t < seqLen; t++) {
|
|
137
|
+
if (attentionMask[t] === 0n) continue;
|
|
138
|
+
tokenCount++;
|
|
139
|
+
const offset = t * dim;
|
|
140
|
+
for (let d = 0; d < dim; d++) {
|
|
141
|
+
pooled[d] += hiddenData[offset + d];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Average
|
|
146
|
+
if (tokenCount > 0) {
|
|
147
|
+
for (let d = 0; d < dim; d++) {
|
|
148
|
+
pooled[d] /= tokenCount;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// L2 normalize
|
|
153
|
+
let norm = 0;
|
|
154
|
+
for (let d = 0; d < dim; d++) {
|
|
155
|
+
norm += pooled[d] * pooled[d];
|
|
156
|
+
}
|
|
157
|
+
norm = Math.sqrt(norm);
|
|
158
|
+
|
|
159
|
+
if (norm > 0) {
|
|
160
|
+
for (let d = 0; d < dim; d++) {
|
|
161
|
+
pooled[d] /= norm;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return pooled;
|
|
166
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Module: embedding-cache — LRU-cached wrapper around an Embedder with noEmbed and paranoid modes
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import type { Embedder } from "@/capture/embedder";
|
|
5
|
+
|
|
6
|
+
/** Configuration options for the cached embedder. */
|
|
7
|
+
export interface CacheOpts {
|
|
8
|
+
/** Maximum number of cache entries before LRU eviction kicks in. Default: 1000. */
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
/** When true, embed() returns null immediately without loading the model. */
|
|
11
|
+
noEmbed?: boolean;
|
|
12
|
+
/** When true, embed() returns null for trustTier === 4 content. */
|
|
13
|
+
paranoid?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Compute a SHA-256 hex digest of the given text. */
|
|
17
|
+
function contentHash(text: string): string {
|
|
18
|
+
return createHash("sha256").update(text).digest("hex");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrap an inner Embedder with LRU caching, noEmbed, and paranoid-mode support.
|
|
23
|
+
*
|
|
24
|
+
* - **LRU cache**: content-hash keyed (SHA-256 of text). Same text yields a
|
|
25
|
+
* cache hit with no ONNX call. When full the oldest entry is evicted.
|
|
26
|
+
* - **noEmbed**: when `opts.noEmbed` is true, `embed()` returns null immediately.
|
|
27
|
+
* - **paranoid**: when `opts.paranoid` is true AND `trustTier === 4`, `embed()`
|
|
28
|
+
* returns null immediately (Tier 4 content is never embedded).
|
|
29
|
+
*/
|
|
30
|
+
export function createCachedEmbedder(inner: Embedder, opts?: CacheOpts): Embedder {
|
|
31
|
+
const maxSize = opts?.maxSize ?? 1000;
|
|
32
|
+
const noEmbed = opts?.noEmbed ?? false;
|
|
33
|
+
const paranoid = opts?.paranoid ?? false;
|
|
34
|
+
|
|
35
|
+
// Map preserves insertion order, which we exploit for LRU eviction.
|
|
36
|
+
const cache = new Map<string, Float32Array | null>();
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
async embed(text: string, trustTier?: number): Promise<Float32Array | null> {
|
|
40
|
+
// --no-embed: short-circuit without touching model
|
|
41
|
+
if (noEmbed) return null;
|
|
42
|
+
|
|
43
|
+
// paranoid mode: block Tier 4 content
|
|
44
|
+
if (paranoid && trustTier === 4) return null;
|
|
45
|
+
|
|
46
|
+
const key = contentHash(text);
|
|
47
|
+
|
|
48
|
+
// Cache hit — move entry to end (most-recently-used)
|
|
49
|
+
const cached = cache.get(key);
|
|
50
|
+
if (cached !== undefined) {
|
|
51
|
+
cache.delete(key);
|
|
52
|
+
cache.set(key, cached);
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Cache miss — delegate to inner embedder
|
|
57
|
+
const result = await inner.embed(text, trustTier);
|
|
58
|
+
|
|
59
|
+
// Evict oldest entry if cache is full
|
|
60
|
+
if (cache.size >= maxSize) {
|
|
61
|
+
const oldest = cache.keys().next().value as string;
|
|
62
|
+
cache.delete(oldest);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cache.set(key, result);
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
close(): void {
|
|
70
|
+
inner.close();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Module: flag-processor — Converts unconsumed session flags into graph entities
|
|
2
|
+
|
|
3
|
+
import { consolidate } from "@/capture/consolidate";
|
|
4
|
+
import type { CandidateFact } from "@/capture/types";
|
|
5
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
6
|
+
import { updateEntity } from "@/graph/entities";
|
|
7
|
+
import { getUnconsumedFlags, markFlagConsumed } from "@/graph/flags";
|
|
8
|
+
import type { SiaConfig } from "@/shared/config";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_BASE_IMPORTANCE = 0.5;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Process all unconsumed session flags for the given session.
|
|
14
|
+
*
|
|
15
|
+
* For each flag:
|
|
16
|
+
* 1. Build a CandidateFact of type "Concept" from the flag reason.
|
|
17
|
+
* 2. Apply the configured flaggedConfidenceThreshold and flaggedImportanceBoost.
|
|
18
|
+
* 3. Run the candidate through the consolidation pipeline.
|
|
19
|
+
* 4. Mark the flag as consumed.
|
|
20
|
+
*
|
|
21
|
+
* Returns the number of flags processed (0 when flagging is disabled).
|
|
22
|
+
*/
|
|
23
|
+
export async function processFlags(
|
|
24
|
+
db: SiaDb,
|
|
25
|
+
sessionId: string,
|
|
26
|
+
config: SiaConfig,
|
|
27
|
+
): Promise<number> {
|
|
28
|
+
if (!config.enableFlagging) return 0;
|
|
29
|
+
|
|
30
|
+
const flags = await getUnconsumedFlags(db, sessionId);
|
|
31
|
+
|
|
32
|
+
for (const flag of flags) {
|
|
33
|
+
const candidate: CandidateFact = {
|
|
34
|
+
type: "Concept",
|
|
35
|
+
name: flag.reason.slice(0, 50),
|
|
36
|
+
content: flag.reason,
|
|
37
|
+
summary: flag.reason.slice(0, 80),
|
|
38
|
+
tags: ["session-flag"],
|
|
39
|
+
file_paths: [],
|
|
40
|
+
trust_tier: 1,
|
|
41
|
+
confidence: config.flaggedConfidenceThreshold,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
await consolidate(db, [candidate]);
|
|
45
|
+
|
|
46
|
+
// Apply the importance boost to the entity just created/updated
|
|
47
|
+
const boostedImportance = DEFAULT_BASE_IMPORTANCE + config.flaggedImportanceBoost;
|
|
48
|
+
const result = await db.execute(
|
|
49
|
+
"SELECT id FROM graph_nodes WHERE name = ? AND type = 'Concept' AND t_valid_until IS NULL AND archived_at IS NULL",
|
|
50
|
+
[candidate.name],
|
|
51
|
+
);
|
|
52
|
+
const row = result.rows[0] as { id: string } | undefined;
|
|
53
|
+
if (row) {
|
|
54
|
+
await updateEntity(db, row.id, {
|
|
55
|
+
base_importance: boostedImportance,
|
|
56
|
+
importance: boostedImportance,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await markFlagConsumed(db, flag.id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return flags.length;
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Module: hook — Hook entry point for capturing Claude Code events
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
import type { HookPayload } from "@/capture/types";
|
|
6
|
+
import { resolveWorktreeRoot } from "@/shared/git-utils";
|
|
7
|
+
|
|
8
|
+
export type { HookPayload };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse raw stdin JSON into a validated HookPayload.
|
|
12
|
+
* Throws on invalid JSON or missing required fields.
|
|
13
|
+
*/
|
|
14
|
+
export function parseHookPayload(stdin: string): HookPayload {
|
|
15
|
+
let parsed: unknown;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(stdin);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error("Invalid JSON in hook payload");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
23
|
+
throw new Error("Hook payload must be a JSON object");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const obj = parsed as Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
if (typeof obj.cwd !== "string" || obj.cwd.length === 0) {
|
|
29
|
+
throw new Error("Hook payload missing required field: cwd");
|
|
30
|
+
}
|
|
31
|
+
if (obj.type !== "PostToolUse" && obj.type !== "Stop") {
|
|
32
|
+
throw new Error("Hook payload field 'type' must be 'PostToolUse' or 'Stop'");
|
|
33
|
+
}
|
|
34
|
+
if (typeof obj.sessionId !== "string" || obj.sessionId.length === 0) {
|
|
35
|
+
throw new Error("Hook payload missing required field: sessionId");
|
|
36
|
+
}
|
|
37
|
+
if (typeof obj.content !== "string") {
|
|
38
|
+
throw new Error("Hook payload missing required field: content");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
cwd: obj.cwd as string,
|
|
43
|
+
type: obj.type as "PostToolUse" | "Stop",
|
|
44
|
+
sessionId: obj.sessionId as string,
|
|
45
|
+
content: obj.content as string,
|
|
46
|
+
toolName: typeof obj.toolName === "string" ? obj.toolName : undefined,
|
|
47
|
+
filePath: typeof obj.filePath === "string" ? obj.filePath : undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Derive a stable repo identifier from a working directory path.
|
|
53
|
+
* Uses the git worktree root so that all paths within the same repo
|
|
54
|
+
* (including subdirectories) resolve to the same hash.
|
|
55
|
+
* Falls back to realpathSync for non-git directories (with a warning).
|
|
56
|
+
*/
|
|
57
|
+
export function resolveRepoHash(cwd: string): string {
|
|
58
|
+
const root = resolveWorktreeRoot(cwd);
|
|
59
|
+
if (!root) {
|
|
60
|
+
console.error(
|
|
61
|
+
`[sia] WARNING: Cannot determine git root for "${cwd}". ` +
|
|
62
|
+
"Using directory path as repo hash — graph data may diverge if git becomes available later.",
|
|
63
|
+
);
|
|
64
|
+
return createHash("sha256").update(realpathSync(cwd)).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
return createHash("sha256").update(root).digest("hex");
|
|
67
|
+
}
|