docsif 0.1.0__py3-none-any.whl

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.
Files changed (86) hide show
  1. docsif-0.1.0.dist-info/METADATA +393 -0
  2. docsif-0.1.0.dist-info/RECORD +86 -0
  3. docsif-0.1.0.dist-info/WHEEL +4 -0
  4. docsif-0.1.0.dist-info/entry_points.txt +3 -0
  5. docsif-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. sif/__init__.py +24 -0
  7. sif/_version.py +3 -0
  8. sif/cli/__init__.py +11 -0
  9. sif/cli/commands/__init__.py +18 -0
  10. sif/cli/commands/bench.py +148 -0
  11. sif/cli/commands/collection.py +360 -0
  12. sif/cli/commands/context.py +154 -0
  13. sif/cli/commands/get.py +200 -0
  14. sif/cli/commands/index.py +294 -0
  15. sif/cli/commands/ls.py +67 -0
  16. sif/cli/commands/mcp.py +93 -0
  17. sif/cli/commands/pull.py +111 -0
  18. sif/cli/commands/search.py +574 -0
  19. sif/cli/config.py +206 -0
  20. sif/cli/formatters.py +269 -0
  21. sif/cli/main.py +205 -0
  22. sif/config/__init__.py +30 -0
  23. sif/config/constants.py +52 -0
  24. sif/config/settings.py +217 -0
  25. sif/core/__init__.py +21 -0
  26. sif/core/collection.py +161 -0
  27. sif/core/context.py +167 -0
  28. sif/core/document.py +125 -0
  29. sif/core/models.py +298 -0
  30. sif/database/__init__.py +23 -0
  31. sif/database/connection.py +139 -0
  32. sif/database/database.py +136 -0
  33. sif/database/migrations.py +187 -0
  34. sif/database/repositories.py +500 -0
  35. sif/database/repository.py +171 -0
  36. sif/database/schema.py +362 -0
  37. sif/embedding/__init__.py +22 -0
  38. sif/embedding/cache.py +196 -0
  39. sif/embedding/embedder.py +328 -0
  40. sif/embedding/factory.py +92 -0
  41. sif/embedding/manager.py +200 -0
  42. sif/embedding/model.py +126 -0
  43. sif/indexing/__init__.py +21 -0
  44. sif/indexing/chunker.py +226 -0
  45. sif/indexing/indexer.py +226 -0
  46. sif/indexing/parser.py +192 -0
  47. sif/indexing/scanner.py +148 -0
  48. sif/indexing/watcher.py +156 -0
  49. sif/mcp/README.md +258 -0
  50. sif/mcp/__init__.py +9 -0
  51. sif/mcp/claude_desktop_config.json +16 -0
  52. sif/mcp/cli.py +92 -0
  53. sif/mcp/protocol.py +336 -0
  54. sif/mcp/server.py +433 -0
  55. sif/mcp/server_http.py +54 -0
  56. sif/mcp/test_mcp.py +108 -0
  57. sif/mcp/tools.py +497 -0
  58. sif/mcp/transport_http.py +424 -0
  59. sif/mcp/transport_stdio.py +292 -0
  60. sif/mcp_server/__init__.py +26 -0
  61. sif/mcp_server/handlers.py +68 -0
  62. sif/mcp_server/server.py +58 -0
  63. sif/mcp_server/tools.py +216 -0
  64. sif/mcp_server/transport.py +221 -0
  65. sif/models/__init__.py +70 -0
  66. sif/models/collection.py +51 -0
  67. sif/models/context.py +45 -0
  68. sif/models/document.py +76 -0
  69. sif/models/download.py +208 -0
  70. sif/models/embedding.py +71 -0
  71. sif/models/search.py +92 -0
  72. sif/search/__init__.py +22 -0
  73. sif/search/benchmark.py +110 -0
  74. sif/search/bm25.py +216 -0
  75. sif/search/expansion.py +157 -0
  76. sif/search/hybrid.py +323 -0
  77. sif/search/rerank.py +390 -0
  78. sif/search/rrf.py +186 -0
  79. sif/search/snippets.py +130 -0
  80. sif/search/strategy.py +97 -0
  81. sif/search/vector.py +177 -0
  82. sif/utils/__init__.py +26 -0
  83. sif/utils/logging.py +143 -0
  84. sif/utils/paths.py +82 -0
  85. sif/utils/progress.py +83 -0
  86. sif/utils/text.py +128 -0
