@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,41 @@
|
|
|
1
|
+
// Module: conflicts — conflict listing and resolution
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
import { invalidateEntity } from "@/graph/entities";
|
|
5
|
+
|
|
6
|
+
export async function listConflicts(db: SiaDb): Promise<Record<string, string[]>> {
|
|
7
|
+
const rows = await db.execute(
|
|
8
|
+
"SELECT conflict_group_id, id FROM graph_nodes WHERE conflict_group_id IS NOT NULL AND archived_at IS NULL AND t_valid_until IS NULL",
|
|
9
|
+
);
|
|
10
|
+
const groups: Record<string, string[]> = {};
|
|
11
|
+
for (const row of rows.rows as Array<{ conflict_group_id: string; id: string }>) {
|
|
12
|
+
if (!groups[row.conflict_group_id]) groups[row.conflict_group_id] = [];
|
|
13
|
+
groups[row.conflict_group_id].push(row.id);
|
|
14
|
+
}
|
|
15
|
+
return groups;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function resolveConflict(
|
|
19
|
+
db: SiaDb,
|
|
20
|
+
groupId: string,
|
|
21
|
+
keepEntityId: string,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const keepRow = await db.execute(
|
|
24
|
+
"SELECT id FROM graph_nodes WHERE id = ? AND conflict_group_id = ?",
|
|
25
|
+
[keepEntityId, groupId],
|
|
26
|
+
);
|
|
27
|
+
if (keepRow.rows.length === 0) {
|
|
28
|
+
throw new Error(`Entity '${keepEntityId}' not found in conflict group '${groupId}'`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rows = await db.execute("SELECT id FROM graph_nodes WHERE conflict_group_id = ?", [
|
|
32
|
+
groupId,
|
|
33
|
+
]);
|
|
34
|
+
for (const row of rows.rows as Array<{ id: string }>) {
|
|
35
|
+
if (row.id === keepEntityId) continue;
|
|
36
|
+
await invalidateEntity(db, row.id);
|
|
37
|
+
}
|
|
38
|
+
await db.execute("UPDATE graph_nodes SET conflict_group_id = NULL WHERE conflict_group_id = ?", [
|
|
39
|
+
groupId,
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Module: digest — Knowledge digest generator for graph activity summaries
|
|
2
|
+
|
|
3
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
4
|
+
|
|
5
|
+
export type DigestPeriod = "daily" | "weekly" | "monthly" | "custom";
|
|
6
|
+
|
|
7
|
+
export interface DigestOpts {
|
|
8
|
+
period?: DigestPeriod;
|
|
9
|
+
startDate?: number; // Unix ms
|
|
10
|
+
endDate?: number; // Unix ms
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DigestSection {
|
|
14
|
+
title: string;
|
|
15
|
+
items: DigestItem[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DigestItem {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
created_at: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DigestResult {
|
|
27
|
+
period: DigestPeriod;
|
|
28
|
+
startDate: number;
|
|
29
|
+
endDate: number;
|
|
30
|
+
sections: DigestSection[];
|
|
31
|
+
totalEntities: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Map entity type to human-readable section title. */
|
|
35
|
+
const TYPE_TITLES: Record<string, string> = {
|
|
36
|
+
Decision: "Decisions Captured",
|
|
37
|
+
Convention: "Conventions Established",
|
|
38
|
+
Bug: "Bugs Identified",
|
|
39
|
+
Solution: "Solutions Found",
|
|
40
|
+
Concept: "Concepts Added",
|
|
41
|
+
FileNode: "Files Indexed",
|
|
42
|
+
ContentChunk: "Documentation Chunks",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Ordered list of types so sections appear in a stable order. */
|
|
46
|
+
const TYPE_ORDER: string[] = [
|
|
47
|
+
"Decision",
|
|
48
|
+
"Convention",
|
|
49
|
+
"Bug",
|
|
50
|
+
"Solution",
|
|
51
|
+
"Concept",
|
|
52
|
+
"FileNode",
|
|
53
|
+
"ContentChunk",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const MS_PER_DAY = 86_400_000;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compute the start/end timestamps for a named period.
|
|
60
|
+
* "custom" requires explicit startDate/endDate in opts.
|
|
61
|
+
*/
|
|
62
|
+
function resolveTimeRange(opts?: DigestOpts): {
|
|
63
|
+
period: DigestPeriod;
|
|
64
|
+
startDate: number;
|
|
65
|
+
endDate: number;
|
|
66
|
+
} {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const period = opts?.period ?? "weekly";
|
|
69
|
+
const endDate = opts?.endDate ?? now;
|
|
70
|
+
|
|
71
|
+
if (period === "custom") {
|
|
72
|
+
if (opts?.startDate == null || opts?.endDate == null) {
|
|
73
|
+
throw new Error("Custom period requires both startDate and endDate");
|
|
74
|
+
}
|
|
75
|
+
return { period, startDate: opts.startDate, endDate: opts.endDate };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let startDate: number;
|
|
79
|
+
switch (period) {
|
|
80
|
+
case "daily":
|
|
81
|
+
startDate = endDate - MS_PER_DAY;
|
|
82
|
+
break;
|
|
83
|
+
case "monthly":
|
|
84
|
+
startDate = endDate - 30 * MS_PER_DAY;
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
startDate = endDate - 7 * MS_PER_DAY;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { period, startDate, endDate };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate a knowledge digest for the specified time period.
|
|
96
|
+
* Queries the graph for entities created within the period, grouped by type.
|
|
97
|
+
*/
|
|
98
|
+
export async function generateDigest(db: SiaDb, opts?: DigestOpts): Promise<DigestResult> {
|
|
99
|
+
const { period, startDate, endDate } = resolveTimeRange(opts);
|
|
100
|
+
|
|
101
|
+
const { rows } = await db.execute(
|
|
102
|
+
`SELECT id, type, name, summary, created_at FROM graph_nodes
|
|
103
|
+
WHERE created_at >= ? AND created_at <= ?
|
|
104
|
+
AND t_valid_until IS NULL AND archived_at IS NULL
|
|
105
|
+
ORDER BY type, importance DESC`,
|
|
106
|
+
[startDate, endDate],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Group rows by type
|
|
110
|
+
const grouped = new Map<string, DigestItem[]>();
|
|
111
|
+
for (const row of rows) {
|
|
112
|
+
const type = row.type as string;
|
|
113
|
+
const item: DigestItem = {
|
|
114
|
+
id: row.id as string,
|
|
115
|
+
name: row.name as string,
|
|
116
|
+
type,
|
|
117
|
+
summary: row.summary as string,
|
|
118
|
+
created_at: row.created_at as number,
|
|
119
|
+
};
|
|
120
|
+
const list = grouped.get(type);
|
|
121
|
+
if (list) {
|
|
122
|
+
list.push(item);
|
|
123
|
+
} else {
|
|
124
|
+
grouped.set(type, [item]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build sections in stable order, only including non-empty ones
|
|
129
|
+
const sections: DigestSection[] = [];
|
|
130
|
+
for (const type of TYPE_ORDER) {
|
|
131
|
+
const items = grouped.get(type);
|
|
132
|
+
if (items && items.length > 0) {
|
|
133
|
+
sections.push({
|
|
134
|
+
title: TYPE_TITLES[type] ?? type,
|
|
135
|
+
items,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Include any types not in TYPE_ORDER (future entity types)
|
|
141
|
+
for (const [type, items] of grouped) {
|
|
142
|
+
if (!TYPE_ORDER.includes(type) && items.length > 0) {
|
|
143
|
+
sections.push({
|
|
144
|
+
title: TYPE_TITLES[type] ?? type,
|
|
145
|
+
items,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const totalEntities = rows.length;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
period,
|
|
154
|
+
startDate,
|
|
155
|
+
endDate,
|
|
156
|
+
sections,
|
|
157
|
+
totalEntities,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format a Unix-ms timestamp as an ISO date string (date portion only).
|
|
163
|
+
*/
|
|
164
|
+
function formatDate(ms: number): string {
|
|
165
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Render a digest as markdown text.
|
|
170
|
+
*/
|
|
171
|
+
export function renderDigestMarkdown(digest: DigestResult): string {
|
|
172
|
+
const lines: string[] = [];
|
|
173
|
+
|
|
174
|
+
lines.push(`# Knowledge Digest — ${digest.period}`);
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push(`**Period:** ${formatDate(digest.startDate)} to ${formatDate(digest.endDate)}`);
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push(`**Total new entities:** ${digest.totalEntities}`);
|
|
179
|
+
|
|
180
|
+
for (const section of digest.sections) {
|
|
181
|
+
const items = section.items.slice(0, 10);
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push(`## ${section.title} (${section.items.length})`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push("| Name | Summary |");
|
|
186
|
+
lines.push("|------|---------|");
|
|
187
|
+
for (const item of items) {
|
|
188
|
+
// Escape pipes in name/summary to avoid breaking the table
|
|
189
|
+
const name = item.name.replace(/\|/g, "\\|");
|
|
190
|
+
const summary = item.summary.replace(/\|/g, "\\|");
|
|
191
|
+
lines.push(`| ${name} | ${summary} |`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
lines.push("");
|
|
196
|
+
return lines.join("\n");
|
|
197
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { getConfig, writeConfig } from "@/shared/config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Disable the sia_flag capture pathway.
|
|
7
|
+
*
|
|
8
|
+
* 1. Persists `enableFlagging: false` in config.
|
|
9
|
+
* 2. Swaps the installed CLAUDE.md back to the base template
|
|
10
|
+
* (Step 4 reverts to the conditional check).
|
|
11
|
+
*
|
|
12
|
+
* Idempotent — running twice is a no-op.
|
|
13
|
+
*/
|
|
14
|
+
export async function disableFlagging(opts?: { siaHome?: string; cwd?: string }): Promise<void> {
|
|
15
|
+
const config = getConfig(opts?.siaHome);
|
|
16
|
+
|
|
17
|
+
if (config.enableFlagging === false) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
writeConfig({ enableFlagging: false }, opts?.siaHome);
|
|
22
|
+
|
|
23
|
+
// Resolve the base template relative to *this* file's directory.
|
|
24
|
+
const templatePath = resolve(import.meta.dirname, "../../agent/claude-md-template.md");
|
|
25
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
26
|
+
|
|
27
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
28
|
+
const claudeDir = join(cwd, ".claude");
|
|
29
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
writeFileSync(join(claudeDir, "CLAUDE.md"), template, "utf-8");
|
|
32
|
+
|
|
33
|
+
console.log("Flagging disabled. CLAUDE.md restored to base template.");
|
|
34
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Module: doctor — System health check with hook + provider diagnostics
|
|
2
|
+
//
|
|
3
|
+
// `npx sia doctor` reports overall system health.
|
|
4
|
+
// `npx sia doctor --providers` adds LLM provider connectivity checks.
|
|
5
|
+
//
|
|
6
|
+
// Checks: runtimes, hooks, capture mode, FTS5, ONNX model, native module,
|
|
7
|
+
// community detection backend, graph integrity, inverted dependency index,
|
|
8
|
+
// and (optionally) LLM provider health.
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
13
|
+
import { detectAgent, getRecommendedCaptureMode } from "@/hooks/agent-detect";
|
|
14
|
+
import { getHookConfig } from "@/hooks/event-router";
|
|
15
|
+
import { getDefaultLlmConfig } from "@/llm/config";
|
|
16
|
+
import type { OperationRole } from "@/llm/provider-registry";
|
|
17
|
+
import { isNativeAvailable } from "@/native/bridge";
|
|
18
|
+
|
|
19
|
+
export interface DoctorCheck {
|
|
20
|
+
name: string;
|
|
21
|
+
status: "ok" | "warn" | "error";
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DoctorReport {
|
|
26
|
+
checks: DoctorCheck[];
|
|
27
|
+
captureMode: string;
|
|
28
|
+
agent: string;
|
|
29
|
+
nativeModule: string;
|
|
30
|
+
communityBackend: string;
|
|
31
|
+
hookHealth: Array<{ event: string; type: string; status: string }>;
|
|
32
|
+
providerHealth: Array<{
|
|
33
|
+
role: string;
|
|
34
|
+
provider: string;
|
|
35
|
+
model: string;
|
|
36
|
+
status: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run the full doctor diagnostic suite.
|
|
42
|
+
*/
|
|
43
|
+
export async function runDoctor(
|
|
44
|
+
db: SiaDb | null,
|
|
45
|
+
cwd: string,
|
|
46
|
+
options?: { providers?: boolean },
|
|
47
|
+
): Promise<DoctorReport> {
|
|
48
|
+
const checks: DoctorCheck[] = [];
|
|
49
|
+
|
|
50
|
+
// 1. Detect agent
|
|
51
|
+
const agent = detectAgent(cwd);
|
|
52
|
+
const captureMode = getRecommendedCaptureMode(agent);
|
|
53
|
+
checks.push({
|
|
54
|
+
name: "Agent detection",
|
|
55
|
+
status: "ok",
|
|
56
|
+
message: `Detected: ${agent} (capture mode: ${captureMode})`,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// 2. Native module
|
|
60
|
+
const nativeStatus = isNativeAvailable();
|
|
61
|
+
checks.push({
|
|
62
|
+
name: "Native module",
|
|
63
|
+
status: nativeStatus === "typescript" ? "warn" : "ok",
|
|
64
|
+
message:
|
|
65
|
+
nativeStatus === "typescript"
|
|
66
|
+
? "Using TypeScript fallbacks (install @sia/native for 5-20x faster AST diffing)"
|
|
67
|
+
: `Loaded: ${nativeStatus}`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 3. Community detection backend
|
|
71
|
+
const communityBackend =
|
|
72
|
+
nativeStatus !== "typescript" ? "Rust Leiden via graphrs" : "JavaScript Louvain (in-process)";
|
|
73
|
+
checks.push({
|
|
74
|
+
name: "Community detection",
|
|
75
|
+
status: "ok",
|
|
76
|
+
message: communityBackend,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 4. ONNX model
|
|
80
|
+
const modelPath = join(
|
|
81
|
+
process.env.SIA_HOME ?? join(process.env.HOME ?? "~", ".sia"),
|
|
82
|
+
"models",
|
|
83
|
+
"all-MiniLM-L6-v2.onnx",
|
|
84
|
+
);
|
|
85
|
+
const modelExists = existsSync(modelPath);
|
|
86
|
+
checks.push({
|
|
87
|
+
name: "ONNX embedding model",
|
|
88
|
+
status: modelExists ? "ok" : "warn",
|
|
89
|
+
message: modelExists
|
|
90
|
+
? `Found at ${modelPath}`
|
|
91
|
+
: `Not found at ${modelPath} — run npx sia download-model`,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 5. Graph integrity (if DB available)
|
|
95
|
+
if (db) {
|
|
96
|
+
try {
|
|
97
|
+
const { rows: entityCount } = await db.execute("SELECT COUNT(*) as cnt FROM graph_nodes", []);
|
|
98
|
+
const { rows: edgeCount } = await db.execute("SELECT COUNT(*) as cnt FROM graph_edges", []);
|
|
99
|
+
const entities = (entityCount[0] as { cnt: number }).cnt;
|
|
100
|
+
const edges = (edgeCount[0] as { cnt: number }).cnt;
|
|
101
|
+
checks.push({
|
|
102
|
+
name: "Graph integrity",
|
|
103
|
+
status: "ok",
|
|
104
|
+
message: `${entities} entities, ${edges} edges`,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check for orphan edges
|
|
108
|
+
const { rows: orphans } = await db.execute(
|
|
109
|
+
`SELECT COUNT(*) as cnt FROM graph_edges e
|
|
110
|
+
WHERE NOT EXISTS (SELECT 1 FROM graph_nodes WHERE id = e.from_id)
|
|
111
|
+
OR NOT EXISTS (SELECT 1 FROM graph_nodes WHERE id = e.to_id)`,
|
|
112
|
+
[],
|
|
113
|
+
);
|
|
114
|
+
const orphanCount = (orphans[0] as { cnt: number }).cnt;
|
|
115
|
+
if (orphanCount > 0) {
|
|
116
|
+
checks.push({
|
|
117
|
+
name: "Orphan edges",
|
|
118
|
+
status: "warn",
|
|
119
|
+
message: `${orphanCount} edges reference non-existent entities`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check FTS5
|
|
124
|
+
try {
|
|
125
|
+
await db.execute("SELECT COUNT(*) FROM graph_nodes_fts", []);
|
|
126
|
+
checks.push({ name: "FTS5 index", status: "ok", message: "Operational" });
|
|
127
|
+
} catch {
|
|
128
|
+
checks.push({ name: "FTS5 index", status: "error", message: "Not available" });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check source_deps (inverted index)
|
|
132
|
+
try {
|
|
133
|
+
const { rows: depCount } = await db.execute("SELECT COUNT(*) as cnt FROM source_deps", []);
|
|
134
|
+
const deps = (depCount[0] as { cnt: number }).cnt;
|
|
135
|
+
checks.push({
|
|
136
|
+
name: "Inverted dependency index",
|
|
137
|
+
status: deps > 0 ? "ok" : "warn",
|
|
138
|
+
message: `${deps} source-to-node mappings`,
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
checks.push({
|
|
142
|
+
name: "Inverted dependency index",
|
|
143
|
+
status: "warn",
|
|
144
|
+
message: "source_deps table not found — run npx sia reindex",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
checks.push({
|
|
149
|
+
name: "Graph integrity",
|
|
150
|
+
status: "error",
|
|
151
|
+
message: `Failed to query graph: ${err instanceof Error ? err.message : String(err)}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 6. Hook health
|
|
157
|
+
const hookConfig = getHookConfig();
|
|
158
|
+
const hookHealth = Object.entries(hookConfig).map(([event, configs]) => {
|
|
159
|
+
const config = configs[0] as Record<string, unknown>;
|
|
160
|
+
const type = config.type as string;
|
|
161
|
+
// We can't actually test HTTP connectivity here without the server running,
|
|
162
|
+
// so we report the configuration status
|
|
163
|
+
return {
|
|
164
|
+
event,
|
|
165
|
+
type,
|
|
166
|
+
status:
|
|
167
|
+
type === "command"
|
|
168
|
+
? "configured (command)"
|
|
169
|
+
: `configured (${config.async ? "async" : "sync"})`,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 7. Provider health (only if --providers flag)
|
|
174
|
+
const providerHealth: DoctorReport["providerHealth"] = [];
|
|
175
|
+
if (options?.providers) {
|
|
176
|
+
const config = getDefaultLlmConfig();
|
|
177
|
+
const roles: OperationRole[] = ["summarize", "validate", "extract", "consolidate"];
|
|
178
|
+
|
|
179
|
+
for (const role of roles) {
|
|
180
|
+
const providerConfig = config.providers[role];
|
|
181
|
+
const isStandby = captureMode === "hooks" && (role === "extract" || role === "consolidate");
|
|
182
|
+
|
|
183
|
+
providerHealth.push({
|
|
184
|
+
role,
|
|
185
|
+
provider: providerConfig?.provider ?? "not configured",
|
|
186
|
+
model: providerConfig?.model ?? "not configured",
|
|
187
|
+
status: isStandby ? "standby (hooks active)" : "configured",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
checks,
|
|
194
|
+
captureMode,
|
|
195
|
+
agent,
|
|
196
|
+
nativeModule: nativeStatus,
|
|
197
|
+
communityBackend,
|
|
198
|
+
hookHealth,
|
|
199
|
+
providerHealth,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Format the doctor report as human-readable output.
|
|
205
|
+
*/
|
|
206
|
+
export function formatDoctorReport(report: DoctorReport): string {
|
|
207
|
+
const lines: string[] = [];
|
|
208
|
+
|
|
209
|
+
lines.push("Sia Doctor Report");
|
|
210
|
+
lines.push("═".repeat(50));
|
|
211
|
+
lines.push(`Capture Mode: ${report.captureMode} (${report.agent} detected)`);
|
|
212
|
+
lines.push("");
|
|
213
|
+
|
|
214
|
+
// Checks
|
|
215
|
+
for (const check of report.checks) {
|
|
216
|
+
const icon = check.status === "ok" ? "✓" : check.status === "warn" ? "⚠" : "✗";
|
|
217
|
+
lines.push(` ${icon} ${check.name}: ${check.message}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Hook configuration
|
|
221
|
+
lines.push("");
|
|
222
|
+
lines.push("Hook Configuration:");
|
|
223
|
+
for (const hook of report.hookHealth) {
|
|
224
|
+
lines.push(` ✓ ${hook.event}: ${hook.status}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Provider health (if checked)
|
|
228
|
+
if (report.providerHealth.length > 0) {
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("LLM Providers:");
|
|
231
|
+
for (const provider of report.providerHealth) {
|
|
232
|
+
const icon = provider.status.includes("standby") ? "⚡" : "✓";
|
|
233
|
+
lines.push(
|
|
234
|
+
` ${icon} ${provider.role}: ${provider.provider} / ${provider.model} — ${provider.status}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Module: download-model — downloads the all-MiniLM-L6-v2 ONNX model and tokenizer
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
statSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { SIA_HOME } from "@/shared/config";
|
|
14
|
+
|
|
15
|
+
const MODEL_URL =
|
|
16
|
+
"https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model_quantized.onnx";
|
|
17
|
+
const TOKENIZER_URL = "https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/tokenizer.json";
|
|
18
|
+
|
|
19
|
+
const MODEL_FILENAME = "all-MiniLM-L6-v2.onnx";
|
|
20
|
+
const TOKENIZER_FILENAME = "tokenizer.json";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Download a single file from `url` to `destPath`.
|
|
24
|
+
* Writes to a temporary file first, then renames to the final destination.
|
|
25
|
+
*/
|
|
26
|
+
async function downloadFile(url: string, destPath: string): Promise<void> {
|
|
27
|
+
const response = await fetch(url);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const contentLength = Number(response.headers.get("content-length") ?? 0);
|
|
33
|
+
const reader = response.body?.getReader();
|
|
34
|
+
if (!reader) {
|
|
35
|
+
throw new Error(`No response body for ${url}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const chunks: Uint8Array[] = [];
|
|
39
|
+
let received = 0;
|
|
40
|
+
let lastPercent = -1;
|
|
41
|
+
|
|
42
|
+
while (true) {
|
|
43
|
+
const { done, value } = await reader.read();
|
|
44
|
+
if (done) break;
|
|
45
|
+
chunks.push(value);
|
|
46
|
+
received += value.length;
|
|
47
|
+
|
|
48
|
+
if (contentLength > 0) {
|
|
49
|
+
const percent = Math.floor((received / contentLength) * 100);
|
|
50
|
+
if (percent !== lastPercent && percent % 10 === 0) {
|
|
51
|
+
console.log(`Downloading ${destPath}... ${percent}%`);
|
|
52
|
+
lastPercent = percent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Concatenate chunks into a single buffer
|
|
58
|
+
const buffer = new Uint8Array(received);
|
|
59
|
+
let offset = 0;
|
|
60
|
+
for (const chunk of chunks) {
|
|
61
|
+
buffer.set(chunk, offset);
|
|
62
|
+
offset += chunk.length;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Write to temp file first, then rename for atomicity
|
|
66
|
+
const tempPath = `${destPath}.tmp`;
|
|
67
|
+
writeFileSync(tempPath, buffer);
|
|
68
|
+
renameSync(tempPath, destPath);
|
|
69
|
+
|
|
70
|
+
console.log(`Downloaded ${destPath} (${received} bytes)`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Verify the SHA-256 checksum of a file against an expected hash.
|
|
75
|
+
*
|
|
76
|
+
* If the hashes match, returns without error.
|
|
77
|
+
* If the hashes do not match, deletes the file and throws an error.
|
|
78
|
+
*
|
|
79
|
+
* @param filePath Path to the file to verify.
|
|
80
|
+
* @param expectedHash Hex-encoded SHA-256 digest to compare against.
|
|
81
|
+
*/
|
|
82
|
+
export function verifyModelChecksum(filePath: string, expectedHash: string): void {
|
|
83
|
+
const fileBuffer = readFileSync(filePath);
|
|
84
|
+
const actualHash = createHash("sha256").update(fileBuffer).digest("hex");
|
|
85
|
+
if (actualHash !== expectedHash) {
|
|
86
|
+
unlinkSync(filePath);
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Checksum mismatch for ${filePath}: expected ${expectedHash}, got ${actualHash}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check whether a file exists and has size > 0.
|
|
95
|
+
*/
|
|
96
|
+
function fileExistsWithContent(filePath: string): boolean {
|
|
97
|
+
if (!existsSync(filePath)) return false;
|
|
98
|
+
const stats = statSync(filePath);
|
|
99
|
+
return stats.size > 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Download the all-MiniLM-L6-v2 ONNX model and its tokenizer.
|
|
104
|
+
*
|
|
105
|
+
* Files are saved to `{siaHome}/models/`.
|
|
106
|
+
* If both files already exist with size > 0, the download is skipped.
|
|
107
|
+
*
|
|
108
|
+
* @returns The path to the downloaded model file.
|
|
109
|
+
*/
|
|
110
|
+
export async function downloadModel(siaHome: string = SIA_HOME): Promise<string> {
|
|
111
|
+
const modelsDir = join(siaHome, "models");
|
|
112
|
+
const modelPath = join(modelsDir, MODEL_FILENAME);
|
|
113
|
+
const tokenizerPath = join(modelsDir, TOKENIZER_FILENAME);
|
|
114
|
+
|
|
115
|
+
// Ensure models directory exists
|
|
116
|
+
if (!existsSync(modelsDir)) {
|
|
117
|
+
mkdirSync(modelsDir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if model already exists
|
|
121
|
+
if (fileExistsWithContent(modelPath) && fileExistsWithContent(tokenizerPath)) {
|
|
122
|
+
console.log("Model already downloaded");
|
|
123
|
+
return modelPath;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Download model if needed
|
|
127
|
+
if (!fileExistsWithContent(modelPath)) {
|
|
128
|
+
console.log("Downloading ONNX model...");
|
|
129
|
+
await downloadFile(MODEL_URL, modelPath);
|
|
130
|
+
// TODO: Replace placeholder with the real SHA-256 hash from the HuggingFace model card:
|
|
131
|
+
// https://huggingface.co/Xenova/all-MiniLM-L6-v2/blob/main/onnx/model_quantized.onnx
|
|
132
|
+
const MODEL_EXPECTED_HASH = "CHECKSUM_NOT_YET_KNOWN";
|
|
133
|
+
if (MODEL_EXPECTED_HASH !== "CHECKSUM_NOT_YET_KNOWN") {
|
|
134
|
+
verifyModelChecksum(modelPath, MODEL_EXPECTED_HASH);
|
|
135
|
+
} else {
|
|
136
|
+
console.warn(
|
|
137
|
+
"[warn] Model checksum not configured — skipping integrity verification. " +
|
|
138
|
+
"Set the real SHA-256 hash in download-model.ts to enable verification.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Download tokenizer if needed
|
|
144
|
+
if (!fileExistsWithContent(tokenizerPath)) {
|
|
145
|
+
console.log("Downloading tokenizer...");
|
|
146
|
+
await downloadFile(TOKENIZER_URL, tokenizerPath);
|
|
147
|
+
// TODO: Replace placeholder with the real SHA-256 hash from the HuggingFace model card:
|
|
148
|
+
// https://huggingface.co/Xenova/all-MiniLM-L6-v2/blob/main/tokenizer.json
|
|
149
|
+
const TOKENIZER_EXPECTED_HASH = "CHECKSUM_NOT_YET_KNOWN";
|
|
150
|
+
if (TOKENIZER_EXPECTED_HASH !== "CHECKSUM_NOT_YET_KNOWN") {
|
|
151
|
+
verifyModelChecksum(tokenizerPath, TOKENIZER_EXPECTED_HASH);
|
|
152
|
+
} else {
|
|
153
|
+
console.warn(
|
|
154
|
+
"[warn] Tokenizer checksum not configured — skipping integrity verification. " +
|
|
155
|
+
"Set the real SHA-256 hash in download-model.ts to enable verification.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return modelPath;
|
|
161
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { getConfig, writeConfig } from "@/shared/config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Enable the sia_flag capture pathway.
|
|
7
|
+
*
|
|
8
|
+
* 1. Persists `enableFlagging: true` in config.
|
|
9
|
+
* 2. Swaps the installed CLAUDE.md to the flagging-enabled template
|
|
10
|
+
* (contains the expanded sia_flag section in Step 4).
|
|
11
|
+
*
|
|
12
|
+
* Idempotent — running twice is a no-op.
|
|
13
|
+
*/
|
|
14
|
+
export async function enableFlagging(opts?: { siaHome?: string; cwd?: string }): Promise<void> {
|
|
15
|
+
const config = getConfig(opts?.siaHome);
|
|
16
|
+
|
|
17
|
+
if (config.enableFlagging === true) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
writeConfig({ enableFlagging: true }, opts?.siaHome);
|
|
22
|
+
|
|
23
|
+
// Resolve the flagging template relative to *this* file's directory.
|
|
24
|
+
const templatePath = resolve(import.meta.dirname, "../../agent/claude-md-template-flagging.md");
|
|
25
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
26
|
+
|
|
27
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
28
|
+
const claudeDir = join(cwd, ".claude");
|
|
29
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
writeFileSync(join(claudeDir, "CLAUDE.md"), template, "utf-8");
|
|
32
|
+
|
|
33
|
+
console.log("Flagging enabled. CLAUDE.md updated with sia_flag section.");
|
|
34
|
+
}
|