guardian-framework 0.1.27 → 0.1.29
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/README.md +47 -7
- package/dist/cli.js +162 -25
- package/dist/exports.js +162 -25
- package/package.json +1 -1
- package/templates/pi/extensions/pipeline.ts +29 -0
- package/templates/pi/preflight_report.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,9 @@ Domain Exploration ←─ you and the agent explore, iterate, refine
|
|
|
35
35
|
Architecture Modules ←─ you review, adjust, approve
|
|
36
36
|
│
|
|
37
37
|
▼
|
|
38
|
+
Enrich Module Docs ←─ agent fills DDD content from exploration
|
|
39
|
+
│
|
|
40
|
+
▼
|
|
38
41
|
ADRs, Diagrams, Specs ←─ you challenge decisions, finalize
|
|
39
42
|
│
|
|
40
43
|
▼
|
|
@@ -75,15 +78,49 @@ You go back and forth until the model reflects your actual business. Only then d
|
|
|
75
78
|
/domain --architect-scaffold <session-id>
|
|
76
79
|
```
|
|
77
80
|
|
|
78
|
-
Guardian generates one architecture module per bounded context, with
|
|
81
|
+
Guardian generates one architecture module per bounded context, with architecture layers, aggregates, repository contracts, and domain events:
|
|
79
82
|
|
|
80
83
|
```markdown
|
|
81
84
|
# Audit Ingestion
|
|
82
85
|
|
|
83
|
-
##
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
## Architecture
|
|
87
|
+
| Layer | Responsibility |
|
|
88
|
+
|-------|--------------|
|
|
89
|
+
| **Domain** | Aggregates, entities, repository interfaces | src/audit-ingestion/domain/ |
|
|
90
|
+
| **Application** | Use cases, DTOs | src/audit-ingestion/application/ |
|
|
91
|
+
|
|
92
|
+
## Aggregates
|
|
93
|
+
### AuditRecord
|
|
94
|
+
**Repository Interface:**
|
|
95
|
+
```rust
|
|
96
|
+
pub trait AuditRecordRepository: Send + Sync {
|
|
97
|
+
async fn find_by_id(&self, id: &AuditRecordId) -> Result<Option<AuditRecord>, DomainError>;
|
|
98
|
+
async fn save(&self, entity: &AuditRecord) -> Result<(), DomainError>;
|
|
99
|
+
async fn delete(&self, id: &AuditRecordId) -> Result<(), DomainError>;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Domain Events
|
|
104
|
+
| Event | Description | Trigger |
|
|
105
|
+
|-------|-------------|--------|
|
|
106
|
+
| AuditRecordIngested | ... | ... |
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3.5 Enrich with Full DDD Content
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
/domain --enrich <session-id>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The agent reads the domain exploration and enhances each module stub with:
|
|
116
|
+
- Mermaid data flow diagrams
|
|
117
|
+
- Real Rust struct stubs
|
|
118
|
+
- API endpoint tables
|
|
119
|
+
- Ubiquitous Language per context
|
|
120
|
+
- Cross-context dependencies
|
|
121
|
+
- Security considerations
|
|
122
|
+
|
|
123
|
+
This produces production-quality architecture docs (5–20 KB each) that an implementer can build from directly.
|
|
87
124
|
|
|
88
125
|
## Idempotency Handler
|
|
89
126
|
status: planned
|
|
@@ -162,9 +199,12 @@ npx guardian-framework init
|
|
|
162
199
|
# 3. Generate architecture from exploration
|
|
163
200
|
/domain --architect-scaffold <session-id>
|
|
164
201
|
|
|
165
|
-
# 4.
|
|
202
|
+
# 4. Enrich module docs with full DDD content
|
|
203
|
+
/domain --enrich <session-id>
|
|
204
|
+
|
|
205
|
+
# 5. Review and refine (ADRs, diagrams, modules — human-in-the-loop)
|
|
166
206
|
|
|
167
|
-
#
|
|
207
|
+
# 6. Adopt the roadmap
|
|
168
208
|
/architect --roadmap
|
|
169
209
|
/architect --adopt-roadmap
|
|
170
210
|
|
package/dist/cli.js
CHANGED
|
@@ -1335,7 +1335,7 @@ __export(exports_package, {
|
|
|
1335
1335
|
bin: () => bin,
|
|
1336
1336
|
author: () => author
|
|
1337
1337
|
});
|
|
1338
|
-
var name = "guardian-framework", version = "0.1.
|
|
1338
|
+
var name = "guardian-framework", version = "0.1.29", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
1339
1339
|
var init_package = __esm(() => {
|
|
1340
1340
|
exports = {
|
|
1341
1341
|
".": {
|
|
@@ -10011,6 +10011,10 @@ function coerceEntityType(val) {
|
|
|
10011
10011
|
return "value-object";
|
|
10012
10012
|
if (val === "aggregate-root")
|
|
10013
10013
|
return "aggregate-root";
|
|
10014
|
+
if (val === "Aggregate Root")
|
|
10015
|
+
return "aggregate-root";
|
|
10016
|
+
if (val === "Value Object")
|
|
10017
|
+
return "value-object";
|
|
10014
10018
|
if (val === "ui-concept")
|
|
10015
10019
|
return "ui-concept";
|
|
10016
10020
|
if (val === "user-intent")
|
|
@@ -10250,6 +10254,9 @@ function readExplorationSession(sessionId, projectDir) {
|
|
|
10250
10254
|
aggregateRoots
|
|
10251
10255
|
};
|
|
10252
10256
|
}
|
|
10257
|
+
function stripMarkdown(text) {
|
|
10258
|
+
return text.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").trim();
|
|
10259
|
+
}
|
|
10253
10260
|
function extractTable(body, sectionName) {
|
|
10254
10261
|
const sectionRegex = new RegExp(`## ${sectionName}\\n+([\\s\\S]*?)(?:\\n##|$)`);
|
|
10255
10262
|
const sectionMatch = body.match(sectionRegex);
|
|
@@ -10263,7 +10270,7 @@ function extractTable(body, sectionName) {
|
|
|
10263
10270
|
const trimmed = line.trim();
|
|
10264
10271
|
const content = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
10265
10272
|
const withoutTrailing = content.endsWith("|") ? content.slice(0, -1) : content;
|
|
10266
|
-
return withoutTrailing.split("|").map((s) => s.trim());
|
|
10273
|
+
return withoutTrailing.split("|").map((s) => stripMarkdown(s.trim()));
|
|
10267
10274
|
});
|
|
10268
10275
|
}
|
|
10269
10276
|
function scaffoldFromExploration(sessionId, options) {
|
|
@@ -10564,11 +10571,12 @@ None yet
|
|
|
10564
10571
|
|
|
10565
10572
|
`;
|
|
10566
10573
|
} else {
|
|
10567
|
-
content
|
|
10574
|
+
content += `# ${bc.name}
|
|
10568
10575
|
|
|
10569
10576
|
`;
|
|
10570
10577
|
content += `## Module Status
|
|
10571
|
-
|
|
10578
|
+
`;
|
|
10579
|
+
content += `
|
|
10572
10580
|
`;
|
|
10573
10581
|
content += `**Status:** Planned
|
|
10574
10582
|
`;
|
|
@@ -10582,57 +10590,186 @@ None yet
|
|
|
10582
10590
|
${bc.description}
|
|
10583
10591
|
|
|
10584
10592
|
`;
|
|
10585
|
-
content +=
|
|
10593
|
+
content += `
|
|
10594
|
+
`;
|
|
10595
|
+
content += `## Architecture
|
|
10596
|
+
`;
|
|
10597
|
+
content += `
|
|
10598
|
+
`;
|
|
10599
|
+
content += `This module follows **Domain-Driven Design** with Clean Architecture layers:
|
|
10600
|
+
`;
|
|
10601
|
+
content += `
|
|
10602
|
+
`;
|
|
10603
|
+
content += `| Layer | Responsibility |
|
|
10604
|
+
`;
|
|
10605
|
+
content += `|-------|--------------|
|
|
10606
|
+
`;
|
|
10607
|
+
content += `| **Domain** | Aggregates, entities, value objects, domain services, repository interfaces | src/${moduleName}/domain/ |
|
|
10608
|
+
`;
|
|
10609
|
+
content += `| **Application** | Use cases / application services, DTOs, input validation | src/${moduleName}/application/ |
|
|
10610
|
+
`;
|
|
10611
|
+
content += `| **Infrastructure** | Repository implementations, DB models, external clients | src/${moduleName}/infrastructure/ |
|
|
10612
|
+
`;
|
|
10613
|
+
content += `| **Interfaces** | API handlers, event subscribers, serialization | src/${moduleName}/interfaces/ |
|
|
10614
|
+
`;
|
|
10615
|
+
content += `
|
|
10616
|
+
`;
|
|
10617
|
+
content += `## Aggregates
|
|
10618
|
+
`;
|
|
10619
|
+
content += `
|
|
10620
|
+
`;
|
|
10621
|
+
if (aggregateRoots.length > 0) {
|
|
10622
|
+
for (const ar of aggregateRoots) {
|
|
10623
|
+
content += `### ${ar.name}
|
|
10586
10624
|
|
|
10587
|
-
|
|
10625
|
+
`;
|
|
10626
|
+
content += ar.description === "" ? "_No description._" : ar.description;
|
|
10627
|
+
content += `
|
|
10628
|
+
`;
|
|
10629
|
+
const ownedEntities = regularEntities.filter((e2) => e2.description.includes(ar.name) || ar.description.includes(e2.name));
|
|
10630
|
+
const ownedVOs = valueObjects.filter((vo) => vo.description.includes(ar.name) || ar.description.includes(vo.name));
|
|
10631
|
+
content += `
|
|
10632
|
+
`;
|
|
10633
|
+
if (ownedEntities.length > 0) {
|
|
10634
|
+
content += `**Owned Entities:**
|
|
10635
|
+
`;
|
|
10636
|
+
for (const ent of ownedEntities) {
|
|
10637
|
+
content += `- ${ent.name}: ${ent.description}
|
|
10638
|
+
`;
|
|
10639
|
+
}
|
|
10640
|
+
content += `
|
|
10641
|
+
`;
|
|
10642
|
+
}
|
|
10643
|
+
if (ownedVOs.length > 0) {
|
|
10644
|
+
content += `**Value Objects:**
|
|
10645
|
+
`;
|
|
10646
|
+
for (const vo of ownedVOs) {
|
|
10647
|
+
content += `- ${vo.name}: ${vo.description}
|
|
10648
|
+
`;
|
|
10649
|
+
}
|
|
10650
|
+
content += `
|
|
10651
|
+
`;
|
|
10652
|
+
}
|
|
10653
|
+
const arVarName = ar.name.charAt(0).toLowerCase() + ar.name.slice(1);
|
|
10654
|
+
content += `**Repository Interface:**
|
|
10655
|
+
`;
|
|
10656
|
+
content += `
|
|
10657
|
+
`;
|
|
10658
|
+
content += "```rust\n";
|
|
10659
|
+
content += `pub trait ${ar.name}Repository: Send + Sync {
|
|
10660
|
+
`;
|
|
10661
|
+
content += ` async fn find_by_id(&self, id: &${ar.name}Id) -> Result<Option<${ar.name}>, DomainError>;
|
|
10662
|
+
`;
|
|
10663
|
+
content += ` async fn save(&self, ${arVarName}: &${ar.name}) -> Result<(), DomainError>;
|
|
10664
|
+
`;
|
|
10665
|
+
content += ` async fn delete(&self, id: &${ar.name}Id) -> Result<(), DomainError>;
|
|
10666
|
+
`;
|
|
10667
|
+
content += `}
|
|
10668
|
+
`;
|
|
10669
|
+
content += "```";
|
|
10670
|
+
content += `
|
|
10671
|
+
`;
|
|
10672
|
+
if (ownedEntities.length > 0 || ownedVOs.length > 0) {
|
|
10673
|
+
content += `**Aggregate Invariants:**
|
|
10674
|
+
`;
|
|
10675
|
+
content += `- All operations go through ${ar.name} aggregate root
|
|
10676
|
+
`;
|
|
10677
|
+
content += `- References to child entities resolve through ${ar.name}
|
|
10678
|
+
`;
|
|
10679
|
+
content += `- ${ar.name}Id is the sole external identifier
|
|
10680
|
+
`;
|
|
10681
|
+
content += `
|
|
10682
|
+
`;
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
10685
|
+
} else {
|
|
10686
|
+
content += `No aggregates defined yet.
|
|
10687
|
+
`;
|
|
10688
|
+
content += `
|
|
10689
|
+
`;
|
|
10690
|
+
}
|
|
10691
|
+
content += `
|
|
10588
10692
|
`;
|
|
10589
10693
|
content += `## Domain Events
|
|
10590
|
-
|
|
10694
|
+
`;
|
|
10695
|
+
content += `
|
|
10591
10696
|
`;
|
|
10592
10697
|
if (contextEvents.length === 0) {
|
|
10593
10698
|
content += `None defined yet.
|
|
10594
|
-
|
|
10699
|
+
`;
|
|
10700
|
+
content += `
|
|
10595
10701
|
`;
|
|
10596
10702
|
} else {
|
|
10703
|
+
content += `| Event | Description | Trigger |
|
|
10704
|
+
`;
|
|
10705
|
+
content += `|-------|-------------|--------|
|
|
10706
|
+
`;
|
|
10597
10707
|
for (const ev of contextEvents) {
|
|
10598
|
-
content +=
|
|
10708
|
+
content += `| ${ev.name} | ${ev.description} | ${ev.triggeredBy} |
|
|
10599
10709
|
`;
|
|
10600
10710
|
}
|
|
10601
10711
|
content += `
|
|
10602
10712
|
`;
|
|
10603
10713
|
}
|
|
10714
|
+
content += `
|
|
10715
|
+
`;
|
|
10604
10716
|
content += `## Ubiquitous Language
|
|
10605
|
-
|
|
10606
10717
|
`;
|
|
10607
|
-
|
|
10608
|
-
content += `None defined yet.
|
|
10609
|
-
|
|
10718
|
+
content += `
|
|
10610
10719
|
`;
|
|
10720
|
+
if (contextTerms.length === 0) {
|
|
10721
|
+
content += "See `.pi/domain/ubiquitous-language.md` for full glossary.\n\n";
|
|
10611
10722
|
} else {
|
|
10612
|
-
content += `| Term | Definition |
|
|
10723
|
+
content += `| Term | Definition |
|
|
10613
10724
|
`;
|
|
10614
|
-
content +=
|
|
10725
|
+
content += `|------|-----------|
|
|
10615
10726
|
`;
|
|
10616
10727
|
for (const t of contextTerms) {
|
|
10617
|
-
content += `| ${t.term} | ${t.definition} |
|
|
10728
|
+
content += `| ${t.term} | ${t.definition} |
|
|
10618
10729
|
`;
|
|
10619
10730
|
}
|
|
10620
10731
|
content += `
|
|
10621
10732
|
`;
|
|
10622
10733
|
}
|
|
10734
|
+
content += `
|
|
10735
|
+
`;
|
|
10623
10736
|
content += `## Dependencies
|
|
10624
|
-
|
|
10625
|
-
${depsSection}
|
|
10626
10737
|
`;
|
|
10627
|
-
content +=
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10738
|
+
content += `
|
|
10739
|
+
`;
|
|
10740
|
+
content += `${depsSection}
|
|
10741
|
+
`;
|
|
10742
|
+
content += `
|
|
10743
|
+
`;
|
|
10744
|
+
content += `## Implementation Notes
|
|
10745
|
+
`;
|
|
10746
|
+
content += `
|
|
10747
|
+
`;
|
|
10748
|
+
content += `Implementation should follow Rust DDD + Clean Architecture patterns:
|
|
10749
|
+
`;
|
|
10750
|
+
content += `
|
|
10751
|
+
`;
|
|
10752
|
+
content += `1. Domain layer: aggregates, entities, value objects, repository interfaces
|
|
10753
|
+
`;
|
|
10754
|
+
content += `2. Infrastructure: Postgres repository implementations via SQLx
|
|
10755
|
+
`;
|
|
10756
|
+
content += `3. Application: use case orchestration
|
|
10757
|
+
`;
|
|
10758
|
+
content += `4. Interfaces: HTTP handlers with Axum
|
|
10759
|
+
`;
|
|
10760
|
+
content += `
|
|
10761
|
+
`;
|
|
10762
|
+
content += `Each layer MUST NOT leak into adjacent layers.
|
|
10763
|
+
`;
|
|
10764
|
+
content += `
|
|
10631
10765
|
`;
|
|
10632
10766
|
content += `## ADRs
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10767
|
+
`;
|
|
10768
|
+
content += `
|
|
10769
|
+
`;
|
|
10770
|
+
content += `None yet
|
|
10771
|
+
`;
|
|
10772
|
+
content += `
|
|
10636
10773
|
`;
|
|
10637
10774
|
}
|
|
10638
10775
|
if (!dryRun) {
|
package/dist/exports.js
CHANGED
|
@@ -995,7 +995,7 @@ __export(exports_package, {
|
|
|
995
995
|
bin: () => bin,
|
|
996
996
|
author: () => author
|
|
997
997
|
});
|
|
998
|
-
var name = "guardian-framework", version = "0.1.
|
|
998
|
+
var name = "guardian-framework", version = "0.1.29", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
999
999
|
var init_package = __esm(() => {
|
|
1000
1000
|
exports = {
|
|
1001
1001
|
".": {
|
|
@@ -11212,6 +11212,10 @@ function coerceEntityType(val) {
|
|
|
11212
11212
|
return "value-object";
|
|
11213
11213
|
if (val === "aggregate-root")
|
|
11214
11214
|
return "aggregate-root";
|
|
11215
|
+
if (val === "Aggregate Root")
|
|
11216
|
+
return "aggregate-root";
|
|
11217
|
+
if (val === "Value Object")
|
|
11218
|
+
return "value-object";
|
|
11215
11219
|
if (val === "ui-concept")
|
|
11216
11220
|
return "ui-concept";
|
|
11217
11221
|
if (val === "user-intent")
|
|
@@ -11451,6 +11455,9 @@ function readExplorationSession(sessionId, projectDir) {
|
|
|
11451
11455
|
aggregateRoots
|
|
11452
11456
|
};
|
|
11453
11457
|
}
|
|
11458
|
+
function stripMarkdown(text) {
|
|
11459
|
+
return text.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").trim();
|
|
11460
|
+
}
|
|
11454
11461
|
function extractTable(body, sectionName) {
|
|
11455
11462
|
const sectionRegex = new RegExp(`## ${sectionName}\\n+([\\s\\S]*?)(?:\\n##|$)`);
|
|
11456
11463
|
const sectionMatch = body.match(sectionRegex);
|
|
@@ -11464,7 +11471,7 @@ function extractTable(body, sectionName) {
|
|
|
11464
11471
|
const trimmed = line.trim();
|
|
11465
11472
|
const content = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
11466
11473
|
const withoutTrailing = content.endsWith("|") ? content.slice(0, -1) : content;
|
|
11467
|
-
return withoutTrailing.split("|").map((s) => s.trim());
|
|
11474
|
+
return withoutTrailing.split("|").map((s) => stripMarkdown(s.trim()));
|
|
11468
11475
|
});
|
|
11469
11476
|
}
|
|
11470
11477
|
function scaffoldFromExploration(sessionId, options) {
|
|
@@ -11765,11 +11772,12 @@ None yet
|
|
|
11765
11772
|
|
|
11766
11773
|
`;
|
|
11767
11774
|
} else {
|
|
11768
|
-
content
|
|
11775
|
+
content += `# ${bc.name}
|
|
11769
11776
|
|
|
11770
11777
|
`;
|
|
11771
11778
|
content += `## Module Status
|
|
11772
|
-
|
|
11779
|
+
`;
|
|
11780
|
+
content += `
|
|
11773
11781
|
`;
|
|
11774
11782
|
content += `**Status:** Planned
|
|
11775
11783
|
`;
|
|
@@ -11783,57 +11791,186 @@ None yet
|
|
|
11783
11791
|
${bc.description}
|
|
11784
11792
|
|
|
11785
11793
|
`;
|
|
11786
|
-
content +=
|
|
11794
|
+
content += `
|
|
11795
|
+
`;
|
|
11796
|
+
content += `## Architecture
|
|
11797
|
+
`;
|
|
11798
|
+
content += `
|
|
11799
|
+
`;
|
|
11800
|
+
content += `This module follows **Domain-Driven Design** with Clean Architecture layers:
|
|
11801
|
+
`;
|
|
11802
|
+
content += `
|
|
11803
|
+
`;
|
|
11804
|
+
content += `| Layer | Responsibility |
|
|
11805
|
+
`;
|
|
11806
|
+
content += `|-------|--------------|
|
|
11807
|
+
`;
|
|
11808
|
+
content += `| **Domain** | Aggregates, entities, value objects, domain services, repository interfaces | src/${moduleName}/domain/ |
|
|
11809
|
+
`;
|
|
11810
|
+
content += `| **Application** | Use cases / application services, DTOs, input validation | src/${moduleName}/application/ |
|
|
11811
|
+
`;
|
|
11812
|
+
content += `| **Infrastructure** | Repository implementations, DB models, external clients | src/${moduleName}/infrastructure/ |
|
|
11813
|
+
`;
|
|
11814
|
+
content += `| **Interfaces** | API handlers, event subscribers, serialization | src/${moduleName}/interfaces/ |
|
|
11815
|
+
`;
|
|
11816
|
+
content += `
|
|
11817
|
+
`;
|
|
11818
|
+
content += `## Aggregates
|
|
11819
|
+
`;
|
|
11820
|
+
content += `
|
|
11821
|
+
`;
|
|
11822
|
+
if (aggregateRoots.length > 0) {
|
|
11823
|
+
for (const ar of aggregateRoots) {
|
|
11824
|
+
content += `### ${ar.name}
|
|
11787
11825
|
|
|
11788
|
-
|
|
11826
|
+
`;
|
|
11827
|
+
content += ar.description === "" ? "_No description._" : ar.description;
|
|
11828
|
+
content += `
|
|
11829
|
+
`;
|
|
11830
|
+
const ownedEntities = regularEntities.filter((e2) => e2.description.includes(ar.name) || ar.description.includes(e2.name));
|
|
11831
|
+
const ownedVOs = valueObjects.filter((vo) => vo.description.includes(ar.name) || ar.description.includes(vo.name));
|
|
11832
|
+
content += `
|
|
11833
|
+
`;
|
|
11834
|
+
if (ownedEntities.length > 0) {
|
|
11835
|
+
content += `**Owned Entities:**
|
|
11836
|
+
`;
|
|
11837
|
+
for (const ent of ownedEntities) {
|
|
11838
|
+
content += `- ${ent.name}: ${ent.description}
|
|
11839
|
+
`;
|
|
11840
|
+
}
|
|
11841
|
+
content += `
|
|
11842
|
+
`;
|
|
11843
|
+
}
|
|
11844
|
+
if (ownedVOs.length > 0) {
|
|
11845
|
+
content += `**Value Objects:**
|
|
11846
|
+
`;
|
|
11847
|
+
for (const vo of ownedVOs) {
|
|
11848
|
+
content += `- ${vo.name}: ${vo.description}
|
|
11849
|
+
`;
|
|
11850
|
+
}
|
|
11851
|
+
content += `
|
|
11852
|
+
`;
|
|
11853
|
+
}
|
|
11854
|
+
const arVarName = ar.name.charAt(0).toLowerCase() + ar.name.slice(1);
|
|
11855
|
+
content += `**Repository Interface:**
|
|
11856
|
+
`;
|
|
11857
|
+
content += `
|
|
11858
|
+
`;
|
|
11859
|
+
content += "```rust\n";
|
|
11860
|
+
content += `pub trait ${ar.name}Repository: Send + Sync {
|
|
11861
|
+
`;
|
|
11862
|
+
content += ` async fn find_by_id(&self, id: &${ar.name}Id) -> Result<Option<${ar.name}>, DomainError>;
|
|
11863
|
+
`;
|
|
11864
|
+
content += ` async fn save(&self, ${arVarName}: &${ar.name}) -> Result<(), DomainError>;
|
|
11865
|
+
`;
|
|
11866
|
+
content += ` async fn delete(&self, id: &${ar.name}Id) -> Result<(), DomainError>;
|
|
11867
|
+
`;
|
|
11868
|
+
content += `}
|
|
11869
|
+
`;
|
|
11870
|
+
content += "```";
|
|
11871
|
+
content += `
|
|
11872
|
+
`;
|
|
11873
|
+
if (ownedEntities.length > 0 || ownedVOs.length > 0) {
|
|
11874
|
+
content += `**Aggregate Invariants:**
|
|
11875
|
+
`;
|
|
11876
|
+
content += `- All operations go through ${ar.name} aggregate root
|
|
11877
|
+
`;
|
|
11878
|
+
content += `- References to child entities resolve through ${ar.name}
|
|
11879
|
+
`;
|
|
11880
|
+
content += `- ${ar.name}Id is the sole external identifier
|
|
11881
|
+
`;
|
|
11882
|
+
content += `
|
|
11883
|
+
`;
|
|
11884
|
+
}
|
|
11885
|
+
}
|
|
11886
|
+
} else {
|
|
11887
|
+
content += `No aggregates defined yet.
|
|
11888
|
+
`;
|
|
11889
|
+
content += `
|
|
11890
|
+
`;
|
|
11891
|
+
}
|
|
11892
|
+
content += `
|
|
11789
11893
|
`;
|
|
11790
11894
|
content += `## Domain Events
|
|
11791
|
-
|
|
11895
|
+
`;
|
|
11896
|
+
content += `
|
|
11792
11897
|
`;
|
|
11793
11898
|
if (contextEvents.length === 0) {
|
|
11794
11899
|
content += `None defined yet.
|
|
11795
|
-
|
|
11900
|
+
`;
|
|
11901
|
+
content += `
|
|
11796
11902
|
`;
|
|
11797
11903
|
} else {
|
|
11904
|
+
content += `| Event | Description | Trigger |
|
|
11905
|
+
`;
|
|
11906
|
+
content += `|-------|-------------|--------|
|
|
11907
|
+
`;
|
|
11798
11908
|
for (const ev of contextEvents) {
|
|
11799
|
-
content +=
|
|
11909
|
+
content += `| ${ev.name} | ${ev.description} | ${ev.triggeredBy} |
|
|
11800
11910
|
`;
|
|
11801
11911
|
}
|
|
11802
11912
|
content += `
|
|
11803
11913
|
`;
|
|
11804
11914
|
}
|
|
11915
|
+
content += `
|
|
11916
|
+
`;
|
|
11805
11917
|
content += `## Ubiquitous Language
|
|
11806
|
-
|
|
11807
11918
|
`;
|
|
11808
|
-
|
|
11809
|
-
content += `None defined yet.
|
|
11810
|
-
|
|
11919
|
+
content += `
|
|
11811
11920
|
`;
|
|
11921
|
+
if (contextTerms.length === 0) {
|
|
11922
|
+
content += "See `.pi/domain/ubiquitous-language.md` for full glossary.\n\n";
|
|
11812
11923
|
} else {
|
|
11813
|
-
content += `| Term | Definition |
|
|
11924
|
+
content += `| Term | Definition |
|
|
11814
11925
|
`;
|
|
11815
|
-
content +=
|
|
11926
|
+
content += `|------|-----------|
|
|
11816
11927
|
`;
|
|
11817
11928
|
for (const t of contextTerms) {
|
|
11818
|
-
content += `| ${t.term} | ${t.definition} |
|
|
11929
|
+
content += `| ${t.term} | ${t.definition} |
|
|
11819
11930
|
`;
|
|
11820
11931
|
}
|
|
11821
11932
|
content += `
|
|
11822
11933
|
`;
|
|
11823
11934
|
}
|
|
11935
|
+
content += `
|
|
11936
|
+
`;
|
|
11824
11937
|
content += `## Dependencies
|
|
11825
|
-
|
|
11826
|
-
${depsSection}
|
|
11827
11938
|
`;
|
|
11828
|
-
content +=
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11939
|
+
content += `
|
|
11940
|
+
`;
|
|
11941
|
+
content += `${depsSection}
|
|
11942
|
+
`;
|
|
11943
|
+
content += `
|
|
11944
|
+
`;
|
|
11945
|
+
content += `## Implementation Notes
|
|
11946
|
+
`;
|
|
11947
|
+
content += `
|
|
11948
|
+
`;
|
|
11949
|
+
content += `Implementation should follow Rust DDD + Clean Architecture patterns:
|
|
11950
|
+
`;
|
|
11951
|
+
content += `
|
|
11952
|
+
`;
|
|
11953
|
+
content += `1. Domain layer: aggregates, entities, value objects, repository interfaces
|
|
11954
|
+
`;
|
|
11955
|
+
content += `2. Infrastructure: Postgres repository implementations via SQLx
|
|
11956
|
+
`;
|
|
11957
|
+
content += `3. Application: use case orchestration
|
|
11958
|
+
`;
|
|
11959
|
+
content += `4. Interfaces: HTTP handlers with Axum
|
|
11960
|
+
`;
|
|
11961
|
+
content += `
|
|
11962
|
+
`;
|
|
11963
|
+
content += `Each layer MUST NOT leak into adjacent layers.
|
|
11964
|
+
`;
|
|
11965
|
+
content += `
|
|
11832
11966
|
`;
|
|
11833
11967
|
content += `## ADRs
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11968
|
+
`;
|
|
11969
|
+
content += `
|
|
11970
|
+
`;
|
|
11971
|
+
content += `None yet
|
|
11972
|
+
`;
|
|
11973
|
+
content += `
|
|
11837
11974
|
`;
|
|
11838
11975
|
}
|
|
11839
11976
|
if (!dryRun) {
|
package/package.json
CHANGED
|
@@ -456,6 +456,35 @@ class PipelineManager {
|
|
|
456
456
|
|
|
457
457
|
if (this.state.currentItemIndex >= this.state.items.length) {
|
|
458
458
|
this.state.status = "done";
|
|
459
|
+
|
|
460
|
+
// Auto-advance roadmap when module epic completes
|
|
461
|
+
const roadmapFile = join(this.cwd, ".pi/.guardian-roadmap-state.json");
|
|
462
|
+
if (existsSync(roadmapFile)) {
|
|
463
|
+
try {
|
|
464
|
+
const roadmap = JSON.parse(readFileSync(roadmapFile, "utf-8"));
|
|
465
|
+
const epicName = this.state.name;
|
|
466
|
+
const currentPhase = roadmap.phases?.[roadmap.currentPhaseIndex];
|
|
467
|
+
if (currentPhase && epicName) {
|
|
468
|
+
// Mark module as completed
|
|
469
|
+
const completed = new Set(currentPhase.completedModules || []);
|
|
470
|
+
completed.add(epicName);
|
|
471
|
+
currentPhase.completedModules = Array.from(completed);
|
|
472
|
+
|
|
473
|
+
// Check if all modules done → mark phase done
|
|
474
|
+
const allDone = currentPhase.modules.every(
|
|
475
|
+
(m: { name: string }) => completed.has(m.name),
|
|
476
|
+
);
|
|
477
|
+
if (allDone) {
|
|
478
|
+
currentPhase.status = "done";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
roadmap.updatedAt = new Date().toISOString();
|
|
482
|
+
writeFileSync(roadmapFile, JSON.stringify(roadmap, null, 2));
|
|
483
|
+
}
|
|
484
|
+
} catch (err) {
|
|
485
|
+
// Silently fail — roadmap update is best-effort
|
|
486
|
+
}
|
|
487
|
+
}
|
|
459
488
|
}
|
|
460
489
|
}
|
|
461
490
|
|