forgedock 1.17.0 → 1.19.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.
Files changed (143) hide show
  1. package/bin/engine/outcome-codes.mjs +37 -2
  2. package/bin/engine/phases.mjs +207 -106
  3. package/bin/engine/runlog.mjs +17 -2
  4. package/bin/engine/telemetry-seam.mjs +3 -0
  5. package/bin/engine/telemetry.test.mjs +3 -2
  6. package/bin/engine-cli.mjs +37 -2
  7. package/bin/engine.mjs +221 -15
  8. package/bin/forgedock.mjs +1 -1
  9. package/bin/hooks/pre-tool-use.mjs +65 -32
  10. package/bin/runner.mjs +125 -6
  11. package/bin/stats/aggregate.mjs +7 -3
  12. package/bin/stats/cache.mjs +7 -3
  13. package/bin/stats/github-source.mjs +203 -24
  14. package/bin/stats/index.mjs +46 -9
  15. package/bin/stats/render.mjs +1 -1
  16. package/bin/stats/stats.test.mjs +264 -15
  17. package/commands/cleanup.md +10 -3
  18. package/commands/deploy-pr.md +64 -0
  19. package/commands/fix-ci.md +20 -6
  20. package/commands/milestone.md +34 -17
  21. package/commands/orchestrate/phase-4-execution.md +11 -4
  22. package/commands/review-pr-staging.md +145 -522
  23. package/commands/review-pr.md +496 -3124
  24. package/commands/test-gate.md +25 -10
  25. package/commands/work-on/build/architect.md +66 -1
  26. package/commands/work-on/build/implement.md +21 -1
  27. package/commands/work-on/build/validate.md +322 -28
  28. package/commands/work-on/build.md +173 -95
  29. package/commands/work-on/close.md +50 -15
  30. package/commands/work-on/decompose.md +72 -34
  31. package/commands/work-on/investigate.md +16 -12
  32. package/commands/work-on/remediate.md +58 -25
  33. package/commands/work-on/review.md +36 -21
  34. package/commands/work-on.md +1 -2
  35. package/docs/CONFIG.md +132 -26
  36. package/docs/OUTCOME-CODES.md +12 -3
  37. package/docs/RELEASE-NOTES.md +12 -0
  38. package/docs/STATS.md +13 -5
  39. package/docs/TELEMETRY.md +7 -4
  40. package/docs/WORK-ON-RUNTIME.md +24 -0
  41. package/docs/spec/forge-protocol-v1.md +2 -2
  42. package/package.json +2 -2
  43. package/packages/protocol/src/phases.js +5 -5
  44. package/packages/protocol/src/types.js +1 -1
  45. package/scripts/acceptance-red-green.sh +90 -0
  46. package/scripts/acceptance-red-green.test.sh +74 -0
  47. package/scripts/cap-findings.sh +1 -1
  48. package/scripts/cap-findings.test.sh +3 -1
  49. package/scripts/check-contract-scope.test.sh +2 -0
  50. package/scripts/check-edge-cases.sh +143 -0
  51. package/scripts/check-edge-cases.test.sh +175 -0
  52. package/scripts/check-spec-bash.sh +5 -1
  53. package/scripts/check-spec-bash.test.sh +20 -0
  54. package/scripts/check-spec-markers.sh +2 -0
  55. package/scripts/check-test-tmp-cleanup.sh +56 -0
  56. package/scripts/check-test-tmp-cleanup.test.sh +69 -0
  57. package/scripts/ci-local/adapters/azure-pipelines.mjs +113 -0
  58. package/scripts/ci-local/adapters/bitbucket.mjs +64 -0
  59. package/scripts/ci-local/adapters/buildkite.mjs +58 -0
  60. package/scripts/ci-local/adapters/circleci.mjs +101 -0
  61. package/scripts/ci-local/adapters/common.mjs +115 -0
  62. package/scripts/ci-local/adapters/explicit.mjs +21 -0
  63. package/scripts/ci-local/adapters/github-actions.mjs +349 -0
  64. package/scripts/ci-local/adapters/gitlab-ci.mjs +184 -0
  65. package/scripts/ci-local/adapters/portability.mjs +104 -0
  66. package/scripts/ci-local/adapters/task-runners.mjs +67 -0
  67. package/scripts/ci-local/cli.mjs +50 -0
  68. package/scripts/ci-local/lib.mjs +371 -0
  69. package/scripts/ci-local/plan.mjs +186 -0
  70. package/scripts/ci-local/run.mjs +345 -0
  71. package/scripts/ci-local/scoping.mjs +508 -0
  72. package/scripts/ci-local.sh +97 -0
  73. package/scripts/ci-local.test.sh +919 -0
  74. package/scripts/classify-finding.sh +32 -6
  75. package/scripts/classify-finding.test.sh +23 -0
  76. package/scripts/classify-test-run.sh +69 -0
  77. package/scripts/classify-test-run.test.sh +90 -0
  78. package/scripts/close-parent-c3.test.sh +71 -0
  79. package/scripts/consumer-sweep.sh +569 -0
  80. package/scripts/consumer-sweep.test.sh +357 -0
  81. package/scripts/detect-append-registries.sh +106 -0
  82. package/scripts/detect-append-registries.test.sh +64 -0
  83. package/scripts/diff-size.sh +93 -4
  84. package/scripts/diff-size.test.sh +59 -2
  85. package/scripts/fixtures/ci-local/azure/azure-pipelines.yml +11 -0
  86. package/scripts/fixtures/ci-local/bitbucket/bitbucket-pipelines.yml +12 -0
  87. package/scripts/fixtures/ci-local/buildkite/.buildkite/pipeline.yml +10 -0
  88. package/scripts/fixtures/ci-local/circleci/.circleci/config.yml +29 -0
  89. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/ci.yml +59 -0
  90. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/docs.yml +10 -0
  91. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/nightly.yml +10 -0
  92. package/scripts/fixtures/ci-local/gha-basic/docs/index.md +1 -0
  93. package/scripts/fixtures/ci-local/gha-basic/src/greet.sh +2 -0
  94. package/scripts/fixtures/ci-local/gha-basic/tests/greet.test.sh +3 -0
  95. package/scripts/fixtures/ci-local/gha-dorny/.github/workflows/ci.yml +51 -0
  96. package/scripts/fixtures/ci-local/gha-dorny/backend/app.py +1 -0
  97. package/scripts/fixtures/ci-local/gha-dorny/docs/a.md +1 -0
  98. package/scripts/fixtures/ci-local/gha-dorny/frontend/app.js +1 -0
  99. package/scripts/fixtures/ci-local/gha-run/.github/workflows/ci.yml +39 -0
  100. package/scripts/fixtures/ci-local/gha-run/src/x.txt +1 -0
  101. package/scripts/fixtures/ci-local/gitlab/.gitlab-ci.yml +43 -0
  102. package/scripts/fixtures/ci-local/gitlab/docs/d.md +1 -0
  103. package/scripts/fixtures/ci-local/gitlab/src/a.txt +1 -0
  104. package/scripts/fixtures/ci-local/monorepo/.github/workflows/ci.yml +13 -0
  105. package/scripts/fixtures/ci-local/monorepo/package-lock.json +1 -0
  106. package/scripts/fixtures/ci-local/monorepo/packages/a/src/a.sh +2 -0
  107. package/scripts/fixtures/ci-local/monorepo/packages/a/tests/a.test.sh +3 -0
  108. package/scripts/fixtures/ci-local/monorepo/packages/b/src/b.sh +2 -0
  109. package/scripts/fixtures/ci-local/monorepo/packages/b/tests/b.test.sh +3 -0
  110. package/scripts/fixtures/ci-local/no-ci/Makefile +6 -0
  111. package/scripts/fixtures/ci-local/no-ci/package.json +1 -0
  112. package/scripts/fixtures/ci-local/no-ci/src/a.js +1 -0
  113. package/scripts/fixtures/ci-local/no-ci/src/b.js +1 -0
  114. package/scripts/fixtures/ci-local/no-ci/tests/a.test.js +2 -0
  115. package/scripts/fixtures/ci-local/no-ci/tests/b.test.js +2 -0
  116. package/scripts/forge-root.test.sh +14 -12
  117. package/scripts/lib/telemetry.sh +1 -0
  118. package/scripts/publish-staging-sync.test.sh +48 -0
  119. package/scripts/remediate-approved-head.test.sh +2 -0
  120. package/scripts/remediate-basesync.test.sh +1 -1
  121. package/scripts/resolve-deploy-gate.sh +32 -6
  122. package/scripts/resolve-deploy-gate.test.sh +42 -1
  123. package/scripts/review-acceptance-gate.sh +2 -2
  124. package/scripts/review-acceptance-gate.test.sh +20 -0
  125. package/scripts/review-delta.sh +8 -1
  126. package/scripts/review-delta.test.sh +19 -0
  127. package/scripts/review-finalize.sh +1579 -0
  128. package/scripts/review-finalize.test.sh +1040 -0
  129. package/scripts/review-pr-delta-wiring.test.sh +32 -72
  130. package/scripts/review-pr-domain-map.test.sh +18 -13
  131. package/scripts/review-pr-gate2.test.sh +9 -65
  132. package/scripts/review-pr-staging-scope.test.sh +14 -98
  133. package/scripts/review-prepare.sh +53 -15
  134. package/scripts/review-prepare.test.sh +37 -3
  135. package/scripts/review-roster.sh +16 -2
  136. package/scripts/review-roster.test.sh +22 -0
  137. package/scripts/run-shell-tests.sh +5 -0
  138. package/scripts/telemetry.test.sh +2 -0
  139. package/scripts/trusted-comments.test.sh +11 -3
  140. package/scripts/verify-phase-trail.test.sh +8 -0
  141. package/scripts/wait-ci-green.sh +61 -9
  142. package/scripts/wait-ci-green.test.sh +44 -6
  143. package/scripts/with-run-tmpdir.sh +20 -0
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env bash
2
- # diff-size.sh — the ONE measurement of a build's diff size for the build phase size gate (forge#3450).
2
+ # diff-size.sh — the ONE measurement of a build's diff size and scope (forge#3450, forge#3862).
3
3
  # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
  #
