septum 0.1.6 → 0.1.8

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 (72) hide show
  1. package/README.md +38 -37
  2. package/package.json +6 -5
  3. package/src/cli/commands/check.ts +34 -122
  4. package/src/cli/commands/hook.ts +68 -16
  5. package/src/cli/commands/init.ts +440 -121
  6. package/src/cli/commands/sync.ts +27 -17
  7. package/src/cli/commands/topology.ts +89 -47
  8. package/src/cli/index.ts +7 -68
  9. package/src/cli/scaffold/harness-config.ts +697 -0
  10. package/src/cli/templates/hooks.ts +6 -6
  11. package/src/cli/templates/rules.ts +8 -8
  12. package/src/cli/templates/skill.ts +11 -18
  13. package/src/cli/ui/prompts.ts +122 -0
  14. package/src/core/boundary/evaluator.ts +21 -25
  15. package/src/core/boundary/staged-auditor.ts +50 -0
  16. package/src/core/config/loader.ts +21 -24
  17. package/src/core/database/client.ts +8 -0
  18. package/src/core/database/repositories/dependency.repository.ts +80 -16
  19. package/src/core/database/repositories/domain.repository.ts +86 -33
  20. package/src/core/database/repositories/symbol.repository.ts +147 -65
  21. package/src/core/database/repository.ts +17 -5
  22. package/src/core/database/schema.ts +1 -1
  23. package/src/core/discovery/topology-detector.ts +71 -19
  24. package/src/core/ingestion/pipeline.ts +274 -126
  25. package/src/core/parser/extractors/frontend.ts +1 -0
  26. package/src/core/parser/extractors/go.ts +2 -2
  27. package/src/core/parser/extractors/laravel-semantic.ts +9 -8
  28. package/src/core/parser/extractors/mcp-cli-semantic.ts +56 -16
  29. package/src/core/parser/extractors/nestjs-semantic.ts +3 -2
  30. package/src/core/parser/extractors/php.ts +2 -2
  31. package/src/core/parser/extractors/python.ts +2 -2
  32. package/src/core/parser/extractors/typescript.ts +2 -2
  33. package/src/core/parser/schema/providers/laravel-migration-provider.ts +4 -3
  34. package/src/core/parser/schema/providers/prisma-schema-provider.ts +2 -1
  35. package/src/core/parser/tree-sitter.ts +6 -1
  36. package/src/core/parser/wasm-assets.ts +17 -0
  37. package/src/core/parser/wasm-loader.ts +3 -0
  38. package/src/core/resolver/call-graph-tracer.ts +4 -3
  39. package/src/core/resolver/module-resolver.ts +1 -10
  40. package/src/core/resolver/path-resolver.ts +57 -4
  41. package/src/core/resolver/vertical-slice-tracer.ts +1 -0
  42. package/src/core/resources/ignore.ts +35 -0
  43. package/src/core/resources/limits.ts +14 -0
  44. package/src/core/resources/source-files.ts +62 -0
  45. package/src/core/resources/work-queue.ts +74 -0
  46. package/src/core/session/session-manager.ts +67 -38
  47. package/src/core/telemetry/telemetry.ts +12 -1
  48. package/src/core/topology/environment-detector.ts +21 -11
  49. package/src/mcp/schemas.ts +381 -8
  50. package/src/mcp/server.ts +326 -529
  51. package/src/mcp/tools/boundary.ts +17 -0
  52. package/src/mcp/tools/check-boundary.ts +97 -43
  53. package/src/mcp/tools/domain.ts +40 -0
  54. package/src/mcp/tools/feature.ts +67 -0
  55. package/src/mcp/tools/get-domain-catalog.ts +15 -7
  56. package/src/mcp/tools/get-environment-topology.ts +2 -2
  57. package/src/mcp/tools/get-feature-context.ts +9 -12
  58. package/src/mcp/tools/get-symbol-impact.ts +5 -2
  59. package/src/mcp/tools/get-symbol.ts +2 -1
  60. package/src/mcp/tools/locate-symbol.ts +28 -5
  61. package/src/mcp/tools/register-domain.ts +25 -9
  62. package/src/mcp/tools/register-feature.ts +40 -21
  63. package/src/mcp/tools/slice.ts +15 -0
  64. package/src/mcp/tools/symbol.ts +82 -0
  65. package/src/mcp/workspace-manager.ts +159 -0
  66. package/src/mcp/workspace-watcher.ts +202 -0
  67. package/src/types/index.ts +21 -1
  68. package/src/types/wasm.d.ts +4 -0
  69. package/src/cli/commands/ingest.ts +0 -29
  70. package/src/cli/commands/locate.ts +0 -61
  71. package/src/cli/commands/query.ts +0 -104
  72. package/src/cli/commands/slice.ts +0 -50
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.8](https://img.shields.io/badge/Version-0.1.8-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
 
@@ -28,16 +28,26 @@ bun add -g septum
28
28
 
29
29
  ### 1. Initialize in Any Project
30
30
 
31
+ Run `septum init` in your project root:
32
+
31
33
  ```bash
32
34
  septum init
33
35
  # or zero-install via bunx:
34
36
  bunx septum init
35
37
  ```
36
- *Auto-detects project topology (Monorepos, Laravel, Next.js, Go, NestJS), registers domains, and ingests AST symbols into `.septum/septum.db`.*
37
38
 
38
- ### 2. Connect to AI Agent (MCP Server)
39
+ Septum automatically:
40
+ 1. **Detects Architecture & Topology:** Discovers bounded domains across Monorepos, Laravel, Next.js, Go, NestJS, and more.
41
+ 2. **Builds AST Symbol SSOT:** Ingests symbols, interfaces, and dependencies into `.septum/septum.db`.
42
+ 3. **Scaffolds AI Agent Harnesses:** Interactively configures MCP servers, guardrail rules, and pre-write boundary hooks for your favorite agent environments:
43
+ - **Claude Code** (`.mcp.json`, `.claude/settings.json`)
44
+ - **Antigravity** (`.gemini/`)
45
+ - **Cursor** (`.cursor/mcp.json`, `.cursor/hooks.json`)
46
+ - **Opencode** (`opencode.json`)
47
+
48
+ ### 2. Manual MCP Client Configuration (Optional)
39
49
 
40
- Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
50
+ If your MCP client is not configured via `septum init`, add Septum manually (`mcp.json` or `claude_desktop_config.json`):
41
51
 
42
52
  ```json
43
53
  {
@@ -57,15 +67,12 @@ Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
57
67
 
58
68
  | Command | Description |
59
69
  |---|---|
60
- | `septum init` | Detect topology, scaffold boundary guards, and build initial catalog |
61
- | `septum ingest` | Full AST re-indexing into `.septum/septum.db` |
62
- | `septum sync` | Fast incremental delta sync (< 15ms) for modified files |
63
- | `septum check --strict` | CI/CD gate: verify boundary integrity & fail on violations |
64
- | `septum query [domain]` | Inspect domain catalog, archetypes, and public signatures |
65
- | `septum locate <query>` | Locate symbol, class, interface, method, or error source |
66
- | `septum slice <route\|intent>` | Trace end-to-end vertical execution slice for route/intent |
67
- | `septum topology [action]` | Inspect 6-layer environment topology plane & distributed nodes |
68
- | `septum feature status\|clear` | Manage active feature lease and editing scope |
70
+ | `septum init [--force]` | Detect topology, scaffold boundary guards, and build initial catalog. `--force` regenerates Septum-owned hooks and harness artifacts while strictly preserving SQLite DB, manual domain policies, and user Git hooks |
71
+ | `septum sync [--json]` | AST synchronization and catalog update into `.septum/septum.db` (full or incremental) |
72
+ | `septum check [--strict] [--staged] [--feature <name>] [--json]` | CI/CD gate & pre-commit hook: verify boundary integrity & fail on cross-domain violations |
73
+ | `septum feature [status|clear] [--force]` | View or release active feature lease and editing scope (`--force` for other owner's session) |
74
+ | `septum hook [subhook]` | Internal pre-tool guardrail hook (e.g. pre-write, post-write) for AI agent tools |
75
+ | `septum topology [subcommand]` | Inspect 6-layer environment topology plane & distributed nodes (`env` alias available). Subcommands: `resolve <layer> <platform>`, `add-node`, `remove-node`, `clear` |
69
76
  | `septum serve` | Launch stdio MCP server for AI coding agents |
70
77
 
71
78
  ---
@@ -74,32 +81,26 @@ Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
74
81
 
75
82
  All tools expose typed schemas automatically via the Model Context Protocol:
76
83
 
77
- - **Self-Aware Environment Topology (`septum_get_environment_topology`):** Maps and verifies the 6-layer production architecture (Edge/WAF, Gateway/Web Server, Host, Runtime, Storage/Cache, Telemetry) and distributed entities with cross-layer invariants.
78
- - **Whole-Project Telescope (`septum_get_domain_catalog`):** Returns macro architecture maps in < 400 tokens.
79
- - **Omni-Trigger Vertical Slice Tracer (`septum_trace_vertical_slice`):** Traces execution chains across HTTP routes, queue jobs, console commands, and event listeners (*Ingress ➔ Validation ➔ Orchestration ➔ Domain ➔ Egress*).
80
- - **Pre-Flight Boundary Guard (`septum_check_boundary`):** Validates proposed edits and imports against bounded-context rules.
81
- - **Scope Locking (`septum_get_feature_context` / `septum_clear_feature_context`):** Locks active feature scope to eliminate context drift.
82
- - **Symbol & Blast Radius (`septum_locate_symbol`, `septum_get_symbol`, `septum_get_symbol_impact`):** Resolves call chains, signatures, and refactoring impact.
83
- - **God Function & Nesting Detector (`septum_get_symbol_hotspots`):** Surfaces oversized functions/methods ranked by physical lines, cyclomatic nesting depth, and composite risk score.
84
- - **Dynamic Topology (`septum_register_domain`):** Registers or updates domains and boundary rules on-the-fly.
85
-
86
- ---
87
-
88
- ## Development & Contributing
89
-
90
- ```bash
91
- # Clone and install dependencies
92
- git clone https://github.com/shironim/septum.git
93
- cd septum
94
- bun install
95
-
96
- # Run test suite & build
97
- bun test
98
- bun run build
99
- ```
84
+ - **Domain Catalog, Registration & Environment Topology (`septum_domain`):**
85
+ - `action: "catalog"`: Whole-project macro architecture map and file connections in < 400 tokens (`detail_level: "compact" | "full"`).
86
+ - `action: "register"`: Dynamically registers or updates bounded-context domains with allowed/forbidden dependencies and layer archetypes.
87
+ - `action: "topology"`: Maps and verifies the 6-layer production architecture (Edge/WAF, Gateway/Web Server, Host, Runtime, Storage/Cache, Telemetry) and distributed nodes with cross-layer invariants.
88
+ - **Unified Symbol Engine (`septum_symbol`):**
89
+ - `action: "locate"`: Fast fuzzy search and error log resolution to pinpoint symbols across workspaces.
90
+ - `action: "inspect"`: Deep inspection of physical coordinates, declarations, and full typed signatures.
91
+ - `action: "impact"`: Refactoring blast radius, call graphs, and inbound/outbound dependency analysis.
92
+ - `action: "hotspots"`: God function and Single Responsibility Principle (SRP) detector ranked by physical lines, cyclomatic nesting depth, and composite risk score.
93
+ - **Omni-Trigger Vertical Slice Tracer (`septum_slice`):**
94
+ - Traces end-to-end execution flow (*Ingress ➔ Validation ➔ Controller ➔ Service ➔ Repository ➔ Entity*) from route URIs, controller actions, or high-level intent across HTTP routes, queue jobs, console commands, and events up to 8 levels deep.
95
+ - **Architecture Guard & Circular Dependency Gate (`septum_boundary`):**
96
+ - Validates proposed edits, additions, and imports against domain rules and feature touchpoints before files are modified on disk (`scope: "file"`).
97
+ - Audits staged files (`scope: "staged"`), domain boundaries (`scope: "domain"`), or the entire repository (`scope: "all"`) for cross-domain leaks and dependency cycles.
98
+ - **Active Touchpoint & Scope Lock Engine (`septum_feature`):**
99
+ - Locks active feature task context, touchpoints, and contracts (`action: "lock"` / `action: "get"` / `action: "release"`) via `.septum/session.json` to eliminate AI context drift and unintended edits.
100
100
 
101
101
  ---
102
102
 
103
103
  ## License
104
104
 
105
105
  [MIT](LICENSE) © shironim
106
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "septum",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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,16 @@
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",
23
+ "postinstall": "bun run scripts/postinstall.ts || true"
22
24
  },
23
25
  "dependencies": {
24
26
  "@modelcontextprotocol/sdk": "^1.5.0",
25
27
  "web-tree-sitter": "^0.24.0",
26
28
  "yaml": "^2.7.0",
27
- "zod": "^3.24.2"
29
+ "zod": "^3.24.2",
30
+ "tree-sitter-wasms": "^0.1.13"
28
31
  },
29
32
  "license": "MIT",
30
33
  "repository": {
@@ -56,8 +59,6 @@
56
59
  "devDependencies": {
57
60
  "@types/bun": "latest",
58
61
  "@types/node": "^22.13.4",
59
- "tree-sitter-wasms": "^0.1.13",
60
62
  "typescript": "^5.7.3"
61
63
  }
62
64
  }
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,49 @@ 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
+ const skipped = JSON.parse(repo.getMeta("skipped_files") || "[]") as {path: string}[];
45
+ if (skipped.length) throw new Error(`Cannot complete boundary audit: files exceed resource limits: ${skipped.map(file => file.path).join(", ")}`);
46
+ violations = evaluator.auditCodebase(config);
107
47
  }
108
48
 
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);
49
+ if (violations.length === 0) {
50
+ console.log("✓ All domain boundaries are clean. Zero cross-domain leaks detected.");
51
+ return;
52
+ }
121
53
 
122
- for (const dep of parsed.dependencies) {
123
- if (dep.is_external) continue;
54
+ console.error(`\n[Septum Boundary Violation] Found ${violations.length} boundary violation(s):\n`);
124
55
 
125
- const targetDomain = evaluator.resolveTargetDomain(
126
- dep.target,
127
- config,
128
- relPath
129
- );
56
+ for (const v of violations) {
57
+ console.error(` ✕ [${v.rule.toUpperCase()}] at ${v.file}:${v.line}`);
58
+ console.error(` Source Domain: ${v.source_domain}`);
59
+ console.error(` Target Domain: ${v.target_domain}`);
60
+ console.error(` Import Target: ${v.imported_target}`);
61
+ console.error(` Message: ${v.message}\n`);
62
+ }
130
63
 
131
- if (!targetDomain || targetDomain === sourceDomain) continue;
132
64
 
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
65
+ const isStrict = options.strict || config.settings.enforcement === "strict";
66
+ if (isStrict) {
67
+ console.error("[Septum Failure] Process terminated with exit code 1 due to boundary violations.");
68
+ process.exitCode = 1;
157
69
  }
70
+ } finally {
71
+ db.close();
158
72
  }
159
-
160
- return violations;
161
73
  }
@@ -7,8 +7,14 @@ import { SeptumRepository } from "../../core/database/repository.ts";
7
7
  import { ASTParserEngine } from "../../core/parser/tree-sitter.ts";
8
8
  import { SessionManager } from "../../core/session/session-manager.ts";
9
9
 
10
+ interface ToolCallInfo {
11
+ name?: string;
12
+ args?: Record<string, unknown>;
13
+ }
14
+
10
15
  export interface ToolPayload {
11
16
  tool_name?: string;
17
+ toolCall?: ToolCallInfo;
12
18
  tool_input?: {
13
19
  TargetFile?: string;
14
20
  file_path?: string;
@@ -30,8 +36,13 @@ export interface ToolPayload {
30
36
  CodeContent?: string;
31
37
  }
32
38
 
39
+ let configuredExitCode = 1;
40
+
33
41
  export async function handleHookCommand(args: string[]): Promise<void> {
34
42
  const subHook = args[1] || "pre-write";
43
+ if (args.includes("--exit-code-2") || args.includes("--claude") || args.includes("--cursor")) {
44
+ configuredExitCode = 2;
45
+ }
35
46
 
36
47
  if (subHook === "pre-write") {
37
48
  await runPreWriteHook();
@@ -40,6 +51,34 @@ export async function handleHookCommand(args: string[]): Promise<void> {
40
51
  }
41
52
  }
42
53
 
54
+ function blockHook(db: SeptumDatabase | null, reason: string): never {
55
+ if (db) {
56
+ try {
57
+ db.close();
58
+ } catch {
59
+ // ignore
60
+ }
61
+ }
62
+
63
+ // Print readable error to stderr for humans and Claude/Cursor/Codex
64
+ console.error(reason);
65
+
66
+ // Print JSON contract to stdout for Antigravity hook engine
67
+ try {
68
+ process.stdout.write(
69
+ JSON.stringify({
70
+ decision: "deny",
71
+ reason: reason.trim(),
72
+ }) + "\n"
73
+ );
74
+ } catch {
75
+ // ignore
76
+ }
77
+
78
+ // Exit code: 1 by default (Unix standard), or 2 if specified for Claude Code / Cursor hooks
79
+ process.exit(configuredExitCode);
80
+ }
81
+
43
82
  async function runPreWriteHook(): Promise<void> {
44
83
  // If .septum/septum.db does not exist, pass through
45
84
  const dbPath = path.resolve(process.cwd(), ".septum", "septum.db");
@@ -62,7 +101,11 @@ async function runPreWriteHook(): Promise<void> {
62
101
  }
63
102
 
64
103
  const toolInput: Record<string, any> =
65
- payload.tool_input || payload.arguments || (payload as Record<string, any>) || {};
104
+ payload.tool_input ||
105
+ payload.arguments ||
106
+ payload.toolCall?.args ||
107
+ (payload as Record<string, any>) ||
108
+ {};
66
109
  const targetFile: string | undefined =
67
110
  toolInput.TargetFile || toolInput.file_path || toolInput.filePath || toolInput.path;
68
111
  const chunkContent = Array.isArray(toolInput.ReplacementChunks)
@@ -115,27 +158,28 @@ async function runPreWriteHook(): Promise<void> {
115
158
  }
116
159
 
117
160
  if (!hasConsulted) {
118
- console.error(
161
+ blockHook(
162
+ db,
119
163
  `\n[SEPTUM GATE 3 VIOLATION - Mandatory Consultation Required]\n` +
120
164
  `✕ Operation blocked: You are attempting to write to domain '${sourceDomain}' (${targetFile}) ` +
121
165
  `without inspecting its authoritative symbol & file-connection catalog first.\n` +
122
- `▶ REQUIRED ACTION: Call tool 'septum_get_domain_catalog({ domain: "${sourceDomain}", detail_level: "compact" })' ` +
123
- `or 'septum_get_feature_context' before writing code.\n`
166
+ `▶ REQUIRED ACTION: Call tool 'septum_domain({ action: "catalog", domain: "${sourceDomain}", detail_level: "compact" })' ` +
167
+ `or 'septum_feature({ action: "get" })' before writing code.\n`
124
168
  );
125
- db.close();
126
- process.exit(1);
127
169
  }
128
170
  }
129
171
 
130
172
  // -------------------------------------------------------------
131
173
  // GATE 1: Hard Touchpoint Lock (if features are defined)
132
174
  // -------------------------------------------------------------
133
- const features = config.features ?? {};
134
175
  const session = SessionManager.getActiveSession(process.cwd());
135
- const activeFeatureKey =
136
- toolInput.feature_key || process.env.SEPTUM_ACTIVE_FEATURE || session?.feature_key;
176
+ const requestedFeatureKey = toolInput.feature_key || process.env.SEPTUM_ACTIVE_FEATURE;
177
+ if (session && requestedFeatureKey && requestedFeatureKey !== session.feature_key) {
178
+ throw new Error(`Requested feature '${requestedFeatureKey}' does not own the active scope '${session.feature_key}'.`);
179
+ }
180
+ const activeFeatureKey = session?.feature_key || requestedFeatureKey;
137
181
 
138
- if (activeFeatureKey && features[activeFeatureKey]) {
182
+ if (activeFeatureKey) {
139
183
  const touchpointViolations = evaluator.checkFeatureTouchpoints(
140
184
  activeFeatureKey,
141
185
  targetFile,
@@ -143,14 +187,13 @@ async function runPreWriteHook(): Promise<void> {
143
187
  );
144
188
 
145
189
  if (touchpointViolations.length > 0) {
146
- console.error(
190
+ blockHook(
191
+ db,
147
192
  `\n[SEPTUM GATE 1 VIOLATION - Scope Touchpoint Lock]\n` +
148
193
  `✕ Operation blocked: File '${targetFile}' is outside the declared allowed_touchpoints for feature '${activeFeatureKey}'.\n` +
149
194
  `▶ REASON: ${touchpointViolations[0].message}\n` +
150
195
  `▶ REQUIRED ACTION: Only modify files declared in allowed_touchpoints for this feature.\n`
151
196
  );
152
- db.close();
153
- process.exit(1);
154
197
  }
155
198
  }
156
199
 
@@ -161,6 +204,9 @@ async function runPreWriteHook(): Promise<void> {
161
204
  if (content && sourceDomain) {
162
205
  const astEngine = new ASTParserEngine();
163
206
  const parsed = await astEngine.parseFile(targetFile, content);
207
+ if (ASTParserEngine.isSourceFile(targetFile) && (parsed.status === "unsupported" || parsed.status === "degraded")) {
208
+ throw new Error(`Cannot reliably check imports in '${targetFile}': parser status is ${parsed.status}.`);
209
+ }
164
210
 
165
211
  const proposedImports = parsed.dependencies.map((d) => d.target);
166
212
  const boundaryViolations = evaluator.checkProposedChanges(
@@ -171,7 +217,8 @@ async function runPreWriteHook(): Promise<void> {
171
217
 
172
218
  if (boundaryViolations.length > 0) {
173
219
  const v = boundaryViolations[0];
174
- console.error(
220
+ blockHook(
221
+ db,
175
222
  `\n[SEPTUM GATE 2 VIOLATION - Forbidden Cross-Domain Import]\n` +
176
223
  `✕ Operation blocked: Proposed code imports forbidden domain '${v.target_domain}' from '${v.source_domain}'.\n` +
177
224
  `▶ OFFENDING IMPORT: ${v.imported_target}\n` +
@@ -179,12 +226,17 @@ async function runPreWriteHook(): Promise<void> {
179
226
  `▶ MESSAGE: ${v.message}\n` +
180
227
  `▶ REQUIRED ACTION: Decouple this dependency. Use an authorized Interface, Event, or Shared Kernel.\n`
181
228
  );
182
- db.close();
183
- process.exit(1);
184
229
  }
185
230
  }
186
231
 
187
232
  db.close();
233
+
234
+ // If running in Antigravity or standard JSON IPC mode, emit allow decision
235
+ try {
236
+ process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
237
+ } catch {
238
+ // ignore
239
+ }
188
240
  process.exit(0);
189
241
  } catch (err) {
190
242
  db.close();