septum 0.1.2 → 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 +7 -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/cli/templates/skill.ts +7 -4
- package/src/core/config/loader.ts +21 -8
- package/src/core/database/repositories/dependency.repository.ts +70 -0
- package/src/core/database/repositories/environment.repository.ts +207 -0
- package/src/core/database/repositories/file.repository.ts +7 -0
- package/src/core/database/repositories/symbol.repository.ts +28 -9
- package/src/core/database/repository.ts +19 -1
- package/src/core/database/schema.ts +153 -120
- package/src/core/discovery/topology-detector.ts +71 -3
- package/src/core/ingestion/pipeline.ts +98 -1
- 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 +2 -0
- package/src/core/parser/extractors/blade.ts +158 -0
- package/src/core/parser/extractors/go.ts +5 -1
- package/src/core/parser/extractors/laravel-semantic.ts +506 -31
- package/src/core/parser/extractors/php.ts +5 -1
- package/src/core/parser/extractors/python.ts +3 -1
- package/src/core/parser/extractors/registry.ts +21 -0
- 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/parser/tree-sitter.ts +2 -0
- package/src/core/resolver/path-resolver.ts +147 -0
- package/src/core/resolver/vertical-slice-tracer.ts +56 -8
- 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 +264 -46
- package/src/mcp/server.ts +130 -26
- package/src/mcp/tools/check-boundary.ts +58 -8
- package/src/mcp/tools/get-domain-catalog.ts +31 -1
- package/src/mcp/tools/get-environment-topology.ts +141 -0
- package/src/mcp/tools/get-symbol-hotspots.ts +13 -3
- package/src/mcp/tools/locate-symbol.ts +119 -3
- package/src/mcp/tools/register-domain.ts +1 -1
- package/src/mcp/tools/trace-vertical-slice.ts +11 -65
- package/src/types/index.ts +20 -1
- 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
|
@@ -1,68 +1,286 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
function withAliases(raw: unknown, aliases: Record<string, string>): unknown {
|
|
4
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
|
|
5
|
+
const obj = { ...(raw as Record<string, unknown>) };
|
|
6
|
+
for (const [alias, target] of Object.entries(aliases)) {
|
|
7
|
+
if (obj[alias] !== undefined && obj[target] === undefined) {
|
|
8
|
+
obj[target] = obj[alias];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return obj;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeBoundaryArgs(raw: unknown): unknown {
|
|
15
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
|
|
16
|
+
const obj = { ...(raw as Record<string, unknown>) };
|
|
17
|
+
const aliases: Record<string, string> = {
|
|
18
|
+
filePath: "file_path",
|
|
19
|
+
file: "file_path",
|
|
20
|
+
path: "file_path",
|
|
21
|
+
filePaths: "file_paths",
|
|
22
|
+
files: "file_paths",
|
|
23
|
+
paths: "file_paths",
|
|
24
|
+
proposedImports: "proposed_imports",
|
|
25
|
+
imports: "proposed_imports",
|
|
26
|
+
featureKey: "feature_key",
|
|
27
|
+
feature: "feature_key",
|
|
28
|
+
};
|
|
29
|
+
for (const [alias, target] of Object.entries(aliases)) {
|
|
30
|
+
if (obj[alias] !== undefined && obj[target] === undefined) {
|
|
31
|
+
obj[target] = obj[alias];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(obj.file_path) && !obj.file_paths) {
|
|
35
|
+
obj.file_paths = obj.file_path;
|
|
36
|
+
delete obj.file_path;
|
|
37
|
+
}
|
|
38
|
+
if (typeof obj.file_paths === "string" && !obj.file_path) {
|
|
39
|
+
obj.file_path = obj.file_paths;
|
|
40
|
+
delete obj.file_paths;
|
|
41
|
+
}
|
|
42
|
+
return obj;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const GetDomainCatalogSchema = z.preprocess(
|
|
46
|
+
(args) =>
|
|
47
|
+
withAliases(args, {
|
|
48
|
+
domain_name: "domain",
|
|
49
|
+
domainName: "domain",
|
|
50
|
+
name: "domain",
|
|
51
|
+
archetype_filter: "archetype",
|
|
52
|
+
archetypeFilter: "archetype",
|
|
53
|
+
type: "archetype",
|
|
54
|
+
workspace: "workspace_path",
|
|
55
|
+
workspacePath: "workspace_path",
|
|
56
|
+
project_root: "workspace_path",
|
|
57
|
+
target_path: "workspace_path",
|
|
58
|
+
path: "workspace_path",
|
|
59
|
+
}),
|
|
60
|
+
z.object({
|
|
61
|
+
domain: z.string().optional(),
|
|
62
|
+
archetype: z.string().optional(),
|
|
63
|
+
workspace_path: z.string().optional(),
|
|
64
|
+
})
|
|
65
|
+
);
|
|
7
66
|
export type GetDomainCatalogArgs = z.infer<typeof GetDomainCatalogSchema>;
|
|
8
67
|
|
|
9
|
-
export const GetFeatureContextSchema = z.
|
|
10
|
-
|
|
11
|
-
|
|
68
|
+
export const GetFeatureContextSchema = z.preprocess(
|
|
69
|
+
(args) =>
|
|
70
|
+
withAliases(args, {
|
|
71
|
+
feature_key: "feature",
|
|
72
|
+
featureKey: "feature",
|
|
73
|
+
name: "feature",
|
|
74
|
+
workspace: "workspace_path",
|
|
75
|
+
workspacePath: "workspace_path",
|
|
76
|
+
}),
|
|
77
|
+
z.object({
|
|
78
|
+
feature: z.string().min(1, "feature is required"),
|
|
79
|
+
workspace_path: z.string().optional(),
|
|
80
|
+
})
|
|
81
|
+
);
|
|
12
82
|
export type GetFeatureContextArgs = z.infer<typeof GetFeatureContextSchema>;
|
|
13
83
|
|
|
14
|
-
export const LocateSymbolSchema = z.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
84
|
+
export const LocateSymbolSchema = z.preprocess(
|
|
85
|
+
(args) =>
|
|
86
|
+
withAliases(args, {
|
|
87
|
+
symbol: "query",
|
|
88
|
+
name: "query",
|
|
89
|
+
symbol_name: "query",
|
|
90
|
+
symbolName: "query",
|
|
91
|
+
target: "query",
|
|
92
|
+
domain_name: "domain",
|
|
93
|
+
domainName: "domain",
|
|
94
|
+
max: "limit",
|
|
95
|
+
workspace: "workspace_path",
|
|
96
|
+
workspacePath: "workspace_path",
|
|
97
|
+
project_root: "workspace_path",
|
|
98
|
+
target_path: "workspace_path",
|
|
99
|
+
path: "workspace_path",
|
|
100
|
+
}),
|
|
101
|
+
z.object({
|
|
102
|
+
query: z.string().min(1, "query is required"),
|
|
103
|
+
domain: z.string().optional(),
|
|
104
|
+
limit: z.number().int().positive().optional().default(10),
|
|
105
|
+
workspace_path: z.string().optional(),
|
|
106
|
+
})
|
|
107
|
+
);
|
|
19
108
|
export type LocateSymbolArgs = z.infer<typeof LocateSymbolSchema>;
|
|
20
109
|
|
|
21
|
-
export const GetSymbolSchema = z.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
110
|
+
export const GetSymbolSchema = z.preprocess(
|
|
111
|
+
(args) =>
|
|
112
|
+
withAliases(args, {
|
|
113
|
+
query: "symbol",
|
|
114
|
+
name: "symbol",
|
|
115
|
+
symbol_name: "symbol",
|
|
116
|
+
symbolName: "symbol",
|
|
117
|
+
target: "symbol",
|
|
118
|
+
domain_name: "domain",
|
|
119
|
+
domainName: "domain",
|
|
120
|
+
includeDependencies: "include_dependencies",
|
|
121
|
+
include_deps: "include_dependencies",
|
|
122
|
+
includeDeps: "include_dependencies",
|
|
123
|
+
dependencies: "include_dependencies",
|
|
124
|
+
deps: "include_dependencies",
|
|
125
|
+
}),
|
|
126
|
+
z.object({
|
|
127
|
+
symbol: z.string().min(1, "symbol is required"),
|
|
128
|
+
domain: z.string().optional(),
|
|
129
|
+
include_dependencies: z.boolean().optional().default(true),
|
|
130
|
+
})
|
|
131
|
+
);
|
|
26
132
|
export type GetSymbolArgs = z.infer<typeof GetSymbolSchema>;
|
|
27
133
|
|
|
28
|
-
export const GetSymbolImpactSchema = z.
|
|
29
|
-
|
|
30
|
-
|
|
134
|
+
export const GetSymbolImpactSchema = z.preprocess(
|
|
135
|
+
(args) =>
|
|
136
|
+
withAliases(args, {
|
|
137
|
+
query: "symbol",
|
|
138
|
+
name: "symbol",
|
|
139
|
+
symbol_name: "symbol",
|
|
140
|
+
symbolName: "symbol",
|
|
141
|
+
file_path: "symbol",
|
|
142
|
+
filePath: "symbol",
|
|
143
|
+
file: "symbol",
|
|
144
|
+
path: "symbol",
|
|
145
|
+
target: "symbol",
|
|
146
|
+
}),
|
|
147
|
+
z.object({
|
|
148
|
+
symbol: z.string().min(1, "symbol is required"),
|
|
149
|
+
})
|
|
150
|
+
);
|
|
31
151
|
export type GetSymbolImpactArgs = z.infer<typeof GetSymbolImpactSchema>;
|
|
32
152
|
|
|
33
|
-
export const TraceVerticalSliceSchema = z.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
153
|
+
export const TraceVerticalSliceSchema = z.preprocess(
|
|
154
|
+
(args) =>
|
|
155
|
+
withAliases(args, {
|
|
156
|
+
route: "query",
|
|
157
|
+
uri: "query",
|
|
158
|
+
route_uri: "query",
|
|
159
|
+
routeUri: "query",
|
|
160
|
+
symbol: "query",
|
|
161
|
+
name: "query",
|
|
162
|
+
feature: "query",
|
|
163
|
+
feature_key: "query",
|
|
164
|
+
featureKey: "query",
|
|
165
|
+
maxDepth: "max_depth",
|
|
166
|
+
depth: "max_depth",
|
|
167
|
+
}),
|
|
168
|
+
z.object({
|
|
169
|
+
query: z.string().min(1, "query is required"),
|
|
170
|
+
max_depth: z.number().int().positive().optional().default(5),
|
|
171
|
+
})
|
|
172
|
+
);
|
|
37
173
|
export type TraceVerticalSliceArgs = z.infer<typeof TraceVerticalSliceSchema>;
|
|
38
174
|
|
|
39
|
-
export const CheckBoundarySchema = z.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
175
|
+
export const CheckBoundarySchema = z.preprocess(
|
|
176
|
+
normalizeBoundaryArgs,
|
|
177
|
+
z.object({
|
|
178
|
+
file_path: z.string().optional(),
|
|
179
|
+
file_paths: z.array(z.string()).optional(),
|
|
180
|
+
proposed_imports: z.array(z.string()).default([]),
|
|
181
|
+
feature_key: z.string().optional(),
|
|
182
|
+
})
|
|
183
|
+
);
|
|
45
184
|
export type CheckBoundaryArgs = z.infer<typeof CheckBoundarySchema>;
|
|
46
185
|
|
|
47
|
-
export const GetSymbolHotspotsSchema = z.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
186
|
+
export const GetSymbolHotspotsSchema = z.preprocess(
|
|
187
|
+
(args) =>
|
|
188
|
+
withAliases(args, {
|
|
189
|
+
domain_name: "domain",
|
|
190
|
+
domainName: "domain",
|
|
191
|
+
max: "limit",
|
|
192
|
+
nesting: "min_nesting",
|
|
193
|
+
minNesting: "min_nesting",
|
|
194
|
+
lines: "min_lines",
|
|
195
|
+
minLines: "min_lines",
|
|
196
|
+
sortBy: "sort_by",
|
|
197
|
+
}),
|
|
198
|
+
z.object({
|
|
199
|
+
min_lines: z.number().int().nonnegative().optional(),
|
|
200
|
+
min_nesting: z.number().int().positive().optional(),
|
|
201
|
+
kind: z.string().optional(),
|
|
202
|
+
domain: z.string().optional(),
|
|
203
|
+
limit: z.number().int().positive().optional().default(10),
|
|
204
|
+
sort_by: z.enum(["lines", "nesting", "risk_score"]).optional(),
|
|
205
|
+
})
|
|
206
|
+
);
|
|
51
207
|
export type GetSymbolHotspotsArgs = z.infer<typeof GetSymbolHotspotsSchema>;
|
|
52
208
|
|
|
53
|
-
export const RegisterDomainSchema = z.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
209
|
+
export const RegisterDomainSchema = z.preprocess(
|
|
210
|
+
(args) =>
|
|
211
|
+
withAliases(args, {
|
|
212
|
+
domain: "name",
|
|
213
|
+
domain_name: "name",
|
|
214
|
+
domainName: "name",
|
|
215
|
+
root_path: "root",
|
|
216
|
+
rootPath: "root",
|
|
217
|
+
path: "root",
|
|
218
|
+
allowedDependencies: "allowed_dependencies",
|
|
219
|
+
allowed: "allowed_dependencies",
|
|
220
|
+
forbiddenDependencies: "forbidden_dependencies",
|
|
221
|
+
forbidden: "forbidden_dependencies",
|
|
222
|
+
archetype: "archetypes",
|
|
223
|
+
ingestNow: "ingest_now",
|
|
224
|
+
}),
|
|
225
|
+
z.object({
|
|
226
|
+
name: z.string().min(1, "Domain name is required"),
|
|
227
|
+
root: z.string().min(1, "Domain root path is required"),
|
|
228
|
+
description: z.string().optional(),
|
|
229
|
+
allowed_dependencies: z.array(z.string()).optional(),
|
|
230
|
+
forbidden_dependencies: z.array(z.string()).optional(),
|
|
231
|
+
archetypes: z.record(z.string()).optional(),
|
|
232
|
+
ingest_now: z.boolean().optional().default(false),
|
|
233
|
+
})
|
|
234
|
+
);
|
|
62
235
|
export type RegisterDomainArgs = z.infer<typeof RegisterDomainSchema>;
|
|
63
236
|
|
|
64
|
-
export const AutoDiscoverDomainsSchema = z.
|
|
65
|
-
|
|
66
|
-
|
|
237
|
+
export const AutoDiscoverDomainsSchema = z.preprocess(
|
|
238
|
+
(args) =>
|
|
239
|
+
withAliases(args, {
|
|
240
|
+
save: "persist",
|
|
241
|
+
}),
|
|
242
|
+
z.object({
|
|
243
|
+
persist: z.boolean().optional().default(true),
|
|
244
|
+
})
|
|
245
|
+
);
|
|
67
246
|
export type AutoDiscoverDomainsArgs = z.infer<typeof AutoDiscoverDomainsSchema>;
|
|
68
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
|
+
|