guardian-framework 0.1.105 → 0.1.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_runtime_reachability.sh — Rust (ADR-009)
|
|
4
|
+
#
|
|
5
|
+
# Every service struct under src/<module>/application/ must be referenced from
|
|
6
|
+
# production code outside its own module and outside tests (tests dirs,
|
|
7
|
+
# *_test.rs, and #[cfg(test)] units inside the same file do not count — we
|
|
8
|
+
# require a reference from a different production file outside the module).
|
|
9
|
+
#
|
|
10
|
+
# Usage: bash check_runtime_reachability.sh [--root <dir>]
|
|
11
|
+
# Exit: 0 all reachable ; 1 dead service(s)
|
|
12
|
+
# ============================================================================
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
ROOT="$(pwd)"
|
|
15
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
16
|
+
cd "$ROOT" || exit 1
|
|
17
|
+
|
|
18
|
+
VIOLATIONS=0
|
|
19
|
+
[ -d src ] || { echo "runtime-reachability (rust): no src/"; exit 0; }
|
|
20
|
+
|
|
21
|
+
while IFS= read -r svc; do
|
|
22
|
+
[ -n "$svc" ] || continue
|
|
23
|
+
mod="$(echo "$svc" | sed 's|^src/||; s|/application/.*||')"
|
|
24
|
+
stem="$(basename "$svc" .rs)"
|
|
25
|
+
# candidate type names: file stem in PascalCase and snake_case both
|
|
26
|
+
pascal="$(echo "$stem" | python3 -c 'import sys; s=sys.stdin.read().strip(); print("".join(w.capitalize() for w in s.split("_")) if s else "")' 2>/dev/null || echo "$stem")"
|
|
27
|
+
# production references: outside the module, excluding tests
|
|
28
|
+
refs="$(grep -rlE "\b(${pascal}|${stem})\b" src 2>/dev/null | grep -vE "/tests?/|_test\.rs$|/test/" | grep -v "^src/${mod}/" | grep -v "^${svc}$" | grep -cv '^$' || true)"
|
|
29
|
+
if [ "${refs:-0}" -eq 0 ]; then
|
|
30
|
+
echo "$svc: [runtime-reachability] application service has no production caller outside its module (tests-only callers don't count — ADR-009)"
|
|
31
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
32
|
+
fi
|
|
33
|
+
done < <(find src -path "*/application/*.rs" -type f 2>/dev/null || true)
|
|
34
|
+
|
|
35
|
+
if [ "$VIOLATIONS" -gt 0 ]; then
|
|
36
|
+
echo "runtime-wiring (rust): ${VIOLATIONS} unwired service(s)" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
echo "runtime-reachability (rust): all application services reachable from production code"
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# coverage.sh — Rust: real line coverage via cargo-llvm-cov
|
|
4
|
+
#
|
|
5
|
+
# cargo-llvm-cov uses native LLVM instrumentation and works with incremental
|
|
6
|
+
# compilation — ~3x faster than cargo-tarpaulin's full instrumented
|
|
7
|
+
# recompilation. Produces the REAL workspace line-coverage number.
|
|
8
|
+
#
|
|
9
|
+
# Usage (normally via the .pi/scripts/coverage.sh dispatcher):
|
|
10
|
+
# bash coverage.sh # measure workspace coverage
|
|
11
|
+
# bash coverage.sh --gate # fail if lines < COVERAGE_THRESHOLD
|
|
12
|
+
# bash coverage.sh --lcov # also write target/coverage.lcov artifact
|
|
13
|
+
#
|
|
14
|
+
# Env:
|
|
15
|
+
# COVERAGE_THRESHOLD minimum line-coverage % (default: 60)
|
|
16
|
+
# ============================================================================
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
|
|
19
|
+
COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-60}"
|
|
20
|
+
GATE=0
|
|
21
|
+
LCOV=0
|
|
22
|
+
|
|
23
|
+
for arg in "$@"; do
|
|
24
|
+
case "$arg" in
|
|
25
|
+
--gate) GATE=1 ;;
|
|
26
|
+
--lcov) LCOV=1 ;;
|
|
27
|
+
*) echo "unknown arg: $arg" >&2; exit 2 ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
# Walk up from CWD to the crate/workspace root (Cargo.toml).
|
|
32
|
+
ROOT="$(pwd)"
|
|
33
|
+
while [ "$ROOT" != "/" ] && [ ! -f "$ROOT/Cargo.toml" ]; do
|
|
34
|
+
ROOT="$(dirname "$ROOT")"
|
|
35
|
+
done
|
|
36
|
+
if [ ! -f "$ROOT/Cargo.toml" ]; then
|
|
37
|
+
echo "✗ No Cargo.toml found in this directory tree" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
cd "$ROOT" || exit 1
|
|
41
|
+
|
|
42
|
+
if ! command -v cargo-llvm-cov >/dev/null 2>&1; then
|
|
43
|
+
echo "cargo-llvm-cov not found — run .pi/scripts/install_coverage_tools.sh" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "═══ Workspace coverage (cargo llvm-cov) ═══"
|
|
48
|
+
# --summary-only always prints the table we parse. When --lcov is also
|
|
49
|
+
# requested, write the lcov artifact in a second (instrumentation-cached,
|
|
50
|
+
# fast) pass — the tool suppresses the console summary in lcov-output mode.
|
|
51
|
+
SUMMARY="$(cargo llvm-cov --workspace --summary-only)"
|
|
52
|
+
if [ "$LCOV" -eq 1 ]; then
|
|
53
|
+
mkdir -p target
|
|
54
|
+
cargo llvm-cov --workspace --lcov --output-path target/coverage.lcov >/dev/null 2>&1 \
|
|
55
|
+
&& echo "lcov artifact written: target/coverage.lcov" \
|
|
56
|
+
|| echo "⚠️ could not write target/coverage.lcov" >&2
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Print the per-file table + TOTAL row (summary tail), then extract the
|
|
60
|
+
# TOTAL line-coverage percentage from the final line.
|
|
61
|
+
echo "$SUMMARY" | tail -12
|
|
62
|
+
COVERAGE="$(echo "$SUMMARY" | tail -1 | grep -oE '[0-9]+\.[0-9]+%' | head -1 | sed 's/%//')"
|
|
63
|
+
echo ""
|
|
64
|
+
echo "Line coverage: ${COVERAGE:-unavailable}% (threshold: ${COVERAGE_THRESHOLD}%)"
|
|
65
|
+
|
|
66
|
+
if [ -z "$COVERAGE" ]; then
|
|
67
|
+
echo "✗ Could not extract coverage from cargo llvm-cov output" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [ "$GATE" -eq 1 ]; then
|
|
72
|
+
if awk "BEGIN { exit !($COVERAGE >= $COVERAGE_THRESHOLD) }"; then
|
|
73
|
+
echo "✓ Coverage gate passed"
|
|
74
|
+
else
|
|
75
|
+
echo "✗ Coverage below threshold (${COVERAGE}% < ${COVERAGE_THRESHOLD}%)" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
exit 0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# install_coverage_tools.sh — Rust: install cargo-llvm-cov
|
|
4
|
+
#
|
|
5
|
+
# cargo-llvm-cov needs the llvm-tools-preview rustup component. It is the
|
|
6
|
+
# real coverage tool used by .pi/scripts/languages/rust/coverage.sh.
|
|
7
|
+
# ============================================================================
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
rustup component add llvm-tools-preview || echo "llvm-tools-preview unavailable on this toolchain"
|
|
11
|
+
|
|
12
|
+
if command -v cargo-llvm-cov >/dev/null 2>&1; then
|
|
13
|
+
echo "cargo-llvm-cov already installed: $(cargo-llvm-cov --version)"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
echo "Installing cargo-llvm-cov..."
|
|
18
|
+
cargo install cargo-llvm-cov --locked
|
|
19
|
+
|
|
20
|
+
echo "cargo-llvm-cov installed: $(cargo-llvm-cov --version)"
|
|
@@ -97,13 +97,14 @@ else
|
|
|
97
97
|
fi
|
|
98
98
|
|
|
99
99
|
# ---------------------------------------------------------------------------
|
|
100
|
-
# Coverage
|
|
100
|
+
# Coverage (uses cargo-llvm-cov — native LLVM instrumentation, ~3x faster than tarpaulin)
|
|
101
101
|
# ---------------------------------------------------------------------------
|
|
102
102
|
echo ""
|
|
103
103
|
echo "--- Coverage ---"
|
|
104
|
-
if command -v cargo &>/dev/null && cargo
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if command -v cargo &>/dev/null && cargo llvm-cov --version &>/dev/null; then
|
|
105
|
+
LLCV_OUT=$(cargo llvm-cov --html --fail-under-lines 80 2>&1 || true)
|
|
106
|
+
LLCV_EXIT=$?
|
|
107
|
+
COVERAGE_PCT=$(echo "$LLCV_OUT" | grep -oE '[0-9]+(\.[0-9]+)?%' | head -1 | tr -d '%' || echo "")
|
|
107
108
|
if [ -n "$COVERAGE_PCT" ]; then
|
|
108
109
|
# Compare coverage against 80% threshold using awk
|
|
109
110
|
MEETS_THRESHOLD=$(echo "$COVERAGE_PCT" | awk '{print ($1 >= 80) ? "yes" : "no"}')
|
|
@@ -112,8 +113,10 @@ if command -v cargo &>/dev/null && cargo tarpaulin --version &>/dev/null; then
|
|
|
112
113
|
else
|
|
113
114
|
fail "Code coverage: ${COVERAGE_PCT}% (< 80%)"
|
|
114
115
|
fi
|
|
116
|
+
elif [ "$LLCV_EXIT" -eq 0 ]; then
|
|
117
|
+
pass "Code coverage meets 80% threshold"
|
|
115
118
|
else
|
|
116
|
-
warn "Could not extract coverage percentage from
|
|
119
|
+
warn "Could not extract coverage percentage from llvm-cov output"
|
|
117
120
|
fi
|
|
118
121
|
elif command -v grcov &>/dev/null; then
|
|
119
122
|
# Use grcov as fallback
|
|
@@ -124,7 +127,7 @@ elif command -v grcov &>/dev/null; then
|
|
|
124
127
|
pass "grcov coverage report generated"
|
|
125
128
|
fi
|
|
126
129
|
else
|
|
127
|
-
warn "No coverage tools available (cargo-
|
|
130
|
+
warn "No coverage tools available (cargo-llvm-cov / grcov), skipping coverage check"
|
|
128
131
|
fi
|
|
129
132
|
|
|
130
133
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_model_judgment.sh — TypeScript (ADR-008/009)
|
|
4
|
+
# Deterministic hard gates, mirrors the Java four:
|
|
5
|
+
# dip-domain domain/ imports framework/persistence types
|
|
6
|
+
# txn-placement transaction/UnitOfWork boundaries outside application/
|
|
7
|
+
# interface-ownership (tactical) infra adapters without a domain/-owned interface
|
|
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
|
+
|
|
16
|
+
VIOLATIONS=0
|
|
17
|
+
report() { echo "$1:$2: [$3] $4"; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
18
|
+
|
|
19
|
+
[ -d src ] || { echo "model-judgment (ts): no src/"; exit 0; }
|
|
20
|
+
while IFS= read -r f; do
|
|
21
|
+
[ -n "$f" ] || continue
|
|
22
|
+
case "$f" in */*test*/|*.test.ts|*.spec.ts) continue ;; esac
|
|
23
|
+
# dip-domain
|
|
24
|
+
case "$f" in */domain/*)
|
|
25
|
+
while IFS=: read -r ln rest; do
|
|
26
|
+
case "$rest" in
|
|
27
|
+
*typeorm*|*prisma*|*@nestjs*|*express*|*sequelize*)
|
|
28
|
+
report "$f" "$ln" "dip-domain" "domain imports framework: $(echo "$rest" | sed 's/^import //; s/;//')" ;;
|
|
29
|
+
esac
|
|
30
|
+
done < <(grep -nE "^import |require\\(" "$f" || true) ;;
|
|
31
|
+
esac
|
|
32
|
+
# txn-placement: transaction/UnitOfWork usage outside application/
|
|
33
|
+
case "$f" in */application/*) ;; *)
|
|
34
|
+
if grep -qE "transaction|unitOfWork|beginTransaction|\.manager\.(save|transaction)" "$f" 2>/dev/null; then
|
|
35
|
+
report "$f" "1" "txn-placement" "transaction/UnitOfWork logic outside application layer"
|
|
36
|
+
fi ;;
|
|
37
|
+
esac
|
|
38
|
+
done < <(find src -type f -name "*.ts" 2>/dev/null || true)
|
|
39
|
+
|
|
40
|
+
# module-doc depth rules (same layout as Java)
|
|
41
|
+
if [ -d ".pi/architecture/modules" ]; then
|
|
42
|
+
for doc in .pi/architecture/modules/*.md; do
|
|
43
|
+
[ -f "$doc" ] || continue
|
|
44
|
+
doc_name="$(basename "$doc" .md)"
|
|
45
|
+
depth="$(grep -m1 '^\*\*Modeling Depth:\*\*' "$doc" | sed 's/.*\*\*Modeling Depth:\*\* *//' | cut -d' ' -f1 || true)"
|
|
46
|
+
docnorm="$(echo "$doc_name" | sed 's/^module[-_]//' | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
47
|
+
match_dir=""
|
|
48
|
+
while IFS= read -r d; do
|
|
49
|
+
norm="$(basename "$d" | tr '[:upper:]' '[:lower:]' | tr '-' '_')"
|
|
50
|
+
if [ "$norm" = "$docnorm" ]; then match_dir="$d"; break; fi
|
|
51
|
+
done < <(find src -mindepth 1 -maxdepth 1 -type d 2>/dev/null || true)
|
|
52
|
+
[ -n "$match_dir" ] || continue
|
|
53
|
+
if [ "$depth" = "adapter" ]; then
|
|
54
|
+
for layer in domain application; do
|
|
55
|
+
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 "*.ts" 2>/dev/null || true)
|
|
56
|
+
done
|
|
57
|
+
elif [ "$depth" = "tactical" ]; then
|
|
58
|
+
while IFS= read -r impl; do
|
|
59
|
+
[ -n "$impl" ] || continue
|
|
60
|
+
simple="$(basename "$impl" .ts)"
|
|
61
|
+
cands="$simple
|
|
62
|
+
$(echo "$simple" | sed -E 's/Impl$//')"
|
|
63
|
+
found=""
|
|
64
|
+
while IFS= read -r cand; do
|
|
65
|
+
[ -n "$cand" ] || continue
|
|
66
|
+
if grep -rqiE "interface ${cand}\\b|abstract class ${cand}\\b|${cand}" "$match_dir/domain" 2>/dev/null; then found=1; break; fi
|
|
67
|
+
done <<< "$cands"
|
|
68
|
+
# separator-stripped fallback: invoice-repository <-> InvoiceRepository
|
|
69
|
+
if [ -z "$found" ]; then
|
|
70
|
+
compact="$(printf '%s' "$simple" | tr -d '_-')"
|
|
71
|
+
if [ -n "$compact" ] && grep -rqil "$compact" "$match_dir/domain" 2>/dev/null; then found=1; fi
|
|
72
|
+
fi
|
|
73
|
+
if [ -z "$found" ]; then report "$impl" "1" "interface-ownership" "adapter without a domain/-owned interface (${simple})"; fi
|
|
74
|
+
done < <(find "$match_dir/infrastructure" -type f \( -iname "*repository*.ts" -o -iname "*gateway*.ts" -o -iname "*service*.ts" \) 2>/dev/null || true)
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ "$VIOLATIONS" -gt 0 ]; then echo "model-judgment (ts): ${VIOLATIONS} violation(s)" >&2; exit 1; fi
|
|
80
|
+
echo "model-judgment (ts): all gates passed (dip-domain, txn-placement, interface-ownership, adapter-purity)"
|
|
81
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_runtime_reachability.sh — TypeScript (ADR-009)
|
|
4
|
+
#
|
|
5
|
+
# Every service file under src/<module>/application/ must be referenced from
|
|
6
|
+
# production code outside its own module and outside tests. References inside
|
|
7
|
+
# the service's own module or in *.test.ts / __tests__ do not count.
|
|
8
|
+
#
|
|
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
|
+
|
|
17
|
+
VIOLATIONS=0
|
|
18
|
+
[ -d src ] || { echo "runtime-reachability (ts): no src/"; exit 0; }
|
|
19
|
+
|
|
20
|
+
while IFS= read -r svc; do
|
|
21
|
+
[ -n "$svc" ] || continue
|
|
22
|
+
case "$svc" in
|
|
23
|
+
*/*test*/|*.test.ts|*.spec.ts) continue ;;
|
|
24
|
+
esac
|
|
25
|
+
mod="$(echo "$svc" | sed 's|^src/||; s|/application/.*||')"
|
|
26
|
+
name="$(basename "$svc" .ts)"
|
|
27
|
+
# production references: whole src, excluding tests and the service's own module
|
|
28
|
+
refs="$(grep -rl "\b${name}\b" src 2>/dev/null | grep -vE "/__tests__/|\.(test|spec)\.ts$|/tests?/|^src/${mod}/" | grep -v "^src/${mod}/" | grep -cv '^$' || true)"
|
|
29
|
+
# also drop the file itself from any residual (own-module exclusion above already does)
|
|
30
|
+
if [ "${refs:-0}" -eq 0 ]; then
|
|
31
|
+
echo "$svc: [runtime-reachability] application service has no production caller outside its module (tests-only callers don't count — ADR-009)"
|
|
32
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
33
|
+
fi
|
|
34
|
+
done < <(find src -path "*/application/*.ts" -type f 2>/dev/null || true)
|
|
35
|
+
|
|
36
|
+
if [ "$VIOLATIONS" -gt 0 ]; then
|
|
37
|
+
echo "runtime-wiring (ts): ${VIOLATIONS} unwired service(s)" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
echo "runtime-reachability (ts): all application services reachable from production code"
|
|
41
|
+
exit 0
|