6
6
  # Usage: diff-size.sh --repo-path P --base B [--threshold N] [--exclude-glob G]...
7
+ # [--test-weight PCT] [--module-depth N] [--plan-files FILE] [--estimate N] [--overrun-pct PCT]
8
+ # The size is never a gate (forge#3862): review_weight scales review depth and the scope diff (--plan-files) flags
9
+ # files outside the architect plan. --threshold/over/cohesive are DEPRECATED (kept one release for old callers).
7
10
  # Measures lines changed (added + deleted) by the index plus any earlier commits against the MERGE BASE of
8
11
  # HEAD and origin/B (git diff --cached --numstat --no-renames $(git merge-base HEAD origin/B)), so commits that
9
12
  # landed on the base after the branch point are never counted as this build's changes. Exit 2 when the merge
@@ -15,7 +18,7 @@
15
18
  # build/ dist/ generated/ fixtures/ vendor/ are anchored to the repo root (so first-party dirs such as
16
19
  # commands/work-on/build/ are counted); __generated__/ and __snapshots__/ match at any depth.
17
20
  # Globs are data, never evaluated.
18
- # Default globs (mirrored in docs/CONFIG.md under build.diff_size.exclude_globs):
21
+ # Default globs (mirrored in docs/CONFIG.md under build.scope.exclude_globs, deprecated alias build.diff_size.exclude_globs):
19
22
  # lockfiles (package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock poetry.lock uv.lock go.sum Gemfile.lock
