guardian-framework 0.1.105 → 0.1.110
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 +11 -2
- package/dist/cli.js +232 -16
- package/dist/exports.js +232 -16
- package/package.json +1 -1
- package/templates/languages/rust-patterns.md +5 -2
- package/templates/pi/context/checklists.md +15 -1
- package/templates/pi/domain/exploration.md +9 -0
- package/templates/pi/domain/ubiquitous-language.md +12 -0
- package/templates/pi/extensions/architect-lib/generators.ts +158 -7
- package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
- package/templates/pi/extensions/architect-lib/types.ts +44 -2
- package/templates/pi/extensions/architect.ts +102 -42
- package/templates/pi/extensions/ask-user-question.ts +2 -2
- package/templates/pi/extensions/bash-guard.ts +2 -2
- package/templates/pi/extensions/config-reload.ts +12 -5
- package/templates/pi/extensions/coordinator.ts +2 -2
- package/templates/pi/extensions/curator.ts +1 -1
- package/templates/pi/extensions/domain-explorer.ts +50 -3
- package/templates/pi/extensions/filechanges.ts +2 -2
- package/templates/pi/extensions/goal-loop.ts +3 -3
- package/templates/pi/extensions/hooks.ts +1 -1
- package/templates/pi/extensions/pi-env.d.ts +96 -0
- package/templates/pi/extensions/pipeline.ts +1 -1
- package/templates/pi/extensions/project-scaffolder.ts +1 -1
- package/templates/pi/extensions/read-only-mode.ts +5 -1
- package/templates/pi/extensions/tsconfig.json +9 -2
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/prompts/ci-blueprint.md +2 -2
- package/templates/pi/scripts/check_model_judgment.sh +28 -0
- package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
- package/templates/pi/scripts/ci/run_preflight.sh +3 -3
- package/templates/pi/scripts/ci/run_stage.sh +1 -1
- package/templates/pi/scripts/ci/stage_lint.sh +1 -1
- package/templates/pi/scripts/coverage.sh +58 -0
- package/templates/pi/scripts/install_coverage_tools.sh +43 -0
- package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
- package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/go/coverage.sh +78 -0
- package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
- package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
- package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
- package/templates/pi/scripts/languages/java/coverage.sh +103 -0
- package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
- package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
- package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
- package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/python/coverage.sh +88 -0
- package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
- package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
- package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
- package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
- package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
- package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
- package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
- package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
- package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
- package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
- package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
- package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
- package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
- package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
- package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
- package/templates/pi/skills/agents/architecture-validator.md +28 -0
- package/templates/pi/skills/agents/test-validator.md +1 -1
- package/templates/pi/skills/go-enterprise-codegen.md +46 -0
- package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
- package/templates/pi/skills/python-enterprise-codegen.md +46 -0
- package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
- package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
package/README.md
CHANGED
|
@@ -72,6 +72,14 @@ Guardian analyzes your intent and produces a structured domain model. But this i
|
|
|
72
72
|
|
|
73
73
|
You go back and forth until the model reflects your actual business. Only then do you proceed.
|
|
74
74
|
|
|
75
|
+
Guardian now applies **proportional modeling depth** (ADR-008): every bounded
|
|
76
|
+
context is classified **core / supporting / generic** — the LLM proposes it,
|
|
77
|
+
you gate it — and must justify entity-vs-VO calls, capture aggregate
|
|
78
|
+
invariants, triage domain events, and draw the context map. Core subdomains
|
|
79
|
+
get full tactical DDD; generic subdomains get an anti-corruption layer, not a
|
|
80
|
+
hand-built model. Depth flows through module docs, contracts and validators
|
|
81
|
+
end-to-end. See the user manual §29.
|
|
82
|
+
|
|
75
83
|
### 3. Scaffold Architecture Modules
|
|
76
84
|
|
|
77
85
|
```
|
|
@@ -204,9 +212,10 @@ cd your-project
|
|
|
204
212
|
npx guardian-framework init
|
|
205
213
|
|
|
206
214
|
# 2. Explore (iterative — review, refine, repeat)
|
|
207
|
-
/domain --explore "your business intent"
|
|
215
|
+
/domain --explore "your business intent" # agent classifies core/supporting/generic — YOU gate it
|
|
216
|
+
/domain --validate <session-id> # structural + UL + ADR-008 depth rules
|
|
208
217
|
|
|
209
|
-
# 3. Generate architecture from exploration
|
|
218
|
+
# 3. Generate architecture from exploration (module docs gain Modeling Depth + invariants)
|
|
210
219
|
/domain --architect-scaffold <session-id>
|
|
211
220
|
|
|
212
221
|
# 4. Enrich module docs with full DDD content
|
package/dist/cli.js
CHANGED
|
@@ -3074,7 +3074,12 @@ function getStoredHash(record) {
|
|
|
3074
3074
|
return hash ? hash.replace("sha256:", "") : null;
|
|
3075
3075
|
}
|
|
3076
3076
|
function categorizeFile(relativePath) {
|
|
3077
|
-
const userEditable = [
|
|
3077
|
+
const userEditable = [
|
|
3078
|
+
"agent/AGENTS.md",
|
|
3079
|
+
"domain/",
|
|
3080
|
+
"architecture/CHANGELOG.md",
|
|
3081
|
+
"architecture/implementation-roadmap.md"
|
|
3082
|
+
];
|
|
3078
3083
|
if (userEditable.some((p3) => relativePath.includes(p3))) {
|
|
3079
3084
|
return "user";
|
|
3080
3085
|
}
|
|
@@ -3197,7 +3202,7 @@ __export(exports_package, {
|
|
|
3197
3202
|
bin: () => bin,
|
|
3198
3203
|
author: () => author
|
|
3199
3204
|
});
|
|
3200
|
-
var name = "guardian-framework", version = "0.1.
|
|
3205
|
+
var name = "guardian-framework", version = "0.1.110", 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;
|
|
3201
3206
|
var init_package = __esm(() => {
|
|
3202
3207
|
exports = {
|
|
3203
3208
|
".": {
|
|
@@ -11625,6 +11630,19 @@ function buildExplorationPrompt(context) {
|
|
|
11625
11630
|
const sanitized = sanitizeBusinessContext(context);
|
|
11626
11631
|
return `You are a Domain-Driven Design expert. Analyze the following business description and extract a structured domain model.
|
|
11627
11632
|
|
|
11633
|
+
Modeling judgment required (professional DDD practice):
|
|
11634
|
+
- Classify EVERY bounded context as a subdomain type: "core" (competitive advantage \u2014 full tactical modeling),
|
|
11635
|
+
"supporting" (needed but not differentiating \u2014 simple services), or "generic" (off-the-shelf problem \u2014 buy/OSS,
|
|
11636
|
+
never model). Add a one-sentence subdomainRationale for each.
|
|
11637
|
+
- For every entity/model entry decide entity vs value object vs aggregate root using the identity test:
|
|
11638
|
+
does it have identity that persists across state changes? Is it immutable + equal by attributes? Record the
|
|
11639
|
+
justification booleans, not just a label.
|
|
11640
|
+
- Capture per-context invariants: business rules that must always hold and the illegal state each prevents.
|
|
11641
|
+
- Triage EVERY event candidate with the 3-question test (domain expert cares? crosses an aggregate boundary?
|
|
11642
|
+
needs audit trail?). Drop candidates failing all three \u2014 they are implementation details, not domain events.
|
|
11643
|
+
- Draw the context map between bounded contexts (partnership / shared-kernel / conformist / anticorruption-layer / open-host).
|
|
11644
|
+
- Record ubiquitous-language conflicts: terms used differently by different parts of the business.
|
|
11645
|
+
|
|
11628
11646
|
Business Context:
|
|
11629
11647
|
${sanitized}
|
|
11630
11648
|
|
|
@@ -11668,7 +11686,20 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
11668
11686
|
{
|
|
11669
11687
|
"name": "ContextName",
|
|
11670
11688
|
"description": "Brief description of this bounded context",
|
|
11671
|
-
"entities": ["EntityName1", "EntityName2"]
|
|
11689
|
+
"entities": ["EntityName1", "EntityName2"],
|
|
11690
|
+
"subdomainType": "core|supporting|generic",
|
|
11691
|
+
"subdomainRationale": "why this subdomain classification",
|
|
11692
|
+
"invariants": [
|
|
11693
|
+
{ "rule": "business rule that must always hold", "illegalStatePrevented": "the illegal state this prevents" }
|
|
11694
|
+
]
|
|
11695
|
+
}
|
|
11696
|
+
],
|
|
11697
|
+
"contextMap": [
|
|
11698
|
+
{
|
|
11699
|
+
"from": "ContextName",
|
|
11700
|
+
"to": "OtherContextName",
|
|
11701
|
+
"relationship": "partnership|shared-kernel|conformist|anticorruption-layer|open-host",
|
|
11702
|
+
"note": "how the two contexts relate"
|
|
11672
11703
|
}
|
|
11673
11704
|
],
|
|
11674
11705
|
"entities": [
|
|
@@ -11676,7 +11707,12 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
11676
11707
|
"name": "EntityName",
|
|
11677
11708
|
"context": "ContextName",
|
|
11678
11709
|
"type": "entity|value-object|aggregate-root",
|
|
11679
|
-
"description": "What this entity represents"
|
|
11710
|
+
"description": "What this entity represents",
|
|
11711
|
+
"justification": {
|
|
11712
|
+
"identityPersists": true,
|
|
11713
|
+
"immutable": false,
|
|
11714
|
+
"equalByState": false
|
|
11715
|
+
}
|
|
11680
11716
|
}
|
|
11681
11717
|
],
|
|
11682
11718
|
"domainEvents": [
|
|
@@ -11684,7 +11720,12 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
11684
11720
|
"name": "EventName",
|
|
11685
11721
|
"context": "ContextName",
|
|
11686
11722
|
"description": "What happened",
|
|
11687
|
-
"triggeredBy": "What caused this event"
|
|
11723
|
+
"triggeredBy": "What caused this event",
|
|
11724
|
+
"triage": {
|
|
11725
|
+
"domainExpertCares": true,
|
|
11726
|
+
"crossesAggregateBoundary": false,
|
|
11727
|
+
"needsAudit": false
|
|
11728
|
+
}
|
|
11688
11729
|
}
|
|
11689
11730
|
],
|
|
11690
11731
|
"ubiquitousLanguage": [
|
|
@@ -11693,7 +11734,10 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
11693
11734
|
"definition": "Clear definition",
|
|
11694
11735
|
"boundedContext": "ContextName",
|
|
11695
11736
|
"aliases": ["AlternativeName1"],
|
|
11696
|
-
"examples": "Usage example"
|
|
11737
|
+
"examples": "Usage example",
|
|
11738
|
+
"conflicts": [
|
|
11739
|
+
{ "termA": "Term used one way", "termB": "Same term used differently", "whyConflicting": "why the meanings diverge" }
|
|
11740
|
+
]
|
|
11697
11741
|
}
|
|
11698
11742
|
],
|
|
11699
11743
|
"openQuestions": "Any open questions about the domain",
|
|
@@ -11829,19 +11873,24 @@ function parseExplorationResponse(rawResponse, sessionId) {
|
|
|
11829
11873
|
boundedContexts: Array.isArray(parsed.boundedContexts) ? parsed.boundedContexts.map((bc) => ({
|
|
11830
11874
|
name: coerceString(bc.name, "Unknown"),
|
|
11831
11875
|
description: coerceString(bc.description, ""),
|
|
11832
|
-
entities: Array.isArray(bc.entities) ? bc.entities.map(String) : []
|
|
11876
|
+
entities: Array.isArray(bc.entities) ? bc.entities.map(String) : [],
|
|
11877
|
+
subdomainType: coerceSubdomainType(bc.subdomainType),
|
|
11878
|
+
subdomainRationale: coerceString(bc.subdomainRationale, ""),
|
|
11879
|
+
invariants: coerceInvariants(bc.invariants)
|
|
11833
11880
|
})) : [],
|
|
11834
11881
|
entities: Array.isArray(parsed.entities) ? parsed.entities.map((e) => ({
|
|
11835
11882
|
name: coerceString(e.name, "Unknown"),
|
|
11836
11883
|
context: coerceString(e.context, ""),
|
|
11837
11884
|
type: coerceEntityType(e.type),
|
|
11838
|
-
description: coerceString(e.description, "")
|
|
11885
|
+
description: coerceString(e.description, ""),
|
|
11886
|
+
justification: coerceJustification(e.justification)
|
|
11839
11887
|
})) : [],
|
|
11840
11888
|
domainEvents: Array.isArray(parsed.domainEvents) ? parsed.domainEvents.map((ev) => ({
|
|
11841
11889
|
name: coerceString(ev.name, "Unknown"),
|
|
11842
11890
|
context: coerceString(ev.context, ""),
|
|
11843
11891
|
description: coerceString(ev.description, ""),
|
|
11844
|
-
triggeredBy: coerceString(ev.triggeredBy, "")
|
|
11892
|
+
triggeredBy: coerceString(ev.triggeredBy, ""),
|
|
11893
|
+
triage: coerceTriage(ev.triage)
|
|
11845
11894
|
})) : [],
|
|
11846
11895
|
functionalRequirements: Array.isArray(parsed.functionalRequirements) ? parsed.functionalRequirements.map((fr) => ({
|
|
11847
11896
|
id: coerceString(fr.id, "FR-???"),
|
|
@@ -11865,10 +11914,21 @@ function parseExplorationResponse(rawResponse, sessionId) {
|
|
|
11865
11914
|
definition: coerceString(t2.definition, ""),
|
|
11866
11915
|
boundedContext: coerceString(t2.boundedContext, ""),
|
|
11867
11916
|
aliases: Array.isArray(t2.aliases) ? t2.aliases.map(String) : [],
|
|
11868
|
-
examples: coerceString(t2.examples, "")
|
|
11917
|
+
examples: coerceString(t2.examples, ""),
|
|
11918
|
+
conflicts: Array.isArray(t2.conflicts) ? t2.conflicts.map((c3) => ({
|
|
11919
|
+
termA: coerceString(c3.termA, ""),
|
|
11920
|
+
termB: coerceString(c3.termB, ""),
|
|
11921
|
+
whyConflicting: coerceString(c3.whyConflicting, "")
|
|
11922
|
+
})) : undefined
|
|
11869
11923
|
})) : [],
|
|
11870
11924
|
openQuestions: coerceString(parsed.openQuestions, ""),
|
|
11871
|
-
aggregateRoots: coerceString(parsed.aggregateRoots, "")
|
|
11925
|
+
aggregateRoots: coerceString(parsed.aggregateRoots, ""),
|
|
11926
|
+
contextMap: Array.isArray(parsed.contextMap) ? parsed.contextMap.map((e) => ({
|
|
11927
|
+
from: coerceString(e.from, ""),
|
|
11928
|
+
to: coerceString(e.to, ""),
|
|
11929
|
+
relationship: coerceContextRelationship(e.relationship),
|
|
11930
|
+
note: coerceString(e.note, "")
|
|
11931
|
+
})) : undefined
|
|
11872
11932
|
};
|
|
11873
11933
|
return { ok: true, value: result };
|
|
11874
11934
|
} catch (err) {
|
|
@@ -11904,6 +11964,49 @@ function coerceEntityType(val) {
|
|
|
11904
11964
|
return "app-shell";
|
|
11905
11965
|
return "entity";
|
|
11906
11966
|
}
|
|
11967
|
+
function coerceSubdomainType(val) {
|
|
11968
|
+
if (val === "core")
|
|
11969
|
+
return "core";
|
|
11970
|
+
if (val === "supporting")
|
|
11971
|
+
return "supporting";
|
|
11972
|
+
if (val === "generic")
|
|
11973
|
+
return "generic";
|
|
11974
|
+
return;
|
|
11975
|
+
}
|
|
11976
|
+
function coerceInvariants(val) {
|
|
11977
|
+
if (!Array.isArray(val))
|
|
11978
|
+
return;
|
|
11979
|
+
return val.map((i2) => ({
|
|
11980
|
+
rule: coerceString(i2.rule, ""),
|
|
11981
|
+
illegalStatePrevented: coerceString(i2.illegalStatePrevented, "")
|
|
11982
|
+
}));
|
|
11983
|
+
}
|
|
11984
|
+
function coerceJustification(val) {
|
|
11985
|
+
if (typeof val !== "object" || val === null)
|
|
11986
|
+
return;
|
|
11987
|
+
const j = val;
|
|
11988
|
+
return {
|
|
11989
|
+
identityPersists: Boolean(j.identityPersists),
|
|
11990
|
+
immutable: Boolean(j.immutable),
|
|
11991
|
+
equalByState: Boolean(j.equalByState)
|
|
11992
|
+
};
|
|
11993
|
+
}
|
|
11994
|
+
function coerceTriage(val) {
|
|
11995
|
+
if (typeof val !== "object" || val === null)
|
|
11996
|
+
return;
|
|
11997
|
+
const t2 = val;
|
|
11998
|
+
return {
|
|
11999
|
+
domainExpertCares: Boolean(t2.domainExpertCares),
|
|
12000
|
+
crossesAggregateBoundary: Boolean(t2.crossesAggregateBoundary),
|
|
12001
|
+
needsAudit: Boolean(t2.needsAudit)
|
|
12002
|
+
};
|
|
12003
|
+
}
|
|
12004
|
+
function coerceContextRelationship(val) {
|
|
12005
|
+
if (val === "partnership" || val === "shared-kernel" || val === "conformist" || val === "anticorruption-layer" || val === "open-host") {
|
|
12006
|
+
return val;
|
|
12007
|
+
}
|
|
12008
|
+
return "anticorruption-layer";
|
|
12009
|
+
}
|
|
11907
12010
|
function coerceFrPriority(val) {
|
|
11908
12011
|
if (val === "critical")
|
|
11909
12012
|
return "critical";
|
|
@@ -12046,6 +12149,14 @@ function answerExploration(sessionId, responseJson, options) {
|
|
|
12046
12149
|
function readExplorationSession(sessionId, projectDir) {
|
|
12047
12150
|
const root = projectDir ?? process.cwd();
|
|
12048
12151
|
const filePath = path2.join(root, ".pi", "domain", "exploration", `${sessionId}.md`);
|
|
12152
|
+
const rawPath = filePath.replace(/\.md$/, ".raw.json");
|
|
12153
|
+
if (fs.existsSync(rawPath)) {
|
|
12154
|
+
try {
|
|
12155
|
+
const parsed = JSON.parse(fs.readFileSync(rawPath, "utf-8"));
|
|
12156
|
+
if (parsed?.sessionId)
|
|
12157
|
+
return parsed;
|
|
12158
|
+
} catch {}
|
|
12159
|
+
}
|
|
12049
12160
|
if (!fs.existsSync(filePath)) {
|
|
12050
12161
|
return null;
|
|
12051
12162
|
}
|
|
@@ -12229,6 +12340,10 @@ function scaffoldFromExploration(sessionId, options) {
|
|
|
12229
12340
|
const contextEntities = entitiesByContext[bc.name] || [];
|
|
12230
12341
|
const contextEvents = result.domainEvents.filter((ev) => ev.context === bc.name);
|
|
12231
12342
|
const contextTerms = result.ubiquitousLanguage.filter((t2) => t2.boundedContext === bc.name);
|
|
12343
|
+
const depth = bc.subdomainType === "core" ? "tactical" : bc.subdomainType === "generic" ? "adapter" : bc.subdomainType === "supporting" ? "service" : "unclassified";
|
|
12344
|
+
const depthHeaderNote = bc.subdomainType === undefined || bc.subdomainType === null ? " (unclassified \u2014 defaulted to tactical; re-run exploration with ADR-008 depth)" : "";
|
|
12345
|
+
const contextEdges = (result.contextMap || []).filter((e) => e.from === bc.name || e.to === bc.name);
|
|
12346
|
+
const invariants = bc.invariants || [];
|
|
12232
12347
|
if (!dryRun && fs.existsSync(filePath)) {
|
|
12233
12348
|
warnings.push(`Module already exists, will be overwritten: ${fileName}`);
|
|
12234
12349
|
}
|
|
@@ -12485,6 +12600,17 @@ None yet
|
|
|
12485
12600
|
|
|
12486
12601
|
${bc.description}
|
|
12487
12602
|
|
|
12603
|
+
`;
|
|
12604
|
+
content += `
|
|
12605
|
+
`;
|
|
12606
|
+
content += "**Modeling Depth:** ";
|
|
12607
|
+
content += depth === "tactical" ? "tactical (core domain)" : depth === "service" ? "service (supporting subdomain)" : depth === "adapter" ? "adapter (generic subdomain)" : "unclassified";
|
|
12608
|
+
content += `${depthHeaderNote}
|
|
12609
|
+
`;
|
|
12610
|
+
content += `**Subdomain Rationale:** ${bc.subdomainRationale || "\u2014"}
|
|
12611
|
+
`;
|
|
12612
|
+
const edgeSummary = contextEdges.map((e) => (e.from === bc.name ? `${e.relationship} \u2192 ${e.to}` : `${e.from} \u2192 ${e.relationship}`) + (e.note ? ` (${e.note})` : "")).join("; ");
|
|
12613
|
+
content += `**Context Map:** ${edgeSummary || "\u2014"}
|
|
12488
12614
|
`;
|
|
12489
12615
|
content += `
|
|
12490
12616
|
`;
|
|
@@ -12565,15 +12691,22 @@ ${bc.description}
|
|
|
12565
12691
|
content += "```";
|
|
12566
12692
|
content += `
|
|
12567
12693
|
`;
|
|
12568
|
-
if (ownedEntities.length > 0 || ownedVOs.length > 0) {
|
|
12694
|
+
if (ownedEntities.length > 0 || ownedVOs.length > 0 || invariants.length > 0) {
|
|
12569
12695
|
content += `**Aggregate Invariants:**
|
|
12570
12696
|
`;
|
|
12571
|
-
|
|
12697
|
+
if (invariants.length > 0) {
|
|
12698
|
+
for (const inv of invariants) {
|
|
12699
|
+
content += `- ${inv.rule} (prevents: ${inv.illegalStatePrevented})
|
|
12700
|
+
`;
|
|
12701
|
+
}
|
|
12702
|
+
} else {
|
|
12703
|
+
content += `- All operations go through ${ar.name} aggregate root
|
|
12572
12704
|
`;
|
|
12573
|
-
|
|
12705
|
+
content += `- References to child entities resolve through ${ar.name}
|
|
12574
12706
|
`;
|
|
12575
|
-
|
|
12707
|
+
content += `- ${ar.name}Id is the sole external identifier
|
|
12576
12708
|
`;
|
|
12709
|
+
}
|
|
12577
12710
|
content += `
|
|
12578
12711
|
`;
|
|
12579
12712
|
}
|
|
@@ -12659,6 +12792,38 @@ ${bc.description}
|
|
|
12659
12792
|
`;
|
|
12660
12793
|
content += `
|
|
12661
12794
|
`;
|
|
12795
|
+
if (depth === "service" || depth === "adapter") {
|
|
12796
|
+
content += `## Modeling Depth Note (ADR-008)
|
|
12797
|
+
`;
|
|
12798
|
+
content += `
|
|
12799
|
+
`;
|
|
12800
|
+
if (depth === "service") {
|
|
12801
|
+
content += `This bounded context is a **supporting subdomain** \u2014 model at service/CRUD depth.
|
|
12802
|
+
`;
|
|
12803
|
+
content += `Keep implementation to application services + persistence. Do NOT introduce new
|
|
12804
|
+
`;
|
|
12805
|
+
content += `aggregates, value objects, or domain events beyond what this exploration captured.
|
|
12806
|
+
`;
|
|
12807
|
+
} else {
|
|
12808
|
+
content += `This bounded context is a **generic subdomain** \u2014 model at adapter depth.
|
|
12809
|
+
`;
|
|
12810
|
+
content += `Integrate an off-the-shelf / vendor / OSS solution behind an anti-corruption layer.
|
|
12811
|
+
`;
|
|
12812
|
+
content += `Do NOT write domain entities or aggregates here.
|
|
12813
|
+
`;
|
|
12814
|
+
}
|
|
12815
|
+
if (contextEdges.length > 0) {
|
|
12816
|
+
content += `
|
|
12817
|
+
**Anti-corruption edges:**
|
|
12818
|
+
`;
|
|
12819
|
+
for (const e of contextEdges) {
|
|
12820
|
+
content += `- ${e.from} ${e.relationship} ${e.to} \u2014 ${e.note || "\u2014"}
|
|
12821
|
+
`;
|
|
12822
|
+
}
|
|
12823
|
+
}
|
|
12824
|
+
content += `
|
|
12825
|
+
`;
|
|
12826
|
+
}
|
|
12662
12827
|
content += `## Acceptance Criteria
|
|
12663
12828
|
`;
|
|
12664
12829
|
content += `
|
|
@@ -12716,6 +12881,11 @@ function loadExplorationTemplate() {
|
|
|
12716
12881
|
}
|
|
12717
12882
|
throw new Error("Exploration template not found in templates/pi/domain/exploration.md");
|
|
12718
12883
|
}
|
|
12884
|
+
function indentBlock(text2, indent = " ") {
|
|
12885
|
+
return text2.split(`
|
|
12886
|
+
`).map((ln) => ln.length > 0 ? indent + ln : ln).join(`
|
|
12887
|
+
`);
|
|
12888
|
+
}
|
|
12719
12889
|
function renderExplorationTemplate(result) {
|
|
12720
12890
|
const template = loadExplorationTemplate();
|
|
12721
12891
|
const actorsTable = result.actors.map((a2) => "| ${a.name} | ${a.description} | ${a.interactions} |").join(`
|
|
@@ -12734,9 +12904,51 @@ function renderExplorationTemplate(result) {
|
|
|
12734
12904
|
`);
|
|
12735
12905
|
const ubiquitousLanguageTable = result.ubiquitousLanguage.map((t2) => `| ${t2.term} | ${t2.definition} | ${t2.boundedContext} | ${t2.aliases.join(", ")} |`).join(`
|
|
12736
12906
|
`);
|
|
12907
|
+
const subdomainRows = result.boundedContexts.filter((bc) => bc.subdomainType || bc.invariants?.length).map((bc) => {
|
|
12908
|
+
const inv = (bc.invariants || []).map((i2) => `- ${i2.rule} (prevents: ${i2.illegalStatePrevented})`).join(`
|
|
12909
|
+
`) || "\u2014";
|
|
12910
|
+
return `### ${bc.name}
|
|
12911
|
+
- **Subdomain:** ${bc.subdomainType ?? "unclassified"} \u2014 ${bc.subdomainRationale ?? ""}
|
|
12912
|
+
- **Invariants:**
|
|
12913
|
+
${indentBlock(inv)}`;
|
|
12914
|
+
}).join(`
|
|
12915
|
+
|
|
12916
|
+
`);
|
|
12917
|
+
const contextMapRows = (result.contextMap || []).map((e) => `| ${e.from} | ${e.to} | ${e.relationship} | ${e.note} |`).join(`
|
|
12918
|
+
`);
|
|
12919
|
+
const eventTriageRows = result.domainEvents.filter((ev) => ev.triage).map((ev) => `| ${ev.name} | ${ev.triage.domainExpertCares ? "yes" : "no"} | ${ev.triage.crossesAggregateBoundary ? "yes" : "no"} | ${ev.triage.needsAudit ? "yes" : "no"} |`).join(`
|
|
12920
|
+
`);
|
|
12921
|
+
const ulConflictRows = result.ubiquitousLanguage.flatMap((t2) => (t2.conflicts || []).map((c3) => `| ${c3.termA} | ${c3.termB} | ${c3.whyConflicting} |`)).join(`
|
|
12922
|
+
`);
|
|
12923
|
+
const modelingParts = [];
|
|
12924
|
+
if (subdomainRows) {
|
|
12925
|
+
modelingParts.push(`### Subdomain Classification & Invariants
|
|
12926
|
+
${subdomainRows}`);
|
|
12927
|
+
}
|
|
12928
|
+
if (contextMapRows) {
|
|
12929
|
+
modelingParts.push(`### Context Map
|
|
12930
|
+
| From | To | Relationship | Note |
|
|
12931
|
+
|-----|----|--------------|------|
|
|
12932
|
+
${contextMapRows}`);
|
|
12933
|
+
}
|
|
12934
|
+
if (eventTriageRows) {
|
|
12935
|
+
modelingParts.push(`### Domain Event Triage
|
|
12936
|
+
| Event | Domain expert cares | Crosses aggregate boundary | Needs audit trail |
|
|
12937
|
+
|-------|---------------------|---------------------------|-------------------|
|
|
12938
|
+
${eventTriageRows}`);
|
|
12939
|
+
}
|
|
12940
|
+
if (ulConflictRows) {
|
|
12941
|
+
modelingParts.push(`### Ubiquitous Language Conflicts
|
|
12942
|
+
| Term A | Term B | Why conflicting |
|
|
12943
|
+
|--------|--------|-----------------|
|
|
12944
|
+
${ulConflictRows}`);
|
|
12945
|
+
}
|
|
12946
|
+
const modelingJudgmentSection = modelingParts.length > 0 ? modelingParts.join(`
|
|
12947
|
+
|
|
12948
|
+
`) : "None captured yet.";
|
|
12737
12949
|
const aggregateRoots = result.aggregateRoots ? result.aggregateRoots : "None identified yet.";
|
|
12738
12950
|
const openQuestions = result.openQuestions ? result.openQuestions : "No open questions.";
|
|
12739
|
-
return template.replaceAll("{{SESSION_ID}}", result.sessionId).replaceAll("{{CREATED_DATE}}", new Date().toISOString().split("T")[0]).replaceAll("{{BUSINESS_CONTEXT}}", escapePlaceholder(result.businessContext)).replaceAll("{{STATUS}}", result.status).replaceAll("{{ACTORS_TABLE}}", actorsTable || "None identified yet.").replaceAll("{{FUNCTIONAL_REQUIREMENTS_TABLE}}", functionalRequirementsTable || "None identified yet.").replaceAll("{{NON_FUNCTIONAL_REQUIREMENTS_TABLE}}", nonFunctionalRequirementsTable || "None identified yet.").replaceAll("{{ASSUMPTIONS_TABLE}}", assumptionsTable || "None identified yet.").replaceAll("{{BOUNDED_CONTEXTS_TABLE}}", boundedContextsTable || "None identified yet.").replaceAll("{{ENTITIES_TABLE}}", entitiesTable || "None identified yet.").replaceAll("{{DOMAIN_EVENTS_TABLE}}", domainEventsTable || "None identified yet.").replaceAll("{{UBIQUITOUS_LANGUAGE_TABLE}}", ubiquitousLanguageTable || "None identified yet.").replaceAll("{{OPEN_QUESTIONS}}", openQuestions).replaceAll("{{AGGREGATE_ROOTS}}", aggregateRoots);
|
|
12951
|
+
return template.replaceAll("{{SESSION_ID}}", result.sessionId).replaceAll("{{CREATED_DATE}}", new Date().toISOString().split("T")[0]).replaceAll("{{BUSINESS_CONTEXT}}", escapePlaceholder(result.businessContext)).replaceAll("{{STATUS}}", result.status).replaceAll("{{ACTORS_TABLE}}", actorsTable || "None identified yet.").replaceAll("{{FUNCTIONAL_REQUIREMENTS_TABLE}}", functionalRequirementsTable || "None identified yet.").replaceAll("{{NON_FUNCTIONAL_REQUIREMENTS_TABLE}}", nonFunctionalRequirementsTable || "None identified yet.").replaceAll("{{ASSUMPTIONS_TABLE}}", assumptionsTable || "None identified yet.").replaceAll("{{BOUNDED_CONTEXTS_TABLE}}", boundedContextsTable || "None identified yet.").replaceAll("{{ENTITIES_TABLE}}", entitiesTable || "None identified yet.").replaceAll("{{DOMAIN_EVENTS_TABLE}}", domainEventsTable || "None identified yet.").replaceAll("{{UBIQUITOUS_LANGUAGE_TABLE}}", ubiquitousLanguageTable || "None identified yet.").replaceAll("{{OPEN_QUESTIONS}}", openQuestions).replaceAll("{{AGGREGATE_ROOTS}}", aggregateRoots).replaceAll("{{MODELING_JUDGMENT_SECTION}}", modelingJudgmentSection);
|
|
12740
12952
|
}
|
|
12741
12953
|
function escapePlaceholder(value) {
|
|
12742
12954
|
return value.replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
@@ -12891,6 +13103,10 @@ ${content}`;
|
|
|
12891
13103
|
const tempPath = `${outputPath}.tmp`;
|
|
12892
13104
|
fs.writeFileSync(tempPath, content, "utf-8");
|
|
12893
13105
|
fs.renameSync(tempPath, outputPath);
|
|
13106
|
+
const rawPath = `${outputPath.replace(/\.md$/, "")}.raw.json`;
|
|
13107
|
+
const rawTemp = `${rawPath}.tmp`;
|
|
13108
|
+
fs.writeFileSync(rawTemp, JSON.stringify(result, null, 2), "utf-8");
|
|
13109
|
+
fs.renameSync(rawTemp, rawPath);
|
|
12894
13110
|
return outputPath;
|
|
12895
13111
|
}
|
|
12896
13112
|
|