septum 0.1.4 → 0.1.6
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/LICENSE +21 -21
- package/README.md +105 -105
- package/bin/septum.ts +4 -4
- package/package.json +63 -63
- package/src/cli/commands/check.ts +161 -161
- package/src/cli/commands/hook.ts +216 -188
- package/src/cli/commands/ingest.ts +29 -29
- package/src/cli/commands/init.ts +213 -211
- package/src/cli/commands/locate.ts +61 -61
- package/src/cli/commands/query.ts +104 -64
- package/src/cli/commands/serve.ts +5 -5
- package/src/cli/commands/slice.ts +50 -48
- package/src/cli/commands/sync.ts +31 -31
- package/src/cli/index.ts +220 -210
- package/src/cli/templates/hooks.ts +75 -75
- package/src/cli/templates/rules.ts +50 -38
- package/src/cli/templates/skill.ts +65 -69
- package/src/core/boundary/evaluator.ts +429 -210
- package/src/core/config/loader.ts +117 -97
- package/src/core/config/schema.ts +39 -39
- package/src/core/database/client.ts +44 -44
- package/src/core/database/repositories/dependency.repository.ts +552 -349
- package/src/core/database/repositories/domain.repository.ts +275 -198
- package/src/core/database/repositories/file.repository.ts +117 -106
- package/src/core/database/repositories/meta.repository.ts +33 -33
- package/src/core/database/repositories/symbol.repository.ts +482 -443
- package/src/core/database/repository.ts +281 -260
- package/src/core/database/schema.ts +154 -153
- package/src/core/discovery/topology-detector.ts +486 -486
- package/src/core/ingestion/hasher.ts +5 -5
- package/src/core/ingestion/pipeline.ts +606 -535
- package/src/core/parser/boundary-tracker.ts +433 -288
- package/src/core/parser/extractors/base.ts +62 -62
- package/src/core/parser/extractors/blade.ts +158 -158
- package/src/core/parser/extractors/frontend.ts +147 -194
- package/src/core/parser/extractors/go.ts +408 -180
- package/src/core/parser/extractors/laravel-semantic.ts +1045 -901
- package/src/core/parser/extractors/mcp-cli-semantic.ts +328 -199
- package/src/core/parser/extractors/nestjs-semantic.ts +134 -119
- package/src/core/parser/extractors/php.ts +415 -153
- package/src/core/parser/extractors/python.ts +460 -228
- package/src/core/parser/extractors/registry.ts +70 -70
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
- package/src/core/parser/extractors/typescript.ts +668 -181
- package/src/core/parser/tree-sitter.ts +41 -41
- package/src/core/parser/wasm-loader.ts +271 -0
- package/src/core/resolver/call-graph-tracer.ts +698 -668
- package/src/core/resolver/module-resolver.ts +479 -342
- package/src/core/resolver/path-resolver.ts +147 -147
- package/src/core/resolver/symbol-locator.ts +543 -543
- package/src/core/resolver/vertical-slice-tracer.ts +323 -285
- package/src/core/session/session-manager.ts +56 -56
- package/src/core/telemetry/telemetry.ts +86 -86
- package/src/core/topology/environment-detector.ts +109 -6
- package/src/index.ts +21 -21
- package/src/mcp/schemas.ts +695 -286
- package/src/mcp/server.ts +927 -716
- package/src/mcp/tools/check-boundary.ts +307 -152
- package/src/mcp/tools/get-domain-catalog.ts +155 -121
- package/src/mcp/tools/get-environment-topology.ts +3 -6
- package/src/mcp/tools/get-feature-context.ts +227 -116
- package/src/mcp/tools/get-symbol-hotspots.ts +54 -59
- package/src/mcp/tools/get-symbol-impact.ts +28 -26
- package/src/mcp/tools/get-symbol.ts +65 -63
- package/src/mcp/tools/locate-symbol.ts +169 -190
- package/src/mcp/tools/register-domain.ts +59 -64
- package/src/mcp/tools/register-feature.ts +91 -0
- package/src/mcp/tools/trace-vertical-slice.ts +74 -48
- package/src/types/index.ts +366 -315
- package/src/version.ts +3 -3
|
@@ -1,152 +1,307 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
args
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import type { BoundaryEvaluator } from "../../core/boundary/evaluator.ts";
|
|
3
|
+
import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
|
|
4
|
+
import type { SeptumRepository } from "../../core/database/repository.ts";
|
|
5
|
+
import type { BoundaryViolation } from "../../types/index.ts";
|
|
6
|
+
import { normalizeBoundaryArgs, type CheckBoundaryArgs } from "../schemas.ts";
|
|
7
|
+
|
|
8
|
+
export type { CheckBoundaryArgs } from "../schemas.ts";
|
|
9
|
+
|
|
10
|
+
export function handleCheckBoundary(
|
|
11
|
+
evaluator: BoundaryEvaluator,
|
|
12
|
+
config: ValidatedSeptumConfig,
|
|
13
|
+
rawArgs: CheckBoundaryArgs & Record<string, unknown>,
|
|
14
|
+
repo?: SeptumRepository,
|
|
15
|
+
workspaceRoot?: string
|
|
16
|
+
) {
|
|
17
|
+
const args = (normalizeBoundaryArgs(rawArgs ?? {}) ?? {}) as CheckBoundaryArgs & Record<string, unknown>;
|
|
18
|
+
const root = workspaceRoot || args.workspace_path || process.cwd();
|
|
19
|
+
const proposedImports = args.proposed_imports ?? [];
|
|
20
|
+
const featureKey = args.feature_key;
|
|
21
|
+
|
|
22
|
+
const filesToCheck: string[] = [];
|
|
23
|
+
if (args.file_paths && Array.isArray(args.file_paths) && args.file_paths.length > 0) {
|
|
24
|
+
filesToCheck.push(...args.file_paths);
|
|
25
|
+
} else if (args.file_path) {
|
|
26
|
+
if (Array.isArray(args.file_path)) {
|
|
27
|
+
filesToCheck.push(...args.file_path);
|
|
28
|
+
} else if (typeof args.file_path === "string" && args.file_path.trim().length > 0) {
|
|
29
|
+
filesToCheck.push(args.file_path.trim());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Repo-wide, domain-scoped, or git-staged boundary audit when neither file_path nor file_paths is provided
|
|
34
|
+
if (filesToCheck.length === 0) {
|
|
35
|
+
const effectiveScope =
|
|
36
|
+
args.scope ?? (args.staged ? "staged" : args.domain ? "domain" : "all");
|
|
37
|
+
|
|
38
|
+
if (effectiveScope === "staged") {
|
|
39
|
+
let stagedFiles: string[] = [];
|
|
40
|
+
try {
|
|
41
|
+
const gitOut = execSync("git diff --name-only --cached", {
|
|
42
|
+
cwd: root,
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
timeout: 5000,
|
|
45
|
+
});
|
|
46
|
+
stagedFiles = gitOut
|
|
47
|
+
.split("\n")
|
|
48
|
+
.map((f) => f.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
} catch {
|
|
51
|
+
stagedFiles = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (stagedFiles.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text" as const,
|
|
59
|
+
text: JSON.stringify(
|
|
60
|
+
{
|
|
61
|
+
status: "approved",
|
|
62
|
+
scope: "staged",
|
|
63
|
+
total_checked: 0,
|
|
64
|
+
total_violations: 0,
|
|
65
|
+
circular_dependencies: [],
|
|
66
|
+
domain_violation_breakdown: {},
|
|
67
|
+
violations: [],
|
|
68
|
+
message: "No git staged files found. Boundary check passed.",
|
|
69
|
+
},
|
|
70
|
+
null,
|
|
71
|
+
2
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const codebaseViolations = evaluator.auditCodebase(config);
|
|
79
|
+
const stagedNormalized = new Set(
|
|
80
|
+
stagedFiles.map((f) => f.replace(/\\/g, "/").replace(/^\.\//, ""))
|
|
81
|
+
);
|
|
82
|
+
const stagedViolations: BoundaryViolation[] = [];
|
|
83
|
+
|
|
84
|
+
for (const filePath of stagedFiles) {
|
|
85
|
+
const evalViolations = evaluator.evaluate(filePath, proposedImports, featureKey, config);
|
|
86
|
+
stagedViolations.push(...evalViolations);
|
|
87
|
+
}
|
|
88
|
+
for (const v of codebaseViolations) {
|
|
89
|
+
const normFile = v.file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
90
|
+
if (stagedNormalized.has(normFile)) {
|
|
91
|
+
stagedViolations.push(v);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const filteredViolations = args.domain
|
|
96
|
+
? stagedViolations.filter(
|
|
97
|
+
(v) =>
|
|
98
|
+
v.source_domain.toLowerCase() === args.domain!.toLowerCase() ||
|
|
99
|
+
v.target_domain.toLowerCase() === args.domain!.toLowerCase()
|
|
100
|
+
)
|
|
101
|
+
: stagedViolations;
|
|
102
|
+
|
|
103
|
+
const circularDependencies = filteredViolations.filter((v) => v.rule === "circular_dependency");
|
|
104
|
+
const domainBreakdown: Record<string, number> = {};
|
|
105
|
+
for (const v of filteredViolations) {
|
|
106
|
+
const pair = `${v.source_domain} -> ${v.target_domain}`;
|
|
107
|
+
domainBreakdown[pair] = (domainBreakdown[pair] || 0) + 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const hasViolations = filteredViolations.length > 0;
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text" as const,
|
|
115
|
+
text: JSON.stringify(
|
|
116
|
+
{
|
|
117
|
+
status: hasViolations ? "rejected" : "approved",
|
|
118
|
+
scope: "staged",
|
|
119
|
+
total_checked: stagedFiles.length,
|
|
120
|
+
total_violations: filteredViolations.length,
|
|
121
|
+
circular_dependencies_count: circularDependencies.length,
|
|
122
|
+
circular_dependencies: circularDependencies,
|
|
123
|
+
domain_violation_breakdown: domainBreakdown,
|
|
124
|
+
violations: filteredViolations,
|
|
125
|
+
message: hasViolations
|
|
126
|
+
? `${filteredViolations.length} boundary violation(s) detected across ${stagedFiles.length} staged file(s).`
|
|
127
|
+
: `All ${stagedFiles.length} staged file(s) approved. No boundary violations detected.`,
|
|
128
|
+
},
|
|
129
|
+
null,
|
|
130
|
+
2
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Scope: "all" or "domain"
|
|
138
|
+
let violations = evaluator.auditCodebase(config);
|
|
139
|
+
if (args.domain) {
|
|
140
|
+
const targetDom = args.domain.toLowerCase();
|
|
141
|
+
violations = violations.filter(
|
|
142
|
+
(v) =>
|
|
143
|
+
v.source_domain.toLowerCase() === targetDom ||
|
|
144
|
+
v.target_domain.toLowerCase() === targetDom
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const circularDependencies = violations.filter((v) => v.rule === "circular_dependency");
|
|
149
|
+
const domainBreakdown: Record<string, number> = {};
|
|
150
|
+
for (const v of violations) {
|
|
151
|
+
const pair = `${v.source_domain} -> ${v.target_domain}`;
|
|
152
|
+
domainBreakdown[pair] = (domainBreakdown[pair] || 0) + 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const hasViolations = violations.length > 0;
|
|
156
|
+
const domainCount = Object.keys(config?.domains || {}).length;
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text" as const,
|
|
162
|
+
text: JSON.stringify(
|
|
163
|
+
{
|
|
164
|
+
status: hasViolations ? "rejected" : "approved",
|
|
165
|
+
scope: args.domain ? "domain" : "all",
|
|
166
|
+
...(args.domain ? { domain: args.domain } : {}),
|
|
167
|
+
total_domains_checked: domainCount,
|
|
168
|
+
total_violations: violations.length,
|
|
169
|
+
circular_dependencies_count: circularDependencies.length,
|
|
170
|
+
circular_dependencies: circularDependencies,
|
|
171
|
+
domain_violation_breakdown: domainBreakdown,
|
|
172
|
+
violations,
|
|
173
|
+
message: hasViolations
|
|
174
|
+
? `${violations.length} boundary violation(s) (${circularDependencies.length} circular) detected${
|
|
175
|
+
args.domain ? ` in domain '${args.domain}'` : " across codebase"
|
|
176
|
+
}.`
|
|
177
|
+
: domainCount === 0
|
|
178
|
+
? "No boundary violations detected. [Septum Notice: Domain catalog is currently unindexed. Run 'septum_register_domain' to initialize bounded contexts.]"
|
|
179
|
+
: `All domain boundaries are clean${
|
|
180
|
+
args.domain ? ` for domain '${args.domain}'` : ""
|
|
181
|
+
}. Zero cross-domain leaks or circular dependencies detected.`,
|
|
182
|
+
},
|
|
183
|
+
null,
|
|
184
|
+
2
|
|
185
|
+
),
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Multi-file batch evaluation
|
|
192
|
+
if (filesToCheck.length > 1) {
|
|
193
|
+
const results = filesToCheck.map((filePath) => {
|
|
194
|
+
const violations = evaluator.evaluate(filePath, proposedImports, featureKey, config);
|
|
195
|
+
const impact = repo?.getFileInboundImpact(filePath);
|
|
196
|
+
return {
|
|
197
|
+
file: filePath,
|
|
198
|
+
status: violations.length > 0 ? "rejected" : "approved",
|
|
199
|
+
violations,
|
|
200
|
+
impact_summary: impact
|
|
201
|
+
? {
|
|
202
|
+
direct_dependents: impact.direct_dependents_count,
|
|
203
|
+
top_consumers: impact.top_consumers,
|
|
204
|
+
symbols_count: impact.symbols_count,
|
|
205
|
+
risk_level: impact.risk_level,
|
|
206
|
+
}
|
|
207
|
+
: undefined,
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const totalViolations = results.reduce((acc, r) => acc + r.violations.length, 0);
|
|
212
|
+
const hasViolations = totalViolations > 0;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: "text" as const,
|
|
218
|
+
text: JSON.stringify(
|
|
219
|
+
{
|
|
220
|
+
status: hasViolations ? "rejected" : "approved",
|
|
221
|
+
total_checked: filesToCheck.length,
|
|
222
|
+
total_violations: totalViolations,
|
|
223
|
+
results,
|
|
224
|
+
message: hasViolations
|
|
225
|
+
? `${totalViolations} boundary violation(s) detected across ${filesToCheck.length} file(s).`
|
|
226
|
+
: Object.keys(config?.domains || {}).length === 0
|
|
227
|
+
? `All ${filesToCheck.length} file(s) approved. [Septum Notice: Domain catalog is currently unindexed. Run 'septum_register_domain' to initialize bounded contexts.]`
|
|
228
|
+
: `All ${filesToCheck.length} file(s) approved. No boundary violations detected.`,
|
|
229
|
+
},
|
|
230
|
+
null,
|
|
231
|
+
2
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Single file evaluation (backward-compatible output)
|
|
239
|
+
const singleFile = filesToCheck[0];
|
|
240
|
+
const violations = evaluator.evaluate(singleFile, proposedImports, featureKey, config);
|
|
241
|
+
const impact = repo?.getFileInboundImpact(singleFile);
|
|
242
|
+
|
|
243
|
+
if (violations.length > 0) {
|
|
244
|
+
return {
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: "text" as const,
|
|
248
|
+
text: JSON.stringify(
|
|
249
|
+
{
|
|
250
|
+
status: "rejected",
|
|
251
|
+
file: singleFile,
|
|
252
|
+
violations_count: violations.length,
|
|
253
|
+
violations,
|
|
254
|
+
impact_summary: impact
|
|
255
|
+
? {
|
|
256
|
+
direct_dependents: impact.direct_dependents_count,
|
|
257
|
+
top_consumers: impact.top_consumers,
|
|
258
|
+
symbols_count: impact.symbols_count,
|
|
259
|
+
risk_level: impact.risk_level,
|
|
260
|
+
}
|
|
261
|
+
: undefined,
|
|
262
|
+
message:
|
|
263
|
+
Object.keys(config?.domains || {}).length === 0
|
|
264
|
+
? "Boundary integrity check failed: Domain catalog is currently unindexed. Run 'septum_register_domain' to initialize bounded contexts."
|
|
265
|
+
: violations.some((v) => v.rule === "unmapped_domain")
|
|
266
|
+
? "Boundary integrity check failed: File does not belong to any declared bounded context."
|
|
267
|
+
: "Boundary violation detected. Proposed imports violate bounded context architecture.",
|
|
268
|
+
},
|
|
269
|
+
null,
|
|
270
|
+
2
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: "text" as const,
|
|
281
|
+
text: JSON.stringify(
|
|
282
|
+
{
|
|
283
|
+
status: "approved",
|
|
284
|
+
file: singleFile,
|
|
285
|
+
impact_summary: impact
|
|
286
|
+
? {
|
|
287
|
+
direct_dependents: impact.direct_dependents_count,
|
|
288
|
+
top_consumers: impact.top_consumers,
|
|
289
|
+
symbols_count: impact.symbols_count,
|
|
290
|
+
risk_level: impact.risk_level,
|
|
291
|
+
}
|
|
292
|
+
: undefined,
|
|
293
|
+
message:
|
|
294
|
+
Object.keys(config?.domains || {}).length === 0
|
|
295
|
+
? "No boundary violations detected. [Septum Notice: Domain catalog is currently unindexed. Run 'septum_register_domain' to initialize bounded contexts.]"
|
|
296
|
+
: impact && impact.direct_dependents_count > 0
|
|
297
|
+
? `No boundary violations detected. Caution: ${impact.direct_dependents_count} dependent file(s) consume this file.`
|
|
298
|
+
: "No boundary violations detected. Proposed imports are permissible.",
|
|
299
|
+
},
|
|
300
|
+
null,
|
|
301
|
+
2
|
|
302
|
+
),
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|