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.
Files changed (69) 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 +158 -7
  10. package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
  11. package/templates/pi/extensions/architect-lib/types.ts +44 -2
  12. package/templates/pi/extensions/architect.ts +102 -42
  13. package/templates/pi/extensions/ask-user-question.ts +2 -2
  14. package/templates/pi/extensions/bash-guard.ts +2 -2
  15. package/templates/pi/extensions/config-reload.ts +12 -5
  16. package/templates/pi/extensions/coordinator.ts +2 -2
  17. package/templates/pi/extensions/curator.ts +1 -1
  18. package/templates/pi/extensions/domain-explorer.ts +50 -3
  19. package/templates/pi/extensions/filechanges.ts +2 -2
  20. package/templates/pi/extensions/goal-loop.ts +3 -3
  21. package/templates/pi/extensions/hooks.ts +1 -1
  22. package/templates/pi/extensions/pi-env.d.ts +96 -0
  23. package/templates/pi/extensions/pipeline.ts +1 -1
  24. package/templates/pi/extensions/project-scaffolder.ts +1 -1
  25. package/templates/pi/extensions/read-only-mode.ts +5 -1
  26. package/templates/pi/extensions/tsconfig.json +9 -2
  27. package/templates/pi/preflight_report.json +1 -1
  28. package/templates/pi/prompts/ci-blueprint.md +2 -2
  29. package/templates/pi/scripts/check_model_judgment.sh +28 -0
  30. package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
  31. package/templates/pi/scripts/ci/run_preflight.sh +3 -3
  32. package/templates/pi/scripts/ci/run_stage.sh +1 -1
  33. package/templates/pi/scripts/ci/stage_lint.sh +1 -1
  34. package/templates/pi/scripts/coverage.sh +58 -0
  35. package/templates/pi/scripts/install_coverage_tools.sh +43 -0
  36. package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
  37. package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
  38. package/templates/pi/scripts/languages/go/coverage.sh +78 -0
  39. package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
  40. package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
  41. package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
  42. package/templates/pi/scripts/languages/java/coverage.sh +103 -0
  43. package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
  44. package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
  45. package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
  46. package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
  47. package/templates/pi/scripts/languages/python/coverage.sh +88 -0
  48. package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
  49. package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
  50. package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
  51. package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
  52. package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
  53. package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
  54. package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
  55. package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
  56. package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
  57. package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
  58. package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
  59. package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
  60. package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
  61. package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
  62. package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
  63. package/templates/pi/skills/agents/architecture-validator.md +28 -0
  64. package/templates/pi/skills/agents/test-validator.md +1 -1
  65. package/templates/pi/skills/go-enterprise-codegen.md +46 -0
  66. package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
  67. package/templates/pi/skills/python-enterprise-codegen.md +46 -0
  68. package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
  69. package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # coverage.sh — TypeScript/JavaScript: real line coverage via the project's
