@boyingliu01/xp-gate 0.32.0 → 0.32.1

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/gate-3.sh CHANGED
@@ -329,12 +329,26 @@ else
329
329
  # is reverted at the next publish, so grading it cannot be acted
330
330
  # on (doctorDelphi CCN=106 blocked every src/npm-package commit,
331
331
  # on HEAD too — no baseline entry could ever exist for it).
332
- # test __tests__/, *.test.*, *_test.{py,go}, *Test(s).java — recorded
333
- # in the report's test bucket, never in the baseline.
332
+ # test test dirs (tests/, test/, __tests__/), *.test.*, test_*.py,
333
+ # *_test.{py,go}, *Test(s).java — recorded in the report's test
334
+ # bucket, never in the baseline.
334
335
  # Both prefixes and the test pattern mirror ccn-report.cjs so the gate and
335
336
  # the writer cannot drift apart again (pinned by __tests__/gate-3-*.bats).
337
+ #
338
+ # #634: pytest's default is `python_files = test_*.py` under a `tests/`
339
+ # root. The previous pattern only knew the SUFFIX form `_test.{py,go}` and
340
+ # `*.test.*`, so a pytest-laid-out project had every test module graded as
341
+ # production source: test-helper complexity was reported as source debt and
342
+ # the ratchet could block on files the baseline deliberately never records
343
+ # (bucket === 'test').
344
+ # `(^|/)(tests?)/` — a tests/ or test/ directory anywhere in the tree
345
+ # `(^|/)test_[^/]*\.py$` — pytest default filenames at any depth
346
+ # Both are anchored with (^|/) so `latest/`, `contest/`, `attest/` and
347
+ # `my_test_dir/` are NOT caught (a leading `_?test_.*\.py$` would have
348
+ # swallowed `src/my_test_dir/bar.py`).
349
+ # Must stay equivalent to TEST_PATH in scripts/ccn-report.cjs.
336
350
  CC_FILES=$(echo "$CC_FILES" | grep -vE \
337
- '^"?src/npm-package/|^"?plugins/[^/]+/src/|(^|/)__tests__?/|\.test\.[cm]?[jt]sx?$|_test\.(py|go)$|Test(s)?\.java$' || true)
351
+ '^"?src/npm-package/|^"?plugins/[^/]+/src/|(^|/)__tests?__/|(^|/)(tests?)/|\.test\.[cm]?[jt]sx?$|(^|/)test_[^/]*\.py$|_test\.(py|go)$|Test(s)?\.java$' || true)
338
352
 
339
353
  if [ -n "$CC_FILES" ]; then
340
354
  echo "Checking complexity for source files..."
package/hooks/gate-3.sh CHANGED
@@ -329,12 +329,26 @@ else
329
329
  # is reverted at the next publish, so grading it cannot be acted
330
330
  # on (doctorDelphi CCN=106 blocked every src/npm-package commit,
331
331
  # on HEAD too — no baseline entry could ever exist for it).
332
- # test __tests__/, *.test.*, *_test.{py,go}, *Test(s).java — recorded
333
- # in the report's test bucket, never in the baseline.
332
+ # test test dirs (tests/, test/, __tests__/), *.test.*, test_*.py,
333
+ # *_test.{py,go}, *Test(s).java — recorded in the report's test
334
+ # bucket, never in the baseline.
334
335
  # Both prefixes and the test pattern mirror ccn-report.cjs so the gate and
335
336
  # the writer cannot drift apart again (pinned by __tests__/gate-3-*.bats).
337
+ #
338
+ # #634: pytest's default is `python_files = test_*.py` under a `tests/`
339
+ # root. The previous pattern only knew the SUFFIX form `_test.{py,go}` and
340
+ # `*.test.*`, so a pytest-laid-out project had every test module graded as
341
+ # production source: test-helper complexity was reported as source debt and
342
+ # the ratchet could block on files the baseline deliberately never records
343
+ # (bucket === 'test').
344
+ # `(^|/)(tests?)/` — a tests/ or test/ directory anywhere in the tree
345
+ # `(^|/)test_[^/]*\.py$` — pytest default filenames at any depth
346
+ # Both are anchored with (^|/) so `latest/`, `contest/`, `attest/` and
347
+ # `my_test_dir/` are NOT caught (a leading `_?test_.*\.py$` would have
348
+ # swallowed `src/my_test_dir/bar.py`).
349
+ # Must stay equivalent to TEST_PATH in scripts/ccn-report.cjs.
336
350
  CC_FILES=$(echo "$CC_FILES" | grep -vE \
337
- '^"?src/npm-package/|^"?plugins/[^/]+/src/|(^|/)__tests__?/|\.test\.[cm]?[jt]sx?$|_test\.(py|go)$|Test(s)?\.java$' || true)
351
+ '^"?src/npm-package/|^"?plugins/[^/]+/src/|(^|/)__tests?__/|(^|/)(tests?)/|\.test\.[cm]?[jt]sx?$|(^|/)test_[^/]*\.py$|_test\.(py|go)$|Test(s)?\.java$' || true)
338
352
 