@@ -0,0 +1,136 @@
1
+ """Database connection and management for SIF."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ from collections.abc import Generator
7
+ from contextlib import contextmanager
8
+ from pathlib import Path
9
+
10
+ import sqlite_vec
11
+
12
+ from sif.database.schema import SchemaManager
13
+
14
+
15
+ class Database:
16
+ """Main database class for SIF."""
17
+
18
+ def __init__(self, db_path: str | Path) -> None:
19
+ self.db_path = Path(db_path).expanduser().resolve()
20
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
21
+ self._connection: sqlite3.Connection | None = None
22
+ self._schema: SchemaManager | None = None
23
+
24
+ @property
25
+ def connection(self) -> sqlite3.Connection:
26
+ """Get or create database connection."""
27
+ if self._connection is None:
28
+ self._connection = sqlite3.connect(
29
+ str(self.db_path),
30
+ detect_types=sqlite3.PARSE_DECLTYPES,
31
+ check_same_thread=False,
32
+ )
33
+ self._connection.row_factory = sqlite3.Row
34
+ self._enable_extensions()
35
+ return self._connection
36
+
37
+ def _enable_extensions(self) -> None:
38
+ """Enable SQLite extensions."""
39
+ # Enable FTS5
40
+ self._connection.execute("PRAGMA foreign_keys = ON")
41
+
42
+ # Load sqlite-vec extension
43
+ try:
44
+ self._connection.enable_load_extension(True)
45
+ sqlite_vec.load(self._connection)
46
+ self._connection.enable_load_extension(False)
47
+ except Exception:
48
+ pass # sqlite-vec is optional
49
+
50
+ def init_schema(self) -> None:
51
+ """Initialize database schema."""
52
+ from sif.config.settings import get_settings # noqa: PLC0415
53
+
54
+ settings = get_settings()
55
+ self._schema = SchemaManager(self.connection, embedding_dim=settings.embedding_dim)
56
+ self._schema.create_all()
57
+
58
+ @contextmanager
59
+ def transaction(self) -> Generator[sqlite3.Connection, None, None]:
60
+ """Execute operations in a transaction."""
61
+ conn = self.connection
62
+ try:
63
+ yield conn
64
+ conn.commit()
65
+ except Exception:
66
+ conn.rollback()
67
+ raise
68
+
69
+ def close(self) -> None:
70
+ """Close database connection."""
71
+ if self._connection:
72
+ self._connection.close()
73
+ self._connection = None
74
+
75
+ def __enter__(self) -> Database:
76
+ """Context manager entry."""
77
+ return self
78
+
79
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
80
+ """Context manager exit."""
81
+ self.close()
82
+
83
+ def get_stats(self) -> dict:
84
+ """Get database statistics."""
85
+ if self._schema is None:
86
+ from sif.config.settings import get_settings # noqa: PLC0415
87
+
88
+ settings = get_settings()
89
+ self._schema = SchemaManager(self.connection, embedding_dim=settings.embedding_dim)
90
+ return self._schema.get_stats()
91
+
92
+ def reset(self) -> None:
93
+ """Reset database (drop all tables)."""
94
+ if self._schema is None:
95
+ from sif.config.settings import get_settings # noqa: PLC0415
96
+
97
+ settings = get_settings()
98
+ self._schema = SchemaManager(self.connection, embedding_dim=settings.embedding_dim)
99
+ self._schema.drop_all()
100
+ self._schema.create_all()
101
+
102
+
103
+ class DatabaseConnection:
104
+ """Lightweight database connection wrapper."""
105
+
106
+ def __init__(self, db_path: str | Path) -> None:
107
+ self.db_path = Path(db_path).expanduser().resolve()
108
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
109
+
110
+ @contextmanager
111
+ def connect(self) -> Generator[sqlite3.Connection, None, None]:
112
+ """Create a connection context."""
113
+ conn = sqlite3.connect(
114
+ str(self.db_path),
115
+ detect_types=sqlite3.PARSE_DECLTYPES,
116
+ check_same_thread=False,
117
+ )
118
+ conn.row_factory = sqlite3.Row
119
+ conn.execute("PRAGMA foreign_keys = ON")
120
+
121
+ # Load sqlite-vec extension
122
+ try:
123
+ conn.enable_load_extension(True)
124
+ sqlite_vec.load(conn)
125
+ conn.enable_load_extension(False)
126
+ except Exception:
127
+ pass # sqlite-vec is optional
128
+
129
+ try:
130
+ yield conn
131
+ conn.commit()
132
+ except Exception:
133
+ conn.rollback()
134
+ raise
135
+ finally:
136
+ conn.close()
@@ -0,0 +1,187 @@
1
+ """Database migration management."""
2
+
3
+ from sif.config.constants import CURRENT_SCHEMA_VERSION
4
+ from sif.utils.logging import get_logger
5
+
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ # Schema definition
11
+ SCHEMA_SQL = """
12
+ -- Collections table
13
+ CREATE TABLE IF NOT EXISTS collections (
14
+ id TEXT PRIMARY KEY,
15
+ name TEXT UNIQUE NOT NULL,
16
+ description TEXT,
17
+ paths TEXT NOT NULL DEFAULT '[]',
18
+ document_count INTEGER NOT NULL DEFAULT 0,
19
+ chunk_count INTEGER NOT NULL DEFAULT 0,
20
+ metadata TEXT NOT NULL DEFAULT '{}',
21
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
22
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
23
+ last_indexed_at DATETIME
24
+ );
25
+
26
+ -- Documents table
27
+ CREATE TABLE IF NOT EXISTS documents (
28
+ id TEXT PRIMARY KEY,
29
+ collection_id TEXT NOT NULL,
30
+ path TEXT NOT NULL,
31
+ content TEXT NOT NULL,
32
+ checksum TEXT NOT NULL,
33
+ file_size INTEGER NOT NULL,
34
+ metadata TEXT NOT NULL DEFAULT '{}',
35
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
36
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
37
+ indexed_at DATETIME,
38
+ FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
39
+ UNIQUE(collection_id, path)
40
+ );
41
+
42
+ -- Chunks table
43
+ CREATE TABLE IF NOT EXISTS chunks (
44
+ id TEXT PRIMARY KEY,
45
+ document_id TEXT NOT NULL,
46
+ content TEXT NOT NULL,
47
+ start_line INTEGER NOT NULL,
48
+ end_line INTEGER NOT NULL,
49
+ token_count INTEGER NOT NULL,
50
+ embedding_id TEXT,
51
+ FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
52
+ );
53
+
54
+ -- Contexts table
55
+ CREATE TABLE IF NOT EXISTS contexts (
56
+ id TEXT PRIMARY KEY,
57
+ content TEXT NOT NULL,
58
+ context_type TEXT NOT NULL,
59
+ target_id TEXT NOT NULL,
60
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
61
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
62
+ UNIQUE(context_type, target_id)
63
+ );
64
+
65
+ -- Schema version tracking
66
+ CREATE TABLE IF NOT EXISTS schema_version (
67
+ version INTEGER PRIMARY KEY,
68
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
69
+ );
70
+
71
+ -- Indexes
72
+ CREATE INDEX IF NOT EXISTS idx_documents_collection ON documents(collection_id);
73
+ CREATE INDEX IF NOT EXISTS idx_documents_path ON documents(path);
74
+ CREATE INDEX IF NOT EXISTS idx_chunks_document ON chunks(document_id);
75
+ CREATE INDEX IF NOT EXISTS idx_contexts_type ON contexts(context_type);
76
+ CREATE INDEX IF NOT EXISTS idx_contexts_target ON contexts(target_id);
77
+ """
78
+
79
+ # FTS5 virtual table
80
+ FTS_SQL = """
81
+ CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
82
+ content,
83
+ content_rowid=rowid,
84
+ tokenize='porter'
85
+ );
86
+
87
+ -- Trigger to keep FTS index in sync
88
+ CREATE TRIGGER IF NOT EXISTS documents_fts_insert
89
+ AFTER INSERT ON documents
90
+ BEGIN
91
+ INSERT INTO documents_fts(rowid, content) VALUES (new.rowid, new.content);
92
+ END;
93
+
94
+ CREATE TRIGGER IF NOT EXISTS documents_fts_update
95
+ AFTER UPDATE ON documents
96
+ BEGIN
97
+ UPDATE documents_fts SET content = new.content WHERE rowid = old.rowid;
98
+ END;
99
+
100
+ CREATE TRIGGER IF NOT EXISTS documents_fts_delete
101
+ AFTER DELETE ON documents
102
+ BEGIN
103
+ DELETE FROM documents_fts WHERE rowid = old.rowid;
104
+ END;
105
+ """
106
+
107
+ # Vector table (sqlite-vec)
108
+ VECTOR_SQL_TEMPLATE = """
109
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunk_embeddings USING vec0(
110
+ embedding_id TEXT PRIMARY KEY,
111
+ embedding FLOAT[{dim}]
112
+ );
113
+ """
114
+
115
+
116
+ class MigrationManager:
117
+ """Manages database schema migrations."""
118
+
119
+ def __init__(self, connection: "DatabaseConnection") -> None: # noqa: F821
120
+ self._connection = connection
121
+
122
+ def get_current_version(self) -> int:
123
+ """Get the current schema version."""
124
+ try:
125
+ row = self._connection.fetchone(
126
+ "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1"
127
+ )
128
+ return row["version"] if row else 0
129
+ except Exception:
130
+ return 0
131
+
132
+ def migrate(self, embedding_dim: int = 384) -> None:
133
+ """Run all pending migrations."""
134
+ current_version = self.get_current_version()
135
+
136
+ if current_version >= CURRENT_SCHEMA_VERSION:
137
+ logger.info("Database schema is up to date")
138
+ return
139
+
140
+ logger.info(
141
+ f"Migrating database from version {current_version} to {CURRENT_SCHEMA_VERSION}"
142
+ )
143
+
144
+ # Apply schema
145
+ self._apply_schema(embedding_dim)
146
+
147
+ # Update version
148
+ self._connection.execute(
149
+ "INSERT INTO schema_version (version) VALUES (?)",
150
+ (CURRENT_SCHEMA_VERSION,),
151
+ )
152
+
153
+ logger.info("Database migration completed successfully")
154
+
155
+ def _apply_schema(self, embedding_dim: int) -> None:
156
+ """Apply the database schema."""
157
+ # Apply main schema
158
+ self._connection.execute(SCHEMA_SQL)
159
+
160
+ # Apply FTS schema
161
+ self._connection.execute(FTS_SQL)
162
+
163
+ # Apply vector schema
164
+ vector_sql = VECTOR_SQL_TEMPLATE.format(dim=embedding_dim)
165
+ self._connection.execute(vector_sql)
166
+
167
+ def reset(self) -> None:
168
+ """Reset the database (drop all tables)."""
169
+ logger.warning("Resetting database - all data will be lost")
170
+
171
+ tables = [
172
+ "documents_fts",
173
+ "chunk_embeddings",
174
+ "chunks",
175
+ "documents",
176
+ "contexts",
177
+ "collections",
178
+ "schema_version",
179
+ ]
180
+
181
+ for table in tables:
182
+ try:
183
+ self._connection.execute(f"DROP TABLE IF EXISTS {table}")
184
+ except Exception as e:
185
+ logger.warning(f"Error dropping table {table}: {e}")
186
+
187
+ logger.info("Database reset completed")