20
23
  # composer.lock), *.min.*, *.snap, root-anchored dist/ build/ generated/ fixtures/ vendor/, any-depth
21
24
  # __generated__/ __snapshots__/
@@ -27,6 +30,17 @@
27
30
  # by basename); cohesive_lines = the union (a new test file counts once). cohesive=true when diff_lines>0 and
28
31
  # cohesive_lines*100 >= 70*diff_lines and diff_lines <= threshold*3/2 (integer math; threshold 0 -> false).
29
32
  # Test lines are classified, never discounted from diff_lines.
33
+ # review_weight (forge#3862): ws_lines = lines changed with whitespace ignored (git diff -w; a pure re-indent is 0)
34
+ # over the counted (non-excluded) files; review_weight = ceil((ws_lines of non-test files + --test-weight percent
35
+ # (default 50) of ws_lines of test files)); modules = distinct directories (first --module-depth path segments,
36
+ # default 2) holding a file with at least one non-whitespace changed line. Keys ws_lines, review_weight, modules
37
+ # follow cohesive= and precede top=.
38
+ # Scope diff, emitted only with --plan-files and/or --estimate: --plan-files FILE lists the architect's planned files
39
+ # (one per line; a leading "- " or "* ", backticks and "./" are stripped; an entry ending in "/" is a directory prefix;
40
+ # blank and "#" lines are ignored). scope=no_baseline (no entries) | ok | out_of_plan; plan_files=N; out_of_plan_count=K;
41
+ # up to 50 "out_of_plan=<path>" lines (counted files absent from the plan). --estimate N with --overrun-pct P
42
+ # (default 200) sets overrun=true when ws_lines*100 > N*P (N=0 means no estimate: overrun=false). Unreadable
43
+ # --plan-files is exit 2 (fail closed: an unreadable plan is never "in plan").
30
44
  # Exit: 0 measured (even when over); 2 usage or git failure with EMPTY stdout (fail closed: unknown is not zero).
31
45
 
32
46
  set -euo pipefail
@@ -34,7 +48,7 @@ set -euo pipefail
34
48
  _TEL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/telemetry.sh"; [ -f "$_TEL" ] && . "$_TEL" 2>/dev/null
35
49
  type forge_emit >/dev/null 2>&1 || forge_emit() { :; }
36
50
 
37
- REPO_PATH=""; BASE=""; THRESHOLD="1000"; EXTRA=()
51
+ REPO_PATH=""; BASE=""; THRESHOLD="1000"; EXTRA=(); TEST_WEIGHT="50"; MODULE_DEPTH="2"; PLAN_FILE=""; ESTIMATE=""; OVERRUN_PCT="200"
38
52
  die() { echo "diff-size.sh: $*" >&2; exit 2; }