339
353
  if [ -n "$CC_FILES" ]; then
340
354
  echo "Checking complexity for source files..."
@@ -26,6 +26,55 @@
26
26
  # than before the fix.
27
27
  JSCPD_DEFAULT_IGNORE_PATTERNS='(^|/)(node_modules|dist|build|target|coverage|out|\.next|\.nuxt|vendor)/|\.log$|\.txt$|\.(ya?ml)$|golden|baseline\.json$|evidence'
28
28
 
29
+ # The BUILD-OUTPUT half of the set above, alone (#649).
30
+ #
31
+ # The combined set answers "is this worth comparing for duplication", which is a
32
+ # question only Gate 2 asks. Filtering file list inputs with the whole set also
33
+ # drops *.yaml, *.yml, *.txt and *.log -- and those are perfectly real project
34
+ # files everywhere else. Doing that broke a live test on the first attempt:
35
+ # `YAML staged files activate lint and IaC but skip SAST` expects a staged
36
+ # infra/deployment.yaml to count as a code change, and whole-set filtering made
37
+ # the hook announce "No code files changed, skipping lint" for a commit that
38
+ # added a deployment manifest.
39
+ #
40
+ # So the two notions are separated here rather than shared:
41
+ #
42
+ # BUILD OUTPUT (this) - a directory tree that is generated or vendored:
43
+ # not our code for ANY gate. Used to filter file lists.
44
+ # NON-REFACTORABLE (the combined set) - files whose duplication is data shape:
45
+ # still project files, just not duplication candidates.
46
+ # Gate 2 only.
47
+ #
48
+ # Same directory names, one definition; a directory added to the build-output
49
+ # list reaches every gate at once, which is the point of #649.
50
+ # Directory names that are generated or vendored in any mainstream toolchain.
51
+ #
52
+ # The second group (caches, virtualenvs, Python bytecode) is here because the
53
+ # first version of this list did not have it and the same class of bug was one
54
+ # `git add -A` away: __pycache__ is written by CPython itself and .pytest_cache
55
+ # by pytest, so a Python project that commits its workspace stages .pyc-adjacent
56
+ # artefacts and every scanner would read them as source. None of these can hold
57
+ # hand-written project code.
58
+ #
59
+ # NOT included on purpose: bin/ and obj/ (real source trees in plenty of
60
+ # projects -- e.g. cmd/bin layout, C# projects where obj/ IS output but bin/ is
61
+ # often committed scripts) and .venv/venv (genuinely ambiguous: some projects
62
+ # keep a small committed venv for reproducibility). Adding a name here removes
63
+ # it from EVERY gate at once, so the list stays the names that are unambiguous.
64
+ JSCPD_BUILD_OUTPUT_PATTERNS='(^|/)(node_modules|dist|build|target|coverage|out|\.next|\.nuxt|vendor|__pycache__|\.pytest_cache|\.mypy_cache|\.ruff_cache|\.tox|\.turbo|\.cache)/'
65
+
66
+ # Is this path inside a dependency tree or a build-output directory?
67
+ #
68
+ # XP_GATE_JSCPD_DEFAULT_IGNORE=0 still disables it, so the escape hatch keeps
69
+ # covering "some project really does want its vendor/ tree linted".
70
+ jscpd_is_build_output() {
71
+ local _path="$1"
72
+ if [ "${XP_GATE_JSCPD_DEFAULT_IGNORE:-1}" = "0" ]; then
73
+ return 1
74
+ fi
75
+ printf '%s\n' "$_path" | grep -qE "$JSCPD_BUILD_OUTPUT_PATTERNS"
76
+ }
77
+
29
78
  # Human-readable form, printed by the gate so the baseline is auditable (#591
