septum 0.1.4 → 0.1.6

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 (70) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -105
  3. package/bin/septum.ts +4 -4
  4. package/package.json +63 -63
  5. package/src/cli/commands/check.ts +161 -161
  6. package/src/cli/commands/hook.ts +216 -188
  7. package/src/cli/commands/ingest.ts +29 -29
  8. package/src/cli/commands/init.ts +213 -211
  9. package/src/cli/commands/locate.ts +61 -61
  10. package/src/cli/commands/query.ts +104 -64
  11. package/src/cli/commands/serve.ts +5 -5
  12. package/src/cli/commands/slice.ts +50 -48
  13. package/src/cli/commands/sync.ts +31 -31
  14. package/src/cli/index.ts +220 -210
  15. package/src/cli/templates/hooks.ts +75 -75
  16. package/src/cli/templates/rules.ts +50 -38
  17. package/src/cli/templates/skill.ts +65 -69
  18. package/src/core/boundary/evaluator.ts +429 -210
  19. package/src/core/config/loader.ts +117 -97
  20. package/src/core/config/schema.ts +39 -39
  21. package/src/core/database/client.ts +44 -44
  22. package/src/core/database/repositories/dependency.repository.ts +552 -349
  23. package/src/core/database/repositories/domain.repository.ts +275 -198
  24. package/src/core/database/repositories/file.repository.ts +117 -106
  25. package/src/core/database/repositories/meta.repository.ts +33 -33
  26. package/src/core/database/repositories/symbol.repository.ts +482 -443
  27. package/src/core/database/repository.ts +281 -260
  28. package/src/core/database/schema.ts +154 -153
  29. package/src/core/discovery/topology-detector.ts +486 -486
  30. package/src/core/ingestion/hasher.ts +5 -5
  31. package/src/core/ingestion/pipeline.ts +606 -535
  32. package/src/core/parser/boundary-tracker.ts +433 -288
  33. package/src/core/parser/extractors/base.ts +62 -62
  34. package/src/core/parser/extractors/blade.ts +158 -158
  35. package/src/core/parser/extractors/frontend.ts +147 -194
  36. package/src/core/parser/extractors/go.ts +408 -180
  37. package/src/core/parser/extractors/laravel-semantic.ts +1045 -901
  38. package/src/core/parser/extractors/mcp-cli-semantic.ts +328 -199
  39. package/src/core/parser/extractors/nestjs-semantic.ts +134 -119
  40. package/src/core/parser/extractors/php.ts +415 -153
  41. package/src/core/parser/extractors/python.ts +460 -228
  42. package/src/core/parser/extractors/registry.ts +70 -70
  43. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
  44. package/src/core/parser/extractors/typescript.ts +668 -181
  45. package/src/core/parser/tree-sitter.ts +41 -41
  46. package/src/core/parser/wasm-loader.ts +271 -0
  47. package/src/core/resolver/call-graph-tracer.ts +698 -668
  48. package/src/core/resolver/module-resolver.ts +479 -342
  49. package/src/core/resolver/path-resolver.ts +147 -147
  50. package/src/core/resolver/symbol-locator.ts +543 -543
  51. package/src/core/resolver/vertical-slice-tracer.ts +323 -285
  52. package/src/core/session/session-manager.ts +56 -56
  53. package/src/core/telemetry/telemetry.ts +86 -86
  54. package/src/core/topology/environment-detector.ts +109 -6
  55. package/src/index.ts +21 -21
  56. package/src/mcp/schemas.ts +695 -286
  57. package/src/mcp/server.ts +927 -716
  58. package/src/mcp/tools/check-boundary.ts +307 -152
  59. package/src/mcp/tools/get-domain-catalog.ts +155 -121
  60. package/src/mcp/tools/get-environment-topology.ts +3 -6
  61. package/src/mcp/tools/get-feature-context.ts +227 -116
  62. package/src/mcp/tools/get-symbol-hotspots.ts +54 -59
  63. package/src/mcp/tools/get-symbol-impact.ts +28 -26
  64. package/src/mcp/tools/get-symbol.ts +65 -63
  65. package/src/mcp/tools/locate-symbol.ts +169 -190
  66. package/src/mcp/tools/register-domain.ts +59 -64
  67. package/src/mcp/tools/register-feature.ts +91 -0
  68. package/src/mcp/tools/trace-vertical-slice.ts +74 -48
  69. package/src/types/index.ts +366 -315
  70. package/src/version.ts +3 -3
