@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,302 @@
|
|
|
1
|
+
// Module: config — SIA_HOME constant, SiaConfig, and config loading
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the SIA home directory.
|
|
8
|
+
* In Claude Code plugin mode, uses CLAUDE_PLUGIN_DATA for persistent storage.
|
|
9
|
+
* In standalone mode, falls back to ~/.sia.
|
|
10
|
+
*
|
|
11
|
+
* Note: SIA_HOME is evaluated once at module load time. If using
|
|
12
|
+
* CLAUDE_PLUGIN_DATA, ensure it is set before this module is first imported.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveSiaHome(): string {
|
|
15
|
+
const pluginData = process.env.CLAUDE_PLUGIN_DATA;
|
|
16
|
+
if (pluginData !== undefined && pluginData !== "") {
|
|
17
|
+
if (!isAbsolute(pluginData)) {
|
|
18
|
+
throw new Error(`CLAUDE_PLUGIN_DATA must be an absolute path, got: "${pluginData}"`);
|
|
19
|
+
}
|
|
20
|
+
return pluginData;
|
|
21
|
+
}
|
|
22
|
+
return join(homedir(), ".sia");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const SIA_HOME: string = resolveSiaHome();
|
|
26
|
+
|
|
27
|
+
/** Configuration for optional sync/replication via @libsql/client. */
|
|
28
|
+
export interface SyncConfig {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
serverUrl: string | null;
|
|
31
|
+
developerId: string | null;
|
|
32
|
+
syncInterval: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Default SyncConfig — sync disabled, no remote. */
|
|
36
|
+
export const DEFAULT_SYNC_CONFIG: SyncConfig = {
|
|
37
|
+
enabled: false,
|
|
38
|
+
serverUrl: null,
|
|
39
|
+
developerId: null,
|
|
40
|
+
syncInterval: 30,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Decay half-life configuration keyed by entity type. */
|
|
44
|
+
export interface DecayHalfLife {
|
|
45
|
+
default: number;
|
|
46
|
+
Decision: number;
|
|
47
|
+
Convention: number;
|
|
48
|
+
Bug: number;
|
|
49
|
+
Solution: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Additional language definition for the language registry. */
|
|
53
|
+
export interface AdditionalLanguage {
|
|
54
|
+
name: string;
|
|
55
|
+
extensions: string[];
|
|
56
|
+
grammar: string;
|
|
57
|
+
tier: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Configuration for the tree-sitter parser integration. */
|
|
61
|
+
export interface TreeSitterConfig {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
preferNative: boolean;
|
|
64
|
+
parseTimeoutMs: number;
|
|
65
|
+
maxCachedTrees: number;
|
|
66
|
+
wasmDir: string;
|
|
67
|
+
queryDir: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Full Sia configuration matching ARCHI section 11. */
|
|
71
|
+
export interface SiaConfig {
|
|
72
|
+
repoDir: string;
|
|
73
|
+
modelPath: string;
|
|
74
|
+
astCacheDir: string;
|
|
75
|
+
snapshotDir: string;
|
|
76
|
+
logDir: string;
|
|
77
|
+
excludePaths?: string[];
|
|
78
|
+
|
|
79
|
+
captureModel: string;
|
|
80
|
+
minExtractConfidence: number;
|
|
81
|
+
stagingPromotionConfidence: number;
|
|
82
|
+
|
|
83
|
+
decayHalfLife: DecayHalfLife;
|
|
84
|
+
archiveThreshold: number;
|
|
85
|
+
maxResponseTokens: number;
|
|
86
|
+
workingMemoryTokenBudget: number;
|
|
87
|
+
communityTriggerNodeCount: number;
|
|
88
|
+
communityMinGraphSize: number;
|
|
89
|
+
|
|
90
|
+
paranoidCapture: boolean;
|
|
91
|
+
|
|
92
|
+
enableFlagging: boolean;
|
|
93
|
+
flaggedConfidenceThreshold: number;
|
|
94
|
+
flaggedImportanceBoost: number;
|
|
95
|
+
|
|
96
|
+
airGapped: boolean;
|
|
97
|
+
|
|
98
|
+
/** Maintenance scheduler: time threshold before startup catchup triggers (ms). Default 24h. */
|
|
99
|
+
maintenanceInterval: number;
|
|
100
|
+
/** Maintenance scheduler: idle gap before opportunistic maintenance starts (ms). Default 60s. */
|
|
101
|
+
idleTimeoutMs: number;
|
|
102
|
+
/** Maintenance scheduler: minimum gap between LLM deep validation calls (ms). Default 5s. */
|
|
103
|
+
deepValidationRateMs: number;
|
|
104
|
+
|
|
105
|
+
additionalLanguages: AdditionalLanguage[];
|
|
106
|
+
|
|
107
|
+
treeSitter?: TreeSitterConfig;
|
|
108
|
+
|
|
109
|
+
claudeMdUpdatedAt: string | null;
|
|
110
|
+
|
|
111
|
+
sync: SyncConfig;
|
|
112
|
+
|
|
113
|
+
// Sandbox execution
|
|
114
|
+
sandboxTimeoutMs: number;
|
|
115
|
+
sandboxOutputMaxBytes: number;
|
|
116
|
+
contextModeThreshold: number;
|
|
117
|
+
contextModeTopK: number;
|
|
118
|
+
// Throttle
|
|
119
|
+
throttleNormalMax: number;
|
|
120
|
+
throttleReducedMax: number;
|
|
121
|
+
// Upgrade
|
|
122
|
+
upgradeReleaseUrl: string | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Valid keys for the decayHalfLife object. */
|
|
126
|
+
const VALID_DECAY_KEYS: ReadonlySet<string> = new Set([
|
|
127
|
+
"default",
|
|
128
|
+
"Decision",
|
|
129
|
+
"Convention",
|
|
130
|
+
"Bug",
|
|
131
|
+
"Solution",
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
/** Default configuration matching ARCHI section 11 values. */
|
|
135
|
+
export const DEFAULT_CONFIG: SiaConfig = {
|
|
136
|
+
repoDir: join(SIA_HOME, "repos"),
|
|
137
|
+
modelPath: join(SIA_HOME, "models", "all-MiniLM-L6-v2.onnx"),
|
|
138
|
+
astCacheDir: join(SIA_HOME, "ast-cache"),
|
|
139
|
+
snapshotDir: join(SIA_HOME, "snapshots"),
|
|
140
|
+
logDir: join(SIA_HOME, "logs"),
|
|
141
|
+
excludePaths: [],
|
|
142
|
+
|
|
143
|
+
captureModel: "claude-haiku-4-5-20251001",
|
|
144
|
+
minExtractConfidence: 0.6,
|
|
145
|
+
stagingPromotionConfidence: 0.75,
|
|
146
|
+
|
|
147
|
+
decayHalfLife: {
|
|
148
|
+
default: 30,
|
|
149
|
+
Decision: 90,
|
|
150
|
+
Convention: 60,
|
|
151
|
+
Bug: 45,
|
|
152
|
+
Solution: 45,
|
|
153
|
+
},
|
|
154
|
+
archiveThreshold: 0.05,
|
|
155
|
+
maxResponseTokens: 1500,
|
|
156
|
+
workingMemoryTokenBudget: 8000,
|
|
157
|
+
communityTriggerNodeCount: 20,
|
|
158
|
+
communityMinGraphSize: 100,
|
|
159
|
+
|
|
160
|
+
paranoidCapture: false,
|
|
161
|
+
|
|
162
|
+
enableFlagging: false,
|
|
163
|
+
flaggedConfidenceThreshold: 0.4,
|
|
164
|
+
flaggedImportanceBoost: 0.15,
|
|
165
|
+
|
|
166
|
+
airGapped: false,
|
|
167
|
+
|
|
168
|
+
maintenanceInterval: 86400000, // 24 hours
|
|
169
|
+
idleTimeoutMs: 60000, // 60 seconds
|
|
170
|
+
deepValidationRateMs: 5000, // 5 seconds
|
|
171
|
+
|
|
172
|
+
additionalLanguages: [],
|
|
173
|
+
|
|
174
|
+
treeSitter: {
|
|
175
|
+
enabled: true,
|
|
176
|
+
preferNative: true,
|
|
177
|
+
parseTimeoutMs: 5000,
|
|
178
|
+
maxCachedTrees: 500,
|
|
179
|
+
wasmDir: join(__dirname, "../../grammars/wasm"),
|
|
180
|
+
queryDir: join(__dirname, "../../grammars/queries"),
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
claudeMdUpdatedAt: null,
|
|
184
|
+
|
|
185
|
+
sync: { ...DEFAULT_SYNC_CONFIG },
|
|
186
|
+
|
|
187
|
+
sandboxTimeoutMs: 30_000,
|
|
188
|
+
sandboxOutputMaxBytes: 1_048_576,
|
|
189
|
+
contextModeThreshold: 10_240,
|
|
190
|
+
contextModeTopK: 5,
|
|
191
|
+
throttleNormalMax: 3,
|
|
192
|
+
throttleReducedMax: 8,
|
|
193
|
+
upgradeReleaseUrl: null,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Deep-merge source into target, returning a new object.
|
|
198
|
+
* Arrays from source replace target arrays (no concatenation).
|
|
199
|
+
*/
|
|
200
|
+
function deepMerge<T extends Record<string, unknown>>(
|
|
201
|
+
target: T,
|
|
202
|
+
source: Record<string, unknown>,
|
|
203
|
+
): T {
|
|
204
|
+
const result = { ...target } as Record<string, unknown>;
|
|
205
|
+
for (const key of Object.keys(source)) {
|
|
206
|
+
const srcVal = source[key];
|
|
207
|
+
const tgtVal = (target as Record<string, unknown>)[key];
|
|
208
|
+
if (
|
|
209
|
+
srcVal !== null &&
|
|
210
|
+
typeof srcVal === "object" &&
|
|
211
|
+
!Array.isArray(srcVal) &&
|
|
212
|
+
tgtVal !== null &&
|
|
213
|
+
typeof tgtVal === "object" &&
|
|
214
|
+
!Array.isArray(tgtVal)
|
|
215
|
+
) {
|
|
216
|
+
result[key] = deepMerge(tgtVal as Record<string, unknown>, srcVal as Record<string, unknown>);
|
|
217
|
+
} else {
|
|
218
|
+
result[key] = srcVal;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return result as T;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Validate decayHalfLife keys. Warns on invalid keys such as 'Architecture'.
|
|
226
|
+
*/
|
|
227
|
+
function validateDecayHalfLife(decayHalfLife: Record<string, unknown>): void {
|
|
228
|
+
for (const key of Object.keys(decayHalfLife)) {
|
|
229
|
+
if (!VALID_DECAY_KEYS.has(key)) {
|
|
230
|
+
if (key === "Architecture") {
|
|
231
|
+
console.warn(
|
|
232
|
+
"Architecture is not a valid entity type. Use Concept with tags: ['architecture'].",
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
console.warn(
|
|
236
|
+
`Invalid decayHalfLife key: '${key}'. Valid keys are: ${[...VALID_DECAY_KEYS].join(", ")}.`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Load Sia configuration from disk, merging with defaults.
|
|
245
|
+
* If the config file does not exist, returns DEFAULT_CONFIG.
|
|
246
|
+
*/
|
|
247
|
+
export function getConfig(siaHome: string = SIA_HOME): SiaConfig {
|
|
248
|
+
const configPath = join(siaHome, "config.json");
|
|
249
|
+
|
|
250
|
+
if (!existsSync(configPath)) {
|
|
251
|
+
return { ...DEFAULT_CONFIG, sync: { ...DEFAULT_SYNC_CONFIG } };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
255
|
+
let parsed: Record<string, unknown>;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Failed to parse Sia config at ${configPath}: ${err instanceof Error ? err.message : String(err)}. Delete or fix the file to use defaults.`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
parsed.decayHalfLife &&
|
|
266
|
+
typeof parsed.decayHalfLife === "object" &&
|
|
267
|
+
!Array.isArray(parsed.decayHalfLife)
|
|
268
|
+
) {
|
|
269
|
+
validateDecayHalfLife(parsed.decayHalfLife as Record<string, unknown>);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return deepMerge({ ...DEFAULT_CONFIG, sync: { ...DEFAULT_SYNC_CONFIG } }, parsed);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Write a partial config update to disk.
|
|
277
|
+
* Reads existing config, deep-merges the partial, and writes the result.
|
|
278
|
+
* Creates the directory and file if they do not exist.
|
|
279
|
+
*/
|
|
280
|
+
export function writeConfig(partial: Partial<SiaConfig>, siaHome: string = SIA_HOME): void {
|
|
281
|
+
const configPath = join(siaHome, "config.json");
|
|
282
|
+
const dir = dirname(configPath);
|
|
283
|
+
|
|
284
|
+
if (!existsSync(dir)) {
|
|
285
|
+
mkdirSync(dir, { recursive: true });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let existing: Record<string, unknown> = {};
|
|
289
|
+
if (existsSync(configPath)) {
|
|
290
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
291
|
+
try {
|
|
292
|
+
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
293
|
+
} catch (err) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Failed to parse existing Sia config at ${configPath}: ${err instanceof Error ? err.message : String(err)}. Delete or fix the file before writing new config.`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const merged = deepMerge(existing, partial as Record<string, unknown>);
|
|
301
|
+
writeFileSync(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
302
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Module: diagnostics — Reusable diagnostic check functions for sia_doctor
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// DiagnosticCheck interface
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export interface DiagnosticCheck {
|
|
11
|
+
name: string;
|
|
12
|
+
category: string;
|
|
13
|
+
status: "ok" | "warn" | "error";
|
|
14
|
+
message: string;
|
|
15
|
+
version?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// checkRuntime — spawn <binary> --version with 5s timeout
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export async function checkRuntime(name: string, binary: string): Promise<DiagnosticCheck> {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
let timedOut = false;
|
|
25
|
+
let stdout = "";
|
|
26
|
+
let stderr = "";
|
|
27
|
+
|
|
28
|
+
const child = spawn(binary, ["--version"], {
|
|
29
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
timedOut = true;
|
|
34
|
+
child.kill("SIGKILL");
|
|
35
|
+
}, 5000);
|
|
36
|
+
|
|
37
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
38
|
+
stdout += chunk.toString();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
42
|
+
stderr += chunk.toString();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on("close", (code) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
|
|
48
|
+
if (timedOut) {
|
|
49
|
+
resolve({
|
|
50
|
+
name,
|
|
51
|
+
category: "runtimes",
|
|
52
|
+
status: "warn",
|
|
53
|
+
message: `${binary} timed out after 5s`,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (code === 0 || stdout.length > 0 || stderr.length > 0) {
|
|
59
|
+
// Many tools print version to stderr (e.g. go, rustc)
|
|
60
|
+
const raw = (stdout || stderr).trim();
|
|
61
|
+
// Extract first line as version string
|
|
62
|
+
const version = raw.split("\n")[0]?.trim() ?? raw;
|
|
63
|
+
|
|
64
|
+
if (code === 0 || version.length > 0) {
|
|
65
|
+
resolve({
|
|
66
|
+
name,
|
|
67
|
+
category: "runtimes",
|
|
68
|
+
status: "ok",
|
|
69
|
+
message: `${binary} is available`,
|
|
70
|
+
version: version || undefined,
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resolve({
|
|
77
|
+
name,
|
|
78
|
+
category: "runtimes",
|
|
79
|
+
status: "warn",
|
|
80
|
+
message: `${binary} not found or returned exit code ${code}`,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on("error", () => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
resolve({
|
|
87
|
+
name,
|
|
88
|
+
category: "runtimes",
|
|
89
|
+
status: "warn",
|
|
90
|
+
message: `${binary} not found`,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// checkFts5 — verify FTS5 virtual table is accessible
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export async function checkFts5(db: SiaDb): Promise<DiagnosticCheck> {
|
|
101
|
+
// Try graph_nodes_fts first (v5 schema), fallback to entities_fts (v1 schema)
|
|
102
|
+
for (const table of ["graph_nodes_fts", "entities_fts"]) {
|
|
103
|
+
try {
|
|
104
|
+
await db.execute(`SELECT * FROM ${table} LIMIT 1`);
|
|
105
|
+
return {
|
|
106
|
+
name: "fts5",
|
|
107
|
+
category: "fts5",
|
|
108
|
+
status: "ok",
|
|
109
|
+
message: `FTS5 index (${table}) is accessible`,
|
|
110
|
+
};
|
|
111
|
+
} catch (err: unknown) {
|
|
112
|
+
const msg = (err as Error).message ?? String(err);
|
|
113
|
+
if (!msg.includes("no such table")) {
|
|
114
|
+
return {
|
|
115
|
+
name: "fts5",
|
|
116
|
+
category: "fts5",
|
|
117
|
+
status: "error",
|
|
118
|
+
message: `FTS5 check failed: ${msg}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Table doesn't exist — try next table
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
name: "fts5",
|
|
127
|
+
category: "fts5",
|
|
128
|
+
status: "warn",
|
|
129
|
+
message: "FTS5 index not found (neither graph_nodes_fts nor entities_fts exists)",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// checkOrphanEdges — count edges where from_id/to_id don't exist in graph_nodes
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
export async function checkOrphanEdges(db: SiaDb): Promise<DiagnosticCheck> {
|
|
138
|
+
try {
|
|
139
|
+
const { rows } = await db.execute(`
|
|
140
|
+
SELECT COUNT(*) as count FROM graph_edges
|
|
141
|
+
WHERE from_id NOT IN (SELECT id FROM graph_nodes)
|
|
142
|
+
OR to_id NOT IN (SELECT id FROM graph_nodes)
|
|
143
|
+
`);
|
|
144
|
+
|
|
145
|
+
const count = (rows[0]?.count as number) ?? 0;
|
|
146
|
+
|
|
147
|
+
if (count > 0) {
|
|
148
|
+
return {
|
|
149
|
+
name: "orphan_edges",
|
|
150
|
+
category: "graph_integrity",
|
|
151
|
+
status: "warn",
|
|
152
|
+
message: `${count} edge(s) reference nonexistent graph_nodes`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name: "orphan_edges",
|
|
158
|
+
category: "graph_integrity",
|
|
159
|
+
status: "ok",
|
|
160
|
+
message: "No orphan edges found",
|
|
161
|
+
};
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return {
|
|
164
|
+
name: "orphan_edges",
|
|
165
|
+
category: "graph_integrity",
|
|
166
|
+
status: "error",
|
|
167
|
+
message: `Failed to check: ${(err as Error).message}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// checkTemporalInvariants — count nodes where t_valid_from > t_valid_until
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
export async function checkTemporalInvariants(db: SiaDb): Promise<DiagnosticCheck> {
|
|
177
|
+
try {
|
|
178
|
+
const { rows } = await db.execute(`
|
|
179
|
+
SELECT COUNT(*) as count FROM graph_nodes
|
|
180
|
+
WHERE t_valid_from IS NOT NULL
|
|
181
|
+
AND t_valid_until IS NOT NULL
|
|
182
|
+
AND t_valid_from > t_valid_until
|
|
183
|
+
`);
|
|
184
|
+
|
|
185
|
+
const count = (rows[0]?.count as number) ?? 0;
|
|
186
|
+
|
|
187
|
+
if (count > 0) {
|
|
188
|
+
return {
|
|
189
|
+
name: "temporal_invariants",
|
|
190
|
+
category: "graph_integrity",
|
|
191
|
+
status: "error",
|
|
192
|
+
message: `${count} node(s) have t_valid_from > t_valid_until (temporal invariant violated)`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
name: "temporal_invariants",
|
|
198
|
+
category: "graph_integrity",
|
|
199
|
+
status: "ok",
|
|
200
|
+
message: "Temporal invariants are valid",
|
|
201
|
+
};
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
name: "temporal_invariants",
|
|
205
|
+
category: "graph_integrity",
|
|
206
|
+
status: "error",
|
|
207
|
+
message: `Failed to check: ${(err as Error).message}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Module: errors — Result type for fallible operations
|
|
2
|
+
|
|
3
|
+
/** Structured error type for all Sia operations. */
|
|
4
|
+
export interface SiaError {
|
|
5
|
+
code: string;
|
|
6
|
+
module: string;
|
|
7
|
+
operation: string;
|
|
8
|
+
message: string;
|
|
9
|
+
cause?: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Discriminated union: either success with data, or failure with error. */
|
|
13
|
+
export type Result<T, E = SiaError> = { ok: true; value: T } | { ok: false; error: E };
|
|
14
|
+
|
|
15
|
+
/** Create a successful result. */
|
|
16
|
+
export function ok<T>(value: T): Result<T, never> {
|
|
17
|
+
return { ok: true, value };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Create a failure result. */
|
|
21
|
+
export function err<E = SiaError>(error: E): Result<never, E> {
|
|
22
|
+
return { ok: false, error };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Create a SiaError. */
|
|
26
|
+
export function siaError(
|
|
27
|
+
code: string,
|
|
28
|
+
module: string,
|
|
29
|
+
operation: string,
|
|
30
|
+
message: string,
|
|
31
|
+
cause?: unknown,
|
|
32
|
+
): SiaError {
|
|
33
|
+
return { code, module, operation, message, cause };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Wrap a promise in a Result, catching any thrown error. */
|
|
37
|
+
export async function tryCatch<T>(
|
|
38
|
+
fn: () => Promise<T>,
|
|
39
|
+
module: string,
|
|
40
|
+
operation: string,
|
|
41
|
+
): Promise<Result<T>> {
|
|
42
|
+
try {
|
|
43
|
+
return ok(await fn());
|
|
44
|
+
} catch (cause) {
|
|
45
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
46
|
+
return err(siaError("UNHANDLED", module, operation, message, cause));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Module: git-utils — Git worktree detection and path resolution
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the root directory of the current git worktree (or main checkout).
|
|
8
|
+
* Uses `git rev-parse --show-toplevel` which returns the worktree root
|
|
9
|
+
* when inside a worktree, or the main checkout root in a normal repo.
|
|
10
|
+
* Returns null if not in a git repo.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveWorktreeRoot(cwd?: string): string | null {
|
|
13
|
+
try {
|
|
14
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
15
|
+
cwd: cwd ?? process.cwd(),
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
+
}).trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if the current directory is inside a git worktree (not the main checkout).
|
|
26
|
+
* Compares --git-dir with --git-common-dir. In a worktree, git-dir points to
|
|
27
|
+
* .git/worktrees/<name> while git-common-dir points to the main .git.
|
|
28
|
+
*/
|
|
29
|
+
export function isWorktree(cwd?: string): boolean {
|
|
30
|
+
try {
|
|
31
|
+
const execOpts = {
|
|
32
|
+
cwd: cwd ?? process.cwd(),
|
|
33
|
+
encoding: "utf-8" as const,
|
|
34
|
+
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
35
|
+
};
|
|
36
|
+
const gitDir = execFileSync("git", ["rev-parse", "--git-dir"], execOpts).trim();
|
|
37
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], execOpts).trim();
|
|
38
|
+
return gitDir !== commonDir && !gitDir.endsWith("/.git") && gitDir !== ".git";
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the current branch name.
|
|
46
|
+
* Returns empty string for detached HEAD.
|
|
47
|
+
*/
|
|
48
|
+
export function currentBranch(cwd?: string): string {
|
|
49
|
+
try {
|
|
50
|
+
return execFileSync("git", ["branch", "--show-current"], {
|
|
51
|
+
cwd: cwd ?? process.cwd(),
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
}).trim();
|
|
55
|
+
} catch {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the previous branch name (the branch checked out before the current one).
|
|
62
|
+
* Uses `git rev-parse --abbrev-ref @{-1}` which reads the reflog.
|
|
63
|
+
* Returns empty string if unavailable (e.g., fresh repo, no prior checkout).
|
|
64
|
+
*/
|
|
65
|
+
export function previousBranch(cwd?: string): string {
|
|
66
|
+
try {
|
|
67
|
+
return execFileSync("git", ["rev-parse", "--abbrev-ref", "@{-1}"], {
|
|
68
|
+
cwd: cwd ?? process.cwd(),
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
71
|
+
}).trim();
|
|
72
|
+
} catch {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the current HEAD commit hash (short form).
|
|
79
|
+
* Returns empty string if not in a git repo or on error.
|
|
80
|
+
*/
|
|
81
|
+
export function currentCommit(cwd?: string): string {
|
|
82
|
+
try {
|
|
83
|
+
return execFileSync("git", ["rev-parse", "--short", "HEAD"], {
|
|
84
|
+
cwd: cwd ?? process.cwd(),
|
|
85
|
+
encoding: "utf-8",
|
|
86
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
87
|
+
}).trim();
|
|
88
|
+
} catch {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the project-local graph database directory.
|
|
95
|
+
* Always `<worktree-root>/.sia-graph/`. Each worktree gets its own
|
|
96
|
+
* independent graph directory so parallel worktrees don't collide.
|
|
97
|
+
* Returns null if not inside a git repo.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveProjectGraphDir(cwd?: string): string | null {
|
|
100
|
+
const root = resolveWorktreeRoot(cwd);
|
|
101
|
+
if (!root) return null;
|
|
102
|
+
return join(root, ".sia-graph");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the list of files changed between two refs (or between HEAD~1 and HEAD).
|
|
107
|
+
* Uses `git diff --name-status` which handles adds, mods, deletes, and renames.
|
|
108
|
+
*/
|
|
109
|
+
export function getChangedFiles(
|
|
110
|
+
fromRef?: string,
|
|
111
|
+
toRef?: string,
|
|
112
|
+
cwd?: string,
|
|
113
|
+
): Array<{ status: string; path: string; oldPath?: string }> {
|
|
114
|
+
try {
|
|
115
|
+
const args = ["diff", "--name-status"];
|
|
116
|
+
if (fromRef && toRef) {
|
|
117
|
+
args.push(fromRef, toRef);
|
|
118
|
+
} else if (fromRef) {
|
|
119
|
+
args.push(fromRef, "HEAD");
|
|
120
|
+
} else {
|
|
121
|
+
args.push("HEAD~1", "HEAD");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const output = execFileSync("git", args, {
|
|
125
|
+
cwd: cwd ?? process.cwd(),
|
|
126
|
+
encoding: "utf-8",
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
}).trim();
|
|
129
|
+
|
|
130
|
+
if (!output) return [];
|
|
131
|
+
|
|
132
|
+
return output.split("\n").map((line) => {
|
|
133
|
+
const parts = line.split("\t");
|
|
134
|
+
const status = parts[0].charAt(0);
|
|
135
|
+
if (status === "R" && parts.length >= 3) {
|
|
136
|
+
return { status, path: parts[2], oldPath: parts[1] };
|
|
137
|
+
}
|
|
138
|
+
return { status, path: parts[1] ?? parts[0] };
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|