30
79
  # point 3). Silence about the default is the same defect as silence about a
31
80
  # skipped gate: the user cannot answer "what did it scan".
@@ -55,6 +104,49 @@ jscpd_is_default_ignored() {
55
104
  printf '%s\n' "$_path" | grep -qE "$JSCPD_DEFAULT_IGNORE_PATTERNS"
56
105
  }
57
106
 
107
+ # Drop dependency and build-output paths from a newline-separated file list on
108
+ # stdin, echoing what remains (#649).
109
+ #
110
+ # Every scanner that builds its own list from `git diff --cached` used to
111
+ # re-derive the exclusions by hand, and they drifted. The test-pairing check
112
+ # excluded node_modules/.next/.nuxt/dist/build/.turbo/.cache but not vendor,
113
+ # target, coverage or out -- so a staged `vendor/x.ts` was reported as a new
114
+ # source file with no test. Six more sites had no exclusions at all. The one
115
+ # place that got a shared ignore set (Gate 2) is the one that stopped having
116
+ # this bug.
117
+ #
118
+ # This is the shared answer: callers pipe their staged list through it and stop
119
+ # maintaining their own list. It reads stdin and writes stdout so it composes
120
+ # with whatever selection the caller already did:
121
+ #
122
+ # NEW_TS_FILES=$(git diff --cached --name-only --diff-filter=A \
123
+ # | filter_project_paths | grep -E '\.(ts|tsx)$' || true)
124
+ #
125
+ # Deliberately a filter, not a generator: a caller that needs a different git
126
+ # query (only-added, only-modified) keeps it, and only the exclusion rule is
127
+ # shared. Fail-open on empty input so `|| true` pipelines stay quiet.
128
+ #
129
+ # ON THE COUPLING (Delphi round 1, MC-01/FC-01): yes, this makes Gate 2's
130
+ # ignore set load-bearing for every gate. Widening it now widens what every
131
+ # scanner skips. That risk is accepted deliberately, and the alternative is
132
+ # worse: the bug this fixes WAS a set of per-scanner definitions that drifted
133
+ # apart, so N independent sets would reproduce it N times. The guard against
134
+ # silent widening is that every change to the set lands in one reviewable
135
+ # place, and #649's tests pin the boundary cases (vendor/target/coverage/out
136
+ # ignored; src/lib/docs kept). If a gate ever needs a genuinely different
137
+ # notion of "our code", introduce a second named set HERE rather than letting a
138
+ # caller edit this one.
139
+ filter_project_paths() {
140
+ local _fp
141
+ while IFS= read -r _fp || [ -n "$_fp" ]; do
142
+ [ -n "$_fp" ] || continue
143
+ if ! jscpd_is_build_output "$_fp"; then
144
+ printf '%s\n' "$_fp"
145
+ fi
146
+ done
147
+ return 0
148
+ }
149
+
58
150
  # Echo the jscpd config the gate should pass, or nothing when the project has
59
151
  # none.
60
152
  #
package/hooks/pre-commit CHANGED
@@ -710,48 +710,37 @@ if [ "$is_protected" = "true" ]; then
710
710
  esac
711
711
  done
712
712
 
713
- # Doc-only changes: no source code files
713
+ # Doc-only / build-tooling-only changes need no VERSION bump.
714
+ #
715
+ # #624 defect 2: the split used to test ONLY `has_source`, which is true for
716
+ # any .py/.ts/... path INCLUDING `scripts/*`. So a commit that touched
717
+ # nothing but build tooling still fell into the version-check branch and was
718
+ # blocked. `has_real_source` is exactly "production source that needs a bump"
719
+ # — it was computed a few lines up but never consulted here. It must decide.
714
720
  if [ "$has_source" = "false" ]; then
715
721
  echo "✅ PASSED - Gate 0: Version Consistency Check (doc-only changes)"
722
+ elif [ "$has_real_source" = "false" ]; then
723
+ echo "✅ PASSED - Gate 0: Version Consistency Check (build-tooling-only changes)"
716
724
  else