39
53
  while [ $# -gt 0 ]; do
40
54
  case "$1" in
@@ -42,12 +56,33 @@ while [ $# -gt 0 ]; do
42
56
  --base) [ $# -ge 2 ] || die "missing value for $1"; BASE="$2"; shift 2 ;;
43
57
  --threshold) [ $# -ge 2 ] || die "missing value for $1"; THRESHOLD="$2"; shift 2 ;;
44
58
  --exclude-glob) [ $# -ge 2 ] || die "missing value for $1"; [ -z "$2" ] || EXTRA+=("$2"); shift 2 ;;
59
+ --test-weight) [ $# -ge 2 ] || die "missing value for $1"; TEST_WEIGHT="$2"; shift 2 ;;
60
+ --module-depth) [ $# -ge 2 ] || die "missing value for $1"; MODULE_DEPTH="$2"; shift 2 ;;
61
+ --plan-files) [ $# -ge 2 ] || die "missing value for $1"; PLAN_FILE="$2"; shift 2 ;;
62
+ --estimate) [ $# -ge 2 ] || die "missing value for $1"; ESTIMATE="$2"; shift 2 ;;
63
+ --overrun-pct) [ $# -ge 2 ] || die "missing value for $1"; OVERRUN_PCT="$2"; shift 2 ;;
45
64
  *) die "unknown argument: $1" ;;
46
65
  esac
47
66
  done
48
67
  [ -n "$REPO_PATH" ] && [ -d "$REPO_PATH" ] || die "--repo-path must be an existing directory"
49
68
  [ -n "$BASE" ] || die "--base is required"
50
69
  case "$THRESHOLD" in ""|*[!0-9]*) die "--threshold must be a non-negative integer" ;; esac
70
+ case "$TEST_WEIGHT" in ""|*[!0-9]*) die "--test-weight must be a non-negative integer percent" ;; esac
71
+ case "$MODULE_DEPTH" in ""|0|*[!0-9]*) die "--module-depth must be a positive integer" ;; esac
72
+ case "$OVERRUN_PCT" in ""|*[!0-9]*) die "--overrun-pct must be a non-negative integer percent" ;; esac
73
+ case "$ESTIMATE" in ""|*[!0-9]*) [ -z "$ESTIMATE" ] || die "--estimate must be a non-negative integer" ;; esac
74
+ PLAN=(); PLAN_SET=0
75
+ if [ -n "$PLAN_FILE" ]; then
76
+ [ -f "$PLAN_FILE" ] && [ -r "$PLAN_FILE" ] || die "--plan-files must be a readable file"
77
+ PLAN_SET=1
78
+ while IFS= read -r _l || [ -n "$_l" ]; do
79
+ _l="${_l#"${_l%%[![:space:]]*}"}"; _l="${_l%"${_l##*[![:space:]]}"}"
80
+ case "$_l" in ""|"#"*) continue ;; esac
81
+ case "$_l" in "- "*|"* "*) _l="${_l#??}" ;; esac
82
+ _l="${_l//\`/}"; _l="${_l#./}"
83
+ [ -n "$_l" ] && PLAN+=("$_l")
84
+ done < "$PLAN_FILE"
85
+ fi
51
86
 
52
87
  DEFAULTS=(package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock poetry.lock uv.lock go.sum Gemfile.lock composer.lock
53
88
  '*.min.*' '*.snap' __generated__/ __snapshots__/)
@@ -102,6 +137,32 @@ while IFS= read -r -d $'\001' rec; do
102
137
  fi
103
138
  done <<< "$RAW"
104
139
 
140
+ # Whitespace-insensitive pass (forge#3862): review_weight and module count come from `git diff -w`.
141
+ RAWW="$(git -C "$REPO_PATH" diff --cached -w --numstat --no-renames -z "$MB" -- | tr '\0' '\001')" \
142
+ || die "git diff -w against merge base $MB failed"
143
+ WSL=0; WS_NT=0; WS_T=0; MODS=$'\001'; NMODS=0
144
+ while IFS= read -r -d $'\001' rec; do
145
+ [ -n "$rec" ] || continue
146
+ a="${rec%%$'\t'*}"; rest="${rec#*$'\t'}"; d="${rest%%$'\t'*}"; path="${rest#*$'\t'}"
147
+ case "$a" in ''|*[!0-9]*) a=0 ;; esac
148
+ case "$d" in ''|*[!0-9]*) d=0 ;; esac
149
+ n=$((a + d))
150
+ excluded "$path" && continue
151
+ WSL=$((WSL + n))
152
+ if is_test "$path"; then WS_T=$((WS_T + n)); else WS_NT=$((WS_NT + n)); fi
153
+ if [ "$n" -gt 0 ]; then
154
+ dir="${path%/*}"; [ "$dir" = "$path" ] && dir="."
155
+ mod=""; _i=0; _rest="$dir"
156
+ while [ "$_i" -lt "$MODULE_DEPTH" ] && [ -n "$_rest" ]; do
157
+ _seg="${_rest%%/*}"; mod="${mod:+$mod/}$_seg"
158
+ case "$_rest" in */*) _rest="${_rest#*/}" ;; *) _rest="" ;; esac
159
+ _i=$((_i + 1))
160
+ done
161
+ case "$MODS" in *$'\001'"$mod"$'\001'*) ;; *) MODS="${MODS}${mod}"$'\001'; NMODS=$((NMODS + 1)) ;; esac
162
+ fi
163
+ done <<< "$RAWW"
164
+ REVIEW_WEIGHT=$(( (WS_NT * 100 + WS_T * TEST_WEIGHT + 99) / 100 ))
165
+
105
166
  OVER=false
