forgedock 1.16.2 → 1.18.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/bin/engine/outcome-codes.mjs +114 -0
- package/bin/engine/phases.mjs +211 -100
- package/bin/engine/projector.mjs +48 -1
- package/bin/engine/runlog.mjs +20 -5
- package/bin/engine/telemetry-seam.mjs +62 -0
- package/bin/engine/telemetry-seam.test.mjs +60 -0
- package/bin/engine/telemetry.mjs +148 -0
- package/bin/engine/telemetry.test.mjs +120 -0
- package/bin/engine-cli.mjs +17 -1
- package/bin/engine.mjs +227 -48
- package/bin/forgedock.mjs +8 -2
- package/bin/hooks/interactive-engine.mjs +10 -0
- package/bin/hooks/pre-tool-use.mjs +220 -0
- package/bin/runner.mjs +70 -4
- package/bin/stats/aggregate.mjs +231 -0
- package/bin/stats/cache.mjs +40 -0
- package/bin/stats/github-source.mjs +211 -0
- package/bin/stats/index.mjs +154 -0
- package/bin/stats/local-source.mjs +79 -0
- package/bin/stats/package.json +6 -0
- package/bin/stats/render.mjs +82 -0
- package/bin/stats/stats.test.mjs +312 -0
- package/commands/cleanup.md +90 -2
- package/commands/deploy-info.md +8 -4
- package/commands/deploy-pr.md +64 -0
- package/commands/fix-ci.md +20 -6
- package/commands/milestone.md +34 -17
- package/commands/orchestrate/phase-1-resolve.md +4 -1
- package/commands/orchestrate/phase-4-execution.md +28 -6
- package/commands/orchestrate/phase-5-cleanup.md +18 -2
- package/commands/pipeline-health/phase-1-context.md +2 -0
- package/commands/pipeline-health/phase-2-metrics.md +12 -5
- package/commands/pipeline-status.md +21 -1
- package/commands/review-pr-agents/protocols.md +1 -0
- package/commands/review-pr-agents.md +10 -2
- package/commands/review-pr-staging.md +196 -496
- package/commands/review-pr.md +512 -3134
- package/commands/test-gate.md +25 -10
- package/commands/work-on/build/architect.md +13 -1
- package/commands/work-on/build/implement.md +28 -6
- package/commands/work-on/build/validate.md +5 -2
- package/commands/work-on/build.md +197 -145
- package/commands/work-on/close.md +66 -20
- package/commands/work-on/decompose.md +113 -34
- package/commands/work-on/investigate.md +15 -14
- package/commands/work-on/remediate.md +49 -19
- package/commands/work-on/review.md +7 -5
- package/commands/work-on.md +1 -2
- package/docs/CONFIG.md +76 -24
- package/docs/OUTCOME-CODES.md +53 -0
- package/docs/RELEASE-NOTES.md +24 -0
- package/docs/STATS.md +63 -0
- package/docs/TELEMETRY.md +65 -0
- package/docs/WORK-ON-RUNTIME.md +19 -0
- package/docs/site/command-reference.md +1 -1
- package/docs/spec/forge-protocol-v1.md +2 -2
- package/docs/spec/label-state-machine.md +1 -1
- package/package.json +3 -2
- package/packages/protocol/src/phases.js +5 -5
- package/scripts/builder-resume-match.test.sh +58 -0
- package/scripts/cap-findings.sh +110 -0
- package/scripts/cap-findings.test.sh +130 -0
- package/scripts/check-spec-bash.sh +5 -1
- package/scripts/check-spec-bash.test.sh +20 -0
- package/scripts/check-spec-markers.sh +2 -0
- package/scripts/classify-finding.sh +65 -16
- package/scripts/classify-finding.test.sh +43 -1
- package/scripts/classify-grep-exit.sh +20 -0
- package/scripts/classify-grep-exit.test.sh +46 -0
- package/scripts/close-parent-c3.test.sh +123 -0
- package/scripts/cost-report.sh +146 -0
- package/scripts/cost-report.test.sh +80 -0
- package/scripts/diff-size.sh +97 -3
- package/scripts/diff-size.test.sh +59 -2
- package/scripts/doctor-pipeline-state.sh +43 -0
- package/scripts/file-deferred-followups.sh +159 -0
- package/scripts/file-deferred-followups.test.sh +106 -0
- package/scripts/fixtures/cost-report/a-merged1.jsonl +6 -0
- package/scripts/fixtures/cost-report/a-merged2-remediated.jsonl +7 -0
- package/scripts/fixtures/cost-report/a-needs-human.jsonl +4 -0
- package/scripts/fixtures/cost-report/b-merged.jsonl +3 -0
- package/scripts/fixtures/cost-report/c-merged-attempt-usage.jsonl +8 -0
- package/scripts/fixtures/cost-report/hydrated-no-usage.jsonl +3 -0
- package/scripts/fixtures/cost-report/old-log-no-version.jsonl +3 -0
- package/scripts/fixtures/cost-report/unfinished-no-terminal.jsonl +2 -0
- package/scripts/fixtures/cost-report/unfinished-reopened.jsonl +5 -0
- package/scripts/forge-root.test.sh +18 -11
- package/scripts/lib/fence-bootstrap.sh +4 -3
- package/scripts/lib/pr-issue-number.sh +18 -0
- package/scripts/lib/review-template-resolve.sh +41 -0
- package/scripts/lib/telemetry.sh +168 -0
- package/scripts/publish-staging-sync.test.sh +48 -0
- package/scripts/remediate-liveness.sh +42 -6
- package/scripts/resolve-deploy-gate.sh +32 -6
- package/scripts/resolve-deploy-gate.test.sh +42 -1
- package/scripts/review-acceptance-gate.sh +16 -7
- package/scripts/review-acceptance-gate.test.sh +13 -0
- package/scripts/review-finalize.sh +1427 -0
- package/scripts/review-finalize.test.sh +833 -0
- package/scripts/review-pr-delta-wiring.test.sh +19 -72
- package/scripts/review-pr-domain-map.test.sh +23 -13
- package/scripts/review-pr-gate2.test.sh +24 -65
- package/scripts/review-pr-staging-finding-filter.test.sh +44 -0
- package/scripts/review-pr-staging-scope.test.sh +56 -80
- package/scripts/review-prepare.sh +553 -0
- package/scripts/review-prepare.test.sh +288 -0
- package/scripts/review-roster.sh +186 -0
- package/scripts/review-roster.test.sh +185 -0
- package/scripts/run-shell-tests.sh +3 -0
- package/scripts/telemetry.test.sh +106 -0
- package/scripts/trusted-comments.test.sh +9 -2
- package/scripts/wait-ci-green.sh +8 -1
- package/scripts/wait-ci-green.test.sh +3 -3
- package/templates/forge.yaml.minimal +9 -0
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
# A base is the deploy gate when it is empty/`null`, the literal `main` (hard
|
|
12
12
|
# floor), the repository's GitHub default branch (`default_branch`), or the
|
|
13
13
|
# `branches.default` of the default branch's COMMITTED forge.yaml
|
|
14
|
-
# (`origin/<default>:forge.yaml`, falling back to the contents API
|
|
14
|
+
# (`refs/remotes/origin/<default>:forge.yaml`, falling back to the contents API;
|
|
15
|
+
# if neither source is readable the result is `true`, never `false`; a source that
|
|
16
|
+
# cleanly reports forge.yaml absent is "no config" and falls through) or of an
|
|
15
17
|
# explicit trusted config (FORGE_TRUSTED_CONFIG, orchestrator-supplied).
|
|
16
18
|
#
|
|
17
19
|
# It NEVER reads forge.yaml from $PWD: the PR worktree is author-controlled, so
|
|
@@ -34,13 +36,37 @@ GH_DEFAULT=$(gh api "repos/${REPO}" --jq .default_branch 2>/dev/null || echo "")
|
|
|
34
36
|
|
|
35
37
|
_cfg_default() { yq '.branches.default // ""' 2>/dev/null || true; }
|
|
36
38
|
|
|
39
|
+
# Parse stdin's branches.default. Returns non-zero when yq is missing or the
|
|
40
|
+
# parse fails, so "config unreadable" is distinguishable from "no branches.default".
|
|
41
|
+
_cfg_parse() { command -v yq >/dev/null 2>&1 && yq '.branches.default // ""' 2>/dev/null; }
|
|
42
|
+
|
|
37
43
|
CFG_DEFAULT=""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
READ_OK=""
|
|
45
|
+
# Committed default-branch config only (a ref, never the working tree). The
|
|
46
|
+
# fully-qualified ref cannot be shadowed by a local tag/branch named origin/<default>.
|
|
47
|
+
# A present ref with no forge.yaml at that path is a clean "no config" read (READ_OK,
|
|
48
|
+
# empty default); an unfetched ref, missing yq or parse error stays unreadable.
|
|
49
|
+
CFG_REF="refs/remotes/origin/${GH_DEFAULT}"
|
|
50
|
+
if git rev-parse -q --verify "${CFG_REF}^{commit}" >/dev/null 2>&1; then
|
|
51
|
+
if git cat-file -e "${CFG_REF}:forge.yaml" 2>/dev/null; then
|
|
52
|
+
CFG_RAW=$(git show "${CFG_REF}:forge.yaml" 2>/dev/null) && [ -n "$CFG_RAW" ] \
|
|
53
|
+
&& CFG_DEFAULT=$(printf '%s\n' "$CFG_RAW" | _cfg_parse) && READ_OK=1
|
|
54
|
+
else
|
|
55
|
+
READ_OK=1
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
if [ -z "$READ_OK" ]; then
|
|
59
|
+
CFG_DEFAULT=""
|
|
60
|
+
API_OUT=$(gh api "repos/${REPO}/contents/forge.yaml?ref=${GH_DEFAULT}" --jq .content 2>&1); API_RC=$?
|
|
61
|
+
if [ "$API_RC" -eq 0 ]; then
|
|
62
|
+
CFG_RAW=$(printf '%s' "$API_OUT" | base64 -d 2>/dev/null) && [ -n "$CFG_RAW" ] \
|
|
63
|
+
&& CFG_DEFAULT=$(printf '%s\n' "$CFG_RAW" | _cfg_parse) && READ_OK=1
|
|
64
|
+
elif printf '%s' "$API_OUT" | grep -q 'HTTP 404'; then
|
|
65
|
+
READ_OK=1 # API says the file does not exist: clean "no config", not a read failure
|
|
66
|
+
fi
|
|
43
67
|
fi
|
|
68
|
+
# Config unreadable from every source (no yq, ref unfetched, API failing): fail closed.
|
|
69
|
+
[ -n "$READ_OK" ] || emit true
|
|
44
70
|
[ -n "$CFG_DEFAULT" ] && [ "$CFG_DEFAULT" != "null" ] && [ "$BASE" = "$CFG_DEFAULT" ] && emit true
|
|
45
71
|
|
|
46
72
|
if [ -n "${FORGE_TRUSTED_CONFIG:-}" ] && [ -f "$FORGE_TRUSTED_CONFIG" ]; then
|
|
@@ -9,7 +9,7 @@ TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
|
9
9
|
ok() { PASS=$((PASS+1)); }
|
|
10
10
|
bad() { FAILN=$((FAILN+1)); echo "FAIL: $1"; }
|
|
11
11
|
|
|
12
|
-
command -v yq >/dev/null 2>&1 ||
|
|
12
|
+
HAVE_YQ=1; command -v yq >/dev/null 2>&1 || HAVE_YQ=0
|
|
13
13
|
|
|
14
14
|
# Stub gh: GH_DEFAULT_STUB drives `repos/<r> --jq .default_branch`; contents API unused (fails).
|
|
15
15
|
mkdir -p "$TMP/bin"
|
|
@@ -17,6 +17,7 @@ cat > "$TMP/bin/gh" <<'EOF'
|
|
|
17
17
|
#!/usr/bin/env bash
|
|
18
18
|
case "$*" in
|
|
19
19
|
*"--jq .default_branch"*) [ -n "${GH_FAIL:-}" ] && exit 1; printf '%s\n' "${GH_DEFAULT_STUB:-}" ;;
|
|
20
|
+
*"contents/forge.yaml"*) [ -n "${GH_CONTENTS_404:-}" ] && { echo "gh: Not Found (HTTP 404)" >&2; exit 1; }; exit 1 ;;
|
|
20
21
|
*) exit 1 ;;
|
|
21
22
|
esac
|
|
22
23
|
EOF
|
|
@@ -40,6 +41,7 @@ run() { # base default_stub [env...]
|
|
|
40
41
|
}
|
|
41
42
|
expect() { [ "$1" = "$2" ] && ok || bad "$3: got '$1' want '$2'"; }
|
|
42
43
|
|
|
44
|
+
if [ "$HAVE_YQ" = 1 ]; then
|
|
43
45
|
expect "$(run main trunk)" true "main with worktree release"
|
|
44
46
|
expect "$(run release trunk)" false "worktree-declared branch is not a gate"
|
|
45
47
|
expect "$(run staging trunk)" false "staging is fast lane"
|
|
@@ -55,5 +57,44 @@ printf 'branches:\n default: prod\n' > "$TMP/trusted.yaml"
|
|
|
55
57
|
expect "$(run prod trunk FORGE_TRUSTED_CONFIG="$TMP/trusted.yaml")" true "trusted config default"
|
|
56
58
|
expect "$(run prod trunk)" false "prod without trusted config"
|
|
57
59
|
|
|
60
|
+
|
|
61
|
+
# Shadowing: a local tag named origin/trunk carrying a config without `develop`
|
|
62
|
+
# must not displace the committed refs/remotes/origin/trunk config.
|
|
63
|
+
printf 'branches:\n default: other\n' > "$REPO/forge.yaml"
|
|
64
|
+
git -C "$REPO" add forge.yaml; git -C "$REPO" commit -qm shadow
|
|
65
|
+
git -C "$REPO" tag origin/trunk
|
|
66
|
+
expect "$(run develop trunk)" true "shadow tag origin/trunk does not displace committed config"
|
|
67
|
+
expect "$(run other trunk)" false "shadow tag config is not honoured"
|
|
68
|
+
git -C "$REPO" tag -d origin/trunk >/dev/null
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Config read fail: no origin ref and failing contents API -> true (fail closed).
|
|
72
|
+
NOREF="$TMP/noref"; git init -q "$NOREF"
|
|
73
|
+
run_in() { # dir base [env...]
|
|
74
|
+
local dir="$1" base="$2"; shift 2
|
|
75
|
+
(cd "$dir" && env PATH="$TMP/bin:$PATH" GH_DEFAULT_STUB=trunk "$@" bash "$S" "$base" owner/repo)
|
|
76
|
+
}
|
|
77
|
+
expect "$(run_in "$NOREF" staging)" true "config read fail (no origin ref, API failing) fails closed"
|
|
78
|
+
|
|
79
|
+
# Missing forge.yaml is a clean "no config" read, not a read failure (falls through to false).
|
|
80
|
+
NOCFG="$TMP/nocfg.git"; NOCFGW="$TMP/nocfgw"
|
|
81
|
+
git init -q --bare "$NOCFG"; git init -q "$NOCFGW"
|
|
82
|
+
git -C "$NOCFGW" config user.email t@t; git -C "$NOCFGW" config user.name t
|
|
83
|
+
echo x > "$NOCFGW/README"; git -C "$NOCFGW" add README; git -C "$NOCFGW" commit -qm init
|
|
84
|
+
git -C "$NOCFGW" branch -M trunk; git -C "$NOCFGW" remote add origin "$NOCFG"
|
|
85
|
+
git -C "$NOCFGW" push -q origin trunk 2>/dev/null; git -C "$NOCFGW" fetch -q origin
|
|
86
|
+
expect "$(run_in "$NOCFGW" staging)" false "forge.yaml absent on default branch ref is no-config (fast lane)"
|
|
87
|
+
expect "$(run_in "$NOCFGW" main)" true "main stays strict with no config"
|
|
88
|
+
expect "$(run_in "$NOCFGW" trunk)" true "github default stays strict with no config"
|
|
89
|
+
# API 404 (ref not fetched, file absent) is also a clean no-config read; other API failures stay closed.
|
|
90
|
+
expect "$(run_in "$NOREF" staging GH_CONTENTS_404=1)" false "contents API 404 is no-config"
|
|
91
|
+
expect "$(run_in "$NOREF" staging)" true "non-404 contents API failure fails closed"
|
|
92
|
+
|
|
93
|
+
# Config unreadable: yq unusable (a failing stub shadows any real yq; portable to Git Bash, unlike a stripped PATH) even though the ref is readable.
|
|
94
|
+
NOYQ="$TMP/noyqbin"; mkdir -p "$NOYQ"
|
|
95
|
+
printf '#!/usr/bin/env bash\nexit 127\n' > "$NOYQ/yq"; chmod +x "$NOYQ/yq"
|
|
96
|
+
cp "$TMP/bin/gh" "$NOYQ/gh"
|
|
97
|
+
expect "$(cd "$REPO" && env PATH="$NOYQ:$PATH" GH_DEFAULT_STUB=trunk bash "$S" staging owner/repo)" true "config unreadable (yq failing) fails closed"
|
|
98
|
+
|
|
58
99
|
echo "resolve-deploy-gate.test.sh: $PASS passed, $FAILN failed"
|
|
59
100
|
[ "$FAILN" -eq 0 ]
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
#
|
|
6
6
|
# Usage: review-acceptance-gate.sh <issue> <gh_repo> "<gh_flag>" <worktree_path> (DRY_RUN=1 skips gh writes)
|
|
7
|
-
# Evaluates the ACCEPTANCE_CHECK lines the build gate (B6.5) deferred: `phase=review` checks, plus
|
|
8
|
-
#
|
|
7
|
+
# Evaluates the ACCEPTANCE_CHECK lines the build gate (B6.5) deferred: `phase=review` checks, plus command/behavior
|
|
8
|
+
# checks on a `gh pr` target (B6.5's legacy deferral ignores any phase= tag, so a tagged phase=build one is selected too). Uses its own FORGE:ACCEPTANCE_GATE_REVIEW marker, never the build
|
|
9
9
|
# gate's FORGE:ACCEPTANCE_GATE header or :PASSED/:FAILED/:BLOCKED sentinels (the engine reads those).
|
|
10
10
|
# Malformed checks (forge#3694 parity with B6.5: 4+ backslash run, `bash -n` parse failure, grep usage error) are skipped and listed,
|
|
11
11
|
# never a FAIL and never needs-human. With zero verified checks and some malformed, a non-blocking notice is posted (no PASSED marker).
|
|
12
12
|
# Exit: 0 passed / no-op / notice / already passed for this head, 1 a deferred check failed (issue gets needs-human).
|
|
13
13
|
set -uo pipefail
|
|
14
|
+
# Telemetry (forge#3834): best-effort decision record; never changes stdout, stderr or the exit code.
|
|
15
|
+
_TEL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/telemetry.sh"; [ -f "$_TEL" ] && . "$_TEL" 2>/dev/null
|
|
16
|
+
type forge_emit >/dev/null 2>&1 || forge_emit() { :; }
|
|
17
|
+
[ -z "${DRY_RUN:-}" ] || FORGE_TELEMETRY=off
|
|
14
18
|
|
|
15
19
|
NUMBER="${1:?issue number}"; GH_REPO="${2:?repo}"; GH_FLAG="${3:-}"; WT="${4:-.}"
|
|
16
20
|
cd "$WT" || exit 2
|
|
@@ -42,6 +46,7 @@ if printf '%s' "$LAST" | grep -qF "<!-- FORGE:ACCEPTANCE_GATE_REVIEW:PASSED:${HE
|
|
|
42
46
|
fi
|
|
43
47
|
|
|
44
48
|
CHECKS=$(printf '%s\n' "$TRUSTED_INV" | jq -r '.' 2>/dev/null | grep "^ACCEPTANCE_CHECK:")
|
|
49
|
+
GREP_CLS=$(forge_resolve classify-grep-exit.sh) # unresolved = grep exit>=2 stays a real FAIL (fail closed, forge#3802)
|
|
45
50
|
GATE_PASS=true; FAILED=""; VERIFIED_COUNT=0; MALFORMED_CHECKS=""; RESULTS=""
|
|
46
51
|
while IFS= read -r check_line; do
|
|
47
52
|
[ -z "$check_line" ] && continue
|
|
@@ -57,12 +62,12 @@ while IFS= read -r check_line; do
|
|
|
57
62
|
[ "$PHASE" = "review" ] || PHASE=build
|
|
58
63
|
case "$TYPE" in command|behavior) ;; *) [ "$PHASE" = "review" ] || continue ;; esac
|
|
59
64
|
if [ "$PHASE" != "review" ]; then
|
|
60
|
-
printf '%s\n' "$check_line" | grep -qE ' phase=(build|review)( |$)' && continue
|
|
61
65
|
case "$TARGET" in *'gh pr'*) ;; *) continue ;; esac
|
|
62
66
|
fi
|
|
63
67
|
# malformed-check (forge#3694): static screen only parses (bash -n), never executes.
|
|
64
68
|
MALFORMED=""
|
|
65
69
|
{ [ "$TYPE" = "command" ] || [ "$TYPE" = "behavior" ]; } && { case "$TARGET" in *'\\\\'*) MALFORMED="run of 4+ backslashes (over-escaped target)";; esac; [ -n "$MALFORMED" ] || bash -n -c "$TARGET" 2>/dev/null || MALFORMED="shell syntax error (unbalanced quotes)"; }
|
|
70
|
+
{ [ "$TYPE" = "command" ] || [ "$TYPE" = "behavior" ]; } && [ -z "$MALFORMED" ] && case "$(printf '%s' "$TARGET" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" in none) MALFORMED='target "none" is not a command (a manual criterion must be type=skipped)';; esac
|
|
66
71
|
if [ -n "$MALFORMED" ]; then MALFORMED_CHECKS="${MALFORMED_CHECKS}\n- **$ID** ($DESC): $MALFORMED"; continue; fi
|
|
67
72
|
RESULT="PASS"; DETAIL=""
|
|
68
73
|
case "$TYPE" in
|
|
@@ -72,12 +77,14 @@ while IFS= read -r check_line; do
|
|
|
72
77
|
command|behavior)
|
|
73
78
|
if [ "$MATCHER" = "exit_0" ]; then
|
|
74
79
|
ERR=$(eval "$TARGET" 2>&1 >/dev/null); RC=$?
|
|
75
|
-
# grep exit >= 2 is a usage error, not a verdict; a
|
|
76
|
-
[ "$RC" -ge 2 ] &&
|
|
80
|
+
# grep exit >= 2 is a usage error, not a verdict; only a positively identified usage error (classify-grep-exit.sh) is malformed; a missing operand stays a real FAIL even with stderr suppressed.
|
|
81
|
+
[ "$RC" -ge 2 ] && MALFORMED=$(bash "$GREP_CLS" "$RC" "$ERR" "$TARGET" 2>/dev/null)
|
|
82
|
+
[ "$RC" -eq 127 ] && [ -z "$MALFORMED" ] && printf '%s' "$ERR" | grep -qF 'command not found' && MALFORMED="command not found (rc 127): the target is not an executable command"
|
|
77
83
|
[ "$RC" -eq 0 ] || [ -n "$MALFORMED" ] || { RESULT="FAIL"; DETAIL="command exited non-zero: $TARGET"; }
|
|
78
84
|
else
|
|
79
|
-
OUTPUT=$(eval "$TARGET" 2
|
|
80
|
-
[ "$RC" -ge 2 ] &&
|
|
85
|
+
ERRF=$(mktemp); OUTPUT=$(eval "$TARGET" 2>"$ERRF"); RC=$?; ERR=$(cat "$ERRF"); rm -f "$ERRF"; [ -z "$ERR" ] || OUTPUT="${OUTPUT}${OUTPUT:+$'\n'}${ERR}" # classify on stderr only, match on both
|
|
86
|
+
[ "$RC" -ge 2 ] && MALFORMED=$(bash "$GREP_CLS" "$RC" "$ERR" "$TARGET" 2>/dev/null)
|
|
87
|
+
[ "$RC" -eq 127 ] && [ -z "$MALFORMED" ] && printf '%s' "$ERR" | grep -qF 'command not found' && MALFORMED="command not found (rc 127): the target is not an executable command"
|
|
81
88
|
if [ -n "$MALFORMED" ]; then :
|
|
82
89
|
else
|
|
83
90
|
printf '%s\n' "$OUTPUT" | grep -qE "$MATCHER" || printf '%s\n' "$OUTPUT" | grep -qF -- "$MATCHER" \
|
|
@@ -95,6 +102,8 @@ done <<< "$CHECKS"
|
|
|
95
102
|
MALFORMED_SECTION=""
|
|
96
103
|
[ -n "$MALFORMED_CHECKS" ] && MALFORMED_SECTION=$(printf '\n**malformed-check (not counted as failures; regenerate the acceptance spec by re-running investigate)**:%b\n' "$MALFORMED_CHECKS")
|
|
97
104
|
|
|
105
|
+
_nf=$(printf '%b' "$FAILED" | grep -c '^- ' || true); _nm=$(printf '%b' "$MALFORMED_CHECKS" | grep -c '^- ' || true)
|
|
106
|
+
FORGE_REPO="${FORGE_REPO:-$GH_REPO}" FORGE_ISSUE="${FORGE_ISSUE:-$NUMBER}" forge_emit acceptance_gate phase=review verified="$VERIFIED_COUNT" failed="${_nf:-0}" malformed="${_nm:-0}" deferred=$((VERIFIED_COUNT + ${_nm:-0}))
|
|
98
107
|
if [ "$VERIFIED_COUNT" -eq 0 ] && [ -z "$MALFORMED_CHECKS" ]; then
|
|
99
108
|
echo "No deferred acceptance checks; R2.5 is a no-op."
|
|
100
109
|
elif [ "$VERIFIED_COUNT" -eq 0 ]; then
|
|
@@ -75,6 +75,9 @@ inv "$(chk ac-1 false)"; rc=$(run); eq "real fail: rc 1" "$rc" 1
|
|
|
75
75
|
grep -q 'needs-human' "$TMP/out" && ok "real fail: needs-human added" || bad "real fail: no needs-human"
|
|
76
76
|
inv "$(chk ac-1 'grep -q nomatch f.txt')"; rc=$(run); eq "grep exit 1 is a real fail" "$rc" 1
|
|
77
77
|
inv "$(chk ac-1 'grep -q x nofile.txt')"; rc=$(run); eq "grep missing operand file stays a real fail" "$rc" 1
|
|
78
|
+
inv "$(chk ac-1 'grep -q x nofile.txt 2>/dev/null')"; rc=$(run); eq "stderr-suppressed missing operand is a real fail (forge#3802)" "$rc" 1
|
|
79
|
+
grep -q ':PASSED:' "$TMP/out" && bad "stderr-suppressed missing operand wrote PASSED" || ok "stderr-suppressed missing operand: no PASSED marker"
|
|
80
|
+
inv "$(chk ac-1 'grep -sq x nofile.txt')"; rc=$(run); eq "grep -s missing operand is a real fail (forge#3802)" "$rc" 1
|
|
78
81
|
|
|
79
82
|
for t in "echo 'unbalanced" 'echo a\\\\\\\\b' "grep -E '[' f.txt"; do
|
|
80
83
|
inv "$(chk ac-7 "$t")"; rc=$(run); eq "malformed skipped, rc 0: $t" "$rc" 0
|
|
@@ -87,4 +90,14 @@ inv "$(chk ac-1 true)" "$(chk ac-7 "echo 'unbalanced")"; rc=$(run); eq "mixed pa
|
|
|
87
90
|
grep -q ':PASSED:' "$TMP/out" && grep -qi 'malformed' "$TMP/out" && ok "mixed: PASSED with malformed listed" || bad "mixed: wrong output"
|
|
88
91
|
inv "$(chk ac-1 false)" "$(chk ac-7 "echo 'unbalanced")"; rc=$(run); eq "mixed fail+malformed: fails closed" "$rc" 1
|
|
89
92
|
|
|
93
|
+
# Selection parity with B6.5 (forge#3809): non-review checks are selected by the `gh pr` target only, never by a phase= text match.
|
|
94
|
+
bt() { printf 'ACCEPTANCE_CHECK: id=%s type=command target="%s" matcher="exit_0" phase=%s description=%s' "$1" "$2" "$3" "$4"; }
|
|
95
|
+
inv "$(bt ac-1 'gh pr view 1 && false' build d)"; rc=$(run); eq "phase=build gh pr check is evaluated (fails)" "$rc" 1
|
|
96
|
+
inv "$(bt ac-1 'gh pr view 1 && true' build d)"; rc=$(run)
|
|
97
|
+
grep -q ':PASSED:' "$TMP/out" && ok "phase=build gh pr check counted and passes" || bad "phase=build gh pr check not counted"
|
|
98
|
+
inv "$(bt ac-1 'false' build d)"; rc=$(run); eq "phase=build non-gh-pr check is not selected" "$rc" 0
|
|
99
|
+
inv "$(bt ac-1 'gh pr view 1 && false' build 'x phase=review y')"; rc=$(run); eq "phase=review in description does not change selection" "$rc" 1
|
|
100
|
+
inv "$(bt ac-1 'false' build 'x phase=review y')"; rc=$(run); eq "phase=review in description does not select a non-gh-pr check" "$rc" 0
|
|
101
|
+
inv 'ACCEPTANCE_CHECK: id=ac-1 type=command target="gh pr view 1 && false" matcher="exit_0" description=untagged'; rc=$(run); eq "untagged gh pr check is evaluated" "$rc" 1
|
|
102
|
+
|
|
90
103
|
echo "$PASS passed, $FAILN failed"; [ "$FAILN" -eq 0 ]
|