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.
- 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 +163 -12
- package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
- package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
- package/templates/pi/extensions/architect-lib/types.ts +44 -2
- package/templates/pi/extensions/architect.ts +105 -41
- 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/tdd-practice.md +17 -9
- package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
package/dist/exports.js
CHANGED
|
@@ -1894,7 +1894,12 @@ function getStoredHash(record) {
|
|
|
1894
1894
|
return hash ? hash.replace("sha256:", "") : null;
|
|
1895
1895
|
}
|
|
1896
1896
|
function categorizeFile(relativePath) {
|
|
1897
|
-
const userEditable = [
|
|
1897
|
+
const userEditable = [
|
|
1898
|
+
"agent/AGENTS.md",
|
|
1899
|
+
"domain/",
|
|
1900
|
+
"architecture/CHANGELOG.md",
|
|
1901
|
+
"architecture/implementation-roadmap.md"
|
|
1902
|
+
];
|
|
1898
1903
|
if (userEditable.some((p2) => relativePath.includes(p2))) {
|
|
1899
1904
|
return "user";
|
|
1900
1905
|
}
|
|
@@ -1982,7 +1987,7 @@ __export(exports_package, {
|
|
|
1982
1987
|
bin: () => bin,
|
|
1983
1988
|
author: () => author
|
|
1984
1989
|
});
|
|
1985
|
-
var name = "guardian-framework", version = "0.1.
|
|
1990
|
+
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;
|
|
1986
1991
|
var init_package = __esm(() => {
|
|
1987
1992
|
exports = {
|
|
1988
1993
|
".": {
|
|
@@ -11949,6 +11954,19 @@ function buildExplorationPrompt(context) {
|
|
|
11949
11954
|
const sanitized = sanitizeBusinessContext(context);
|
|
11950
11955
|
return `You are a Domain-Driven Design expert. Analyze the following business description and extract a structured domain model.
|
|
11951
11956
|
|
|
11957
|
+
Modeling judgment required (professional DDD practice):
|
|
11958
|
+
- Classify EVERY bounded context as a subdomain type: "core" (competitive advantage — full tactical modeling),
|
|
11959
|
+
"supporting" (needed but not differentiating — simple services), or "generic" (off-the-shelf problem — buy/OSS,
|
|
11960
|
+
never model). Add a one-sentence subdomainRationale for each.
|
|
11961
|
+
- For every entity/model entry decide entity vs value object vs aggregate root using the identity test:
|
|
11962
|
+
does it have identity that persists across state changes? Is it immutable + equal by attributes? Record the
|
|
11963
|
+
justification booleans, not just a label.
|
|
11964
|
+
- Capture per-context invariants: business rules that must always hold and the illegal state each prevents.
|
|
11965
|
+
- Triage EVERY event candidate with the 3-question test (domain expert cares? crosses an aggregate boundary?
|
|
11966
|
+
needs audit trail?). Drop candidates failing all three — they are implementation details, not domain events.
|
|
11967
|
+
- Draw the context map between bounded contexts (partnership / shared-kernel / conformist / anticorruption-layer / open-host).
|
|
11968
|
+
- Record ubiquitous-language conflicts: terms used differently by different parts of the business.
|
|
11969
|
+
|
|
11952
11970
|
Business Context:
|
|
11953
11971
|
${sanitized}
|
|
11954
11972
|
|
|
@@ -11992,7 +12010,20 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
11992
12010
|
{
|
|
11993
12011
|
"name": "ContextName",
|
|
11994
12012
|
"description": "Brief description of this bounded context",
|
|
11995
|
-
"entities": ["EntityName1", "EntityName2"]
|
|
12013
|
+
"entities": ["EntityName1", "EntityName2"],
|
|
12014
|
+
"subdomainType": "core|supporting|generic",
|
|
12015
|
+
"subdomainRationale": "why this subdomain classification",
|
|
12016
|
+
"invariants": [
|
|
12017
|
+
{ "rule": "business rule that must always hold", "illegalStatePrevented": "the illegal state this prevents" }
|
|
12018
|
+
]
|
|
12019
|
+
}
|
|
12020
|
+
],
|
|
12021
|
+
"contextMap": [
|
|
12022
|
+
{
|
|
12023
|
+
"from": "ContextName",
|
|
12024
|
+
"to": "OtherContextName",
|
|
12025
|
+
"relationship": "partnership|shared-kernel|conformist|anticorruption-layer|open-host",
|
|
12026
|
+
"note": "how the two contexts relate"
|
|
11996
12027
|
}
|
|
11997
12028
|
],
|
|
11998
12029
|
"entities": [
|
|
@@ -12000,7 +12031,12 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
12000
12031
|
"name": "EntityName",
|
|
12001
12032
|
"context": "ContextName",
|
|
12002
12033
|
"type": "entity|value-object|aggregate-root",
|
|
12003
|
-
"description": "What this entity represents"
|
|
12034
|
+
"description": "What this entity represents",
|
|
12035
|
+
"justification": {
|
|
12036
|
+
"identityPersists": true,
|
|
12037
|
+
"immutable": false,
|
|
12038
|
+
"equalByState": false
|
|
12039
|
+
}
|
|
12004
12040
|
}
|
|
12005
12041
|
],
|
|
12006
12042
|
"domainEvents": [
|
|
@@ -12008,7 +12044,12 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
12008
12044
|
"name": "EventName",
|
|
12009
12045
|
"context": "ContextName",
|
|
12010
12046
|
"description": "What happened",
|
|
12011
|
-
"triggeredBy": "What caused this event"
|
|
12047
|
+
"triggeredBy": "What caused this event",
|
|
12048
|
+
"triage": {
|
|
12049
|
+
"domainExpertCares": true,
|
|
12050
|
+
"crossesAggregateBoundary": false,
|
|
12051
|
+
"needsAudit": false
|
|
12052
|
+
}
|
|
12012
12053
|
}
|
|
12013
12054
|
],
|
|
12014
12055
|
"ubiquitousLanguage": [
|
|
@@ -12017,7 +12058,10 @@ Respond with ONLY valid JSON matching this exact structure. Do not include markd
|
|
|
12017
12058
|
"definition": "Clear definition",
|
|
12018
12059
|
"boundedContext": "ContextName",
|
|
12019
12060
|
"aliases": ["AlternativeName1"],
|
|
12020
|
-
"examples": "Usage example"
|
|
12061
|
+
"examples": "Usage example",
|
|
12062
|
+
"conflicts": [
|
|
12063
|
+
{ "termA": "Term used one way", "termB": "Same term used differently", "whyConflicting": "why the meanings diverge" }
|
|
12064
|
+
]
|
|
12021
12065
|
}
|
|
12022
12066
|
],
|
|
12023
12067
|
"openQuestions": "Any open questions about the domain",
|
|
@@ -12153,19 +12197,24 @@ function parseExplorationResponse(rawResponse, sessionId) {
|
|
|
12153
12197
|
boundedContexts: Array.isArray(parsed.boundedContexts) ? parsed.boundedContexts.map((bc) => ({
|
|
12154
12198
|
name: coerceString(bc.name, "Unknown"),
|
|
12155
12199
|
description: coerceString(bc.description, ""),
|
|
12156
|
-
entities: Array.isArray(bc.entities) ? bc.entities.map(String) : []
|
|
12200
|
+
entities: Array.isArray(bc.entities) ? bc.entities.map(String) : [],
|
|
12201
|
+
subdomainType: coerceSubdomainType(bc.subdomainType),
|
|
12202
|
+
subdomainRationale: coerceString(bc.subdomainRationale, ""),
|
|
12203
|
+
invariants: coerceInvariants(bc.invariants)
|
|
12157
12204
|
})) : [],
|
|
12158
12205
|
entities: Array.isArray(parsed.entities) ? parsed.entities.map((e) => ({
|
|
12159
12206
|
name: coerceString(e.name, "Unknown"),
|
|
12160
12207
|
context: coerceString(e.context, ""),
|
|
12161
12208
|
type: coerceEntityType(e.type),
|
|
12162
|
-
description: coerceString(e.description, "")
|
|
12209
|
+
description: coerceString(e.description, ""),
|
|
12210
|
+
justification: coerceJustification(e.justification)
|
|
12163
12211
|
})) : [],
|
|
12164
12212
|
domainEvents: Array.isArray(parsed.domainEvents) ? parsed.domainEvents.map((ev) => ({
|
|
12165
12213
|
name: coerceString(ev.name, "Unknown"),
|
|
12166
12214
|
context: coerceString(ev.context, ""),
|
|
12167
12215
|
description: coerceString(ev.description, ""),
|
|
12168
|
-
triggeredBy: coerceString(ev.triggeredBy, "")
|
|
12216
|
+
triggeredBy: coerceString(ev.triggeredBy, ""),
|
|
12217
|
+
triage: coerceTriage(ev.triage)
|
|
12169
12218
|
})) : [],
|
|
12170
12219
|
functionalRequirements: Array.isArray(parsed.functionalRequirements) ? parsed.functionalRequirements.map((fr) => ({
|
|
12171
12220
|
id: coerceString(fr.id, "FR-???"),
|
|
@@ -12189,10 +12238,21 @@ function parseExplorationResponse(rawResponse, sessionId) {
|
|
|
12189
12238
|
definition: coerceString(t2.definition, ""),
|
|
12190
12239
|
boundedContext: coerceString(t2.boundedContext, ""),
|
|
12191
12240
|
aliases: Array.isArray(t2.aliases) ? t2.aliases.map(String) : [],
|
|
12192
|
-
examples: coerceString(t2.examples, "")
|
|
12241
|
+
examples: coerceString(t2.examples, ""),
|
|
12242
|
+
conflicts: Array.isArray(t2.conflicts) ? t2.conflicts.map((c3) => ({
|
|
12243
|
+
termA: coerceString(c3.termA, ""),
|
|
12244
|
+
termB: coerceString(c3.termB, ""),
|
|
12245
|
+
whyConflicting: coerceString(c3.whyConflicting, "")
|
|
12246
|
+
})) : undefined
|
|
12193
12247
|
})) : [],
|
|
12194
12248
|
openQuestions: coerceString(parsed.openQuestions, ""),
|
|
12195
|
-
aggregateRoots: coerceString(parsed.aggregateRoots, "")
|
|
12249
|
+
aggregateRoots: coerceString(parsed.aggregateRoots, ""),
|
|
12250
|
+
contextMap: Array.isArray(parsed.contextMap) ? parsed.contextMap.map((e) => ({
|
|
12251
|
+
from: coerceString(e.from, ""),
|
|
12252
|
+
to: coerceString(e.to, ""),
|
|
12253
|
+
relationship: coerceContextRelationship(e.relationship),
|
|
12254
|
+
note: coerceString(e.note, "")
|
|
12255
|
+
})) : undefined
|
|
12196
12256
|
};
|
|
12197
12257
|
return { ok: true, value: result };
|
|
12198
12258
|
} catch (err) {
|
|
@@ -12228,6 +12288,49 @@ function coerceEntityType(val) {
|
|
|
12228
12288
|
return "app-shell";
|
|
12229
12289
|
return "entity";
|
|
12230
12290
|
}
|
|
12291
|
+
function coerceSubdomainType(val) {
|
|
12292
|
+
if (val === "core")
|
|
12293
|
+
return "core";
|
|
12294
|
+
if (val === "supporting")
|
|
12295
|
+
return "supporting";
|
|
12296
|
+
if (val === "generic")
|
|
12297
|
+
return "generic";
|
|
12298
|
+
return;
|
|
12299
|
+
}
|
|
12300
|
+
function coerceInvariants(val) {
|
|
12301
|
+
if (!Array.isArray(val))
|
|
12302
|
+
return;
|
|
12303
|
+
return val.map((i2) => ({
|
|
12304
|
+
rule: coerceString(i2.rule, ""),
|
|
12305
|
+
illegalStatePrevented: coerceString(i2.illegalStatePrevented, "")
|
|
12306
|
+
}));
|
|
12307
|
+
}
|
|
12308
|
+
function coerceJustification(val) {
|
|
12309
|
+
if (typeof val !== "object" || val === null)
|
|
12310
|
+
return;
|
|
12311
|
+
const j = val;
|
|
12312
|
+
return {
|
|
12313
|
+
identityPersists: Boolean(j.identityPersists),
|
|
12314
|
+
immutable: Boolean(j.immutable),
|
|
12315
|
+
equalByState: Boolean(j.equalByState)
|
|
12316
|
+
};
|
|
12317
|
+
}
|
|
12318
|
+
function coerceTriage(val) {
|
|
12319
|
+
if (typeof val !== "object" || val === null)
|
|
12320
|
+
return;
|
|
12321
|
+
const t2 = val;
|
|
12322
|
+
return {
|
|
12323
|
+
domainExpertCares: Boolean(t2.domainExpertCares),
|
|
12324
|
+
crossesAggregateBoundary: Boolean(t2.crossesAggregateBoundary),
|
|
12325
|
+
needsAudit: Boolean(t2.needsAudit)
|
|
12326
|
+
};
|
|
12327
|
+
}
|
|
12328
|
+
function coerceContextRelationship(val) {
|
|
12329
|
+
if (val === "partnership" || val === "shared-kernel" || val === "conformist" || val === "anticorruption-layer" || val === "open-host") {
|
|
12330
|
+
return val;
|
|
12331
|
+
}
|
|
12332
|
+
return "anticorruption-layer";
|
|
12333
|
+
}
|
|
12231
12334
|
function coerceFrPriority(val) {
|
|
12232
12335
|
if (val === "critical")
|
|
12233
12336
|
return "critical";
|
|
@@ -12370,6 +12473,14 @@ function answerExploration(sessionId, responseJson, options) {
|
|
|
12370
12473
|
function readExplorationSession(sessionId, projectDir) {
|
|
12371
12474
|
const root = projectDir ?? process.cwd();
|
|
12372
12475
|
const filePath = path11.join(root, ".pi", "domain", "exploration", `${sessionId}.md`);
|
|
12476
|
+
const rawPath = filePath.replace(/\.md$/, ".raw.json");
|
|
12477
|
+
if (fs12.existsSync(rawPath)) {
|
|
12478
|
+
try {
|
|
12479
|
+
const parsed = JSON.parse(fs12.readFileSync(rawPath, "utf-8"));
|
|
12480
|
+
if (parsed?.sessionId)
|
|
12481
|
+
return parsed;
|
|
12482
|
+
} catch {}
|
|
12483
|
+
}
|
|
12373
12484
|
if (!fs12.existsSync(filePath)) {
|
|
12374
12485
|
return null;
|
|
12375
12486
|
}
|
|
@@ -12553,6 +12664,10 @@ function scaffoldFromExploration(sessionId, options) {
|
|
|
12553
12664
|
const contextEntities = entitiesByContext[bc.name] || [];
|
|
12554
12665
|
const contextEvents = result.domainEvents.filter((ev) => ev.context === bc.name);
|
|
12555
12666
|
const contextTerms = result.ubiquitousLanguage.filter((t2) => t2.boundedContext === bc.name);
|
|
12667
|
+
const depth = bc.subdomainType === "core" ? "tactical" : bc.subdomainType === "generic" ? "adapter" : bc.subdomainType === "supporting" ? "service" : "unclassified";
|
|
12668
|
+
const depthHeaderNote = bc.subdomainType === undefined || bc.subdomainType === null ? " (unclassified — defaulted to tactical; re-run exploration with ADR-008 depth)" : "";
|
|
12669
|
+
const contextEdges = (result.contextMap || []).filter((e) => e.from === bc.name || e.to === bc.name);
|
|
12670
|
+
const invariants = bc.invariants || [];
|
|
12556
12671
|
if (!dryRun && fs12.existsSync(filePath)) {
|
|
12557
12672
|
warnings.push(`Module already exists, will be overwritten: ${fileName}`);
|
|
12558
12673
|
}
|
|
@@ -12809,6 +12924,17 @@ None yet
|
|
|
12809
12924
|
|
|
12810
12925
|
${bc.description}
|
|
12811
12926
|
|
|
12927
|
+
`;
|
|
12928
|
+
content += `
|
|
12929
|
+
`;
|
|
12930
|
+
content += "**Modeling Depth:** ";
|
|
12931
|
+
content += depth === "tactical" ? "tactical (core domain)" : depth === "service" ? "service (supporting subdomain)" : depth === "adapter" ? "adapter (generic subdomain)" : "unclassified";
|
|
12932
|
+
content += `${depthHeaderNote}
|
|
12933
|
+
`;
|
|
12934
|
+
content += `**Subdomain Rationale:** ${bc.subdomainRationale || "—"}
|
|
12935
|
+
`;
|
|
12936
|
+
const edgeSummary = contextEdges.map((e) => (e.from === bc.name ? `${e.relationship} → ${e.to}` : `${e.from} → ${e.relationship}`) + (e.note ? ` (${e.note})` : "")).join("; ");
|
|
12937
|
+
content += `**Context Map:** ${edgeSummary || "—"}
|
|
12812
12938
|
`;
|
|
12813
12939
|
content += `
|
|
12814
12940
|
`;
|
|
@@ -12889,15 +13015,22 @@ ${bc.description}
|
|
|
12889
13015
|
content += "```";
|
|
12890
13016
|
content += `
|
|
12891
13017
|
`;
|
|
12892
|
-
if (ownedEntities.length > 0 || ownedVOs.length > 0) {
|
|
13018
|
+
if (ownedEntities.length > 0 || ownedVOs.length > 0 || invariants.length > 0) {
|
|
12893
13019
|
content += `**Aggregate Invariants:**
|
|
12894
13020
|
`;
|
|
12895
|
-
|
|
13021
|
+
if (invariants.length > 0) {
|
|
13022
|
+
for (const inv of invariants) {
|
|
13023
|
+
content += `- ${inv.rule} (prevents: ${inv.illegalStatePrevented})
|
|
13024
|
+
`;
|
|
13025
|
+
}
|
|
13026
|
+
} else {
|
|
13027
|
+
content += `- All operations go through ${ar.name} aggregate root
|
|
12896
13028
|
`;
|
|
12897
|
-
|
|
13029
|
+
content += `- References to child entities resolve through ${ar.name}
|
|
12898
13030
|
`;
|
|
12899
|
-
|
|
13031
|
+
content += `- ${ar.name}Id is the sole external identifier
|
|
12900
13032
|
`;
|
|
13033
|
+
}
|
|
12901
13034
|
content += `
|
|
12902
13035
|
`;
|
|
12903
13036
|
}
|
|
@@ -12983,6 +13116,38 @@ ${bc.description}
|
|
|
12983
13116
|
`;
|
|
12984
13117
|
content += `
|
|
12985
13118
|
`;
|
|
13119
|
+
if (depth === "service" || depth === "adapter") {
|
|
13120
|
+
content += `## Modeling Depth Note (ADR-008)
|
|
13121
|
+
`;
|
|
13122
|
+
content += `
|
|
13123
|
+
`;
|
|
13124
|
+
if (depth === "service") {
|
|
13125
|
+
content += `This bounded context is a **supporting subdomain** — model at service/CRUD depth.
|
|
13126
|
+
`;
|
|
13127
|
+
content += `Keep implementation to application services + persistence. Do NOT introduce new
|
|
13128
|
+
`;
|
|
13129
|
+
content += `aggregates, value objects, or domain events beyond what this exploration captured.
|
|
13130
|
+
`;
|
|
13131
|
+
} else {
|
|
13132
|
+
content += `This bounded context is a **generic subdomain** — model at adapter depth.
|
|
13133
|
+
`;
|
|
13134
|
+
content += `Integrate an off-the-shelf / vendor / OSS solution behind an anti-corruption layer.
|
|
13135
|
+
`;
|
|
13136
|
+
content += `Do NOT write domain entities or aggregates here.
|
|
13137
|
+
`;
|
|
13138
|
+
}
|
|
13139
|
+
if (contextEdges.length > 0) {
|
|
13140
|
+
content += `
|
|
13141
|
+
**Anti-corruption edges:**
|
|
13142
|
+
`;
|
|
13143
|
+
for (const e of contextEdges) {
|
|
13144
|
+
content += `- ${e.from} ${e.relationship} ${e.to} — ${e.note || "—"}
|
|
13145
|
+
`;
|
|
13146
|
+
}
|
|
13147
|
+
}
|
|
13148
|
+
content += `
|
|
13149
|
+
`;
|
|
13150
|
+
}
|
|
12986
13151
|
content += `## Acceptance Criteria
|
|
12987
13152
|
`;
|
|
12988
13153
|
content += `
|
|
@@ -13040,6 +13205,11 @@ function loadExplorationTemplate() {
|
|
|
13040
13205
|
}
|
|
13041
13206
|
throw new Error("Exploration template not found in templates/pi/domain/exploration.md");
|
|
13042
13207
|
}
|
|
13208
|
+
function indentBlock(text2, indent = " ") {
|
|
13209
|
+
return text2.split(`
|
|
13210
|
+
`).map((ln) => ln.length > 0 ? indent + ln : ln).join(`
|
|
13211
|
+
`);
|
|
13212
|
+
}
|
|
13043
13213
|
function renderExplorationTemplate(result) {
|
|
13044
13214
|
const template = loadExplorationTemplate();
|
|
13045
13215
|
const actorsTable = result.actors.map((a2) => "| ${a.name} | ${a.description} | ${a.interactions} |").join(`
|
|
@@ -13058,9 +13228,51 @@ function renderExplorationTemplate(result) {
|
|
|
13058
13228
|
`);
|
|
13059
13229
|
const ubiquitousLanguageTable = result.ubiquitousLanguage.map((t2) => `| ${t2.term} | ${t2.definition} | ${t2.boundedContext} | ${t2.aliases.join(", ")} |`).join(`
|
|
13060
13230
|
`);
|
|
13231
|
+
const subdomainRows = result.boundedContexts.filter((bc) => bc.subdomainType || bc.invariants?.length).map((bc) => {
|
|
13232
|
+
const inv = (bc.invariants || []).map((i2) => `- ${i2.rule} (prevents: ${i2.illegalStatePrevented})`).join(`
|
|
13233
|
+
`) || "—";
|
|
13234
|
+
return `### ${bc.name}
|
|
13235
|
+
- **Subdomain:** ${bc.subdomainType ?? "unclassified"} — ${bc.subdomainRationale ?? ""}
|
|
13236
|
+
- **Invariants:**
|
|
13237
|
+
${indentBlock(inv)}`;
|
|
13238
|
+
}).join(`
|
|
13239
|
+
|
|
13240
|
+
`);
|
|
13241
|
+
const contextMapRows = (result.contextMap || []).map((e) => `| ${e.from} | ${e.to} | ${e.relationship} | ${e.note} |`).join(`
|
|
13242
|
+
`);
|
|
13243
|
+
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(`
|
|
13244
|
+
`);
|
|
13245
|
+
const ulConflictRows = result.ubiquitousLanguage.flatMap((t2) => (t2.conflicts || []).map((c3) => `| ${c3.termA} | ${c3.termB} | ${c3.whyConflicting} |`)).join(`
|
|
13246
|
+
`);
|
|
13247
|
+
const modelingParts = [];
|
|
13248
|
+
if (subdomainRows) {
|
|
13249
|
+
modelingParts.push(`### Subdomain Classification & Invariants
|
|
13250
|
+
${subdomainRows}`);
|
|
13251
|
+
}
|
|
13252
|
+
if (contextMapRows) {
|
|
13253
|
+
modelingParts.push(`### Context Map
|
|
13254
|
+
| From | To | Relationship | Note |
|
|
13255
|
+
|-----|----|--------------|------|
|
|
13256
|
+
${contextMapRows}`);
|
|
13257
|
+
}
|
|
13258
|
+
if (eventTriageRows) {
|
|
13259
|
+
modelingParts.push(`### Domain Event Triage
|
|
13260
|
+
| Event | Domain expert cares | Crosses aggregate boundary | Needs audit trail |
|
|
13261
|
+
|-------|---------------------|---------------------------|-------------------|
|
|
13262
|
+
${eventTriageRows}`);
|
|
13263
|
+
}
|
|
13264
|
+
if (ulConflictRows) {
|
|
13265
|
+
modelingParts.push(`### Ubiquitous Language Conflicts
|
|
13266
|
+
| Term A | Term B | Why conflicting |
|
|
13267
|
+
|--------|--------|-----------------|
|
|
13268
|
+
${ulConflictRows}`);
|
|
13269
|
+
}
|
|
13270
|
+
const modelingJudgmentSection = modelingParts.length > 0 ? modelingParts.join(`
|
|
13271
|
+
|
|
13272
|
+
`) : "None captured yet.";
|
|
13061
13273
|
const aggregateRoots = result.aggregateRoots ? result.aggregateRoots : "None identified yet.";
|
|
13062
13274
|
const openQuestions = result.openQuestions ? result.openQuestions : "No open questions.";
|
|
13063
|
-
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);
|
|
13275
|
+
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);
|
|
13064
13276
|
}
|
|
13065
13277
|
function escapePlaceholder(value) {
|
|
13066
13278
|
return value.replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
@@ -13215,6 +13427,10 @@ ${content}`;
|
|
|
13215
13427
|
const tempPath = `${outputPath}.tmp`;
|
|
13216
13428
|
fs12.writeFileSync(tempPath, content, "utf-8");
|
|
13217
13429
|
fs12.renameSync(tempPath, outputPath);
|
|
13430
|
+
const rawPath = `${outputPath.replace(/\.md$/, "")}.raw.json`;
|
|
13431
|
+
const rawTemp = `${rawPath}.tmp`;
|
|
13432
|
+
fs12.writeFileSync(rawTemp, JSON.stringify(result, null, 2), "utf-8");
|
|
13433
|
+
fs12.renameSync(rawTemp, rawPath);
|
|
13218
13434
|
return outputPath;
|
|
13219
13435
|
}
|
|
13220
13436
|
|
package/package.json
CHANGED
|
@@ -211,8 +211,11 @@ cargo fmt --check
|
|
|
211
211
|
# Security audit
|
|
212
212
|
cargo audit
|
|
213
213
|
|
|
214
|
-
# Coverage
|
|
215
|
-
|
|
214
|
+
# Coverage (real line coverage via cargo-llvm-cov — native LLVM
|
|
215
|
+
# instrumentation, ~3x faster than tarpaulin). Install once:
|
|
216
|
+
# bash .pi/scripts/install_coverage_tools.sh
|
|
217
|
+
# Gate in CI: bash .pi/scripts/coverage.sh --gate --lcov
|
|
218
|
+
cargo llvm-cov --workspace --summary-only
|
|
216
219
|
```
|
|
217
220
|
|
|
218
221
|
---
|
|
@@ -23,6 +23,20 @@
|
|
|
23
23
|
- [ ] E2E tests pass
|
|
24
24
|
- [ ] Architecture contract tests pass
|
|
25
25
|
|
|
26
|
+
|
|
27
|
+
### Model Judgment (Java, ADR-008) — hard gates
|
|
28
|
+
- [ ] txn-placement: @Transactional only in application/ (never domain/infrastructure/interfaces)
|
|
29
|
+
- [ ] dip-domain: domain/ imports no jakarta.persistence/javax.persistence/springframework/hibernate
|
|
30
|
+
- [ ] interface-ownership (tactical modules): every infrastructure/ repository has a domain/-owned interface
|
|
31
|
+
- [ ] adapter-purity: generic-subdomain modules contain no domain/application model code
|
|
32
|
+
|
|
33
|
+
### Model Judgment (Java, ADR-008) — review questions (validator agent, warn-level)
|
|
34
|
+
- [ ] R1 anemic-model: app-service if/getStatus/setX chains on domain refs (grep evidence)
|
|
35
|
+
- [ ] R2 LSP: domain entity extends a non-abstract base class
|
|
36
|
+
- [ ] R3 cohesion: Helper/Utils/Manager/Orchestrator classes in domain/
|
|
37
|
+
- [ ] R4 primitive obsession: >2 raw primitives on domain service signatures
|
|
38
|
+
- [ ] R5 production-reachability (ADR-009): implemented application services have a production caller outside their module and outside tests (validate-runtime-wiring.sh)
|
|
39
|
+
|
|
26
40
|
## Security Validation
|
|
27
41
|
|
|
28
42
|
### All Code Changes
|
|
@@ -56,7 +70,7 @@
|
|
|
56
70
|
|
|
57
71
|
### Coverage
|
|
58
72
|
- [ ] All tests pass
|
|
59
|
-
- [ ] Coverage
|
|
73
|
+
- [ ] Coverage meets the project's real coverage gate (run the project's coverage tool / CI coverage job — no per-module heuristic *_coverage.sh)
|
|
60
74
|
- [ ] All new code has tests
|
|
61
75
|
- [ ] Tests follow AAA pattern (Arrange-Act-Assert)
|
|
62
76
|
|
|
@@ -91,3 +91,12 @@ status: {{STATUS}}
|
|
|
91
91
|
## Aggregate Roots
|
|
92
92
|
|
|
93
93
|
{{AGGREGATE_ROOTS}}
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Modeling Judgment (ADR-008)
|
|
97
|
+
|
|
98
|
+
Subdomain classification (core/supporting/generic), aggregate invariants,
|
|
99
|
+
context map, domain-event triage, and ubiquitous-language conflicts.
|
|
100
|
+
|
|
101
|
+
{{MODELING_JUDGMENT_SECTION}}
|
|
102
|
+
|
|
@@ -22,3 +22,15 @@
|
|
|
22
22
|
|
|
23
23
|
> **Rule of thumb:** If two agents use different names for the same concept, add an entry.
|
|
24
24
|
> The canonical term is the one used in the architecture module documents.
|
|
25
|
+
|
|
26
|
+
## Term Conflicts (language gaps)
|
|
27
|
+
|
|
28
|
+
When the same term is used with different meanings by different parts of the
|
|
29
|
+
business (e.g. "transaction" = a database transaction for engineers but a
|
|
30
|
+
statement line for the business), that is a bounded-context boundary signal —
|
|
31
|
+
record the conflict here and resolve it in the exploration review:
|
|
32
|
+
|
|
33
|
+
| Term A | Term B | Why conflicting | Resolution |
|
|
34
|
+
|--------|--------|-----------------|-----------|
|
|
35
|
+
| <!-- e.g. Transaction | Booking | engineering vs business meaning | adopt "Booking" in Billing context --> |
|
|
36
|
+
|