717
- # Check for bypass: commit message prefix (read from COMMIT_EDITMSG during pre-commit hook)
718
- COMMIT_MSG=$(cat "$(git rev-parse --git-dir)/COMMIT_EDITMSG" 2>/dev/null | head -1 || echo "")
719
- if echo "$COMMIT_MSG" | grep -qE '^\[skip-version-check\]'; then
720
- if echo "$COMMIT_MSG" | grep -qE '^\[skip-version-check\].*(chore:|docs:|release:)'; then
721
- if [ "$has_real_source" = "false" ]; then
722
- echo "✅ PASSED - Gate 0: Version Consistency Check (bypass: chore/docs/release)"
723
- else
724
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
725
- VERSION CONSISTENCY CHECK (Gate 0)
726
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
727
- Branch: $CURRENT_BRANCH
728
- ERROR: [skip-version-check] bypass used but production source files detected.
729
-
730
- Bypass only allows build-tooling changes (adapters/, scripts/, hooks/).
731
- Production source files require VERSION/CHANGELOG update.
732
-
733
- Files: $(echo "$STAGED_FILES" | tr '\n' ', ' | sed 's/,$//')
734
- "
735
- exit 1
736
- fi
737
- else
738
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
739
- VERSION CONSISTENCY CHECK (Gate 0)
740
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
741
- Branch: $CURRENT_BRANCH
742
- ERROR: [skip-version-check] bypass prefix invalid.
743
-
744
- Allowed prefixes: chore:, docs:, release:
745
- Example: [skip-version-check] chore: update dependencies
746
- "
747
- exit 1
748
- fi
749
- else
750
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
725
+ # Production source on a protected branch without VERSION/CHANGELOG.
726
+ #
727
+ # #624 defect 1: this branch used to offer a `[skip-version-check]` message
728
+ # prefix as a bypass, read from `.git/COMMIT_EDITMSG`. That cannot work:
729
+ # git runs pre-commit BEFORE it builds the message, so the file does not
730
+ # exist yet (`-m`, `-F` and the interactive editor path all measured on
731
+ # git 2.54), and the hook gets no arguments. What the old code read was the
732
+ # PREVIOUS commit's message, so the prefix never matched — the documented
733
+ # recipe silently could not clear the gate, and --no-verify was the only
734
+ # way through. Rather than keep a bypass whose input this hook structurally
735
+ # cannot see, the escape hatch is the one that actually works and is
736
+ # already documented in CHANGELOG: the SKIP_VERSION_CHECK=1 environment
737
+ # variable (handled earlier in this Gate 0 block).
738
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
751
739
  VERSION CONSISTENCY CHECK (Gate 0)
752
740
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
753
741
  Branch: $CURRENT_BRANCH
754
- ERROR: Committing to a protected branch without VERSION/CHANGELOG update.
742
+ ERROR: Committing production source to a protected branch without a
743
+ VERSION/CHANGELOG update.
755
744
 
756
745
  This bypasses the ship workflow and causes version drift.
757
746
  Before committing:
@@ -759,18 +748,19 @@ Before committing:
759
748
  2. Add entry to CHANGELOG.md
760
749
  3. Run: node scripts/sync-version.cjs
761
750
 
762
- Or use the bypass prefix — the commit message FIRST LINE must START with:
763
- [skip-version-check] chore: <description>
764
- Allowed types: chore: docs: release:. The bypass only permits build
765
- tooling changes (adapters/, scripts/, hooks/); production source files
766
- still require a VERSION/CHANGELOG update.
751
+ Build-tooling-only changes (adapters/, scripts/, hooks/) need NO version bump
752
+ and pass this gate automatically — no marker required.
753
+
754
+ If you genuinely must land production source without a version bump, the
755
+ supported escape hatch is the env var (a message prefix cannot work here
756
+ because pre-commit runs before git builds the message):
757
+ SKIP_VERSION_CHECK=1 git commit ...
767
758
 
768
759
  Staged files: $(echo "$STAGED_FILES" | tr '\n' ', ' | sed 's/,$//')
769
760
  "
770
- GATE_0_STATUS="BLOCK"
771
- record_gate_audit "gate-0" "version-consistency" "$GATE_0_STATUS" "1" "$GATE_0_START"
772
- exit 1
773
- fi
761
+ GATE_0_STATUS="BLOCK"
762
+ record_gate_audit "gate-0" "version-consistency" "$GATE_0_STATUS" "1" "$GATE_0_START"
763
+ exit 1
774
764
  fi
775
765
  fi
776
766
  fi # closes SKIP_VERSION_CHECK if/else
@@ -809,7 +799,44 @@ echo ""
809
799
  MIRROR_PATH_FILTER='^src/npm-package/'
