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,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 tarpaulin --version &>/dev/null; then
105
- TARP_OUT=$(cargo tarpaulin --out Html 2>&1 || true)
106
- COVERAGE_PCT=$(echo "$TARP_OUT" | grep -oE '[0-9]+(\.[0-9]+)?%' | head -1 | tr -d '%' || echo "")
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 tarpaulin output"
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-tarpaulin / grcov), skipping coverage check"
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
- # Look for matching TS files (by convention: module name maps to src/<module>/ or similar)
53
- if find "$SRC_DIR" -type f \( -name "*.ts" -o -name "*.tsx" \) 2>/dev/null | grep -qi "$MODULE_NAME" 2>/dev/null; then
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 format . --check 2>/dev/null; then
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 format . --check 2>/dev/null; then
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 ""