septum 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +105 -105
- package/bin/septum.ts +4 -4
- package/package.json +63 -63
- package/src/cli/commands/check.ts +161 -161
- package/src/cli/commands/hook.ts +188 -188
- package/src/cli/commands/ingest.ts +29 -29
- package/src/cli/commands/init.ts +211 -211
- package/src/cli/commands/locate.ts +61 -61
- package/src/cli/commands/query.ts +64 -64
- package/src/cli/commands/serve.ts +5 -5
- package/src/cli/commands/slice.ts +48 -48
- package/src/cli/commands/sync.ts +31 -31
- package/src/cli/index.ts +210 -210
- package/src/cli/templates/hooks.ts +75 -75
- package/src/cli/templates/rules.ts +38 -38
- package/src/cli/templates/skill.ts +69 -69
- package/src/core/boundary/evaluator.ts +232 -210
- package/src/core/config/loader.ts +97 -97
- package/src/core/config/schema.ts +39 -39
- package/src/core/database/client.ts +44 -44
- package/src/core/database/repositories/dependency.repository.ts +366 -349
- package/src/core/database/repositories/domain.repository.ts +218 -198
- package/src/core/database/repositories/file.repository.ts +116 -106
- package/src/core/database/repositories/meta.repository.ts +33 -33
- package/src/core/database/repositories/symbol.repository.ts +443 -443
- package/src/core/database/repository.ts +264 -260
- package/src/core/database/schema.ts +153 -153
- package/src/core/discovery/topology-detector.ts +486 -486
- package/src/core/ingestion/hasher.ts +5 -5
- package/src/core/ingestion/pipeline.ts +584 -535
- package/src/core/parser/boundary-tracker.ts +288 -288
- package/src/core/parser/extractors/base.ts +62 -62
- package/src/core/parser/extractors/blade.ts +158 -158
- package/src/core/parser/extractors/frontend.ts +194 -194
- package/src/core/parser/extractors/go.ts +229 -180
- package/src/core/parser/extractors/laravel-semantic.ts +1039 -901
- package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -199
- package/src/core/parser/extractors/nestjs-semantic.ts +119 -119
- package/src/core/parser/extractors/php.ts +153 -153
- package/src/core/parser/extractors/python.ts +228 -228
- package/src/core/parser/extractors/registry.ts +70 -70
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
- package/src/core/parser/extractors/typescript.ts +181 -181
- package/src/core/parser/tree-sitter.ts +41 -41
- package/src/core/resolver/call-graph-tracer.ts +666 -668
- package/src/core/resolver/module-resolver.ts +408 -342
- package/src/core/resolver/path-resolver.ts +147 -147
- package/src/core/resolver/symbol-locator.ts +543 -543
- package/src/core/resolver/vertical-slice-tracer.ts +301 -285
- package/src/core/session/session-manager.ts +56 -56
- package/src/core/telemetry/telemetry.ts +86 -86
- package/src/index.ts +21 -21
- package/src/mcp/schemas.ts +578 -286
- package/src/mcp/server.ts +875 -716
- package/src/mcp/tools/check-boundary.ts +171 -152
- package/src/mcp/tools/get-domain-catalog.ts +145 -121
- package/src/mcp/tools/get-feature-context.ts +212 -116
- package/src/mcp/tools/get-symbol-hotspots.ts +60 -59
- package/src/mcp/tools/get-symbol-impact.ts +28 -26
- package/src/mcp/tools/get-symbol.ts +65 -63
- package/src/mcp/tools/locate-symbol.ts +171 -190
- package/src/mcp/tools/register-domain.ts +68 -64
- package/src/mcp/tools/register-feature.ts +100 -0
- package/src/mcp/tools/trace-vertical-slice.ts +74 -48
- package/src/types/index.ts +335 -315
- package/src/version.ts +3 -3
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
export const SEPTUM_MAP_SKILL_TEMPLATE = `---
|
|
2
|
-
name: septum-map
|
|
3
|
-
description: Framework-agnostic codebase discovery, architectural domain mapping, and bounded-context initialization for Septum. Uses AI reasoning to map unknown project topologies (JS/TS, PHP, Python, Go), auto-registers bounded contexts into Septum's SQLite catalog, and enforces deterministic architectural boundaries.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# \`septum-map\` — Framework-Agnostic Semantic Discovery & Bounded-Context Initialization
|
|
7
|
-
|
|
8
|
-
> **Prinsip Utama:** AI Agent berperan sebagai *The Architect* untuk membedah topologi arsitektur proyek apa pun tanpa terikat pada framework tertentu, merumuskan batas bounded-context, dan mendaftarkannya langsung ke database deterministik (.septum/septum.db via septum_register_domain) agar AI coding berikutnya tidak mengalami context poisoning atau over-editing.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. Landasan Riset & Filosofi Desain
|
|
13
|
-
|
|
14
|
-
Skill ini berakar langsung pada riset rekayasa perangkat lunak terkemuka:
|
|
15
|
-
1. **Software Reflexion Models (Murphy, Notkin, & Sullivan, IEEE TSE):**
|
|
16
|
-
Membedakan model arsitektur tingkat tinggi yang dirancang (*high-level intention*) dengan implementasi fisik di kode sumber. Septum mendeteksi *Divergences* (pelanggaran baru) dan *Absences* sebelum kode di-merge.
|
|
17
|
-
2. **Eliminasi Over-Editing (SWE-bench, RepoCoder - NeurIPS / ICLR):**
|
|
18
|
-
Akar kegagalan agen AI terbesar adalah mengedit file di luar lokus tugas (*local scope myopia*). \`septum-map\` menetapkan *bounded context* dan *allowed touchpoints* sejak awal.
|
|
19
|
-
3. **Principle of Least Privilege & Object-Capabilities (Miller):**
|
|
20
|
-
Agen hanya diberikan kapabilitas membaca dan mengubah file dalam domain/fitur aktif, memangkas *blast radius* kesalahan hingga mendekati nol.
|
|
21
|
-
4. **Hybrid Defense (SynCode / NeMo Guardrails):**
|
|
22
|
-
Probabilistic AI digunakan untuk penalaran awal (*discovery & blueprinting*), sementara eksekusi dan penegakannya 100% deterministik (*Tree-sitter WASM, SQLite WAL, Pre-Commit Hooks*).
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 2. Kapan Wajib Menggunakan (Trigger Moments)
|
|
27
|
-
|
|
28
|
-
> [!NOTE] Zero-Config Default (\`septum init\`)
|
|
29
|
-
> Untuk mayoritas proyek (library kecil/menengah, layered monolith standar, MVC, CLI, MCP tools), pengguna atau agen **cukup menjalankan \`septum init\`** di terminal. Perintah \`septum init\` secara default berjalan 100% otomatis tanpa wawancara manual (zero-config heuristic auto-detect & auto-ingest).
|
|
30
|
-
|
|
31
|
-
Gunakan alur 4 langkah skill ini HANYA saat:
|
|
32
|
-
1. Memetakan domain bisnis kustom / modular DDD enterprise yang kompleks di mana batasan dependency non-standar perlu disepakati bersama Tech Lead.
|
|
33
|
-
2. Memetakan ulang domain bisnis setelah terjadi refactoring arsitektur besar (*re-mapping*).
|
|
34
|
-
3. Mengonfigurasi sesi fitur baru (\`features\` session) untuk mengunci daftar file yang boleh dimodifikasi (*allowed_touchpoints*) dan simbol yang wajib dipakai ulang (*reuse_symbols*).
|
|
35
|
-
4. Pengguna secara eksplisit meminta konsultasi desain domain boundary interaktif.
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 3. Alur Kerja 4 Langkah (The 4-Step Discovery Lifecycle)
|
|
40
|
-
|
|
41
|
-
### Langkah 1: Topological Scan (Maksimal 2 Tool Call)
|
|
42
|
-
Analisis karakteristik proyek secara framework-agnostic:
|
|
43
|
-
1. **Periksa Manifest & Config Root:**
|
|
44
|
-
* JS/TS: \`package.json\`, \`tsconfig.json\` (baca dependencies, path aliases).
|
|
45
|
-
* PHP: \`composer.json\` (baca \`autoload.psr-4\`, framework dependencies).
|
|
46
|
-
* Python: \`pyproject.toml\`, \`requirements.txt\`, \`setup.py\` (baca modules & framework).
|
|
47
|
-
* Go: \`go.mod\` (baca module path).
|
|
48
|
-
2. **Petakan Direktori Tingkat Atas:**
|
|
49
|
-
Identifikasi apakah proyek menggunakan pola:
|
|
50
|
-
* *Domain-Driven / Vertical Slice:* \`src/domains/\`, \`app/Domain/\`, \`features/\`.
|
|
51
|
-
* *Layered / Clean Architecture:* \`core/\`, \`infrastructure/\`, \`application/\`, \`interfaces/\`.
|
|
52
|
-
* *Framework Conventional:* NestJS modules, Laravel app folders, FastAPI routers, Next.js route groups.
|
|
53
|
-
|
|
54
|
-
### Langkah 2: Semantic Blueprint Drafting & Domain Boundary Formulation
|
|
55
|
-
Rumuskan spesifikasi domain dengan prinsip **Contract & Invariants (Bukan Prosedural)**:
|
|
56
|
-
* **\`root\`:** Direktori fisik domain (gunakan path kanonikal).
|
|
57
|
-
* **\`allowed_dependencies\`:** Domain mana saja yang sah diimpor (inbound whitelist).
|
|
58
|
-
* **\`forbidden_dependencies\`:** Domain yang dilarang keras diimpor (outbound blacklist).
|
|
59
|
-
* **\`archetypes\`:** Pola glob penamaan layer (\`service: "*Service.*"\`, \`model: "*Model.*"\`).
|
|
60
|
-
|
|
61
|
-
### Langkah 3: Human-in-the-Loop Approval Gate
|
|
62
|
-
Tampilkan ringkasan domain boundary yang dirumuskan kepada pengguna / Tech Lead untuk disetujui atau disesuaikan sebelum didaftarkan.
|
|
63
|
-
|
|
64
|
-
### Langkah 4: Deterministic Registration & Ingestion
|
|
65
|
-
Setelah disetujui:
|
|
66
|
-
1. Daftarkan domain secara langsung via tool MCP \`septum_register_domain(domain_name, root, ...)\` dengan opsi \`ingest_now: true\`.
|
|
67
|
-
2. Atau jalankan CLI \`septum ingest\` untuk mengekstrak seluruh simbol AST dan menyimpannya ke database SQLite \`.septum/septum.db\`.
|
|
68
|
-
3. Pastikan hook pre-commit terpasang aktif di \`.git/hooks/pre-commit\`.
|
|
69
|
-
`;
|
|
1
|
+
export const SEPTUM_MAP_SKILL_TEMPLATE = `---
|
|
2
|
+
name: septum-map
|
|
3
|
+
description: Framework-agnostic codebase discovery, architectural domain mapping, and bounded-context initialization for Septum. Uses AI reasoning to map unknown project topologies (JS/TS, PHP, Python, Go), auto-registers bounded contexts into Septum's SQLite catalog, and enforces deterministic architectural boundaries.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# \`septum-map\` — Framework-Agnostic Semantic Discovery & Bounded-Context Initialization
|
|
7
|
+
|
|
8
|
+
> **Prinsip Utama:** AI Agent berperan sebagai *The Architect* untuk membedah topologi arsitektur proyek apa pun tanpa terikat pada framework tertentu, merumuskan batas bounded-context, dan mendaftarkannya langsung ke database deterministik (.septum/septum.db via septum_register_domain) agar AI coding berikutnya tidak mengalami context poisoning atau over-editing.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Landasan Riset & Filosofi Desain
|
|
13
|
+
|
|
14
|
+
Skill ini berakar langsung pada riset rekayasa perangkat lunak terkemuka:
|
|
15
|
+
1. **Software Reflexion Models (Murphy, Notkin, & Sullivan, IEEE TSE):**
|
|
16
|
+
Membedakan model arsitektur tingkat tinggi yang dirancang (*high-level intention*) dengan implementasi fisik di kode sumber. Septum mendeteksi *Divergences* (pelanggaran baru) dan *Absences* sebelum kode di-merge.
|
|
17
|
+
2. **Eliminasi Over-Editing (SWE-bench, RepoCoder - NeurIPS / ICLR):**
|
|
18
|
+
Akar kegagalan agen AI terbesar adalah mengedit file di luar lokus tugas (*local scope myopia*). \`septum-map\` menetapkan *bounded context* dan *allowed touchpoints* sejak awal.
|
|
19
|
+
3. **Principle of Least Privilege & Object-Capabilities (Miller):**
|
|
20
|
+
Agen hanya diberikan kapabilitas membaca dan mengubah file dalam domain/fitur aktif, memangkas *blast radius* kesalahan hingga mendekati nol.
|
|
21
|
+
4. **Hybrid Defense (SynCode / NeMo Guardrails):**
|
|
22
|
+
Probabilistic AI digunakan untuk penalaran awal (*discovery & blueprinting*), sementara eksekusi dan penegakannya 100% deterministik (*Tree-sitter WASM, SQLite WAL, Pre-Commit Hooks*).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. Kapan Wajib Menggunakan (Trigger Moments)
|
|
27
|
+
|
|
28
|
+
> [!NOTE] Zero-Config Default (\`septum init\`)
|
|
29
|
+
> Untuk mayoritas proyek (library kecil/menengah, layered monolith standar, MVC, CLI, MCP tools), pengguna atau agen **cukup menjalankan \`septum init\`** di terminal. Perintah \`septum init\` secara default berjalan 100% otomatis tanpa wawancara manual (zero-config heuristic auto-detect & auto-ingest).
|
|
30
|
+
|
|
31
|
+
Gunakan alur 4 langkah skill ini HANYA saat:
|
|
32
|
+
1. Memetakan domain bisnis kustom / modular DDD enterprise yang kompleks di mana batasan dependency non-standar perlu disepakati bersama Tech Lead.
|
|
33
|
+
2. Memetakan ulang domain bisnis setelah terjadi refactoring arsitektur besar (*re-mapping*).
|
|
34
|
+
3. Mengonfigurasi sesi fitur baru (\`features\` session) untuk mengunci daftar file yang boleh dimodifikasi (*allowed_touchpoints*) dan simbol yang wajib dipakai ulang (*reuse_symbols*).
|
|
35
|
+
4. Pengguna secara eksplisit meminta konsultasi desain domain boundary interaktif.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. Alur Kerja 4 Langkah (The 4-Step Discovery Lifecycle)
|
|
40
|
+
|
|
41
|
+
### Langkah 1: Topological Scan (Maksimal 2 Tool Call)
|
|
42
|
+
Analisis karakteristik proyek secara framework-agnostic:
|
|
43
|
+
1. **Periksa Manifest & Config Root:**
|
|
44
|
+
* JS/TS: \`package.json\`, \`tsconfig.json\` (baca dependencies, path aliases).
|
|
45
|
+
* PHP: \`composer.json\` (baca \`autoload.psr-4\`, framework dependencies).
|
|
46
|
+
* Python: \`pyproject.toml\`, \`requirements.txt\`, \`setup.py\` (baca modules & framework).
|
|
47
|
+
* Go: \`go.mod\` (baca module path).
|
|
48
|
+
2. **Petakan Direktori Tingkat Atas:**
|
|
49
|
+
Identifikasi apakah proyek menggunakan pola:
|
|
50
|
+
* *Domain-Driven / Vertical Slice:* \`src/domains/\`, \`app/Domain/\`, \`features/\`.
|
|
51
|
+
* *Layered / Clean Architecture:* \`core/\`, \`infrastructure/\`, \`application/\`, \`interfaces/\`.
|
|
52
|
+
* *Framework Conventional:* NestJS modules, Laravel app folders, FastAPI routers, Next.js route groups.
|
|
53
|
+
|
|
54
|
+
### Langkah 2: Semantic Blueprint Drafting & Domain Boundary Formulation
|
|
55
|
+
Rumuskan spesifikasi domain dengan prinsip **Contract & Invariants (Bukan Prosedural)**:
|
|
56
|
+
* **\`root\`:** Direktori fisik domain (gunakan path kanonikal).
|
|
57
|
+
* **\`allowed_dependencies\`:** Domain mana saja yang sah diimpor (inbound whitelist).
|
|
58
|
+
* **\`forbidden_dependencies\`:** Domain yang dilarang keras diimpor (outbound blacklist).
|
|
59
|
+
* **\`archetypes\`:** Pola glob penamaan layer (\`service: "*Service.*"\`, \`model: "*Model.*"\`).
|
|
60
|
+
|
|
61
|
+
### Langkah 3: Human-in-the-Loop Approval Gate
|
|
62
|
+
Tampilkan ringkasan domain boundary yang dirumuskan kepada pengguna / Tech Lead untuk disetujui atau disesuaikan sebelum didaftarkan.
|
|
63
|
+
|
|
64
|
+
### Langkah 4: Deterministic Registration & Ingestion
|
|
65
|
+
Setelah disetujui:
|
|
66
|
+
1. Daftarkan domain secara langsung via tool MCP \`septum_register_domain(domain_name, root, ...)\` dengan opsi \`ingest_now: true\`.
|
|
67
|
+
2. Atau jalankan CLI \`septum ingest\` untuk mengekstrak seluruh simbol AST dan menyimpannya ke database SQLite \`.septum/septum.db\`.
|
|
68
|
+
3. Pastikan hook pre-commit terpasang aktif di \`.git/hooks/pre-commit\`.
|
|
69
|
+
`;
|
|
@@ -1,210 +1,232 @@
|
|
|
1
|
-
import type { BoundaryViolation } from "../../types/index.ts";
|
|
2
|
-
import type { ValidatedSeptumConfig } from "../config/schema.ts";
|
|
3
|
-
import { ConfigLoader } from "../config/loader.ts";
|
|
4
|
-
import type { SeptumRepository } from "../database/repository.ts";
|
|
5
|
-
import { ModuleResolver } from "../resolver/module-resolver.ts";
|
|
6
|
-
|
|
7
|
-
export class BoundaryEvaluator {
|
|
8
|
-
private repo: SeptumRepository;
|
|
9
|
-
private resolver: ModuleResolver;
|
|
10
|
-
|
|
11
|
-
constructor(repo: SeptumRepository, resolver?: ModuleResolver) {
|
|
12
|
-
this.repo = repo;
|
|
13
|
-
this.resolver = resolver ?? new ModuleResolver(process.cwd(), repo);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public getResolver(): ModuleResolver {
|
|
17
|
-
return this.resolver;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public evaluate(
|
|
21
|
-
filePath: string,
|
|
22
|
-
proposedImports: string[] = [],
|
|
23
|
-
featureKey?: string,
|
|
24
|
-
config?: ValidatedSeptumConfig
|
|
25
|
-
): BoundaryViolation[] {
|
|
26
|
-
const activeConfig = config ?? ConfigLoader.loadFromDatabaseOrDefaults();
|
|
27
|
-
const violations: BoundaryViolation[] = [];
|
|
28
|
-
|
|
29
|
-
if (featureKey) {
|
|
30
|
-
violations.push(...this.checkFeatureTouchpoints(featureKey, filePath, activeConfig));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (proposedImports.length > 0) {
|
|
34
|
-
violations.push(...this.checkProposedChanges(filePath, proposedImports, activeConfig));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return violations;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public auditCodebase(config: ValidatedSeptumConfig): BoundaryViolation[] {
|
|
41
|
-
const violations: BoundaryViolation[] = [];
|
|
42
|
-
const allDependencies = this.repo.getAllDependenciesWithDomains();
|
|
43
|
-
|
|
44
|
-
for (const dep of allDependencies) {
|
|
45
|
-
const targetDomain = this.resolveTargetDomain(dep.target, config, dep.source_file);
|
|
46
|
-
if (!targetDomain || targetDomain === dep.source_domain) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const sourceDomainConfig = config.domains[dep.source_domain];
|
|
51
|
-
if (!sourceDomainConfig) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const forbidden = sourceDomainConfig.forbidden_dependencies ?? [];
|
|
56
|
-
const allowed = sourceDomainConfig.allowed_dependencies ?? [];
|
|
57
|
-
|
|
58
|
-
// 1. Explicit forbidden check
|
|
59
|
-
if (forbidden.includes(targetDomain)) {
|
|
60
|
-
violations.push({
|
|
61
|
-
file: dep.source_file,
|
|
62
|
-
line: dep.line_number,
|
|
63
|
-
source_domain: dep.source_domain,
|
|
64
|
-
target_domain: targetDomain,
|
|
65
|
-
imported_target: dep.target,
|
|
66
|
-
rule: "forbidden_dependency",
|
|
67
|
-
message: `Forbidden cross-domain dependency detected: '${dep.source_domain}' is strictly prohibited from importing domain '${targetDomain}'.`,
|
|
68
|
-
});
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 2. Strict enforcement allowed check
|
|
73
|
-
if (config.settings.enforcement === "strict" && !allowed.includes(targetDomain)) {
|
|
74
|
-
violations.push({
|
|
75
|
-
file: dep.source_file,
|
|
76
|
-
line: dep.line_number,
|
|
77
|
-
source_domain: dep.source_domain,
|
|
78
|
-
target_domain: targetDomain,
|
|
79
|
-
imported_target: dep.target,
|
|
80
|
-
rule: "disallowed_dependency",
|
|
81
|
-
message: `Disallowed dependency: '${targetDomain}' is not declared in allowed_dependencies for domain '${dep.source_domain}'.`,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return violations;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public checkProposedChanges(
|
|
90
|
-
sourceFilePath: string,
|
|
91
|
-
proposedImports: string[],
|
|
92
|
-
config: ValidatedSeptumConfig
|
|
93
|
-
): BoundaryViolation[] {
|
|
94
|
-
const violations: BoundaryViolation[] = [];
|
|
95
|
-
const sourceDomain = this.resolveSourceDomain(sourceFilePath, config);
|
|
96
|
-
|
|
97
|
-
if (!sourceDomain) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
config: ValidatedSeptumConfig
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1
|
+
import type { BoundaryViolation } from "../../types/index.ts";
|
|
2
|
+
import type { ValidatedSeptumConfig } from "../config/schema.ts";
|
|
3
|
+
import { ConfigLoader } from "../config/loader.ts";
|
|
4
|
+
import type { SeptumRepository } from "../database/repository.ts";
|
|
5
|
+
import { ModuleResolver } from "../resolver/module-resolver.ts";
|
|
6
|
+
|
|
7
|
+
export class BoundaryEvaluator {
|
|
8
|
+
private repo: SeptumRepository;
|
|
9
|
+
private resolver: ModuleResolver;
|
|
10
|
+
|
|
11
|
+
constructor(repo: SeptumRepository, resolver?: ModuleResolver) {
|
|
12
|
+
this.repo = repo;
|
|
13
|
+
this.resolver = resolver ?? new ModuleResolver(process.cwd(), repo);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getResolver(): ModuleResolver {
|
|
17
|
+
return this.resolver;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public evaluate(
|
|
21
|
+
filePath: string,
|
|
22
|
+
proposedImports: string[] = [],
|
|
23
|
+
featureKey?: string,
|
|
24
|
+
config?: ValidatedSeptumConfig
|
|
25
|
+
): BoundaryViolation[] {
|
|
26
|
+
const activeConfig = config ?? ConfigLoader.loadFromDatabaseOrDefaults();
|
|
27
|
+
const violations: BoundaryViolation[] = [];
|
|
28
|
+
|
|
29
|
+
if (featureKey) {
|
|
30
|
+
violations.push(...this.checkFeatureTouchpoints(featureKey, filePath, activeConfig));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (proposedImports.length > 0) {
|
|
34
|
+
violations.push(...this.checkProposedChanges(filePath, proposedImports, activeConfig));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return violations;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public auditCodebase(config: ValidatedSeptumConfig): BoundaryViolation[] {
|
|
41
|
+
const violations: BoundaryViolation[] = [];
|
|
42
|
+
const allDependencies = this.repo.getAllDependenciesWithDomains();
|
|
43
|
+
|
|
44
|
+
for (const dep of allDependencies) {
|
|
45
|
+
const targetDomain = this.resolveTargetDomain(dep.target, config, dep.source_file);
|
|
46
|
+
if (!targetDomain || targetDomain === dep.source_domain) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sourceDomainConfig = config.domains[dep.source_domain];
|
|
51
|
+
if (!sourceDomainConfig) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const forbidden = sourceDomainConfig.forbidden_dependencies ?? [];
|
|
56
|
+
const allowed = sourceDomainConfig.allowed_dependencies ?? [];
|
|
57
|
+
|
|
58
|
+
// 1. Explicit forbidden check
|
|
59
|
+
if (forbidden.includes(targetDomain)) {
|
|
60
|
+
violations.push({
|
|
61
|
+
file: dep.source_file,
|
|
62
|
+
line: dep.line_number,
|
|
63
|
+
source_domain: dep.source_domain,
|
|
64
|
+
target_domain: targetDomain,
|
|
65
|
+
imported_target: dep.target,
|
|
66
|
+
rule: "forbidden_dependency",
|
|
67
|
+
message: `Forbidden cross-domain dependency detected: '${dep.source_domain}' is strictly prohibited from importing domain '${targetDomain}'.`,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Strict enforcement allowed check
|
|
73
|
+
if (config.settings.enforcement === "strict" && !allowed.includes(targetDomain)) {
|
|
74
|
+
violations.push({
|
|
75
|
+
file: dep.source_file,
|
|
76
|
+
line: dep.line_number,
|
|
77
|
+
source_domain: dep.source_domain,
|
|
78
|
+
target_domain: targetDomain,
|
|
79
|
+
imported_target: dep.target,
|
|
80
|
+
rule: "disallowed_dependency",
|
|
81
|
+
message: `Disallowed dependency: '${targetDomain}' is not declared in allowed_dependencies for domain '${dep.source_domain}'.`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return violations;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public checkProposedChanges(
|
|
90
|
+
sourceFilePath: string,
|
|
91
|
+
proposedImports: string[],
|
|
92
|
+
config: ValidatedSeptumConfig
|
|
93
|
+
): BoundaryViolation[] {
|
|
94
|
+
const violations: BoundaryViolation[] = [];
|
|
95
|
+
const sourceDomain = this.resolveSourceDomain(sourceFilePath, config);
|
|
96
|
+
|
|
97
|
+
if (!sourceDomain) {
|
|
98
|
+
violations.push({
|
|
99
|
+
file: sourceFilePath,
|
|
100
|
+
line: 1,
|
|
101
|
+
source_domain: "unmapped",
|
|
102
|
+
target_domain: "unknown",
|
|
103
|
+
imported_target: "",
|
|
104
|
+
rule: "unmapped_domain",
|
|
105
|
+
message: `File '${sourceFilePath}' does not belong to any declared bounded context. Boundary integrity cannot be enforced.`,
|
|
106
|
+
});
|
|
107
|
+
return violations;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sourceDomainConfig = config.domains[sourceDomain];
|
|
111
|
+
if (!sourceDomainConfig) {
|
|
112
|
+
return violations;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const forbidden = sourceDomainConfig.forbidden_dependencies ?? [];
|
|
116
|
+
const allowed = sourceDomainConfig.allowed_dependencies ?? [];
|
|
117
|
+
|
|
118
|
+
for (const statement of proposedImports) {
|
|
119
|
+
const targetDomain = this.resolveTargetDomain(statement, config, sourceFilePath);
|
|
120
|
+
if (!targetDomain || targetDomain === sourceDomain) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (forbidden.includes(targetDomain)) {
|
|
125
|
+
violations.push({
|
|
126
|
+
file: sourceFilePath,
|
|
127
|
+
line: 1,
|
|
128
|
+
source_domain: sourceDomain,
|
|
129
|
+
target_domain: targetDomain,
|
|
130
|
+
imported_target: statement,
|
|
131
|
+
rule: "forbidden_dependency",
|
|
132
|
+
message: `Illegal import blocked: Domain '${sourceDomain}' is strictly forbidden from importing domain '${targetDomain}'.`,
|
|
133
|
+
});
|
|
134
|
+
} else if (config.settings.enforcement === "strict" && !allowed.includes(targetDomain)) {
|
|
135
|
+
violations.push({
|
|
136
|
+
file: sourceFilePath,
|
|
137
|
+
line: 1,
|
|
138
|
+
source_domain: sourceDomain,
|
|
139
|
+
target_domain: targetDomain,
|
|
140
|
+
imported_target: statement,
|
|
141
|
+
rule: "disallowed_dependency",
|
|
142
|
+
message: `Unlisted domain dependency: '${targetDomain}' must be explicitly added to allowed_dependencies before domain '${sourceDomain}' can import it.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return violations;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public resolveSourceDomain(filePath: string, config: ValidatedSeptumConfig): string | null {
|
|
151
|
+
return this.resolver.resolveSourceDomain(filePath, config);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public resolveTargetDomain(
|
|
155
|
+
targetStatement: string,
|
|
156
|
+
config: ValidatedSeptumConfig,
|
|
157
|
+
sourceFilePath?: string
|
|
158
|
+
): string | null {
|
|
159
|
+
return this.resolver.resolveTargetDomain(targetStatement, sourceFilePath, config);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public checkFeatureTouchpoints(
|
|
163
|
+
featureKey: string,
|
|
164
|
+
targetFilePath: string,
|
|
165
|
+
config: ValidatedSeptumConfig
|
|
166
|
+
): BoundaryViolation[] {
|
|
167
|
+
const violations: BoundaryViolation[] = [];
|
|
168
|
+
const features = config.features ?? {};
|
|
169
|
+
const featureConfig = features[featureKey];
|
|
170
|
+
|
|
171
|
+
if (!featureConfig) {
|
|
172
|
+
violations.push({
|
|
173
|
+
file: targetFilePath,
|
|
174
|
+
line: 1,
|
|
175
|
+
source_domain: "unknown",
|
|
176
|
+
target_domain: "unknown",
|
|
177
|
+
imported_target: targetFilePath,
|
|
178
|
+
rule: "unknown_feature",
|
|
179
|
+
message: `Feature '${featureKey}' is not defined in septum configuration.`,
|
|
180
|
+
});
|
|
181
|
+
return violations;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const normalizedTarget = targetFilePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
185
|
+
const allowedTouchpoints = featureConfig.allowed_touchpoints.map((tp) =>
|
|
186
|
+
tp.replace(/\\/g, "/").replace(/^\.\//, "")
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const domainCfg = config.domains[featureConfig.domain];
|
|
190
|
+
const domainRoot = domainCfg?.root ? domainCfg.root.replace(/\\/g, "/").replace(/^\.\//, "") : "";
|
|
191
|
+
|
|
192
|
+
const isAllowed = allowedTouchpoints.some((pattern) => {
|
|
193
|
+
if (pattern.endsWith("/**")) {
|
|
194
|
+
const prefix = pattern.slice(0, -3);
|
|
195
|
+
return normalizedTarget.startsWith(prefix);
|
|
196
|
+
}
|
|
197
|
+
if (pattern.endsWith("/*")) {
|
|
198
|
+
const prefix = pattern.slice(0, -2);
|
|
199
|
+
return normalizedTarget.startsWith(prefix);
|
|
200
|
+
}
|
|
201
|
+
if (normalizedTarget === pattern) return true;
|
|
202
|
+
|
|
203
|
+
if (
|
|
204
|
+
domainRoot &&
|
|
205
|
+
(normalizedTarget === `${domainRoot}/${pattern}` ||
|
|
206
|
+
normalizedTarget.startsWith(`${domainRoot}/${pattern}`))
|
|
207
|
+
) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (pattern.includes("/") && normalizedTarget.endsWith("/" + pattern)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (!isAllowed) {
|
|
219
|
+
violations.push({
|
|
220
|
+
file: targetFilePath,
|
|
221
|
+
line: 1,
|
|
222
|
+
source_domain: featureConfig.domain,
|
|
223
|
+
target_domain: "unknown",
|
|
224
|
+
imported_target: targetFilePath,
|
|
225
|
+
rule: "touchpoint_violation",
|
|
226
|
+
message: `Scope violation: File '${targetFilePath}' is outside the declared allowed_touchpoints for feature '${featureKey}'. Permitted: [${allowedTouchpoints.join(", ")}].`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return violations;
|
|
231
|
+
}
|
|
232
|
+
}
|