septum 0.1.3 → 0.1.4

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 (35) hide show
  1. package/README.md +5 -2
  2. package/package.json +1 -1
  3. package/src/cli/commands/slice.ts +4 -46
  4. package/src/cli/commands/topology.ts +119 -0
  5. package/src/cli/index.ts +11 -0
  6. package/src/core/database/repositories/environment.repository.ts +207 -0
  7. package/src/core/database/repositories/symbol.repository.ts +28 -9
  8. package/src/core/database/repository.ts +6 -1
  9. package/src/core/database/schema.ts +153 -120
  10. package/src/core/parser/behavioral-analyzer.ts +119 -0
  11. package/src/core/parser/boundary-tracker.ts +160 -0
  12. package/src/core/parser/extractors/base.ts +1 -0
  13. package/src/core/parser/extractors/blade.ts +158 -158
  14. package/src/core/parser/extractors/go.ts +5 -1
  15. package/src/core/parser/extractors/laravel-semantic.ts +320 -4
  16. package/src/core/parser/extractors/php.ts +5 -1
  17. package/src/core/parser/extractors/python.ts +3 -1
  18. package/src/core/parser/extractors/typescript.ts +5 -1
  19. package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
  20. package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
  21. package/src/core/parser/schema/schema-provider-registry.ts +43 -0
  22. package/src/core/parser/schema/schema-provider.interface.ts +33 -0
  23. package/src/core/resolver/path-resolver.ts +147 -147
  24. package/src/core/resolver/vertical-slice-tracer.ts +34 -1
  25. package/src/core/topology/environment-detector.ts +860 -0
  26. package/src/core/topology/types.ts +83 -0
  27. package/src/index.ts +3 -0
  28. package/src/mcp/schemas.ts +48 -0
  29. package/src/mcp/server.ts +62 -2
  30. package/src/mcp/tools/get-environment-topology.ts +141 -0
  31. package/src/mcp/tools/get-symbol-hotspots.ts +13 -3
  32. package/src/mcp/tools/register-domain.ts +1 -1
  33. package/src/mcp/tools/trace-vertical-slice.ts +9 -64
  34. package/src/types/index.ts +16 -0
  35. package/src/version.ts +3 -3
