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.
- package/README.md +5 -2
- package/package.json +1 -1
- package/src/cli/commands/slice.ts +4 -46
- package/src/cli/commands/topology.ts +119 -0
- package/src/cli/index.ts +11 -0
- package/src/core/database/repositories/environment.repository.ts +207 -0
- package/src/core/database/repositories/symbol.repository.ts +28 -9
- package/src/core/database/repository.ts +6 -1
- package/src/core/database/schema.ts +153 -120
- package/src/core/parser/behavioral-analyzer.ts +119 -0
- package/src/core/parser/boundary-tracker.ts +160 -0
- package/src/core/parser/extractors/base.ts +1 -0
- package/src/core/parser/extractors/blade.ts +158 -158
- package/src/core/parser/extractors/go.ts +5 -1
- package/src/core/parser/extractors/laravel-semantic.ts +320 -4
- package/src/core/parser/extractors/php.ts +5 -1
- package/src/core/parser/extractors/python.ts +3 -1
- package/src/core/parser/extractors/typescript.ts +5 -1
- package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
- package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
- package/src/core/parser/schema/schema-provider-registry.ts +43 -0
- package/src/core/parser/schema/schema-provider.interface.ts +33 -0
- package/src/core/resolver/path-resolver.ts +147 -147
- package/src/core/resolver/vertical-slice-tracer.ts +34 -1
- package/src/core/topology/environment-detector.ts +860 -0
- package/src/core/topology/types.ts +83 -0
- package/src/index.ts +3 -0
- package/src/mcp/schemas.ts +48 -0
- package/src/mcp/server.ts +62 -2
- package/src/mcp/tools/get-environment-topology.ts +141 -0
- package/src/mcp/tools/get-symbol-hotspots.ts +13 -3
- package/src/mcp/tools/register-domain.ts +1 -1
- package/src/mcp/tools/trace-vertical-slice.ts +9 -64
- package/src/types/index.ts +16 -0
- 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
|
|
package/src/mcp/schemas.ts
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
formattedText += `
|
|
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
|
|
package/src/types/index.ts
CHANGED
|
@@ -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;
|