@boyingliu01/xp-gate 0.19.2 → 0.20.0
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/adapters/gate-4.sh +83 -18
- package/adapters/gate-8.sh +49 -5
- package/adapters/iac.sh +1 -1
- package/adapters/typescript.sh +15 -60
- package/bin/xp-gate.js +15 -16
- package/gate-10.sh +35 -2
- package/gate-4.sh +110 -21
- package/gate-8.sh +49 -5
- package/hooks/gate-10.sh +35 -2
- package/hooks/gate-4.sh +110 -21
- package/hooks/gate-8.sh +49 -5
- package/hooks/lib/jscpd-run.sh +63 -0
- package/hooks/lib/sprint-gate-report.sh +58 -0
- package/hooks/lib/test-failure.sh +152 -0
- package/hooks/lib/typecheck.sh +141 -0
- package/hooks/lib/validate-code-walkthrough.cjs +43 -5
- package/hooks/post-merge +100 -0
- package/hooks/pre-commit +401 -155
- package/hooks/pre-push +75 -19
- package/lib/__tests__/bootstrap.test.js +23 -41
- package/lib/__tests__/check-version.test.js +8 -1
- package/lib/__tests__/detect-deps.test.js +16 -0
- package/lib/__tests__/doctor-drift-round2.test.js +212 -0
- package/lib/__tests__/doctor-executed-surface.test.js +209 -0
- package/lib/__tests__/doctor-hook-drift.test.js +329 -0
- package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
- package/lib/__tests__/doctor-module-drift.test.js +206 -0
- package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
- package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
- package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
- package/lib/__tests__/doctor.test.js +155 -0
- package/lib/__tests__/gate-runner.test.js +19 -3
- package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
- package/lib/__tests__/init.test.js +44 -17
- package/lib/__tests__/install-skill-full-dir.test.js +151 -0
- package/lib/__tests__/install-skill.test.js +51 -132
- package/lib/__tests__/language-tools-detect.test.js +158 -0
- package/lib/__tests__/phase-transition.test.js +15 -1
- package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
- package/lib/__tests__/shared-paths.test.js +20 -0
- package/lib/__tests__/shared-utils.test.js +69 -0
- package/lib/__tests__/skill-flags.test.js +48 -0
- package/lib/__tests__/ui-detector.test.ts +6 -2
- package/lib/__tests__/uninstall.test.js +20 -0
- package/lib/__tests__/update-hooks.test.js +12 -0
- package/lib/__tests__/update-skill.test.js +7 -4
- package/lib/detect-deps.js +3 -20
- package/lib/doctor-tui.js +22 -5
- package/lib/doctor.js +1178 -31
- package/lib/init.js +184 -33
- package/lib/install-cmd.js +7 -4
- package/lib/install-skill.js +38 -95
- package/lib/language-tools.js +83 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-paths.js +5 -2
- package/lib/shared-utils.js +47 -3
- package/lib/uninstall.js +12 -0
- package/lib/update-hooks.js +4 -4
- package/lib/update-skill.js +6 -7
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/mutation/__tests__/gate-m.test.ts +66 -14
- package/mutation/__tests__/mutmut-runner.test.ts +91 -42
- package/mutation/__tests__/test-filter-parity.test.ts +37 -0
- package/mutation/gate-m.ts +21 -8
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/README.md +169 -4
- package/plugins/dsh/dsh-overlay.patch.yml +67 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +24 -19
- package/plugins/dsh/setup-dsh.ps1 +160 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/command.test.ts +66 -2
- package/plugins/dsh/src/command.ts +36 -14
- package/plugins/dsh/src/contract.test.ts +31 -1
- package/plugins/dsh/src/delphi-config.test.ts +190 -0
- package/plugins/dsh/src/delphi-config.ts +223 -0
- package/plugins/dsh/src/delphi-run.test.ts +181 -0
- package/plugins/dsh/src/delphi-run.ts +129 -0
- package/plugins/dsh/src/delphi.test.ts +222 -0
- package/plugins/dsh/src/delphi.ts +247 -0
- package/plugins/dsh/src/dialect.test.ts +59 -0
- package/plugins/dsh/src/dialect.ts +40 -0
- package/plugins/dsh/src/index.ts +70 -4
- package/plugins/dsh/src/skills.test.ts +26 -19
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/agents/delphi-architecture.md +1 -1
- package/plugins/qoder/agents/delphi-feasibility.md +1 -1
- package/plugins/qoder/agents/delphi-technical.md +1 -1
- package/plugins/qoder/plugin.json +2 -2
- package/plugins/qoder/skills/admin-template-guidelines/SKILL.md +6 -797
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-1.md +91 -0
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-2.md +155 -0
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-3.md +70 -0
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-4.md +154 -0
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-5.md +126 -0
- package/plugins/qoder/skills/admin-template-guidelines/references/rule-6.md +180 -0
- package/plugins/qoder/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +61 -28
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/delphi-review/INSTALL.md +97 -98
- package/plugins/qoder/skills/delphi-review/SKILL.md +170 -63
- package/plugins/qoder/skills/delphi-review/hooks.md +19 -0
- package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/qoder/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
- package/plugins/qoder/skills/delphi-review/references/requirements.md +402 -0
- package/plugins/qoder/skills/delphi-review/references/round-templates.md +134 -0
- package/plugins/qoder/skills/delphi-review/scripts/verify-consensus.sh +43 -0
- package/plugins/qoder/skills/delphi-review/tools.md +29 -0
- package/plugins/qoder/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/qoder/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/qoder/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/qoder/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/qoder/skills/grilling/SKILL.md +12 -0
- package/plugins/qoder/skills/ralph-loop/SKILL.md +87 -428
- package/plugins/qoder/skills/ralph-loop/references/components.md +113 -0
- package/plugins/qoder/skills/ralph-loop/references/workflow-details.md +99 -0
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +5 -5
- package/plugins/qoder/skills/sprint-flow/SKILL.md +255 -454
- package/plugins/qoder/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
- package/plugins/qoder/skills/sprint-flow/hooks.md +18 -0
- package/plugins/qoder/skills/sprint-flow/references/components/memory.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/components/middleware.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/plugins/qoder/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +0 -1
- package/plugins/qoder/skills/sprint-flow/references/orchestration-rules.md +34 -27
- package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +160 -123
- package/plugins/qoder/skills/sprint-flow/references/phase-3-build.md +203 -2
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +107 -34
- package/plugins/qoder/skills/sprint-flow/references/phase-5-ship.md +194 -12
- package/plugins/qoder/skills/sprint-flow/references/phase-6-close.md +139 -7
- package/plugins/qoder/skills/sprint-flow/references/phase-overview.md +486 -0
- package/plugins/qoder/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
- package/plugins/qoder/skills/sprint-flow/tools.md +29 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/skills/test-specification-alignment/SKILL.md +104 -601
- package/plugins/qoder/skills/to-issues/SKILL.md +1 -1
- package/principles/AGENTS.md +9 -8
- package/principles/__tests__/adapter-metrics.test.ts +292 -0
- package/principles/__tests__/analyzer.test.ts +29 -6
- package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
- package/principles/__tests__/baseline-prune.test.ts +338 -0
- package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
- package/principles/__tests__/boy-scout-integration.test.ts +97 -6
- package/principles/__tests__/boy-scout.test.ts +253 -7
- package/principles/__tests__/config-enforcement.test.ts +469 -0
- package/principles/__tests__/config-git-timeout.test.ts +66 -0
- package/principles/__tests__/direct-execution.test.ts +187 -0
- package/principles/__tests__/index.test.ts +16 -3
- package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
- package/principles/adapters/typescript.ts +613 -13
- package/principles/analyzer.ts +22 -0
- package/principles/baseline-prune.ts +217 -0
- package/principles/boy-scout.ts +481 -51
- package/principles/config.ts +362 -54
- package/principles/direct-execution.ts +60 -0
- package/principles/index.ts +40 -17
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
- package/principles/rules/clean-code/code-duplication.ts +8 -8
- package/principles/rules/clean-code/deep-nesting.ts +8 -8
- package/principles/rules/clean-code/god-class.ts +8 -8
- package/principles/rules/clean-code/large-file.ts +15 -11
- package/principles/rules/clean-code/long-function.ts +8 -8
- package/principles/rules/clean-code/magic-numbers.ts +10 -7
- package/principles/rules/clean-code/many-exports.ts +7 -7
- package/principles/rules/clean-code/missing-error-handling.ts +5 -5
- package/principles/rules/clean-code/too-many-params.ts +8 -8
- package/principles/rules/clean-code/unused-imports.ts +5 -5
- package/principles/rules/solid/dip.ts +18 -8
- package/principles/rules/solid/isp.ts +9 -8
- package/principles/rules/solid/lsp.ts +6 -6
- package/principles/rules/solid/ocp.ts +6 -5
- package/principles/rules/solid/srp.ts +7 -8
- package/principles/types.ts +15 -0
- package/scripts/__tests__/sync-package-content.test.js +28 -0
- package/scripts/delphi-external-review.cjs +193 -10
- package/scripts/sync-package-content.js +7 -4
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/delphi-review/SKILL.md +98 -14
- package/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/skills/delphi-review/references/requirements.md +6 -6
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/lib/__tests__/download-skill.test.js +0 -281
- package/lib/download-skill.js +0 -96
- package/plugins/qoder/skills/ralph-loop/references/components/memory.md +0 -55
- package/plugins/qoder/skills/ralph-loop/references/components/middleware.md +0 -54
- package/plugins/qoder/skills/ralph-loop/references/components/skill-invocations.md +0 -39
- package/plugins/qoder/skills/ralph-loop/references/components/system-prompt.md +0 -24
- package/plugins/qoder/skills/ralph-loop/references/components/tool-descriptions.md +0 -32
package/adapters/gate-4.sh
CHANGED
|
@@ -16,29 +16,97 @@ if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
|
16
16
|
else
|
|
17
17
|
# Get source files to check against principles
|
|
18
18
|
PRINCIPLES_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|kt|dart|swift|cpp|c|hpp|h|m|mm)$' || true)
|
|
19
|
+
|
|
20
|
+
# One path per line, into an array. Passing $PRINCIPLES_FILES unquoted relies on
|
|
21
|
+
# word splitting to turn the list into separate argv entries, which also splits
|
|
22
|
+
# any path containing a space -- handing the checker two bogus paths and checking
|
|
23
|
+
# neither. Quoting it would instead collapse the list into one argument. An array
|
|
24
|
+
# is the only form that does both. Same defect class as the --config fix (#457).
|
|
25
|
+
PRINCIPLES_ARGS=()
|
|
26
|
+
while IFS= read -r _principles_file; do
|
|
27
|
+
[ -n "$_principles_file" ] && PRINCIPLES_ARGS+=("$_principles_file")
|
|
28
|
+
done <<< "$PRINCIPLES_FILES"
|
|
19
29
|
|
|
20
30
|
if [ -n "$PRINCIPLES_FILES" ]; then
|
|
21
|
-
#
|
|
31
|
+
# Same anchoring as githooks/gate-4.sh: CHANGED_FILES paths are repo-root relative
|
|
32
|
+
# and the checker resolves them against its own working directory, so both the
|
|
33
|
+
# probes and the invocation run from the repo root. Resolving them against the
|
|
34
|
+
# hook's CWD lost the checker in single-language subdir mode (#478).
|
|
35
|
+
PRINCIPLES_BASE="${PROJECT_ROOT:-$(pwd)}"
|
|
36
|
+
|
|
22
37
|
PRINCIPLES_DIR=""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
for _principles_candidate in \
|
|
39
|
+
"$PRINCIPLES_BASE/.xp-gate/modules/principles" \
|
|
40
|
+
"$PRINCIPLES_BASE/src/principles" \
|
|
41
|
+
"$(pwd)/.xp-gate/modules/principles" \
|
|
42
|
+
"$(pwd)/src/principles" \
|
|
43
|
+
"$HOME/.config/xp-gate/modules/principles"; do
|
|
44
|
+
if [ -f "$_principles_candidate/index.ts" ]; then
|
|
45
|
+
PRINCIPLES_DIR="$_principles_candidate"
|
|
46
|
+
break
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
30
49
|
|
|
31
50
|
if [ -n "$PRINCIPLES_DIR" ]; then
|
|
32
51
|
echo "Checking Clean Code + SOLID principles..."
|
|
33
52
|
|
|
34
53
|
if command -v npx > /dev/null 2>&1; then
|
|
35
|
-
# Run principles checker and store results
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
# Run principles checker and store results.
|
|
55
|
+
#
|
|
56
|
+
# Do NOT wrap this in `if ...; then`: the checker's exit codes are
|
|
57
|
+
# deliberately distinct (0 clean, 1 ERROR-severity findings, >=2 tool
|
|
58
|
+
# failure), and an `if` collapses 1 and 2 into "failed". That sent real
|
|
59
|
+
# error-severity findings down the crash branch, which SKIPs and prints
|
|
60
|
+
# PASSED -- releasing exactly the most serious violations.
|
|
61
|
+
# The trailing `|| PRINCIPLES_EXIT=$?` captures the exit status without
|
|
62
|
+
# letting a non-zero return abort the hook under `set -e`. pre-commit itself
|
|
63
|
+
# runs with only `set -o pipefail` (no `set -e`, pinned by
|
|
64
|
+
# scripts/__tests__/gate-5-runner-error.test.ts AC-454-08), but this module is
|
|
65
|
+
# also sourced by consumer hooks that may set it, and a bare call followed by
|
|
66
|
+
# `PRINCIPLES_EXIT=$?` never reaches the assignment under `set -e`. `|| true`
|
|
67
|
+
# would erase the status -- which is exactly the distinction this gate depends
|
|
68
|
+
# on.
|
|
69
|
+
PRINCIPLES_EXIT=0
|
|
70
|
+
# Keep stderr: it carries the checker's configuration warnings -- a rejected
|
|
71
|
+
# threshold or an out-of-vocabulary severity says so here and nowhere else.
|
|
72
|
+
# Sending it to /dev/null made an exit-2 SKIP undiagnosable and hid every
|
|
73
|
+
# .principlesrc typo (Delphi walkthrough MC-03). Captured to a file so a clean
|
|
74
|
+
# run stays quiet, then replayed only when the checker had a problem.
|
|
75
|
+
# `cd || exit 2`: a base we cannot enter means the checker never ran, which is
|
|
76
|
+
# this module's tool-failure code. Falling through would return the exit status
|
|
77
|
+
# of the failed cd (1), and 1 asserts "I examined files and found errors" -- an
|
|
78
|
+
# empty report at 1 reads as a clean pass.
|
|
79
|
+
#
|
|
80
|
+
# The report is a PER-INVOCATION mktemp, not a shared `/tmp/principles-output.json`:
|
|
81
|
+
# two commits racing on one machine (two worktrees, a hook and a CI job) overwrote
|
|
82
|
+
# each other's report before it was counted, so a run with ERROR findings could
|
|
83
|
+
# read a clean report and PASS (#457). PRINCIPLES_JSON is exported so a caller can
|
|
84
|
+
# point a harness at the same file.
|
|
85
|
+
PRINCIPLES_STDERR=$(mktemp)
|
|
86
|
+
PRINCIPLES_JSON=$(mktemp)
|
|
87
|
+
export PRINCIPLES_JSON
|
|
88
|
+
( cd "$PRINCIPLES_BASE" 2>/dev/null || exit 2
|
|
89
|
+
npx tsx "$PRINCIPLES_DIR/index.ts" --files ${PRINCIPLES_ARGS[@]+"${PRINCIPLES_ARGS[@]}"} --format json ) > "$PRINCIPLES_JSON" 2>"$PRINCIPLES_STDERR" || PRINCIPLES_EXIT=$?
|
|
90
|
+
|
|
91
|
+
if [ -s "$PRINCIPLES_STDERR" ]; then
|
|
92
|
+
sed 's/^/ /' "$PRINCIPLES_STDERR"
|
|
93
|
+
fi
|
|
94
|
+
rm -f "$PRINCIPLES_STDERR"
|
|
95
|
+
|
|
96
|
+
if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
|
|
97
|
+
echo "⚠️ Warning: Principles checker execution failed"
|
|
98
|
+
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
99
|
+
GATE_4_STATUS="SKIP"
|
|
100
|
+
rm -f "$PRINCIPLES_JSON"
|
|
101
|
+
else
|
|
102
|
+
# Check severity levels. The reporter emits JSON.stringify(out, null, 2),
|
|
103
|
+
# i.e. `"severity": "warning"` WITH a space; tolerate any whitespace so a
|
|
104
|
+
# future minified format cannot silently zero these counts again (#444).
|
|
105
|
+
ERROR_COUNT=$(grep -cE '"severity"[[:space:]]*:[[:space:]]*"error"' "$PRINCIPLES_JSON" 2>/dev/null || true)
|
|
39
106
|
ERROR_COUNT=${ERROR_COUNT:-0}
|
|
40
|
-
WARNING_COUNT=$(grep -
|
|
107
|
+
WARNING_COUNT=$(grep -cE '"severity"[[:space:]]*:[[:space:]]*"warning"' "$PRINCIPLES_JSON" 2>/dev/null || true)
|
|
41
108
|
WARNING_COUNT=${WARNING_COUNT:-0}
|
|
109
|
+
rm -f "$PRINCIPLES_JSON"
|
|
42
110
|
|
|
43
111
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
|
44
112
|
echo ""
|
|
@@ -47,7 +115,8 @@ else
|
|
|
47
115
|
echo " - error-handling violations"
|
|
48
116
|
echo " - SOLID principle violations"
|
|
49
117
|
echo " - architectural violations"
|
|
50
|
-
|
|
118
|
+
( cd "$PRINCIPLES_BASE" 2>/dev/null || exit 2
|
|
119
|
+
npx tsx "$PRINCIPLES_DIR/index.ts" --files ${PRINCIPLES_ARGS[@]+"${PRINCIPLES_ARGS[@]}"} --format console )
|
|
51
120
|
GATE_4_STATUS="FAIL"
|
|
52
121
|
exit 1
|
|
53
122
|
fi
|
|
@@ -57,10 +126,6 @@ else
|
|
|
57
126
|
if [ "$WARNING_COUNT" -gt 0 ]; then
|
|
58
127
|
echo "ℹ️ $WARNING_COUNT warnings found (will be handled by Boy Scout Rule)."
|
|
59
128
|
fi
|
|
60
|
-
else
|
|
61
|
-
echo "⚠️ Warning: Principles checker execution failed"
|
|
62
|
-
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
63
|
-
GATE_4_STATUS="SKIP"
|
|
64
129
|
fi
|
|
65
130
|
else
|
|
66
131
|
echo "ℹ️ npx not available - skipping principles check"
|
package/adapters/gate-8.sh
CHANGED
|
@@ -19,16 +19,60 @@ fi
|
|
|
19
19
|
if [ -n "$GITLEAKS_CMD" ]; then
|
|
20
20
|
GITLEAKS_CONFIG=""
|
|
21
21
|
if [ -f ".gitleaks.toml" ]; then
|
|
22
|
-
|
|
22
|
+
# gitleaks is a native binary and may not understand MSYS paths, so pass an
|
|
23
|
+
# absolute path in the host's own format when we can derive one (#449).
|
|
24
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
25
|
+
GITLEAKS_CONFIG="--config=$(cygpath -w "$(pwd)/.gitleaks.toml")"
|
|
26
|
+
else
|
|
27
|
+
GITLEAKS_CONFIG="--config=$(pwd)/.gitleaks.toml"
|
|
28
|
+
fi
|
|
23
29
|
fi
|
|
24
30
|
|
|
25
|
-
#
|
|
26
|
-
|
|
31
|
+
# Scan the INDEX, not history. gitleaks 8.x `git --pre-commit` walks committed
|
|
32
|
+
# revisions and reports "0 commits scanned" on a pre-commit run -- so a staged
|
|
33
|
+
# secret was never seen and Gate 8 always passed. `git --staged` scans
|
|
34
|
+
# `git diff --cached`, which is exactly the pre-commit payload (#449).
|
|
35
|
+
GITLEAKS_REPORT="${TMPDIR:-/tmp}/gitleaks-report.json"
|
|
36
|
+
rm -f "$GITLEAKS_REPORT"
|
|
37
|
+
GITLEAKS_OUTPUT=$($GITLEAKS_CMD git --staged --redact --no-banner $GITLEAKS_CONFIG --report-format=json --report-path="$GITLEAKS_REPORT" 2>&1)
|
|
27
38
|
GITLEAKS_EXIT=$?
|
|
28
39
|
|
|
29
40
|
if [ "$GITLEAKS_EXIT" -eq 0 ]; then
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
# Decide SKIP from the REPORT ARTIFACT, not from log wording. Parsing stdout
|
|
42
|
+
# meant a gitleaks message change would silently turn a no-evidence run into
|
|
43
|
+
# a PASS -- the same fail-open class this gate just fixed (#449).
|
|
44
|
+
# The report exists and is a JSON array of findings; `[]` == clean scan.
|
|
45
|
+
GITLEAKS_FINDINGS="?"
|
|
46
|
+
if [ -f "$GITLEAKS_REPORT" ]; then
|
|
47
|
+
# A report that is not shaped like a JSON array is no evidence: leave the
|
|
48
|
+
# '?' sentinel so the gate reports SKIP, never a PASS it cannot support
|
|
49
|
+
# (the fail-closed-on-the-claim rule #449 established).
|
|
50
|
+
if grep -q '^\[' "$GITLEAKS_REPORT" 2>/dev/null; then
|
|
51
|
+
# Count occurrences, not matching lines: `grep -c` prints its own zero AND
|
|
52
|
+
# exits 1 on a clean report, so the old `|| echo 0` handed the verdict table
|
|
53
|
+
# $'0\n0' -- no branch matched and a clean scan was BLOCKED as if it leaked
|
|
54
|
+
# (#493). wc -l emits exactly one value, and `|| true` keeps the assignment
|
|
55
|
+
# errexit/pipefail-safe (#489).
|
|
56
|
+
GITLEAKS_FINDINGS=$(grep -o '"RuleID"' "$GITLEAKS_REPORT" 2>/dev/null | wc -l | tr -d '[:space:]') || true
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [ "$GITLEAKS_FINDINGS" = "0" ]; then
|
|
61
|
+
echo " ✅ PASSED - No secrets detected."
|
|
62
|
+
GATE_8_STATUS="PASS"
|
|
63
|
+
elif [ "$GITLEAKS_FINDINGS" = "?" ]; then
|
|
64
|
+
# Exit 0 but no readable report: we have no evidence the scan happened.
|
|
65
|
+
# Fail closed on the *claim*: report SKIP, never PASS.
|
|
66
|
+
echo " ⚠️ gitleaks exited 0 but produced no readable report"
|
|
67
|
+
echo " ⏭️ SKIPPED - secret scanning produced no evidence (not a pass)"
|
|
68
|
+
GATE_8_STATUS="SKIP"
|
|
69
|
+
else
|
|
70
|
+
echo " ❌ BLOCKED - gitleaks reported $GITLEAKS_FINDINGS finding(s)"
|
|
71
|
+
echo "$GITLEAKS_OUTPUT"
|
|
72
|
+
echo ""
|
|
73
|
+
echo "Remediation: remove the secret, or allowlist a false positive in .gitleaks.toml."
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
32
76
|
elif [ "$GITLEAKS_EXIT" -eq 1 ]; then
|
|
33
77
|
# Secrets found — output details
|
|
34
78
|
echo "$GITLEAKS_OUTPUT"
|
package/adapters/iac.sh
CHANGED
|
@@ -124,7 +124,7 @@ run_static_analysis() {
|
|
|
124
124
|
|
|
125
125
|
# Print captured output then check for failures
|
|
126
126
|
cat "$checkov_log"
|
|
127
|
-
local failed_count=$(grep -c "FAILED for resource" "$checkov_log" 2>/dev/null ||
|
|
127
|
+
local failed_count=$(grep -c "FAILED for resource" "$checkov_log" 2>/dev/null || true)
|
|
128
128
|
rm -f "$checkov_log"
|
|
129
129
|
|
|
130
130
|
if [ "$failed_count" -gt 0 ]; then
|
package/adapters/typescript.sh
CHANGED
|
@@ -3,70 +3,25 @@
|
|
|
3
3
|
# TypeScript adapter for quality gates
|
|
4
4
|
|
|
5
5
|
run_static_analysis() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
npx tsc --noEmit
|
|
16
|
-
TSC_EXIT=$?
|
|
17
|
-
if [ $TSC_EXIT -ne 0 ]; then
|
|
18
|
-
HAS_ERRORS=1
|
|
6
|
+
# The type-check decision is shared with pre-commit's Gate 1 (#436): a project's
|
|
7
|
+
# own checker (typecheck script, or GATE_TS_TYPECHECK_CMD) is used when declared,
|
|
8
|
+
# otherwise the pre-#436 `npx tsc --noEmit` path. This adapter's contract predates
|
|
9
|
+
# that split, so it keeps reporting only 0 (clean/skipped) or 1 (type errors).
|
|
10
|
+
local lib_dir
|
|
11
|
+
lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)"
|
|
12
|
+
if [ -n "$lib_dir" ] && [ -f "$lib_dir/typecheck.sh" ]; then
|
|
13
|
+
# shellcheck source=/dev/null
|
|
14
|
+
source "$lib_dir/typecheck.sh"
|
|
19
15
|
fi
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
local HAS_TESTS=false
|
|
25
|
-
if [ -d "src/__tests__" ] || [ -d "src/tests" ] || [ -d "tests" ] || [ -d "__tests__" ]; then
|
|
26
|
-
HAS_TESTS=true
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
if [ "$HAS_TESTS" = true ] && [ -f "tsconfig.json" ]; then
|
|
30
|
-
# Check if tsconfig.json already includes test files
|
|
31
|
-
if npx tsc --noEmit --listFiles 2>/dev/null | grep -qE '__tests__/|\.test\.ts|\.spec\.ts'; then
|
|
32
|
-
: # Test files already checked — no additional pass needed
|
|
33
|
-
elif [ -f "tsconfig.tests.json" ]; then
|
|
34
|
-
echo "Checking test files with tsconfig.tests.json..."
|
|
35
|
-
npx tsc --noEmit --project tsconfig.tests.json
|
|
36
|
-
TSC_TEST_EXIT=$?
|
|
37
|
-
if [ $TSC_TEST_EXIT -ne 0 ]; then
|
|
38
|
-
HAS_ERRORS=1
|
|
39
|
-
fi
|
|
40
|
-
else
|
|
41
|
-
# Generate a temp tsconfig that includes test files for the additional pass
|
|
42
|
-
local TSC_TEST_EXIT=0
|
|
43
|
-
echo "Checking test files for type errors (generating temp tsconfig)..."
|
|
44
|
-
npx tsc --noEmit 2>/dev/null
|
|
45
|
-
# Create temp tsconfig extending the original but with test files included
|
|
46
|
-
local TEMP_TSCONFIG=".tsconfig.withtests.json"
|
|
47
|
-
if [ ! -f "$TEMP_TSCONFIG" ]; then
|
|
48
|
-
node -e "
|
|
49
|
-
const cfg = JSON.parse(require('fs').readFileSync('tsconfig.json','utf8'));
|
|
50
|
-
delete cfg.exclude;
|
|
51
|
-
cfg.include = cfg.include || ['src/**/*'];
|
|
52
|
-
cfg.include.push('src/**/__tests__/**','src/**/*.test.ts','src/**/*.spec.ts');
|
|
53
|
-
require('fs').writeFileSync('$TEMP_TSCONFIG', JSON.stringify(cfg, null, 2));
|
|
54
|
-
" 2>/dev/null
|
|
55
|
-
npx tsc --noEmit --project "$TEMP_TSCONFIG"
|
|
56
|
-
TSC_TEST_EXIT=$?
|
|
57
|
-
rm -f "$TEMP_TSCONFIG"
|
|
58
|
-
fi
|
|
59
|
-
if [ $TSC_TEST_EXIT -ne 0 ]; then
|
|
60
|
-
echo ""
|
|
61
|
-
echo "❌ BLOCKED - TYPE ERRORS in test files"
|
|
62
|
-
echo "Your project's tsconfig.json excludes test files from type checking."
|
|
63
|
-
echo "Fix the type errors above or add a tsconfig.tests.json to customise test file checking."
|
|
64
|
-
HAS_ERRORS=1
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
17
|
+
if ! command -v run_typescript_typecheck >/dev/null 2>&1; then
|
|
18
|
+
echo "typecheck.sh not resolvable, skipping TypeScript static analysis"
|
|
19
|
+
return 0
|
|
67
20
|
fi
|
|
68
21
|
|
|
69
|
-
|
|
22
|
+
run_typescript_typecheck
|
|
23
|
+
[ $? -eq 1 ] && return 1
|
|
24
|
+
return 0
|
|
70
25
|
}
|
|
71
26
|
|
|
72
27
|
run_lint() {
|
package/bin/xp-gate.js
CHANGED
|
@@ -6,6 +6,7 @@ const { updateSkill } = require('../lib/update-skill.js');
|
|
|
6
6
|
const { uninstallSkill } = require('../lib/uninstall-skill.js');
|
|
7
7
|
const { uninstall } = require('../lib/uninstall.js');
|
|
8
8
|
const { doctor } = require('../lib/doctor.js');
|
|
9
|
+
const { parseSkillFlags, SKILL_FLAGS_BY_COMMAND } = require('../lib/shared-utils.js');
|
|
9
10
|
const { checkDeps } = require('../lib/detect-deps.js');
|
|
10
11
|
const { migrate } = require('../lib/migrate.js');
|
|
11
12
|
const { handleBaseline } = require('../lib/baseline.js');
|
|
@@ -80,18 +81,18 @@ const COMMANDS = {
|
|
|
80
81
|
usage: 'xp-gate setup-global'
|
|
81
82
|
},
|
|
82
83
|
'install-skill': {
|
|
83
|
-
description: 'Install a xp-gate skill from
|
|
84
|
+
description: 'Install a xp-gate skill from the package bundle',
|
|
84
85
|
run: subargs => {
|
|
85
86
|
const name = subargs[0];
|
|
86
87
|
if (!name) {
|
|
87
88
|
console.error('Error: Skill name required');
|
|
88
|
-
console.error('Usage: xp-gate install-skill <name>[
|
|
89
|
+
console.error('Usage: xp-gate install-skill <name> [--verbose] [--force]');
|
|
89
90
|
process.exit(1);
|
|
90
91
|
}
|
|
91
|
-
const options = parseOptions(subargs.slice(1));
|
|
92
|
+
const options = parseOptions(subargs.slice(1), 'install-skill');
|
|
92
93
|
installSkill(name, options).then(code => process.exit(code));
|
|
93
94
|
},
|
|
94
|
-
usage: 'xp-gate install-skill <name>
|
|
95
|
+
usage: 'xp-gate install-skill <name> [--verbose] [--force]'
|
|
95
96
|
},
|
|
96
97
|
'update-skill': {
|
|
97
98
|
description: 'Update installed skill(s)',
|
|
@@ -101,12 +102,12 @@ const COMMANDS = {
|
|
|
101
102
|
let name, options;
|
|
102
103
|
if (allFlagIndex !== -1) {
|
|
103
104
|
// Remove --all from args before parsing, name is undefined
|
|
104
|
-
options = parseOptions([...subargs.slice(0, allFlagIndex), ...subargs.slice(allFlagIndex + 1)]);
|
|
105
|
+
options = parseOptions([...subargs.slice(0, allFlagIndex), ...subargs.slice(allFlagIndex + 1)], 'update-skill');
|
|
105
106
|
options.all = true;
|
|
106
107
|
name = undefined; // explicit: no single name when --all is used
|
|
107
108
|
} else {
|
|
108
109
|
name = subargs[0];
|
|
109
|
-
options = parseOptions(subargs.slice(1));
|
|
110
|
+
options = parseOptions(subargs.slice(1), 'update-skill');
|
|
110
111
|
}
|
|
111
112
|
updateSkill(name, options).then(code => process.exit(code));
|
|
112
113
|
},
|
|
@@ -121,7 +122,7 @@ const COMMANDS = {
|
|
|
121
122
|
console.error('Usage: xp-gate uninstall-skill <name>');
|
|
122
123
|
process.exit(1);
|
|
123
124
|
}
|
|
124
|
-
const options = parseOptions(subargs.slice(1));
|
|
125
|
+
const options = parseOptions(subargs.slice(1), 'uninstall-skill');
|
|
125
126
|
uninstallSkill(name, options).then(code => process.exit(code));
|
|
126
127
|
},
|
|
127
128
|
usage: 'xp-gate uninstall-skill <name> [--force]'
|
|
@@ -139,7 +140,7 @@ const COMMANDS = {
|
|
|
139
140
|
'doctor': {
|
|
140
141
|
description: 'Diagnose xp-gate installation health',
|
|
141
142
|
run: subargs => doctor(subargs).then(code => process.exit(code)),
|
|
142
|
-
usage: 'xp-gate doctor [--fix]'
|
|
143
|
+
usage: 'xp-gate doctor [--fix [--install-tools] | --sync-hooks [--force]] | --json | --format json (the two machine-readable modes print one JSON document and refuse --fix/--sync-hooks)'
|
|
143
144
|
},
|
|
144
145
|
'ui-review': {
|
|
145
146
|
description: 'Run UI review for non-sprint developers (generates .ui-gate-result.json)',
|
|
@@ -487,14 +488,12 @@ function printStatsTable(stats) {
|
|
|
487
488
|
console.log('└──────────────────┴────────┴────────┴────────────┘');
|
|
488
489
|
}
|
|
489
490
|
|
|
490
|
-
function parseOptions(args) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (arg === '--all') options.all = true;
|
|
497
|
-
if (arg === '--check') options.check = true;
|
|
491
|
+
function parseOptions(args, command) {
|
|
492
|
+
const { options, unknown } = parseSkillFlags(args, command);
|
|
493
|
+
if (unknown.length > 0) {
|
|
494
|
+
console.error(`Unknown argument(s) for ${command}: ${unknown.join(', ')}`);
|
|
495
|
+
console.error(`Supported flags: ${(SKILL_FLAGS_BY_COMMAND[command] || []).join(', ') || '(none)'}`);
|
|
496
|
+
process.exit(1);
|
|
498
497
|
}
|
|
499
498
|
return options;
|
|
500
499
|
}
|
package/gate-10.sh
CHANGED
|
@@ -33,15 +33,39 @@ else
|
|
|
33
33
|
echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
|
|
34
34
|
GATE_10_STATUS="SKIP"
|
|
35
35
|
else
|
|
36
|
+
# One path per line into an array. Quoting the newline-joined list would hand
|
|
37
|
+
# semgrep ONE argument containing newlines -> "Invalid scanning root" once
|
|
38
|
+
# >=2 files are staged (#490). Same defect class as the Gate 4 --files fix
|
|
39
|
+
# (#457): an array is the only form that keeps spaces intact AND splits files.
|
|
40
|
+
#
|
|
41
|
+
# Each element gets a ./ prefix: a tracked path may legitimately start with
|
|
42
|
+
# '-' (`git add -- ./-o.ts`), and bare in argv that element reads as an option
|
|
43
|
+
# and rewrites the scan it was meant to constrain. ./ is a path to every
|
|
44
|
+
# argument parser.
|
|
45
|
+
#
|
|
46
|
+
# The unquoted heredoc below expands $SEMGREP_FILES once, then feeds literal
|
|
47
|
+
# lines to `read`; bash never rescans the result for separators. Its correct
|
|
48
|
+
# reading rests on one property of the producer above, not of the loop:
|
|
49
|
+
# `git diff --cached --name-only` terminates paths with LF and quotes control
|
|
50
|
+
# characters (a CR in a name arrives as the four bytes \215, never as a bare
|
|
51
|
+
# CR), so one line is exactly one path. Asserted for spaces and a leading '-'
|
|
52
|
+
# by AC-490-04.
|
|
53
|
+
SEMGREP_ARGS=()
|
|
54
|
+
while IFS= read -r _semgrep_file; do
|
|
55
|
+
[ -n "$_semgrep_file" ] && SEMGREP_ARGS+=("./$_semgrep_file")
|
|
56
|
+
done <<EOF
|
|
57
|
+
$SEMGREP_FILES
|
|
58
|
+
EOF
|
|
59
|
+
|
|
36
60
|
# Run semgrep with JSON output
|
|
37
61
|
# --config=p/security-audit: explicit security ruleset
|
|
38
62
|
# --json: machine-readable output
|
|
39
63
|
# --disable-version-check: skip network call
|
|
40
|
-
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check "$
|
|
64
|
+
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check "${SEMGREP_ARGS[@]}" 2>&1)
|
|
41
65
|
SEMGREP_EXIT=$?
|
|
42
66
|
|
|
43
67
|
if [ "$SEMGREP_EXIT" -eq 0 ]; then
|
|
44
|
-
echo " ✅ PASSED - No security vulnerabilities found."
|
|
68
|
+
echo " ✅ PASSED - No security vulnerabilities found (files scanned: ${#SEMGREP_ARGS[@]})."
|
|
45
69
|
GATE_10_STATUS="PASS"
|
|
46
70
|
elif [ "$SEMGREP_EXIT" -eq 1 ]; then
|
|
47
71
|
# Findings detected - parse JSON to categorize
|
|
@@ -96,7 +120,16 @@ console.log(' ['+severity+'] '+ruleId);console.log(' '+path+':'+line+' → '+m
|
|
|
96
120
|
fi
|
|
97
121
|
else
|
|
98
122
|
# semgrep runtime error (timeout, config error, etc.)
|
|
123
|
+
#
|
|
124
|
+
# SEMGREP_OUTPUT is captured above and was never shown here, so an intermittent
|
|
125
|
+
# ruleset-fetch failure looked identical to a broken install: the gate printed
|
|
126
|
+
# "runtime error" and kept the reason to itself (#475). The verdict stays SKIP --
|
|
127
|
+
# an operational failure must not block the commit, and must not be called a pass.
|
|
99
128
|
echo " ⚠️ semgrep exited with code ${SEMGREP_EXIT} — skipping gate"
|
|
129
|
+
if [ -n "$SEMGREP_OUTPUT" ]; then
|
|
130
|
+
echo " ── what semgrep reported ──"
|
|
131
|
+
printf '%s\n' "$SEMGREP_OUTPUT" | tail -10 | sed 's/^/ /'
|
|
132
|
+
fi
|
|
100
133
|
echo " ⏭️ SKIPPED - SAST (semgrep runtime error)"
|
|
101
134
|
GATE_10_STATUS="SKIP"
|
|
102
135
|
fi
|
package/gate-4.sh
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
# Reuses existing principles checker logic
|
|
4
4
|
# ============================================================================
|
|
5
5
|
|
|
6
|
+
# Run a TypeScript entrypoint under tsx without routing arguments through the
|
|
7
|
+
# npx shim: Windows cmd.exe caps command lines at 8191 chars and long staged
|
|
8
|
+
# file lists overflow it, turning a healthy run into a false BLOCK. Prefer the
|
|
9
|
+
# repo-local tsx CLI driven by node directly; keep npx as fallback for consumer
|
|
10
|
+
# repos relying on a global install. Defined here as well so this module also
|
|
11
|
+
# works when sourced outside pre-commit.
|
|
12
|
+
if ! declare -F run_tsx >/dev/null 2>&1; then
|
|
13
|
+
run_tsx() {
|
|
14
|
+
local _root="${PROJECT_ROOT:-$(pwd)}" _timeout=()
|
|
15
|
+
if [ -n "${RUN_TSX_TIMEOUT:-}" ]; then
|
|
16
|
+
_timeout=(timeout "$RUN_TSX_TIMEOUT")
|
|
17
|
+
fi
|
|
18
|
+
if [ -f "$_root/node_modules/tsx/dist/cli.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
19
|
+
"${_timeout[@]}" node "$_root/node_modules/tsx/dist/cli.mjs" "$@"
|
|
20
|
+
else
|
|
21
|
+
"${_timeout[@]}" npx tsx "$@"
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
fi
|
|
25
|
+
|
|
6
26
|
2>&1 echo ""
|
|
7
27
|
2>&1 echo "→ Gate 4: Principles checker (Clean Code + SOLID)..."
|
|
8
28
|
GATE_4_START=$(gate_start_ms)
|
|
@@ -16,29 +36,101 @@ if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
|
16
36
|
else
|
|
17
37
|
# Get source files to check against principles
|
|
18
38
|
PRINCIPLES_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|kt|dart|swift|cpp|c|hpp|h|m|mm)$' || true)
|
|
39
|
+
|
|
40
|
+
# One path per line, into an array. Passing $PRINCIPLES_FILES unquoted relies on
|
|
41
|
+
# word splitting to turn the list into separate argv entries, which also splits
|
|
42
|
+
# any path containing a space -- handing the checker two bogus paths and checking
|
|
43
|
+
# neither. Quoting it would instead collapse the list into one argument. An array
|
|
44
|
+
# is the only form that does both. Same defect class as the --config fix (#457).
|
|
45
|
+
PRINCIPLES_ARGS=()
|
|
46
|
+
while IFS= read -r _principles_file; do
|
|
47
|
+
[ -n "$_principles_file" ] && PRINCIPLES_ARGS+=("$_principles_file")
|
|
48
|
+
done <<< "$PRINCIPLES_FILES"
|
|
19
49
|
|
|
20
50
|
if [ -n "$PRINCIPLES_FILES" ]; then
|
|
21
|
-
#
|
|
51
|
+
# The checker resolves every path it is given -- and every path in CHANGED_FILES
|
|
52
|
+
# comes from `git diff`, so it is repo-root relative -- against its own working
|
|
53
|
+
# directory. Resolving against the hook's CWD broke in single-language subdir
|
|
54
|
+
# mode, where pre-commit has already cd'd into $PROJECT_SUBDIR: web/src/a.ts was
|
|
55
|
+
# looked up as web/web/src/a.ts, the probes below missed the checker that was
|
|
56
|
+
# installed one level up, and a working gate degraded to SKIPPED (#478).
|
|
57
|
+
# Everything this gate reads is therefore anchored at the repo root.
|
|
58
|
+
PRINCIPLES_BASE="${PROJECT_ROOT:-$(pwd)}"
|
|
59
|
+
|
|
22
60
|
PRINCIPLES_DIR=""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
61
|
+
for _principles_candidate in \
|
|
62
|
+
"$PRINCIPLES_BASE/.xp-gate/modules/principles" \
|
|
63
|
+
"$PRINCIPLES_BASE/src/principles" \
|
|
64
|
+
"$(pwd)/.xp-gate/modules/principles" \
|
|
65
|
+
"$(pwd)/src/principles" \
|
|
66
|
+
"$HOME/.config/xp-gate/modules/principles"; do
|
|
67
|
+
if [ -f "$_principles_candidate/index.ts" ]; then
|
|
68
|
+
PRINCIPLES_DIR="$_principles_candidate"
|
|
69
|
+
break
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
30
72
|
|
|
31
73
|
if [ -n "$PRINCIPLES_DIR" ]; then
|
|
32
74
|
echo "Checking Clean Code + SOLID principles..."
|
|
33
75
|
|
|
34
|
-
if command -v npx > /dev/null 2>&1; then
|
|
35
|
-
# Run principles checker and store results
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
76
|
+
if command -v npx > /dev/null 2>&1 || [ -f "$PRINCIPLES_BASE/node_modules/tsx/dist/cli.mjs" ]; then
|
|
77
|
+
# Run principles checker and store results.
|
|
78
|
+
#
|
|
79
|
+
# Do NOT wrap this in `if ...; then`: the checker's exit codes are
|
|
80
|
+
# deliberately distinct (0 clean, 1 ERROR-severity findings, >=2 tool
|
|
81
|
+
# failure), and an `if` collapses 1 and 2 into "failed". That sent real
|
|
82
|
+
# error-severity findings down the crash branch, which SKIPs and prints
|
|
83
|
+
# PASSED -- releasing exactly the most serious violations.
|
|
84
|
+
# The trailing `|| PRINCIPLES_EXIT=$?` captures the exit status without
|
|
85
|
+
# letting a non-zero return abort the hook under `set -e`. pre-commit itself
|
|
86
|
+
# runs with only `set -o pipefail` (no `set -e`, pinned by
|
|
87
|
+
# scripts/__tests__/gate-5-runner-error.test.ts AC-454-08), but this module is
|
|
88
|
+
# also sourced by consumer hooks that may set it, and a bare call followed by
|
|
89
|
+
# `PRINCIPLES_EXIT=$?` never reaches the assignment under `set -e`. `|| true`
|
|
90
|
+
# would erase the status -- which is exactly the distinction this gate depends
|
|
91
|
+
# on.
|
|
92
|
+
PRINCIPLES_EXIT=0
|
|
93
|
+
# Keep stderr: it carries the checker's configuration warnings -- a rejected
|
|
94
|
+
# threshold or an out-of-vocabulary severity says so here and nowhere else.
|
|
95
|
+
# Sending it to /dev/null made an exit-2 SKIP undiagnosable and hid every
|
|
96
|
+
# .principlesrc typo (Delphi walkthrough MC-03). Captured to a file so a clean
|
|
97
|
+
# run stays quiet, then replayed only when the checker had a problem.
|
|
98
|
+
#
|
|
99
|
+
# `cd || exit 2`: a base we cannot enter means the checker never ran, which is
|
|
100
|
+
# this module's tool-failure code. Falling through would return the exit status
|
|
101
|
+
# of the failed cd (1), and 1 asserts "I examined files and found errors" -- an
|
|
102
|
+
# empty report at 1 reads as a clean pass.
|
|
103
|
+
#
|
|
104
|
+
# The report is a PER-INVOCATION mktemp, not a shared `/tmp/principles-output.json`:
|
|
105
|
+
# two commits racing on one machine (two worktrees, a hook and a CI job) overwrote
|
|
106
|
+
# each other's report before it was counted, so a run with ERROR findings could
|
|
107
|
+
# read a clean report and PASS (#457). PRINCIPLES_JSON is exported so a caller can
|
|
108
|
+
# point a harness at the same file.
|
|
109
|
+
PRINCIPLES_STDERR=$(mktemp)
|
|
110
|
+
PRINCIPLES_JSON=$(mktemp)
|
|
111
|
+
export PRINCIPLES_JSON
|
|
112
|
+
( cd "$PRINCIPLES_BASE" 2>/dev/null || exit 2
|
|
113
|
+
run_tsx "$PRINCIPLES_DIR/index.ts" --files ${PRINCIPLES_ARGS[@]+"${PRINCIPLES_ARGS[@]}"} --format json ) > "$PRINCIPLES_JSON" 2>"$PRINCIPLES_STDERR" || PRINCIPLES_EXIT=$?
|
|
114
|
+
|
|
115
|
+
if [ -s "$PRINCIPLES_STDERR" ]; then
|
|
116
|
+
sed 's/^/ /' "$PRINCIPLES_STDERR"
|
|
117
|
+
fi
|
|
118
|
+
rm -f "$PRINCIPLES_STDERR"
|
|
119
|
+
|
|
120
|
+
if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
|
|
121
|
+
echo "⚠️ Warning: Principles checker execution failed"
|
|
122
|
+
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
123
|
+
GATE_4_STATUS="SKIP"
|
|
124
|
+
rm -f "$PRINCIPLES_JSON"
|
|
125
|
+
else
|
|
126
|
+
# Check severity levels. The reporter emits JSON.stringify(out, null, 2),
|
|
127
|
+
# i.e. `"severity": "warning"` WITH a space; tolerate any whitespace so a
|
|
128
|
+
# future minified format cannot silently zero these counts again (#444).
|
|
129
|
+
ERROR_COUNT=$(grep -cE '"severity"[[:space:]]*:[[:space:]]*"error"' "$PRINCIPLES_JSON" 2>/dev/null || true)
|
|
39
130
|
ERROR_COUNT=${ERROR_COUNT:-0}
|
|
40
|
-
WARNING_COUNT=$(grep -
|
|
131
|
+
WARNING_COUNT=$(grep -cE '"severity"[[:space:]]*:[[:space:]]*"warning"' "$PRINCIPLES_JSON" 2>/dev/null || true)
|
|
41
132
|
WARNING_COUNT=${WARNING_COUNT:-0}
|
|
133
|
+
rm -f "$PRINCIPLES_JSON"
|
|
42
134
|
|
|
43
135
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
|
44
136
|
echo ""
|
|
@@ -47,7 +139,8 @@ else
|
|
|
47
139
|
echo " - error-handling violations"
|
|
48
140
|
echo " - SOLID principle violations"
|
|
49
141
|
echo " - architectural violations"
|
|
50
|
-
|
|
142
|
+
( cd "$PRINCIPLES_BASE" 2>/dev/null || exit 2
|
|
143
|
+
run_tsx "$PRINCIPLES_DIR/index.ts" --files ${PRINCIPLES_ARGS[@]+"${PRINCIPLES_ARGS[@]}"} --format console )
|
|
51
144
|
GATE_4_STATUS="FAIL"
|
|
52
145
|
exit 1
|
|
53
146
|
fi
|
|
@@ -57,14 +150,10 @@ else
|
|
|
57
150
|
if [ "$WARNING_COUNT" -gt 0 ]; then
|
|
58
151
|
echo "ℹ️ $WARNING_COUNT warnings found (will be handled by Boy Scout Rule)."
|
|
59
152
|
fi
|
|
60
|
-
else
|
|
61
|
-
echo "⚠️ Warning: Principles checker execution failed"
|
|
62
|
-
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
63
|
-
GATE_4_STATUS="SKIP"
|
|
64
153
|
fi
|
|
65
154
|
else
|
|
66
|
-
echo "ℹ️ npx not available - skipping principles check"
|
|
67
|
-
echo "⏭️ SKIPPED - Principles check (no Node.js/
|
|
155
|
+
echo "ℹ️ npx/tsx not available - skipping principles check"
|
|
156
|
+
echo "⏭️ SKIPPED - Principles check (no Node.js/tsx)"
|
|
68
157
|
GATE_4_STATUS="SKIP"
|
|
69
158
|
fi
|
|
70
159
|
else
|