4
+ # coverage-enabled test runner
5
+ #
6
+ # Runs the test suite with coverage enabled, using the first runner that is
7
+ # actually installed: vitest → jest → bun test --coverage → c8. All of them
8
+ # emit a text table with an "All files" summary row; the "% Lines" column is
9
+ # parsed from it (header-driven — istanbul-style tables have 5 percentage
10
+ # columns, bun's has 2). No heuristic grep counting.
11
+ #
12
+ # Usage (normally via the .pi/scripts/coverage.sh dispatcher):
13
+ # bash coverage.sh # measure coverage
14
+ # bash coverage.sh --gate # fail if lines < COVERAGE_THRESHOLD
15
+ # bash coverage.sh --lcov # report an existing lcov artifact, if any
16
+ #
17
+ # Env:
18
+ # COVERAGE_THRESHOLD minimum line-coverage % (default: 60)
19
+ # ============================================================================
20
+ set -uo pipefail
21
+
22
+ COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-60}"
23
+ GATE=0
24
+ LCOV=0
25
+
26
+ for arg in "$@"; do
27
+ case "$arg" in
28
+ --gate) GATE=1 ;;
29
+ --lcov) LCOV=1 ;;
30
+ *) echo "unknown arg: $arg" >&2; exit 2 ;;
31
+ esac
32
+ done
33
+
34
+ # Walk up from CWD to the project root (package.json).
35
+ ROOT="$(pwd)"
36
+ while [ "$ROOT" != "/" ] && [ ! -f "$ROOT/package.json" ]; do
37
+ ROOT="$(dirname "$ROOT")"
38
+ done
39
+ if [ ! -f "$ROOT/package.json" ]; then
40
+ echo "✗ No package.json found in this directory tree" >&2
41
+ exit 1
42
+ fi
43
+ cd "$ROOT" || exit 1
44
+
45
+ OUT_FILE="$(mktemp)"
46
+ trap 'rm -f "$OUT_FILE"' EXIT
47
+
48
+ run_and_capture() {
49
+ # shellcheck disable=SC2015
50
+ "$@" >"$OUT_FILE" 2>&1 || true
51
+ }
52
+
53
+ echo "═══ Coverage (real test-runner coverage) ═══"
54
+ RUN=""
55
+ if [ -x "node_modules/.bin/vitest" ]; then
56
+ echo " runner: vitest"
57
+ run_and_capture node_modules/.bin/vitest run --coverage
58
+ RUN="vitest"
59
+ elif command -v vitest >/dev/null 2>&1; then
60
+ echo " runner: vitest (PATH)"
61
+ run_and_capture vitest run --coverage
62
+ RUN="vitest"
63
+ elif [ -x "node_modules/.bin/jest" ]; then
64
+ echo " runner: jest"
65
+ run_and_capture node_modules/.bin/jest --coverage
66
+ RUN="jest"
67
+ elif command -v jest >/dev/null 2>&1; then
68
+ echo " runner: jest (PATH)"
69
+ run_and_capture jest --coverage
70
+ RUN="jest"
71
+ elif command -v bun >/dev/null 2>&1; then
72
+ echo " runner: bun test --coverage"
73
+ run_and_capture bun test --coverage
74
+ RUN="bun"
75
+ elif [ -x "node_modules/.bin/c8" ]; then
76
+ # c8 (istanbul) wraps the project's own test command.
77
+ echo " runner: c8 (npm test)"
78
+ run_and_capture node_modules/.bin/c8 --reporter=text npm test
79
+ RUN="c8"
80
+ else
81
+ echo "✗ No coverage-enabled test runner found (vitest/jest/bun/c8)" >&2
82
+ echo " run .pi/scripts/install_coverage_tools.sh for guidance" >&2
83
+ exit 1
84
+ fi
85
+
86
+ # Parse the "% Lines" column of the "All files" row from the runner's text
87
+ # table. Header-driven: locate the "% Lines" column in the header row, then
88
+ # read it from the "All files" summary row (istanbul-style tables list
89
+ # % Stmts, % Branch, % Funcs, % Lines; bun lists % Funcs, % Lines).
90
+ COVERAGE="$(python3 - "$OUT_FILE" <<'PY'
91
+ import sys, re
92
+ with open(sys.argv[1], encoding="utf-8", errors="replace") as fh:
93
+ lines = [ln.strip() for ln in fh]
94
+
95
+ header = next((ln for ln in lines if ln.startswith("File") and "%" in ln), None)
96
+ allrow = next((ln for ln in lines if ln.startswith("All files")), None)
97
+ if not header or not allrow:
98
+ m = re.search(r"([0-9]+(?:\.[0-9]+)?)%", lines[-1] if lines else "")
99
+ print(m.group(1) if m else "")
100
+ raise SystemExit
101
+
102
+ cols = [c.strip() for c in header.split("|")]
103
+ idx = next(
104
+ (i for i, c in enumerate(cols) if c.replace("%", "").strip().lower() == "lines"),
105
+ None,
106
+ )
107
+ if idx is None:
108
+ idx = next((i for i, c in enumerate(cols) if c.startswith("%")), 1) + 1
109
+
110
+ cells = [c.strip() for c in allrow.split("|")]
111
+ if idx is not None and idx < len(cells):
112
+ val = cells[idx].replace("%", "").strip()
113
+ if val.replace(".", "", 1).isdigit():
114
+ print(val)
115
+ raise SystemExit
116
+ m = re.search(r"([0-9]+(?:\.[0-9]+)?)%", allrow)
117
+ print(m.group(1) if m else "")
118
+ PY
119
+ )"
120
+
121
+ tail -8 "$OUT_FILE"
122
+ echo ""
123
+ echo "Line coverage: ${COVERAGE:-unavailable}% (threshold: ${COVERAGE_THRESHOLD}%, runner: ${RUN})"
124
+
125
+ if [ -z "$COVERAGE" ]; then
126
+ echo "✗ Could not extract an 'All files' line-coverage percentage from ${RUN} output." >&2
127
+ echo " Ensure the runner is configured to emit a text coverage summary" >&2
128
+ echo " (vitest: coverage.reporter includes 'text'; jest: coverageReporters includes 'text')." >&2
129
+ exit 1
130
+ fi
131
+
132
+ if [ "$LCOV" -eq 1 ]; then
133
+ LCOV_FOUND=""
134
+ for cand in "coverage/lcov.info" "lcov.info"; do
135
+ if [ -f "$cand" ]; then
136
+ echo "lcov artifact found: $cand"
137
+ LCOV_FOUND=1
138
+ break
139
+ fi
140
+ done
141
+ if [ -z "$LCOV_FOUND" ]; then
142
+ echo "⚠️ no lcov.info artifact found — configure your runner's lcov reporter" >&2
143
+ fi
144
+ fi
145
+
146
+ if [ "$GATE" -eq 1 ]; then
147
+ if awk "BEGIN { exit !($COVERAGE >= $COVERAGE_THRESHOLD) }"; then
148
+ echo "✓ Coverage gate passed"
149
+ else
150
+ echo "✗ Coverage below threshold (${COVERAGE}% < ${COVERAGE_THRESHOLD}%)" >&2
151
+ exit 1
152
+ fi
153
+ fi
154
+ exit 0
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # install_coverage_tools.sh — TypeScript/JavaScript: install a real coverage
4
+ # tool
5
+ #
6
+ # The coverage gate (languages/typescript/coverage.sh) prefers the runner
7
+ # your project already uses (vitest → jest → bun → c8). This script installs
8
+ # whatever is missing for that chain:
9
+ # - vitest coverage needs the matching provider package
10
+ # - jest coverage is built-in (text reporter via config)
11
+ # - bun test --coverage is built into the Bun runtime (no install)
12
+ # - c8 is a universal fallback that wraps `npm test`
13
+ #
14
+ # Run this inside the project root (where package.json lives).
15
+ # ============================================================================
16
+ set -euo pipefail
17
+
18
+ if [ ! -f "package.json" ]; then
19
+ echo "✗ No package.json found in the current directory" >&2
20
+ exit 1
21
+ fi
22
+
23
+ if grep -q '"vitest"' package.json; then
24
+ if grep -q 'coverage-v8\|coverage-istanbul' package.json; then
25
+ echo "vitest + coverage provider present."
26
+ else
27
+ echo "vitest detected — installing @vitest/coverage-v8 (required for vitest --coverage)..."
28
+ npm install --save-dev @vitest/coverage-v8 \
29
+ || { echo "✗ install failed — run: npm install --save-dev @vitest/coverage-v8" >&2; exit 1; }
30
+ fi
31
+ elif grep -q '"jest"' package.json; then
32
+ echo "jest detected — enable 'coverageReporters': ['text', 'lcov'] in jest config for the coverage gate."
33
+ elif command -v bun >/dev/null 2>&1; then
34
+ echo "bun detected — 'bun test --coverage' is built in; no install needed."
35
+ else
36
+ echo "No vitest/jest/bun detected — installing c8 as a universal coverage runner..."
37
+ npm install --save-dev c8 \
38
+ || { echo "✗ install failed — run: npm install --save-dev c8" >&2; exit 1; }
39
+ fi
40
+
41
+ echo "TypeScript coverage tooling ready."
@@ -46,11 +46,15 @@ echo "--- Module-to-Implementation Mapping ---"
46
46
  if [ -d ".pi/architecture/modules" ]; then