810
800
  DASHBOARD_PATH_FILTER='^dashboard/'
811
801
  RAW_CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
812
- CHANGED_FILES=$(printf '%s\n' "$RAW_CHANGED_FILES" | grep -v "$MIRROR_PATH_FILTER" | grep -v "$DASHBOARD_PATH_FILTER" || true)
802
+
803
+ # Dependencies and build output are not project code (#649).
804
+ #
805
+ # `git diff --cached` knows nothing about .gitignore: anything that reaches the
806
+ # index is listed. A project that vendors its dependencies, a CI fixture that
807
+ # commits after `npm install`, an accidental `git add -A`, or a monorepo's
808
+ # nested `packages/*/node_modules` therefore hands every downstream scanner a
809
+ # list containing third-party source, and each scanner judges it as the
810
+ # project's own.
811
+ #
812
+ # This has already cost two outages, one language branch at a time: #595 (Gate 1
813
+ # PowerShell arm blocked every commit once Playwright's node_modules was
814
+ # scanned for Write-Host) and #648 (Gate 1 JS/TS arm blocked on the bare
815
+ # `debugger;` in node_modules/typescript/dist/internal/utils.js). Both were
816
+ # fixed at the single site that happened to fail, so the next branch re-broke
817
+ # the same way. The filter belongs HERE, at the one place the list is built, so
818
+ # a scanner added tomorrow is safe by default instead of by memory.
819
+ #
820
+ # The exclusion itself is the shared build-output list from jscpd-run.sh
821
+ # (jscpd_is_build_output), so the directory names have one definition across the
822
+ # gates. Deliberately NOT the full Gate 2 ignore set: that one also drops
823
+ # *.yaml/*.yml/*.txt/*.log because repetition there is data shape rather than a
824
+ # refactoring target, and using it here would make a commit that only adds
825
+ # infra/deployment.yaml look like "No code files changed" -- skipping the IaC
826
+ # and lint gates that exist to scan exactly that file. The XP_GATE_JSCPD_DEFAULT_IGNORE=0
827
+ # escape hatch still applies.
828
+ #
829
+ # RAW_CHANGED_FILES stays unfiltered on purpose: it is the only truthful answer
830
+ # to "did the user stage anything at all", and a future "you accidentally
831
+ # committed dependencies" warning needs to see what this filter removed.
832
+ CHANGED_FILES=$(printf '%s\n' "$RAW_CHANGED_FILES" \
833
+ | grep -v "$MIRROR_PATH_FILTER" | grep -v "$DASHBOARD_PATH_FILTER" \
834
+ | {
835
+ while IFS= read -r _cf; do
836
+ [ -n "$_cf" ] || continue
837
+ jscpd_is_build_output "$_cf" || printf '%s\n' "$_cf"
838
+ done
839
+ } || true)
813
840
 
814
841
  # REQ-F2: one commit-level docs-only verdict, computed once and reused by the
815
842
  # heavy gates (2/5/9/12). Light gates never consult it — a secret in a README
@@ -844,7 +871,22 @@ any_changed_files_match() {
844
871
  local pattern
845
872
 
846
873
  for pattern in "$@"; do
847
- if printf '%s\n' "$CHANGED_FILES" | grep -qE -- "$pattern"; then
874
+ # Here-string, not `printf | grep -q`. Under the `set -o pipefail` above,
875
+ # `grep -q` exits the moment it matches, the writer takes SIGPIPE, and the
876
+ # pipeline reports 141 -- so the `if` reads "no match" even though grep DID
877
+ # match. Whether that happens depends on whether the list fits in the pipe
878
+ # buffer, which is why it showed up as a size-dependent, flaky SKIP.
879
+ #
880
+ # It was not theoretical: in the Cross-Platform CI first-commit fixture the
881
+ # staged list is long enough to trip it, and Gate 1 announced "No code files
882
+ # changed, skipping lint" for a commit that had staged 19 shell scripts --
883
+ # while Gate 2, reading the same variable, happily scanned them and reported
884
+ # duplicates. The hook log carried the fingerprint the whole time:
885
+ # `line 847: printf: Broken pipe`.
886
+ #
887
+ # A here-string has no writer process to be killed, so the match cannot be
888
+ # lost. Same reason `run_tsx` and the coverage probes avoid `| head`.
889
+ if grep -qE -- "$pattern" <<< "$CHANGED_FILES"; then
848
890
  return 0
849
891
  fi
850
892
  done
@@ -1524,7 +1566,7 @@ else
1524
1566
  TS_DEBUG_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx)$' | {
