septum 0.1.7 → 0.1.8
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/README.md +36 -55
- package/package.json +3 -2
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/hook.ts +59 -12
- package/src/cli/commands/init.ts +440 -121
- package/src/cli/commands/sync.ts +27 -17
- package/src/cli/commands/topology.ts +89 -47
- package/src/cli/index.ts +5 -67
- package/src/cli/scaffold/harness-config.ts +697 -0
- package/src/cli/templates/hooks.ts +6 -6
- package/src/cli/templates/rules.ts +8 -8
- package/src/cli/templates/skill.ts +11 -18
- package/src/cli/ui/prompts.ts +122 -0
- package/src/core/boundary/staged-auditor.ts +5 -2
- package/src/core/database/client.ts +8 -0
- package/src/core/database/repositories/dependency.repository.ts +77 -13
- package/src/core/database/repositories/domain.repository.ts +86 -33
- package/src/core/database/repositories/symbol.repository.ts +128 -117
- package/src/core/database/repository.ts +17 -5
- package/src/core/database/schema.ts +1 -1
- package/src/core/discovery/topology-detector.ts +71 -19
- package/src/core/ingestion/pipeline.ts +228 -94
- package/src/core/parser/extractors/laravel-semantic.ts +9 -8
- package/src/core/parser/extractors/mcp-cli-semantic.ts +56 -16
- package/src/core/parser/extractors/nestjs-semantic.ts +3 -2
- package/src/core/parser/schema/providers/laravel-migration-provider.ts +4 -3
- package/src/core/parser/schema/providers/prisma-schema-provider.ts +2 -1
- package/src/core/resolver/call-graph-tracer.ts +4 -3
- package/src/core/resolver/path-resolver.ts +57 -4
- package/src/core/resources/ignore.ts +35 -0
- package/src/core/resources/limits.ts +14 -0
- package/src/core/resources/source-files.ts +62 -0
- package/src/core/resources/work-queue.ts +74 -0
- package/src/core/session/session-manager.ts +19 -6
- package/src/core/telemetry/telemetry.ts +12 -1
- package/src/core/topology/environment-detector.ts +21 -11
- package/src/mcp/schemas.ts +381 -8
- package/src/mcp/server.ts +321 -535
- package/src/mcp/tools/boundary.ts +17 -0
- package/src/mcp/tools/check-boundary.ts +83 -11
- package/src/mcp/tools/domain.ts +40 -0
- package/src/mcp/tools/feature.ts +67 -0
- package/src/mcp/tools/get-domain-catalog.ts +9 -6
- package/src/mcp/tools/get-environment-topology.ts +2 -2
- package/src/mcp/tools/get-feature-context.ts +9 -12
- package/src/mcp/tools/get-symbol-impact.ts +5 -2
- package/src/mcp/tools/get-symbol.ts +2 -1
- package/src/mcp/tools/locate-symbol.ts +3 -3
- package/src/mcp/tools/register-domain.ts +25 -9
- package/src/mcp/tools/register-feature.ts +34 -17
- package/src/mcp/tools/slice.ts +15 -0
- package/src/mcp/tools/symbol.ts +82 -0
- package/src/mcp/workspace-manager.ts +159 -0
- package/src/mcp/workspace-watcher.ts +202 -0
- package/src/types/index.ts +1 -0
- package/src/cli/commands/ingest.ts +0 -29
- package/src/cli/commands/locate.ts +0 -61
- package/src/cli/commands/query.ts +0 -104
- package/src/cli/commands/slice.ts +0 -50
package/src/mcp/server.ts
CHANGED
|
@@ -1,63 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkspaceManager, resolveServerWorkspace, isWorkspaceInitialized, type WorkspaceLease } from "./workspace-manager.ts";
|
|
2
|
+
import { RESOURCE_LIMITS } from "../core/resources/limits.ts";
|
|
3
3
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import {
|
|
6
6
|
CallToolRequestSchema,
|
|
7
7
|
ListToolsRequestSchema,
|
|
8
8
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { BoundaryEvaluator } from "../core/boundary/evaluator.ts";
|
|
10
|
-
import { ModuleResolver } from "../core/resolver/module-resolver.ts";
|
|
11
9
|
import { ConfigLoader } from "../core/config/loader.ts";
|
|
12
|
-
import { SeptumDatabase } from "../core/database/client.ts";
|
|
13
|
-
import { SeptumRepository } from "../core/database/repository.ts";
|
|
14
|
-
import { IngestionPipeline } from "../core/ingestion/pipeline.ts";
|
|
15
|
-
import { ASTParserEngine } from "../core/parser/tree-sitter.ts";
|
|
16
10
|
import { SessionManager } from "../core/session/session-manager.ts";
|
|
17
11
|
import { SeptumTelemetry } from "../core/telemetry/telemetry.ts";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import { handleLocateSymbol } from "./tools/locate-symbol.ts";
|
|
25
|
-
import { handleTraceVerticalSlice } from "./tools/trace-vertical-slice.ts";
|
|
26
|
-
import { handleGetSymbolHotspots } from "./tools/get-symbol-hotspots.ts";
|
|
27
|
-
import { handleGetSymbol } from "./tools/get-symbol.ts";
|
|
28
|
-
import { handleGetSymbolImpact } from "./tools/get-symbol-impact.ts";
|
|
29
|
-
import { handleRegisterDomain } from "./tools/register-domain.ts";
|
|
30
|
-
import { handleRegisterFeature } from "./tools/register-feature.ts";
|
|
31
|
-
import { handleGetEnvironmentTopology } from "./tools/get-environment-topology.ts";
|
|
12
|
+
import { handleDomainTool } from "./tools/domain.ts";
|
|
13
|
+
import { handleSymbolTool } from "./tools/symbol.ts";
|
|
14
|
+
import { handleSliceTool } from "./tools/slice.ts";
|
|
15
|
+
import { handleBoundaryTool } from "./tools/boundary.ts";
|
|
16
|
+
import { handleFeatureTool } from "./tools/feature.ts";
|
|
17
|
+
|
|
32
18
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
GetSymbolImpactSchema,
|
|
39
|
-
GetSymbolSchema,
|
|
40
|
-
LocateSymbolSchema,
|
|
41
|
-
RegisterDomainSchema,
|
|
42
|
-
RegisterFeatureSchema,
|
|
43
|
-
TraceVerticalSliceSchema,
|
|
19
|
+
DomainToolSchema,
|
|
20
|
+
SymbolToolSchema,
|
|
21
|
+
SliceToolSchema,
|
|
22
|
+
BoundaryToolSchema,
|
|
23
|
+
FeatureToolSchema,
|
|
44
24
|
} from "./schemas.ts";
|
|
45
25
|
import { SEPTUM_VERSION } from "../version.ts";
|
|
46
|
-
import { resolveWorkspaceRoot } from "../core/resolver/path-resolver.ts";
|
|
47
|
-
import type { ValidatedSeptumConfig } from "../core/config/schema.ts";
|
|
48
|
-
|
|
49
|
-
interface WorkspaceContext {
|
|
50
|
-
root: string;
|
|
51
|
-
config: ValidatedSeptumConfig;
|
|
52
|
-
db: SeptumDatabase;
|
|
53
|
-
repo: SeptumRepository;
|
|
54
|
-
evaluator: BoundaryEvaluator;
|
|
55
|
-
pipeline: IngestionPipeline;
|
|
56
|
-
ready: Promise<void>;
|
|
57
|
-
watcher: { flush: () => Promise<void>; close: () => void };
|
|
58
|
-
}
|
|
59
26
|
|
|
60
|
-
const
|
|
27
|
+
const TOOL_SCHEMAS = {
|
|
28
|
+
septum_domain: DomainToolSchema,
|
|
29
|
+
septum_symbol: SymbolToolSchema,
|
|
30
|
+
septum_slice: SliceToolSchema,
|
|
31
|
+
septum_boundary: BoundaryToolSchema,
|
|
32
|
+
septum_feature: FeatureToolSchema,
|
|
33
|
+
};
|
|
61
34
|
|
|
62
35
|
export function extractPathHint(args: unknown): string | undefined {
|
|
63
36
|
if (!args) return undefined;
|
|
@@ -130,112 +103,16 @@ export function extractPathHint(args: unknown): string | undefined {
|
|
|
130
103
|
return undefined;
|
|
131
104
|
}
|
|
132
105
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
let closed = false;
|
|
138
|
-
let needsFullSync = false;
|
|
139
|
-
const ignored = new Set(["node_modules", "vendor", ".git", ".septum", ".strata", "dist", "build", ".output", ".nuxt", ".next", ".cache", "coverage", ".turbo"]);
|
|
140
|
-
let watcher: FSWatcher | null = null;
|
|
141
|
-
let polling: ReturnType<typeof setInterval> | null = null;
|
|
142
|
-
|
|
143
|
-
const flush = async (): Promise<void> => {
|
|
144
|
-
if (timer) { clearTimeout(timer); timer = null; }
|
|
145
|
-
if (flushing) return flushing;
|
|
146
|
-
flushing = (async () => {
|
|
147
|
-
await ready;
|
|
148
|
-
if (!closed && (!watcher || needsFullSync || repo.getMeta("sync_error"))) {
|
|
149
|
-
needsFullSync = false;
|
|
150
|
-
await pipeline.run(ConfigLoader.load(undefined, root), { verifyContent: true });
|
|
151
|
-
repo.setMeta("sync_error", "");
|
|
152
|
-
}
|
|
153
|
-
while (!closed && pending.size > 0) {
|
|
154
|
-
const paths = [...pending];
|
|
155
|
-
pending.clear();
|
|
156
|
-
const config = ConfigLoader.load(undefined, root);
|
|
157
|
-
for (const filename of paths) {
|
|
158
|
-
const target = resolve(root, filename);
|
|
159
|
-
const relPath = relative(root, target).replace(/\\/g, "/");
|
|
160
|
-
if (!existsSync(target)) {
|
|
161
|
-
repo.runInTransaction(() => { repo.deleteFileByPath(relPath); repo.clearVerticalSlices();
|
|
162
|
-
const unsupported = JSON.parse(repo.getMeta("unsupported_files") || "[]") as string[];
|
|
163
|
-
repo.setMeta("unsupported_files", JSON.stringify(unsupported.filter(path => path !== relPath))); });
|
|
164
|
-
} else {
|
|
165
|
-
await pipeline.ingestFile(target, config);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
})();
|
|
170
|
-
try { await flushing; } finally { flushing = null; }
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const usePolling = (error: unknown) => {
|
|
174
|
-
watcher?.close();
|
|
175
|
-
watcher = null;
|
|
176
|
-
repo.setMeta("watcher_mode", "polling");
|
|
177
|
-
console.error(`[Septum Watcher Warning] Using polling: ${String(error)}`);
|
|
178
|
-
if (!polling) polling = setInterval(() => {
|
|
179
|
-
void flush().catch(failure => repo.setMeta("sync_error", String(failure)));
|
|
180
|
-
}, 1000);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
watcher = watch(root, { recursive: true }, (_event, filename) => {
|
|
185
|
-
if (!filename || closed) return;
|
|
186
|
-
const norm = filename.replace(/\\/g, "/");
|
|
187
|
-
if (norm.split("/").some(segment => ignored.has(segment))) return;
|
|
188
|
-
const target = resolve(root, norm);
|
|
189
|
-
let directory = false;
|
|
190
|
-
try { directory = statSync(target).isDirectory(); } catch { /* The path may have been removed before the event arrives. */ }
|
|
191
|
-
if (directory || (!ASTParserEngine.isSourceFile(norm) && (repo.getAllFiles().some(file => file.path.startsWith(norm + "/")) || (JSON.parse(repo.getMeta("unsupported_files") || "[]") as string[]).some(file => file.startsWith(norm + "/"))))) {
|
|
192
|
-
needsFullSync = true;
|
|
193
|
-
} else if (ASTParserEngine.isSourceFile(norm)) {
|
|
194
|
-
pending.add(norm);
|
|
195
|
-
} else {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (timer) clearTimeout(timer);
|
|
199
|
-
timer = setTimeout(() => {
|
|
200
|
-
void flush().catch(error => {
|
|
201
|
-
repo.setMeta("sync_error", String(error));
|
|
202
|
-
console.error(`[Septum Watcher Warning] ${String(error)}`);
|
|
203
|
-
});
|
|
204
|
-
}, 300);
|
|
205
|
-
});
|
|
206
|
-
repo.setMeta("watcher_mode", "native");
|
|
207
|
-
watcher.on("error", usePolling);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
usePolling(error);
|
|
106
|
+
function countLines(text: string): number {
|
|
107
|
+
let count = 1;
|
|
108
|
+
for (let i = 0; i < text.length; i++) {
|
|
109
|
+
if (text.charCodeAt(i) === 10) count++;
|
|
210
110
|
}
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function getWorkspaceContext(pathHint?: string): WorkspaceContext {
|
|
215
|
-
const root = resolveWorkspaceRoot(pathHint);
|
|
216
|
-
let ctx = workspaceContextCache.get(root);
|
|
217
|
-
if (ctx) {
|
|
218
|
-
return ctx;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const config = ConfigLoader.load(undefined, root);
|
|
222
|
-
const rawDbPath = config.settings.db_path || ".septum/septum.db";
|
|
223
|
-
const resolvedDbPath = resolve(root, rawDbPath);
|
|
224
|
-
const db = new SeptumDatabase(resolvedDbPath);
|
|
225
|
-
const repo = new SeptumRepository(db.raw);
|
|
226
|
-
const evaluator = new BoundaryEvaluator(repo, new ModuleResolver(root, repo));
|
|
227
|
-
const pipeline = new IngestionPipeline(repo, root);
|
|
228
|
-
|
|
229
|
-
const ready = pipeline.run(config, { verifyContent: true }).then(() => {});
|
|
230
|
-
const watcher = createWorkspaceWatcher(root, repo, pipeline, ready);
|
|
231
|
-
ctx = { root, config, db, repo, evaluator, pipeline, ready, watcher };
|
|
232
|
-
workspaceContextCache.set(root, ctx);
|
|
233
|
-
return ctx;
|
|
111
|
+
return count;
|
|
234
112
|
}
|
|
235
113
|
|
|
236
114
|
export async function runMCPServer(): Promise<void> {
|
|
237
|
-
const
|
|
238
|
-
await initialCtx.ready;
|
|
115
|
+
const manager = new WorkspaceManager();
|
|
239
116
|
|
|
240
117
|
const server = new Server(
|
|
241
118
|
{
|
|
@@ -253,425 +130,377 @@ export async function runMCPServer(): Promise<void> {
|
|
|
253
130
|
return {
|
|
254
131
|
tools: [
|
|
255
132
|
{
|
|
256
|
-
name: "
|
|
133
|
+
name: "septum_domain",
|
|
257
134
|
description:
|
|
258
|
-
"
|
|
135
|
+
"Domain Catalog, Registration & Environment Topology Engine. Manages macro architecture, domain topology, bounded-context registration, and infrastructure plane (Edge, Gateway, Host, Runtime, Storage, Telemetry). Token-efficient (< 400 tokens).",
|
|
259
136
|
inputSchema: {
|
|
260
137
|
type: "object",
|
|
261
138
|
properties: {
|
|
262
|
-
|
|
139
|
+
action: {
|
|
263
140
|
type: "string",
|
|
264
|
-
|
|
141
|
+
enum: ["catalog", "register", "topology"],
|
|
142
|
+
default: "catalog",
|
|
143
|
+
description: "Sub-operation to execute: 'catalog' (inspect domain/macro map), 'register' (dynamically register bounded-context domain), or 'topology' (6-layer infrastructure plane).",
|
|
265
144
|
},
|
|
266
|
-
|
|
145
|
+
domain: {
|
|
267
146
|
type: "string",
|
|
268
|
-
description: "
|
|
147
|
+
description: "Target domain name (e.g. 'orders', 'core'). If omitted in catalog action, returns project-wide Macro Semantic Map.",
|
|
269
148
|
},
|
|
270
149
|
detail_level: {
|
|
271
150
|
type: "string",
|
|
272
151
|
enum: ["compact", "full"],
|
|
273
152
|
default: "compact",
|
|
274
|
-
description: "Catalog detail level: 'compact' (default, token-efficient summary with file connections) or 'full' (includes full
|
|
153
|
+
description: "Catalog detail level: 'compact' (default, token-efficient summary with file connections) or 'full' (includes full signatures).",
|
|
275
154
|
},
|
|
276
|
-
|
|
155
|
+
archetype_filter: {
|
|
277
156
|
type: "string",
|
|
278
|
-
description: "Optional
|
|
157
|
+
description: "Optional archetype filter (e.g. 'service', 'model', 'controller', 'repository').",
|
|
279
158
|
},
|
|
280
|
-
|
|
281
|
-
required: [],
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: "septum_get_feature_context",
|
|
286
|
-
description:
|
|
287
|
-
"Returns authoritative task/feature context (< 300 tokens) including allowed_touchpoints, reusable symbols with signatures, and contracts. Use this when implementing a specific feature.",
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: "object",
|
|
290
|
-
properties: {
|
|
291
|
-
feature_key: {
|
|
159
|
+
file_filter: {
|
|
292
160
|
type: "string",
|
|
293
|
-
description:
|
|
294
|
-
"Unique identifier for the feature to inspect (e.g. 'checkout_flow'). Symmetrically accepts 'feature'.",
|
|
161
|
+
description: "Optional substring filter to inspect specific file(s) within the domain.",
|
|
295
162
|
},
|
|
296
|
-
|
|
163
|
+
root: {
|
|
297
164
|
type: "string",
|
|
298
|
-
description: "
|
|
165
|
+
description: "Root directory path for domain registration (action='register').",
|
|
299
166
|
},
|
|
300
|
-
|
|
301
|
-
required: [],
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
name: "septum_check_boundary",
|
|
306
|
-
description:
|
|
307
|
-
"Pre-flight boundary check to verify proposed file changes and imports against domain rules and feature touchpoints, or audit the entire repository / staged files / domain for cross-domain and circular dependencies.",
|
|
308
|
-
inputSchema: {
|
|
309
|
-
type: "object",
|
|
310
|
-
properties: {
|
|
311
|
-
file_path: {
|
|
167
|
+
description: {
|
|
312
168
|
type: "string",
|
|
313
|
-
description: "
|
|
169
|
+
description: "Architectural purpose of the domain (action='register').",
|
|
314
170
|
},
|
|
315
|
-
|
|
171
|
+
allowed_dependencies: {
|
|
316
172
|
type: "array",
|
|
317
173
|
items: { type: "string" },
|
|
318
|
-
description: "List of
|
|
174
|
+
description: "List of domain names this domain is allowed to import (action='register').",
|
|
319
175
|
},
|
|
320
|
-
|
|
176
|
+
forbidden_dependencies: {
|
|
321
177
|
type: "array",
|
|
322
178
|
items: { type: "string" },
|
|
323
|
-
description: "List of
|
|
179
|
+
description: "List of domain names strictly forbidden from being imported (action='register').",
|
|
324
180
|
},
|
|
325
|
-
|
|
326
|
-
type: "
|
|
327
|
-
description: "
|
|
181
|
+
archetypes: {
|
|
182
|
+
type: "object",
|
|
183
|
+
description: "Layer archetypes mapping (action='register').",
|
|
328
184
|
},
|
|
329
|
-
|
|
330
|
-
type: "
|
|
331
|
-
|
|
332
|
-
description: "
|
|
185
|
+
ingest_now: {
|
|
186
|
+
type: "boolean",
|
|
187
|
+
default: false,
|
|
188
|
+
description: "Whether to immediately run AST ingestion on domain files upon registration.",
|
|
333
189
|
},
|
|
334
|
-
|
|
190
|
+
resolve: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
layer: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ["edge", "gateway", "host", "runtime", "storage", "telemetry"],
|
|
196
|
+
},
|
|
197
|
+
platform: { type: "string" },
|
|
198
|
+
constraints: { type: "array", items: { type: "string" } },
|
|
199
|
+
},
|
|
200
|
+
description: "Record and verify infrastructure layer in SQLite SSOT (action='topology').",
|
|
201
|
+
},
|
|
202
|
+
resolve_node: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
id: { type: "string" },
|
|
206
|
+
layer: { type: "string", enum: ["edge", "gateway", "host", "runtime", "storage", "telemetry"] },
|
|
207
|
+
name: { type: "string" },
|
|
208
|
+
platform: { type: "string" },
|
|
209
|
+
domain_or_ip: { type: "string" },
|
|
210
|
+
role: { type: "string" },
|
|
211
|
+
connected_to: { type: "array", items: { type: "string" } },
|
|
212
|
+
constraints: { type: "array", items: { type: "string" } },
|
|
213
|
+
},
|
|
214
|
+
description: "Record distributed topology node in SQLite SSOT (action='topology').",
|
|
215
|
+
},
|
|
216
|
+
refresh: {
|
|
217
|
+
type: "boolean",
|
|
218
|
+
description: "Force re-detection from codebase files, ignoring cached status (action='topology').",
|
|
219
|
+
},
|
|
220
|
+
workspace_path: {
|
|
335
221
|
type: "string",
|
|
336
|
-
description: "
|
|
222
|
+
description: "Explicit workspace root path. Specifies the target project root in multi-root environments.",
|
|
337
223
|
},
|
|
338
224
|
},
|
|
225
|
+
required: [],
|
|
339
226
|
},
|
|
340
227
|
},
|
|
341
228
|
{
|
|
342
|
-
name: "
|
|
229
|
+
name: "septum_symbol",
|
|
343
230
|
description:
|
|
344
|
-
"
|
|
231
|
+
"Unified Symbol Engine: single entrypoint for symbol location, full contract inspection, refactoring blast radius (impact), and Single Responsibility / God Function complexity hotspots.",
|
|
345
232
|
inputSchema: {
|
|
346
233
|
type: "object",
|
|
347
234
|
properties: {
|
|
348
|
-
|
|
349
|
-
type: "
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
type: "number",
|
|
354
|
-
description: "Minimum control-flow nesting depth (e.g. nested if/for/while/try > 4).",
|
|
235
|
+
action: {
|
|
236
|
+
type: "string",
|
|
237
|
+
enum: ["locate", "inspect", "impact", "hotspots"],
|
|
238
|
+
default: "locate",
|
|
239
|
+
description: "Sub-operation: 'locate' (fuzzy search/error log resolution), 'inspect' (physical coordinates & signatures), 'impact' (blast radius & inbound callers), or 'hotspots' (complexity & SRP smells).",
|
|
355
240
|
},
|
|
356
|
-
|
|
241
|
+
query: {
|
|
357
242
|
type: "string",
|
|
358
|
-
description: "
|
|
243
|
+
description: "Symbol name, call syntax (e.g. 'OrderController::cancelOrder'), or search query (for locate, inspect, impact).",
|
|
359
244
|
},
|
|
360
245
|
domain: {
|
|
361
246
|
type: "string",
|
|
362
|
-
description: "
|
|
247
|
+
description: "Optional domain filter (e.g. 'orders').",
|
|
363
248
|
},
|
|
364
|
-
|
|
365
|
-
type: "
|
|
366
|
-
|
|
249
|
+
include_dependencies: {
|
|
250
|
+
type: "boolean",
|
|
251
|
+
default: true,
|
|
252
|
+
description: "Whether to include injected dependencies and outbound calls (action='inspect').",
|
|
367
253
|
},
|
|
368
|
-
|
|
369
|
-
type: "
|
|
370
|
-
|
|
371
|
-
description: "Sorting criteria: 'risk_score' (combines lines & nesting depth), 'nesting', or 'lines'. Default: 'risk_score'.",
|
|
254
|
+
min_lines: {
|
|
255
|
+
type: "number",
|
|
256
|
+
description: "Minimum physical lines of code threshold (action='hotspots', default: 30).",
|
|
372
257
|
},
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
{
|
|
377
|
-
name: "septum_clear_feature_context",
|
|
378
|
-
description:
|
|
379
|
-
"Releases the active feature session lock (.septum/session.json) once feature implementation or editing is complete.",
|
|
380
|
-
inputSchema: {
|
|
381
|
-
type: "object",
|
|
382
|
-
properties: {},
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
name: "septum_locate_symbol",
|
|
387
|
-
description:
|
|
388
|
-
"Locates a symbol or resolves diagnostic error logs (e.g. 'OrderController::calculateTotal()' or 'Method X::y() does not exist'). Returns target file path, existing sibling methods, and ranked alternative suggestions when a method doesn't exist.",
|
|
389
|
-
inputSchema: {
|
|
390
|
-
type: "object",
|
|
391
|
-
properties: {
|
|
392
|
-
query: {
|
|
393
|
-
type: "string",
|
|
394
|
-
description:
|
|
395
|
-
"Symbol name, call syntax (e.g. 'OrderController::calculateTotal'), or error log snippet. Symmetrically accepts 'symbol'.",
|
|
258
|
+
min_nesting: {
|
|
259
|
+
type: "number",
|
|
260
|
+
description: "Minimum control-flow nesting depth threshold (action='hotspots').",
|
|
396
261
|
},
|
|
397
|
-
|
|
262
|
+
kind: {
|
|
398
263
|
type: "string",
|
|
399
|
-
description: "
|
|
264
|
+
description: "Filter symbol kind: 'function' | 'method' | 'class' | 'struct' | 'interface' (action='hotspots').",
|
|
400
265
|
},
|
|
401
|
-
|
|
266
|
+
sort_by: {
|
|
402
267
|
type: "string",
|
|
403
|
-
|
|
268
|
+
enum: ["risk_score", "line_count", "lines", "nesting"],
|
|
269
|
+
default: "risk_score",
|
|
270
|
+
description: "Sorting criteria for hotspots (action='hotspots').",
|
|
404
271
|
},
|
|
405
272
|
limit: {
|
|
406
273
|
type: "number",
|
|
407
|
-
description: "Maximum
|
|
274
|
+
description: "Maximum results to return (default: 10 for locate, 30 for hotspots).",
|
|
275
|
+
},
|
|
276
|
+
workspace_path: {
|
|
277
|
+
type: "string",
|
|
278
|
+
description: "Explicit workspace root path. Specifies the target project root in multi-root environments.",
|
|
408
279
|
},
|
|
409
280
|
},
|
|
410
|
-
required: [
|
|
281
|
+
required: [],
|
|
411
282
|
},
|
|
412
283
|
},
|
|
413
284
|
{
|
|
414
|
-
name: "
|
|
285
|
+
name: "septum_slice",
|
|
415
286
|
description:
|
|
416
|
-
"
|
|
287
|
+
"Vertical Execution Slice Tracer: traces end-to-end execution flow (Ingress -> Validation -> Controller -> Service -> Repository -> Entity) from route URI, controller action, or high-level intent without guessing files.",
|
|
417
288
|
inputSchema: {
|
|
418
289
|
type: "object",
|
|
419
290
|
properties: {
|
|
420
291
|
query: {
|
|
421
292
|
type: "string",
|
|
422
|
-
description:
|
|
423
|
-
"Route URI (e.g. 'POST /orders/{id}/status'), action (e.g. 'OrderController@updateStatus'), or intent (e.g. 'Ubah status order di dashboard'). Symmetrically accepts 'symbol'.",
|
|
424
|
-
},
|
|
425
|
-
symbol: {
|
|
426
|
-
type: "string",
|
|
427
|
-
description: "Alias for 'query'.",
|
|
293
|
+
description: "Route URI (e.g. 'POST /orders/{id}/pay'), action (e.g. 'OrderController@pay'), or high-level task intent.",
|
|
428
294
|
},
|
|
429
295
|
max_depth: {
|
|
430
296
|
type: "number",
|
|
431
297
|
default: 5,
|
|
432
298
|
minimum: 1,
|
|
433
299
|
maximum: 8,
|
|
434
|
-
description: "Maximum depth to trace (default: 5, min: 1, max: 8).",
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
required: ["query"],
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: "septum_get_symbol",
|
|
442
|
-
description:
|
|
443
|
-
"Retrieves physical coordinates and structural metadata of a symbol (exact start_line, end_line, total_lines, signature, visibility, injected dependencies, and outbound calls). Returns deterministic JSON for direct piping into view_file without line guessing.",
|
|
444
|
-
inputSchema: {
|
|
445
|
-
type: "object",
|
|
446
|
-
properties: {
|
|
447
|
-
symbol: {
|
|
448
|
-
type: "string",
|
|
449
|
-
description:
|
|
450
|
-
"Symbol name, e.g. 'OrderController::cancelOrder', 'OrderController', or 'cancelOrder'. Symmetrically accepts 'query'.",
|
|
451
|
-
},
|
|
452
|
-
query: {
|
|
453
|
-
type: "string",
|
|
454
|
-
description: "Alias for 'symbol'.",
|
|
455
|
-
},
|
|
456
|
-
domain: {
|
|
457
|
-
type: "string",
|
|
458
|
-
description: "Optional domain filter (e.g. 'orders')",
|
|
459
|
-
},
|
|
460
|
-
include_dependencies: {
|
|
461
|
-
type: "boolean",
|
|
462
|
-
description: "Whether to include injected dependencies and outbound calls (default: true)",
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
required: ["symbol"],
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
name: "septum_get_symbol_impact",
|
|
470
|
-
description:
|
|
471
|
-
"Calculates blast radius and maps all inbound callers/dependents of a symbol across domains. Prevents Local Scope Myopia by reporting all files, callers, and line numbers that depend on the target symbol before refactoring.",
|
|
472
|
-
inputSchema: {
|
|
473
|
-
type: "object",
|
|
474
|
-
properties: {
|
|
475
|
-
symbol: {
|
|
476
|
-
type: "string",
|
|
477
|
-
description:
|
|
478
|
-
"Target symbol to inspect, e.g. 'OrderService::cancelOrder' or 'OrderService'. Symmetrically accepts 'query'.",
|
|
300
|
+
description: "Maximum depth to trace (default: 5, min: 1, max: 8). Use 6-8 for deep Event/Job/Queue traces.",
|
|
479
301
|
},
|
|
480
|
-
|
|
302
|
+
workspace_path: {
|
|
481
303
|
type: "string",
|
|
482
|
-
description: "
|
|
304
|
+
description: "Explicit workspace root path. Specifies the target project root in multi-root environments.",
|
|
483
305
|
},
|
|
484
306
|
},
|
|
485
|
-
required: ["
|
|
307
|
+
required: ["query"],
|
|
486
308
|
},
|
|
487
309
|
},
|
|
488
310
|
{
|
|
489
|
-
name: "
|
|
311
|
+
name: "septum_boundary",
|
|
490
312
|
description:
|
|
491
|
-
"
|
|
313
|
+
"Architecture Guard & Circular Dependency Gate: Pre-flight boundary check verifying proposed file edits, additions, and imports against domain rules and feature touchpoints, or auditing the entire repository / staged files for cross-domain and circular dependencies.",
|
|
492
314
|
inputSchema: {
|
|
493
315
|
type: "object",
|
|
494
316
|
properties: {
|
|
495
|
-
|
|
496
|
-
type: "string",
|
|
497
|
-
description:
|
|
498
|
-
"Unique domain name/identifier (e.g. 'orders', 'checkout', 'billing'). Symmetrically accepts 'name'.",
|
|
499
|
-
},
|
|
500
|
-
name: {
|
|
501
|
-
type: "string",
|
|
502
|
-
description: "Alias for 'domain'.",
|
|
503
|
-
},
|
|
504
|
-
root: {
|
|
317
|
+
scope: {
|
|
505
318
|
type: "string",
|
|
506
|
-
|
|
319
|
+
enum: ["file", "staged", "all", "domain"],
|
|
320
|
+
description: "Audit scope: 'file' requires file_path or non-empty file_paths; 'staged', 'all', and 'domain' reject file targets and proposed_imports. 'domain' requires a registered domain; 'all' rejects domain filters. If omitted, infer file/staged/domain/all from arguments.",
|
|
507
321
|
},
|
|
508
|
-
|
|
322
|
+
file_path: {
|
|
509
323
|
type: "string",
|
|
510
|
-
description: "
|
|
324
|
+
description: "Path of the file being edited or created (scope='file' only). Use either file_path or file_paths.",
|
|
511
325
|
},
|
|
512
|
-
|
|
326
|
+
file_paths: {
|
|
513
327
|
type: "array",
|
|
514
328
|
items: { type: "string" },
|
|
515
|
-
description: "
|
|
329
|
+
description: "Non-empty list of file paths for batch proposed-import checks (scope='file' only). Use either file_path or file_paths.",
|
|
516
330
|
},
|
|
517
|
-
|
|
331
|
+
proposed_imports: {
|
|
518
332
|
type: "array",
|
|
519
333
|
items: { type: "string" },
|
|
520
|
-
description: "List of
|
|
334
|
+
description: "List of proposed import statements or target symbols (scope='file' only).",
|
|
521
335
|
},
|
|
522
|
-
|
|
523
|
-
type: "
|
|
524
|
-
description: "
|
|
336
|
+
domain: {
|
|
337
|
+
type: "string",
|
|
338
|
+
description: "Registered domain name, required for scope='domain'; optional for staged/file checks. Explicit scope='all' does not accept a domain filter.",
|
|
525
339
|
},
|
|
526
|
-
|
|
527
|
-
type: "
|
|
528
|
-
description: "
|
|
340
|
+
feature_key: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "Optional feature key to validate touchpoints against.",
|
|
343
|
+
},
|
|
344
|
+
workspace_path: {
|
|
345
|
+
type: "string",
|
|
346
|
+
description: "Explicit workspace root path. Specifies the target project root in multi-root environments.",
|
|
529
347
|
},
|
|
530
348
|
},
|
|
531
|
-
required: [
|
|
349
|
+
required: [],
|
|
532
350
|
},
|
|
533
351
|
},
|
|
534
352
|
{
|
|
535
|
-
name: "
|
|
353
|
+
name: "septum_feature",
|
|
536
354
|
description:
|
|
537
|
-
"
|
|
355
|
+
"Active Touchpoint & Scope Lock Engine: manages feature task context, touchpoint locks (.septum/session.json), and session release to enforce surgical blast-radius boundaries.",
|
|
538
356
|
inputSchema: {
|
|
539
357
|
type: "object",
|
|
540
358
|
properties: {
|
|
541
|
-
|
|
359
|
+
action: {
|
|
542
360
|
type: "string",
|
|
543
|
-
|
|
544
|
-
|
|
361
|
+
enum: ["get", "lock", "release"],
|
|
362
|
+
default: "get",
|
|
363
|
+
description: "Feature lifecycle action: 'get' (retrieve feature context), 'lock' (register feature & establish active session lease), or 'release' (release active session lock).",
|
|
545
364
|
},
|
|
546
|
-
|
|
365
|
+
feature_key: {
|
|
547
366
|
type: "string",
|
|
548
|
-
description: "
|
|
367
|
+
description: "Unique feature identifier (e.g. 'checkout_flow'). Symmetrically accepts 'feature'.",
|
|
549
368
|
},
|
|
550
369
|
domain: {
|
|
551
370
|
type: "string",
|
|
552
|
-
description: "Bounded-context domain
|
|
371
|
+
description: "Bounded-context domain for this feature (action='lock').",
|
|
553
372
|
},
|
|
554
373
|
description: {
|
|
555
374
|
type: "string",
|
|
556
|
-
description: "Scope and rationale for the feature",
|
|
375
|
+
description: "Scope and rationale for the feature (action='lock').",
|
|
557
376
|
},
|
|
558
377
|
allowed_touchpoints: {
|
|
559
378
|
type: "array",
|
|
560
379
|
items: { type: "string" },
|
|
561
|
-
description: "
|
|
380
|
+
description: "Permitted file paths or glob patterns (action='lock').",
|
|
562
381
|
},
|
|
563
382
|
reuse_symbols: {
|
|
564
383
|
type: "array",
|
|
565
384
|
items: { type: "string" },
|
|
566
|
-
description: "
|
|
385
|
+
description: "Mandatory reusable symbols (action='lock').",
|
|
567
386
|
},
|
|
568
387
|
input_contract: {
|
|
569
388
|
type: "object",
|
|
570
|
-
description: "Expected input payload / DTO schema",
|
|
389
|
+
description: "Expected input payload / DTO schema (action='lock').",
|
|
571
390
|
},
|
|
572
391
|
output_contract: {
|
|
573
392
|
type: "object",
|
|
574
|
-
description: "Expected output response
|
|
393
|
+
description: "Expected output response contract (action='lock').",
|
|
575
394
|
},
|
|
576
|
-
|
|
577
|
-
required: ["feature_key", "domain"],
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
name: "septum_get_environment_topology",
|
|
582
|
-
description:
|
|
583
|
-
"Self-Aware Production Topology Engine: Maps and verifies the 6-layer environment topology (Edge/WAF, Gateway/Web Server, Host Platform, Runtime, Storage/Cache, Telemetry). Identifies cross-layer constraints (e.g. Cloudflare + LiteSpeed + Laravel throttle), surfaces pending architectural inquiries if layers are ambiguous, and allows recording verified environment answers to SQLite SSOT.",
|
|
584
|
-
inputSchema: {
|
|
585
|
-
type: "object",
|
|
586
|
-
properties: {
|
|
587
|
-
resolve: {
|
|
588
|
-
type: "object",
|
|
589
|
-
properties: {
|
|
590
|
-
layer: {
|
|
591
|
-
type: "string",
|
|
592
|
-
enum: ["edge", "gateway", "host", "runtime", "storage", "telemetry"],
|
|
593
|
-
description: "Target layer to manually resolve/verify",
|
|
594
|
-
},
|
|
595
|
-
platform: {
|
|
596
|
-
type: "string",
|
|
597
|
-
description: "Confirmed platform name (e.g. 'Hostinger Shared Hosting', 'Cloudflare WAF', 'LiteSpeed')",
|
|
598
|
-
},
|
|
599
|
-
constraints: {
|
|
600
|
-
type: "array",
|
|
601
|
-
items: { type: "string" },
|
|
602
|
-
description: "Optional list of architectural constraints for this layer",
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
required: ["layer", "platform"],
|
|
606
|
-
description: "Optional argument to record and verify a specific layer into SQLite SSOT",
|
|
607
|
-
},
|
|
608
|
-
resolveNode: {
|
|
609
|
-
type: "object",
|
|
610
|
-
properties: {
|
|
611
|
-
id: {
|
|
612
|
-
type: "string",
|
|
613
|
-
description: "Unique node identifier (e.g. 'api-gateway-1', 'db-primary')",
|
|
614
|
-
},
|
|
615
|
-
layer: {
|
|
616
|
-
type: "string",
|
|
617
|
-
enum: ["edge", "gateway", "host", "runtime", "storage", "telemetry"],
|
|
618
|
-
description: "Topology layer for this node",
|
|
619
|
-
},
|
|
620
|
-
name: {
|
|
621
|
-
type: "string",
|
|
622
|
-
description: "Human-readable node name",
|
|
623
|
-
},
|
|
624
|
-
platform: {
|
|
625
|
-
type: "string",
|
|
626
|
-
description: "Platform or infrastructure provider for this node",
|
|
627
|
-
},
|
|
628
|
-
domain_or_ip: {
|
|
629
|
-
type: "string",
|
|
630
|
-
description: "Optional domain or IP address of the node",
|
|
631
|
-
},
|
|
632
|
-
role: {
|
|
633
|
-
type: "string",
|
|
634
|
-
description: "Optional architectural role of the node",
|
|
635
|
-
},
|
|
636
|
-
connected_to: {
|
|
637
|
-
type: "array",
|
|
638
|
-
items: { type: "string" },
|
|
639
|
-
description: "Optional list of target node IDs this node connects to",
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
required: ["id", "layer", "name", "platform"],
|
|
643
|
-
description: "Optional argument to record and verify a distributed topology node into SQLite SSOT",
|
|
644
|
-
},
|
|
645
|
-
refresh: {
|
|
395
|
+
force: {
|
|
646
396
|
type: "boolean",
|
|
647
|
-
|
|
397
|
+
default: false,
|
|
398
|
+
description: "Force release session (action='release').",
|
|
399
|
+
},
|
|
400
|
+
workspace_path: {
|
|
401
|
+
type: "string",
|
|
402
|
+
description: "Explicit workspace root path. Specifies the target project root in multi-root environments.",
|
|
648
403
|
},
|
|
649
404
|
},
|
|
405
|
+
required: [],
|
|
650
406
|
},
|
|
651
407
|
},
|
|
652
408
|
],
|
|
653
409
|
};
|
|
654
410
|
});
|
|
655
411
|
|
|
656
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
412
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
657
413
|
const { name, arguments: args } = request.params;
|
|
658
414
|
const startTime = performance.now();
|
|
659
|
-
|
|
660
|
-
|
|
415
|
+
let lease: WorkspaceLease | undefined;
|
|
416
|
+
try {
|
|
417
|
+
extra?.signal?.throwIfAborted();
|
|
418
|
+
const schema = TOOL_SCHEMAS[name as keyof typeof TOOL_SCHEMAS];
|
|
419
|
+
if (!schema) throw new Error(`Unknown tool: '${name}'`);
|
|
420
|
+
const validated = schema.safeParse(args ?? {});
|
|
421
|
+
if (!validated.success) throw new Error(`[Septum Validation Error] Invalid arguments for ${name}: ${validated.error.message}`);
|
|
422
|
+
const record = (validated.data ?? {}) as Record<string, any>;
|
|
423
|
+
const raw = (args ?? {}) as Record<string, any>;
|
|
424
|
+
const workspacePath = record.workspace_path ?? raw.workspace_path ?? raw.workspacePath ?? raw.workspace ?? raw.project_root ?? raw.projectRoot;
|
|
425
|
+
const knownRoots = manager.activeRoots;
|
|
426
|
+
if (manager.initialRoot && !knownRoots.includes(manager.initialRoot)) {
|
|
427
|
+
knownRoots.unshift(manager.initialRoot);
|
|
428
|
+
}
|
|
429
|
+
const fallbackRoot = manager.initialRoot || manager.lastRoot;
|
|
430
|
+
const root = resolveServerWorkspace({
|
|
431
|
+
workspacePath,
|
|
432
|
+
filePath: workspacePath ? undefined : extractPathHint(args),
|
|
433
|
+
fallbackRoot,
|
|
434
|
+
knownRoots,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
if (!isWorkspaceInitialized(root)) {
|
|
438
|
+
if (name === "septum_domain" && (!record.action || record.action === "catalog")) {
|
|
439
|
+
const diagnostic = {
|
|
440
|
+
status: "NOT_INDEXED",
|
|
441
|
+
total_domains: 0,
|
|
442
|
+
message: `Workspace '${root}' belum diinisialisasi dengan Septum. Jalankan 'septum init' terlebih dahulu di terminal workspace.`,
|
|
443
|
+
suggested_action: {
|
|
444
|
+
tool: "septum_domain",
|
|
445
|
+
params: {
|
|
446
|
+
action: "register",
|
|
447
|
+
name: "core",
|
|
448
|
+
root: "src",
|
|
449
|
+
ingest_now: true,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
agent_guidance:
|
|
453
|
+
"Panggil tool 'septum_domain(action: \"register\", name: \"core\", root: \"src\", ingest_now: true)' untuk mendaftarkan dan mengindeks domain utama secara otomatis, atau jalankan 'septum init' di terminal workspace.",
|
|
454
|
+
};
|
|
455
|
+
return {
|
|
456
|
+
content: [
|
|
457
|
+
{
|
|
458
|
+
type: "text",
|
|
459
|
+
text: JSON.stringify(diagnostic, null, 2),
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
metadata: diagnostic,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const isRegistration = name === "septum_domain" && record.action === "register";
|
|
466
|
+
if (!isRegistration) {
|
|
467
|
+
return {
|
|
468
|
+
isError: true,
|
|
469
|
+
content: [
|
|
470
|
+
{
|
|
471
|
+
type: "text",
|
|
472
|
+
text: `[Septum MCP Error] Workspace '${root}' belum diinisialisasi. Jalankan 'septum init' di terminal workspace atau gunakan tool 'septum_domain' dengan action='register' untuk mendaftarkan domain.`,
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
extra?.signal?.throwIfAborted();
|
|
480
|
+
lease = manager.acquire(root);
|
|
481
|
+
const ctx = lease.context;
|
|
661
482
|
await ctx.ready;
|
|
662
|
-
|
|
483
|
+
extra?.signal?.throwIfAborted();
|
|
484
|
+
await ctx.watcher.flush(true, extra?.signal);
|
|
663
485
|
if (ctx.repo.getMeta("sync_error")) throw new Error(`Workspace index synchronization failed: ${ctx.repo.getMeta("sync_error")}`);
|
|
664
486
|
ctx.config = ConfigLoader.load(undefined, ctx.root);
|
|
665
487
|
ctx.evaluator.getResolver().setRepository(ctx.repo);
|
|
666
488
|
|
|
667
|
-
|
|
489
|
+
let activeSession = null;
|
|
490
|
+
let sessionCorruptError: Error | null = null;
|
|
491
|
+
try {
|
|
492
|
+
activeSession = SessionManager.getActiveSession(ctx.root);
|
|
493
|
+
} catch (err: any) {
|
|
494
|
+
sessionCorruptError = err instanceof Error ? err : new Error(String(err));
|
|
495
|
+
}
|
|
668
496
|
const sessionPayload = activeSession
|
|
669
497
|
? { feature_key: activeSession.feature_key, domain: activeSession.domain }
|
|
670
498
|
: null;
|
|
671
499
|
|
|
672
500
|
const executeTool = async () => {
|
|
673
|
-
|
|
674
|
-
|
|
501
|
+
// 5 Unified Tools Handlers
|
|
502
|
+
if (name === "septum_domain") {
|
|
503
|
+
const parsed = DomainToolSchema.safeParse(args ?? {});
|
|
675
504
|
if (!parsed.success) {
|
|
676
505
|
return {
|
|
677
506
|
isError: true,
|
|
@@ -683,9 +512,9 @@ export async function runMCPServer(): Promise<void> {
|
|
|
683
512
|
],
|
|
684
513
|
};
|
|
685
514
|
}
|
|
686
|
-
return
|
|
687
|
-
} else if (name === "
|
|
688
|
-
const parsed =
|
|
515
|
+
return await handleDomainTool(ctx.repo, ctx.config, parsed.data, ctx.root, extra?.signal);
|
|
516
|
+
} else if (name === "septum_symbol") {
|
|
517
|
+
const parsed = SymbolToolSchema.safeParse(args ?? {});
|
|
689
518
|
if (!parsed.success) {
|
|
690
519
|
return {
|
|
691
520
|
isError: true,
|
|
@@ -697,9 +526,9 @@ export async function runMCPServer(): Promise<void> {
|
|
|
697
526
|
],
|
|
698
527
|
};
|
|
699
528
|
}
|
|
700
|
-
return
|
|
701
|
-
} else if (name === "
|
|
702
|
-
const parsed =
|
|
529
|
+
return await handleSymbolTool(ctx.repo, ctx.config, parsed.data, ctx.pipeline, ctx.root);
|
|
530
|
+
} else if (name === "septum_slice") {
|
|
531
|
+
const parsed = SliceToolSchema.safeParse(args ?? {});
|
|
703
532
|
if (!parsed.success) {
|
|
704
533
|
return {
|
|
705
534
|
isError: true,
|
|
@@ -711,23 +540,20 @@ export async function runMCPServer(): Promise<void> {
|
|
|
711
540
|
],
|
|
712
541
|
};
|
|
713
542
|
}
|
|
714
|
-
return
|
|
715
|
-
} else if (name === "
|
|
716
|
-
|
|
717
|
-
if (!parsed.success) {
|
|
543
|
+
return handleSliceTool(ctx.repo, ctx.config, parsed.data, ctx.root);
|
|
544
|
+
} else if (name === "septum_boundary") {
|
|
545
|
+
if (sessionCorruptError) {
|
|
718
546
|
return {
|
|
719
547
|
isError: true,
|
|
720
548
|
content: [
|
|
721
549
|
{
|
|
722
550
|
type: "text",
|
|
723
|
-
text: `[Septum
|
|
551
|
+
text: `[Septum Session Error] Active feature session in '.septum/session.json' is corrupted or invalid: ${sessionCorruptError.message}. Release it explicitly with 'septum_feature({action: "release", force: true})' before evaluating boundaries.`,
|
|
724
552
|
},
|
|
725
553
|
],
|
|
726
554
|
};
|
|
727
555
|
}
|
|
728
|
-
|
|
729
|
-
} else if (name === "septum_get_symbol_impact") {
|
|
730
|
-
const parsed = GetSymbolImpactSchema.safeParse(args ?? {});
|
|
556
|
+
const parsed = BoundaryToolSchema.safeParse(args ?? {});
|
|
731
557
|
if (!parsed.success) {
|
|
732
558
|
return {
|
|
733
559
|
isError: true,
|
|
@@ -739,9 +565,9 @@ export async function runMCPServer(): Promise<void> {
|
|
|
739
565
|
],
|
|
740
566
|
};
|
|
741
567
|
}
|
|
742
|
-
return
|
|
743
|
-
} else if (name === "
|
|
744
|
-
const parsed =
|
|
568
|
+
return await handleBoundaryTool(ctx.evaluator, ctx.config, parsed.data, ctx.repo, ctx.root);
|
|
569
|
+
} else if (name === "septum_feature") {
|
|
570
|
+
const parsed = FeatureToolSchema.safeParse(args ?? {});
|
|
745
571
|
if (!parsed.success) {
|
|
746
572
|
return {
|
|
747
573
|
isError: true,
|
|
@@ -753,96 +579,26 @@ export async function runMCPServer(): Promise<void> {
|
|
|
753
579
|
],
|
|
754
580
|
};
|
|
755
581
|
}
|
|
756
|
-
|
|
757
|
-
} else if (name === "septum_clear_feature_context") {
|
|
758
|
-
const cleared = handleClearFeatureContext(ctx.root);
|
|
759
|
-
return {
|
|
760
|
-
content: [
|
|
761
|
-
{
|
|
762
|
-
type: "text",
|
|
763
|
-
text: cleared
|
|
764
|
-
? "Active feature session successfully released."
|
|
765
|
-
: "No active feature session found to release.",
|
|
766
|
-
},
|
|
767
|
-
],
|
|
768
|
-
};
|
|
769
|
-
} else if (name === "septum_check_boundary") {
|
|
770
|
-
const parsed = CheckBoundarySchema.safeParse(args ?? {});
|
|
771
|
-
if (!parsed.success) {
|
|
772
|
-
return {
|
|
773
|
-
isError: true,
|
|
774
|
-
content: [
|
|
775
|
-
{
|
|
776
|
-
type: "text",
|
|
777
|
-
text: `[Septum Validation Error] Invalid arguments for ${name}: ${parsed.error.message}`,
|
|
778
|
-
},
|
|
779
|
-
],
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
return handleCheckBoundary(ctx.evaluator, ctx.config, parsed.data, ctx.repo, ctx.root);
|
|
783
|
-
} else if (name === "septum_get_symbol_hotspots") {
|
|
784
|
-
const parsed = GetSymbolHotspotsSchema.safeParse(args ?? {});
|
|
785
|
-
if (!parsed.success) {
|
|
582
|
+
if (sessionCorruptError && (parsed.data.action !== "release" || !parsed.data.force)) {
|
|
786
583
|
return {
|
|
787
584
|
isError: true,
|
|
788
585
|
content: [
|
|
789
586
|
{
|
|
790
587
|
type: "text",
|
|
791
|
-
text: `[Septum
|
|
588
|
+
text: `[Septum Session Error] Active feature session in '.septum/session.json' is corrupted or invalid: ${sessionCorruptError.message}. Release it explicitly with 'septum_feature({action: "release", force: true})'.`,
|
|
792
589
|
},
|
|
793
590
|
],
|
|
794
591
|
};
|
|
795
592
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const parsed = RegisterDomainSchema.safeParse(args ?? {});
|
|
799
|
-
if (!parsed.success) {
|
|
800
|
-
return {
|
|
801
|
-
isError: true,
|
|
802
|
-
content: [
|
|
803
|
-
{
|
|
804
|
-
type: "text",
|
|
805
|
-
text: `[Septum Validation Error] Invalid arguments for ${name}: ${parsed.error.message}`,
|
|
806
|
-
},
|
|
807
|
-
],
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
return await handleRegisterDomain(ctx.repo, ctx.config, parsed.data, ctx.root);
|
|
811
|
-
} else if (name === "septum_register_feature") {
|
|
812
|
-
const parsed = RegisterFeatureSchema.safeParse(args ?? {});
|
|
813
|
-
if (!parsed.success) {
|
|
814
|
-
return {
|
|
815
|
-
isError: true,
|
|
816
|
-
content: [
|
|
817
|
-
{
|
|
818
|
-
type: "text",
|
|
819
|
-
text: `[Septum Validation Error] Invalid arguments for ${name}: ${parsed.error.message}`,
|
|
820
|
-
},
|
|
821
|
-
],
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
return await handleRegisterFeature(ctx.repo, ctx.config, parsed.data, ctx.root);
|
|
825
|
-
} else if (name === "septum_get_environment_topology") {
|
|
826
|
-
const parsed = GetEnvironmentTopologySchema.safeParse(args ?? {});
|
|
827
|
-
if (!parsed.success) {
|
|
828
|
-
return {
|
|
829
|
-
isError: true,
|
|
830
|
-
content: [
|
|
831
|
-
{
|
|
832
|
-
type: "text",
|
|
833
|
-
text: `[Septum Validation Error] Invalid arguments for ${name}: ${parsed.error.message}`,
|
|
834
|
-
},
|
|
835
|
-
],
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
return handleGetEnvironmentTopology(ctx.repo, ctx.config, parsed.data, ctx.root);
|
|
593
|
+
extra?.signal?.throwIfAborted();
|
|
594
|
+
return await handleFeatureTool(ctx.repo, ctx.config, parsed.data, ctx.root);
|
|
839
595
|
} else {
|
|
840
596
|
throw new Error(`Unknown tool: '${name}'`);
|
|
841
597
|
}
|
|
842
598
|
};
|
|
843
599
|
|
|
844
600
|
try {
|
|
845
|
-
const response = await executeTool
|
|
601
|
+
const response = await manager.runTool(executeTool, extra?.signal);
|
|
846
602
|
const durationMs = Math.round(performance.now() - startTime);
|
|
847
603
|
|
|
848
604
|
let bytesOut = 0;
|
|
@@ -851,7 +607,7 @@ export async function runMCPServer(): Promise<void> {
|
|
|
851
607
|
for (const item of response.content) {
|
|
852
608
|
if (typeof item.text === "string") {
|
|
853
609
|
bytesOut += Buffer.byteLength(item.text, "utf8");
|
|
854
|
-
linesOut += item.text
|
|
610
|
+
linesOut += countLines(item.text);
|
|
855
611
|
}
|
|
856
612
|
}
|
|
857
613
|
}
|
|
@@ -894,37 +650,67 @@ export async function runMCPServer(): Promise<void> {
|
|
|
894
650
|
],
|
|
895
651
|
};
|
|
896
652
|
}
|
|
653
|
+
} catch (error) {
|
|
654
|
+
return {isError: true, content: [{type: "text" as const, text: `[Septum MCP Error] ${error instanceof Error ? error.message : String(error)}`}]};
|
|
655
|
+
} finally { lease?.release(); }
|
|
897
656
|
});
|
|
898
657
|
|
|
899
658
|
const transport = new StdioServerTransport();
|
|
900
659
|
|
|
660
|
+
let stopping = false;
|
|
661
|
+
let memoryTimer: ReturnType<typeof setInterval> | undefined;
|
|
901
662
|
const cleanup = () => {
|
|
902
|
-
|
|
663
|
+
if (stopping) return;
|
|
664
|
+
stopping = true;
|
|
665
|
+
if (memoryTimer) clearInterval(memoryTimer);
|
|
666
|
+
const deadline = setTimeout(() => {
|
|
667
|
+
console.error("[Septum MCP] Shutdown deadline exceeded; indexing may require reconciliation.");
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}, RESOURCE_LIMITS.shutdownMs);
|
|
670
|
+
void (async () => {
|
|
903
671
|
try {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
672
|
+
await server.close();
|
|
673
|
+
await manager.close();
|
|
674
|
+
clearTimeout(deadline);
|
|
675
|
+
console.error("[Septum MCP] All workspace resources closed cleanly.");
|
|
676
|
+
process.exit(0);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.error(`[Septum MCP] Cleanup failed: ${String(error)}`);
|
|
679
|
+
clearTimeout(deadline);
|
|
680
|
+
process.exit(1);
|
|
908
681
|
}
|
|
909
|
-
}
|
|
910
|
-
console.error("[Septum MCP] All database connections closed cleanly.");
|
|
911
|
-
process.exit(0);
|
|
682
|
+
})();
|
|
912
683
|
};
|
|
913
|
-
|
|
914
|
-
process.
|
|
915
|
-
process.
|
|
916
|
-
process.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
684
|
+
process.once("SIGINT", cleanup);
|
|
685
|
+
process.once("SIGTERM", cleanup);
|
|
686
|
+
process.stdin.once("end", cleanup);
|
|
687
|
+
process.stdin.once("close", cleanup);
|
|
688
|
+
server.onclose = cleanup;
|
|
689
|
+
await server.connect(transport);
|
|
690
|
+
try {
|
|
691
|
+
const initialRoot = resolveServerWorkspace({initial:true});
|
|
692
|
+
manager.initialRoot = initialRoot;
|
|
693
|
+
manager.lastRoot = initialRoot;
|
|
694
|
+
if (isWorkspaceInitialized(initialRoot)) {
|
|
695
|
+
const initial = manager.acquire(initialRoot);
|
|
696
|
+
try { await initial.context.ready; } finally { initial.release(); }
|
|
924
697
|
}
|
|
925
|
-
|
|
698
|
+
if (process.env.SEPTUM_MEMORY_DIAGNOSTICS === "1") {
|
|
699
|
+
memoryTimer = setInterval(() => {
|
|
700
|
+
const sample = { ...process.memoryUsage(), workspaces: manager.size, watches: manager.watchCount, pending_paths: manager.pendingPaths, sync_duration_ms: manager.syncDurationMs };
|
|
701
|
+
if (manager.lastRoot && isWorkspaceInitialized(manager.lastRoot)) {
|
|
702
|
+
SeptumTelemetry.recordMemorySample(sample, manager.lastRoot);
|
|
703
|
+
} else {
|
|
704
|
+
console.error(`[Septum Memory] ${JSON.stringify({timestamp: new Date().toISOString(), event: "memory_sample", metrics: sample})}`);
|
|
705
|
+
}
|
|
706
|
+
}, 60_000);
|
|
707
|
+
memoryTimer.unref();
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
await manager.close();
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
926
713
|
|
|
927
|
-
await server.connect(transport);
|
|
928
714
|
if (process.stdin.isTTY) {
|
|
929
715
|
console.error(
|
|
930
716
|
`\x1b[36m[Septum MCP]\x1b[0m Server daemon running on stdio (PID: ${process.pid}, v${SEPTUM_VERSION}).`
|