forgedock 1.10.3 → 1.11.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/commands/ci-audit.md +3 -3
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-pr.md +6 -6
- package/commands/explain.md +2 -2
- package/commands/fix-ci.md +2 -2
- package/commands/forge-stats.md +1 -1
- package/commands/forgedock-init.md +3 -3
- package/commands/optimize.md +5 -5
- package/commands/orchestrate/phase-1-resolve.md +2 -2
- package/commands/orchestrate/phase-4-execution.md +182 -41
- package/commands/quality-gate.md +1 -1
- package/commands/recover-orphans.md +6 -6
- package/commands/review-pr-agents/protocols.md +1 -0
- package/commands/review-pr-staging.md +28 -6
- package/commands/review-pr.md +434 -120
- package/commands/security-audit.md +1 -1
- package/commands/spec-doctor.md +2 -2
- package/commands/test-gate.md +7 -7
- package/commands/work-on/build/architect.md +21 -0
- package/commands/work-on/build/context.md +1 -1
- package/commands/work-on/build/implement.md +17 -0
- package/commands/work-on/build/validate.md +2 -2
- package/commands/work-on/build.md +98 -9
- package/commands/work-on/close.md +33 -11
- package/commands/work-on/decompose.md +19 -5
- package/commands/work-on/investigate.md +34 -4
- package/commands/work-on/remediate.md +30 -6
- package/commands/work-on/review.md +10 -8
- package/commands/work-on.md +20 -11
- package/docs/CONFIG.md +30 -0
- package/docs/RELEASE-NOTES.md +9 -0
- package/docs/WORK-ON-RUNTIME.md +22 -2
- package/docs/site/architecture.md +1 -1
- package/docs/spec/forge-protocol-v1.md +10 -0
- package/docs/spec/review-protocol.md +1 -0
- package/package.json +1 -1
- package/packages/protocol/src/types.js +17 -0
- package/scripts/check-spec-bash.sh +45 -2
- package/scripts/check-spec-bash.test.sh +24 -1
- package/scripts/check-spec-markers.sh +53 -0
- package/scripts/diff-size.sh +84 -0
- package/scripts/diff-size.test.sh +117 -0
- package/scripts/forge-root.test.sh +15 -0
- package/scripts/remediation-marker-match.test.sh +51 -0
- package/scripts/review-pr-arguments.test.sh +180 -0
- package/scripts/trusted-comments.test.sh +23 -0
|
@@ -12,7 +12,19 @@
|
|
|
12
12
|
#
|
|
13
13
|
# Usage:
|
|
14
14
|
# check-spec-bash.sh [--base <git-ref>] [--shellcheck] <file.md>...
|
|
15
|
+
# check-spec-bash.sh --positional [<file.md>...]
|
|
16
|
+
# check-spec-bash.sh (no args: same as --positional over all Skill-loaded commands/**/*.md,
|
|
17
|
+
# excluding orchestrate/**, pipeline-health/**, review-pr-agents/**)
|
|
15
18
|
#
|
|
19
|
+
# --positional Different check, opt-in: fail on any `$0`..`$9` inside a fenced code block.
|
|
20
|
+
# Claude Code rewrites those tokens with the invocation's arguments whenever a
|
|
21
|
+
# spec is loaded via Skill/slash command WITH args (awk `{print $2}` silently
|
|
22
|
+
# becomes `{print --issue}`). Use `${1}` in shell and `$(1)` in awk instead;
|
|
23
|
+
# `${N}`, `$(N)`, `$NF`, `$@`, `$#` and `$10`+ are not substituted. Suppress a
|
|
24
|
+
# deliberate hit by putting `allowlist:positional-arg` on the same line.
|
|
25
|
+
# Scope the file list to Skill-loaded specs (Read-loaded orchestrate/**,
|
|
26
|
+
# pipeline-health/** and the review-pr-agents catalog are not substituted).
|
|
27
|
+
# Output: `FAIL <file>:<line> positional arg ...` then `SPEC-POSITIONAL: ...`.
|
|
16
28
|
# --base <ref> Only check blocks that contain a line changed relative to <ref>
|
|
17
29
|
# (`git diff -U0 <ref> -- <file>`), so pre-existing blocks never fail
|
|
18
30
|
# a new change. Without --base every block is checked.
|
|
@@ -33,20 +45,51 @@
|
|
|
33
45
|
|
|
34
46
|
set -u
|
|
35
47
|
|
|
36
|
-
BASE=""; SHELLCHECK=0; FILES=""
|
|
37
|
-
usage() { echo "ERROR: Usage: check-spec-bash.sh [--base <git-ref>] [--shellcheck] <file.md>..." >&2; exit 2; }
|
|
48
|
+
BASE=""; SHELLCHECK=0; POSITIONAL=0; FILES=""
|
|
49
|
+
usage() { echo "ERROR: Usage: check-spec-bash.sh [--base <git-ref>] [--shellcheck] <file.md>... | --positional [<file.md>...]" >&2; exit 2; }
|
|
38
50
|
|
|
39
51
|
while [ "$#" -gt 0 ]; do
|
|
40
52
|
case "$1" in
|
|
41
53
|
--base) [ "$#" -ge 2 ] || usage; BASE="$2"; shift 2 ;;
|
|
42
54
|
--shellcheck) SHELLCHECK=1; shift ;;
|
|
55
|
+
--positional) POSITIONAL=1; shift ;;
|
|
43
56
|
--*) usage ;;
|
|
44
57
|
*) FILES="$FILES
|
|
45
58
|
$1"; shift ;;
|
|
46
59
|
esac
|
|
47
60
|
done
|
|
61
|
+
|
|
62
|
+
# No file arguments: default to the --positional check over every Skill-loaded spec under commands/
|
|
63
|
+
# (orchestrate/**, pipeline-health/** and the review-pr-agents catalog are Read-loaded, not substituted).
|
|
64
|
+
# Resolved relative to this script, so it works from any cwd and from linked worktrees.
|
|
65
|
+
if [ -z "$FILES" ] && { [ "$#" -eq 0 ] && [ "$POSITIONAL" = 0 ] && [ -z "$BASE" ] && [ "$SHELLCHECK" = 0 ] || [ "$POSITIONAL" = 1 ]; }; then
|
|
66
|
+
POSITIONAL=1
|
|
67
|
+
cd "$(dirname "$0")/.." || exit 2
|
|
68
|
+
FILES=$(find commands -name '*.md' -not -path 'commands/orchestrate/*' \
|
|
69
|
+
-not -path 'commands/pipeline-health/*' -not -path 'commands/review-pr-agents/*' | LC_ALL=C sort)
|
|
70
|
+
fi
|
|
48
71
|
[ -n "$FILES" ] || usage
|
|
49
72
|
|
|
73
|
+
if [ "$POSITIONAL" = 1 ]; then
|
|
74
|
+
pfail=0; pfiles=0
|
|
75
|
+
while IFS= read -r f; do
|
|
76
|
+
[ -n "$f" ] || continue
|
|
77
|
+
case "$f" in *.md) ;; *) continue ;; esac
|
|
78
|
+
[ -f "$f" ] || { echo "SKIP $f missing"; continue; }
|
|
79
|
+
pfiles=$((pfiles + 1))
|
|
80
|
+
hits=$(awk -v f="$f" '
|
|
81
|
+
/^[ \t]*```/ { inb = !inb; next }
|
|
82
|
+
inb && /\$[0-9]([^0-9]|$)/ && !/allowlist:positional-arg/ { print "FAIL " f ":" NR " positional arg ($0-$9) in fenced block: " $0 }
|
|
83
|
+
' "$f")
|
|
84
|
+
if [ -n "$hits" ]; then printf '%s\n' "$hits"; pfail=$((pfail + $(printf '%s\n' "$hits" | wc -l))); fi
|
|
85
|
+
done <<EOP
|
|
86
|
+
$FILES
|
|
87
|
+
EOP
|
|
88
|
+
echo "SPEC-POSITIONAL: files=$pfiles violations=$pfail"
|
|
89
|
+
[ "$pfail" -eq 0 ]
|
|
90
|
+
exit $?
|
|
91
|
+
fi
|
|
92
|
+
|
|
50
93
|
TMPD=$(mktemp -d "${TMPDIR:-/tmp}/check-spec-bash.XXXXXX") || exit 2
|
|
51
94
|
trap 'rm -rf "$TMPD"' EXIT
|
|
52
95
|
|
|
@@ -70,7 +70,9 @@ check 0 "SKIP allow.md:2 allowlisted" "allowlisted fragment skipped" -
|
|
|
70
70
|
check 0 "checked=0 failed=0" "non-bash fence ignored" -- notbash.md
|
|
71
71
|
check 1 "FAIL prose-mention.md:2" "prose mention of the marker does not allowlist" -- prose-mention.md
|
|
72
72
|
check 1 "checked=2 failed=1 skipped=1" "multi-file summary" -- good.md bad.md pseudo.md
|
|
73
|
-
check
|
|
73
|
+
check 0 "SPEC-POSITIONAL: files=" "no args defaults to --positional over commands/" --
|
|
74
|
+
check 0 "SPEC-POSITIONAL: files=" "--positional with no files uses the default set" -- --positional
|
|
75
|
+
check 2 "" "--shellcheck alone is a usage error" -- --shellcheck
|
|
74
76
|
check 2 "" "unknown flag" -- --bogus good.md
|
|
75
77
|
|
|
76
78
|
# --base: only blocks touching changed lines are checked, so pre-existing breakage never fails a change.
|
|
@@ -86,5 +88,26 @@ ${FENCE}
|
|
|
86
88
|
EOF
|
|
87
89
|
check 1 "FAIL good.md:" "--base: newly added broken block fails" -- --base HEAD good.md
|
|
88
90
|
|
|
91
|
+
# --positional: $0-$9 inside fences are flagged; ${N}, $(N), $NF, prose and allowlisted lines are not.
|
|
92
|
+
cat > "$T/pos-bad.md" <<EOF
|
|
93
|
+
${FENCE}bash
|
|
94
|
+
X=\$(echo a b | awk '{print \$2}')
|
|
95
|
+
local a="\$1"
|
|
96
|
+
${FENCE}
|
|
97
|
+
EOF
|
|
98
|
+
cat > "$T/pos-good.md" <<EOF
|
|
99
|
+
Prose mentions \$1 and \$2 freely.
|
|
100
|
+
${FENCE}bash
|
|
101
|
+
X=\$(echo a b | awk '{print \$(2) \$NF}')
|
|
102
|
+
local a="\${1}" b="\$@" n="\$#"
|
|
103
|
+
echo \$10
|
|
104
|
+
y=\$1 # allowlist:positional-arg
|
|
105
|
+
${FENCE}
|
|
106
|
+
EOF
|
|
107
|
+
check 1 "FAIL pos-bad.md:2" "--positional flags awk \$2" -- --positional pos-bad.md
|
|
108
|
+
check 1 "FAIL pos-bad.md:3" "--positional flags shell \$1" -- --positional pos-bad.md
|
|
109
|
+
check 1 "violations=2" "--positional counts violations" -- --positional pos-bad.md
|
|
110
|
+
check 0 "violations=0" "--positional passes safe forms, prose, allowlist" -- --positional pos-good.md
|
|
111
|
+
|
|
89
112
|
echo "check-spec-bash.test.sh: passed=$pass failed=$fail"
|
|
90
113
|
[ "$fail" -eq 0 ]
|
|
@@ -105,6 +105,7 @@ MARKER_REGISTRY="
|
|
|
105
105
|
DECOMPOSED
|
|
106
106
|
DECOMPOSE_BLOCKED
|
|
107
107
|
DESIGN
|
|
108
|
+
DIFF_SIZE
|
|
108
109
|
DISPATCH
|
|
109
110
|
DISPATCHER
|
|
110
111
|
DOSSIER_UPDATED
|
|
@@ -157,6 +158,7 @@ MARKER_REGISTRY="
|
|
|
157
158
|
RECOVERY_CLAIM_RELEASED
|
|
158
159
|
REMEDIATION
|
|
159
160
|
REREVIEW_DISPATCHED
|
|
161
|
+
REREVIEW_RELEASED
|
|
160
162
|
REREVIEW_SKIPPED
|
|
161
163
|
REVIEW
|
|
162
164
|
REVIEWER
|
|
@@ -165,6 +167,7 @@ MARKER_REGISTRY="
|
|
|
165
167
|
SECURITY_AUDIT
|
|
166
168
|
SIGNAL_RESOLVED
|
|
167
169
|
SIGNAL_UNRESOLVED
|
|
170
|
+
SIZE_OVERRIDE
|
|
168
171
|
SPAWN_POLICY
|
|
169
172
|
SPEC_DOCTOR_COMPLETE
|
|
170
173
|
SPEC_LOADED
|
|
@@ -361,6 +364,56 @@ if [ -f "$P4_SPEC" ]; then
|
|
|
361
364
|
fi
|
|
362
365
|
fi
|
|
363
366
|
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
# Router claim pre-check must classify the existing/winning claim's lease (forge#3431)
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
WORKON_SPEC="$COMMANDS_DIR/work-on.md"
|
|
372
|
+
if [ -f "$WORKON_SPEC" ]; then
|
|
373
|
+
# Scope: the "Claim pre-check" paragraph only, from its "**Claim pre-check" line up to the line before
|
|
374
|
+
# "**Terminal fallback". Full-line HTML comments are skipped, then each numbered step (N. ...) is collapsed
|
|
375
|
+
# to one whitespace-normalised line tagged "STEP<N>:" so tokens in other steps, the Terminal fallback
|
|
376
|
+
# paragraph, or comments cannot satisfy another step's assertion.
|
|
377
|
+
PRECHECK_STEPS=$(awk '
|
|
378
|
+
/^\*\*Terminal fallback/ { inpre = 0 }
|
|
379
|
+
/^\*\*Claim pre-check/ { inpre = 1; next }
|
|
380
|
+
inpre {
|
|
381
|
+
line = $0
|
|
382
|
+
if (line ~ /^[[:space:]]*<!--.*-->[[:space:]]*$/) next
|
|
383
|
+
gsub(/<!--[^>]*-->/, "", line)
|
|
384
|
+
if (match(line, /^[0-9]+\. /)) {
|
|
385
|
+
n = substr(line, 1, RLENGTH - 2)
|
|
386
|
+
cur = n
|
|
387
|
+
steps[cur] = "STEP" n ": " substr(line, RLENGTH + 1)
|
|
388
|
+
next
|
|
389
|
+
}
|
|
390
|
+
if (line ~ /^Only after the read-back/) { cur = ""; next }
|
|
391
|
+
if (cur != "") steps[cur] = steps[cur] " " line
|
|
392
|
+
}
|
|
393
|
+
END { for (k in steps) print steps[k] }
|
|
394
|
+
' "$WORKON_SPEC" | tr -s '[:space:]' ' ' | sed 's/ STEP/\nSTEP/g' || true)
|
|
395
|
+
# WIRE:PROVEN: manual mutation in a temp copy of commands/work-on.md: removing `STALE` from step 4 only fired step 4, removing it from step 2 only fired step 2, tokens only in an HTML comment or only in the Terminal fallback paragraph still fired, a router-local REREVIEW_LEASE_SECS= or rereview_lease_state fired, clean tree passes
|
|
396
|
+
for STEP_N in 2 4; do
|
|
397
|
+
STEP_TXT=$(printf '%s\n' "$PRECHECK_STEPS" | grep "^STEP${STEP_N}:" || true)
|
|
398
|
+
if [ -z "$STEP_TXT" ] \
|
|
399
|
+
|| [[ "$STEP_TXT" != *STALE* ]] \
|
|
400
|
+
|| [[ "$STEP_TXT" != *phase-4-execution.md* ]] \
|
|
401
|
+
|| [[ "$STEP_TXT" != *fallback* ]]; then
|
|
402
|
+
echo "HIGH | $WORKON_SPEC | claim pre-check step $STEP_N does not route STALE to the terminal fallback via the shared phase-4-execution.md Step 1 classification" >&2
|
|
403
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
404
|
+
fi
|
|
405
|
+
done
|
|
406
|
+
# The router must reference the orchestrator's single lease definition, never redefine it.
|
|
407
|
+
if grep -qE '^[[:space:]]*REREVIEW_LEASE_SECS=' "$WORKON_SPEC"; then
|
|
408
|
+
echo "HIGH | $WORKON_SPEC | router redefines REREVIEW_LEASE_SECS (single definition lives in phase-4-execution.md Step 1)" >&2
|
|
409
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
410
|
+
fi
|
|
411
|
+
if grep -rq 'rereview_lease_state' "$COMMANDS_DIR" 2>/dev/null; then
|
|
412
|
+
echo "HIGH | $COMMANDS_DIR | competing lease helper rereview_lease_state reintroduced (router must reuse the shared Step 1 classification)" >&2
|
|
413
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
414
|
+
fi
|
|
415
|
+
fi
|
|
416
|
+
|
|
364
417
|
# ---------------------------------------------------------------------------
|
|
365
418
|
# Summary
|
|
366
419
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,84 @@
|
|
|
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).
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
# Usage: diff-size.sh --repo-path P --base B [--threshold N] [--exclude-glob G]...
|
|
7
|
+
# Measures lines changed (added + deleted) by the index plus any earlier commits against the MERGE BASE of
|
|
8
|
+
# HEAD and origin/B (git diff --cached --numstat --no-renames $(git merge-base HEAD origin/B)), so commits that
|
|
9
|
+
# landed on the base after the branch point are never counted as this build's changes. Exit 2 when the merge
|
|
10
|
+
# base cannot be computed. It must run AFTER implement staged its changes
|
|
11
|
+
# and BEFORE validate commits. Unstaged or untracked files are not measured. Binary files count 0.
|
|
12
|
+
# Files matching the built-in default globs or any --exclude-glob are excluded (additive).
|
|
13
|
+
# Glob rules: a configured (--exclude-glob) glob ending in "/" matches that directory at any depth; any other
|
|
14
|
+
# glob is a bash case pattern matched against the basename and the full path. The built-in directory defaults
|
|
15
|
+
# build/ dist/ generated/ fixtures/ vendor/ are anchored to the repo root (so first-party dirs such as
|
|
16
|
+
# commands/work-on/build/ are counted); __generated__/ and __snapshots__/ match at any depth.
|
|
17
|
+
# Globs are data, never evaluated.
|
|
18
|
+
# Default globs (mirrored in docs/CONFIG.md under build.diff_size.exclude_globs):
|
|
19
|
+
# lockfiles (package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock poetry.lock uv.lock go.sum Gemfile.lock
|
|
20
|
+
# composer.lock), *.min.*, *.snap, root-anchored dist/ build/ generated/ fixtures/ vendor/, any-depth
|
|
21
|
+
# __generated__/ __snapshots__/
|
|
22
|
+
# Output (stdout, key=value lines): diff_lines=N excluded_lines=M threshold=T over=true|false, then up to 10
|
|
23
|
+
# "top=<lines> <path>" lines, largest first. threshold 0 disables the gate (over=false always).
|
|
24
|
+
# Exit: 0 measured (even when over); 2 usage or git failure with EMPTY stdout (fail closed: unknown is not zero).
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
REPO_PATH=""; BASE=""; THRESHOLD="1000"; EXTRA=()
|
|
29
|
+
die() { echo "diff-size.sh: $*" >&2; exit 2; }
|
|
30
|
+
while [ $# -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--repo-path) [ $# -ge 2 ] || die "missing value for $1"; REPO_PATH="$2"; shift 2 ;;
|
|
33
|
+
--base) [ $# -ge 2 ] || die "missing value for $1"; BASE="$2"; shift 2 ;;
|
|
34
|
+
--threshold) [ $# -ge 2 ] || die "missing value for $1"; THRESHOLD="$2"; shift 2 ;;
|
|
35
|
+
--exclude-glob) [ $# -ge 2 ] || die "missing value for $1"; [ -z "$2" ] || EXTRA+=("$2"); shift 2 ;;
|
|
36
|
+
*) die "unknown argument: $1" ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
[ -n "$REPO_PATH" ] && [ -d "$REPO_PATH" ] || die "--repo-path must be an existing directory"
|
|
40
|
+
[ -n "$BASE" ] || die "--base is required"
|
|
41
|
+
case "$THRESHOLD" in ""|*[!0-9]*) die "--threshold must be a non-negative integer" ;; esac
|
|
42
|
+
|
|
43
|
+
DEFAULTS=(package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock poetry.lock uv.lock go.sum Gemfile.lock composer.lock
|
|
44
|
+
'*.min.*' '*.snap' __generated__/ __snapshots__/)
|
|
45
|
+
ROOT_DIRS=(dist/ build/ generated/ fixtures/ vendor/)
|
|
46
|
+
GLOBS=("${DEFAULTS[@]}" ${EXTRA[@]+"${EXTRA[@]}"})
|
|
47
|
+
|
|
48
|
+
excluded() { # path -> 0 when excluded
|
|
49
|
+
local p="$1" b="${1##*/}" g
|
|
50
|
+
for g in "${ROOT_DIRS[@]}"; do case "$p" in "$g"*) return 0 ;; esac; done
|
|
51
|
+
for g in "${GLOBS[@]}"; do
|
|
52
|
+
case "$g" in
|
|
53
|
+
*/) case "/$p" in *"/$g"*) return 0 ;; esac ;;
|
|
54
|
+
*) case "$b" in $g) return 0 ;; esac; case "$p" in $g) return 0 ;; esac ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
MB="$(git -C "$REPO_PATH" merge-base HEAD "origin/$BASE" 2>/dev/null)" && [ -n "$MB" ] \
|
|
61
|
+
|| die "cannot compute merge base of HEAD and origin/$BASE"
|
|
62
|
+
RAW="$(git -C "$REPO_PATH" diff --cached --numstat --no-renames -z "$MB" -- | tr '\0' '\001')" \
|
|
63
|
+
|| die "git diff against merge base $MB failed"
|
|
64
|
+
|
|
65
|
+
TOTAL=0; EXCL=0; LIST=""
|
|
66
|
+
# -z numstat records are "added<TAB>deleted<TAB>path" separated by NUL (translated to \001 above).
|
|
67
|
+
while IFS= read -r -d $'\001' rec; do
|
|
68
|
+
[ -n "$rec" ] || continue
|
|
69
|
+
a="${rec%%$'\t'*}"; rest="${rec#*$'\t'}"; d="${rest%%$'\t'*}"; path="${rest#*$'\t'}"
|
|
70
|
+
case "$a" in ''|*[!0-9]*) a=0 ;; esac
|
|
71
|
+
case "$d" in ''|*[!0-9]*) d=0 ;; esac
|
|
72
|
+
n=$((a + d))
|
|
73
|
+
if excluded "$path"; then EXCL=$((EXCL + n)); else TOTAL=$((TOTAL + n)); LIST="${LIST}${n} ${path}"$'\n'; fi
|
|
74
|
+
done <<< "$RAW"
|
|
75
|
+
|
|
76
|
+
OVER=false
|
|
77
|
+
if [ "$THRESHOLD" -gt 0 ] && [ "$TOTAL" -gt "$THRESHOLD" ]; then OVER=true; fi
|
|
78
|
+
echo "diff_lines=$TOTAL"
|
|
79
|
+
echo "excluded_lines=$EXCL"
|
|
80
|
+
echo "threshold=$THRESHOLD"
|
|
81
|
+
echo "over=$OVER"
|
|
82
|
+
if [ -n "$LIST" ]; then
|
|
83
|
+
printf '%s' "$LIST" | sort -t' ' -k1,1nr | head -n 10 | sed 's/^/top=/' || true
|
|
84
|
+
fi
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# diff-size.test.sh — cases for scripts/diff-size.sh (forge#3450).
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/diff-size.sh"
|
|
7
|
+
PASS=0; FAILN=0
|
|
8
|
+
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
9
|
+
ok() { PASS=$((PASS+1)); }
|
|
10
|
+
bad() { FAILN=$((FAILN+1)); echo "FAIL: $1"; }
|
|
11
|
+
|
|
12
|
+
# Fixture: a bare "origin" plus a clone on a feature branch.
|
|
13
|
+
git init -q --bare "$TMP/origin.git"
|
|
14
|
+
git clone -q "$TMP/origin.git" "$TMP/w" 2>/dev/null
|
|
15
|
+
cd "$TMP/w" || exit 1
|
|
16
|
+
git config user.email t@example.com; git config user.name t
|
|
17
|
+
echo base > README.md
|
|
18
|
+
git add README.md; git commit -qm base; git branch -M staging; git push -q origin staging 2>/dev/null
|
|
19
|
+
git checkout -q -b feat
|
|
20
|
+
|
|
21
|
+
gen() { awk -v n="$1" 'BEGIN{for(i=0;i<n;i++) print "line " i}'; }
|
|
22
|
+
reset() { git reset -q --hard origin/staging; git clean -qfd; }
|
|
23
|
+
run() { bash "$S" --repo-path "$TMP/w" --base staging "$@"; }
|
|
24
|
+
val() { printf '%s\n' "$1" | sed -n "s/^$2=//p" | head -n1; }
|
|
25
|
+
check() { # name out key want
|
|
26
|
+
local got; got="$(val "$2" "$3")"
|
|
27
|
+
if [ "$got" = "$4" ]; then ok; else bad "$1 ($3=$got want $4)"; fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# empty diff
|
|
31
|
+
OUT="$(run)"; check "empty diff lines" "$OUT" diff_lines 0; check "empty diff over" "$OUT" over false
|
|
32
|
+
|
|
33
|
+
# under threshold
|
|
34
|
+
reset; gen 10 > a.txt; git add a.txt
|
|
35
|
+
OUT="$(run --threshold 100)"; check "under lines" "$OUT" diff_lines 10; check "under over" "$OUT" over false
|
|
36
|
+
|
|
37
|
+
# over threshold, exit 0 even when over
|
|
38
|
+
reset; gen 150 > a.txt; git add a.txt
|
|
39
|
+
OUT="$(run --threshold 100)"; rc=$?
|
|
40
|
+
[ "$rc" = 0 ] && ok || bad "over exits 0 (rc=$rc)"
|
|
41
|
+
check "over lines" "$OUT" diff_lines 150; check "over flag" "$OUT" over true
|
|
42
|
+
|
|
43
|
+
# exactly at threshold is not over
|
|
44
|
+
OUT="$(run --threshold 150)"; check "at threshold" "$OUT" over false
|
|
45
|
+
|
|
46
|
+
# threshold 0 disables
|
|
47
|
+
OUT="$(run --threshold 0)"; check "threshold 0 disables" "$OUT" over false
|
|
48
|
+
|
|
49
|
+
# default exclusions: lockfile, min, snap, root dist/, any-depth __snapshots__/
|
|
50
|
+
reset; gen 5 > a.txt
|
|
51
|
+
gen 500 > package-lock.json; gen 500 > app.min.js; gen 500 > x.snap
|
|
52
|
+
mkdir -p dist pkg/__snapshots__/deep; gen 500 > dist/out.js; gen 500 > pkg/__snapshots__/deep/f.json
|
|
53
|
+
git add -A
|
|
54
|
+
OUT="$(run --threshold 100)"
|
|
55
|
+
check "defaults diff_lines" "$OUT" diff_lines 5; check "defaults excluded" "$OUT" excluded_lines 2500; check "defaults over" "$OUT" over false
|
|
56
|
+
|
|
57
|
+
# SEC-2: built-in directory defaults are root-anchored; first-party nested dirs are counted
|
|
58
|
+
reset; mkdir -p commands/work-on/build pkg/vendor pkg/fixtures; gen 300 > commands/work-on/build/a.md
|
|
59
|
+
gen 200 > pkg/vendor/v.js; gen 100 > pkg/fixtures/f.json; mkdir -p build; gen 50 > build/root.js; git add -A
|
|
60
|
+
OUT="$(run --threshold 100)"
|
|
61
|
+
check "nested defaults counted" "$OUT" diff_lines 600; check "root build excluded" "$OUT" excluded_lines 50; check "nested defaults over" "$OUT" over true
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# a directory glob is not a substring match (distribution/ is not dist/)
|
|
65
|
+
reset; mkdir -p distribution; gen 30 > distribution/a.txt; git add -A
|
|
66
|
+
OUT="$(run --threshold 100)"; check "dist is not substring" "$OUT" diff_lines 30
|
|
67
|
+
|
|
68
|
+
# extra glob is additive
|
|
69
|
+
reset; gen 40 > keep.txt; mkdir -p docs/gen; gen 400 > docs/gen/big.md; git add -A
|
|
70
|
+
OUT="$(run --threshold 100 --exclude-glob 'docs/gen/*')"
|
|
71
|
+
check "extra glob lines" "$OUT" diff_lines 40; check "extra glob excluded" "$OUT" excluded_lines 400
|
|
72
|
+
OUT="$(run --threshold 100 --exclude-glob 'docs/gen/*' --exclude-glob '*.txt')"
|
|
73
|
+
check "two extra globs" "$OUT" diff_lines 0
|
|
74
|
+
|
|
75
|
+
# glob is data, not evaluated
|
|
76
|
+
reset; gen 10 > a.txt; git add -A
|
|
77
|
+
OUT="$(run --exclude-glob '$(touch '"$TMP"'/pwned)' 2>/dev/null)"
|
|
78
|
+
[ ! -e "$TMP/pwned" ] && ok || bad "glob evaluated as code"
|
|
79
|
+
|
|
80
|
+
# binary file counts 0
|
|
81
|
+
reset; head -c 2000 /dev/urandom > b.bin; gen 7 > a.txt; git add -A
|
|
82
|
+
OUT="$(run)"; check "binary counts 0" "$OUT" diff_lines 7
|
|
83
|
+
|
|
84
|
+
# deletions and renames count both sides (--no-renames)
|
|
85
|
+
reset; gen 20 > r.txt; git add -A; git commit -qm r; git push -q origin feat:staging 2>/dev/null
|
|
86
|
+
git mv r.txt r2.txt; OUT="$(run)"; check "rename counts add+delete" "$OUT" diff_lines 40
|
|
87
|
+
git reset -q --hard origin/staging
|
|
88
|
+
|
|
89
|
+
# committed + staged are both measured
|
|
90
|
+
reset; gen 10 > c1.txt; git add -A; git commit -qm c1; gen 5 > c2.txt; git add -A
|
|
91
|
+
OUT="$(run)"; check "committed + staged" "$OUT" diff_lines 15
|
|
92
|
+
|
|
93
|
+
# top files, largest first
|
|
94
|
+
reset; gen 3 > s.txt; gen 9 > l.txt; git add -A
|
|
95
|
+
OUT="$(run)"; FIRST="$(printf '%s\n' "$OUT" | sed -n 's/^top=//p' | head -n1)"
|
|
96
|
+
[ "$FIRST" = "9 l.txt" ] && ok || bad "top ordering (got '$FIRST')"
|
|
97
|
+
|
|
98
|
+
# fail closed: bad repo path, bad base, bad threshold, unknown arg, missing base
|
|
99
|
+
OUT="$(bash "$S" --repo-path "$TMP/nope" --base staging 2>/dev/null)"; rc=$?
|
|
100
|
+
{ [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "bad repo path (rc=$rc out='$OUT')"
|
|
101
|
+
OUT="$(run2() { bash "$S" --repo-path "$TMP/w" --base nonexistent 2>/dev/null; }; run2)"; rc=$?
|
|
102
|
+
{ [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "bad base (rc=$rc out='$OUT')"
|
|
103
|
+
OUT="$(bash "$S" --repo-path "$TMP/w" --base staging --threshold abc 2>/dev/null)"; rc=$?
|
|
104
|
+
{ [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "bad threshold (rc=$rc)"
|
|
105
|
+
OUT="$(bash "$S" --repo-path "$TMP/w" --bogus 2>/dev/null)"; rc=$?
|
|
106
|
+
{ [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "unknown arg (rc=$rc)"
|
|
107
|
+
OUT="$(bash "$S" --repo-path "$TMP/w" 2>/dev/null)"; rc=$?
|
|
108
|
+
{ [ "$rc" = 2 ] && [ -z "$OUT" ]; } && ok || bad "missing base (rc=$rc)"
|
|
109
|
+
|
|
110
|
+
# SEC-1: commits landing on the base after the branch point are not counted
|
|
111
|
+
reset; git checkout -q staging; gen 3000 > base-only.txt; git add base-only.txt; git commit -qm "base moves"
|
|
112
|
+
git push -q origin staging 2>/dev/null; git checkout -q feat; gen 10 > a.txt; git add a.txt
|
|
113
|
+
OUT="$(run --threshold 100)"
|
|
114
|
+
check "moving base lines" "$OUT" diff_lines 10; check "moving base over" "$OUT" over false
|
|
115
|
+
|
|
116
|
+
echo "diff-size.test.sh: $PASS passed, $FAILN failed"
|
|
117
|
+
[ "$FAILN" = 0 ]
|
|
@@ -229,5 +229,20 @@ if command -v node >/dev/null 2>&1; then
|
|
|
229
229
|
done
|
|
230
230
|
fi
|
|
231
231
|
|
|
232
|
+
# Guard (#3400): gate-helper (trusted-comments.sh) resolution loops must never list "$PWD" — the repo
|
|
233
|
+
# under review is author-controlled. Exact copy counts + normalized identity prevent drift.
|
|
234
|
+
TC_LOOPS="commands/review-pr.md:7 commands/work-on/review.md:1"
|
|
235
|
+
: > "$T/tc_all"
|
|
236
|
+
for e in $TC_LOOPS; do
|
|
237
|
+
f="${e%%:*}"; want="${e##*:}"
|
|
238
|
+
n=$(grep -c 'trusted-comments.sh" \]' "$ROOT/$f"); expect "trusted-comments.sh loop count in $f" "$want" "$n"
|
|
239
|
+
grep -n 'for _c in' "$ROOT/$f" | while IFS=: read -r ln _; do
|
|
240
|
+
sed -n "$((ln+1))p" "$ROOT/$f" | grep -q 'trusted-comments.sh' && sed -n "${ln}p" "$ROOT/$f" | sed 's/^[ ]*//' >> "$T/tc_all"
|
|
241
|
+
done
|
|
242
|
+
done
|
|
243
|
+
expect "trusted-comments.sh loops total" 8 "$(wc -l < "$T/tc_all" | tr -d ' ')"
|
|
244
|
+
expect "trusted-comments.sh loops byte-identical" 1 "$(sort -u "$T/tc_all" | wc -l | tr -d ' ')"
|
|
245
|
+
grep -qF '"$PWD"' "$T/tc_all" && bad 'trusted-comments.sh loop lists "$PWD"' || ok
|
|
246
|
+
|
|
232
247
|
echo "forge-root tests: pass=$PASS fail=$FAILN skipped=$SKIPPED"
|
|
233
248
|
[ "$FAILN" -eq 0 ]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# remediation-marker-match.test.sh — the anchored, trusted FORGE:REMEDIATION trail match (forge#3412)
|
|
3
|
+
# Exercises the exact regexes used by commands/work-on/remediate.md Phase M0, commands/work-on.md Phase 0B
|
|
4
|
+
# and commands/orchestrate/phase-4-execution.md. No network. Usage: bash scripts/remediation-marker-match.test.sh
|
|
5
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
6
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
7
|
+
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
TC="$SCRIPT_DIR/trusted-comments.sh"
|
|
12
|
+
unset FORGE_TRAIL_TRUSTED_ASSOCIATIONS FORGE_TRAIL_TRUSTED_LOGINS
|
|
13
|
+
|
|
14
|
+
PASS=0; FAILN=0
|
|
15
|
+
ok() { PASS=$((PASS+1)); echo "ok - $1"; }
|
|
16
|
+
bad() { FAILN=$((FAILN+1)); echo "FAIL - $1"; }
|
|
17
|
+
eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (got '$2', want '$3')"; fi; }
|
|
18
|
+
|
|
19
|
+
COMPLETE_RE='^<!-- FORGE:REMEDIATION -->[\s\S]*<!-- FORGE:REMEDIATION:COMPLETE -->'
|
|
20
|
+
TRAIL_RE='^<!-- FORGE:REMEDIATION -->'
|
|
21
|
+
# c <assoc> <type> <login> <body>
|
|
22
|
+
c() { jq -nc --arg a "$1" --arg t "$2" --arg l "$3" --arg b "$4" '{id:1,author_association:$a,user:{type:$t,login:$l},body:$b}'; }
|
|
23
|
+
complete_n() { printf '%s' "$1" | bash "$TC" count "$COMPLETE_RE"; }
|
|
24
|
+
trail_n() { printf '%s' "$1" | bash "$TC" count "$TRAIL_RE"; }
|
|
25
|
+
|
|
26
|
+
REAL=$'<!-- FORGE:REMEDIATION -->\n## Remediation\nVerdict: FIXED\n\n<!-- FORGE:REMEDIATION:COMPLETE -->'
|
|
27
|
+
INTERIM=$'<!-- FORGE:REMEDIATION -->\n## Remediation (in progress)'
|
|
28
|
+
QUOTED_REVIEW=$'## Review\nThe prior run posted FORGE:REMEDIATION:COMPLETE on this PR.\n<!-- FORGE:REMEDIATION:COMPLETE -->'
|
|
29
|
+
QUOTED_WORK_ORDER=$'<!-- FORGE:INPR_FIX: head=abc1234 -->\nquoting <!-- FORGE:REMEDIATION -->\n<!-- FORGE:REMEDIATION:COMPLETE -->'
|
|
30
|
+
|
|
31
|
+
eq "genuine M8 trail by bot matches COMPLETE" "$(complete_n "[$(c NONE Bot 'app[bot]' "$REAL")]")" 1
|
|
32
|
+
eq "review comment quoting COMPLETE mid-body does not match" "$(complete_n "[$(c OWNER User u "$QUOTED_REVIEW")]")" 0
|
|
33
|
+
eq "INPR_FIX work order quoting the markers does not match COMPLETE" "$(complete_n "[$(c NONE Bot b "$QUOTED_WORK_ORDER")]")" 0
|
|
34
|
+
eq "INPR_FIX work order quoting the markers is not a trail comment" "$(trail_n "[$(c NONE Bot b "$QUOTED_WORK_ORDER")]")" 0
|
|
35
|
+
eq "quoting comment posted AFTER the real trail does not hide it" "$(complete_n "[$(c NONE Bot b "$REAL"),$(c NONE Bot b "$QUOTED_WORK_ORDER")]")" 1
|
|
36
|
+
eq "untrusted-author trail does not match" "$(complete_n "[$(c NONE User mallory "$REAL")]")" 0
|
|
37
|
+
eq "trusted human (MEMBER) trail matches" "$(complete_n "[$(c MEMBER User dev "$REAL")]")" 1
|
|
38
|
+
eq "interim trail (no COMPLETE) is a trail but not complete" "$(complete_n "[$(c NONE Bot b "$INTERIM")]")/$(trail_n "[$(c NONE Bot b "$INTERIM")]")" "0/1"
|
|
39
|
+
eq "paginated concatenated arrays" "$(complete_n "[$(c NONE Bot b "$QUOTED_REVIEW")] [$(c NONE Bot b "$REAL")]")" 1
|
|
40
|
+
printf 'not json' | bash "$TC" count "$COMPLETE_RE" >/dev/null 2>&1; eq "unparsable comments fail closed (exit 2)" "$?" 2
|
|
41
|
+
|
|
42
|
+
# Spec wiring: no unanchored marker read remains, and each reader goes through the trust helper.
|
|
43
|
+
! grep -qF 'contains("FORGE:REMEDIATION")' "$ROOT/commands/work-on/remediate.md" && ok "remediate.md has no unanchored contains(FORGE:REMEDIATION)" || bad "remediate.md still uses unanchored contains"
|
|
44
|
+
! grep -qF 'contains("FORGE:REMEDIATION")' "$ROOT/commands/orchestrate/phase-4-execution.md" && ok "phase-4-execution.md has no unanchored contains(FORGE:REMEDIATION)" || bad "phase-4-execution.md still uses unanchored contains"
|
|
45
|
+
for f in commands/work-on/remediate.md commands/work-on.md commands/orchestrate/phase-4-execution.md; do
|
|
46
|
+
grep -qF 'trusted-comments.sh' "$ROOT/$f" && ok "$f references trusted-comments.sh" || bad "$f does not reference trusted-comments.sh"
|
|
47
|
+
done
|
|
48
|
+
grep -qF "count '$COMPLETE_RE'" "$ROOT/commands/work-on/remediate.md" && ok "remediate.md uses the tested COMPLETE regex" || bad "remediate.md COMPLETE regex drifted from this test"
|
|
49
|
+
|
|
50
|
+
echo "passed=$PASS failed=$FAILN"
|
|
51
|
+
[ "$FAILN" -eq 0 ]
|