106
167
  if [ "$THRESHOLD" -gt 0 ] && [ "$TOTAL" -gt "$THRESHOLD" ]; then OVER=true; fi
107
168
  echo "diff_lines=$TOTAL"
@@ -115,8 +176,36 @@ echo "new_lines=$NEWL"
115
176
  echo "test_lines=$TESTL"
116
177
  echo "cohesive_lines=$COHL"
117
178
  echo "cohesive=$COHESIVE"
179
+ echo "ws_lines=$WSL"
180
+ echo "review_weight=$REVIEW_WEIGHT"
181
+ echo "modules=$NMODS"
182
+ OOP_LIST=""; OOP=0; SCOPE=ok
183
+ if [ "$PLAN_SET" = 1 ] || [ -n "$ESTIMATE" ]; then
184
+ if [ "${#PLAN[@]}" -eq 0 ]; then SCOPE=no_baseline; else
185
+ while IFS= read -r -d $'\001' rec; do
186
+ [ -n "$rec" ] || continue
187
+ path="${rec#*$'\t'}"; path="${path#*$'\t'}"
188
+ excluded "$path" && continue
189
+ inplan=0
190
+ for _p in "${PLAN[@]}"; do
191
+ case "$_p" in */) case "$path" in "$_p"*) inplan=1; break ;; esac ;; *) [ "$path" = "$_p" ] && { inplan=1; break; } ;; esac
192
+ done
193
+ if [ "$inplan" = 0 ]; then OOP=$((OOP + 1)); OOP_LIST="${OOP_LIST}${path}"$'\n'; fi
194
+ done <<< "$RAW"
195
+ [ "$OOP" -gt 0 ] && SCOPE=out_of_plan
196
+ fi
197
+ OVERRUN=false
198
+ if [ -n "$ESTIMATE" ] && [ "$ESTIMATE" -gt 0 ] && [ $((WSL * 100)) -gt $((ESTIMATE * OVERRUN_PCT)) ]; then OVERRUN=true; fi
199
+ echo "scope=$SCOPE"
200
+ echo "plan_files=${#PLAN[@]}"
201
+ echo "out_of_plan_count=$OOP"
202
+ [ -n "$ESTIMATE" ] && { echo "estimate=$ESTIMATE"; echo "overrun=$OVERRUN"; }
203
+ fi
118
204
  _res=OK; if [ "$COHESIVE" = true ]; then _res=COHESIVE; elif [ "$OVER" = true ]; then _res=OVER; fi
119
- forge_emit diff_size lines="$TOTAL" excluded="$EXCL" threshold="$THRESHOLD" result="$_res"
205
+ forge_emit diff_size lines="$TOTAL" excluded="$EXCL" threshold="$THRESHOLD" result="$_res" review_weight="$REVIEW_WEIGHT" modules="$NMODS" ws_lines="$WSL"
120
206
  if [ -n "$LIST" ]; then
121
207
  printf '%s' "$LIST" | sort -t' ' -k1,1nr | head -n 10 | sed 's/^/top=/' || true
122
208
  fi
209
+ if [ -n "$OOP_LIST" ]; then
210
+ printf '%s' "$OOP_LIST" | head -n 50 | sed 's/^/out_of_plan=/' || true
211
+ fi
@@ -137,10 +137,67 @@ check "test name patterns" "$OUT" test_lines 30
137
137
  reset; gen 20 > keep.py; gen 900 > package-lock.json; git add -A
138
138
  OUT="$(run --threshold 1000)"; check "excluded not in new_lines" "$OUT" new_lines 20
139
139
 
140
- # existing key order is unchanged: cohesion keys sit between over= and top=
140
+ # existing key order is unchanged: cohesion keys sit between over= and top=; review keys follow cohesive=
141
141
  reset; gen 3 > s.txt; git add -A
142
142
  KEYS="$(run | sed -n 's/^\([a-z_]*\)=.*/\1/p' | tr '\n' ' ')"