1525
1567
  while IFS= read -r _df; do
1526
1568
  [ -n "$_df" ] || continue
1527
- if ! jscpd_is_default_ignored "$_df"; then
1569
+ if ! jscpd_is_build_output "$_df"; then
1528
1570
  printf '%s\n' "$_df"
1529
1571
  fi
1530
1572
  done
@@ -2212,7 +2254,10 @@ else
2212
2254
  fi
2213
2255
 
2214
2256
  # --- BLOCK path: New .ts/.tsx files without corresponding tests ---
2215
- NEW_TS_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E '\.(ts|tsx)$' | grep -v '\.d\.ts$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__' | grep -v 'node_modules/' | grep -v '\.next/' | grep -v '\.nuxt/' | grep -v 'dist/' | grep -v 'build/' | grep -v '.turbo/' | grep -v '.cache/' | grep -Ev '(^|/)vitest\.config\.' | grep -Ev '(^|/)vite\.config\.' | grep -Ev '(^|/)jest\.config\.' | grep -Ev '(^|/)tsconfig\.' | grep -Ev '(^|/)eslint\.' | grep -Ev '(^|/)prettier\.' | grep -Ev '(^|/)tailwind\.' || true)
2257
+ # Exclusions come from filter_project_paths (#649). The hand-maintained list
2258
+ # this replaced omitted vendor/target/coverage/out, so a staged vendor/x.ts
2259
+ # was reported as new source with no test.
2260
+ NEW_TS_FILES=$(git diff --cached --name-only --diff-filter=A | filter_project_paths | grep -E '\.(ts|tsx)$' | grep -v '\.d\.ts$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__' | grep -Ev '(^|/)vitest\.config\.' | grep -Ev '(^|/)vite\.config\.' | grep -Ev '(^|/)jest\.config\.' | grep -Ev '(^|/)tsconfig\.' | grep -Ev '(^|/)eslint\.' | grep -Ev '(^|/)prettier\.' | grep -Ev '(^|/)tailwind\.' || true)
2216
2261
 
2217
2262
  # Strip PROJECT_SUBDIR prefix from new-TS-file paths ONLY when the hook
2218
2263
  # actually cd'd into the subdirectory (IN_PROJECT_SUBDIR=1; single-language
@@ -2297,10 +2342,12 @@ else
2297
2342
  fi
2298
2343
 
2299
2344
  # --- WARNING path: Modified files (any lang) + New non-TS files ---
2345
+ # filter_project_paths strips dependencies and build output (#649); without
2346
+ # it a staged vendor/ or target/ module is reported as source needing a test.
2300
2347
  WARNING_SOURCE_FILES=$(
2301
2348
  {
2302
- git diff --cached --name-only --diff-filter=M | grep -E '\.(ts|tsx|py|go|java|kt|kts|cpp|cc|cxx|c|swift|dart|rb|rs)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__'
2303
- git diff --cached --name-only --diff-filter=A | grep -E '\.(py|go|java|kt|kts|cpp|cc|cxx|c|swift|dart|rb|rs)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__'
2349
+ git diff --cached --name-only --diff-filter=M | filter_project_paths | grep -E '\.(ts|tsx|py|go|java|kt|kts|cpp|cc|cxx|c|swift|dart|rb|rs)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__'
2350
+ git diff --cached --name-only --diff-filter=A | filter_project_paths | grep -E '\.(py|go|java|kt|kts|cpp|cc|cxx|c|swift|dart|rb|rs)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__'
2304
2351
  } | sort -u || true
2305
2352
  )
2306
2353
 
@@ -2423,7 +2470,7 @@ else
2423
2470
  # Gate 5b: Mock Density ADVISORY Scan (pure bash — no npx tsx overhead)
2424
2471
  # 30% = ADVISORY, 50% = suggests @mock-justified. Does NOT block commit.
2425
2472
  # ========================================================================
2426
- CHANGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(test|spec)\.(ts|tsx|js|jsx|cjs|mjs|py|go)$' || true)
2473
+ CHANGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM | filter_project_paths | grep -E '\.(test|spec)\.(ts|tsx|js|jsx|cjs|mjs|py|go)$' || true)
2427
2474
 
