guardian-framework 0.1.105 → 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 +158 -7
- package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
- package/templates/pi/extensions/architect-lib/types.ts +44 -2
- package/templates/pi/extensions/architect.ts +102 -42
- package/templates/pi/extensions/ask-user-question.ts +2 -2
- package/templates/pi/extensions/bash-guard.ts +2 -2
- package/templates/pi/extensions/config-reload.ts +12 -5
- package/templates/pi/extensions/coordinator.ts +2 -2
- package/templates/pi/extensions/curator.ts +1 -1
- package/templates/pi/extensions/domain-explorer.ts +50 -3
- package/templates/pi/extensions/filechanges.ts +2 -2
- package/templates/pi/extensions/goal-loop.ts +3 -3
- package/templates/pi/extensions/hooks.ts +1 -1
- package/templates/pi/extensions/pi-env.d.ts +96 -0
- package/templates/pi/extensions/pipeline.ts +1 -1
- package/templates/pi/extensions/project-scaffolder.ts +1 -1
- package/templates/pi/extensions/read-only-mode.ts +5 -1
- package/templates/pi/extensions/tsconfig.json +9 -2
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/prompts/ci-blueprint.md +2 -2
- package/templates/pi/scripts/check_model_judgment.sh +28 -0
- package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
- package/templates/pi/scripts/ci/run_preflight.sh +3 -3
- package/templates/pi/scripts/ci/run_stage.sh +1 -1
- package/templates/pi/scripts/ci/stage_lint.sh +1 -1
- package/templates/pi/scripts/coverage.sh +58 -0
- package/templates/pi/scripts/install_coverage_tools.sh +43 -0
- package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
- package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/go/coverage.sh +78 -0
- package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
- package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
- package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
- package/templates/pi/scripts/languages/java/coverage.sh +103 -0
- package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
- package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
- package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
- package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/python/coverage.sh +88 -0
- package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
- package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
- package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
- package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
- package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
- package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
- package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
- package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
- package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
- package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
- package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
- package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
- package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
- package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
- package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
- package/templates/pi/skills/agents/architecture-validator.md +28 -0
- package/templates/pi/skills/agents/test-validator.md +1 -1
- package/templates/pi/skills/go-enterprise-codegen.md +46 -0
- package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
- package/templates/pi/skills/python-enterprise-codegen.md +46 -0
- package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
- package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
|
@@ -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
|
-
|
|
53
|
-
|
|
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
|
|
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
|
|
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
|
|
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.*
|