septum 0.1.5 → 0.1.7
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 +23 -3
- package/package.json +5 -5
- package/src/cli/commands/check.ts +32 -122
- package/src/cli/commands/hook.ts +45 -12
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/query.ts +45 -5
- package/src/cli/commands/slice.ts +4 -2
- package/src/cli/index.ts +17 -6
- package/src/cli/templates/hooks.ts +6 -6
- package/src/cli/templates/rules.ts +35 -23
- package/src/cli/templates/skill.ts +47 -51
- package/src/core/boundary/evaluator.ts +217 -24
- package/src/core/boundary/staged-auditor.ts +47 -0
- package/src/core/config/loader.ts +29 -12
- package/src/core/database/repositories/dependency.repository.ts +252 -66
- package/src/core/database/repositories/domain.repository.ts +96 -39
- package/src/core/database/repositories/file.repository.ts +20 -19
- package/src/core/database/repositories/symbol.repository.ts +115 -5
- package/src/core/database/repository.ts +19 -2
- package/src/core/database/schema.ts +3 -2
- package/src/core/ingestion/pipeline.ts +96 -60
- package/src/core/parser/boundary-tracker.ts +145 -0
- package/src/core/parser/extractors/frontend.ts +44 -90
- package/src/core/parser/extractors/go.ts +200 -21
- package/src/core/parser/extractors/laravel-semantic.ts +18 -12
- package/src/core/parser/extractors/mcp-cli-semantic.ts +173 -44
- package/src/core/parser/extractors/nestjs-semantic.ts +20 -5
- package/src/core/parser/extractors/php.ts +293 -31
- package/src/core/parser/extractors/python.ts +242 -10
- package/src/core/parser/extractors/typescript.ts +604 -117
- package/src/core/parser/tree-sitter.ts +6 -1
- package/src/core/parser/wasm-assets.ts +17 -0
- package/src/core/parser/wasm-loader.ts +274 -0
- package/src/core/resolver/call-graph-tracer.ts +49 -17
- package/src/core/resolver/module-resolver.ts +88 -26
- package/src/core/resolver/vertical-slice-tracer.ts +34 -11
- package/src/core/session/session-manager.ts +55 -39
- package/src/core/topology/environment-detector.ts +109 -6
- package/src/mcp/schemas.ts +146 -29
- package/src/mcp/server.ts +177 -114
- package/src/mcp/tools/check-boundary.ts +159 -41
- package/src/mcp/tools/get-domain-catalog.ts +30 -15
- package/src/mcp/tools/get-environment-topology.ts +3 -6
- package/src/mcp/tools/get-feature-context.ts +35 -20
- package/src/mcp/tools/get-symbol-hotspots.ts +4 -10
- package/src/mcp/tools/locate-symbol.ts +37 -16
- package/src/mcp/tools/register-domain.ts +3 -12
- package/src/mcp/tools/register-feature.ts +25 -32
- package/src/mcp/tools/trace-vertical-slice.ts +8 -8
- package/src/types/index.ts +56 -6
- package/src/types/wasm.d.ts +4 -0
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> **Deterministic Bounded-Context & Anti-Poisoning Architecture Guard for AI Coding Agents.**
|
|
4
4
|
|
|
5
|
-
[](package.json)
|
|
6
6
|
[](https://bun.sh)
|
|
7
7
|
[](https://modelcontextprotocol.io/)
|
|
8
8
|
[-003B57?logo=sqlite)](https://sqlite.org/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
|
-
Septum
|
|
11
|
+
Septum helps AI coding agents navigate code and respect declared architectural boundaries. It indexes your codebase into an embedded SQLite SSOT (`.septum/septum.db`), enforces strict bounded-context domains, and intercepts unauthorized cross-domain edits before they hit disk.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -59,7 +59,7 @@ Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
|
|
|
59
59
|
|---|---|
|
|
60
60
|
| `septum init` | Detect topology, scaffold boundary guards, and build initial catalog |
|
|
61
61
|
| `septum ingest` | Full AST re-indexing into `.septum/septum.db` |
|
|
62
|
-
| `septum sync` |
|
|
62
|
+
| `septum sync` | Incremental synchronization of modified files for modified files |
|
|
63
63
|
| `septum check --strict` | CI/CD gate: verify boundary integrity & fail on violations |
|
|
64
64
|
| `septum query [domain]` | Inspect domain catalog, archetypes, and public signatures |
|
|
65
65
|
| `septum locate <query>` | Locate symbol, class, interface, method, or error source |
|
|
@@ -103,3 +103,23 @@ bun run build
|
|
|
103
103
|
## License
|
|
104
104
|
|
|
105
105
|
[MIT](LICENSE) © shironim
|
|
106
|
+
|
|
107
|
+
## Reliability and Scope
|
|
108
|
+
|
|
109
|
+
`septum check --strict` enables strict dependency rules before evaluating the codebase. A full check synchronizes the index first. CLI and MCP staged checks read Git index blobs, so unstaged edits cannot hide a violation in the proposed commit. Staged checks also evaluate dependency cycles using indexed dependencies with staged imports overlaid.
|
|
110
|
+
|
|
111
|
+
Domain ownership uses the most specific normalized root in both full and single-file ingestion. Feature touchpoints are normalized against the workspace; `/*` permits immediate children and `/**` permits descendants.
|
|
112
|
+
|
|
113
|
+
Each active MCP workspace has a watcher. Changes to multiple files are batched without dropping earlier paths; deletion and directory moves reconcile the catalog. If a native watcher is unavailable, Septum falls back to polling and synchronizes before answering queries. Catalog responses expose `index_status.watcher_mode` and `index_status.unsupported_files`. Single-file updates invalidate cached execution slices; the next trace rebuilds them.
|
|
114
|
+
|
|
115
|
+
Tree-sitter grammars are runtime dependencies and are embedded in compiled executables. The active extractors cover TypeScript/JavaScript, PHP/Blade, Python, Go, and script extraction from Vue/Svelte/Astro. Rust, Java, C#, and Ruby do not yet have active extractors. Unsupported files are reported explicitly; an incomplete audit does not receive a clean approval. Unindexed symbols return `risk_level: "unknown"`.
|
|
116
|
+
|
|
117
|
+
Feature sessions have a single owner per workspace. The default owner is the MCP process; set `SEPTUM_SESSION_ID` when several commands intentionally share the same owner. Another owner cannot replace or release the active session. Use `septum feature clear --force` for explicit recovery after a process exits or for a legacy session without ownership metadata.
|
|
118
|
+
|
|
119
|
+
Run the real-server integration checks with Python 3:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bun run test:integration
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
These checks exercise live rule changes, Git staged blobs, multiple workspaces, rapid edits, directory moves, and unsupported-file diagnostics. The normal `bun test` suite includes a standalone-binary regression for forbidden multiline imports.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "septum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Deterministic Bounded-Context, Structural File Catalog & Anti-Poisoning Partition for AI Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
"check": "bun run bin/septum.ts check",
|
|
19
19
|
"test": "bun test",
|
|
20
20
|
"build": "bun build ./bin/septum.ts --compile --outfile dist/septum",
|
|
21
|
-
"typecheck": "tsc --noEmit"
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test:integration": "python3 tests/integration/mcp-reliability.py"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"@modelcontextprotocol/sdk": "^1.5.0",
|
|
25
26
|
"web-tree-sitter": "^0.24.0",
|
|
26
27
|
"yaml": "^2.7.0",
|
|
27
|
-
"zod": "^3.24.2"
|
|
28
|
+
"zod": "^3.24.2",
|
|
29
|
+
"tree-sitter-wasms": "^0.1.13"
|
|
28
30
|
},
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"repository": {
|
|
@@ -56,8 +58,6 @@
|
|
|
56
58
|
"devDependencies": {
|
|
57
59
|
"@types/bun": "latest",
|
|
58
60
|
"@types/node": "^22.13.4",
|
|
59
|
-
"tree-sitter-wasms": "^0.1.13",
|
|
60
61
|
"typescript": "^5.7.3"
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
1
|
import { BoundaryEvaluator } from "../../core/boundary/evaluator.ts";
|
|
5
|
-
import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
|
|
6
2
|
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
7
3
|
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
8
4
|
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
9
|
-
import { ASTParserEngine } from "../../core/parser/tree-sitter.ts";
|
|
10
5
|
import type { BoundaryViolation } from "../../types/index.ts";
|
|
6
|
+
import { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
|
|
7
|
+
import { auditStagedFiles } from "../../core/boundary/staged-auditor.ts";
|
|
11
8
|
|
|
12
9
|
export interface CheckCommandOptions {
|
|
13
10
|
strict?: boolean;
|
|
@@ -28,134 +25,47 @@ export async function handleCheckCommand(
|
|
|
28
25
|
);
|
|
29
26
|
|
|
30
27
|
const config = ConfigLoader.load();
|
|
28
|
+
if (options.strict) config.settings.enforcement = "strict";
|
|
31
29
|
const db = new SeptumDatabase(config.settings.db_path);
|
|
32
30
|
const repo = new SeptumRepository(db.raw);
|
|
33
31
|
const evaluator = new BoundaryEvaluator(repo);
|
|
34
32
|
|
|
35
|
-
let violations: BoundaryViolation[] = [];
|
|
36
|
-
|
|
37
|
-
if (options.staged) {
|
|
38
|
-
violations = await auditStagedFiles(evaluator, config, options.feature);
|
|
39
|
-
} else {
|
|
40
|
-
violations = evaluator.auditCodebase(config);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (violations.length === 0) {
|
|
44
|
-
console.log("✓ All domain boundaries are clean. Zero cross-domain leaks detected.");
|
|
45
|
-
db.close();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.error(`\n[Septum Boundary Violation] Found ${violations.length} boundary violation(s):\n`);
|
|
50
|
-
|
|
51
|
-
for (const v of violations) {
|
|
52
|
-
console.error(` ✕ [${v.rule.toUpperCase()}] at ${v.file}:${v.line}`);
|
|
53
|
-
console.error(` Source Domain: ${v.source_domain}`);
|
|
54
|
-
console.error(` Target Domain: ${v.target_domain}`);
|
|
55
|
-
console.error(` Import Target: ${v.imported_target}`);
|
|
56
|
-
console.error(` Message: ${v.message}\n`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
db.close();
|
|
60
|
-
|
|
61
|
-
const isStrict = options.strict || config.settings.enforcement === "strict";
|
|
62
|
-
if (isStrict) {
|
|
63
|
-
console.error("[Septum Failure] Process terminated with exit code 1 due to boundary violations.");
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function auditStagedFiles(
|
|
69
|
-
evaluator: BoundaryEvaluator,
|
|
70
|
-
config: ValidatedSeptumConfig,
|
|
71
|
-
featureKey?: string
|
|
72
|
-
): Promise<BoundaryViolation[]> {
|
|
73
|
-
const violations: BoundaryViolation[] = [];
|
|
74
|
-
let stagedFiles: string[] = [];
|
|
75
|
-
|
|
76
33
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.warn(`[Septum Warning] Could not retrieve git staged files: ${msg}`);
|
|
89
|
-
return violations;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (stagedFiles.length === 0) {
|
|
93
|
-
console.log(" Notice: No staged files found in git repository.");
|
|
94
|
-
return violations;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const astEngine = new ASTParserEngine();
|
|
98
|
-
|
|
99
|
-
for (const relPath of stagedFiles) {
|
|
100
|
-
const fullPath = path.resolve(process.cwd(), relPath);
|
|
101
|
-
if (!fs.existsSync(fullPath)) continue;
|
|
102
|
-
|
|
103
|
-
// 1. Feature touchpoint check if featureKey provided
|
|
104
|
-
if (featureKey) {
|
|
105
|
-
const tpViolations = evaluator.checkFeatureTouchpoints(featureKey, relPath, config);
|
|
106
|
-
violations.push(...tpViolations);
|
|
34
|
+
let violations: BoundaryViolation[] = [];
|
|
35
|
+
|
|
36
|
+
if (options.staged) {
|
|
37
|
+
violations = (await auditStagedFiles(evaluator, config, process.cwd(), options.feature, repo)).violations;
|
|
38
|
+
} else {
|
|
39
|
+
await new IngestionPipeline(repo).run(config, { verifyContent: true });
|
|
40
|
+
const unsupported = JSON.parse(repo.getMeta("unsupported_files") || "[]") as string[];
|
|
41
|
+
if (unsupported.length > 0) {
|
|
42
|
+
throw new Error(`Cannot complete boundary audit: unsupported source files: ${unsupported.join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
violations = evaluator.auditCodebase(config);
|
|
107
45
|
}
|
|
108
46
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!sourceConfig) continue;
|
|
114
|
-
|
|
115
|
-
const forbidden = sourceConfig.forbidden_dependencies ?? [];
|
|
116
|
-
const allowed = sourceConfig.allowed_dependencies ?? [];
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const content = fs.readFileSync(fullPath, "utf-8");
|
|
120
|
-
const parsed = await astEngine.parseFile(fullPath, content);
|
|
47
|
+
if (violations.length === 0) {
|
|
48
|
+
console.log("✓ All domain boundaries are clean. Zero cross-domain leaks detected.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
121
51
|
|
|
122
|
-
|
|
123
|
-
if (dep.is_external) continue;
|
|
52
|
+
console.error(`\n[Septum Boundary Violation] Found ${violations.length} boundary violation(s):\n`);
|
|
124
53
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
54
|
+
for (const v of violations) {
|
|
55
|
+
console.error(` ✕ [${v.rule.toUpperCase()}] at ${v.file}:${v.line}`);
|
|
56
|
+
console.error(` Source Domain: ${v.source_domain}`);
|
|
57
|
+
console.error(` Target Domain: ${v.target_domain}`);
|
|
58
|
+
console.error(` Import Target: ${v.imported_target}`);
|
|
59
|
+
console.error(` Message: ${v.message}\n`);
|
|
60
|
+
}
|
|
130
61
|
|
|
131
|
-
if (!targetDomain || targetDomain === sourceDomain) continue;
|
|
132
62
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
source_domain: sourceDomain,
|
|
138
|
-
target_domain: targetDomain,
|
|
139
|
-
imported_target: dep.target,
|
|
140
|
-
rule: "forbidden_dependency",
|
|
141
|
-
message: `Illegal import: Domain '${sourceDomain}' is strictly prohibited from importing domain '${targetDomain}'.`,
|
|
142
|
-
});
|
|
143
|
-
} else if (config.settings.enforcement === "strict" && !allowed.includes(targetDomain)) {
|
|
144
|
-
violations.push({
|
|
145
|
-
file: relPath,
|
|
146
|
-
line: dep.line_number,
|
|
147
|
-
source_domain: sourceDomain,
|
|
148
|
-
target_domain: targetDomain,
|
|
149
|
-
imported_target: dep.target,
|
|
150
|
-
rule: "disallowed_dependency",
|
|
151
|
-
message: `Disallowed dependency: '${targetDomain}' is not declared in allowed_dependencies for domain '${sourceDomain}'.`,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
} catch {
|
|
156
|
-
// Ignore unparseable files
|
|
63
|
+
const isStrict = options.strict || config.settings.enforcement === "strict";
|
|
64
|
+
if (isStrict) {
|
|
65
|
+
console.error("[Septum Failure] Process terminated with exit code 1 due to boundary violations.");
|
|
66
|
+
process.exitCode = 1;
|
|
157
67
|
}
|
|
68
|
+
} finally {
|
|
69
|
+
db.close();
|
|
158
70
|
}
|
|
159
|
-
|
|
160
|
-
return violations;
|
|
161
71
|
}
|
package/src/cli/commands/hook.ts
CHANGED
|
@@ -8,14 +8,26 @@ import { ASTParserEngine } from "../../core/parser/tree-sitter.ts";
|
|
|
8
8
|
import { SessionManager } from "../../core/session/session-manager.ts";
|
|
9
9
|
|
|
10
10
|
export interface ToolPayload {
|
|
11
|
-
tool_name
|
|
12
|
-
tool_input
|
|
11
|
+
tool_name?: string;
|
|
12
|
+
tool_input?: {
|
|
13
13
|
TargetFile?: string;
|
|
14
14
|
file_path?: string;
|
|
15
|
+
filePath?: string;
|
|
16
|
+
path?: string;
|
|
15
17
|
ReplacementContent?: string;
|
|
16
18
|
CodeContent?: string;
|
|
19
|
+
new_string?: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
ReplacementChunks?: Array<{ ReplacementContent?: string }>;
|
|
17
22
|
feature_key?: string;
|
|
18
23
|
};
|
|
24
|
+
arguments?: Record<string, unknown>;
|
|
25
|
+
TargetFile?: string;
|
|
26
|
+
file_path?: string;
|
|
27
|
+
filePath?: string;
|
|
28
|
+
path?: string;
|
|
29
|
+
ReplacementContent?: string;
|
|
30
|
+
CodeContent?: string;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
export async function handleHookCommand(args: string[]): Promise<void> {
|
|
@@ -49,9 +61,20 @@ async function runPreWriteHook(): Promise<void> {
|
|
|
49
61
|
process.exit(0);
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
const toolInput
|
|
53
|
-
|
|
54
|
-
const
|
|
64
|
+
const toolInput: Record<string, any> =
|
|
65
|
+
payload.tool_input || payload.arguments || (payload as Record<string, any>) || {};
|
|
66
|
+
const targetFile: string | undefined =
|
|
67
|
+
toolInput.TargetFile || toolInput.file_path || toolInput.filePath || toolInput.path;
|
|
68
|
+
const chunkContent = Array.isArray(toolInput.ReplacementChunks)
|
|
69
|
+
? toolInput.ReplacementChunks.map((c: any) => c?.ReplacementContent || "").join("\n")
|
|
70
|
+
: "";
|
|
71
|
+
const content: string =
|
|
72
|
+
toolInput.ReplacementContent ||
|
|
73
|
+
toolInput.CodeContent ||
|
|
74
|
+
toolInput.new_string ||
|
|
75
|
+
toolInput.content ||
|
|
76
|
+
chunkContent ||
|
|
77
|
+
"";
|
|
55
78
|
|
|
56
79
|
if (!targetFile) {
|
|
57
80
|
process.exit(0);
|
|
@@ -77,7 +100,12 @@ async function runPreWriteHook(): Promise<void> {
|
|
|
77
100
|
if (fs.existsSync(accessLogPath)) {
|
|
78
101
|
try {
|
|
79
102
|
const rawAccess = JSON.parse(fs.readFileSync(accessLogPath, "utf-8"));
|
|
80
|
-
const
|
|
103
|
+
const domainsMap: Record<string, string> = rawAccess.domains || {};
|
|
104
|
+
const lowerSource = sourceDomain.toLowerCase();
|
|
105
|
+
const domainAccessedAt =
|
|
106
|
+
domainsMap[sourceDomain] ||
|
|
107
|
+
domainsMap[lowerSource] ||
|
|
108
|
+
Object.entries(domainsMap).find(([k]) => k.toLowerCase() === lowerSource)?.[1];
|
|
81
109
|
if (domainAccessedAt) {
|
|
82
110
|
hasConsulted = true;
|
|
83
111
|
}
|
|
@@ -90,8 +118,8 @@ async function runPreWriteHook(): Promise<void> {
|
|
|
90
118
|
console.error(
|
|
91
119
|
`\n[SEPTUM GATE 3 VIOLATION - Mandatory Consultation Required]\n` +
|
|
92
120
|
`✕ Operation blocked: You are attempting to write to domain '${sourceDomain}' (${targetFile}) ` +
|
|
93
|
-
`without inspecting its authoritative catalog first.\n` +
|
|
94
|
-
`▶ REQUIRED ACTION: Call tool 'septum_get_domain_catalog(domain: "${sourceDomain}")' ` +
|
|
121
|
+
`without inspecting its authoritative symbol & file-connection catalog first.\n` +
|
|
122
|
+
`▶ REQUIRED ACTION: Call tool 'septum_get_domain_catalog({ domain: "${sourceDomain}", detail_level: "compact" })' ` +
|
|
95
123
|
`or 'septum_get_feature_context' before writing code.\n`
|
|
96
124
|
);
|
|
97
125
|
db.close();
|
|
@@ -102,12 +130,14 @@ async function runPreWriteHook(): Promise<void> {
|
|
|
102
130
|
// -------------------------------------------------------------
|
|
103
131
|
// GATE 1: Hard Touchpoint Lock (if features are defined)
|
|
104
132
|
// -------------------------------------------------------------
|
|
105
|
-
const features = config.features ?? {};
|
|
106
133
|
const session = SessionManager.getActiveSession(process.cwd());
|
|
107
|
-
const
|
|
108
|
-
|
|
134
|
+
const requestedFeatureKey = toolInput.feature_key || process.env.SEPTUM_ACTIVE_FEATURE;
|
|
135
|
+
if (session && requestedFeatureKey && requestedFeatureKey !== session.feature_key) {
|
|
136
|
+
throw new Error(`Requested feature '${requestedFeatureKey}' does not own the active scope '${session.feature_key}'.`);
|
|
137
|
+
}
|
|
138
|
+
const activeFeatureKey = session?.feature_key || requestedFeatureKey;
|
|
109
139
|
|
|
110
|
-
if (activeFeatureKey
|
|
140
|
+
if (activeFeatureKey) {
|
|
111
141
|
const touchpointViolations = evaluator.checkFeatureTouchpoints(
|
|
112
142
|
activeFeatureKey,
|
|
113
143
|
targetFile,
|
|
@@ -133,6 +163,9 @@ async function runPreWriteHook(): Promise<void> {
|
|
|
133
163
|
if (content && sourceDomain) {
|
|
134
164
|
const astEngine = new ASTParserEngine();
|
|
135
165
|
const parsed = await astEngine.parseFile(targetFile, content);
|
|
166
|
+
if (ASTParserEngine.isSourceFile(targetFile) && (parsed.status === "unsupported" || parsed.status === "degraded")) {
|
|
167
|
+
throw new Error(`Cannot reliably check imports in '${targetFile}': parser status is ${parsed.status}.`);
|
|
168
|
+
}
|
|
136
169
|
|
|
137
170
|
const proposedImports = parsed.dependencies.map((d) => d.target);
|
|
138
171
|
const boundaryViolations = evaluator.checkProposedChanges(
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -9,20 +9,22 @@ import {
|
|
|
9
9
|
import { SEPTUM_RULES_TEMPLATE } from "../templates/rules.ts";
|
|
10
10
|
import { SEPTUM_MAP_SKILL_TEMPLATE } from "../templates/skill.ts";
|
|
11
11
|
import { TopologyDetector } from "../../core/discovery/topology-detector.ts";
|
|
12
|
+
import { EnvironmentDetector } from "../../core/topology/environment-detector.ts";
|
|
12
13
|
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
13
14
|
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
14
15
|
|
|
15
16
|
export async function handleInitCommand(force: boolean = false): Promise<void> {
|
|
16
|
-
console.log("[Septum] Initializing Septum bounded-context environment (Zero-Config)...\n");
|
|
17
|
+
console.log("[Septum] Initializing Septum bounded-context & AST environment (Zero-Config SQLite SSOT)...\n");
|
|
17
18
|
|
|
18
19
|
const cwd = process.cwd();
|
|
19
20
|
const dbPath = ".septum/septum.db";
|
|
20
21
|
|
|
21
22
|
// 1. Auto-discover project topology and persist to SQLite SSOT
|
|
22
|
-
console.log(" • Analyzing codebase topology and
|
|
23
|
+
console.log(" • Analyzing codebase topology, modular boundaries, and 6-layer environment plane...");
|
|
23
24
|
const db = new SeptumDatabase(dbPath);
|
|
24
25
|
const repo = new SeptumRepository(db.raw);
|
|
25
26
|
const domains = TopologyDetector.discoverAndPersist(repo, cwd);
|
|
27
|
+
EnvironmentDetector.detect(cwd, repo);
|
|
26
28
|
const domainNames = Object.keys(domains);
|
|
27
29
|
console.log(` ✓ Discovered and registered ${domainNames.length} domains directly in SQLite (${dbPath}):`);
|
|
28
30
|
for (const name of domainNames) {
|
|
@@ -40,12 +42,12 @@ export async function handleInitCommand(force: boolean = false): Promise<void> {
|
|
|
40
42
|
scaffoldAgentCustomizations(cwd);
|
|
41
43
|
|
|
42
44
|
// 5. Run initial codebase ingestion
|
|
43
|
-
console.log("\n[Septum] Running initial ingestion to build
|
|
45
|
+
console.log("\n[Septum] Running initial Tree-Sitter WASM ingestion to build symbol & file-connection catalog...");
|
|
44
46
|
try {
|
|
45
47
|
await handleIngestCommand();
|
|
46
48
|
console.log("\n✓ Septum successfully initialized and ready for production use!");
|
|
47
49
|
console.log(" • Pre-commit hook is active (runs 'septum check --staged' before each commit).");
|
|
48
|
-
console.log(" • AI Agent skill '/septum-map' and boundary hooks installed in '.agents/'.");
|
|
50
|
+
console.log(" • AI Agent skill '/septum-map', anti-duplication rules, and boundary hooks installed in '.agents/'.");
|
|
49
51
|
console.log(" • MCP server is ready for AI coding agents via 'septum serve'.\n");
|
|
50
52
|
} catch (err: unknown) {
|
|
51
53
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
2
|
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
3
|
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
import type { DomainCatalogResponse } from "../../types/index.ts";
|
|
5
|
+
|
|
6
|
+
export interface QueryCommandOptions {
|
|
7
|
+
json?: boolean;
|
|
8
|
+
full?: boolean;
|
|
9
|
+
file?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function handleQueryCommand(
|
|
13
|
+
domainName?: string,
|
|
14
|
+
archetype?: string,
|
|
15
|
+
jsonOutputOrOptions?: boolean | QueryCommandOptions,
|
|
16
|
+
extraOptions?: QueryCommandOptions
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const options: QueryCommandOptions =
|
|
19
|
+
typeof jsonOutputOrOptions === "boolean"
|
|
20
|
+
? { json: jsonOutputOrOptions, ...extraOptions }
|
|
21
|
+
: { ...jsonOutputOrOptions, ...extraOptions };
|
|
22
|
+
|
|
23
|
+
const jsonOutput = Boolean(options.json);
|
|
24
|
+
const detailLevel: "compact" | "full" = options.full ? "full" : "compact";
|
|
25
|
+
const fileFilter = options.file;
|
|
4
26
|
|
|
5
|
-
export async function handleQueryCommand(domainName?: string, archetype?: string, jsonOutput?: boolean): Promise<void> {
|
|
6
27
|
const config = ConfigLoader.load();
|
|
7
28
|
const db = new SeptumDatabase(config.settings.db_path);
|
|
8
29
|
const repo = new SeptumRepository(db.raw);
|
|
@@ -23,7 +44,11 @@ export async function handleQueryCommand(domainName?: string, archetype?: string
|
|
|
23
44
|
return;
|
|
24
45
|
}
|
|
25
46
|
|
|
26
|
-
const catalog = repo.getDomainCatalog
|
|
47
|
+
const catalog: DomainCatalogResponse | null = (repo.getDomainCatalog as any)(
|
|
48
|
+
domainName,
|
|
49
|
+
archetype,
|
|
50
|
+
{ detailLevel, fileFilter }
|
|
51
|
+
);
|
|
27
52
|
if (!catalog) {
|
|
28
53
|
console.error(`[Septum Error] Domain '${domainName}' not found in database. Run 'septum ingest' first.`);
|
|
29
54
|
db.close();
|
|
@@ -36,15 +61,26 @@ export async function handleQueryCommand(domainName?: string, archetype?: string
|
|
|
36
61
|
console.log(`=================================================`);
|
|
37
62
|
console.log(` Domain: ${catalog.domain.toUpperCase()} (Root: ${catalog.root})`);
|
|
38
63
|
console.log(`=================================================`);
|
|
64
|
+
console.log(` Detail Level: ${catalog.detail_level ?? detailLevel}`);
|
|
39
65
|
console.log(` Allowed Dependencies: [${catalog.allowed_dependencies.join(", ")}]`);
|
|
40
66
|
console.log(` Forbidden Dependencies: [${catalog.forbidden_dependencies.join(", ")}]`);
|
|
41
|
-
console.log(` Total Files Cataloged: ${catalog.files.length}`);
|
|
67
|
+
console.log(` Total Files Cataloged: ${catalog.total_files ?? catalog.files.length} (Showing: ${catalog.showing_files ?? catalog.files.length})`);
|
|
42
68
|
console.log(`-------------------------------------------------`);
|
|
43
69
|
|
|
44
70
|
for (const file of catalog.files) {
|
|
45
|
-
|
|
71
|
+
const lineInfo = file.line_count ? `, ${file.line_count} lines` : "";
|
|
72
|
+
console.log(`\n File: ${file.path} [archetype: ${file.archetype}${lineInfo}]`);
|
|
73
|
+
if (file.connections) {
|
|
74
|
+
const importsStr = file.connections.imports_from.length > 0 ? file.connections.imports_from.join(", ") : "none";
|
|
75
|
+
console.log(` ↔ Connections: imports [${importsStr}] | imported by ${file.connections.imported_by_count} file(s)`);
|
|
76
|
+
}
|
|
46
77
|
for (const sym of file.symbols) {
|
|
47
|
-
|
|
78
|
+
const spanStr = sym.line_span ? ` (L${sym.line_span})` : "";
|
|
79
|
+
const countsStr =
|
|
80
|
+
sym.methods_count !== undefined
|
|
81
|
+
? ` [${sym.methods_count} methods${sym.properties_count ? `, ${sym.properties_count} props` : ""}]`
|
|
82
|
+
: "";
|
|
83
|
+
console.log(` • ${sym.kind.toUpperCase()} ${sym.name}${spanStr}${countsStr}`);
|
|
48
84
|
if (sym.methods && sym.methods.length > 0) {
|
|
49
85
|
for (const m of sym.methods) {
|
|
50
86
|
console.log(` - ${m}`);
|
|
@@ -57,8 +93,12 @@ export async function handleQueryCommand(domainName?: string, archetype?: string
|
|
|
57
93
|
}
|
|
58
94
|
}
|
|
59
95
|
}
|
|
96
|
+
if (catalog.agent_hint) {
|
|
97
|
+
console.log(`\n 💡 ${catalog.agent_hint}`);
|
|
98
|
+
}
|
|
60
99
|
console.log(`\n=================================================`);
|
|
61
100
|
}
|
|
62
101
|
|
|
63
102
|
db.close();
|
|
64
103
|
}
|
|
104
|
+
|
|
@@ -5,15 +5,16 @@ import { VerticalSliceTracer } from "../../core/resolver/vertical-slice-tracer.t
|
|
|
5
5
|
|
|
6
6
|
export async function handleSliceCommand(
|
|
7
7
|
query: string,
|
|
8
|
-
options?: { json?: boolean }
|
|
8
|
+
options?: { json?: boolean; maxDepth?: number }
|
|
9
9
|
): Promise<void> {
|
|
10
10
|
const config = ConfigLoader.load();
|
|
11
11
|
const db = new SeptumDatabase(config.settings.db_path);
|
|
12
12
|
const repo = new SeptumRepository(db.raw);
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
|
+
const maxDepth = Math.min(Math.max(Math.trunc(Number(options?.maxDepth ?? 5)), 1), 8);
|
|
15
16
|
const tracer = new VerticalSliceTracer(repo);
|
|
16
|
-
const result = tracer.trace(query);
|
|
17
|
+
const result = (tracer.trace as any)(query, maxDepth);
|
|
17
18
|
|
|
18
19
|
if (options?.json) {
|
|
19
20
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -46,3 +47,4 @@ export async function handleSliceCommand(
|
|
|
46
47
|
db.close();
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
|
package/src/cli/index.ts
CHANGED
|
@@ -22,9 +22,12 @@ export async function runCLI(argv: string[]): Promise<void> {
|
|
|
22
22
|
version: { type: "boolean", short: "v" },
|
|
23
23
|
force: { type: "boolean", short: "f" },
|
|
24
24
|
json: { type: "boolean" },
|
|
25
|
+
full: { type: "boolean" },
|
|
26
|
+
file: { type: "string" },
|
|
25
27
|
strict: { type: "boolean" },
|
|
26
28
|
staged: { type: "boolean" },
|
|
27
29
|
domain: { type: "string", short: "d" },
|
|
30
|
+
"max-depth": { type: "string" },
|
|
28
31
|
archetype: { type: "string", short: "a" },
|
|
29
32
|
feature: { type: "string" },
|
|
30
33
|
},
|
|
@@ -73,7 +76,9 @@ export async function runCLI(argv: string[]): Promise<void> {
|
|
|
73
76
|
const domainName = positionals[1];
|
|
74
77
|
const archetype = typeof flags.archetype === "string" ? flags.archetype : undefined;
|
|
75
78
|
const jsonOutput = Boolean(flags.json);
|
|
76
|
-
|
|
79
|
+
const full = Boolean(flags.full);
|
|
80
|
+
const file = typeof flags.file === "string" ? flags.file : undefined;
|
|
81
|
+
await handleQueryCommand(domainName, archetype, { json: jsonOutput, full, file });
|
|
77
82
|
break;
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -92,11 +97,16 @@ export async function runCLI(argv: string[]): Promise<void> {
|
|
|
92
97
|
case "slice": {
|
|
93
98
|
const query = positionals[1];
|
|
94
99
|
if (!query) {
|
|
95
|
-
console.error("Usage: septum slice <route-or-intent> [--json]");
|
|
100
|
+
console.error("Usage: septum slice <route-or-intent> [-d|--max-depth <number>] [--json]");
|
|
96
101
|
process.exit(1);
|
|
97
102
|
}
|
|
98
103
|
const json = Boolean(flags.json);
|
|
99
|
-
|
|
104
|
+
const rawDepth = flags["max-depth"] ?? flags.domain;
|
|
105
|
+
const parsedDepth = rawDepth !== undefined ? Number(rawDepth) : 5;
|
|
106
|
+
const maxDepth = !Number.isNaN(parsedDepth)
|
|
107
|
+
? Math.min(Math.max(Math.trunc(parsedDepth), 1), 8)
|
|
108
|
+
: 5;
|
|
109
|
+
await handleSliceCommand(query, { json, maxDepth });
|
|
100
110
|
break;
|
|
101
111
|
}
|
|
102
112
|
|
|
@@ -122,7 +132,7 @@ export async function runCLI(argv: string[]): Promise<void> {
|
|
|
122
132
|
case "feature": {
|
|
123
133
|
const sub = positionals[1];
|
|
124
134
|
if (sub === "clear") {
|
|
125
|
-
const cleared = SessionManager.clearActiveSession(process.cwd());
|
|
135
|
+
const cleared = SessionManager.clearActiveSession(process.cwd(), undefined, Boolean(flags.force));
|
|
126
136
|
if (cleared) {
|
|
127
137
|
console.log("✓ Active feature session successfully cleared.");
|
|
128
138
|
} else {
|
|
@@ -183,16 +193,17 @@ COMMANDS:
|
|
|
183
193
|
|
|
184
194
|
Structural Discovery:
|
|
185
195
|
query [domain] Query structural catalog for a domain or whole project
|
|
186
|
-
Flags: -a, --archetype <name>, --json
|
|
196
|
+
Flags: -a, --archetype <name>, --full, --file <filter>, --json
|
|
187
197
|
locate <query> Locate symbol, class, interface, method, or error source
|
|
188
198
|
Flags: -d, --domain <name>, --json
|
|
189
199
|
slice <route|intent> Trace end-to-end vertical execution slice for route/intent
|
|
190
|
-
Flags: --json
|
|
200
|
+
Flags: -d, --max-depth <number>, --json
|
|
191
201
|
|
|
192
202
|
Boundary & Enforcement:
|
|
193
203
|
check Audit codebase for cross-domain boundary violations
|
|
194
204
|
Flags: --strict, --staged, --feature <name>
|
|
195
205
|
feature [status|clear] View or release active feature context session lock
|
|
206
|
+
Flags: --force (explicit recovery of another owner's session)
|
|
196
207
|
hook [subhook] Internal pre-tool guardrail hook (e.g. pre-write)
|
|
197
208
|
|
|
198
209
|
Environment & Topology:
|
|
@@ -2,7 +2,7 @@ export const SEPTUM_HOOKS_CONFIG = {
|
|
|
2
2
|
"septum-boundary-guard": {
|
|
3
3
|
"PreToolUse": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "write_to_file|replace_file_content",
|
|
5
|
+
"matcher": "write_to_file|replace_file_content|multi_replace_file_content|Write|Edit|MultiEdit",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
@@ -14,7 +14,7 @@ export const SEPTUM_HOOKS_CONFIG = {
|
|
|
14
14
|
],
|
|
15
15
|
"PostToolUse": [
|
|
16
16
|
{
|
|
17
|
-
"matcher": "write_to_file|replace_file_content",
|
|
17
|
+
"matcher": "write_to_file|replace_file_content|multi_replace_file_content|Write|Edit|MultiEdit",
|
|
18
18
|
"hooks": [
|
|
19
19
|
{
|
|
20
20
|
"type": "command",
|
|
@@ -61,13 +61,13 @@ fi
|
|
|
61
61
|
|
|
62
62
|
# Trigger background incremental sync
|
|
63
63
|
if command -v bun >/dev/null 2>&1 && [ -f "$ROOT_DIR/bin/septum.ts" ]; then
|
|
64
|
-
bun run "$ROOT_DIR/bin/septum.ts" sync
|
|
64
|
+
bun run "$ROOT_DIR/bin/septum.ts" sync >/dev/null 2>&1 &
|
|
65
65
|
elif command -v septum >/dev/null 2>&1; then
|
|
66
|
-
septum sync
|
|
66
|
+
septum sync >/dev/null 2>&1 &
|
|
67
67
|
elif [ -f "$ROOT_DIR/node_modules/.bin/septum" ]; then
|
|
68
|
-
"$ROOT_DIR/node_modules/.bin/septum" sync
|
|
68
|
+
"$ROOT_DIR/node_modules/.bin/septum" sync >/dev/null 2>&1 &
|
|
69
69
|
elif command -v bunx >/dev/null 2>&1; then
|
|
70
|
-
bunx septum sync
|
|
70
|
+
bunx septum sync >/dev/null 2>&1 &
|
|
71
71
|
fi
|
|
72
72
|
|
|
73
73
|
exit 0
|