septum 0.1.3 → 0.1.5

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 (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -102
  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 +188 -188
  7. package/src/cli/commands/ingest.ts +29 -29
  8. package/src/cli/commands/init.ts +211 -211
  9. package/src/cli/commands/locate.ts +61 -61
  10. package/src/cli/commands/query.ts +64 -64
  11. package/src/cli/commands/serve.ts +5 -5
  12. package/src/cli/commands/slice.ts +48 -90
  13. package/src/cli/commands/sync.ts +31 -31
  14. package/src/cli/commands/topology.ts +119 -0
  15. package/src/cli/index.ts +210 -199
  16. package/src/cli/templates/hooks.ts +75 -75
  17. package/src/cli/templates/rules.ts +38 -38
  18. package/src/cli/templates/skill.ts +69 -69
  19. package/src/core/boundary/evaluator.ts +232 -210
  20. package/src/core/config/loader.ts +97 -97
  21. package/src/core/config/schema.ts +39 -39
  22. package/src/core/database/client.ts +44 -44
  23. package/src/core/database/repositories/dependency.repository.ts +366 -349
  24. package/src/core/database/repositories/domain.repository.ts +218 -198
  25. package/src/core/database/repositories/environment.repository.ts +207 -0
  26. package/src/core/database/repositories/file.repository.ts +116 -106
  27. package/src/core/database/repositories/meta.repository.ts +33 -33
  28. package/src/core/database/repositories/symbol.repository.ts +443 -424
  29. package/src/core/database/repository.ts +264 -255
  30. package/src/core/database/schema.ts +34 -1
  31. package/src/core/discovery/topology-detector.ts +486 -486
  32. package/src/core/ingestion/hasher.ts +5 -5
  33. package/src/core/ingestion/pipeline.ts +584 -535
  34. package/src/core/parser/behavioral-analyzer.ts +119 -0
  35. package/src/core/parser/boundary-tracker.ts +288 -128
  36. package/src/core/parser/extractors/base.ts +62 -61
  37. package/src/core/parser/extractors/frontend.ts +194 -194
  38. package/src/core/parser/extractors/go.ts +229 -176
  39. package/src/core/parser/extractors/laravel-semantic.ts +1039 -585
  40. package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -199
  41. package/src/core/parser/extractors/nestjs-semantic.ts +119 -119
  42. package/src/core/parser/extractors/php.ts +153 -149
  43. package/src/core/parser/extractors/python.ts +228 -226
  44. package/src/core/parser/extractors/registry.ts +70 -70
  45. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
  46. package/src/core/parser/extractors/typescript.ts +181 -177
  47. package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
  48. package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
  49. package/src/core/parser/schema/schema-provider-registry.ts +43 -0
  50. package/src/core/parser/schema/schema-provider.interface.ts +33 -0
  51. package/src/core/parser/tree-sitter.ts +41 -41
  52. package/src/core/resolver/call-graph-tracer.ts +666 -668
  53. package/src/core/resolver/module-resolver.ts +408 -342
  54. package/src/core/resolver/symbol-locator.ts +543 -543
  55. package/src/core/resolver/vertical-slice-tracer.ts +301 -252
  56. package/src/core/session/session-manager.ts +56 -56
  57. package/src/core/telemetry/telemetry.ts +86 -86
  58. package/src/core/topology/environment-detector.ts +860 -0
  59. package/src/core/topology/types.ts +83 -0
  60. package/src/index.ts +21 -18
  61. package/src/mcp/schemas.ts +578 -238
  62. package/src/mcp/server.ts +875 -656
  63. package/src/mcp/tools/check-boundary.ts +171 -152
  64. package/src/mcp/tools/get-domain-catalog.ts +145 -121
  65. package/src/mcp/tools/get-environment-topology.ts +141 -0
  66. package/src/mcp/tools/get-feature-context.ts +212 -116
  67. package/src/mcp/tools/get-symbol-hotspots.ts +60 -49
  68. package/src/mcp/tools/get-symbol-impact.ts +28 -26
  69. package/src/mcp/tools/get-symbol.ts +65 -63
  70. package/src/mcp/tools/locate-symbol.ts +171 -190
  71. package/src/mcp/tools/register-domain.ts +68 -64
  72. package/src/mcp/tools/register-feature.ts +100 -0
  73. package/src/mcp/tools/trace-vertical-slice.ts +74 -103
  74. package/src/types/index.ts +335 -299
@@ -1,63 +1,65 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
4
- import type { GetSymbolArgs } from "../../types/index.ts";
5
-
6
- export function handleGetSymbol(
7
- repo: SeptumRepository,
8
- _config: ValidatedSeptumConfig,
9
- args: GetSymbolArgs
10
- ) {
11
- if (!args.symbol) {
12
- throw new Error(
13
- "Missing required argument: 'symbol' (e.g. 'OrderController::cancelOrder', 'OrderService', or 'cancelOrder')"
14
- );
15
- }
16
-
17
- const details = repo.getSymbolDetails(args.symbol, {
18
- domain: args.domain,
19
- includeDependencies: args.include_dependencies,
20
- });
21
-
22
- if (details) {
23
- return {
24
- content: [
25
- {
26
- type: "text" as const,
27
- text: JSON.stringify(details, null, 2),
28
- },
29
- ],
30
- };
31
- }
32
-
33
- // Fallback to fuzzy SymbolLocator to provide actionable hints
34
- const locator = new SymbolLocator(repo);
35
- const fallback = locator.locate(args.symbol, args.domain);
36
-
37
- const payload = {
38
- symbol: args.symbol,
39
- found: false,
40
- message: `Symbol '${args.symbol}' was not found in the deterministic catalog.`,
41
- suggestions: fallback.suggestions.map((s) => ({
42
- name: s.name,
43
- similarity_score: Math.round(s.similarity_score * 100) / 100,
44
- exact_range: {
45
- start_line: s.line_start,
46
- end_line: s.line_end,
47
- total_lines: s.line_end - s.line_start + 1,
48
- },
49
- signature: s.signature,
50
- file_path: s.file_path,
51
- })),
52
- sibling_methods: fallback.sibling_methods,
53
- };
54
-
55
- return {
56
- content: [
57
- {
58
- type: "text" as const,
59
- text: JSON.stringify(payload, null, 2),
60
- },
61
- ],
62
- };
63
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
4
+ import type { GetSymbolArgs } from "../../types/index.ts";
5
+
6
+ export function handleGetSymbol(
7
+ repo: SeptumRepository,
8
+ _config: ValidatedSeptumConfig,
9
+ args: GetSymbolArgs,
10
+ _workspaceRoot?: string
11
+ ) {
12
+ const symbol = (args.symbol || (args as unknown as Record<string, unknown>).query || "").toString().trim();
13
+ if (!symbol) {
14
+ throw new Error(
15
+ "Missing required argument: 'symbol' (e.g. 'OrderController::cancelOrder', 'OrderService', or 'cancelOrder')"
16
+ );
17
+ }
18
+
19
+ const details = repo.getSymbolDetails(symbol, {
20
+ domain: args.domain,
21
+ includeDependencies: args.include_dependencies,
22
+ });
23
+
24
+ if (details) {
25
+ return {
26
+ content: [
27
+ {
28
+ type: "text" as const,
29
+ text: JSON.stringify(details, null, 2),
30
+ },
31
+ ],
32
+ };
33
+ }
34
+
35
+ // Fallback to fuzzy SymbolLocator to provide actionable hints
36
+ const locator = new SymbolLocator(repo);
37
+ const fallback = locator.locate(symbol, args.domain);
38
+
39
+ const payload = {
40
+ symbol,
41
+ found: false,
42
+ message: `Symbol '${symbol}' was not found in the deterministic catalog.`,
43
+ suggestions: fallback.suggestions.map((s) => ({
44
+ name: s.name,
45
+ similarity_score: Math.round(s.similarity_score * 100) / 100,
46
+ exact_range: {
47
+ start_line: s.line_start,
48
+ end_line: s.line_end,
49
+ total_lines: s.line_end - s.line_start + 1,
50
+ },
51
+ signature: s.signature,
52
+ file_path: s.file_path,
53
+ })),
54
+ sibling_methods: fallback.sibling_methods,
55
+ };
56
+
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text" as const,
61
+ text: JSON.stringify(payload, null, 2),
62
+ },
63
+ ],
64
+ };
65
+ }
@@ -1,190 +1,171 @@
1
- import { existsSync, readdirSync } from "node:fs";
2
- import { isAbsolute, join, basename } from "node:path";
3
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
4
- import type { SeptumRepository } from "../../core/database/repository.ts";
5
- import type { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
6
- import {
7
- SymbolLocator,
8
- type SymbolLocationResult,
9
- } from "../../core/resolver/symbol-locator.ts";
10
-
11
- export interface LocateSymbolArgs {
12
- query: string;
13
- domain?: string;
14
- workspace_path?: string;
15
- }
16
-
17
- export async function handleLocateSymbol(
18
- repo: SeptumRepository,
19
- config: ValidatedSeptumConfig,
20
- args: LocateSymbolArgs,
21
- pipeline?: IngestionPipeline,
22
- workspaceRoot?: string
23
- ) {
24
- if (!args.query) {
25
- throw new Error(
26
- "Missing required argument: 'query' (e.g. 'OrderController::calculateTotal' or error log snippet)"
27
- );
28
- }
29
-
30
- const root = workspaceRoot || process.cwd();
31
-
32
- // 1. Guarded Lazy Ingestion on Empty Catalog / Cold Start
33
- if (repo.countFiles() === 0 && pipeline) {
34
- try {
35
- await pipeline.run(config);
36
- } catch {
37
- // Ingestion fallback
38
- }
39
-
40
- if (repo.countFiles() === 0) {
41
- const diagnostic = {
42
- status: "NOT_INDEXED",
43
- found: false,
44
- query: args.query,
45
- message: `Workspace '${root}' belum terindeks dalam Septum catalog.`,
46
- suggested_action: {
47
- tool: "septum_register_domain",
48
- params: { name: "core", root: "src", ingest_now: true },
49
- },
50
- agent_guidance:
51
- "Panggil tool 'septum_register_domain(name: \"core\", root: \"src\", ingest_now: true)' untuk mendaftarkan dan mengindeks codebase ini, atau jalankan 'septum init' di terminal workspace.",
52
- };
53
-
54
- return {
55
- content: [
56
- {
57
- type: "text",
58
- text: JSON.stringify(diagnostic, null, 2),
59
- },
60
- ],
61
- metadata: diagnostic,
62
- };
63
- }
64
- }
65
-
66
- const locator = new SymbolLocator(repo);
67
- let result: SymbolLocationResult = locator.locate(args.query, args.domain);
68
-
69
- // 2. JIT Delta Sync: Check if located file has changed on disk since last ingest
70
- if (result.found && result.file_path && pipeline) {
71
- const fullPath = isAbsolute(result.file_path)
72
- ? result.file_path
73
- : join(root, result.file_path);
74
- try {
75
- const reindexed = await pipeline.ingestFile(fullPath, config);
76
- if (reindexed) {
77
- result = locator.locate(args.query, args.domain);
78
- }
79
- } catch {
80
- // Silently fall back to existing result
81
- }
82
- }
83
-
84
- // 3. Fallback on Cache Miss: Search for candidate source files matching the query terms
85
- if (!result.found && pipeline) {
86
- try {
87
- const cleanTerm = args.query.replace(/[^a-zA-Z0-9_]/g, " ").trim().split(/\s+/)[0] || "";
88
- if (cleanTerm.length >= 3) {
89
- const candidateFiles = findCandidateFiles(root, cleanTerm);
90
- let anyReindexed = false;
91
- for (const candidate of candidateFiles) {
92
- const reindexed = await pipeline.ingestFile(candidate, config);
93
- if (reindexed) anyReindexed = true;
94
- }
95
- if (anyReindexed) {
96
- result = locator.locate(args.query, args.domain);
97
- }
98
- }
99
- } catch {
100
- // Ignore candidate scan errors
101
- }
102
- }
103
-
104
- let formattedText = "";
105
-
106
- if (result.found) {
107
- formattedText = `=== SYMBOL LOCATED ===\n`;
108
- formattedText += `Status: FOUND (Exact Match)\n`;
109
- formattedText += `File: ${result.file_path}\n`;
110
- if (result.exact_symbol) {
111
- formattedText += `Symbol: ${result.exact_symbol.name} [${result.exact_symbol.kind.toUpperCase()}]\n`;
112
- formattedText += `Signature: ${result.exact_symbol.signature}\n`;
113
- formattedText += `Lines: ${result.exact_symbol.line_start} - ${result.exact_symbol.line_end}\n`;
114
- }
115
- if (result.sibling_methods && result.sibling_methods.length > 0) {
116
- formattedText += `\nSibling Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
117
- formattedText += ` ${result.sibling_methods.join(", ")}\n`;
118
- }
119
- } else {
120
- formattedText = `=== SYMBOL NOT FOUND / DIAGNOSTIC RESOLUTION ===\n`;
121
- formattedText += `Status: NOT FOUND\n`;
122
- if (result.file_path) {
123
- formattedText += `Target File: ${result.file_path}\n`;
124
- formattedText += `Target Container: ${result.container_name || "Unknown"}\n`;
125
- }
126
- formattedText += `Message: ${result.message}\n`;
127
-
128
- if (result.sibling_methods && result.sibling_methods.length > 0) {
129
- formattedText += `\nExisting Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
130
- formattedText += ` ${result.sibling_methods.join(", ")}\n`;
131
- }
132
-
133
- if (result.suggestions.length > 0) {
134
- formattedText += `\nTop Suggestions (Ranked by Similarity):\n`;
135
- for (const sug of result.suggestions) {
136
- const percent = Math.round(sug.similarity_score * 100);
137
- formattedText += ` • ${sug.name} [${percent}% match] -> lines ${sug.line_start}-${sug.line_end} (${sug.signature})\n`;
138
- }
139
- }
140
- }
141
-
142
- return {
143
- content: [
144
- {
145
- type: "text",
146
- text: formattedText.trim(),
147
- },
148
- ],
149
- metadata: result,
150
- };
151
- }
152
-
153
- /**
154
- * Searches for potential candidate files whose names resemble parts of the query term.
155
- */
156
- function findCandidateFiles(root: string, term: string): string[] {
157
- const matched: string[] = [];
158
- const lowerTerm = term.toLowerCase().replace(/_/g, "");
159
- const searchDirs = ["src", "app", "lib"].map((d) => join(root, d)).filter((d) => existsSync(d));
160
-
161
- function scan(dir: string, depth: number = 0) {
162
- if (depth > 4) return;
163
- try {
164
- const entries = readdirSync(dir, { withFileTypes: true });
165
- for (const e of entries) {
166
- if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist") continue;
167
- const full = join(dir, e.name);
168
- if (e.isDirectory()) {
169
- scan(full, depth + 1);
170
- } else if (e.isFile() && /\.(ts|tsx|js|jsx|php|py|go)$/i.test(e.name)) {
171
- const lowerName = e.name.toLowerCase().replace(/[-_.]/g, "");
172
- if (lowerName.includes(lowerTerm) || lowerTerm.includes(lowerName.replace(/\.[^.]+$/, ""))) {
173
- matched.push(full);
174
- if (matched.length >= 5) return;
175
- }
176
- }
177
- }
178
- } catch {
179
- // Ignore
180
- }
181
- }
182
-
183
- for (const sDir of searchDirs) {
184
- scan(sDir);
185
- if (matched.length >= 5) break;
186
- }
187
-
188
- return matched;
189
- }
190
-
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { isAbsolute, join, basename } from "node:path";
3
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
4
+ import type { SeptumRepository } from "../../core/database/repository.ts";
5
+ import type { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
6
+ import {
7
+ SymbolLocator,
8
+ type SymbolLocationResult,
9
+ } from "../../core/resolver/symbol-locator.ts";
10
+
11
+ export interface LocateSymbolArgs {
12
+ query?: string;
13
+ symbol?: string;
14
+ domain?: string;
15
+ workspace_path?: string;
16
+ }
17
+
18
+ export async function handleLocateSymbol(
19
+ repo: SeptumRepository,
20
+ config: ValidatedSeptumConfig,
21
+ args: LocateSymbolArgs,
22
+ pipeline?: IngestionPipeline,
23
+ workspaceRoot?: string
24
+ ) {
25
+ const query = (args.query || args.symbol || "").toString().trim();
26
+ if (!query) {
27
+ throw new Error(
28
+ "Missing required argument: 'query' (e.g. 'OrderController::calculateTotal' or error log snippet)"
29
+ );
30
+ }
31
+
32
+ const root = workspaceRoot || process.cwd();
33
+
34
+ // 1. Guarded Lazy Ingestion on Empty Catalog / Cold Start
35
+ if (repo.countFiles() === 0 && pipeline) {
36
+ try {
37
+ await pipeline.run(config);
38
+ } catch {
39
+ // Ingestion fallback
40
+ }
41
+
42
+ if (repo.countFiles() === 0) {
43
+ const diagnostic = {
44
+ status: "NOT_INDEXED",
45
+ found: false,
46
+ query,
47
+ message: `Workspace '${root}' belum terindeks dalam Septum catalog.`,
48
+ suggested_action: {
49
+ tool: "septum_register_domain",
50
+ params: { name: "core", root: "src", ingest_now: true },
51
+ },
52
+ agent_guidance:
53
+ "Panggil tool 'septum_register_domain(name: \"core\", root: \"src\", ingest_now: true)' untuk mendaftarkan dan mengindeks codebase ini, atau jalankan 'septum init' di terminal workspace.",
54
+ };
55
+
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: JSON.stringify(diagnostic, null, 2),
61
+ },
62
+ ],
63
+ metadata: diagnostic,
64
+ };
65
+ }
66
+ }
67
+
68
+ const locator = new SymbolLocator(repo);
69
+ let result: SymbolLocationResult = locator.locate(query, args.domain);
70
+
71
+ // 2. JIT Delta Sync: Check if located file has changed on disk since last ingest
72
+ if (result.found && result.file_path && pipeline) {
73
+ const fullPath = isAbsolute(result.file_path)
74
+ ? result.file_path
75
+ : join(root, result.file_path);
76
+ try {
77
+ const reindexed = await pipeline.ingestFile(fullPath, config);
78
+ if (reindexed) {
79
+ result = locator.locate(query, args.domain);
80
+ }
81
+ } catch {
82
+ // Silently fall back to existing result
83
+ }
84
+ }
85
+
86
+ // 3. Fallback on Cache Miss: Search for candidate source files matching the query terms
87
+ if (!result.found && pipeline) {
88
+ try {
89
+ const cleanTerm = query.replace(/[^a-zA-Z0-9_]/g, " ").trim().split(/\s+/)[0] || "";
90
+ if (cleanTerm.length >= 3) {
91
+ const candidateFiles = findCandidateFiles(root, cleanTerm);
92
+ let anyReindexed = false;
93
+ for (const candidate of candidateFiles) {
94
+ const reindexed = await pipeline.ingestFile(candidate, config);
95
+ if (reindexed) anyReindexed = true;
96
+ }
97
+ if (anyReindexed) {
98
+ result = locator.locate(query, args.domain);
99
+ }
100
+ }
101
+ } catch {
102
+ // Ignore candidate scan errors
103
+ }
104
+ }
105
+
106
+ const summary = result.found
107
+ ? `[FOUND] ${result.exact_symbol?.name || query} in ${result.file_path} (lines ${result.exact_symbol?.line_start}-${result.exact_symbol?.line_end})`
108
+ : `[NOT FOUND] Symbol '${query}' not found. ${result.suggestions.length > 0 ? `Top suggestions: ${result.suggestions.map((s) => s.name).slice(0, 3).join(", ")}` : "No close suggestions."}`;
109
+
110
+ const payload = {
111
+ status: result.found ? "success" : "not_found",
112
+ found: result.found,
113
+ query,
114
+ file_path: result.file_path || null,
115
+ container_name: result.container_name || null,
116
+ exact_symbol: result.exact_symbol || null,
117
+ sibling_methods: result.sibling_methods || [],
118
+ suggestions: result.suggestions || [],
119
+ summary,
120
+ message: result.message || summary,
121
+ };
122
+
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text" as const,
127
+ text: JSON.stringify(payload, null, 2),
128
+ },
129
+ ],
130
+ metadata: payload,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Searches for potential candidate files whose names resemble parts of the query term.
136
+ */
137
+ function findCandidateFiles(root: string, term: string): string[] {
138
+ const matched: string[] = [];
139
+ const lowerTerm = term.toLowerCase().replace(/_/g, "");
140
+ const searchDirs = ["src", "app", "lib"].map((d) => join(root, d)).filter((d) => existsSync(d));
141
+
142
+ function scan(dir: string, depth: number = 0) {
143
+ if (depth > 4) return;
144
+ try {
145
+ const entries = readdirSync(dir, { withFileTypes: true });
146
+ for (const e of entries) {
147
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist") continue;
148
+ const full = join(dir, e.name);
149
+ if (e.isDirectory()) {
150
+ scan(full, depth + 1);
151
+ } else if (e.isFile() && /\.(ts|tsx|js|jsx|php|py|go)$/i.test(e.name)) {
152
+ const lowerName = e.name.toLowerCase().replace(/[-_.]/g, "");
153
+ if (lowerName.includes(lowerTerm) || lowerTerm.includes(lowerName.replace(/\.[^.]+$/, ""))) {
154
+ matched.push(full);
155
+ if (matched.length >= 5) return;
156
+ }
157
+ }
158
+ }
159
+ } catch {
160
+ // Ignore
161
+ }
162
+ }
163
+
164
+ for (const sDir of searchDirs) {
165
+ scan(sDir);
166
+ if (matched.length >= 5) break;
167
+ }
168
+
169
+ return matched;
170
+ }
171
+
@@ -1,64 +1,68 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import { IngestionPipeline, type IngestionMetrics } from "../../core/ingestion/pipeline.ts";
4
- import type { DomainConfig } from "../../types/index.ts";
5
-
6
- export interface RegisterDomainArgs {
7
- name: string;
8
- root: string;
9
- description?: string;
10
- allowed_dependencies?: string[];
11
- forbidden_dependencies?: string[];
12
- archetypes?: Record<string, string>;
13
- ingest_now?: boolean;
14
- }
15
-
16
- export async function handleRegisterDomain(
17
- repo: SeptumRepository,
18
- config: ValidatedSeptumConfig,
19
- args: RegisterDomainArgs
20
- ) {
21
- if (!args.name || !args.root) {
22
- throw new Error("Missing required arguments: 'name' and 'root' are required.");
23
- }
24
-
25
- const domainConfig: DomainConfig = {
26
- root: args.root,
27
- description: args.description || `Domain: ${args.name}`,
28
- allowed_dependencies: args.allowed_dependencies || [],
29
- forbidden_dependencies: args.forbidden_dependencies || [],
30
- archetypes: args.archetypes || {},
31
- };
32
-
33
- const domainId = repo.domains.upsertDomain(args.name, domainConfig);
34
-
35
- config.domains[args.name] = domainConfig;
36
-
37
- let ingestionMetrics: IngestionMetrics | null = null;
38
- if (args.ingest_now) {
39
- const pipeline = new IngestionPipeline(repo);
40
- ingestionMetrics = await pipeline.run(config);
41
- }
42
-
43
- return {
44
- content: [
45
- {
46
- type: "text" as const,
47
- text: JSON.stringify(
48
- {
49
- status: "success",
50
- message: `Domain '${args.name}' successfully registered in SQLite SSOT.${
51
- args.ingest_now ? " Initial ingestion completed." : ""
52
- }`,
53
- domain_id: domainId,
54
- domain: args.name,
55
- config: domainConfig,
56
- ...(ingestionMetrics ? { ingestion_metrics: ingestionMetrics } : {}),
57
- },
58
- null,
59
- 2
60
- ),
61
- },
62
- ],
63
- };
64
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { IngestionPipeline, type IngestionMetrics } from "../../core/ingestion/pipeline.ts";
4
+ import type { DomainConfig } from "../../types/index.ts";
5
+
6
+ export interface RegisterDomainArgs {
7
+ domain?: string;
8
+ name?: string;
9
+ root: string;
10
+ description?: string;
11
+ allowed_dependencies?: string[];
12
+ forbidden_dependencies?: string[];
13
+ archetypes?: Record<string, string>;
14
+ ingest_now?: boolean;
15
+ }
16
+
17
+ export async function handleRegisterDomain(
18
+ repo: SeptumRepository,
19
+ config: ValidatedSeptumConfig,
20
+ args: RegisterDomainArgs,
21
+ workspaceRoot: string = process.cwd()
22
+ ) {
23
+ const domainName = (args.domain || args.name || "").trim();
24
+ if (!domainName || !args.root) {
25
+ throw new Error("Missing required arguments: 'domain' (or 'name') and 'root' are required.");
26
+ }
27
+
28
+ const domainConfig = {
29
+ root: args.root,
30
+ description: args.description || `Domain: ${domainName}`,
31
+ allowed_dependencies: args.allowed_dependencies || [],
32
+ forbidden_dependencies: args.forbidden_dependencies || [],
33
+ archetypes: args.archetypes || {},
34
+ };
35
+
36
+ const domainId = repo.domains.upsertDomain(domainName, domainConfig);
37
+
38
+ config.domains[domainName] = domainConfig;
39
+
40
+ let ingestionMetrics: IngestionMetrics | null = null;
41
+ if (args.ingest_now) {
42
+ const pipeline = new IngestionPipeline(repo, workspaceRoot);
43
+ ingestionMetrics = await pipeline.run(config);
44
+ }
45
+
46
+ return {
47
+ content: [
48
+ {
49
+ type: "text" as const,
50
+ text: JSON.stringify(
51
+ {
52
+ status: "success",
53
+ message: `Domain '${domainName}' successfully registered in SQLite SSOT.${
54
+ args.ingest_now ? " Initial ingestion completed." : ""
55
+ }`,
56
+ domain_id: domainId,
57
+ domain: domainName,
58
+ name: domainName,
59
+ config: domainConfig,
60
+ ...(ingestionMetrics ? { ingestion_metrics: ingestionMetrics } : {}),
61
+ },
62
+ null,
63
+ 2
64
+ ),
65
+ },
66
+ ],
67
+ };
68
+ }