Skip to content

Commit 672998d

Browse files
committed
feat: add module field to domain types, derived from file path during scan
- Added 'module: String' to Entity, ValueObject, Service, Repository, DomainEvent - module_from_file_path() derives module name from file stem during AST scanning - CozoDB migration v2 adds module column to all 5 domain relations - show_model includes module in output; mutate_model accepts module parameter - Added .dendrites/ to .gitignore
1 parent 809131e commit 672998d

File tree

10 files changed

+286
-88
lines changed

10 files changed

+286
-88
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
.dendrites/

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dendrites"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
edition = "2024"
55
description = "Domain Model Context Protocol Server - Architectural meta-layer for GitHub Copilot"
66
license = "MIT"

Formula/dendrites.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ class Dendrites < Formula
22
desc "Domain Model Context Protocol Server — architectural meta-layer for GitHub Copilot"
33
homepage "https://github.com/flavioaiello/dendrites"
44
license "MIT"
5-
url "https://github.com/flavioaiello/dendrites/archive/refs/tags/v0.1.6.tar.gz"
6-
sha256 "642c7e1156f5816857b94d03aaaaed90add2b6202c20b24cbb0a2ef1074dc134"
7-
version "0.1.6"
5+
url "https://github.com/flavioaiello/dendrites/archive/refs/tags/v0.1.7.tar.gz"
6+
sha256 "PLACEHOLDER"
7+
version "0.1.7"
88

99
head "https://github.com/flavioaiello/dendrites.git", branch: "main"
1010