143
- [ "$KEYS" = "diff_lines excluded_lines threshold over new_lines test_lines cohesive_lines cohesive top " ] && ok || bad "key order ($KEYS)"
143
+ [ "$KEYS" = "diff_lines excluded_lines threshold over new_lines test_lines cohesive_lines cohesive ws_lines review_weight modules top " ] && ok || bad "key order ($KEYS)"
144
+
145
+ # review_weight (forge#3862): a pure re-indent counts about 0, whatever its raw size
146
+ reset; mkdir -p svc; awk 'BEGIN{for(i=0;i<120;i++) print "if x:\n y" i}' > svc/h.py; git add -A; git commit -qm h; git push -q origin feat:staging 2>/dev/null
147
+ awk 'BEGIN{for(i=0;i<120;i++) print "if x:\n y" i}' > svc/h.py; git add -A
148
+ OUT="$(run)"; check "re-indent raw diff_lines" "$OUT" diff_lines 240; check "re-indent ws_lines" "$OUT" ws_lines 0
149
+ check "re-indent review_weight" "$OUT" review_weight 0; check "re-indent modules" "$OUT" modules 0
150
+ git reset -q --hard origin/staging
151
+
152
+ # tests are weighted (default 50%, configurable); ws_lines is unweighted
153
+ reset; mkdir -p src tests; gen 100 > src/m.py; gen 200 > tests/test_m.py; git add -A
154
+ OUT="$(run)"; check "weight ws_lines" "$OUT" ws_lines 300; check "weight default 50" "$OUT" review_weight 200
155
+ OUT="$(run --test-weight 25)"; check "weight 25" "$OUT" review_weight 150
156
+ OUT="$(run --test-weight 0)"; check "weight 0" "$OUT" review_weight 100
157
+ OUT="$(run --test-weight 100)"; check "weight 100" "$OUT" review_weight 300
158
+ reset; mkdir -p src; gen 3 > src/o.py; mkdir -p tests; gen 1 > tests/test_o.py; git add -A
159
+ OUT="$(run)"; check "weight rounds up" "$OUT" review_weight 4
160
+
161
+ # generated and vendored code is excluded from review_weight and modules
162
+ reset; gen 10 > keep.py; mkdir -p vendor generated; gen 900 > vendor/v.js; gen 900 > generated/g.js; gen 900 > package-lock.json; git add -A
163
+ OUT="$(run)"; check "excluded ws_lines" "$OUT" ws_lines 10; check "excluded review_weight" "$OUT" review_weight 10; check "excluded modules" "$OUT" modules 1
164
+
165
+ # module count: distinct directories at --module-depth
166
+ reset; mkdir -p a/b/c a/b/d a/e f; gen 5 > a/b/c/1.py; gen 5 > a/b/d/2.py; gen 5 > a/e/3.py; gen 5 > f/4.py; gen 5 > root.py; git add -A
167
+ OUT="$(run)"; check "modules depth 2" "$OUT" modules 4
168
+ OUT="$(run --module-depth 1)"; check "modules depth 1" "$OUT" modules 3
169
+ OUT="$(run --module-depth 3)"; check "modules depth 3" "$OUT" modules 5
170
+
171
+ # scope diff: in-plan, out-of-plan, prefix entries, formatting noise, no baseline, overrun
172
+ reset; mkdir -p src tests docs; gen 10 > src/a.py; gen 10 > src/b.py; gen 10 > tests/test_a.py; gen 10 > docs/x.md; git add -A
173
+ printf '%s\n' '# plan' '- `src/a.py`' '* ./src/b.py' '' 'tests/' > "$TMP/plan"
174
+ OUT="$(run --plan-files "$TMP/plan")"
175
+ check "scope out_of_plan" "$OUT" scope out_of_plan; check "scope count" "$OUT" out_of_plan_count 1
176
+ check "scope plan_files" "$OUT" plan_files 3; check "scope path" "$OUT" out_of_plan docs/x.md
177
+ printf '%s\n' src/a.py src/b.py tests/test_a.py docs/x.md > "$TMP/plan"
178
+ OUT="$(run --plan-files "$TMP/plan")"; check "scope ok" "$OUT" scope ok; check "scope ok count" "$OUT" out_of_plan_count 0
179
+ printf '%s\n' 'src/a.py' > "$TMP/plan"
180
+ OUT="$(run --plan-files "$TMP/plan")"; check "scope 3 out" "$OUT" out_of_plan_count 3
181
+ : > "$TMP/plan"; OUT="$(run --plan-files "$TMP/plan")"; check "empty plan is no_baseline" "$OUT" scope no_baseline; check "no_baseline count" "$OUT" out_of_plan_count 0
182
+ OUT="$(run)"; [ -z "$(val "$OUT" scope)" ] && ok || bad "scope keys absent without plan args"
183
+ # excluded files are never out-of-plan; a path is data (glob chars, spaces) not a pattern
184
+ reset; mkdir -p vendor; gen 5 > vendor/z.js; gen 5 > 'we ird[1].txt'; git add -A
185
+ printf '%s\n' 'src/a.py' > "$TMP/plan"
186
+ OUT="$(run --plan-files "$TMP/plan")"; check "excluded not out-of-plan" "$OUT" out_of_plan_count 1; check "path with space and glob quoted" "$OUT" out_of_plan 'we ird[1].txt'
187
+ printf '%s\n' '*.txt' > "$TMP/plan"; OUT="$(run --plan-files "$TMP/plan")"; check "plan entry is not a glob" "$OUT" out_of_plan_count 1
188
+ # overrun against the plan estimate (ws-insensitive actual)
189
+ reset; gen 500 > big.py; git add -A
190
+ OUT="$(run --estimate 200)"; check "overrun 2.5x" "$OUT" overrun true; check "overrun estimate" "$OUT" estimate 200; check "overrun no plan file is no_baseline" "$OUT" scope no_baseline
191
+ OUT="$(run --estimate 250)"; check "at 2x not overrun" "$OUT" overrun false
192
+ OUT="$(run --estimate 200 --overrun-pct 300)"; check "custom overrun pct" "$OUT" overrun false
193
+ OUT="$(run --estimate 0)"; check "estimate 0 is no estimate" "$OUT" overrun false
194
+ # unreadable plan fails closed; bad numeric args are usage errors
195
+ OUT="$(bash "$S" --repo-path "$TMP/w" --base staging --plan-files "$TMP/none" 2>/dev/null)"; rc=$?
196
+ { [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "unreadable plan (rc=$rc)"
197
+ for bad_arg in "--test-weight x" "--module-depth 0" "--overrun-pct -1" "--estimate x"; do
198
+ OUT="$(bash "$S" --repo-path "$TMP/w" --base staging $bad_arg 2>/dev/null)"; rc=$?
199
+ { [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "bad arg $bad_arg (rc=$rc)"
200
+ done
144
201
 
145
202
  # fail closed: bad repo path, bad base, bad threshold, unknown arg, missing base
146
203
  OUT="$(bash "$S" --repo-path "$TMP/nope" --base staging 2>/dev/null)"; rc=$?
@@ -0,0 +1,11 @@
1
+ pr:
2
+ branches:
3
+ include: [main]
4
+ pool:
5
+ vmImage: ubuntu-latest
6
+ variables:
7
+ greeting: hi
8
+ steps:
9
+ - script: echo "$(greeting) azure"
10
+ displayName: say
11
+ - task: SomeUnknownTask@1
@@ -0,0 +1,12 @@
1
+ image: atlassian/default-image:4
2
+ pipelines:
3
+ default:
4
+ - step:
5
+ name: Build and test
6
+ script:
7
+ - echo bitbucket-test
8
+ - step:
9
+ name: Deploy
10
+ deployment: production
11
+ script:
12
+ - echo deploy
@@ -0,0 +1,10 @@
1
+ steps:
2
+ - label: ":hammer: test"
3
+ command: echo bk-test
4
+ - wait
5
+ - label: "docker plugin"
6
+ command: echo bk-docker
7
+ plugins:
8
+ - docker#v5.0.0:
9
+ image: node:20
10
+ - block: ":rocket: release"
@@ -0,0 +1,29 @@
1
+ version: 2.1
2
+ jobs:
3
+ test:
4
+ docker:
5
+ - image: cimg/base:stable
6
+ steps:
7
+ - checkout
8
+ - run:
9
+ name: unit
10
+ command: echo circle-unit
11
+ secured:
12
+ docker:
13
+ - image: cimg/base:stable
14
+ steps:
15
+ - run: echo secured
16
+ deploy-prod:
17
+ docker:
18
+ - image: cimg/base:stable
19
+ steps:
20
+ - run: echo deploy
21
+ workflows:
22
+ main:
23
+ jobs:
24
+ - test
25
+ - secured:
26
+ context: org-secrets
27
+ requires: [test]
28
+ - deploy-prod:
29
+ requires: [test]
@@ -0,0 +1,59 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ branches: [main]
5
+ paths:
6
+ - 'src/**'
7
+ - 'tests/**'
8
+ - '.github/workflows/ci.yml'
9
+ jobs:
10
+ unit:
11
+ name: Unit
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ - name: unit tests
19
+ run: bash tests/greet.test.sh
20
+ matrix:
21
+ runs-on: ${{ matrix.os }}
22
+ strategy:
23
+ matrix:
24
+ os: [ubuntu-latest, macos-14, windows-latest]
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - run: echo matrix-ok
28
+ withservice:
29
+ runs-on: ubuntu-latest
30
+ services:
31
+ postgres:
32
+ image: postgres:16
33
+ env:
34
+ POSTGRES_PASSWORD: example
35
+ ports:
36
+ - 5432:5432
37
+ options: --health-cmd pg_isready --health-interval 5s
38
+ steps:
39
+ - run: echo service-ok
40
+ needsecret:
41
+ runs-on: ubuntu-latest
42
+ env:
43
+ API_TOKEN: ${{ secrets.API_TOKEN }}
44
+ steps:
45
+ - run: echo uses-secret
46
+ unknownaction:
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: some-org/mystery-action@v9
50
+ deployprod:
51
+ runs-on: ubuntu-latest
52
+ environment: production
53
+ steps:
54
+ - run: echo deploy
55
+ conditional:
56
+ runs-on: ubuntu-latest
57
+ if: github.event_name == 'push'
58
+ steps:
59
+ - run: echo push-only
@@ -0,0 +1,10 @@
1
+ name: Docs
2
+ on:
3
+ pull_request:
4
+ paths:
5
+ - 'docs/**'
6
+ jobs:
7
+ docs:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - run: echo docs-ok
@@ -0,0 +1,10 @@
1
+ name: Nightly
2
+ on:
3
+ schedule:
4
+ - cron: '0 3 * * *'
5
+ workflow_dispatch:
6
+ jobs:
7
+ nightly:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - run: echo nightly
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ greet() { echo "hello $1"; }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ . "$(dirname "$0")/../src/greet.sh"
3
+ [ "$(greet x)" = "hello x" ] || exit 1
@@ -0,0 +1,51 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ branches: [staging, main]
5
+ jobs:
6
+ changes:
7
+ runs-on: ubuntu-latest
8
+ outputs:
9
+ backend: ${{ steps.filter.outputs.backend }}
10
+ frontend: ${{ steps.filter.outputs.frontend }}
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: dorny/paths-filter@v3
14
+ id: filter
15
+ with:
16
+ filters: |
17
+ backend:
18
+ - 'backend/**'
19
+ frontend:
20
+ - 'frontend/**'
21
+ backend:
22
+ needs: changes
23
+ if: needs.changes.outputs.backend == 'true'
24
+ runs-on: ubuntu-latest
25
+ services:
26
+ redis:
27
+ image: redis:7
28
+ ports:
29
+ - 6379:6379
30
+ steps:
31
+ - run: echo backend-ok
32
+ frontend:
33
+ needs: changes
34
+ if: needs.changes.outputs.frontend == 'true'
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - run: echo frontend-ok
38
+ integration:
39
+ needs: [changes]
40
+ if: needs.changes.outputs.backend == 'true'
41
+ runs-on: ubuntu-latest
42
+ env:
43
+ DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
44
+ steps:
45
+ - run: echo integration
46
+ deploy-staging:
47
+ needs: [backend]
48
+ runs-on: ubuntu-latest
49
+ environment: staging
50
+ steps:
51
+ - run: echo deploy
@@ -0,0 +1 @@
1
+ print("a")
@@ -0,0 +1 @@
1
+ console.log(1)
@@ -0,0 +1,39 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ jobs:
5
+ first:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - run: echo first >> "$RUN_LOG"
9
+ - name: reads GITHUB_ENV
10
+ run: |
11
+ echo "FROM_STEP=carried" >> "$GITHUB_ENV"
12
+ - run: test "$FROM_STEP" = carried
13
+ env:
14
+ RUN_LOG: run.log
15
+ second:
16
+ needs: first
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - run: echo second
20
+ failing:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - run: |
24
+ echo "about to fail"
25
+ exit 3
26
+ skippedbyneed:
27
+ needs: failing
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - run: echo never
31
+ slow:
32
+ runs-on: ubuntu-latest
33
+ timeout-minutes: 1
34
+ steps:
35
+ - run: echo slow-ok
36
+ broken-install:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - run: npm ci --no-audit --registry=http://127.0.0.1:9
@@ -0,0 +1,43 @@
1
+ stages: [build, test, deploy]
2
+ variables:
3
+ GREETING: hello
4
+ .template:
5
+ script:
6
+ - echo template
7
+ lint:
8
+ stage: test
9
+ image: node:20
10
+ rules:
11
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
12
+ changes:
13
+ - src/**/*
14
+ script:
15
+ - echo "$GREETING lint"
16
+ unit:
17
+ stage: test
18
+ extends: .template
19
+ only:
20
+ - merge_requests
21
+ withdb:
22
+ stage: test
23
+ services:
24
+ - postgres:16
25
+ script:
26
+ - echo db
27
+ manualjob:
28
+ stage: test
29
+ when: manual
30
+ script:
31
+ - echo manual
32
+ release:
33
+ stage: deploy
34
+ environment: production
35
+ script:
36
+ - echo release
37
+ docsonly:
38
+ stage: test
39
+ rules:
40
+ - changes:
41
+ - docs/**/*
42
+ script:
43
+ - echo docs
@@ -0,0 +1,13 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ jobs:
5
+ test-a:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - run: bash packages/a/tests/a.test.sh
9
+ - run: bash packages/b/tests/b.test.sh
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - run: shellcheck -S error packages/a/src/a.sh packages/b/src/b.sh
@@ -0,0 +1 @@
1
+ {"lockfileVersion":1}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ a() { echo a; }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ . "$(dirname "$0")/../src/a.sh"
3
+ [ "$(a)" = a ]
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ b() { echo b; }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ . "$(dirname "$0")/../src/b.sh"
3
+ [ "$(b)" = b ]
@@ -0,0 +1,6 @@
1
+ test:
2
+ @echo make-test
3
+ lint:
4
+ @echo make-lint
5
+ build:
6
+ @echo build
@@ -0,0 +1 @@
1
+ { "name": "noci", "version": "1.0.0", "scripts": { "test": "node --test tests/", "lint": "echo lint-ok" } }
@@ -0,0 +1 @@
1
+ module.exports = 1
@@ -0,0 +1 @@
1
+ module.exports = 2
@@ -0,0 +1,2 @@
1
+ const t = require('node:test');
2
+ t('a', () => require('../src/a.js'));
@@ -0,0 +1,2 @@
1
+ const t = require('node:test');
2
+ t('b', () => require('../src/b.js'));