@@ -0,0 +1,83 @@
1
+ export type EnvironmentLayer =
2
+ | "edge"
3
+ | "gateway"
4
+ | "host"
5
+ | "runtime"
6
+ | "storage"
7
+ | "telemetry";
8
+
9
+ export type LayerResolutionStatus = "DETECTED" | "AMBIGUOUS" | "UNRESOLVED" | "VERIFIED";
10
+
11
+ export type CanonicalPlatformTag =
12
+ | "shared_hosting"
13
+ | "vps"
14
+ | "docker"
15
+ | "serverless"
16
+ | "kubernetes"
17
+ | "managed_db"
18
+ | "cdn_edge"
19
+ | "paas"
20
+ | "static_cdn"
21
+ | "unspecified";
22
+
23
+ export interface EnvironmentLayerInfo {
24
+ layer: EnvironmentLayer;
25
+ platform?: string;
26
+ canonical_tag?: CanonicalPlatformTag;
27
+ status: LayerResolutionStatus;
28
+ detected_from?: string[];
29
+ evidence?: string[];
30
+ constraints?: string[];
31
+ unresolved_inquiry?: string;
32
+ }
33
+
34
+ export interface TopologyNode {
35
+ id: string; // e.g. "edge:cloudflare", "host:api-vps", "host:worker-vps", "storage:central-db"
36
+ layer: EnvironmentLayer;
37
+ name: string; // e.g. "Cloudflare Perimeter", "API Backend VPS", "Postgres Cluster"
38
+ platform: string; // e.g. "Cloudflare WAF", "Hostinger VPS", "AWS RDS Postgres"
39
+ canonical_tag: CanonicalPlatformTag;
40
+ domain_or_ip?: string; // e.g. "api.example.com", "10.0.0.5"
41
+ role?: string; // e.g. "web_api", "queue_worker", "frontend_spa", "database_primary"
42
+ status: LayerResolutionStatus;
43
+ detected_from: string[];
44
+ constraints: string[];
45
+ connected_to?: string[]; // IDs of nodes this node interacts with
46
+ unresolved_inquiry?: string;
47
+ }
48
+
49
+ export interface CrossLayerInterplay {
50
+ title: string;
51
+ involved_layers: EnvironmentLayer[];
52
+ involved_node_ids?: string[];
53
+ risk_or_rule: string;
54
+ required_action: string;
55
+ }
56
+
57
+ export interface EnvironmentTopology {
58
+ is_distributed: boolean;
59
+ nodes: TopologyNode[];
60
+ layers: Record<EnvironmentLayer, EnvironmentLayerInfo>;
61
+ overall_status: "RESOLVED" | "NEEDS_CLARIFICATION";
62
+ interplays: CrossLayerInterplay[];
63
+ pending_inquiries: string[];
64
+ architectural_summary: string;
65
+ }
66
+
67
+ export interface ResolveLayerInput {
68
+ layer: EnvironmentLayer;
69
+ platform: string;
70
+ constraints?: string[];
71
+ }
72
+
73
+ export interface ResolveNodeInput {
74
+ id: string;
75
+ layer: EnvironmentLayer;
76
+ name: string;
77
+ platform: string;
78
+ canonical_tag?: CanonicalPlatformTag;
79
+ domain_or_ip?: string;
80
+ role?: string;
81
+ constraints?: string[];
82
+ connected_to?: string[];
83
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,9 @@ export * from "./core/ingestion/pipeline.ts";
9
9
  export * from "./core/boundary/evaluator.ts";
10
10
  export * from "./core/resolver/call-graph-tracer.ts";
11
11
  export * from "./core/resolver/vertical-slice-tracer.ts";
12
+ export * from "./core/topology/types.ts";
13
+ export * from "./core/topology/environment-detector.ts";
14
+ export * from "./mcp/tools/get-environment-topology.ts";
12
15
  export * from "./mcp/server.ts";
13
16
  export * from "./cli/index.ts";
14
17
 
@@ -189,10 +189,19 @@ export const GetSymbolHotspotsSchema = z.preprocess(
189
189
  domain_name: "domain",
190
190
  domainName: "domain",
191
191
  max: "limit",
192
+ nesting: "min_nesting",
193
+ minNesting: "min_nesting",
194
+ lines: "min_lines",
195
+ minLines: "min_lines",
196
+ sortBy: "sort_by",
192
197
  }),
193
198
  z.object({
199
+ min_lines: z.number().int().nonnegative().optional(),
200
+ min_nesting: z.number().int().positive().optional(),
201
+ kind: z.string().optional(),
194
202
  domain: z.string().optional(),
195
203
  limit: z.number().int().positive().optional().default(10),
204
+ sort_by: z.enum(["lines", "nesting", "risk_score"]).optional(),
196
205
  })
197
206
  );
198
207
  export type GetSymbolHotspotsArgs = z.infer<typeof GetSymbolHotspotsSchema>;
@@ -236,3 +245,42 @@ export const AutoDiscoverDomainsSchema = z.preprocess(
236
245
  );
237
246
  export type AutoDiscoverDomainsArgs = z.infer<typeof AutoDiscoverDomainsSchema>;
238
247
 
