guardian-framework 0.1.104 → 0.1.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +11 -2
  2. package/dist/cli.js +232 -16
  3. package/dist/exports.js +232 -16
  4. package/package.json +1 -1
  5. package/templates/languages/rust-patterns.md +5 -2
  6. package/templates/pi/context/checklists.md +15 -1
  7. package/templates/pi/domain/exploration.md +9 -0
  8. package/templates/pi/domain/ubiquitous-language.md +12 -0
  9. package/templates/pi/extensions/architect-lib/generators.ts +163 -12
  10. package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
  11. package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
  12. package/templates/pi/extensions/architect-lib/types.ts +44 -2
  13. package/templates/pi/extensions/architect.ts +105 -41
  14. package/templates/pi/extensions/ask-user-question.ts +2 -2
  15. package/templates/pi/extensions/bash-guard.ts +2 -2
  16. package/templates/pi/extensions/config-reload.ts +12 -5
  17. package/templates/pi/extensions/coordinator.ts +2 -2
  18. package/templates/pi/extensions/curator.ts +1 -1
  19. package/templates/pi/extensions/domain-explorer.ts +50 -3
  20. package/templates/pi/extensions/filechanges.ts +2 -2
  21. package/templates/pi/extensions/goal-loop.ts +3 -3
  22. package/templates/pi/extensions/hooks.ts +1 -1
  23. package/templates/pi/extensions/pi-env.d.ts +96 -0
  24. package/templates/pi/extensions/pipeline.ts +1 -1
  25. package/templates/pi/extensions/project-scaffolder.ts +1 -1
  26. package/templates/pi/extensions/read-only-mode.ts +5 -1
  27. package/templates/pi/extensions/tsconfig.json +9 -2
  28. package/templates/pi/preflight_report.json +1 -1
  29. package/templates/pi/prompts/ci-blueprint.md +2 -2
  30. package/templates/pi/scripts/check_model_judgment.sh +28 -0
  31. package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
  32. package/templates/pi/scripts/ci/run_preflight.sh +3 -3
  33. package/templates/pi/scripts/ci/run_stage.sh +1 -1
  34. package/templates/pi/scripts/ci/stage_lint.sh +1 -1
  35. package/templates/pi/scripts/coverage.sh +58 -0
  36. package/templates/pi/scripts/install_coverage_tools.sh +43 -0
  37. package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
  38. package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
  39. package/templates/pi/scripts/languages/go/coverage.sh +78 -0
  40. package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
  41. package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
  42. package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
  43. package/templates/pi/scripts/languages/java/coverage.sh +103 -0
  44. package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
  45. package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
  46. package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
  47. package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
  48. package/templates/pi/scripts/languages/python/coverage.sh +88 -0
  49. package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
  50. package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
  51. package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
  52. package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
  53. package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
  54. package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
  55. package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
  56. package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
  57. package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
  58. package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
  59. package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
  60. package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
  61. package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
  62. package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
  63. package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
  64. package/templates/pi/skills/agents/architecture-validator.md +28 -0
  65. package/templates/pi/skills/agents/test-validator.md +1 -1
  66. package/templates/pi/skills/go-enterprise-codegen.md +46 -0
  67. package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
  68. package/templates/pi/skills/python-enterprise-codegen.md +46 -0
  69. package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
  70. package/templates/pi/skills/tdd-practice.md +17 -9
  71. package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
@@ -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