septum 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/bin/septum.ts +4 -0
- package/package.json +62 -0
- package/src/cli/commands/check.ts +161 -0
- package/src/cli/commands/hook.ts +188 -0
- package/src/cli/commands/ingest.ts +29 -0
- package/src/cli/commands/init.ts +211 -0
- package/src/cli/commands/locate.ts +61 -0
- package/src/cli/commands/query.ts +64 -0
- package/src/cli/commands/serve.ts +5 -0
- package/src/cli/commands/slice.ts +90 -0
- package/src/cli/commands/sync.ts +31 -0
- package/src/cli/index.ts +178 -0
- package/src/cli/templates/hooks.ts +75 -0
- package/src/cli/templates/rules.ts +38 -0
- package/src/cli/templates/skill.ts +66 -0
- package/src/core/boundary/evaluator.ts +210 -0
- package/src/core/config/loader.ts +84 -0
- package/src/core/config/schema.ts +39 -0
- package/src/core/database/client.ts +132 -0
- package/src/core/database/repositories/dependency.repository.ts +279 -0
- package/src/core/database/repositories/domain.repository.ts +198 -0
- package/src/core/database/repositories/file.repository.ts +99 -0
- package/src/core/database/repositories/meta.repository.ts +33 -0
- package/src/core/database/repositories/symbol.repository.ts +424 -0
- package/src/core/database/repository.ts +242 -0
- package/src/core/database/schema.sql +92 -0
- package/src/core/discovery/topology-detector.ts +418 -0
- package/src/core/ingestion/hasher.ts +5 -0
- package/src/core/ingestion/pipeline.ts +438 -0
- package/src/core/parser/boundary-tracker.ts +128 -0
- package/src/core/parser/extractors/base.ts +60 -0
- package/src/core/parser/extractors/frontend.ts +194 -0
- package/src/core/parser/extractors/go.ts +176 -0
- package/src/core/parser/extractors/laravel-semantic.ts +426 -0
- package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -0
- package/src/core/parser/extractors/nestjs-semantic.ts +119 -0
- package/src/core/parser/extractors/php.ts +149 -0
- package/src/core/parser/extractors/python.ts +226 -0
- package/src/core/parser/extractors/registry.ts +49 -0
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -0
- package/src/core/parser/extractors/typescript.ts +177 -0
- package/src/core/parser/tree-sitter.ts +39 -0
- package/src/core/resolver/call-graph-tracer.ts +668 -0
- package/src/core/resolver/module-resolver.ts +323 -0
- package/src/core/resolver/symbol-locator.ts +405 -0
- package/src/core/resolver/vertical-slice-tracer.ts +236 -0
- package/src/core/session/session-manager.ts +56 -0
- package/src/core/telemetry/telemetry.ts +86 -0
- package/src/index.ts +18 -0
- package/src/mcp/schemas.ts +68 -0
- package/src/mcp/server.ts +602 -0
- package/src/mcp/tools/check-boundary.ts +102 -0
- package/src/mcp/tools/get-domain-catalog.ts +91 -0
- package/src/mcp/tools/get-feature-context.ts +116 -0
- package/src/mcp/tools/get-symbol-hotspots.ts +49 -0
- package/src/mcp/tools/get-symbol-impact.ts +26 -0
- package/src/mcp/tools/get-symbol.ts +63 -0
- package/src/mcp/tools/locate-symbol.ts +74 -0
- package/src/mcp/tools/register-domain.ts +64 -0
- package/src/mcp/tools/trace-vertical-slice.ts +102 -0
- package/src/types/index.ts +296 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shironim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Septum
|
|
2
|
+
|
|
3
|
+
> **Deterministic Bounded-Context & Anti-Poisoning Architecture Guard for AI Coding Agents.**
|
|
4
|
+
|
|
5
|
+
[](https://bun.sh)
|
|
6
|
+
[](https://modelcontextprotocol.io/)
|
|
7
|
+
[-003B57?logo=sqlite)](https://sqlite.org/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
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
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Install Septum globally using [Bun](https://bun.sh):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add -g septum
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **Note:** You can also run commands on-the-fly without global installation using `bunx septum <command>`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Initialize in Any Project
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
septum init
|
|
32
|
+
# or zero-install via bunx:
|
|
33
|
+
bunx septum init
|
|
34
|
+
```
|
|
35
|
+
*Auto-detects project topology (Monorepos, Laravel, Next.js, Go, NestJS), registers domains, and ingests AST symbols into `.septum/septum.db`.*
|
|
36
|
+
|
|
37
|
+
### 2. Connect to AI Agent (MCP Server)
|
|
38
|
+
|
|
39
|
+
Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"septum": {
|
|
45
|
+
"command": "septum",
|
|
46
|
+
"args": ["serve"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
*(Or use `"command": "bunx", "args": ["septum", "serve"]` if running without global installation).*
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## CLI Reference
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `septum init` | Detect topology, scaffold boundary guards, and build initial catalog |
|
|
60
|
+
| `septum ingest` | Full AST re-indexing into `.septum/septum.db` |
|
|
61
|
+
| `septum sync` | Fast incremental delta sync (< 15ms) for modified files |
|
|
62
|
+
| `septum check --strict` | CI/CD gate: verify boundary integrity & fail on violations |
|
|
63
|
+
| `septum query [domain]` | Inspect domain catalog, archetypes, and public signatures |
|
|
64
|
+
| `septum feature status\|clear` | Manage active feature lease and editing scope |
|
|
65
|
+
| `septum serve` | Launch stdio MCP server for AI coding agents |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Core Capabilities & MCP Tools
|
|
70
|
+
|
|
71
|
+
All tools expose typed schemas automatically via the Model Context Protocol:
|
|
72
|
+
|
|
73
|
+
- **Whole-Project Telescope (`septum_get_domain_catalog`):** Returns macro architecture maps in < 400 tokens.
|
|
74
|
+
- **Vertical Slice Tracer (`septum_trace_vertical_slice`):** Traces execution chains (*Ingress ➔ Validation ➔ Orchestration ➔ Domain ➔ Egress*) across frameworks.
|
|
75
|
+
- **Pre-Flight Boundary Guard (`septum_check_boundary`):** Validates proposed edits and imports against bounded-context rules.
|
|
76
|
+
- **Scope Locking (`septum_get_feature_context` / `septum_clear_feature_context`):** Locks active feature scope to eliminate context drift.
|
|
77
|
+
- **Symbol & Blast Radius (`septum_locate_symbol`, `septum_get_symbol`, `septum_get_symbol_impact`):** Resolves call chains, signatures, and refactoring impact.
|
|
78
|
+
- **God Function Detector (`septum_get_symbol_hotspots`):** Surfaces oversized functions/methods by physical lines-of-code.
|
|
79
|
+
- **Dynamic Topology (`septum_register_domain`):** Registers or updates domains and boundary rules on-the-fly.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Development & Contributing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Clone and install dependencies
|
|
87
|
+
git clone https://github.com/shironim/septum.git
|
|
88
|
+
cd septum
|
|
89
|
+
bun install
|
|
90
|
+
|
|
91
|
+
# Run test suite & build
|
|
92
|
+
bun test
|
|
93
|
+
bun run build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
[MIT](LICENSE) © shironim
|
package/bin/septum.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "septum",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic Bounded-Context, Structural File Catalog & Anti-Poisoning Partition for AI Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"septum": "./bin/septum.ts"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"bun": ">=1.1.0"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "bun run bin/septum.ts",
|
|
15
|
+
"serve": "bun run bin/septum.ts serve",
|
|
16
|
+
"ingest": "bun run bin/septum.ts ingest",
|
|
17
|
+
"sync": "bun run bin/septum.ts sync",
|
|
18
|
+
"check": "bun run bin/septum.ts check",
|
|
19
|
+
"test": "bun test",
|
|
20
|
+
"build": "bun build ./bin/septum.ts --compile --outfile dist/septum",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.5.0",
|
|
25
|
+
"web-tree-sitter": "^0.24.0",
|
|
26
|
+
"yaml": "^2.7.0",
|
|
27
|
+
"zod": "^3.24.2"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/shironim/septum.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/shironim/septum#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/shironim/septum/issues"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"mcp",
|
|
40
|
+
"model-context-protocol",
|
|
41
|
+
"bounded-context",
|
|
42
|
+
"architecture",
|
|
43
|
+
"ai-agent",
|
|
44
|
+
"tree-sitter",
|
|
45
|
+
"clean-architecture",
|
|
46
|
+
"domain-driven-design",
|
|
47
|
+
"codebase-catalog",
|
|
48
|
+
"antigravity"
|
|
49
|
+
],
|
|
50
|
+
"files": [
|
|
51
|
+
"bin",
|
|
52
|
+
"src",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE"
|
|
55
|
+
],
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/bun": "latest",
|
|
58
|
+
"@types/node": "^22.13.4",
|
|
59
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
60
|
+
"typescript": "^5.7.3"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { BoundaryEvaluator } from "../../core/boundary/evaluator.ts";
|
|
5
|
+
import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
|
|
6
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
7
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
8
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
9
|
+
import { ASTParserEngine } from "../../core/parser/tree-sitter.ts";
|
|
10
|
+
import type { BoundaryViolation } from "../../types/index.ts";
|
|
11
|
+
|
|
12
|
+
export interface CheckCommandOptions {
|
|
13
|
+
strict?: boolean;
|
|
14
|
+
staged?: boolean;
|
|
15
|
+
feature?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleCheckCommand(
|
|
19
|
+
optionsOrStrict?: boolean | CheckCommandOptions
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const options: CheckCommandOptions =
|
|
22
|
+
typeof optionsOrStrict === "boolean" ? { strict: optionsOrStrict } : optionsOrStrict || {};
|
|
23
|
+
|
|
24
|
+
console.log(
|
|
25
|
+
options.staged
|
|
26
|
+
? "[Septum] Auditing git staged files against domain boundaries..."
|
|
27
|
+
: "[Septum] Running codebase boundary integrity check..."
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const config = ConfigLoader.load();
|
|
31
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
32
|
+
const repo = new SeptumRepository(db.raw);
|
|
33
|
+
const evaluator = new BoundaryEvaluator(repo);
|
|
34
|
+
|
|
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
|
+
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);
|
|
107
|
+
}
|
|
108
|
+
|
|
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);
|
|
121
|
+
|
|
122
|
+
for (const dep of parsed.dependencies) {
|
|
123
|
+
if (dep.is_external) continue;
|
|
124
|
+
|
|
125
|
+
const targetDomain = evaluator.resolveTargetDomain(
|
|
126
|
+
dep.target,
|
|
127
|
+
config,
|
|
128
|
+
relPath
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (!targetDomain || targetDomain === sourceDomain) continue;
|
|
132
|
+
|
|
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
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return violations;
|
|
161
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { BoundaryEvaluator } from "../../core/boundary/evaluator.ts";
|
|
4
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
5
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
6
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
7
|
+
import { ASTParserEngine } from "../../core/parser/tree-sitter.ts";
|
|
8
|
+
import { SessionManager } from "../../core/session/session-manager.ts";
|
|
9
|
+
|
|
10
|
+
export interface ToolPayload {
|
|
11
|
+
tool_name: string;
|
|
12
|
+
tool_input: {
|
|
13
|
+
TargetFile?: string;
|
|
14
|
+
file_path?: string;
|
|
15
|
+
ReplacementContent?: string;
|
|
16
|
+
CodeContent?: string;
|
|
17
|
+
feature_key?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function handleHookCommand(args: string[]): Promise<void> {
|
|
22
|
+
const subHook = args[1] || "pre-write";
|
|
23
|
+
|
|
24
|
+
if (subHook === "pre-write") {
|
|
25
|
+
await runPreWriteHook();
|
|
26
|
+
} else {
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runPreWriteHook(): Promise<void> {
|
|
32
|
+
// If .septum/septum.db does not exist, pass through
|
|
33
|
+
const dbPath = path.resolve(process.cwd(), ".septum", "septum.db");
|
|
34
|
+
if (!fs.existsSync(dbPath)) {
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Read JSON payload from stdin
|
|
39
|
+
const stdinData = await readStdin();
|
|
40
|
+
if (!stdinData.trim()) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let payload: ToolPayload;
|
|
45
|
+
try {
|
|
46
|
+
payload = JSON.parse(stdinData);
|
|
47
|
+
} catch {
|
|
48
|
+
// Non-JSON stdin, pass through
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const toolInput = payload.tool_input || {};
|
|
53
|
+
const targetFile = toolInput.TargetFile || toolInput.file_path;
|
|
54
|
+
const content = toolInput.ReplacementContent || toolInput.CodeContent || "";
|
|
55
|
+
|
|
56
|
+
if (!targetFile) {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const config = ConfigLoader.load();
|
|
61
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
62
|
+
const repo = new SeptumRepository(db.raw);
|
|
63
|
+
const evaluator = new BoundaryEvaluator(repo);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const sourceDomain = evaluator.resolveSourceDomain(targetFile, config);
|
|
67
|
+
|
|
68
|
+
// If file is not inside any domain, allow or check feature scope
|
|
69
|
+
if (sourceDomain) {
|
|
70
|
+
// -------------------------------------------------------------
|
|
71
|
+
// GATE 3: Mandatory Catalog Consultation
|
|
72
|
+
// Verify that the AI has consulted the domain catalog before writing
|
|
73
|
+
// -------------------------------------------------------------
|
|
74
|
+
const accessLogPath = path.resolve(process.cwd(), ".septum/catalog_access.json");
|
|
75
|
+
let hasConsulted = false;
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(accessLogPath)) {
|
|
78
|
+
try {
|
|
79
|
+
const rawAccess = JSON.parse(fs.readFileSync(accessLogPath, "utf-8"));
|
|
80
|
+
const domainAccessedAt = rawAccess.domains?.[sourceDomain];
|
|
81
|
+
if (domainAccessedAt) {
|
|
82
|
+
hasConsulted = true;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// ignore parse error
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!hasConsulted) {
|
|
90
|
+
console.error(
|
|
91
|
+
`\n[SEPTUM GATE 3 VIOLATION - Mandatory Consultation Required]\n` +
|
|
92
|
+
`✕ 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}")' ` +
|
|
95
|
+
`or 'septum_get_feature_context' before writing code.\n`
|
|
96
|
+
);
|
|
97
|
+
db.close();
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// -------------------------------------------------------------
|
|
103
|
+
// GATE 1: Hard Touchpoint Lock (if features are defined)
|
|
104
|
+
// -------------------------------------------------------------
|
|
105
|
+
const features = config.features ?? {};
|
|
106
|
+
const session = SessionManager.getActiveSession(process.cwd());
|
|
107
|
+
const activeFeatureKey =
|
|
108
|
+
toolInput.feature_key || process.env.SEPTUM_ACTIVE_FEATURE || session?.feature_key;
|
|
109
|
+
|
|
110
|
+
if (activeFeatureKey && features[activeFeatureKey]) {
|
|
111
|
+
const touchpointViolations = evaluator.checkFeatureTouchpoints(
|
|
112
|
+
activeFeatureKey,
|
|
113
|
+
targetFile,
|
|
114
|
+
config
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (touchpointViolations.length > 0) {
|
|
118
|
+
console.error(
|
|
119
|
+
`\n[SEPTUM GATE 1 VIOLATION - Scope Touchpoint Lock]\n` +
|
|
120
|
+
`✕ Operation blocked: File '${targetFile}' is outside the declared allowed_touchpoints for feature '${activeFeatureKey}'.\n` +
|
|
121
|
+
`▶ REASON: ${touchpointViolations[0].message}\n` +
|
|
122
|
+
`▶ REQUIRED ACTION: Only modify files declared in allowed_touchpoints for this feature.\n`
|
|
123
|
+
);
|
|
124
|
+
db.close();
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// -------------------------------------------------------------
|
|
130
|
+
// GATE 2: Real-time AST Import Interception
|
|
131
|
+
// Extract imports from proposed content and check for boundary leaks
|
|
132
|
+
// -------------------------------------------------------------
|
|
133
|
+
if (content && sourceDomain) {
|
|
134
|
+
const astEngine = new ASTParserEngine();
|
|
135
|
+
const parsed = await astEngine.parseFile(targetFile, content);
|
|
136
|
+
|
|
137
|
+
const proposedImports = parsed.dependencies.map((d) => d.target);
|
|
138
|
+
const boundaryViolations = evaluator.checkProposedChanges(
|
|
139
|
+
targetFile,
|
|
140
|
+
proposedImports,
|
|
141
|
+
config
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (boundaryViolations.length > 0) {
|
|
145
|
+
const v = boundaryViolations[0];
|
|
146
|
+
console.error(
|
|
147
|
+
`\n[SEPTUM GATE 2 VIOLATION - Forbidden Cross-Domain Import]\n` +
|
|
148
|
+
`✕ Operation blocked: Proposed code imports forbidden domain '${v.target_domain}' from '${v.source_domain}'.\n` +
|
|
149
|
+
`▶ OFFENDING IMPORT: ${v.imported_target}\n` +
|
|
150
|
+
`▶ RULE: ${v.rule}\n` +
|
|
151
|
+
`▶ MESSAGE: ${v.message}\n` +
|
|
152
|
+
`▶ REQUIRED ACTION: Decouple this dependency. Use an authorized Interface, Event, or Shared Kernel.\n`
|
|
153
|
+
);
|
|
154
|
+
db.close();
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
db.close();
|
|
160
|
+
process.exit(0);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
db.close();
|
|
163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
164
|
+
console.error(`\x1b[31m[Septum Hook Error]\x1b[0m ${message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readStdin(): Promise<string> {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
let data = "";
|
|
172
|
+
if (process.stdin.isTTY) {
|
|
173
|
+
resolve("");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
process.stdin.setEncoding("utf-8");
|
|
178
|
+
process.stdin.on("data", (chunk) => {
|
|
179
|
+
data += chunk;
|
|
180
|
+
});
|
|
181
|
+
process.stdin.on("end", () => {
|
|
182
|
+
resolve(data);
|
|
183
|
+
});
|
|
184
|
+
process.stdin.on("error", () => {
|
|
185
|
+
resolve("");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
import { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
|
|
5
|
+
|
|
6
|
+
export async function handleIngestCommand(): Promise<void> {
|
|
7
|
+
console.log("[Septum] Starting codebase structural ingestion...");
|
|
8
|
+
|
|
9
|
+
const config = ConfigLoader.load();
|
|
10
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
11
|
+
const repo = new SeptumRepository(db.raw);
|
|
12
|
+
const pipeline = new IngestionPipeline(repo);
|
|
13
|
+
|
|
14
|
+
const metrics = await pipeline.run(config);
|
|
15
|
+
|
|
16
|
+
console.log("=========================================");
|
|
17
|
+
console.log(" Septum Ingestion Complete");
|
|
18
|
+
console.log("=========================================");
|
|
19
|
+
console.log(` Domains Processed: ${metrics.domains_processed}`);
|
|
20
|
+
console.log(` Files Scanned: ${metrics.files_scanned}`);
|
|
21
|
+
console.log(` Files Updated/Added: ${metrics.files_updated}`);
|
|
22
|
+
console.log(` Files Cached/Skipped: ${metrics.files_skipped}`);
|
|
23
|
+
console.log(` Symbols Indexed: ${metrics.symbols_indexed}`);
|
|
24
|
+
console.log(` Dependencies Indexed: ${metrics.dependencies_indexed}`);
|
|
25
|
+
console.log(` Execution Time: ${metrics.duration_ms} ms`);
|
|
26
|
+
console.log("=========================================");
|
|
27
|
+
|
|
28
|
+
db.close();
|
|
29
|
+
}
|