248
+ export const GetEnvironmentTopologySchema = z.object({
249
+ resolve: z
250
+ .object({
251
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
252
+ platform: z.string().min(1, "platform name cannot be empty"),
253
+ constraints: z.array(z.string()).optional(),
254
+ })
255
+ .optional(),
256
+ resolveNode: z
257
+ .object({
258
+ id: z.string().min(1, "Node id cannot be empty"),
259
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
260
+ name: z.string().min(1, "Node name cannot be empty"),
261
+ platform: z.string().min(1, "Platform cannot be empty"),
262
+ canonical_tag: z
263
+ .enum([
264
+ "shared_hosting",
265
+ "vps",
266
+ "docker",
267
+ "serverless",
268
+ "kubernetes",
269
+ "managed_db",
270
+ "cdn_edge",
271
+ "paas",
272
+ "static_cdn",
273
+ "unspecified",
274
+ ])
275
+ .optional(),
276
+ domain_or_ip: z.string().optional(),
277
+ role: z.string().optional(),
278
+ constraints: z.array(z.string()).optional(),
279
+ connected_to: z.array(z.string()).optional(),
280
+ })
281
+ .optional(),
282
+ refresh: z.boolean().optional().default(false),
283
+ });
284
+ export type GetEnvironmentTopologyArgs = z.infer<typeof GetEnvironmentTopologySchema>;
285
+
286
+
package/src/mcp/server.ts CHANGED
@@ -41,10 +41,12 @@ import {
41
41
  import { handleGetSymbol } from "./tools/get-symbol.ts";
42
42
  import { handleGetSymbolImpact } from "./tools/get-symbol-impact.ts";
43
43
  import { handleRegisterDomain } from "./tools/register-domain.ts";
44
+ import { handleGetEnvironmentTopology } from "./tools/get-environment-topology.ts";
44
45
  import type { GetSymbolArgs, GetSymbolImpactArgs } from "../types/index.ts";
45
46
  import {
46
47
  CheckBoundarySchema,
47
48
  GetDomainCatalogSchema,
49
+ GetEnvironmentTopologySchema,
48
50
  GetFeatureContextSchema,
49
51
  GetSymbolHotspotsSchema,
50
52
  GetSymbolImpactSchema,
@@ -180,7 +182,7 @@ export async function runMCPServer(): Promise<void> {
180
182
  {
181
183
  name: "septum_get_symbol_hotspots",
182
184
  description:
183
- "Discovers oversized functions, methods, and classes exceeding a specified line count threshold (default: 30 lines). Essential for Single Responsibility Principle (SRP) enforcement, finding God Functions, and isolating refactoring targets deterministically without reading raw files.",
185
+ "Discovers oversized functions, methods, and classes exceeding a specified line count threshold or cyclomatic control flow nesting depth (> 4). Essential for Single Responsibility Principle (SRP) enforcement, finding God Functions / bug magnets, and isolating refactoring targets deterministically without reading raw files.",
184
186
  inputSchema: {
185
187
  type: "object",
186
188
  properties: {
@@ -188,6 +190,10 @@ export async function runMCPServer(): Promise<void> {
188
190
  type: "number",
189
191
  description: "Minimum physical lines of code (LoC) threshold (default: 30).",
190
192
  },
193
+ min_nesting: {
194
+ type: "number",
195
+ description: "Minimum control-flow nesting depth (e.g. nested if/for/while/try > 4).",
196
+ },
191
197
  kind: {
192
198
  type: "string",
193
199
  description: "Filter by symbol kind ('function', 'method', 'class', 'struct', 'interface').",
@@ -200,6 +206,11 @@ export async function runMCPServer(): Promise<void> {
200
206
  type: "number",
201
207
  description: "Maximum results to return (default: 30).",
202
208
  },
209
+ sort_by: {
210
+ type: "string",
211
+ enum: ["lines", "nesting", "risk_score"],
212
+ description: "Sorting criteria: 'risk_score' (combines lines & nesting depth), 'nesting', or 'lines'. Default: 'risk_score'.",
213
+ },
203
214
  },
204
215
  },
205
216
  },
@@ -329,6 +340,41 @@ export async function runMCPServer(): Promise<void> {
329
340
  required: ["name", "root"],
330
341
  },
331
342
  },
343
+ {
344
+ name: "septum_get_environment_topology",
345
+ description:
346
+ "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.",
347
+ inputSchema: {
348
+ type: "object",
349
+ properties: {
350
+ resolve: {
351
+ type: "object",
352
+ properties: {
353
+ layer: {
354
+ type: "string",
355
+ enum: ["edge", "gateway", "host", "runtime", "storage", "telemetry"],
356
+ description: "Target layer to manually resolve/verify",
357
+ },
358
+ platform: {
359
+ type: "string",
360
+ description: "Confirmed platform name (e.g. 'Hostinger Shared Hosting', 'Cloudflare WAF', 'LiteSpeed')",
361
+ },
362
+ constraints: {
363
+ type: "array",
364
+ items: { type: "string" },
365
+ description: "Optional list of architectural constraints for this layer",
366
+ },
367
+ },
368
+ required: ["layer", "platform"],
369
+ description: "Optional argument to record and verify a specific layer into SQLite SSOT",
370
+ },
371
+ refresh: {
372
+ type: "boolean",
373
+ description: "Force re-detection from codebase files, ignoring cached status",
374
+ },
375
+ },
376
+ },
377
+ },
332
378
  ],
333
379
  };
334
380
  });