47
47
  MAPPED=0
48
48
  UNMAPPED=0
49
+ # Modules are mapped by canonical reference headers in source files
50
+ # (// Canonical: .pi/architecture/modules/<module>.md) — with a filename
51
+ # heuristic fallback for repos that name directories after modules.
52
+ REFERRED=$(grep -rEho '\.pi/architecture/modules/[a-z0-9-]+\.md' "$SRC_DIR" --include="*.ts" 2>/dev/null | sort -u) || true
49
53
  for module_doc in .pi/architecture/modules/*.md; do
50
54
  [ -f "$module_doc" ] || continue
51
55
  MODULE_NAME=$(basename "$module_doc" .md)
52
- # Look for matching TS files (by convention: module name maps to src/<module>/ or similar)
53
- if find "$SRC_DIR" -type f \( -name "*.ts" -o -name "*.tsx" \) 2>/dev/null | grep -qi "$MODULE_NAME" 2>/dev/null; then
56
+ if echo "$REFERRED" | grep -q "$MODULE_NAME\.md" 2>/dev/null \
57
+ || find "$SRC_DIR" -type f \( -name "*.ts" -o -name "*.tsx" \) 2>/dev/null | grep -qi "$MODULE_NAME" 2>/dev/null; then
54
58
  MAPPED=$((MAPPED + 1))
55
59
  else
56
60
  UNMAPPED=$((UNMAPPED + 1))
@@ -152,13 +152,13 @@ echo ""
152
152
  echo "--- Format ---"
153
153
  if [ -f "biome.json" ]; then
154
154
  if command -v biome &>/dev/null; then
155
- if biome format . --check 2>/dev/null; then
155
+ if biome ci . 2>/dev/null; then
156
156
  pass "Format check passed (biome format)"
157
157
  else
158
158
  fail "Format check failed (biome format)"
159
159
  fi
160
160
  elif command -v npx &>/dev/null; then
161
- if npx biome format . --check 2>/dev/null; then
161
+ if npx biome ci . 2>/dev/null; then
162
162
  pass "Format check passed (npx biome format)"
163
163
  else
164
164
  fail "Format check failed (npx biome format)"
@@ -26,6 +26,7 @@ FAIL=0
26
26
  log_pass() { echo -e " ${GREEN}✓ PASS${NC} $1"; PASS=$((PASS + 1)); }
27
27
  log_fail() { echo -e " ${RED}✗ FAIL${NC} $1 — $2"; FAIL=$((FAIL + 1)); }
28
28
  log_skip() { echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2"; PASS=$((PASS + 1)); }
29
+ log_warn() { echo -e " ${YELLOW}⚠ WARN${NC} $1 — $2"; }
29
30
 
30
31
  echo "═══ Architecture Readiness Checks ═══"
31
32
  echo ""
@@ -134,6 +135,34 @@ else
134
135
  log_skip "Architecture conformance" "check_architecture_conformance.sh not found"
135
136
  fi
136
137
 
138
+
139
+ # ── 7. ADR-009: runtime wiring gates ──
140
+ # a) ADR-acceptance: an IMPLEMENTED module that documents runtime integration
141
+ # must not leave its referenced feature ADR Proposed.
142
+ echo "Checking ADR-acceptance + legacy-removal (ADR-009)..."
143
+ ADR_GATE_FAIL=0
144
+ for doc in .pi/architecture/modules/*.md; do
145
+ [ -f "$doc" ] || continue
146
+ if ! grep -q "status: implemented" "$doc"; then continue; fi
147
+ if ! grep -qE "^##{2,3}.*Integration|^#{2,3}.*Dispatch" "$doc"; then continue; fi
148
+ refs=$(grep -oE "ADR-[0-9]{3}" "$doc" | sort -u | tr "\n" " ")
149
+ for adr in $refs; do
150
+ adrfile=$(ls .pi/architecture/decisions/${adr}-*.md 2>/dev/null | head -1)
151
+ [ -n "$adrfile" ] || continue
152
+ if grep -q "\*\*Status:\*\* Proposed" "$adrfile" || grep -qE "^\*\*Status:\*\* *Proposed" "$adrfile"; then
153
+ log_fail "ADR-acceptance (ADR-009)" "${doc##*/} is implemented + documents runtime integration but ${adr} is still Proposed — wire it and Accept the ADR"
154
+ ADR_GATE_FAIL=1
155
+ fi
156
+ done
157
+ # b) legacy-removal warning (declared migration rule / legacy note)
158
+ if grep -qE "legacy .*still|still uses|GAP-.*migration" "$doc"; then
159
+ log_warn "legacy-removal (ADR-009)" "${doc##*/} declares a legacy path/migration rule — verify it is removed or delegating (validate-runtime-wiring.sh)"
160
+ fi
161
+ done
162
+ if [ $ADR_GATE_FAIL -eq 0 ]; then
163
+ log_pass "ADR-acceptance (ADR-009)" "no implemented module left a runtime-integration ADR Proposed"
164
+ fi
165
+
137
166
  # ── Summary ──