src/domain/analyze.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,13 @@ enum StructKind {
432432
///
433433
/// Enums are most commonly ValueObjects (status codes, type discriminators).
434434
/// Event naming suffixes override to Event.
435+
fn module_from_file_path(file_path: &str) -> String {
436+
std::path::Path::new(file_path)
437+
.file_stem()
438+
.map(|s| s.to_string_lossy().to_string())
439+
.unwrap_or_default()
440+
}
441+
435442
fn classify_enum(name: &str) -> StructKind {
436443
let upper = name.to_uppercase();
437444

@@ -646,6 +653,7 @@ pub fn scan_actual_model(
646653

647654
for discovered in &structs {
648655
let name = &discovered.name;
656+
let module_name = module_from_file_path(&discovered.file_path);
649657

650658
// Collect public methods for this struct
651659
let struct_methods: Vec<Method> = methods
@@ -687,6 +695,7 @@ pub fn scan_actual_model(
687695
bc.entities.push(Entity {
688696
name: name.clone(),
689697
description: desired_ent.map_or(String::new(), |e| e.description.clone()),
698+
module: module_name.clone(),
690699
aggregate_root: desired_ent.is_some_and(|e| e.aggregate_root),
691700
fields: discovered.fields.clone(),
692701
methods: struct_methods,
@@ -699,6 +708,7 @@ pub fn scan_actual_model(
699708
bc.value_objects.push(ValueObject {
700709
name: name.clone(),
701710
description: desired_vo.map_or(String::new(), |v| v.description.clone()),
711+
module: module_name.clone(),
702712
fields: discovered.fields.clone(),
703713
validation_rules: desired_vo.map_or(vec![], |v| v.validation_rules.clone()),
704714
});
@@ -709,6 +719,7 @@ pub fn scan_actual_model(
709719
bc.services.push(Service {
710720
name: name.clone(),
711721
description: desired_svc.map_or(String::new(), |s| s.description.clone()),
722+
module: module_name.clone(),
712723
kind: desired_svc.map_or(ServiceKind::Domain, |s| s.kind.clone()),
713724
methods: struct_methods,
714725
dependencies: desired_svc.map_or(vec![], |s| s.dependencies.clone()),
@@ -719,6 +730,7 @@ pub fn scan_actual_model(
719730
.and_then(|dbc| dbc.repositories.iter().find(|r| r.name.eq_ignore_ascii_case(name)));
720731
bc.repositories.push(Repository {
721732
name: name.clone(),
733+
module: module_name.clone(),
722734
aggregate: desired_repo.map_or(String::new(), |r| r.aggregate.clone()),
723735
methods: struct_methods,
724736
});
@@ -729,6 +741,7 @@ pub fn scan_actual_model(
729741
bc.events.push(DomainEvent {
730742
name: name.clone(),
731743
description: desired_evt.map_or(String::new(), |e| e.description.clone()),
744+
module: module_name.clone(),
732745
fields: discovered.fields.clone(),
733746
source: desired_evt.map_or(String::new(), |e| e.source.clone()),
734747
});
@@ -739,6 +752,7 @@ pub fn scan_actual_model(
739752
// ── Classify discovered enums ──
740753
for discovered in &enums {
741754
let name = &discovered.name;
755+
let module_name = module_from_file_path(&discovered.file_path);
742756

743757
// Collect public methods for this enum (from impl blocks)
744758
let enum_methods: Vec<Method> = methods
@@ -776,6 +790,7 @@ pub fn scan_actual_model(
776790
bc.entities.push(Entity {
777791
name: name.clone(),
778792
description: desired_ent.map_or(String::new(), |e| e.description.clone()),
793+
module: module_name.clone(),
779794
aggregate_root: desired_ent.is_some_and(|e| e.aggregate_root),
780795
fields: discovered.variants.clone(),
781796
methods: enum_methods,
@@ -788,6 +803,7 @@ pub fn scan_actual_model(
788803
bc.value_objects.push(ValueObject {
789804
name: name.clone(),
790805
description: desired_vo.map_or(String::new(), |v| v.description.clone()),
806+
module: module_name.clone(),
791807
fields: discovered.variants.clone(),
792808
validation_rules: desired_vo.map_or(vec![], |v| v.validation_rules.clone()),
793809
});
@@ -798,6 +814,7 @@ pub fn scan_actual_model(
798814
bc.services.push(Service {
799815
name: name.clone(),
800816
description: desired_svc.map_or(String::new(), |s| s.description.clone()),
817+
module: module_name.clone(),
801818
kind: desired_svc.map_or(ServiceKind::Domain, |s| s.kind.clone()),
802819
methods: enum_methods,
803820
dependencies: desired_svc.map_or(vec![], |s| s.dependencies.clone()),
@@ -808,6 +825,7 @@ pub fn scan_actual_model(
808825
.and_then(|dbc| dbc.repositories.iter().find(|r| r.name.eq_ignore_ascii_case(name)));
809826
bc.repositories.push(Repository {
810827
name: name.clone(),
828+
module: module_name.clone(),
811829
aggregate: desired_repo.map_or(String::new(), |r| r.aggregate.clone()),
812830
methods: enum_methods,
813831
});
@@ -818,6 +836,7 @@ pub fn scan_actual_model(
818836
bc.events.push(DomainEvent {
819837
name: name.clone(),
820838
description: desired_evt.map_or(String::new(), |e| e.description.clone()),
839+
module: module_name.clone(),
821840
fields: discovered.variants.clone(),
822841
source: desired_evt.map_or(String::new(), |e| e.source.clone()),
823842
});
@@ -1230,6 +1249,7 @@ impl User {
12301249
entities: vec![Entity {
12311250
name: "User".into(),
12321251
description: "".into(),
1252+
module: String::new(),
12331253
aggregate_root: true,
12341254
fields: vec![],
12351255
methods: vec![],
@@ -1238,6 +1258,7 @@ impl User {
12381258
value_objects: vec![ValueObject {
12391259
name: "Email".into(),
12401260
description: "".into(),
1261+
module: String::new(),
12411262
fields: vec![],
12421263
validation_rules: vec![],
12431264
}],

src/domain/model.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ pub struct Entity {
229229
pub name: String,
230230
#[serde(default)]
231231
pub description: String,
232+
/// The source module (file stem) this entity was discovered in
233+
#[serde(default)]
234+
pub module: String,
232235
/// Whether this is an aggregate root
233236
#[serde(default)]
234237
pub aggregate_root: bool,
@@ -247,6 +250,9 @@ pub struct ValueObject {
247250
pub name: String,
248251
#[serde(default)]
249252
pub description: String,
253+
/// The source module (file stem) this value object was discovered in
254+
#[serde(default)]
255+
pub module: String,
250256
#[serde(default)]
251257
pub fields: Vec<Field>,
252258
#[serde(default)]
@@ -260,6 +266,9 @@ pub struct Service {
260266
pub name: String,
261267
#[serde(default)]
262268
pub description: String,
269+
/// The source module (file stem) this service was discovered in
270+
#[serde(default)]
271+
pub module: String,
263272
#[serde(default)]
264273
pub kind: ServiceKind,
265274
#[serde(default)]
@@ -282,6 +291,9 @@ pub enum ServiceKind {
282291
#[derive(Debug, Clone, Serialize, Deserialize)]
283292
pub struct Repository {
284293
pub name: String,
294+
/// The source module (file stem) this repository was discovered in
295+
#[serde(default)]
296+
pub module: String,
285297
/// The aggregate root this repository manages
286298
pub aggregate: String,
287299
#[serde(default)]
@@ -295,6 +307,9 @@ pub struct DomainEvent {
295307
pub name: String,
296308
#[serde(default)]
297309
pub description: String,
310+
/// The source module (file stem) this event was discovered in
311+
#[serde(default)]
312+
pub module: String,
298313
#[serde(default)]
299314
pub fields: Vec<Field>,
300315
/// Which entity/aggregate emits this event

src/mcp/tools.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -187,32 +187,32 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
187187
).unwrap_or_default();
188188

189189
let entities = store.run_datalog(
190-
&format!("?[ctx, name, description, aggregate_root] := \
191-
*entity{{workspace: $ws, context: ctx, name, description, aggregate_root, state: '{state}'}}"),
190+
&format!("?[ctx, name, description, module, aggregate_root] := \
191+
*entity{{workspace: $ws, context: ctx, name, description, module, aggregate_root, state: '{state}'}}"),
192192
workspace,
193193
).unwrap_or_default();
194194

195195
let services = store.run_datalog(
196-
&format!("?[ctx, name, description, kind] := \
197-
*service{{workspace: $ws, context: ctx, name, description, kind, state: '{state}'}}"),
196+
&format!("?[ctx, name, description, module, kind] := \
197+
*service{{workspace: $ws, context: ctx, name, description, module, kind, state: '{state}'}}"),
198198
workspace,
199199
).unwrap_or_default();
200200

201201
let events = store.run_datalog(
202-
&format!("?[ctx, name, description, source] := \
203-
*event{{workspace: $ws, context: ctx, name, description, source, state: '{state}'}}"),
202+
&format!("?[ctx, name, description, module, source] := \
203+
*event{{workspace: $ws, context: ctx, name, description, module, source, state: '{state}'}}"),
204204
workspace,
205205
).unwrap_or_default();
206206

207207
let value_objects = store.run_datalog(
208-
&format!("?[ctx, name, description] := \
209-
*value_object{{workspace: $ws, context: ctx, name, description, state: '{state}'}}"),
208+
&format!("?[ctx, name, description, module] := \
209+
*value_object{{workspace: $ws, context: ctx, name, description, module, state: '{state}'}}"),
210210
workspace,
211211
).unwrap_or_default();
212212

213213
let repositories = store.run_datalog(
214-
&format!("?[ctx, name, aggregate] := \
215-
*repository{{workspace: $ws, context: ctx, name, aggregate, state: '{state}'}}"),
214+
&format!("?[ctx, name, aggregate, module] := \
215+
*repository{{workspace: $ws, context: ctx, name, aggregate, module, state: '{state}'}}"),
216216
workspace,
217217
).unwrap_or_default();
218218

@@ -285,7 +285,8 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
285285
.collect();
286286
json!({
287287
"name": ent_name, "description": e[2],
288-
"aggregate_root": e[3] == "true",
288+
"module": e[3],
289+
"aggregate_root": e[4] == "true",
289290
"fields": ent_fields, "methods": ent_methods,
290291
"invariants": ent_invariants,
291292
})
@@ -305,7 +306,7 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
305306
json!({"name": m[3], "description": m[4], "return_type": m[5], "parameters": params})
306307
})
307308
.collect();
308-
json!({"name": s[1], "description": s[2], "kind": s[3], "methods": svc_methods})
309+
json!({"name": s[1], "description": s[2], "module": s[3], "kind": s[4], "methods": svc_methods})
309310
})
310311
.collect();
311312

@@ -316,7 +317,7 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
316317
.filter(|f| f[0] == *ctx_name && f[1] == "event" && f[2] == ev[1])
317318
.map(|f| json!({"name": f[3], "type": f[4], "required": f[5] == "true"}))
318319
.collect();
319-
json!({"name": ev[1], "description": ev[2], "source": ev[3], "fields": evt_fields})
320+
json!({"name": ev[1], "description": ev[2], "module": ev[3], "source": ev[4], "fields": evt_fields})
320321
})
321322
.collect();
322323

@@ -331,7 +332,7 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
331332
.filter(|r| r[0] == *ctx_name && r[1] == vo[1])
332333
.map(|r| r[2].as_str())
333334
.collect();
334-
json!({"name": vo[1], "description": vo[2], "fields": vo_fields, "validation_rules": rules})
335+
json!({"name": vo[1], "description": vo[2], "module": vo[3], "fields": vo_fields, "validation_rules": rules})
335336
})
336337
.collect();
337338

@@ -348,7 +349,7 @@ pub fn build_model_overview(store: &Store, workspace: &str, state: &str) -> Valu
348349
json!({"name": m[3], "description": m[4], "return_type": m[5], "parameters": params})
349350
})
350351
.collect();
351-
json!({"name": repo[1], "aggregate": repo[2], "methods": repo_methods})
352+
json!({"name": repo[1], "aggregate": repo[2], "module": repo[3], "methods": repo_methods})
352353
})
353354
.collect();
354355

@@ -406,6 +407,7 @@ mod tests {
406407
entities: vec![Entity {
407408
name: "User".into(),
408409
description: "A user".into(),
410+
module: String::new(),
409411
aggregate_root: true,
410412
fields: vec![Field {
411413
name: "id".into(),
@@ -420,6 +422,7 @@ mod tests {
420422
services: vec![Service {
421423
name: "AuthService".into(),
422424
description: "Handles auth".into(),
425+
module: String::new(),
423426
kind: ServiceKind::Application,
424427
methods: vec![],
425428
dependencies: vec![],

0 commit comments

Comments
 (0)