@@ -483,6 +529,20 @@ export async function runMCPServer(): Promise<void> {
483
529
  };
484
530
  }
485
531
  return await handleRegisterDomain(ctx.repo, ctx.config, parsed.data);
532
+ } else if (name === "septum_get_environment_topology") {
533
+ const parsed = GetEnvironmentTopologySchema.safeParse(args ?? {});
534
+ if (!parsed.success) {
535
+ return {
536
+ isError: true,
537
+ content: [
538
+ {
539
+ type: "text",
540
+ text: `[Septum Validation Error] Invalid arguments for ${name}: ${parsed.error.message}`,
541
+ },
542
+ ],
543
+ };
544
+ }
545
+ return handleGetEnvironmentTopology(ctx.repo, ctx.config, parsed.data, ctx.root);
486
546
  } else {
487
547
  throw new Error(`Unknown tool: '${name}'`);
488
548
  }
@@ -510,7 +570,7 @@ export async function runMCPServer(): Promise<void> {
510
570
  input: (args as Record<string, any>) ?? {},
511
571
  metrics: { bytes_out: bytesOut, lines_out: linesOut },
512
572
  session: sessionPayload,
513
- status: response?.isError ? "error" : "success",
573
+ status: (response as { isError?: boolean } | undefined)?.isError ? "error" : "success",
514
574
  });
515
575
 
516
576
  return response;
