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,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
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# coverage.sh — TypeScript/JavaScript: real line coverage via the project's
|
|
4
|
+
# coverage-enabled test runner
|
|
5
|
+
#
|
|
6
|
+
# Runs the test suite with coverage enabled, using the first runner that is
|
|
7
|
+
# actually installed: vitest → jest → bun test --coverage → c8. All of them
|
|
8
|
+
# emit a text table with an "All files" summary row; the "% Lines" column is
|
|
9
|
+
# parsed from it (header-driven — istanbul-style tables have 5 percentage
|
|
10
|
+
# columns, bun's has 2). No heuristic grep counting.
|
|
11
|
+
#
|
|
12
|
+
# Usage (normally via the .pi/scripts/coverage.sh dispatcher):
|
|
13
|
+
# bash coverage.sh # measure coverage
|
|
14
|
+
# bash coverage.sh --gate # fail if lines < COVERAGE_THRESHOLD
|
|
15
|
+
# bash coverage.sh --lcov # report an existing lcov artifact, if any
|
|
16
|
+
#
|
|
17
|
+
# Env:
|
|
18
|
+
# COVERAGE_THRESHOLD minimum line-coverage % (default: 60)
|
|
19
|
+
# ============================================================================
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
|
|
22
|
+
COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-60}"
|
|
23
|
+
GATE=0
|
|
24
|
+
LCOV=0
|
|
25
|
+
|
|
26
|
+
for arg in "$@"; do
|
|
27
|
+
case "$arg" in
|
|
28
|
+
--gate) GATE=1 ;;
|
|
29
|
+
--lcov) LCOV=1 ;;
|
|
30
|
+
*) echo "unknown arg: $arg" >&2; exit 2 ;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
# Walk up from CWD to the project root (package.json).
|
|
35
|
+
ROOT="$(pwd)"
|
|
36
|
+
while [ "$ROOT" != "/" ] && [ ! -f "$ROOT/package.json" ]; do
|
|
37
|
+
ROOT="$(dirname "$ROOT")"
|
|
38
|
+
done
|
|
39
|
+
if [ ! -f "$ROOT/package.json" ]; then
|
|
40
|
+
echo "✗ No package.json found in this directory tree" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
cd "$ROOT" || exit 1
|
|
44
|
+
|
|
45
|
+
OUT_FILE="$(mktemp)"
|
|
46
|
+
trap 'rm -f "$OUT_FILE"' EXIT
|
|
47
|
+
|
|
48
|
+
run_and_capture() {
|
|
49
|
+
# shellcheck disable=SC2015
|
|
50
|
+
"$@" >"$OUT_FILE" 2>&1 || true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
echo "═══ Coverage (real test-runner coverage) ═══"
|
|
54
|
+
RUN=""
|
|
55
|
+
if [ -x "node_modules/.bin/vitest" ]; then
|
|
56
|
+
echo " runner: vitest"
|
|
57
|
+
run_and_capture node_modules/.bin/vitest run --coverage
|
|
58
|
+
RUN="vitest"
|
|
59
|
+
elif command -v vitest >/dev/null 2>&1; then
|
|
60
|
+
echo " runner: vitest (PATH)"
|
|
61
|
+
run_and_capture vitest run --coverage
|
|
62
|
+
RUN="vitest"
|
|
63
|
+
elif [ -x "node_modules/.bin/jest" ]; then
|
|
64
|
+
echo " runner: jest"
|
|
65
|
+
run_and_capture node_modules/.bin/jest --coverage
|
|
66
|
+
RUN="jest"
|
|
67
|
+
elif command -v jest >/dev/null 2>&1; then
|
|
68
|
+
echo " runner: jest (PATH)"
|
|
69
|
+
run_and_capture jest --coverage
|
|
70
|
+
RUN="jest"
|
|
71
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
72
|
+
echo " runner: bun test --coverage"
|
|
73
|
+
run_and_capture bun test --coverage
|
|
74
|
+
RUN="bun"
|
|
75
|
+
elif [ -x "node_modules/.bin/c8" ]; then
|
|
76
|
+
# c8 (istanbul) wraps the project's own test command.
|
|
77
|
+
echo " runner: c8 (npm test)"
|
|
78
|
+
run_and_capture node_modules/.bin/c8 --reporter=text npm test
|
|
79
|
+
RUN="c8"
|
|
80
|
+
else
|
|
81
|
+
echo "✗ No coverage-enabled test runner found (vitest/jest/bun/c8)" >&2
|
|
82
|
+
echo " run .pi/scripts/install_coverage_tools.sh for guidance" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Parse the "% Lines" column of the "All files" row from the runner's text
|
|
87
|
+
# table. Header-driven: locate the "% Lines" column in the header row, then
|
|
88
|
+
# read it from the "All files" summary row (istanbul-style tables list
|
|
89
|
+
# % Stmts, % Branch, % Funcs, % Lines; bun lists % Funcs, % Lines).
|
|
90
|
+
COVERAGE="$(python3 - "$OUT_FILE" <<'PY'
|
|
91
|
+
import sys, re
|
|
92
|
+
with open(sys.argv[1], encoding="utf-8", errors="replace") as fh:
|
|
93
|
+
lines = [ln.strip() for ln in fh]
|
|
94
|
+
|
|
95
|
+
header = next((ln for ln in lines if ln.startswith("File") and "%" in ln), None)
|
|
96
|
+
allrow = next((ln for ln in lines if ln.startswith("All files")), None)
|
|
97
|
+
if not header or not allrow:
|
|
98
|
+
m = re.search(r"([0-9]+(?:\.[0-9]+)?)%", lines[-1] if lines else "")
|
|
99
|
+
print(m.group(1) if m else "")
|
|
100
|
+
raise SystemExit
|
|
101
|
+
|
|
102
|
+
cols = [c.strip() for c in header.split("|")]
|
|
103
|
+
idx = next(
|
|
104
|
+
(i for i, c in enumerate(cols) if c.replace("%", "").strip().lower() == "lines"),
|
|
105
|
+
None,
|
|
106
|
+
)
|
|
107
|
+
if idx is None:
|
|
108
|
+
idx = next((i for i, c in enumerate(cols) if c.startswith("%")), 1) + 1
|
|
109
|
+
|
|
110
|
+
cells = [c.strip() for c in allrow.split("|")]
|
|
111
|
+
if idx is not None and idx < len(cells):
|
|
112
|
+
val = cells[idx].replace("%", "").strip()
|
|
113
|
+
if val.replace(".", "", 1).isdigit():
|
|
114
|
+
print(val)
|
|
115
|
+
raise SystemExit
|
|
116
|
+
m = re.search(r"([0-9]+(?:\.[0-9]+)?)%", allrow)
|
|
117
|
+
print(m.group(1) if m else "")
|
|
118
|
+
PY
|
|
119
|
+
)"
|
|
120
|
+
|
|
121
|
+
tail -8 "$OUT_FILE"
|
|
122
|
+
echo ""
|
|
123
|
+
echo "Line coverage: ${COVERAGE:-unavailable}% (threshold: ${COVERAGE_THRESHOLD}%, runner: ${RUN})"
|
|
124
|
+
|
|
125
|
+
if [ -z "$COVERAGE" ]; then
|
|
126
|
+
echo "✗ Could not extract an 'All files' line-coverage percentage from ${RUN} output." >&2
|
|
127
|
+
echo " Ensure the runner is configured to emit a text coverage summary" >&2
|
|
128
|
+
echo " (vitest: coverage.reporter includes 'text'; jest: coverageReporters includes 'text')." >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [ "$LCOV" -eq 1 ]; then
|
|
133
|
+
LCOV_FOUND=""
|
|
134
|
+
for cand in "coverage/lcov.info" "lcov.info"; do
|
|
135
|
+
if [ -f "$cand" ]; then
|
|
136
|
+
echo "lcov artifact found: $cand"
|
|
137
|
+
LCOV_FOUND=1
|
|
138
|
+
break
|
|
139
|
+
fi
|
|
140
|
+
done
|
|
141
|
+
if [ -z "$LCOV_FOUND" ]; then
|
|
142
|
+
echo "⚠️ no lcov.info artifact found — configure your runner's lcov reporter" >&2
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if [ "$GATE" -eq 1 ]; then
|
|
147
|
+
if awk "BEGIN { exit !($COVERAGE >= $COVERAGE_THRESHOLD) }"; then
|
|
148
|
+
echo "✓ Coverage gate passed"
|
|
149
|
+
else
|
|
150
|
+
echo "✗ Coverage below threshold (${COVERAGE}% < ${COVERAGE_THRESHOLD}%)" >&2
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# install_coverage_tools.sh — TypeScript/JavaScript: install a real coverage
|
|
4
|
+
# tool
|
|
5
|
+
#
|
|
6
|
+
# The coverage gate (languages/typescript/coverage.sh) prefers the runner
|
|
7
|
+
# your project already uses (vitest → jest → bun → c8). This script installs
|
|
8
|
+
# whatever is missing for that chain:
|
|
9
|
+
# - vitest coverage needs the matching provider package
|
|
10
|
+
# - jest coverage is built-in (text reporter via config)
|
|
11
|
+
# - bun test --coverage is built into the Bun runtime (no install)
|
|
12
|
+
# - c8 is a universal fallback that wraps `npm test`
|
|
13
|
+
#
|
|
14
|
+
# Run this inside the project root (where package.json lives).
|
|
15
|
+
# ============================================================================
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
if [ ! -f "package.json" ]; then
|
|
19
|
+
echo "✗ No package.json found in the current directory" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if grep -q '"vitest"' package.json; then
|
|
24
|
+
if grep -q 'coverage-v8\|coverage-istanbul' package.json; then
|
|
25
|
+
echo "vitest + coverage provider present."
|
|
26
|
+
else
|
|
27
|
+
echo "vitest detected — installing @vitest/coverage-v8 (required for vitest --coverage)..."
|
|
28
|
+
npm install --save-dev @vitest/coverage-v8 \
|
|
29
|
+
|| { echo "✗ install failed — run: npm install --save-dev @vitest/coverage-v8" >&2; exit 1; }
|
|
30
|
+
fi
|
|
31
|
+
elif grep -q '"jest"' package.json; then
|
|
32
|
+
echo "jest detected — enable 'coverageReporters': ['text', 'lcov'] in jest config for the coverage gate."
|
|
33
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
34
|
+
echo "bun detected — 'bun test --coverage' is built in; no install needed."
|
|
35
|
+
else
|
|
36
|
+
echo "No vitest/jest/bun detected — installing c8 as a universal coverage runner..."
|
|
37
|
+
npm install --save-dev c8 \
|
|
38
|
+
|| { echo "✗ install failed — run: npm install --save-dev c8" >&2; exit 1; }
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
echo "TypeScript coverage tooling ready."
|
|
@@ -46,11 +46,15 @@ echo "--- Module-to-Implementation Mapping ---"
|
|
|
46
46
|
if [ -d ".pi/architecture/modules" ]; then
|
|
47
47
|
MAPPED=0
|
|
48
48
|
UNMAPPED=0
|
|
49
|
+
# Modules are mapped by canonical reference headers in source files
|
|
50
|
+
# (// Canonical: .pi/architecture/modules/<module>.md) — with a filename
|
|
51
|
+
# heuristic fallback for repos that name directories after modules.
|
|
52
|
+
REFERRED=$(grep -rEho '\.pi/architecture/modules/[a-z0-9-]+\.md' "$SRC_DIR" --include="*.ts" 2>/dev/null | sort -u) || true
|
|
49
53
|
for module_doc in .pi/architecture/modules/*.md; do
|
|
50
54
|
[ -f "$module_doc" ] || continue
|
|
51
55
|
MODULE_NAME=$(basename "$module_doc" .md)
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
if echo "$REFERRED" | grep -q "$MODULE_NAME\.md" 2>/dev/null \
|
|
57
|
+
|| find "$SRC_DIR" -type f \( -name "*.ts" -o -name "*.tsx" \) 2>/dev/null | grep -qi "$MODULE_NAME" 2>/dev/null; then
|
|
54
58
|
MAPPED=$((MAPPED + 1))
|
|
55
59
|
else
|
|
56
60
|
UNMAPPED=$((UNMAPPED + 1))
|
|
@@ -152,13 +152,13 @@ echo ""
|
|
|
152
152
|
echo "--- Format ---"
|
|
153
153
|
if [ -f "biome.json" ]; then
|
|
154
154
|
if command -v biome &>/dev/null; then
|
|
155
|
-
if biome
|
|
155
|
+
if biome ci . 2>/dev/null; then
|
|
156
156
|
pass "Format check passed (biome format)"
|
|
157
157
|
else
|
|
158
158
|
fail "Format check failed (biome format)"
|
|
159
159
|
fi
|
|
160
160
|
elif command -v npx &>/dev/null; then
|
|
161
|
-
if npx biome
|
|
161
|
+
if npx biome ci . 2>/dev/null; then
|
|
162
162
|
pass "Format check passed (npx biome format)"
|
|
163
163
|
else
|
|
164
164
|
fail "Format check failed (npx biome format)"
|
|
@@ -26,6 +26,7 @@ FAIL=0
|
|
|
26
26
|
log_pass() { echo -e " ${GREEN}✓ PASS${NC} $1"; PASS=$((PASS + 1)); }
|
|
27
27
|
log_fail() { echo -e " ${RED}✗ FAIL${NC} $1 — $2"; FAIL=$((FAIL + 1)); }
|
|
28
28
|
log_skip() { echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2"; PASS=$((PASS + 1)); }
|
|
29
|
+
log_warn() { echo -e " ${YELLOW}⚠ WARN${NC} $1 — $2"; }
|
|
29
30
|
|
|
30
31
|
echo "═══ Architecture Readiness Checks ═══"
|
|
31
32
|
echo ""
|
|
@@ -134,6 +135,34 @@ else
|
|
|
134
135
|
log_skip "Architecture conformance" "check_architecture_conformance.sh not found"
|
|
135
136
|
fi
|
|
136
137
|
|
|
138
|
+
|
|
139
|
+
# ── 7. ADR-009: runtime wiring gates ──
|
|
140
|
+
# a) ADR-acceptance: an IMPLEMENTED module that documents runtime integration
|
|
141
|
+
# must not leave its referenced feature ADR Proposed.
|
|
142
|
+
echo "Checking ADR-acceptance + legacy-removal (ADR-009)..."
|
|
143
|
+
ADR_GATE_FAIL=0
|
|
144
|
+
for doc in .pi/architecture/modules/*.md; do
|
|
145
|
+
[ -f "$doc" ] || continue
|
|
146
|
+
if ! grep -q "status: implemented" "$doc"; then continue; fi
|
|
147
|
+
if ! grep -qE "^##{2,3}.*Integration|^#{2,3}.*Dispatch" "$doc"; then continue; fi
|
|
148
|
+
refs=$(grep -oE "ADR-[0-9]{3}" "$doc" | sort -u | tr "\n" " ")
|
|
149
|
+
for adr in $refs; do
|
|
150
|
+
adrfile=$(ls .pi/architecture/decisions/${adr}-*.md 2>/dev/null | head -1)
|
|
151
|
+
[ -n "$adrfile" ] || continue
|
|
152
|
+
if grep -q "\*\*Status:\*\* Proposed" "$adrfile" || grep -qE "^\*\*Status:\*\* *Proposed" "$adrfile"; then
|
|
153
|
+
log_fail "ADR-acceptance (ADR-009)" "${doc##*/} is implemented + documents runtime integration but ${adr} is still Proposed — wire it and Accept the ADR"
|
|
154
|
+
ADR_GATE_FAIL=1
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
# b) legacy-removal warning (declared migration rule / legacy note)
|
|
158
|
+
if grep -qE "legacy .*still|still uses|GAP-.*migration" "$doc"; then
|
|
159
|
+
log_warn "legacy-removal (ADR-009)" "${doc##*/} declares a legacy path/migration rule — verify it is removed or delegating (validate-runtime-wiring.sh)"
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
162
|
+
if [ $ADR_GATE_FAIL -eq 0 ]; then
|
|
163
|
+
log_pass "ADR-acceptance (ADR-009)" "no implemented module left a runtime-integration ADR Proposed"
|
|
164
|
+
fi
|
|
165
|
+
|
|
137
166
|
# ── Summary ──
|
|
138
167
|
|
|
139
168
|
echo ""
|