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.
Files changed (51) hide show
  1. package/README.md +23 -3
  2. package/package.json +5 -5
  3. package/src/cli/commands/check.ts +32 -122
  4. package/src/cli/commands/hook.ts +45 -12
  5. package/src/cli/commands/init.ts +6 -4
  6. package/src/cli/commands/query.ts +45 -5
  7. package/src/cli/commands/slice.ts +4 -2
  8. package/src/cli/index.ts +17 -6
  9. package/src/cli/templates/hooks.ts +6 -6
  10. package/src/cli/templates/rules.ts +35 -23
  11. package/src/cli/templates/skill.ts +47 -51
  12. package/src/core/boundary/evaluator.ts +217 -24
  13. package/src/core/boundary/staged-auditor.ts +47 -0
  14. package/src/core/config/loader.ts +29 -12
  15. package/src/core/database/repositories/dependency.repository.ts +252 -66
  16. package/src/core/database/repositories/domain.repository.ts +96 -39
  17. package/src/core/database/repositories/file.repository.ts +20 -19
  18. package/src/core/database/repositories/symbol.repository.ts +115 -5
  19. package/src/core/database/repository.ts +19 -2
  20. package/src/core/database/schema.ts +3 -2
  21. package/src/core/ingestion/pipeline.ts +96 -60
  22. package/src/core/parser/boundary-tracker.ts +145 -0
  23. package/src/core/parser/extractors/frontend.ts +44 -90
  24. package/src/core/parser/extractors/go.ts +200 -21
  25. package/src/core/parser/extractors/laravel-semantic.ts +18 -12
  26. package/src/core/parser/extractors/mcp-cli-semantic.ts +173 -44
  27. package/src/core/parser/extractors/nestjs-semantic.ts +20 -5
  28. package/src/core/parser/extractors/php.ts +293 -31
  29. package/src/core/parser/extractors/python.ts +242 -10
  30. package/src/core/parser/extractors/typescript.ts +604 -117
  31. package/src/core/parser/tree-sitter.ts +6 -1
  32. package/src/core/parser/wasm-assets.ts +17 -0
  33. package/src/core/parser/wasm-loader.ts +274 -0
  34. package/src/core/resolver/call-graph-tracer.ts +49 -17
  35. package/src/core/resolver/module-resolver.ts +88 -26
  36. package/src/core/resolver/vertical-slice-tracer.ts +34 -11
  37. package/src/core/session/session-manager.ts +55 -39
  38. package/src/core/topology/environment-detector.ts +109 -6
  39. package/src/mcp/schemas.ts +146 -29
  40. package/src/mcp/server.ts +177 -114
  41. package/src/mcp/tools/check-boundary.ts +159 -41
  42. package/src/mcp/tools/get-domain-catalog.ts +30 -15
  43. package/src/mcp/tools/get-environment-topology.ts +3 -6
  44. package/src/mcp/tools/get-feature-context.ts +35 -20
  45. package/src/mcp/tools/get-symbol-hotspots.ts +4 -10
  46. package/src/mcp/tools/locate-symbol.ts +37 -16
  47. package/src/mcp/tools/register-domain.ts +3 -12
  48. package/src/mcp/tools/register-feature.ts +25 -32
  49. package/src/mcp/tools/trace-vertical-slice.ts +8 -8
  50. package/src/types/index.ts +56 -6
  51. 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
- [![Version: 0.1.5](https://img.shields.io/badge/Version-0.1.5-blue)](package.json)
5
+ [![Version: 0.1.7](https://img.shields.io/badge/Version-0.1.7-blue)](package.json)
6
6
  [![Runtime: Bun](https://img.shields.io/badge/Runtime-Bun-black?logo=bun)](https://bun.sh)
7
7
  [![Protocol: Model Context Protocol](https://img.shields.io/badge/Protocol-MCP-green)](https://modelcontextprotocol.io/)
8
8
  [![Database: SQLite WAL](https://img.shields.io/badge/Database-SQLite%20(bun:sqlite)-003B57?logo=sqlite)](https://sqlite.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue)](LICENSE)
10
10
 
11
- Septum stops **context poisoning** in AI coding agents. 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.
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` | Fast incremental delta sync (< 15ms) for modified files |
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.5",
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
- const gitOutput = execSync("git diff --cached --name-only --diff-filter=ACM", {
78
- encoding: "utf-8",
79
- timeout: 5000,
80
- maxBuffer: 10 * 1024 * 1024,
81
- });
82
- stagedFiles = gitOutput
83
- .split("\n")
84
- .map((f) => f.trim())
85
- .filter(Boolean);
86
- } catch (err) {
87
- const msg = err instanceof Error ? err.message : String(err);
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
- const sourceDomain = evaluator.resolveSourceDomain(relPath, config);
110
- if (!sourceDomain) continue;
111
-
112
- const sourceConfig = config.domains[sourceDomain];
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
- for (const dep of parsed.dependencies) {
123
- if (dep.is_external) continue;
52
+ console.error(`\n[Septum Boundary Violation] Found ${violations.length} boundary violation(s):\n`);
124
53
 
125
- const targetDomain = evaluator.resolveTargetDomain(
126
- dep.target,
127
- config,
128
- relPath
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
- if (forbidden.includes(targetDomain)) {
134
- violations.push({
135
- file: relPath,
136
- line: dep.line_number,
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
  }
@@ -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: string;
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 = payload.tool_input || {};
53
- const targetFile = toolInput.TargetFile || toolInput.file_path;
54
- const content = toolInput.ReplacementContent || toolInput.CodeContent || "";
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 domainAccessedAt = rawAccess.domains?.[sourceDomain];
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 activeFeatureKey =
108
- toolInput.feature_key || process.env.SEPTUM_ACTIVE_FEATURE || session?.feature_key;
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 && features[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(
@@ -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 modular boundaries...");
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 deterministic structural catalog...");
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(domainName, archetype);
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
- console.log(`\n File: ${file.path} [archetype: ${file.archetype}]`);
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
- console.log(` • ${sym.kind.toUpperCase()} ${sym.name}`);
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
- await handleQueryCommand(domainName, archetype, jsonOutput);
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
- await handleSliceCommand(query, { json });
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 --json >/dev/null 2>&1 &
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 --json >/dev/null 2>&1 &
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 --json >/dev/null 2>&1 &
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 --json >/dev/null 2>&1 &
70
+ bunx septum sync >/dev/null 2>&1 &
71
71
  fi
72
72
 
73
73
  exit 0