@@ -0,0 +1,141 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { EnvironmentDetector } from "../../core/topology/environment-detector.ts";
4
+ import type { EnvironmentLayer, ResolveLayerInput, ResolveNodeInput } from "../../core/topology/types.ts";
5
+
6
+ export interface GetEnvironmentTopologyArgs {
7
+ resolve?: ResolveLayerInput;
8
+ resolveNode?: ResolveNodeInput;
9
+ refresh?: boolean;
10
+ }
11
+
12
+ export function handleGetEnvironmentTopology(
13
+ repo: SeptumRepository,
14
+ _config: ValidatedSeptumConfig,
15
+ args: GetEnvironmentTopologyArgs,
16
+ workspaceRoot: string
17
+ ) {
18
+ // If layer resolution input is provided, persist into SQLite SSOT
19
+ if (args.resolve) {
20
+ const existing = repo.environment.getLayer(args.resolve.layer);
21
+ repo.environment.saveLayer({
22
+ layer: args.resolve.layer,
23
+ platform: args.resolve.platform,
24
+ canonical_tag: EnvironmentDetector.matchCanonicalTag(args.resolve.platform),
25
+ status: "VERIFIED",
26
+ constraints: args.resolve.constraints && args.resolve.constraints.length > 0 ? args.resolve.constraints : (existing?.constraints ?? []),
27
+ detected_from: ["Manual Developer Resolution via Septum"],
28
+ });
29
+ }
30
+
31
+ // If node resolution input is provided, persist node into SQLite SSOT
32
+ if (args.resolveNode) {
33
+ repo.environment.saveNode({
34
+ id: args.resolveNode.id,
35
+ layer: args.resolveNode.layer,
36
+ name: args.resolveNode.name,
37
+ platform: args.resolveNode.platform,
38
+ canonical_tag: args.resolveNode.canonical_tag || EnvironmentDetector.matchCanonicalTag(args.resolveNode.platform),
39
+ domain_or_ip: args.resolveNode.domain_or_ip,
40
+ role: args.resolveNode.role,
41
+ status: "VERIFIED",
42
+ detected_from: ["Manual Developer Resolution via Septum MCP"],
43
+ constraints: args.resolveNode.constraints ?? [],
44
+ connected_to: args.resolveNode.connected_to ?? [],
45
+ });
46
+ }
47
+
48
+ // Detect current topology merging heuristics with SQLite SSOT
49
+ const topology = EnvironmentDetector.detect(workspaceRoot, repo, args.refresh);
50
+
51
+ // Format into high-density Markdown output
52
+ let output = `### 🌐 Septum Environment & Topology Plane\n\n`;
53
+ output += `> **Overall Status:** \`${topology.overall_status}\`\n`;
54
+ output += `> **Topology Mode:** \`${topology.is_distributed ? "DISTRIBUTED MULTI-NODE GRAPH" : "MONOLITHIC SINGLE-NODE"}\`\n`;
55
+ output += `> **Architecture:** ${topology.architectural_summary}\n\n`;
56
+
57
+ // Section 1: Distributed Nodes Graph
58
+ if (topology.nodes.length > 0) {
59
+ output += `#### 📦 Distributed Topology Nodes (${topology.nodes.length} entities):\n`;
60
+ output += `| Node ID | Layer | Platform / Specs | Role | Domain / Host | Connected To | Status |\n`;
61
+ output += `| :--- | :--- | :--- | :--- | :--- | :--- | :--- |\n`;
62
+ for (const node of topology.nodes) {
63
+ const statusBadge = node.status === "VERIFIED" ? "✅ VERIFIED" : node.status === "DETECTED" ? "🔍 DETECTED" : "⚠️ " + node.status;
64
+ const domain = node.domain_or_ip ? `\`${node.domain_or_ip}\`` : "-";
65
+ const role = node.role ? `\`${node.role}\`` : "-";
66
+ const connected = node.connected_to && node.connected_to.length > 0 ? node.connected_to.join(", ") : "-";
67
+ output += `| **\`${node.id}\`** | ${node.layer.toUpperCase()} | ${node.platform} | ${role} | ${domain} | ${connected} | ${statusBadge} |\n`;
68
+ }
69
+ output += `\n`;
70
+ }
71
+
72
+ // Section 2: 6-Layer Summary
73
+ output += `#### 🗺️ 6-Layer Plane Summary:\n`;
74
+ output += `| Layer | Plane Domain | Platform / Setup | Status | Evidence / Files |\n`;
75
+ output += `| :--- | :--- | :--- | :--- | :--- |\n`;
76
+
77
+ const layerLabels: Record<EnvironmentLayer, string> = {
78
+ edge: "Perimeter & WAF",
79
+ gateway: "Ingress & Web Server",
80
+ host: "Deployment Platform",
81
+ runtime: "Application Engine",
82
+ storage: "Data & Cache",
83
+ telemetry: "Observability & Logs",
84
+ };
85
+
86
+ for (const layerKey of Object.keys(topology.layers) as EnvironmentLayer[]) {
87
+ const info = topology.layers[layerKey];
88
+ const evidence = info.detected_from && info.detected_from.length > 0 ? info.detected_from.join(", ") : "-";
89
+ const statusBadge = info.status === "VERIFIED" ? "✅ VERIFIED" : info.status === "DETECTED" ? "🔍 DETECTED" : "⚠️ " + info.status;
90
+ output += `| **${layerKey.toUpperCase()}** | ${layerLabels[layerKey]} | ${info.platform || "Not Defined"} | ${statusBadge} | ${evidence} |\n`;
91
+ }
92
+
93
+ // Section 3: Active Constraints
94
+ output += `\n#### 📌 Active Architectural Constraints:\n`;
95
+ let hasConstraints = false;
96
+ for (const node of topology.nodes) {
97
+ if (node.constraints && node.constraints.length > 0) {
98
+ hasConstraints = true;
99
+ output += `- **\`${node.id}\` (${node.platform}):**\n`;
100
+ for (const c of node.constraints) {
101
+ output += ` • ${c}\n`;
102
+ }
103
+ }
104
+ }
105
+ if (!hasConstraints) {
106
+ output += `*No specific node constraints recorded.*\n`;
107
+ }
108
+
109
+ // Section 4: Cross-Layer & Distributed Interplays
110
+ if (topology.interplays.length > 0) {
111
+ output += `\n#### ⚡ Cross-Layer & Distributed Invariants:\n`;
112
+ for (const interplay of topology.interplays) {
113
+ const nodeTrace = interplay.involved_node_ids && interplay.involved_node_ids.length > 0 ? ` (Nodes: \`${interplay.involved_node_ids.join(", ")}\`)` : "";
114
+ output += `- **${interplay.title}** (Layers: \`${interplay.involved_layers.join(" ➔ ")}\`)${nodeTrace}:\n`;
115
+ output += ` • *Invarian:* ${interplay.risk_or_rule}\n`;
116
+ output += ` • *Tindakan Wajib:* ${interplay.required_action}\n`;
117
+ }
118
+ }
119
+
120
+ // Section 5: Pending Inquiries
121
+ if (topology.pending_inquiries.length > 0) {
122
+ output += `\n#### ❓ Pending Architectural Inquiries (Self-Aware Inquiry Required):\n`;
123
+ output += `Terdapat layer atau node arsitektural yang belum terkonfirmasi. Sebelum mengeksekusi kode terkait deployment/throttling/cron/cookies, klarifikasikan hal berikut ke pengguna:\n`;
124
+ for (const q of topology.pending_inquiries) {
125
+ output += `- ${q}\n`;
126
+ }
127
+ output += `\n*Untuk mengunci jawaban pengguna ke SQLite SSOT, panggil kembali tool ini dengan argumen:*\n`;
128
+ output += `\`septum_get_environment_topology(resolve: { layer: "<layer_name>", platform: "<platform_name>" })\`\n`;
129
+ output += `*atau untuk entitas terdistribusi:*\n`;
130
+ output += `\`septum_get_environment_topology(resolveNode: { id: "<node_id>", layer: "<layer>", name: "<name>", platform: "<platform>", domain_or_ip: "<domain_or_ip>", role: "<role>" })\`\n`;
131
+ }
132
+
133
+ return {
134
+ content: [
135
+ {
136
+ type: "text" as const,
137
+ text: output,
138
+ },
139
+ ],
140
+ };
141
+ }
@@ -4,9 +4,11 @@ import type { SymbolKind } from "../../types/index.ts";
4
4
 