138
167
 
139
168
  echo ""
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # validate-exploration-depth.sh — ADR-008 modeling-depth rules over a
4
+ # domain-exploration session's structured result (JSON).
5
+ #
6
+ # Deterministic, machine-parseable checks (no LLM):
7
+ # depth-subdomain every bounded context has subdomainType + rationale
8
+ # depth-invariants invariant entries carry rule + illegalStatePrevented
9
+ # event-triage every domain event passes >=1 triage question
10
+ # roots-justified aggregate-root entities claim identity (identityPersists)
11
+ # ul-cross-context no term reused across contexts without a context-map edge
12
+ #
13
+ # Usage:
14
+ # bash .pi/scripts/validate-exploration-depth.sh [<session-id>]
15
+ # without id: validates the most recent *.raw.json session
16
+ # exit 0 = all pass ; exit 1 = violations (one '[rule-id] error:' per line)
17
+ # ============================================================================
18
+ set -uo pipefail
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
+ EXPLORATION_DIR="${1:-}"
22
+ RAW=""
23
+
24
+ if [ -z "$EXPLORATION_DIR" ]; then
25
+ # No session id given: locate the most recent session raw JSON.
26
+ RAW="$(ls -1t "${SCRIPT_DIR}/../domain/exploration/"*.raw.json 2>/dev/null | head -1)"
27
+ elif [[ "$EXPLORATION_DIR" == */* || "$EXPLORATION_DIR" == *.json ]]; then
28
+ RAW="$EXPLORATION_DIR"
29
+ else
30
+ RAW="${SCRIPT_DIR}/../domain/exploration/${EXPLORATION_DIR}.raw.json"
31
+ fi
32
+
33
+ if [ -z "$RAW" ] || [ ! -f "$RAW" ]; then
34
+ echo "no exploration session raw.json found (expected at .pi/domain/exploration/<session>.raw.json)" >&2
35
+ exit 1
36
+ fi
37
+
38
+ python3 - "$RAW" <<'PY'
39
+ import json, sys
40
+
41
+ path = sys.argv[1]
42
+ try:
43
+ with open(path, encoding="utf-8") as fh:
44
+ data = json.load(fh)
45
+ except Exception as exc: # noqa: BLE001
46
+ print(f"[json-invalid] error: {path} is not valid JSON — {exc}")
47
+ sys.exit(1)
48
+
49
+ errors = []
50
+
51
+ def err(rule, msg):
52
+ errors.append(f"[{rule}] error: {msg}")
53
+
54
+ # Rule 1: subdomain classification present
55
+ for bc in data.get("boundedContexts", []):
56
+ name = bc.get("name", "?")
57
+ st = bc.get("subdomainType")
58
+ if st not in ("core", "supporting", "generic"):
59
+ err("depth-subdomain", f"bounded context '{name}' missing subdomainType (core|supporting|generic)")
60
+ if not bc.get("subdomainRationale"):
61
+ err("depth-subdomain", f"bounded context '{name}' missing subdomainRationale")
62
+
63
+ # Rule 2: invariants well-formed
64
+ for bc in data.get("boundedContexts", []):
65
+ name = bc.get("name", "?")
66
+ for inv in bc.get("invariants", []) or []:
67
+ if not inv.get("rule"):
68
+ err("depth-invariants", f"context '{name}' has an invariant with an empty rule")
69
+ if not inv.get("illegalStatePrevented"):
70
+ err("depth-invariants", f"context '{name}' invariant '{inv.get('rule','')}' missing illegalStatePrevented")
71
+
72
+ # Rule 3: event triage (>=1 question answered yes)
73
+ for ev in data.get("domainEvents", []):
74
+ triage = ev.get("triage") or {}
75
+ hits = sum(bool(triage.get(k)) for k in ("domainExpertCares", "crossesAggregateBoundary", "needsAudit"))
76
+ if hits == 0:
77
+ err("event-triage", f"event '{ev.get('name','?')}' fails all triage questions (implementation detail, not a domain event)")
78
+
79
+ # Rule 4: aggregate roots claim identity
80
+ for ent in data.get("entities", []):
81
+ if ent.get("type") == "aggregate-root":
82
+ just = ent.get("justification") or {}
83
+ if not just.get("identityPersists"):
84
+ err("roots-justified", f"aggregate-root '{ent.get('name','?')}' must justify identityPersists=true")
85
+
86
+ # Rule 5: cross-context term reuse needs a context-map edge
87
+ term_contexts = {}
88
+ for t in data.get("ubiquitousLanguage", []):
89
+ term = t.get("term", "")
90
+ ctx = t.get("boundedContext", "")
91
+ if not term or not ctx:
92
+ continue
93
+ term_contexts.setdefault(term, set()).add(ctx)
94
+ edges = {
95
+ (e.get("from"), e.get("to"))
96
+ for e in data.get("contextMap", [])
97
+ if e.get("from") and e.get("to")
98
+ }
99
+ for term, contexts in term_contexts.items():
100
+ ctxs = sorted(contexts)
101
+ if len(ctxs) > 1:
102
+ connected = any((ctxs[0], c) in edges or (c, ctxs[0]) in edges for c in ctxs[1:])
103
+ if not connected:
104
+ err("ul-cross-context", f"term '{term}' used in contexts {', '.join(ctxs)} without a context-map edge or UL conflicts entry")
105
+
106
+ if errors:
107
+ print("\n".join(errors))
108
+ sys.exit(1)
109
+ print("depth validation passed (subdomain classification, invariants, event triage, aggregate roots, UL contexts)")
110
+ sys.exit(0)
111
+ PY
112
+ exit $?
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # validate-runtime-wiring.sh — ADR-009 production-reachability dispatcher
4
+ #
5
+ # Deterministic check: every application-layer service must be referenced from
6
+ # production code OUTSIDE its own module and OUTSIDE tests. A service whose
7
+ # only callers are its own tests is implemented-but-unwired (dead code with a
8
+ # test suite) — the recurring rigorix pattern ADR-009 fixes.
9
+ #
10
+ # Delegates to languages/<lang>/check_runtime_reachability.sh.
11
+ # Usage: bash .pi/scripts/validate-runtime-wiring.sh [--root <dir>]
12
+ # Exit: 0 all services reachable (or language not yet covered)
13
+ # 1 unreachable service(s) — one 'file: [runtime-reachability] ...' line each
14
+ # ============================================================================
15
+ set -uo pipefail
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ ROOT="$(pwd)"
18
+ if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
19
+ cd "$ROOT" || exit 1
20
+
21
+ detect_language() {
22
+ if [ -f "Cargo.toml" ]; then echo "rust"
23
+ elif [ -f "package.json" ]; then echo "typescript"
24
+ elif [ -f "go.mod" ]; then echo "go"
25
+ elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then echo "python"
26
+ elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then echo "java"
27
+ else echo "unknown"; fi
28
+ }
29
+ LANG="$(detect_language)"
30
+ SCRIPT="${SCRIPT_DIR}/languages/${LANG}/check_runtime_reachability.sh"
31
+ if [ -f "$SCRIPT" ]; then
32
+ exec bash "$SCRIPT" --root "$ROOT"
33
+ fi
34
+ echo "runtime-wiring: no reachability check for '${LANG}' yet (ADR-009 planned: go/python/java)"
35
+ exit 0
@@ -65,6 +65,34 @@ grep -r "registry.register" [src paths]
65
65
  grep -r "#\[from\]" [error file]
66
66
  ```
67
67
 
68
+
69
+ ## Model Judgment Review (ADR-008) — all languages
70
+
71
+ **MANDATORY** when the reviewed scope touches domain/application code. Record
72
+ each finding as WARN with `file:line: <finding>` — never a merge block on its
73
+ own; promotion to a gate requires a written decision after a trial. Evidence
74
+ commands are per-language; hard gates (deterministic) live in
75
+ `.pi/scripts/languages/<lang>/check_model_judgment.sh`.
76
+
77
+ - **R1 anemic-model** — app/application layer reads object state to decide
78
+ (`if (x.getStatus()…)`, `x.status =`, `x.State ==`) then mutates via
79
+ setters; business rules outside the domain object. Evidence per language:
80
+ Java/TS getter/setter chains; Go struct field pokes in services; Python
81
+ attribute mutation on domain objects; Rust `.field` writes / `set_*` from
82
+ services.
83
+ - **R2 LSP / fragile-base** — domain entities inherit from a non-abstract
84
+ base (Java/TS `extends`, Go embedding, Python subclassing, Rust — prefer
85
+ composition over inheritance, flag base-class aggregates).
86
+ - **R3 cohesion** — `Helper | Utils | Manager | Orchestrator`-style catch-all
87
+ classes/structs/types in the domain layer.
88
+ - **R4 primitive obsession** — >2 raw primitives on a domain service method
89
+ signature where a value object/struct/record should exist (IDs, money,
90
+ ranges, statuses).
91
+ - **R5 production reachability (ADR-009)** — an `implemented` application
92
+ service with no production caller outside its own module and outside tests
93
+ is dead code: report it and require the Runtime-Integration issue. Evidence:
94
+ `grep -rn "<ServiceName>" <src> | grep -v tests` per language.
95
+
68
96
  ## Output
69
97
  Use format from `.pi/context/output-formats.md` → "Validation Report"
70
98
 
@@ -17,7 +17,7 @@ You validate test coverage and quality.
17
17
  ## Checks
18
18
 
19
19
  1. **All tests pass** — Run full test suite
20
- 2. **Coverage ≥ 80%** — Run coverage tool
20
+ 2. **Coverage meets the project's real threshold** — Run the project's real coverage tool/gate (no per-module heuristic *_coverage.sh)
21
21
  3. **New code tested** — Every new function has tests
22
22
  4. **AAA pattern** — Arrange-Act-Assert structure
23
23
  5. **No flaky tests** — Deterministic, no timing dependencies
@@ -471,3 +471,49 @@ go.mod
471
471
  *Version: 1.0.0*
472
472
  *Last updated: 2026-07-03*
473
473
  *Source: Guardian DDD patterns + Clean Architecture + context7 (/wesionaryteam/go_clean_architecture, /golang/go)*
474
+
475
+ ## Judgment Tables (ADR-008)
476
+
477
+ Compact decision tables distilled from professional OOP/DDD practice (token-lean;
478
+ the full rationale lives in the curriculum, not here).
479
+
480
+ ### Entity vs Value Object
481
+ | Question | Entity | Value Object |
482
+ |----------|--------|--------------|
483
+ | Identity persists across state changes? | yes | no |
484
+ | Change = mutate in place? | yes | replace entirely |
485
+ | Equality by identity or by attributes? | identity | all attributes |
486
+ | Immutable by nature? | no | yes |
487
+ | Track change history? | yes | no |
488
+
489
+ ### Aggregate Boundaries
490
+ - An aggregate is a **consistency boundary**, not a data grouping.
491
+ - One aggregate modified per transaction; everything else via events.
492
+ - Reference other aggregates **by identity**, never by object graph.
493
+ - Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
494
+
495
+ ### Service Classifier
496
+ | Service | Layer | Contains | Named by |
497
+ |---------|-------|----------|----------|
498
+ | Domain service | domain/ | business process spanning entities | ubiquitous language |
499
+ | Application service | application/ | use-case orchestration (thin) | technical intent |
500
+ | Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
501
+ | Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
502
+ | Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
503
+
504
+ ### Event Triage (3 questions)
505
+ Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
506
+ (2) does it cross an aggregate boundary? (3) do we need an audit trail?
507
+ Otherwise it is an implementation detail. Publish from the aggregate;
508
+ dispatch **after** save from the application service.
509
+
510
+ ### Transaction & Persistence Placement
511
+ | Concern | Location | Why |
512
+ |---------|----------|-----|
513
+ | `sql.Tx` / transaction scope | application service only | one aggregate per transaction |
514
+ | Repository interface | domain/ package | dependency inversion |
515
+ | Repository impl (sqlx/gorm) | infrastructure/ | framework out of domain |
516
+ | Value object | domain/ (struct, immutable by convention) | replace, don't mutate |
517
+ | Wiring | explicit constructors in `cmd/` | no framework magic |
518
+
519
+ *Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*
@@ -540,3 +540,53 @@ pom.xml (or build.gradle)
540
540
  *Version: 1.0.0*
541
541
  *Last updated: 2026-07-03*
542
542
  *Source: Guardian DDD patterns + Spring Boot best practices + context7 (/jkazama/ddd-java, /websites/spring_io_spring-boot)*
543
+
544
+ ## 10. Judgment Tables (ADR-008)
545
+
546
+ Compact decision tables distilled from professional OOP/DDD/Spring practice
547
+ (keep token-lean — the full rationale lives in the curriculum, not here).
548
+
549
+ ### Entity vs Value Object
550
+ | Question | Entity | Value Object |
551
+ |----------|--------|--------------|
552
+ | Identity persists across state changes? | yes | no |
553
+ | Change = mutate in place? | yes | replace entirely |
554
+ | Equality by identity or by attributes? | identity | all attributes |
555
+ | Immutable by nature? | no | yes |
556
+ | Track change history? | yes | no |
557
+
558
+ ### Aggregate Boundaries
559
+ - An aggregate is a **consistency boundary**, not a data grouping.
560
+ - One aggregate modified per transaction; everything else via events.
561
+ - Reference other aggregates **by identity**, never by object graph.
562
+ - Design **small** aggregates (size by consistency, not relationships).
563
+ - Root is the only entry point — state changes via behavior methods (no setters).
564
+
565
+ ### Service Classifier
566
+ | Service | Layer | Contains | Named by |
567
+ |---------|-------|----------|----------|
568
+ | Domain service | domain/ | business process spanning entities | ubiquitous language |
569
+ | Application service | application/ | use-case orchestration (thin) | technical intent |
570
+ | Infrastructure service | infrastructure/ | technical impl (email, storage, publishing) | implementation |
571
+ | Repository (interface) | domain/ | aggregate persistence contract | aggregate language |
572
+ | Repository (impl) | infrastructure/ | JPA/ORM adapter | — |
573
+
574
+ ### Event Triage (3 questions)
575
+ Ask of every candidate event; keep it only if ≥1 is true:
576
+ 1. Would a domain expert care?
577
+ 2. Does it cross an aggregate boundary?
578
+ 3. Do we need an audit trail?
579
+ Otherwise it is an implementation detail, not a domain event.
580
+ Publish events from the aggregate (`releaseEvents()`), dispatch **after** save
581
+ from the application service.
582
+
583
+ ### Transaction & Persistence Placement
584
+ | Concern | Location | Why |
585
+ |---------|----------|-----|
586
+ | `@Transactional` | application service methods only | one aggregate per transaction |
587
+ | repository `save`/`find` | no `@Transactional` | don't open transactions per call |
588
+ | VO → `@Embeddable` | domain value objects | immutable + replaceable |
589
+ | Aggregate id | VO (`@EmbeddedId`) | type-safe identity |
590
+ | Optimistic lock | version field on aggregate | concurrency safety |
591
+
592
+ *Distilled 2026-09-03 from professional OOP/DDD/Spring curriculum (Modules 0–6) — ADR-008.*
@@ -401,3 +401,49 @@ pyproject.toml
401
401
  *Version: 1.0.0*
402
402
  *Last updated: 2026-07-03*
403
403
  *Source: Guardian DDD patterns + Python best practices + context7 (/fastapi/fastapi)*
404
+
405
+ ## Judgment Tables (ADR-008)
406
+
407
+ Compact decision tables distilled from professional OOP/DDD practice (token-lean;
408
+ the full rationale lives in the curriculum, not here).
409
+
410
+ ### Entity vs Value Object
411
+ | Question | Entity | Value Object |
412
+ |----------|--------|--------------|
413
+ | Identity persists across state changes? | yes | no |
414
+ | Change = mutate in place? | yes | replace entirely |
415
+ | Equality by identity or by attributes? | identity | all attributes |
416
+ | Immutable by nature? | no | yes |
417
+ | Track change history? | yes | no |
418
+
419
+ ### Aggregate Boundaries
420
+ - An aggregate is a **consistency boundary**, not a data grouping.
421
+ - One aggregate modified per transaction; everything else via events.
422
+ - Reference other aggregates **by identity**, never by object graph.
423
+ - Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
424
+
425
+ ### Service Classifier
426
+ | Service | Layer | Contains | Named by |
427
+ |---------|-------|----------|----------|
428
+ | Domain service | domain/ | business process spanning entities | ubiquitous language |
429
+ | Application service | application/ | use-case orchestration (thin) | technical intent |
430
+ | Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
431
+ | Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
432
+ | Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
433
+
434
+ ### Event Triage (3 questions)
435
+ Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
436
+ (2) does it cross an aggregate boundary? (3) do we need an audit trail?
437
+ Otherwise it is an implementation detail. Publish from the aggregate;
438
+ dispatch **after** save from the application service.
439
+
440
+ ### Transaction & Persistence Placement
441
+ | Concern | Location | Why |
442
+ |---------|----------|-----|
443
+ | Session / commit scope | application service only | one aggregate per transaction |
444
+ | Repository Protocol/ABC | domain/ | dependency inversion |
445
+ | Repository impl (SQLAlchemy/Django) | infrastructure/ | ORM out of domain |
446
+ | Value object | domain/ (dataclass frozen / attrs) | replace, don't mutate |
447
+ | Framework (FastAPI/Django) | interfaces/ | web out of domain |
448
+
449
+ *Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*