@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,278 @@
|
|
|
1
|
+
// Module: qa-report — Generate QA-focused testing intelligence report
|
|
2
|
+
//
|
|
3
|
+
// Queries the graph for bugs, solutions, decisions, and recent changes,
|
|
4
|
+
// then produces a markdown report grouped by risk level with test
|
|
5
|
+
// recommendations derived from bug history.
|
|
6
|
+
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
10
|
+
|
|
11
|
+
export interface QaReportOptions {
|
|
12
|
+
since?: number;
|
|
13
|
+
outputPath?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface EntityRow {
|
|
17
|
+
id: string;
|
|
18
|
+
type: string;
|
|
19
|
+
name: string;
|
|
20
|
+
content: string;
|
|
21
|
+
summary: string | null;
|
|
22
|
+
trust_tier: number;
|
|
23
|
+
importance: number;
|
|
24
|
+
created_at: number;
|
|
25
|
+
file_paths: string | null;
|
|
26
|
+
edge_count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatDate(ms: number): string {
|
|
30
|
+
return new Date(ms).toISOString().split("T")[0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function riskLevel(
|
|
34
|
+
bugCount: number,
|
|
35
|
+
recentCount: number,
|
|
36
|
+
edgeCount: number,
|
|
37
|
+
): { label: string; score: number } {
|
|
38
|
+
// Weighted score: bug density 40%, change velocity 35%, dependency fan-out 25%
|
|
39
|
+
const bugScore = Math.min(bugCount * 20, 100);
|
|
40
|
+
const changeScore = Math.min(recentCount * 15, 100);
|
|
41
|
+
const edgeScore = Math.min(edgeCount * 5, 100);
|
|
42
|
+
const score = Math.round(bugScore * 0.4 + changeScore * 0.35 + edgeScore * 0.25);
|
|
43
|
+
|
|
44
|
+
if (score > 70) return { label: "HIGH", score };
|
|
45
|
+
if (score >= 40) return { label: "MEDIUM", score };
|
|
46
|
+
return { label: "LOW", score };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function generateQaReport(db: SiaDb, opts: QaReportOptions = {}): Promise<string> {
|
|
50
|
+
const since = opts.since ?? Date.now() - 86400000 * 14; // default: 14 days
|
|
51
|
+
|
|
52
|
+
// Query all active entities
|
|
53
|
+
const allResult = await db.execute(
|
|
54
|
+
"SELECT COUNT(*) as cnt FROM graph_nodes WHERE t_valid_until IS NULL AND archived_at IS NULL",
|
|
55
|
+
);
|
|
56
|
+
const totalEntities = (allResult.rows[0] as any).cnt;
|
|
57
|
+
|
|
58
|
+
if (totalEntities === 0) {
|
|
59
|
+
return `# QA Report\n\n*No entities in the knowledge graph yet. Run \`/sia-learn\` to build the graph.*\n`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Query bugs
|
|
63
|
+
const bugsResult = await db.execute(
|
|
64
|
+
`SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
|
|
65
|
+
FROM graph_nodes
|
|
66
|
+
WHERE type = 'Bug' AND t_valid_until IS NULL AND archived_at IS NULL
|
|
67
|
+
ORDER BY created_at DESC
|
|
68
|
+
LIMIT 50`,
|
|
69
|
+
);
|
|
70
|
+
const bugs = bugsResult.rows as unknown as EntityRow[];
|
|
71
|
+
|
|
72
|
+
// Query solutions
|
|
73
|
+
const solutionsResult = await db.execute(
|
|
74
|
+
`SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
|
|
75
|
+
FROM graph_nodes
|
|
76
|
+
WHERE type = 'Solution' AND t_valid_until IS NULL AND archived_at IS NULL
|
|
77
|
+
ORDER BY created_at DESC
|
|
78
|
+
LIMIT 50`,
|
|
79
|
+
);
|
|
80
|
+
const solutions = solutionsResult.rows as unknown as EntityRow[];
|
|
81
|
+
|
|
82
|
+
// Query recent decisions
|
|
83
|
+
const decisionsResult = await db.execute(
|
|
84
|
+
`SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
|
|
85
|
+
FROM graph_nodes
|
|
86
|
+
WHERE type = 'Decision' AND t_valid_until IS NULL AND archived_at IS NULL AND created_at >= ?
|
|
87
|
+
ORDER BY created_at DESC
|
|
88
|
+
LIMIT 50`,
|
|
89
|
+
[since],
|
|
90
|
+
);
|
|
91
|
+
const recentDecisions = decisionsResult.rows as unknown as EntityRow[];
|
|
92
|
+
|
|
93
|
+
// Recent entities (all types)
|
|
94
|
+
const recentResult = await db.execute(
|
|
95
|
+
`SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
|
|
96
|
+
FROM graph_nodes
|
|
97
|
+
WHERE t_valid_until IS NULL AND archived_at IS NULL AND created_at >= ?
|
|
98
|
+
ORDER BY created_at DESC
|
|
99
|
+
LIMIT 100`,
|
|
100
|
+
[since],
|
|
101
|
+
);
|
|
102
|
+
const recentEntities = recentResult.rows as unknown as EntityRow[];
|
|
103
|
+
|
|
104
|
+
const recentBugs = bugs.filter((b) => b.created_at >= since);
|
|
105
|
+
const totalEdgeCount = bugs.reduce((sum, b) => sum + (b.edge_count || 0), 0);
|
|
106
|
+
|
|
107
|
+
const risk = riskLevel(bugs.length, recentEntities.length, totalEdgeCount);
|
|
108
|
+
|
|
109
|
+
const sections: string[] = [];
|
|
110
|
+
|
|
111
|
+
// Header
|
|
112
|
+
sections.push("# QA Report");
|
|
113
|
+
sections.push(
|
|
114
|
+
`\n*Generated on ${formatDate(Date.now())} | Since: ${formatDate(since)} | ${totalEntities} total entities*\n`,
|
|
115
|
+
);
|
|
116
|
+
sections.push("---\n");
|
|
117
|
+
|
|
118
|
+
// Summary
|
|
119
|
+
sections.push("## Summary\n");
|
|
120
|
+
sections.push(`| Metric | Count |`);
|
|
121
|
+
sections.push(`|---|---|`);
|
|
122
|
+
sections.push(`| Total entities | ${totalEntities} |`);
|
|
123
|
+
sections.push(`| Recent changes (since ${formatDate(since)}) | ${recentEntities.length} |`);
|
|
124
|
+
sections.push(`| Total bugs | ${bugs.length} |`);
|
|
125
|
+
sections.push(`| Recent bugs | ${recentBugs.length} |`);
|
|
126
|
+
sections.push(`| Solutions | ${solutions.length} |`);
|
|
127
|
+
sections.push(`| Recent decisions | ${recentDecisions.length} |`);
|
|
128
|
+
sections.push(`| Overall risk | **${risk.label}** (score: ${risk.score}) |`);
|
|
129
|
+
sections.push("");
|
|
130
|
+
|
|
131
|
+
// High-Risk Areas
|
|
132
|
+
sections.push("## High-Risk Areas\n");
|
|
133
|
+
if (bugs.length === 0 && recentEntities.length === 0) {
|
|
134
|
+
sections.push("*No risk areas identified — no bugs or recent changes.*\n");
|
|
135
|
+
} else {
|
|
136
|
+
// Group bugs by file path to identify risky areas
|
|
137
|
+
const areaMap = new Map<string, { bugs: number; recent: number; edges: number }>();
|
|
138
|
+
for (const bug of bugs) {
|
|
139
|
+
const area = bug.file_paths && bug.file_paths !== "[]" ? bug.file_paths : "Unknown";
|
|
140
|
+
const entry = areaMap.get(area) ?? { bugs: 0, recent: 0, edges: 0 };
|
|
141
|
+
entry.bugs++;
|
|
142
|
+
entry.edges += bug.edge_count || 0;
|
|
143
|
+
if (bug.created_at >= since) entry.recent++;
|
|
144
|
+
areaMap.set(area, entry);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (areaMap.size > 0) {
|
|
148
|
+
const sorted = [...areaMap.entries()].sort((a, b) => {
|
|
149
|
+
const riskA = riskLevel(a[1].bugs, a[1].recent, a[1].edges);
|
|
150
|
+
const riskB = riskLevel(b[1].bugs, b[1].recent, b[1].edges);
|
|
151
|
+
return riskB.score - riskA.score;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
sections.push("| Area | Bugs | Recent Changes | Risk |");
|
|
155
|
+
sections.push("|---|---|---|---|");
|
|
156
|
+
for (const [area, data] of sorted) {
|
|
157
|
+
const r = riskLevel(data.bugs, data.recent, data.edges);
|
|
158
|
+
sections.push(`| ${area} | ${data.bugs} | ${data.recent} | **${r.label}** (${r.score}) |`);
|
|
159
|
+
}
|
|
160
|
+
sections.push("");
|
|
161
|
+
} else {
|
|
162
|
+
sections.push("*No bug-affected areas found.*\n");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Bug Activity
|
|
167
|
+
sections.push("## Bug Activity\n");
|
|
168
|
+
if (bugs.length === 0) {
|
|
169
|
+
sections.push("*No bugs recorded in the knowledge graph.*\n");
|
|
170
|
+
} else {
|
|
171
|
+
sections.push(`| Bug | Date | Trust | Files |`);
|
|
172
|
+
sections.push(`|---|---|---|---|`);
|
|
173
|
+
for (const bug of bugs) {
|
|
174
|
+
const date = formatDate(bug.created_at);
|
|
175
|
+
const tier =
|
|
176
|
+
bug.trust_tier === 1 ? "verified" : bug.trust_tier === 2 ? "code-derived" : "inferred";
|
|
177
|
+
const files = bug.file_paths && bug.file_paths !== "[]" ? bug.file_paths : "—";
|
|
178
|
+
sections.push(`| ${bug.name} | ${date} | ${tier} | ${files} |`);
|
|
179
|
+
}
|
|
180
|
+
sections.push("");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Solutions
|
|
184
|
+
if (solutions.length > 0) {
|
|
185
|
+
sections.push("## Solutions Applied\n");
|
|
186
|
+
sections.push(`| Solution | Date | Files |`);
|
|
187
|
+
sections.push(`|---|---|---|`);
|
|
188
|
+
for (const sol of solutions) {
|
|
189
|
+
const date = formatDate(sol.created_at);
|
|
190
|
+
const files = sol.file_paths && sol.file_paths !== "[]" ? sol.file_paths : "—";
|
|
191
|
+
sections.push(`| ${sol.name} | ${date} | ${files} |`);
|
|
192
|
+
}
|
|
193
|
+
sections.push("");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Recent Decisions
|
|
197
|
+
if (recentDecisions.length > 0) {
|
|
198
|
+
sections.push("## Recent Decisions\n");
|
|
199
|
+
for (const dec of recentDecisions) {
|
|
200
|
+
sections.push(`### ${dec.name}`);
|
|
201
|
+
sections.push(`*${formatDate(dec.created_at)}*\n`);
|
|
202
|
+
if (dec.content) sections.push(`${dec.content.slice(0, 300)}\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Test Recommendations
|
|
207
|
+
sections.push("## Test Recommendations\n");
|
|
208
|
+
if (bugs.length === 0) {
|
|
209
|
+
sections.push("*No bugs in the graph — no specific test cases to recommend.*\n");
|
|
210
|
+
} else {
|
|
211
|
+
sections.push("Based on bug history, prioritize testing these scenarios:\n");
|
|
212
|
+
for (let i = 0; i < bugs.length; i++) {
|
|
213
|
+
const bug = bugs[i];
|
|
214
|
+
sections.push(`${i + 1}. **${bug.name}** — ${bug.content.slice(0, 150)}`);
|
|
215
|
+
}
|
|
216
|
+
sections.push("");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Coverage Gaps
|
|
220
|
+
sections.push("## Coverage Gaps\n");
|
|
221
|
+
const codeEntitiesResult = await db.execute(
|
|
222
|
+
`SELECT COUNT(*) as cnt FROM graph_nodes
|
|
223
|
+
WHERE type = 'CodeEntity' AND t_valid_until IS NULL AND archived_at IS NULL`,
|
|
224
|
+
);
|
|
225
|
+
const codeCount = (codeEntitiesResult.rows[0] as any).cnt;
|
|
226
|
+
if (codeCount > 0 && bugs.length > 0) {
|
|
227
|
+
sections.push(`- ${codeCount} code entities tracked, ${bugs.length} bugs found`);
|
|
228
|
+
sections.push(`- Areas with bugs but no corresponding solutions may indicate coverage gaps`);
|
|
229
|
+
const unresolvedCount = bugs.length - solutions.length;
|
|
230
|
+
if (unresolvedCount > 0) {
|
|
231
|
+
sections.push(
|
|
232
|
+
`- **${unresolvedCount} bugs without matching solutions** — potential untested areas`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
} else if (codeCount === 0) {
|
|
236
|
+
sections.push("*No code entities indexed yet — run `/sia-learn` for coverage analysis.*");
|
|
237
|
+
} else {
|
|
238
|
+
sections.push("*No bugs recorded — coverage analysis not applicable.*");
|
|
239
|
+
}
|
|
240
|
+
sections.push("");
|
|
241
|
+
|
|
242
|
+
// Footer
|
|
243
|
+
sections.push("---\n");
|
|
244
|
+
sections.push("*Generated by SIA QA Report — run `/sia-qa-report` to regenerate.*");
|
|
245
|
+
|
|
246
|
+
return sections.join("\n");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function runQaReport(args: string[]): Promise<void> {
|
|
250
|
+
const { resolveRepoHash } = await import("@/capture/hook");
|
|
251
|
+
const { openGraphDb } = await import("@/graph/semantic-db");
|
|
252
|
+
const { resolveSiaHome } = await import("@/shared/config");
|
|
253
|
+
|
|
254
|
+
let outputPath = "QA-REPORT.md";
|
|
255
|
+
let since: number | undefined;
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < args.length; i++) {
|
|
258
|
+
if (args[i] === "--output" && args[i + 1]) outputPath = args[++i];
|
|
259
|
+
if (args[i] === "--since" && args[i + 1]) {
|
|
260
|
+
const parsed = Date.parse(args[++i]);
|
|
261
|
+
if (!Number.isNaN(parsed)) since = parsed;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const cwd = process.cwd();
|
|
266
|
+
const repoHash = resolveRepoHash(cwd);
|
|
267
|
+
const siaHome = resolveSiaHome();
|
|
268
|
+
const db = openGraphDb(repoHash, siaHome);
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const markdown = await generateQaReport(db, { since, outputPath });
|
|
272
|
+
const fullPath = join(cwd, outputPath);
|
|
273
|
+
writeFileSync(fullPath, markdown, "utf-8");
|
|
274
|
+
console.log(`QA report written to ${outputPath} (${markdown.length} chars)`);
|
|
275
|
+
} finally {
|
|
276
|
+
await db.close();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { type IndexResult, indexRepository } from "@/ast/indexer";
|
|
5
|
+
import { openMetaDb, registerRepo } from "@/graph/meta-db";
|
|
6
|
+
import { openGraphDb } from "@/graph/semantic-db";
|
|
7
|
+
import { getConfig } from "@/shared/config";
|
|
8
|
+
import { detectApiContracts, writeDetectedContracts } from "@/workspace/api-contracts";
|
|
9
|
+
import { detectMonorepoPackages, registerMonorepoPackages } from "@/workspace/detector";
|
|
10
|
+
|
|
11
|
+
export interface ReindexOptions {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
siaHome?: string;
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ReindexResult extends IndexResult {
|
|
18
|
+
repoHash: string;
|
|
19
|
+
dryRun: boolean;
|
|
20
|
+
packagesDetected: number;
|
|
21
|
+
contractsDetected: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findRepoRoot(startDir: string): string | null {
|
|
25
|
+
let dir = resolve(startDir);
|
|
26
|
+
const root = resolve("/");
|
|
27
|
+
while (dir !== root) {
|
|
28
|
+
if (existsSync(join(dir, ".git"))) {
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
const parent = resolve(dir, "..");
|
|
32
|
+
if (parent === dir) break;
|
|
33
|
+
dir = parent;
|
|
34
|
+
}
|
|
35
|
+
if (existsSync(join(dir, ".git"))) {
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function siaReindex(opts: ReindexOptions = {}): Promise<ReindexResult> {
|
|
42
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
43
|
+
const repoRoot = findRepoRoot(cwd);
|
|
44
|
+
if (!repoRoot) {
|
|
45
|
+
throw new Error(`No .git directory found from ${cwd}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolvedRoot = resolve(repoRoot);
|
|
49
|
+
const repoHash = createHash("sha256").update(resolvedRoot).digest("hex");
|
|
50
|
+
const isDryRun = opts.dryRun ?? false;
|
|
51
|
+
const prefix = isDryRun ? "(dry-run) " : "";
|
|
52
|
+
|
|
53
|
+
const config = getConfig(opts.siaHome);
|
|
54
|
+
const db = openGraphDb(repoHash, opts.siaHome);
|
|
55
|
+
|
|
56
|
+
let packagesDetected = 0;
|
|
57
|
+
let contractsDetected = 0;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Re-detect monorepo structure and API contracts
|
|
61
|
+
if (!isDryRun) {
|
|
62
|
+
const metaDb = openMetaDb(opts.siaHome);
|
|
63
|
+
try {
|
|
64
|
+
const repoId = await registerRepo(metaDb, resolvedRoot);
|
|
65
|
+
|
|
66
|
+
const packages = await detectMonorepoPackages(resolvedRoot);
|
|
67
|
+
if (packages.length > 0) {
|
|
68
|
+
await registerMonorepoPackages(metaDb, repoId, resolvedRoot, packages);
|
|
69
|
+
packagesDetected = packages.length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const contracts = await detectApiContracts(resolvedRoot);
|
|
73
|
+
if (contracts.length > 0) {
|
|
74
|
+
await writeDetectedContracts(metaDb, repoId, contracts);
|
|
75
|
+
contractsDetected = contracts.length;
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
await metaDb.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = await indexRepository(resolvedRoot, db, config, {
|
|
83
|
+
dryRun: isDryRun,
|
|
84
|
+
repoHash,
|
|
85
|
+
onProgress: ({ filesProcessed, entitiesCreated, file }) => {
|
|
86
|
+
console.log(`${prefix}[${filesProcessed}] ${file ?? "..."} (${entitiesCreated} entities)`);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
`${prefix}Reindex complete: ${result.filesProcessed} files, ${result.entitiesCreated} entities, ${packagesDetected} packages, ${contractsDetected} contracts.`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
...result,
|
|
96
|
+
repoHash,
|
|
97
|
+
dryRun: isDryRun,
|
|
98
|
+
packagesDetected,
|
|
99
|
+
contractsDetected,
|
|
100
|
+
};
|
|
101
|
+
} finally {
|
|
102
|
+
await db.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Module: rollback — restore graph from a snapshot
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
5
|
+
import type { SnapshotData } from "@/graph/snapshots";
|
|
6
|
+
import { findNearestSnapshot, listSnapshots, restoreSnapshot } from "@/graph/snapshots";
|
|
7
|
+
|
|
8
|
+
export interface RollbackOpts {
|
|
9
|
+
/** Target date string (YYYY-MM-DD) or timestamp to roll back to. */
|
|
10
|
+
target?: string | number;
|
|
11
|
+
/** Override SIA_HOME. */
|
|
12
|
+
siaHome?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RollbackResult {
|
|
16
|
+
snapshotUsed: string;
|
|
17
|
+
restoredEntities: number;
|
|
18
|
+
restoredEdges: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Roll back the graph database to a previous snapshot.
|
|
23
|
+
*
|
|
24
|
+
* - If `opts.target` is a YYYY-MM-DD string, it is parsed to a UTC timestamp.
|
|
25
|
+
* - If `opts.target` is a number, it is used directly as a timestamp.
|
|
26
|
+
* - If no target is provided, the most recent snapshot is used.
|
|
27
|
+
*/
|
|
28
|
+
export async function rollbackGraph(
|
|
29
|
+
db: SiaDb,
|
|
30
|
+
repoHash: string,
|
|
31
|
+
opts?: RollbackOpts,
|
|
32
|
+
): Promise<RollbackResult> {
|
|
33
|
+
const siaHome = opts?.siaHome;
|
|
34
|
+
let snapshotPath: string | null = null;
|
|
35
|
+
|
|
36
|
+
if (opts?.target != null) {
|
|
37
|
+
const targetTs =
|
|
38
|
+
typeof opts.target === "string" ? new Date(opts.target).getTime() : opts.target;
|
|
39
|
+
|
|
40
|
+
snapshotPath = findNearestSnapshot(repoHash, targetTs, siaHome);
|
|
41
|
+
} else {
|
|
42
|
+
// No target — use the most recent snapshot (last item)
|
|
43
|
+
const all = listSnapshots(repoHash, siaHome);
|
|
44
|
+
snapshotPath = all.length > 0 ? all[all.length - 1] : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!snapshotPath) {
|
|
48
|
+
throw new Error("No snapshot found for the specified date");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Restore the snapshot into the database
|
|
52
|
+
await restoreSnapshot(db, snapshotPath, repoHash, siaHome);
|
|
53
|
+
|
|
54
|
+
// Read the snapshot file to count entities and edges
|
|
55
|
+
const raw = readFileSync(snapshotPath, "utf-8");
|
|
56
|
+
const data = JSON.parse(raw) as SnapshotData;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
snapshotUsed: snapshotPath,
|
|
60
|
+
restoredEntities: data.entities.length,
|
|
61
|
+
restoredEdges: data.edges.length,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* List all available snapshot file paths for a repo, sorted oldest-first.
|
|
67
|
+
*/
|
|
68
|
+
export function listAvailableSnapshots(repoHash: string, siaHome?: string): string[] {
|
|
69
|
+
return listSnapshots(repoHash, siaHome);
|
|
70
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Module: search — CLI search against the knowledge graph
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
|
|
5
|
+
export interface SearchOpts {
|
|
6
|
+
limit?: number;
|
|
7
|
+
taskType?: string;
|
|
8
|
+
packagePath?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SearchResultItem {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
content: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
importance: number;
|
|
18
|
+
confidence: number;
|
|
19
|
+
trustTier: number;
|
|
20
|
+
score: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sanitize an FTS5 query string: strip special characters and wrap tokens
|
|
25
|
+
* in double-quotes so SQLite treats them as literals.
|
|
26
|
+
*/
|
|
27
|
+
function sanitizeFtsQuery(query: string): string {
|
|
28
|
+
// Remove FTS5 operators and special chars, keep alphanumerics, underscores, hyphens, spaces
|
|
29
|
+
const cleaned = query.replace(/[^a-zA-Z0-9_\- ]/g, " ").trim();
|
|
30
|
+
if (cleaned.length === 0) return '""';
|
|
31
|
+
|
|
32
|
+
// Wrap each token in double-quotes to avoid FTS5 syntax errors
|
|
33
|
+
const tokens = cleaned.split(/\s+/).filter(Boolean);
|
|
34
|
+
return tokens.map((t) => `"${t}"`).join(" ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Map a raw database row to a SearchResultItem.
|
|
39
|
+
*/
|
|
40
|
+
function rowToItem(row: Record<string, unknown>, score: number): SearchResultItem {
|
|
41
|
+
return {
|
|
42
|
+
id: String(row.id ?? ""),
|
|
43
|
+
name: String(row.name ?? ""),
|
|
44
|
+
type: String(row.type ?? ""),
|
|
45
|
+
content: String(row.content ?? ""),
|
|
46
|
+
summary: String(row.summary ?? ""),
|
|
47
|
+
importance: Number(row.importance ?? 0),
|
|
48
|
+
confidence: Number(row.confidence ?? 0),
|
|
49
|
+
trustTier: Number(row.trust_tier ?? 0),
|
|
50
|
+
score,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Search the knowledge graph for entities matching the given query.
|
|
56
|
+
*
|
|
57
|
+
* Tries FTS5 first for ranked full-text search; falls back to a LIKE-based
|
|
58
|
+
* query if the FTS5 virtual table is unavailable or the query fails.
|
|
59
|
+
*/
|
|
60
|
+
export async function searchGraph(
|
|
61
|
+
db: SiaDb,
|
|
62
|
+
query: string,
|
|
63
|
+
opts?: SearchOpts,
|
|
64
|
+
): Promise<SearchResultItem[]> {
|
|
65
|
+
const limit = opts?.limit ?? 20;
|
|
66
|
+
|
|
67
|
+
// --- Attempt FTS5 search ---
|
|
68
|
+
try {
|
|
69
|
+
const ftsQuery = sanitizeFtsQuery(query);
|
|
70
|
+
const result = await db.execute(
|
|
71
|
+
`SELECT graph_nodes.id, graph_nodes.name, graph_nodes.type, graph_nodes.content,
|
|
72
|
+
graph_nodes.summary, graph_nodes.importance, graph_nodes.confidence,
|
|
73
|
+
graph_nodes.trust_tier, graph_nodes_fts.rank
|
|
74
|
+
FROM graph_nodes_fts
|
|
75
|
+
JOIN graph_nodes ON graph_nodes.rowid = graph_nodes_fts.rowid
|
|
76
|
+
WHERE graph_nodes_fts MATCH ?
|
|
77
|
+
AND graph_nodes.t_valid_until IS NULL
|
|
78
|
+
AND graph_nodes.archived_at IS NULL
|
|
79
|
+
ORDER BY rank
|
|
80
|
+
LIMIT ?`,
|
|
81
|
+
[ftsQuery, limit],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return result.rows.map((row) => rowToItem(row, Number(row.rank ?? 0)));
|
|
85
|
+
} catch {
|
|
86
|
+
// FTS5 table missing or query error — fall through to LIKE search
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Fallback: LIKE search ---
|
|
90
|
+
const likePattern = `%${query}%`;
|
|
91
|
+
const result = await db.execute(
|
|
92
|
+
`SELECT id, name, type, content, summary, importance, confidence, trust_tier
|
|
93
|
+
FROM graph_nodes
|
|
94
|
+
WHERE (name LIKE ? OR content LIKE ?)
|
|
95
|
+
AND t_valid_until IS NULL
|
|
96
|
+
AND archived_at IS NULL
|
|
97
|
+
ORDER BY importance DESC
|
|
98
|
+
LIMIT ?`,
|
|
99
|
+
[likePattern, likePattern, limit],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return result.rows.map((row) => rowToItem(row, Number(row.importance ?? 0)));
|
|
103
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { SIA_HOME } from "@/shared/config";
|
|
6
|
+
|
|
7
|
+
const SERVER_DIR = join(SIA_HOME, "server");
|
|
8
|
+
const CONFIG_PATH = join(SERVER_DIR, "server.json");
|
|
9
|
+
const ENV_PATH = join(SERVER_DIR, ".env");
|
|
10
|
+
const COMPOSE_PATH = join(SERVER_DIR, "docker-compose.yml");
|
|
11
|
+
|
|
12
|
+
interface ServerConfig {
|
|
13
|
+
url: string;
|
|
14
|
+
running: boolean;
|
|
15
|
+
startedAt: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readConfig(): ServerConfig {
|
|
19
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
20
|
+
return { url: "", running: false, startedAt: null };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as ServerConfig;
|
|
24
|
+
} catch {
|
|
25
|
+
return { url: "", running: false, startedAt: null };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeServerConfig(config: ServerConfig): void {
|
|
30
|
+
mkdirSync(SERVER_DIR, { recursive: true });
|
|
31
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const COMPOSE_TEMPLATE = `version: "3.8"
|
|
35
|
+
services:
|
|
36
|
+
sqld:
|
|
37
|
+
image: ghcr.io/tursodatabase/libsql-server:latest
|
|
38
|
+
ports:
|
|
39
|
+
- "8080:8080"
|
|
40
|
+
env_file:
|
|
41
|
+
- .env
|
|
42
|
+
volumes:
|
|
43
|
+
- sqld-data:/var/lib/sqld
|
|
44
|
+
volumes:
|
|
45
|
+
sqld-data:
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export function serverStart(opts?: { port?: number }): ServerConfig {
|
|
49
|
+
const port = opts?.port ?? 8080;
|
|
50
|
+
mkdirSync(SERVER_DIR, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// Generate JWT secret
|
|
53
|
+
const jwtSecret = randomBytes(32).toString("hex");
|
|
54
|
+
writeFileSync(ENV_PATH, `SQLD_AUTH_JWT_KEY=${jwtSecret}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
55
|
+
|
|
56
|
+
// Write docker-compose.yml
|
|
57
|
+
const compose = COMPOSE_TEMPLATE.replace("8080:8080", `${port}:8080`);
|
|
58
|
+
writeFileSync(COMPOSE_PATH, compose, "utf-8");
|
|
59
|
+
|
|
60
|
+
// Start container
|
|
61
|
+
try {
|
|
62
|
+
execFileSync("docker", ["compose", "-f", COMPOSE_PATH, "up", "-d"], { stdio: "pipe" });
|
|
63
|
+
} catch (err) {
|
|
64
|
+
throw new Error(`Failed to start server: ${(err as Error).message}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const config: ServerConfig = {
|
|
68
|
+
url: `http://localhost:${port}`,
|
|
69
|
+
running: true,
|
|
70
|
+
startedAt: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
writeServerConfig(config);
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function serverStop(): ServerConfig {
|
|
77
|
+
try {
|
|
78
|
+
if (existsSync(COMPOSE_PATH)) {
|
|
79
|
+
execFileSync("docker", ["compose", "-f", COMPOSE_PATH, "down"], { stdio: "pipe" });
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Container may not be running
|
|
83
|
+
}
|
|
84
|
+
const config: ServerConfig = { url: "", running: false, startedAt: null };
|
|
85
|
+
writeServerConfig(config);
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function serverStatus(): ServerConfig {
|
|
90
|
+
return readConfig();
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Module: share — adjust entity visibility for sharing
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
import { updateEntity } from "@/graph/entities";
|
|
5
|
+
import { openMetaDb, resolveWorkspaceName } from "@/graph/meta-db";
|
|
6
|
+
import type { SyncConfig } from "@/shared/config";
|
|
7
|
+
import { pushChanges } from "@/sync/push";
|
|
8
|
+
|
|
9
|
+
export async function shareEntity(
|
|
10
|
+
db: SiaDb,
|
|
11
|
+
entityId: string,
|
|
12
|
+
opts: { team?: boolean; project?: string | null; siaHome?: string; syncConfig?: SyncConfig } = {},
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
let workspaceScope: string | null = null;
|
|
15
|
+
if (opts.project) {
|
|
16
|
+
const metaDb = openMetaDb(opts.siaHome);
|
|
17
|
+
try {
|
|
18
|
+
const wsId = await resolveWorkspaceName(metaDb, opts.project);
|
|
19
|
+
if (!wsId) throw new Error(`Workspace '${opts.project}' not found`);
|
|
20
|
+
workspaceScope = wsId;
|
|
21
|
+
} finally {
|
|
22
|
+
await metaDb.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const visibility = opts.team ? "team" : opts.project ? "project" : "private";
|
|
27
|
+
await updateEntity(db, entityId, { visibility, workspace_scope: workspaceScope });
|
|
28
|
+
|
|
29
|
+
// Trigger immediate push
|
|
30
|
+
if (opts.syncConfig?.enabled) {
|
|
31
|
+
await pushChanges(db, opts.syncConfig);
|
|
32
|
+
}
|
|
33
|
+
}
|