5
5
  export interface GetSymbolHotspotsArgs {
6
6
  min_lines?: number;
7
- kind?: SymbolKind;
7
+ min_nesting?: number;
8
+ kind?: SymbolKind | string;
8
9
  domain?: string;
9
10
  limit?: number;
11
+ sort_by?: "lines" | "nesting" | "risk_score";
10
12
  }
11
13
 
12
14
  export function handleGetSymbolHotspots(
@@ -14,23 +16,31 @@ export function handleGetSymbolHotspots(
14
16
  _config: ValidatedSeptumConfig,
15
17
  args: GetSymbolHotspotsArgs
16
18
  ) {
17
- const minLines = args.min_lines ?? 30;
19
+ const minLines = args.min_lines ?? (args.min_nesting ? 0 : 30);
20
+ const minNesting = args.min_nesting ?? 1;
18
21
  const limit = args.limit ?? 30;
22
+ const sortBy = args.sort_by ?? "risk_score";
19
23
 
20
24
  const hotspots = repo.getHotspotSymbols({
21
25
  minLines,
22
- kind: args.kind,
26
+ minNesting,
27
+ kind: args.kind as SymbolKind | undefined,
23
28
  domain: args.domain,
24
29
  limit,
30
+ sortBy,
25
31
  });
26
32
 
27
33
  const payload = {
28
34
  total_found: hotspots.length,
29
35
  threshold_min_lines: minLines,
36
+ threshold_min_nesting: minNesting,
37
+ sort_by: sortBy,
30
38
  hotspots: hotspots.map((h) => ({
31
39
  name: h.name,
32
40
  kind: h.kind,
33
41
  line_count: h.line_count,
42
+ nesting_depth: (h as any).nesting_depth ?? 1,
43
+ risk_score: (h as any).risk_score ?? h.line_count,
34
44
  line_span: `${h.line_start}-${h.line_end}`,
35
45
  file_path: h.file_path,
36
46
  domain: h.domain_name,
@@ -22,7 +22,7 @@ export async function handleRegisterDomain(
22
22
  throw new Error("Missing required arguments: 'name' and 'root' are required.");
23
23
  }
24
24
 
25
- const domainConfig: DomainConfig = {
25
+ const domainConfig = {
26
26
  root: args.root,
27
27
  description: args.description || `Domain: ${args.name}`,
28
28
  allowed_dependencies: args.allowed_dependencies || [],
@@ -22,72 +22,17 @@ export function handleTraceVerticalSlice(
22
22
  const tracer = new VerticalSliceTracer(repo, workspaceRoot);
23
23
  const result: VerticalSliceTraceResponse = tracer.trace(args.query);
24
24
 
25
- let formattedText = "";
25
+ let formattedText = result.message;
26
26
 
27
- if (result.found && result.slice) {
28
- const s = result.slice;
29
- formattedText = `=== VERTICAL SLICE TRACE ===\n`;
30
- formattedText += `Query: ${result.query}\n`;
31
- formattedText += `Status: RESOLVED\n\n`;
32
-
33
- formattedText += `• Route:\n`;
34
- formattedText += ` Method: ${s.route.method}\n`;
35
- formattedText += ` URI: ${s.route.uri}\n`;
36
- if (s.route.name) {
37
- formattedText += ` Name: ${s.route.name}\n`;
38
- }
39
-
40
- if (s.request) {
41
- formattedText += `\n• Request / Validation:\n`;
42
- formattedText += ` Class: ${s.request.class} (${s.request.file || "inline"})\n`;
43
- const ruleKeys = Object.keys(s.request.rules);
44
- if (ruleKeys.length > 0) {
45
- formattedText += ` Rules:\n`;
46
- for (const k of ruleKeys) {
47
- formattedText += ` - ${k} => ${s.request.rules[k]}\n`;
48
- }
49
- }
50
- }
51
-
52
- formattedText += `\n• Controller Action:\n`;
53
- formattedText += ` Class: ${s.controller.class}@${s.controller.action}\n`;
54
- formattedText += ` File: ${s.controller.file || "unknown"}:${s.controller.line}\n`;
55
-
56
- if (s.model) {
57
- formattedText += `\n• Model:\n`;
58
- formattedText += ` Class: ${s.model.class} (${s.model.file || "unknown"})\n`;
59
- if (s.model.fillable && s.model.fillable.length > 0) {
60
- formattedText += ` Fillable: [${s.model.fillable.join(", ")}]\n`;
61
- }
62
- if (s.model.casts && Object.keys(s.model.casts).length > 0) {
63
- formattedText += ` Casts: ${JSON.stringify(s.model.casts)}\n`;
64
- }
27
+ if (result.found && result.alternatives && result.alternatives.length > 0) {
28
+ formattedText += `\n\n• Other Matching Slices:\n`;
29
+ for (const alt of result.alternatives) {
30
+ formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
65
31
  }
66
-
67
- if (s.frontend) {
68
- formattedText += `\n• Frontend Target:\n`;
69
- formattedText += ` Page: ${s.frontend.target}\n`;
70
- if (s.frontend.props.length > 0) {
71
- formattedText += ` Props: [${s.frontend.props.join(", ")}]\n`;
72
- }
73
- }
74
-
75
- if (result.alternatives && result.alternatives.length > 0) {
76
- formattedText += `\n• Other Matching Slices:\n`;
77
- for (const alt of result.alternatives) {
78
- formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
79
- }
80
- }
81
- } else {
82
- formattedText = `=== VERTICAL SLICE NOT FOUND ===\n`;
83
- formattedText += `Query: ${result.query}\n`;
84
- formattedText += `Message: ${result.message}\n`;
85
-
86
- if (result.alternatives && result.alternatives.length > 0) {
87
- formattedText += `\nAvailable Slices:\n`;
88
- for (const alt of result.alternatives) {
89
- formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
90
- }
32
+ } else if (!result.found && result.alternatives && result.alternatives.length > 0) {
33
+ formattedText += `\n\nAvailable Slices:\n`;
34
+ for (const alt of result.alternatives) {
35
+ formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
91
36
  }
92
37
  }
93
38
 
@@ -12,6 +12,7 @@ export type ArchetypeKind =
12
12
  | "middleware"
13
13
  | "request"
14
14
  | "resource"
15
+ | "route"
15
16
  | "util"
16
17
  | "view"
17
18
  | "unknown";
@@ -123,6 +124,7 @@ export interface SymbolRecord {
123
124
  line_start: number;
124
125
  line_end: number;
125
126
  line_count: number;
127
+ nesting_depth: number;
126
128
  }
127
129
 
128
130
  export interface DependencyRecord {
@@ -142,6 +144,7 @@ export interface ExtractedSymbol {
142
144
  line_start: number;
143
145
  line_end: number;
144
146
  line_count: number;
147
+ nesting_depth?: number;
145
148
  }
146
149
 
147
150
  export interface ExtractedDependency {
@@ -193,12 +196,17 @@ export interface DomainCatalogResponse {
193
196
  export interface ExecutionChainNode {
194
197
  stage:
195
198
  | "ingress"
199
+ | "guard"
196
200
  | "validation"
197
201
  | "controller"
198
202
  | "orchestrator"
199
203
  | "use_case"
200
204
  | "domain"
201
205
  | "entity"
206
+ | "side_effect"
207
+ | "job"
208
+ | "event"
209
+ | "transaction"
202
210
  | "egress"
203
211
  | "repository"
204
212
  | string;
@@ -206,6 +214,14 @@ export interface ExecutionChainNode {
206
214
  file?: string;
207
215
  line?: number;
208
216
  description?: string;
217
+ table?: string;
218
+ schema_file?: string;
219
+ columns_summary?: string;
220
+ guards?: string[];
221
+ mutations?: string[];
222
+ emits?: string[];
223
+ dispatches?: string[];
224
+ transactions?: boolean;
209
225
  }
210
226
 
211
227
  export interface VerticalSliceRecord {
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
- import packageJson from "../package.json" with { type: "json" };
2
-
3
- export const SEPTUM_VERSION: string = packageJson.version;
1
+ import packageJson from "../package.json" with { type: "json" };
2
+
3
+ export const SEPTUM_VERSION: string = packageJson.version;