guardian-framework 0.1.104 → 0.1.109

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.
Files changed (71) hide show
  1. package/README.md +11 -2
  2. package/dist/cli.js +232 -16
  3. package/dist/exports.js +232 -16
  4. package/package.json +1 -1
  5. package/templates/languages/rust-patterns.md +5 -2
  6. package/templates/pi/context/checklists.md +15 -1
  7. package/templates/pi/domain/exploration.md +9 -0
  8. package/templates/pi/domain/ubiquitous-language.md +12 -0
  9. package/templates/pi/extensions/architect-lib/generators.ts +163 -12
  10. package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
  11. package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
  12. package/templates/pi/extensions/architect-lib/types.ts +44 -2
  13. package/templates/pi/extensions/architect.ts +105 -41
  14. package/templates/pi/extensions/ask-user-question.ts +2 -2
  15. package/templates/pi/extensions/bash-guard.ts +2 -2
  16. package/templates/pi/extensions/config-reload.ts +12 -5
  17. package/templates/pi/extensions/coordinator.ts +2 -2
  18. package/templates/pi/extensions/curator.ts +1 -1
  19. package/templates/pi/extensions/domain-explorer.ts +50 -3
  20. package/templates/pi/extensions/filechanges.ts +2 -2
  21. package/templates/pi/extensions/goal-loop.ts +3 -3
  22. package/templates/pi/extensions/hooks.ts +1 -1
  23. package/templates/pi/extensions/pi-env.d.ts +96 -0
  24. package/templates/pi/extensions/pipeline.ts +1 -1
  25. package/templates/pi/extensions/project-scaffolder.ts +1 -1
  26. package/templates/pi/extensions/read-only-mode.ts +5 -1
  27. package/templates/pi/extensions/tsconfig.json +9 -2
  28. package/templates/pi/preflight_report.json +1 -1
  29. package/templates/pi/prompts/ci-blueprint.md +2 -2
  30. package/templates/pi/scripts/check_model_judgment.sh +28 -0
  31. package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
  32. package/templates/pi/scripts/ci/run_preflight.sh +3 -3
  33. package/templates/pi/scripts/ci/run_stage.sh +1 -1
  34. package/templates/pi/scripts/ci/stage_lint.sh +1 -1
  35. package/templates/pi/scripts/coverage.sh +58 -0
  36. package/templates/pi/scripts/install_coverage_tools.sh +43 -0
  37. package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
  38. package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
  39. package/templates/pi/scripts/languages/go/coverage.sh +78 -0
  40. package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
  41. package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
  42. package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
  43. package/templates/pi/scripts/languages/java/coverage.sh +103 -0
  44. package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
  45. package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
  46. package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
  47. package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
  48. package/templates/pi/scripts/languages/python/coverage.sh +88 -0
  49. package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
  50. package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
  51. package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
  52. package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
  53. package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
  54. package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
  55. package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
  56. package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
  57. package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
  58. package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
  59. package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
  60. package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
  61. package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
  62. package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
  63. package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
  64. package/templates/pi/skills/agents/architecture-validator.md +28 -0
  65. package/templates/pi/skills/agents/test-validator.md +1 -1
  66. package/templates/pi/skills/go-enterprise-codegen.md +46 -0
  67. package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
  68. package/templates/pi/skills/python-enterprise-codegen.md +46 -0
  69. package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
  70. package/templates/pi/skills/tdd-practice.md +17 -9
  71. 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 = ["agent/AGENTS.md", "domain/", "architecture/CHANGELOG.md", "architecture/implementation-roadmap.md"];
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.104", 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;
3205
+ var name = "guardian-framework", version = "0.1.109", 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
- content += `- All operations go through ${ar.name} aggregate root
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
- content += `- References to child entities resolve through ${ar.name}
12705
+ content += `- References to child entities resolve through ${ar.name}
12574
12706
  `;
12575
- content += `- ${ar.name}Id is the sole external identifier
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