@@ -1,106 +1,117 @@
1
- import type { Database } from "bun:sqlite";
2
- import type { ArchetypeKind, DomainRecord, FileRecord } from "../../../types/index.ts";
3
-
4
- export class FileRepository {
5
- constructor(private db: Database) {}
6
-
7
- public getFileByPath(path: string): FileRecord | null {
8
- return this.db
9
- .query<FileRecord, [string]>("SELECT * FROM files WHERE path = ?")
10
- .get(path);
11
- }
12
-
13
- public getFileById(id: number): FileRecord | null {
14
- return this.db
15
- .query<FileRecord, [number]>("SELECT * FROM files WHERE id = ?")
16
- .get(id);
17
- }
18
-
19
- public getFilesByDomain(domainId: number): FileRecord[] {
20
- return this.db
21
- .query<FileRecord, [number]>("SELECT * FROM files WHERE domain_id = ? ORDER BY path ASC")
22
- .all(domainId);
23
- }
24
-
25
- public countFiles(): number {
26
- const row = this.db
27
- .query<{ count: number }, []>("SELECT COUNT(*) as count FROM files")
28
- .get();
29
- return row?.count ?? 0;
30
- }
31
-
32
- public upsertFile(
33
- domainId: number,
34
- path: string,
35
- archetype: ArchetypeKind,
36
- contentHash: string,
37
- lineCount: number,
38
- mtimeMs: number = 0,
39
- sizeBytes: number = 0
40
- ): number {
41
- const existing = this.getFileByPath(path);
42
- if (existing) {
43
- this.db
44
- .query(
45
- `UPDATE files
46
- SET domain_id = ?, archetype = ?, content_hash = ?, line_count = ?, mtime_ms = ?, size_bytes = ?, last_scanned_at = CURRENT_TIMESTAMP
47
- WHERE id = ?`
48
- )
49
- .run(domainId, archetype, contentHash, lineCount, mtimeMs, sizeBytes, existing.id);
50
- return existing.id;
51
- } else {
52
- const result = this.db
53
- .query(
54
- `INSERT INTO files (domain_id, path, archetype, content_hash, line_count, mtime_ms, size_bytes)
55
- VALUES (?, ?, ?, ?, ?, ?, ?)`
56
- )
57
- .run(domainId, path, archetype, contentHash, lineCount, mtimeMs, sizeBytes);
58
- return Number(result.lastInsertRowid);
59
- }
60
- }
61
-
62
- public updateFileMetadataOnly(id: number, mtimeMs: number, sizeBytes: number): void {
63
- this.db
64
- .query(
65
- `UPDATE files
66
- SET mtime_ms = ?, size_bytes = ?, last_scanned_at = CURRENT_TIMESTAMP
67
- WHERE id = ?`
68
- )
69
- .run(mtimeMs, sizeBytes, id);
70
- }
71
-
72
- public deleteFilesNotInPaths(domainId: number, validPaths: string[]): number {
73
- if (validPaths.length === 0) {
74
- const res = this.db.query("DELETE FROM files WHERE domain_id = ?").run(domainId);
75
- return Number(res.changes);
76
- }
77
- const existingFiles = this.getFilesByDomain(domainId);
78
- const validSet = new Set(validPaths);
79
- let deletedCount = 0;
80
- for (const f of existingFiles) {
81
- if (!validSet.has(f.path)) {
82
- this.db.query("DELETE FROM files WHERE id = ?").run(f.id);
83
- deletedCount++;
84
- }
85
- }
86
- return deletedCount;
87
- }
88
-
89
- public deleteFile(path: string): void {
90
- this.db.query("DELETE FROM files WHERE path = ?").run(path);
91
- }
92
-
93
- public getFileWithDomain(
94
- filePath: string
95
- ): { file: FileRecord; domain: DomainRecord } | null {
96
- const file = this.getFileByPath(filePath);
97
- if (!file) return null;
98
-
99
- const domain = this.db
100
- .query<DomainRecord, [number]>("SELECT * FROM domains WHERE id = ?")
101
- .get(file.domain_id);
102
- if (!domain) return null;
103
-
104
- return { file, domain };
105
- }
106
- }
1
+ import type { Database } from "bun:sqlite";
2
+ import type { ArchetypeKind, DomainRecord, FileRecord } from "../../../types/index.ts";
3
+
4
+ export class FileRepository {
5
+ constructor(private db: Database) {}
6
+
7
+ public getFileByPath(path: string): FileRecord | null {
8
+ return this.db
9
+ .query<FileRecord, [string]>("SELECT * FROM files WHERE path = ?")
10
+ .get(path);
11
+ }
12
+
13
+ public getFileById(id: number): FileRecord | null {
14
+ return this.db
15
+ .query<FileRecord, [number]>("SELECT * FROM files WHERE id = ?")
16
+ .get(id);
17
+ }
18
+
19
+ public getFilesByDomain(domainId: number): FileRecord[] {
20
+ return this.db
21
+ .query<FileRecord, [number]>("SELECT * FROM files WHERE domain_id = ? ORDER BY path ASC")
22
+ .all(domainId);
23
+ }
24
+
25
+ public getAllFiles(): FileRecord[] {
26
+ return this.db
27
+ .query<FileRecord, []>("SELECT * FROM files ORDER BY path ASC")
28
+ .all();
29
+ }
30
+
31
+ public countFiles(): number {
32
+ const row = this.db
33
+ .query<{ count: number }, []>("SELECT COUNT(*) as count FROM files")
34
+ .get();
35
+ return row?.count ?? 0;
36
+ }
37
+
38
+ public upsertFile(
39
+ domainId: number,
40
+ path: string,
41
+ archetype: ArchetypeKind,
42
+ contentHash: string,
43
+ lineCount: number,
44
+ mtimeMs: number = 0,
45
+ sizeBytes: number = 0
46
+ ): number {
47
+ const row = this.db
48
+ .query<{ id: number }, [number, string, string, string, number, number, number]>(
49
+ `INSERT INTO files (domain_id, path, archetype, content_hash, line_count, mtime_ms, size_bytes)
50
+ VALUES (?, ?, ?, ?, ?, ?, ?)
51
+ ON CONFLICT(path) DO UPDATE SET
52
+ domain_id = excluded.domain_id,
53
+ archetype = excluded.archetype,
54
+ content_hash = excluded.content_hash,
55
+ line_count = excluded.line_count,
56
+ mtime_ms = excluded.mtime_ms,
57
+ size_bytes = excluded.size_bytes,
58
+ last_scanned_at = CURRENT_TIMESTAMP
59
+ RETURNING id`
60
+ )
61
+ .get(domainId, path, archetype, contentHash, lineCount, mtimeMs, sizeBytes);
62
+ return row ? Number(row.id) : 0;
63
+ }
64
+
65
+ public updateFileMetadataOnly(id: number, mtimeMs: number, sizeBytes: number): void {
66
+ this.db
67
+ .query(
68
+ `UPDATE files
69
+ SET mtime_ms = ?, size_bytes = ?, last_scanned_at = CURRENT_TIMESTAMP
70
+ WHERE id = ?`
71
+ )
72
+ .run(mtimeMs, sizeBytes, id);
73
+ }
74
+
75
+ public deleteFilesNotInPaths(domainId: number, validPaths: string[]): number {
76
+ if (validPaths.length === 0) {
77
+ const res = this.db.query("DELETE FROM files WHERE domain_id = ?").run(domainId);
78
+ return Number(res.changes);
79
+ }
80
+ const existingFiles = this.getFilesByDomain(domainId);
81
+ const validSet = new Set(validPaths);
82
+ const idsToDelete = existingFiles.filter((f) => !validSet.has(f.path)).map((f) => f.id);
83
+ if (idsToDelete.length === 0) return 0;
84
+
85
+ const chunkSize = 200;
86
+ let totalDeleted = 0;
87
+ for (let i = 0; i < idsToDelete.length; i += chunkSize) {
88
+ const chunk = idsToDelete.slice(i, i + chunkSize);
89
+ const placeholders = chunk.map(() => "?").join(",");
90
+ const res = this.db.query(`DELETE FROM files WHERE id IN (${placeholders})`).run(...chunk);
91
+ totalDeleted += Number(res.changes);
92
+ }
93
+ return totalDeleted;
94
+ }
95
+
96
+ public deleteFile(path: string): void {
97
+ this.db.query("DELETE FROM files WHERE path = ?").run(path);
98
+ }
99
+
100
+ public deleteFileByPath(path: string): void {
101
+ this.deleteFile(path);
102
+ }
103
+
104
+ public getFileWithDomain(
105
+ filePath: string
106
+ ): { file: FileRecord; domain: DomainRecord } | null {
107
+ const file = this.getFileByPath(filePath);
108
+ if (!file) return null;
109
+
110
+ const domain = this.db
111
+ .query<DomainRecord, [number]>("SELECT * FROM domains WHERE id = ?")
112
+ .get(file.domain_id);
113
+ if (!domain) return null;
114
+
115
+ return { file, domain };
116
+ }
117
+ }
@@ -1,33 +1,33 @@
1
- import type { Database } from "bun:sqlite";
2
-
3
- export class MetaRepository {
4
- constructor(private db: Database) {}
5
-
6
- public getMeta(key: string): string | null {
7
- const row = this.db
8
- .query<{ value: string }, [string]>("SELECT value FROM repo_meta WHERE key = ?")
9
- .get(key);
10
- return row ? row.value : null;
11
- }
12
-
13
- public setMeta(key: string, value: string): void {
14
- this.db
15
- .query(
16
- `INSERT INTO repo_meta (key, value, updated_at)
17
- VALUES (?, ?, CURRENT_TIMESTAMP)
18
- ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP`
19
- )
20
- .run(key, value);
21
- }
22
-
23
- public getAllMeta(): Record<string, string> {
24
- const rows = this.db
25
- .query<{ key: string; value: string }, []>("SELECT key, value FROM repo_meta")
26
- .all();
27
- const result: Record<string, string> = {};
28
- for (const r of rows) {
29
- result[r.key] = r.value;
30
- }
31
- return result;
32
- }
33
- }
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ export class MetaRepository {
4
+ constructor(private db: Database) {}
5
+
6
+ public getMeta(key: string): string | null {
7
+ const row = this.db
8
+ .query<{ value: string }, [string]>("SELECT value FROM repo_meta WHERE key = ?")
9
+ .get(key);
10
+ return row ? row.value : null;
11
+ }
12
+
13
+ public setMeta(key: string, value: string): void {
14
+ this.db
15
+ .query(
16
+ `INSERT INTO repo_meta (key, value, updated_at)
17
+ VALUES (?, ?, CURRENT_TIMESTAMP)
18
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP`
19
+ )
20
+ .run(key, value);
21
+ }
22
+
23
+ public getAllMeta(): Record<string, string> {
24
+ const rows = this.db
25
+ .query<{ key: string; value: string }, []>("SELECT key, value FROM repo_meta")
26
+ .all();
27
+ const result: Record<string, string> = {};
28
+ for (const r of rows) {
29
+ result[r.key] = r.value;
30
+ }
31
+ return result;
32
+ }
33
+ }