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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_model_judgment_java.sh — ADR-008 Java model-judgment HARD GATES
|
|
4
|
+
#
|
|
5
|
+
# Deterministic, language-scoped checks (contract §5). Fuzzy smells (anemic
|
|
6
|
+
# model, LSP base classes, Helper/Utils, primitive obsession) are NOT gated
|
|
7
|
+
# here — they surface as architecture-validator review questions instead.
|
|
8
|
+
#
|
|
9
|
+
# Rules (stable ids):
|
|
10
|
+
# txn-placement @Transactional outside the application layer
|
|
11
|
+
# dip-domain domain/ imports jakarta/javax.persistence,
|
|
12
|
+
# springframework or hibernate
|
|
13
|
+
# interface-ownership (tactical modules) repository implementation in
|
|
14
|
+
# infrastructure/ without a domain/-owned interface
|
|
15
|
+
# adapter-purity (adapter modules) production java under domain/ or
|
|
16
|
+
# application/ of a generic subdomain
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# bash check_model_judgment_java.sh [--root <dir>] (default: cwd)
|
|
20
|
+
# Output: one 'file:line: [rule-id] message' per violation.
|
|
21
|
+
# Exit: 0 all pass ; 1 violations found.
|
|
22
|
+
# ============================================================================
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
ROOT="$(pwd)"
|
|
26
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then
|
|
27
|
+
ROOT="$2"
|
|
28
|
+
fi
|
|
29
|
+
cd "$ROOT" || exit 1
|
|
30
|
+
|
|
31
|
+
VIOLATIONS=0
|
|
32
|
+
|
|
33
|
+
report() { # file line rule message
|
|
34
|
+
echo "$1:$2: [$3] $4"
|
|
35
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
is_prod_java() { # path
|
|
39
|
+
case "$1" in
|
|
40
|
+
*/test/*|*/src/test/*) return 1 ;;
|
|
41
|
+
*Test.java|*Tests.java) return 1 ;;
|
|
42
|
+
esac
|
|
43
|
+
return 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# ── Rule 2: DIP — domain imports framework types ─────────────────────────────
|
|
47
|
+
while IFS= read -r f; do
|
|
48
|
+
[ -n "$f" ] || continue
|
|
49
|
+
is_prod_java "$f" || continue
|
|
50
|
+
case "$f" in
|
|
51
|
+
*/domain/*)
|
|
52
|
+
while IFS=: read -r ln rest; do
|
|
53
|
+
case "$rest" in
|
|
54
|
+
*jakarta.persistence*|*javax.persistence*|*org.springframework*|*org.hibernate*)
|
|
55
|
+
imp="$(echo "$rest" | sed 's/^[[:space:]]*import[[:space:]]*//; s/;//')"
|
|
56
|
+
report "$f" "$ln" "dip-domain" "domain layer imports framework type: $imp"
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
done < <(grep -nE '^[[:space:]]*import[[:space:]]+' "$f" || true)
|
|
60
|
+
;;
|
|
61
|
+
esac
|
|
62
|
+
done < <(find src -type f -name "*.java" 2>/dev/null || true)
|
|
63
|
+
|
|
64
|
+
# ── Rule 1: transactions only in the application layer ───────────────────────
|
|
65
|
+
while IFS= read -r f; do
|
|
66
|
+
[ -n "$f" ] || continue
|
|
67
|
+
is_prod_java "$f" || continue
|
|
68
|
+
case "$f" in
|
|
69
|
+
*/application/*) continue ;;
|
|
70
|
+
esac
|
|
71
|
+
while IFS=: read -r ln rest; do
|
|
72
|
+
case "$rest" in
|
|
73
|
+
*@Transactional*)
|
|
74
|
+
report "$f" "$ln" "txn-placement" "@Transactional must live on application-layer services (transaction boundaries)"
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done < <(grep -nE '@Transactional' "$f" || true)
|
|
78
|
+
done < <(find src -type f -name "*.java" 2>/dev/null || true)
|
|
79
|
+
|
|
80
|
+
# ── Rules 3 & 4: module docs drive depth-aware checks ─────────────────────────
|
|
81
|
+
if [ -d ".pi/architecture/modules" ]; then
|
|
82
|
+
for doc in .pi/architecture/modules/*.md; do
|
|
83
|
+
[ -f "$doc" ] || continue
|
|
84
|
+
doc_name="$(basename "$doc" .md)"
|
|
85
|
+
depth="$(grep -m1 '^\*\*Modeling Depth:\*\*' "$doc" | sed 's/.*\*\*Modeling Depth:\*\* *//' | cut -d' ' -f1 || true)"
|
|
86
|
+
docnorm="$(echo "$doc_name" | sed 's/^module[-_]//' | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
87
|
+
|
|
88
|
+
match_dir=""
|
|
89
|
+
while IFS= read -r d; do
|
|
90
|
+
[ -n "$d" ] || continue
|
|
91
|
+
norm="$(basename "$d" | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
92
|
+
if [ "$norm" = "$docnorm" ]; then
|
|
93
|
+
match_dir="$d"
|
|
94
|
+
break
|
|
95
|
+
fi
|
|
96
|
+
done < <(find src -mindepth 1 -maxdepth 1 -type d 2>/dev/null || true)
|
|
97
|
+
|
|
98
|
+
[ -n "$match_dir" ] || continue
|
|
99
|
+
|
|
100
|
+
if [ "$depth" = "adapter" ]; then
|
|
101
|
+
# adapter-purity: no production java under domain/ or application/
|
|
102
|
+
for layer in domain application; do
|
|
103
|
+
while IFS= read -r f; do
|
|
104
|
+
[ -n "$f" ] || continue
|
|
105
|
+
is_prod_java "$f" || continue
|
|
106
|
+
report "$f" "1" "adapter-purity" "generic subdomain '${doc_name}' must not contain ${layer}/ model code (ADR-008)"
|
|
107
|
+
done < <(find "$match_dir/$layer" -type f -name "*.java" 2>/dev/null || true)
|
|
108
|
+
done
|
|
109
|
+
elif [ "$depth" = "tactical" ]; then
|
|
110
|
+
# interface-ownership: infra repository implementations need a
|
|
111
|
+
# domain/-owned interface of the same simple name.
|
|
112
|
+
while IFS= read -r impl; do
|
|
113
|
+
[ -n "$impl" ] || continue
|
|
114
|
+
simple="$(basename "$impl")"
|
|
115
|
+
# Candidate interface names: as-is, Impl-suffix stripped, and
|
|
116
|
+
# common impl prefixes (Jpa/Jdbc/Hibernate/Spring) stripped.
|
|
117
|
+
candidates="$simple
|
|
118
|
+
$(echo "$simple" | sed -E 's/Impl\.java$/.java/')
|
|
119
|
+
$(echo "$simple" | sed -E 's/^(Jpa|Jdbc|Hibernate|Spring)([A-Z].*\.java)$/\2/')"
|
|
120
|
+
domain_iface=""
|
|
121
|
+
while IFS= read -r cand; do
|
|
122
|
+
[ -n "$cand" ] || continue
|
|
123
|
+
if [ -f "$match_dir/domain/$cand" ]; then
|
|
124
|
+
domain_iface="$match_dir/domain/$cand"
|
|
125
|
+
break
|
|
126
|
+
fi
|
|
127
|
+
done <<< "$candidates"
|
|
128
|
+
if [ ! -f "$domain_iface" ]; then
|
|
129
|
+
report "$impl" "1" "interface-ownership" "repository implementation without a domain/-owned interface (interface-ownership)"
|
|
130
|
+
fi
|
|
131
|
+
done < <(find "$match_dir/infrastructure" -type f -name "*Repository*.java" 2>/dev/null || true)
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ "$VIOLATIONS" -gt 0 ]; then
|
|
137
|
+
echo "model-judgment: ${VIOLATIONS} violation(s) found" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
echo "model-judgment (java): all gates passed (txn-placement, dip-domain, interface-ownership, adapter-purity)"
|
|
142
|
+
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_runtime_reachability.sh — Java (ADR-009)
|
|
4
|
+
#
|
|
5
|
+
# Every application-layer class under src/<module>/application/ must be
|
|
6
|
+
# referenced from production code OUTSIDE its own module and OUTSIDE tests.
|
|
7
|
+
# Java file == public class: the candidate is the file stem.
|
|
8
|
+
# Usage: bash check_runtime_reachability.sh [--root <dir>]
|
|
9
|
+
# Exit: 0 all reachable ; 1 dead service(s)
|
|
10
|
+
# ============================================================================
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
ROOT="$(pwd)"
|
|
13
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
14
|
+
cd "$ROOT" || exit 1
|
|
15
|
+
VIOLATIONS=0
|
|
16
|
+
[ -d src ] || { echo "runtime-reachability (java): no src/"; exit 0; }
|
|
17
|
+
|
|
18
|
+
while IFS= read -r svc; do
|
|
19
|
+
[ -n "$svc" ] || continue
|
|
20
|
+
mod="$(echo "$svc" | sed 's|^src/||; s|/application/.*||')"
|
|
21
|
+
stem="$(basename "$svc" .java)"
|
|
22
|
+
refs="$(grep -rl "\b${stem}\b" src 2>/dev/null | grep -vE "src/test/|/tests?/|Test[s]?\.java$" | grep -v "^src/${mod}/" | grep -v "^${svc}$" | grep -cv '^$' || true)"
|
|
23
|
+
if [ "${refs:-0}" -eq 0 ]; then
|
|
24
|
+
echo "$svc: [runtime-reachability] application service has no production caller outside its module (tests-only callers don't count — ADR-009)"
|
|
25
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
26
|
+
fi
|
|
27
|
+
done < <(find src -path "*/application/*.java" -type f 2>/dev/null | grep -vE "Test[s]?\.java$" || true)
|
|
28
|
+
|
|
29
|
+
if [ "$VIOLATIONS" -gt 0 ]; then echo "runtime-wiring (java): ${VIOLATIONS} unwired service(s)" >&2; exit 1; fi
|
|
30
|
+
echo "runtime-reachability (java): all application services reachable from production code"
|
|
31
|
+
exit 0
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# coverage.sh — Java: real line coverage via JaCoCo (maven/gradle)
|
|
4
|
+
#
|
|
5
|
+
# Runs tests and the JaCoCo report, then parses the TOTAL line-coverage
|
|
6
|
+
# percentage from the generated HTML report.
|
|
7
|
+
#
|
|
8
|
+
# Usage (normally via the .pi/scripts/coverage.sh dispatcher):
|
|
9
|
+
# bash coverage.sh # measure coverage
|
|
10
|
+
# bash coverage.sh --gate # fail if lines < COVERAGE_THRESHOLD
|
|
11
|
+
# bash coverage.sh --lcov # keep the jacoco.xml report artifact
|
|
12
|
+
#
|
|
13
|
+
# Env:
|
|
14
|
+
# COVERAGE_THRESHOLD minimum line-coverage % (default: 60)
|
|
15
|
+
# ============================================================================
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
|
|
18
|
+
COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-60}"
|
|
19
|
+
GATE=0
|
|
20
|
+
LCOV=0
|
|
21
|
+
|
|
22
|
+
for arg in "$@"; do
|
|
23
|
+
case "$arg" in
|
|
24
|
+
--gate) GATE=1 ;;
|
|
25
|
+
--lcov) LCOV=1 ;;
|
|
26
|
+
*) echo "unknown arg: $arg" >&2; exit 2 ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
# Walk up from CWD to the project root (pom.xml / build.gradle[.kts]).
|
|
31
|
+
ROOT="$(pwd)"
|
|
32
|
+
while [ "$ROOT" != "/" ] \
|
|
33
|
+
&& [ ! -f "$ROOT/pom.xml" ] \
|
|
34
|
+
&& [ ! -f "$ROOT/build.gradle" ] \
|
|
35
|
+
&& [ ! -f "$ROOT/build.gradle.kts" ]; do
|
|
36
|
+
ROOT="$(dirname "$ROOT")"
|
|
37
|
+
done
|
|
38
|
+
if [ ! -f "$ROOT/pom.xml" ] && [ ! -f "$ROOT/build.gradle" ] \
|
|
39
|
+
&& [ ! -f "$ROOT/build.gradle.kts" ]; then
|
|
40
|
+
echo "✗ No Maven/Gradle build file found in this directory tree" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
cd "$ROOT" || exit 1
|
|
44
|
+
|
|
45
|
+
REPORT_HTML=""
|
|
46
|
+
COVERAGE=""
|
|
47
|
+
|
|
48
|
+
echo "═══ Coverage (JaCoCo) ═══"
|
|
49
|
+
if [ -f "pom.xml" ]; then
|
|
50
|
+
if ! grep -q "jacoco" pom.xml; then
|
|
51
|
+
echo "✗ JaCoCo not configured in pom.xml — add org.jacoco:jacoco-maven-plugin (with jacoco:report) to enable the coverage gate." >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
if ! command -v mvn >/dev/null 2>&1; then
|
|
55
|
+
echo "✗ mvn not found on PATH" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
mvn test -q >/dev/null 2>&1 || true
|
|
59
|
+
mvn jacoco:report -q >/dev/null 2>&1 || true
|
|
60
|
+
REPORT_HTML="target/site/jacoco/index.html"
|
|
61
|
+
XML_ARTIFACT="target/site/jacoco/jacoco.xml"
|
|
62
|
+
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
|
|
63
|
+
if ! grep -q "jacoco" build.gradle build.gradle.kts 2>/dev/null; then
|
|
64
|
+
echo "✗ JaCoCo not configured in the Gradle build — add the org.gradle.jacoco plugin to enable the coverage gate." >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
if ! command -v gradle >/dev/null 2>&1; then
|
|
68
|
+
echo "✗ gradle not found on PATH" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
gradle test jacocoTestReport -q >/dev/null 2>&1 || true
|
|
72
|
+
REPORT_HTML="build/reports/jacoco/test/html/index.html"
|
|
73
|
+
XML_ARTIFACT="build/reports/jacoco/test/jacocoTestReport.xml"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if [ -f "$REPORT_HTML" ]; then
|
|
77
|
+
COVERAGE="$(grep -oP 'Total.*?([0-9]+)%' "$REPORT_HTML" 2>/dev/null | grep -oP '[0-9]+' | head -1 || echo "")"
|
|
78
|
+
fi
|
|
79
|
+
echo ""
|
|
80
|
+
echo "Line coverage: ${COVERAGE:-unavailable}% (threshold: ${COVERAGE_THRESHOLD}%)"
|
|
81
|
+
|
|
82
|
+
if [ -z "$COVERAGE" ]; then
|
|
83
|
+
echo "✗ JaCoCo report not found / percentage not detected at ${REPORT_HTML}" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [ "$LCOV" -eq 1 ]; then
|
|
88
|
+
if [ -f "$XML_ARTIFACT" ]; then
|
|
89
|
+
echo "jacoco report artifact kept: $XML_ARTIFACT"
|
|
90
|
+
else
|
|
91
|
+
echo "⚠️ jacoco.xml artifact not found at ${XML_ARTIFACT}" >&2
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if [ "$GATE" -eq 1 ]; then
|
|
96
|
+
if [ "$COVERAGE" -ge "$COVERAGE_THRESHOLD" ] 2>/dev/null; then
|
|
97
|
+
echo "✓ Coverage gate passed"
|
|
98
|
+
else
|
|
99
|
+
echo "✗ Coverage below threshold (${COVERAGE}% < ${COVERAGE_THRESHOLD}%)" >&2
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
exit 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# install_coverage_tools.sh — Java: JaCoCo is configured in the build, not
|
|
4
|
+
# installed
|
|
5
|
+
#
|
|
6
|
+
# Enables/verifies the JaCoCo plugin for Maven or Gradle. The coverage gate
|
|
7
|
+
# (.pi/scripts/languages/java/coverage.sh) reads its HTML/XML reports.
|
|
8
|
+
# ============================================================================
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
if [ -f "pom.xml" ]; then
|
|
12
|
+
if grep -q "jacoco" pom.xml; then
|
|
13
|
+
echo "JaCoCo already configured in pom.xml."
|
|
14
|
+
else
|
|
15
|
+
echo "Adding org.jacoco:jacoco-maven-plugin to pom.xml requires an edit — add to <build><plugins>:"
|
|
16
|
+
cat <<'EOF'
|
|
17
|
+
<plugin>
|
|
18
|
+
<groupId>org.jacoco</groupId>
|
|
19
|
+
<artifactId>jacoco-maven-plugin</artifactId>
|
|
20
|
+
<version>0.8.12</version>
|
|
21
|
+
<executions><execution><goals><goal>prepare-agent</goal><goal>report</goal></goals></execution></executions>
|
|
22
|
+
</plugin>
|
|
23
|
+
EOF
|
|
24
|
+
echo "✗ Not editing pom.xml automatically — apply the snippet above, then re-run." >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
|
|
28
|
+
if grep -q "jacoco" build.gradle build.gradle.kts 2>/dev/null; then
|
|
29
|
+
echo "JaCoCo already configured in the Gradle build."
|
|
30
|
+
else
|
|
31
|
+
echo "Add to build.gradle: apply plugin: 'jacoco' (then: gradle jacocoTestReport)" >&2
|
|
32
|
+
echo "✗ Not editing the Gradle build automatically — apply the plugin, then re-run." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
else
|
|
36
|
+
echo "✗ No Maven/Gradle build file found" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "JaCoCo coverage tooling ready."
|
|
@@ -137,6 +137,26 @@ else
|
|
|
137
137
|
fi
|
|
138
138
|
|
|
139
139
|
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# ADR-008 model-judgment hard gates (contract §5)
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
MJ_SCRIPT="$(dirname "$0")/check_model_judgment.sh"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "--- Model Judgment (ADR-008) ---"
|
|
147
|
+
if [ -f "$MJ_SCRIPT" ]; then
|
|
148
|
+
MJ_OUT="$(bash "$MJ_SCRIPT" --root "$(pwd)" 2>&1)" && MJ_EXIT=0 || MJ_EXIT=1
|
|
149
|
+
if [ "$MJ_EXIT" -eq 0 ]; then
|
|
150
|
+
pass "model-judgment hard gates (txn-placement, dip-domain, interface-ownership, adapter-purity)"
|
|
151
|
+
else
|
|
152
|
+
while IFS= read -r mj_line; do
|
|
153
|
+
[ -n "$mj_line" ] && fail "$mj_line"
|
|
154
|
+
done <<< "$MJ_OUT"
|
|
155
|
+
fi
|
|
156
|
+
else
|
|
157
|
+
warn "check_model_judgment.sh not found — model-judgment gates skipped"
|
|
158
|
+
fi
|
|
159
|
+
|
|
140
160
|
# Summary
|
|
141
161
|
# ---------------------------------------------------------------------------
|
|
142
162
|
echo ""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_model_judgment.sh — Python (ADR-008/009)
|
|
4
|
+
# Deterministic hard gates:
|
|
5
|
+
# dip-domain domain/ imports sqlalchemy/django/fastapi
|
|
6
|
+
# txn-placement session.commit/transaction outside application/
|
|
7
|
+
# interface-ownership (tactical) infra adapters without a domain Protocol/ABC
|
|
8
|
+
# adapter-purity (adapter modules) model code in generic subdomains
|
|
9
|
+
# Usage: bash check_model_judgment.sh [--root <dir>]
|
|
10
|
+
# ============================================================================
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
ROOT="$(pwd)"
|
|
13
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
14
|
+
cd "$ROOT" || exit 1
|
|
15
|
+
VIOLATIONS=0
|
|
16
|
+
report() { echo "$1:$2: [$3] $4"; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
17
|
+
[ -d src ] || { echo "model-judgment (python): no src/"; exit 0; }
|
|
18
|
+
|
|
19
|
+
while IFS= read -r f; do
|
|
20
|
+
[ -n "$f" ] || continue
|
|
21
|
+
case "$f" in */domain/*)
|
|
22
|
+
while IFS=: read -r ln rest; do
|
|
23
|
+
case "$rest" in
|
|
24
|
+
*sqlalchemy*|*django*|*fastapi*|*flask*|*peewee*|*tortoise*)
|
|
25
|
+
report "$f" "$ln" "dip-domain" "domain imports framework/ORM: $rest" ;;
|
|
26
|
+
esac
|
|
27
|
+
done < <(grep -nE "^\s*(import|from) " "$f" || true) ;;
|
|
28
|
+
esac
|
|
29
|
+
case "$f" in */application/*) ;; *)
|
|
30
|
+
if grep -qE "\.session\.(commit|begin)|\.commit\(\)|begin_nested|\.atomic" "$f" 2>/dev/null; then
|
|
31
|
+
report "$f" "1" "txn-placement" "transaction/session handling outside application layer"
|
|
32
|
+
fi ;;
|
|
33
|
+
esac
|
|
34
|
+
done < <(find src -type f -name "*.py" 2>/dev/null | grep -vE "/tests?/|test_.*\.py$|_test\.py$" || true)
|
|
35
|
+
|
|
36
|
+
if [ -d ".pi/architecture/modules" ]; then
|
|
37
|
+
for doc in .pi/architecture/modules/*.md; do
|
|
38
|
+
[ -f "$doc" ] || continue
|
|
39
|
+
doc_name="$(basename "$doc" .md)"
|
|
40
|
+
depth="$(grep -m1 '^\*\*Modeling Depth:\*\*' "$doc" | sed 's/.*\*\*Modeling Depth:\*\* *//' | cut -d' ' -f1 || true)"
|
|
41
|
+
docnorm="$(echo "$doc_name" | sed 's/^module[-_]//' | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
42
|
+
match_dir=""
|
|
43
|
+
while IFS= read -r d; do
|
|
44
|
+
norm="$(basename "$d" | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
45
|
+
if [ "$norm" = "$docnorm" ]; then match_dir="$d"; break; fi
|
|
46
|
+
done < <(find src -mindepth 1 -maxdepth 1 -type d 2>/dev/null || true)
|
|
47
|
+
[ -n "$match_dir" ] || continue
|
|
48
|
+
if [ "$depth" = "adapter" ]; then
|
|
49
|
+
for layer in domain application; do
|
|
50
|
+
while IFS= read -r f; do [ -n "$f" ] || continue; report "$f" "1" "adapter-purity" "generic subdomain '${doc_name}' must not contain ${layer}/ model code (ADR-008)"; done < <(find "$match_dir/$layer" -type f -name "*.py" 2>/dev/null || true)
|
|
51
|
+
done
|
|
52
|
+
elif [ "$depth" = "tactical" ]; then
|
|
53
|
+
while IFS= read -r impl; do
|
|
54
|
+
[ -n "$impl" ] || continue
|
|
55
|
+
simple="$(basename "$impl" .py)"
|
|
56
|
+
stem="$(echo "$simple" | sed -E 's/_impl$//;s/Impl$//')"
|
|
57
|
+
if ! grep -rqiE "class ${stem}|class ${simple}|Protocol|ABC" "$match_dir/domain" 2>/dev/null; then
|
|
58
|
+
report "$impl" "1" "interface-ownership" "adapter without a domain/-owned Protocol/ABC (${simple})"
|
|
59
|
+
fi
|
|
60
|
+
done < <(find "$match_dir/infrastructure" -type f -name "*.py" 2>/dev/null || true)
|
|
61
|
+
fi
|
|
62
|
+
done
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ "$VIOLATIONS" -gt 0 ]; then echo "model-judgment (python): ${VIOLATIONS} violation(s)" >&2; exit 1; fi
|
|
66
|
+
echo "model-judgment (python): all gates passed (dip-domain, txn-placement, interface-ownership, adapter-purity)"
|
|
67
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_runtime_reachability.sh — Python (ADR-009)
|
|
4
|
+
#
|
|
5
|
+
# Every module under src/<module>/application/ must be referenced from
|
|
6
|
+
# production code OUTSIDE its own module and OUTSIDE tests. Candidates: the
|
|
7
|
+
# file stem, its PascalCase class name, and the separator-stripped form
|
|
8
|
+
# (case-insensitive).
|
|
9
|
+
# Usage: bash check_runtime_reachability.sh [--root <dir>]
|
|
10
|
+
# Exit: 0 all reachable ; 1 dead service(s)
|
|
11
|
+
# ============================================================================
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
ROOT="$(pwd)"
|
|
14
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
15
|
+
cd "$ROOT" || exit 1
|
|
16
|
+
VIOLATIONS=0
|
|
17
|
+
[ -d src ] || { echo "runtime-reachability (python): no src/"; exit 0; }
|
|
18
|
+
|
|
19
|
+
while IFS= read -r svc; do
|
|
20
|
+
[ -n "$svc" ] || continue
|
|
21
|
+
mod="$(echo "$svc" | sed 's|^src/||; s|/application/.*||')"
|
|
22
|
+
stem="$(basename "$svc" .py)"
|
|
23
|
+
pascal="$(echo "$stem" | python3 -c 'import sys; s=sys.stdin.read().strip(); print("".join(w.capitalize() for w in s.replace("-","_").split("_")))' 2>/dev/null || echo "$stem")"
|
|
24
|
+
compact="$(echo "$stem" | tr -d '_-')"
|
|
25
|
+
refs="$(grep -rliE "\b(${pascal}|${stem}|${compact})\b" src 2>/dev/null | grep -vE "/tests?/|test_.*\.py$|_test\.py$|/test/" | grep -v "^src/${mod}/" | grep -v "^${svc}$" | grep -cv '^$' || true)"
|
|
26
|
+
if [ "${refs:-0}" -eq 0 ]; then
|
|
27
|
+
echo "$svc: [runtime-reachability] application service has no production caller outside its module (tests-only callers don't count — ADR-009)"
|
|
28
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
29
|
+
fi
|
|
30
|
+
done < <(find src -path "*/application/*.py" -type f 2>/dev/null | grep -vE "/tests?/|test_.*\.py$|_test\.py$" || true)
|
|
31
|
+
|
|
32
|
+
if [ "$VIOLATIONS" -gt 0 ]; then echo "runtime-wiring (python): ${VIOLATIONS} unwired service(s)" >&2; exit 1; fi
|
|
33
|
+
echo "runtime-reachability (python): all application services reachable from production code"
|
|
34
|
+
exit 0
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# coverage.sh — Python: real line coverage via coverage.py
|
|
4
|
+
#
|
|
5
|
+
# Runs the test suite under coverage.py and reports the TOTAL line coverage.
|
|
6
|
+
# Uses pytest when available, unittest otherwise.
|
|
7
|
+
#
|
|
8
|
+
# Usage (normally via the .pi/scripts/coverage.sh dispatcher):
|
|
9
|
+
# bash coverage.sh # measure coverage
|
|
10
|
+
# bash coverage.sh --gate # fail if lines < COVERAGE_THRESHOLD
|
|
11
|
+
# bash coverage.sh --lcov # also write coverage.xml (cobertura) artifact
|
|
12
|
+
#
|
|
13
|
+
# Env:
|
|
14
|
+
# COVERAGE_THRESHOLD minimum line-coverage % (default: 60)
|
|
15
|
+
# ============================================================================
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
|
|
18
|
+
COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-60}"
|
|
19
|
+
GATE=0
|
|
20
|
+
LCOV=0
|
|
21
|
+
|
|
22
|
+
for arg in "$@"; do
|
|
23
|
+
case "$arg" in
|
|
24
|
+
--gate) GATE=1 ;;
|
|
25
|
+
--lcov) LCOV=1 ;;
|
|
26
|
+
*) echo "unknown arg: $arg" >&2; exit 2 ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
# Walk up from CWD to the project root (pyproject.toml / setup.py /
|
|
31
|
+
# setup.cfg / requirements.txt).
|
|
32
|
+
ROOT="$(pwd)"
|
|
33
|
+
while [ "$ROOT" != "/" ] \
|
|
34
|
+
&& [ ! -f "$ROOT/pyproject.toml" ] \
|
|
35
|
+
&& [ ! -f "$ROOT/setup.py" ] \
|
|
36
|
+
&& [ ! -f "$ROOT/setup.cfg" ] \
|
|
37
|
+
&& [ ! -f "$ROOT/requirements.txt" ]; do
|
|
38
|
+
ROOT="$(dirname "$ROOT")"
|
|
39
|
+
done
|
|
40
|
+
if [ ! -f "$ROOT/pyproject.toml" ] && [ ! -f "$ROOT/setup.py" ] \
|
|
41
|
+
&& [ ! -f "$ROOT/setup.cfg" ] && [ ! -f "$ROOT/requirements.txt" ]; then
|
|
42
|
+
echo "✗ No Python project marker found in this directory tree" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
cd "$ROOT" || exit 1
|
|
46
|
+
|
|
47
|
+
if ! python3 -m coverage --version >/dev/null 2>&1; then
|
|
48
|
+
echo "coverage.py not found — run .pi/scripts/install_coverage_tools.sh" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "═══ Coverage (coverage.py) ═══"
|
|
53
|
+
# Scope measurement to the project source (src/ layout when present, else
|
|
54
|
+
# the project root) so site-packages / pytest internals are not counted.
|
|
55
|
+
COV_SOURCE="src"
|
|
56
|
+
[ -d "src" ] || COV_SOURCE="."
|
|
57
|
+
if python3 -m pytest --version >/dev/null 2>&1; then
|
|
58
|
+
python3 -m coverage run --source="$COV_SOURCE" -m pytest -q >/dev/null 2>&1 || true
|
|
59
|
+
else
|
|
60
|
+
python3 -m coverage run --source="$COV_SOURCE" -m unittest discover -s tests >/dev/null 2>&1 || true
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
REPORT="$(python3 -m coverage report 2>&1 || true)"
|
|
64
|
+
echo "$REPORT" | tail -20
|
|
65
|
+
COVERAGE="$(echo "$REPORT" | awk '/^TOTAL/{print $NF}' | tr -d '%')"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "Line coverage: ${COVERAGE:-unavailable}% (threshold: ${COVERAGE_THRESHOLD}%)"
|
|
68
|
+
|
|
69
|
+
if [ -z "$COVERAGE" ]; then
|
|
70
|
+
echo "✗ Could not extract TOTAL coverage from coverage.py output" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ "$LCOV" -eq 1 ]; then
|
|
75
|
+
python3 -m coverage xml -o coverage.xml >/dev/null 2>&1 \
|
|
76
|
+
&& echo "cobertura artifact written: coverage.xml" \
|
|
77
|
+
|| echo "⚠️ could not write coverage.xml" >&2
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [ "$GATE" -eq 1 ]; then
|
|
81
|
+
if awk "BEGIN { exit !($COVERAGE >= $COVERAGE_THRESHOLD) }"; then
|
|
82
|
+
echo "✓ Coverage gate passed"
|
|
83
|
+
else
|
|
84
|
+
echo "✗ Coverage below threshold (${COVERAGE}% < ${COVERAGE_THRESHOLD}%)" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
exit 0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# install_coverage_tools.sh — Python: install coverage.py (+ pytest)
|
|
4
|
+
#
|
|
5
|
+
# Installs the real coverage tooling used by
|
|
6
|
+
# .pi/scripts/languages/python/coverage.sh.
|
|
7
|
+
# ============================================================================
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
if python3 -m coverage --version >/dev/null 2>&1; then
|
|
11
|
+
echo "coverage.py already installed: $(python3 -m coverage --version | head -1)"
|
|
12
|
+
else
|
|
13
|
+
echo "Installing coverage.py..."
|
|
14
|
+
python3 -m pip install coverage
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if ! python3 -m pytest --version >/dev/null 2>&1; then
|
|
18
|
+
echo "Installing pytest (optional, for test discovery)..."
|
|
19
|
+
python3 -m pip install pytest || true
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "coverage tooling ready."
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_model_judgment.sh — Rust (ADR-008/009)
|
|
4
|
+
# Deterministic hard gates:
|
|
5
|
+
# dip-domain domain/ (crate/module) imports sqlx/diesel/web frameworks
|
|
6
|
+
# interface-ownership (tactical) infra impl without a domain/-owned trait
|
|
7
|
+
# adapter-purity (adapter modules) model code in generic subdomains
|
|
8
|
+
# Usage: bash check_model_judgment.sh [--root <dir>]
|
|
9
|
+
# ============================================================================
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
ROOT="$(pwd)"
|
|
12
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
13
|
+
cd "$ROOT" || exit 1
|
|
14
|
+
VIOLATIONS=0
|
|
15
|
+
report() { echo "$1:$2: [$3] $4"; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
16
|
+
[ -d src ] || { echo "model-judgment (rust): no src/"; exit 0; }
|
|
17
|
+
|
|
18
|
+
while IFS= read -r f; do
|
|
19
|
+
[ -n "$f" ] || continue
|
|
20
|
+
case "$f" in */domain/*)
|
|
21
|
+
while IFS=: read -r ln rest; do
|
|
22
|
+
case "$rest" in
|
|
23
|
+
*sqlx*|*diesel*|*sea_orm*|*axum*|*actix*|*tokio-postgres*|*mongodb*|*redis*)
|
|
24
|
+
report "$f" "$ln" "dip-domain" "domain imports framework/persistence crate: $(echo "$rest" | sed 's/^use //; s/;.*//')" ;;
|
|
25
|
+
esac
|
|
26
|
+
done < <(grep -nE "^use |^extern crate " "$f" || true) ;;
|
|
27
|
+
esac
|
|
28
|
+
done < <(find src -type f -name "*.rs" 2>/dev/null | grep -vE "_test\.rs$|/tests?/" || true)
|
|
29
|
+
|
|
30
|
+
if [ -d ".pi/architecture/modules" ]; then
|
|
31
|
+
for doc in .pi/architecture/modules/*.md; do
|
|
32
|
+
[ -f "$doc" ] || continue
|
|
33
|
+
doc_name="$(basename "$doc" .md)"
|
|
34
|
+
depth="$(grep -m1 '^\*\*Modeling Depth:\*\*' "$doc" | sed 's/.*\*\*Modeling Depth:\*\* *//' | cut -d' ' -f1 || true)"
|
|
35
|
+
docnorm="$(echo "$doc_name" | sed 's/^module[-_]//' | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
36
|
+
match_dir=""
|
|
37
|
+
while IFS= read -r d; do
|
|
38
|
+
norm="$(basename "$d" | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
39
|
+
if [ "$norm" = "$docnorm" ]; then match_dir="$d"; break; fi
|
|
40
|
+
done < <(find src -mindepth 1 -maxdepth 1 -type d 2>/dev/null || true)
|
|
41
|
+
[ -n "$match_dir" ] || continue
|
|
42
|
+
if [ "$depth" = "adapter" ]; then
|
|
43
|
+
for layer in domain application; do
|
|
44
|
+
while IFS= read -r f; do [ -n "$f" ] || continue; report "$f" "1" "adapter-purity" "generic subdomain '${doc_name}' must not contain ${layer}/ model code (ADR-008)"; done < <(find "$match_dir/$layer" -type f -name "*.rs" 2>/dev/null || true)
|
|
45
|
+
done
|
|
46
|
+
elif [ "$depth" = "tactical" ]; then
|
|
47
|
+
while IFS= read -r impl; do
|
|
48
|
+
[ -n "$impl" ] || continue
|
|
49
|
+
simple="$(basename "$impl" .rs)"
|
|
50
|
+
stem="$(echo "$simple" | sed -E 's/_impl$//')"
|
|
51
|
+
if ! grep -rqiE "(pub )?trait ${stem}\\b|trait ${simple}\\b|${stem}" "$match_dir/domain" 2>/dev/null; then
|
|
52
|
+
report "$impl" "1" "interface-ownership" "repository/adapter impl without a domain/-owned trait (${simple})"
|
|
53
|
+
fi
|
|
54
|
+
done < <(find "$match_dir/infrastructure" -type f \( -name "*repository*.rs" -o -name "*repo*.rs" -o -name "*gateway*.rs" \) 2>/dev/null || true)
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ "$VIOLATIONS" -gt 0 ]; then echo "model-judgment (rust): ${VIOLATIONS} violation(s)" >&2; exit 1; fi
|
|
60
|
+
echo "model-judgment (rust): all gates passed (dip-domain, interface-ownership, adapter-purity)"
|
|
61
|
+
exit 0
|