2428
2475
  # Nested plugin sub-projects (plugins/<name>/) own their own vitest config and
2429
2476
  # are excluded from the root suite (`vitest.config.ts` excludes `plugins/**`,
@@ -2571,7 +2618,7 @@ else
2571
2618
  CHANGED_TEST_FILE_COUNT=$(echo "$SELECTED_TEST_FILES" | grep -c '.' || true)
2572
2619
 
2573
2620
  # Also get changed source files (non-test) for related-test lookup
2574
- CHANGED_SRC_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|js|jsx)$' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__tests__' | grep -v '\.d\.ts$' | grep -v '__snapshots__' || true)
2621
+ CHANGED_SRC_FILES=$(git diff --cached --name-only --diff-filter=ACM | filter_project_paths | grep -E '\.(ts|tsx|js|jsx)$' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__tests__' | grep -v '\.d\.ts$' | grep -v '__snapshots__' || true)
2575
2622
  # Nested plugin sub-projects are excluded from the root suite; their
2576
2623
  # sources must not drive root-vitest related-test lookup either (it
2577
2624
  # would resolve to plugin-local paths vitest cannot collect).
@@ -2807,9 +2854,14 @@ else
2807
2854
  # full -> unchanged behavior: whole suite + real threshold.
2808
2855
  # skip -> nothing test-relevant changed; no pytest at all.
2809
2856
  PY_CHANGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null \
2857
+ | filter_project_paths \
2810
2858
  | grep -E '(^|/)test_[^/]*\.py$|_test\.py$' || true)
2811
2859
  PY_CHANGED_SRC_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null \
2860
+ | filter_project_paths \
2812
2861
  | grep -E '\.py$' | grep -vE '(^|/)test_[^/]*\.py$|_test\.py$' || true)
2862
+ # NOT filtered on purpose: this list answers "did a requirements
2863
+ # file change", which is a property of the whole commit including
2864
+ # paths under a vendor/ tree that filter_project_paths removes.
2813
2865
  PY_CHANGED_ALL=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)
2814
2866
  # `grep -c '.'` prints its own zero AND exits 1 on an empty list
2815
2867
  # (#493), so the count is derived with `|| true` + wc -l instead.
@@ -3363,7 +3415,7 @@ else
3363
3415
  # New files must have ≥80% coverage individually.
3364
3416
  # ============================================================================
3365
3417
  if has_project_lang "typescript" && [ -f "coverage/coverage-summary.json" ]; then
3366
- NEW_SOURCE_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E '\.(ts|tsx)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__' | grep -v '\.d\.ts$' || true)
3418
+ NEW_SOURCE_FILES=$(git diff --cached --name-only --diff-filter=A | filter_project_paths | grep -E '\.(ts|tsx)$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__' | grep -v '\.d\.ts$' || true)
3367
3419
  if [ -n "$NEW_SOURCE_FILES" ]; then
3368
3420
  echo ""
3369
3421
  echo " └─ Per-file coverage check for new files:"
@@ -3764,8 +3816,11 @@ else
3764
3816
  # governed by Mirror Parity in CI, which compares content rather than
3765
3817
  # re-linting it. (See the same exclusion in adapters/powershell.sh.)
3766
3818
  MIRROR_EXCLUDE="$MIRROR_PATH_FILTER"
3767
- NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
3768
- MODIFIED_FILES=$(git diff --cached --name-only --diff-filter=M | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
3819
+ # filter_project_paths also removes dependencies and build output (#649):
3820
+ # a staged node_modules/ or dist/ file is not budgetable project code, and
3821
+ # entering it in the Boy Scout baseline would budget a path nobody edits.
3822
+ NEW_FILES=$(git diff --cached --name-only --diff-filter=A | filter_project_paths | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
3823
+ MODIFIED_FILES=$(git diff --cached --name-only --diff-filter=M | filter_project_paths | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
3769
3824
 
3770
3825
  if [ -z "$NEW_FILES" ] && [ -z "$MODIFIED_FILES" ]; then
3771
3826
  echo " ✅ No new or modified files to check."
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-28T17:43:58.740Z",
5
+ "checked_at": "2026-10-29T23:47:25.119Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-28
4
- **Commit:** 0f58c300
3
+ **Generated:** 2026-10-29
4
+ **Commit:** 101a0ff3
5
5
  **Branch:** main
6
- **Version:** 0.32.0.0
6
+ **Version:** 0.32.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.