@metasession.co/devaudit-cli 0.3.7 → 0.3.8
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/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +10 -0
- package/sdlc/files/_common/scripts/close-out-contract.sh +34 -0
- package/sdlc/files/_common/scripts/derive-release-version.sh +18 -0
- package/sdlc/files/_common/scripts/derive-release-version.test.sh +9 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.sh +119 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.test.sh +278 -0
- package/sdlc/files/_common/scripts/validate-test-summary.sh +37 -0
- package/sdlc/files/_common/scripts/validate-test-summary.test.sh +87 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +16 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +7 -4
- package/sdlc/files/ci/check-release-approval.yml.template +80 -21
- package/sdlc/files/ci/ci-status-fallback.yml.template +3 -0
- package/sdlc/files/ci/ci.yml.template +24 -33
- package/sdlc/files/ci/close-out-release.yml.template +67 -11
- package/sdlc/files/ci/compliance-evidence.yml.template +32 -16
- package/sdlc/files/ci/quality-gates-provenance.yml.template +62 -0
- package/sdlc/files/stacks/node/hooks/pre-push +58 -8
- package/sdlc/package.json +1 -1
- package/sdlc/src/bin/devaudit-sdlc.js +22 -2
- package/sdlc/src/blueprints/3-compile-evidence.raw.md +20 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^0.8.2",
|
|
36
|
-
"@metasession.co/devaudit-plugin-sdk": "^0.3.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.3.8",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"consola": "^3.2.3",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
# release-ticket H1). Forwarded as
|
|
22
22
|
# `releaseTitle`; the portal no-clobbers
|
|
23
23
|
# existing non-null values.
|
|
24
|
+
# --release-summary <text> Reviewer-facing short description of what
|
|
25
|
+
# the release covers. Forwarded as
|
|
26
|
+
# `releaseSummary`; the portal no-clobbers
|
|
27
|
+
# existing non-null values.
|
|
28
|
+
# DevAudit-Installer#285.
|
|
24
29
|
# --change-type <type> Conventional-commit prefix (feat / fix /
|
|
25
30
|
# refactor / perf / chore / docs / ci /
|
|
26
31
|
# build / test / compliance / revert) for
|
|
@@ -80,6 +85,7 @@ CREATE_RELEASE_IF_MISSING=false
|
|
|
80
85
|
ENVIRONMENT=""
|
|
81
86
|
EVIDENCE_CATEGORY=""
|
|
82
87
|
RELEASE_TITLE=""
|
|
88
|
+
RELEASE_SUMMARY=""
|
|
83
89
|
CHANGE_TYPE=""
|
|
84
90
|
GATE_STATUS=""
|
|
85
91
|
SDLC_STAGE=""
|
|
@@ -103,6 +109,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
103
109
|
# the portal's findOrCreateRelease no-clobber backfill. Both optional;
|
|
104
110
|
# unknown change-type values are dropped server-side, not 400'd.
|
|
105
111
|
--release-title) RELEASE_TITLE="$2"; shift 2 ;;
|
|
112
|
+
--release-summary) RELEASE_SUMMARY="$2"; shift 2 ;;
|
|
106
113
|
--change-type) CHANGE_TYPE="$2"; shift 2 ;;
|
|
107
114
|
# passed/failed/skipped — surfaces failed gates on the portal so
|
|
108
115
|
# ran-and-failed != never-ran. Unknown values dropped server-side.
|
|
@@ -320,6 +327,8 @@ upload_presigned() {
|
|
|
320
327
|
\"releaseBranch\": \"${BRANCH}\",
|
|
321
328
|
\"environment\": \"${ENVIRONMENT}\",
|
|
322
329
|
\"evidenceCategory\": \"${EVIDENCE_CATEGORY}\",
|
|
330
|
+
\"releaseTitle\": \"${RELEASE_TITLE}\",
|
|
331
|
+
\"releaseSummary\": \"${RELEASE_SUMMARY}\",
|
|
323
332
|
\"sdlcStage\": \"${SDLC_STAGE}\",
|
|
324
333
|
\"testCycleId\": \"${TEST_CYCLE}\"
|
|
325
334
|
}") || curl_exit=$?
|
|
@@ -469,6 +478,7 @@ for FILE in "${FILES[@]}"; do
|
|
|
469
478
|
[ -n "$ENVIRONMENT" ] && CURL_ARGS+=(-F "environment=${ENVIRONMENT}")
|
|
470
479
|
[ -n "$EVIDENCE_CATEGORY" ] && CURL_ARGS+=(-F "evidenceCategory=${EVIDENCE_CATEGORY}")
|
|
471
480
|
[ -n "$RELEASE_TITLE" ] && CURL_ARGS+=(-F "releaseTitle=${RELEASE_TITLE}")
|
|
481
|
+
[ -n "$RELEASE_SUMMARY" ] && CURL_ARGS+=(-F "releaseSummary=${RELEASE_SUMMARY}")
|
|
472
482
|
[ -n "$CHANGE_TYPE" ] && CURL_ARGS+=(-F "changeType=${CHANGE_TYPE}")
|
|
473
483
|
[ -n "$GATE_STATUS" ] && CURL_ARGS+=(-F "gateStatus=${GATE_STATUS}")
|
|
474
484
|
[ -n "$SDLC_STAGE" ] && CURL_ARGS+=(-F "sdlcStage=${SDLC_STAGE}")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# close-out-contract.sh — Shared constants for release close-out automation.
|
|
3
|
+
#
|
|
4
|
+
# Sourced by close-out-release.yml.template and any CI logic that needs to
|
|
5
|
+
# recognise or suppress reconciliation pushes (devaudit#281, devaudit#284).
|
|
6
|
+
#
|
|
7
|
+
# Usage: source scripts/close-out-contract.sh
|
|
8
|
+
# CLOSEOUT_BRANCH_PREFIX — reserved automation branch prefix
|
|
9
|
+
# CLOSEOUT_MARKER — structured marker for PR/commit bodies
|
|
10
|
+
# CLOSEOUT_MARKER_REGEX — grep pattern to detect the marker
|
|
11
|
+
|
|
12
|
+
# Reserved branch prefix for automation. Humans should not use this for
|
|
13
|
+
# unrelated housekeeping work.
|
|
14
|
+
CLOSEOUT_BRANCH_PREFIX="chore/close-out-"
|
|
15
|
+
|
|
16
|
+
# Structured close-out marker emitted in the PR body / merge commit body.
|
|
17
|
+
# Format: Release-Closeout: REQ-XXX
|
|
18
|
+
CLOSEOUT_MARKER_PREFIX="Release-Closeout:"
|
|
19
|
+
CLOSEOUT_MARKER_REGEX='^Release-Closeout:[[:space:]]*REQ-[0-9]{3,}'
|
|
20
|
+
|
|
21
|
+
# Emit the marker for a given REQ-ID.
|
|
22
|
+
closeout_marker() {
|
|
23
|
+
printf '%s %s\n' "$CLOSEOUT_MARKER_PREFIX" "$1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Check if a string (e.g. commit body, PR body) contains the close-out marker.
|
|
27
|
+
is_closeout_marker() {
|
|
28
|
+
grep -qE "$CLOSEOUT_MARKER_REGEX" <<< "$1"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Extract the REQ-ID from a close-out marker string.
|
|
32
|
+
closeout_marker_req() {
|
|
33
|
+
grep -oE "$CLOSEOUT_MARKER_REGEX" <<< "$1" | grep -oE 'REQ-[0-9]{3,}' | head -1
|
|
34
|
+
}
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
# 4-bis. RTM.md IN PROGRESS row: exactly one tracked REQ marked
|
|
17
17
|
# IN PROGRESS in compliance/RTM.md
|
|
18
18
|
# -> REQ-XXX
|
|
19
|
+
# 4-ter. Close-out marker: commit body contains
|
|
20
|
+
# "Release-Closeout: REQ-XXX"
|
|
21
|
+
# (devaudit#284 — suppresses false
|
|
22
|
+
# housekeeping stubs from release
|
|
23
|
+
# reconciliation merges)
|
|
24
|
+
# -> empty/skip
|
|
19
25
|
# 5. Fallback: bare date -> v2026.05.17
|
|
20
26
|
#
|
|
21
27
|
# Step 4 (DevAudit-Installer#92) handles `chore:` / `docs:` / `ci:`
|
|
@@ -127,5 +133,17 @@ if [ -f "$RTM_PATH" ]; then
|
|
|
127
133
|
fi
|
|
128
134
|
fi
|
|
129
135
|
|
|
136
|
+
# 4-ter. Close-out marker suppression (devaudit#284).
|
|
137
|
+
# A push that is solely the result of completing a tracked release's
|
|
138
|
+
# reconciliation/close-out path must not derive a bare-date housekeeping
|
|
139
|
+
# release. The close-out workflow (devaudit#281) emits a structured
|
|
140
|
+
# `Release-Closeout: REQ-XXX` marker in the merge commit body. When
|
|
141
|
+
# present, emit no version. Workflow callers translate the empty result
|
|
142
|
+
# to an explicit `skip` sentinel so reconciliation pushes neither create
|
|
143
|
+
# housekeeping releases nor attach new evidence to an already released REQ.
|
|
144
|
+
if echo "$BODY" | grep -qE '^Release-Closeout:[[:space:]]*REQ-[0-9]{3,}'; then
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
130
148
|
# 5. Fallback: bare date in UTC
|
|
131
149
|
echo "v$(date -u +%Y.%m.%d)"
|
|
@@ -266,6 +266,15 @@ cat > compliance/RTM.md <<'RTM'
|
|
|
266
266
|
RTM
|
|
267
267
|
assert_eq "RTM two IN PROGRESS w/ escaped pipes -> bare date $TODAY" "$TODAY" "$(run_helper)"
|
|
268
268
|
|
|
269
|
+
# Case 21: close-out reconciliation marker suppresses release registration.
|
|
270
|
+
# The workflow caller translates this empty stdout to version=skip so the
|
|
271
|
+
# reconciliation push neither creates housekeeping stubs nor attaches evidence
|
|
272
|
+
# to an already released REQ.
|
|
273
|
+
make_fixture "$WORK/c21" "chore(release): reconcile REQ-089 close-out
|
|
274
|
+
|
|
275
|
+
Release-Closeout: REQ-089"
|
|
276
|
+
assert_eq "close-out marker -> empty version" "" "$(run_helper)"
|
|
277
|
+
|
|
269
278
|
echo ""
|
|
270
279
|
echo "=== Summary: $PASS pass / $FAIL fail ==="
|
|
271
280
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# extract-release-metadata.sh — Extract canonical tracked-release metadata.
|
|
3
|
+
#
|
|
4
|
+
# DevAudit-Installer#285 — Provides one shared extraction source of truth for
|
|
5
|
+
# tracked-release title and summary, usable from both CI workflow paths
|
|
6
|
+
# (ci.yml.template and compliance-evidence.yml.template).
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# source scripts/extract-release-metadata.sh
|
|
10
|
+
# extract_release_metadata "REQ-089"
|
|
11
|
+
#
|
|
12
|
+
# Sets the following shell variables:
|
|
13
|
+
# RELEASE_TITLE — canonical human title (empty if none found)
|
|
14
|
+
# RELEASE_SUMMARY — reviewer-facing summary from ## Summary section (empty if none)
|
|
15
|
+
#
|
|
16
|
+
# Title fallback chain (normative per #285):
|
|
17
|
+
# 1. **Requirement:** line in RELEASE-TICKET-REQ-XXX.md → human part after "REQ-XXX —"
|
|
18
|
+
# 2. GitHub issue title from RTM row (best-effort, requires gh CLI)
|
|
19
|
+
# 3. Normalised ticket H1 only if no better source exists
|
|
20
|
+
# 4. Empty rather than garbage
|
|
21
|
+
#
|
|
22
|
+
# Summary rules:
|
|
23
|
+
# - Capture content between ## Summary and the next ## heading
|
|
24
|
+
# - Trim boilerplate / empty placeholders
|
|
25
|
+
# - Empty if section absent or still a stub
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
extract_release_metadata() {
|
|
30
|
+
local req_id="$1"
|
|
31
|
+
RELEASE_TITLE=""
|
|
32
|
+
RELEASE_SUMMARY=""
|
|
33
|
+
|
|
34
|
+
local ticket_file=""
|
|
35
|
+
for FILE in \
|
|
36
|
+
"compliance/pending-releases/RELEASE-TICKET-${req_id}.md" \
|
|
37
|
+
"compliance/approved-releases/RELEASE-TICKET-${req_id}.md"; do
|
|
38
|
+
if [ -f "$FILE" ]; then
|
|
39
|
+
ticket_file="$FILE"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
# --- Title extraction ---
|
|
45
|
+
# 1. Try **Requirement:** REQ-XXX — <human title>
|
|
46
|
+
if [ -n "$ticket_file" ]; then
|
|
47
|
+
local req_line
|
|
48
|
+
req_line=$(grep -m1 '^\*\*Requirement:\*\*' "$ticket_file" 2>/dev/null || true)
|
|
49
|
+
if [ -n "$req_line" ]; then
|
|
50
|
+
# Extract the human part after "REQ-XXX —"
|
|
51
|
+
# Format: **Requirement:** REQ-089 — Some human title here
|
|
52
|
+
RELEASE_TITLE=$(printf '%s' "$req_line" \
|
|
53
|
+
| sed -E 's/^\*\*Requirement:\*\*[[:space:]]*//' \
|
|
54
|
+
| sed -E 's/^REQ-[0-9]+[[:space:]]*[—–:-][[:space:]]*//' \
|
|
55
|
+
| sed -E 's/[[:space:]]*$//')
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# 2. Fallback: GitHub issue title from RTM row
|
|
60
|
+
if [ -z "$RELEASE_TITLE" ]; then
|
|
61
|
+
local rtm_row
|
|
62
|
+
rtm_row=$(grep -m1 "| ${req_id} " compliance/RTM.md 2>/dev/null || true)
|
|
63
|
+
if [ -n "$rtm_row" ] && command -v gh >/dev/null 2>&1; then
|
|
64
|
+
# Try to extract issue number from RTM row if present
|
|
65
|
+
local issue_num
|
|
66
|
+
issue_num=$(printf '%s' "$rtm_row" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || true)
|
|
67
|
+
if [ -n "$issue_num" ]; then
|
|
68
|
+
RELEASE_TITLE=$(gh issue view "$issue_num" --json title --jq '.title' 2>/dev/null || true)
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 3. Fallback: normalised ticket H1 (least preferred)
|
|
74
|
+
if [ -z "$RELEASE_TITLE" ] && [ -n "$ticket_file" ]; then
|
|
75
|
+
local h1_line
|
|
76
|
+
h1_line=$(grep -m1 '^# ' "$ticket_file" 2>/dev/null || true)
|
|
77
|
+
if [ -n "$h1_line" ]; then
|
|
78
|
+
# Strip the leading "# " and any "Release Ticket — REQ-XXX" prefix
|
|
79
|
+
RELEASE_TITLE=$(printf '%s' "$h1_line" \
|
|
80
|
+
| sed -E 's/^# *//' \
|
|
81
|
+
| sed -E 's/^Release Ticket[[:space:]]*[—:-][[:space:]]*//' \
|
|
82
|
+
| sed -E 's/^REQ-[0-9]+[[:space:]]*[—:-][[:space:]]*//' \
|
|
83
|
+
| sed -E 's/[[:space:]]*$//')
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 4. If still empty, leave empty rather than inventing garbage
|
|
88
|
+
|
|
89
|
+
# --- Summary extraction ---
|
|
90
|
+
# Capture content between ## Summary and the next ## heading
|
|
91
|
+
local summary_raw
|
|
92
|
+
summary_raw=""
|
|
93
|
+
if [ -n "$ticket_file" ]; then
|
|
94
|
+
summary_raw=$(awk '
|
|
95
|
+
/^## Summary/ { found=1; next }
|
|
96
|
+
/^## / { if (found) exit }
|
|
97
|
+
found { print }
|
|
98
|
+
' "$ticket_file" 2>/dev/null || true)
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ -n "$summary_raw" ]; then
|
|
102
|
+
# Trim leading/trailing blank lines and trailing whitespace per line
|
|
103
|
+
RELEASE_SUMMARY=$(printf '%s' "$summary_raw" \
|
|
104
|
+
| sed -E 's/[[:space:]]+$//' \
|
|
105
|
+
| awk 'NF { p=1 } p { print }' \
|
|
106
|
+
| awk '{ lines[NR]=$0 } END { for (i=NR; i>=1; i--) { if (lines[i] ~ /[^[:space:]]/) { last=i; break } } for (i=1; i<=last; i++) print lines[i] }')
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# If summary is just placeholder/stub text, clear it
|
|
110
|
+
if [ -n "$RELEASE_SUMMARY" ]; then
|
|
111
|
+
local summary_lower
|
|
112
|
+
summary_lower=$(printf '%s' "$RELEASE_SUMMARY" | tr '[:upper:]' '[:lower:]')
|
|
113
|
+
case "$summary_lower" in
|
|
114
|
+
*"placeholder"*|*"todo"*|*"tbd"*|*"to be completed"*|*"stub"*)
|
|
115
|
+
RELEASE_SUMMARY=""
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
fi
|
|
119
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# extract-release-metadata.test.sh — Tests for extract-release-metadata.sh.
|
|
3
|
+
#
|
|
4
|
+
# Creates throwaway release-ticket fixtures, sources the helper, and asserts
|
|
5
|
+
# on the extracted title and summary. Hermetic: runs inside mktemp'd
|
|
6
|
+
# directories that are torn down at the end.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./scripts/extract-release-metadata.test.sh
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
HELPER="$SCRIPT_DIR/extract-release-metadata.sh"
|
|
15
|
+
[ -x "$HELPER" ] || chmod +x "$HELPER"
|
|
16
|
+
|
|
17
|
+
PASS=0
|
|
18
|
+
FAIL=0
|
|
19
|
+
|
|
20
|
+
WORK=$(mktemp -d)
|
|
21
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
22
|
+
|
|
23
|
+
# $1 = test name, $2 = expected, $3 = actual
|
|
24
|
+
assert_eq() {
|
|
25
|
+
local name="$1" expected="$2" actual="$3"
|
|
26
|
+
if [ "$expected" = "$actual" ]; then
|
|
27
|
+
echo " ✓ $name"
|
|
28
|
+
PASS=$((PASS + 1))
|
|
29
|
+
else
|
|
30
|
+
echo " ✗ $name"
|
|
31
|
+
echo " Expected: $expected"
|
|
32
|
+
echo " Actual: $actual"
|
|
33
|
+
FAIL=$((FAIL + 1))
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
assert_empty() {
|
|
38
|
+
local name="$1" actual="$2"
|
|
39
|
+
if [ -z "$actual" ]; then
|
|
40
|
+
echo " ✓ $name"
|
|
41
|
+
PASS=$((PASS + 1))
|
|
42
|
+
else
|
|
43
|
+
echo " ✗ $name (expected empty, got: $actual)"
|
|
44
|
+
FAIL=$((FAIL + 1))
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# $1 = test dir
|
|
49
|
+
make_fixture() {
|
|
50
|
+
local dir="$1"
|
|
51
|
+
rm -rf "$dir"
|
|
52
|
+
mkdir -p "$dir/compliance/pending-releases"
|
|
53
|
+
mkdir -p "$dir/compliance/approved-releases"
|
|
54
|
+
cd "$dir"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
echo "=== extract-release-metadata.sh tests ==="
|
|
58
|
+
echo ""
|
|
59
|
+
|
|
60
|
+
# --- Test 1: Extract title from **Requirement:** line ---
|
|
61
|
+
echo "--- Test 1: Title from **Requirement:** line ---"
|
|
62
|
+
make_fixture "$WORK/test1"
|
|
63
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-089.md" <<'TICKET'
|
|
64
|
+
# Release Ticket — REQ-089
|
|
65
|
+
|
|
66
|
+
**Requirement:** REQ-089 — Admin order management: portion size selection, manual price override, per-item special instructions, stock validation
|
|
67
|
+
|
|
68
|
+
## Summary
|
|
69
|
+
This release implements admin order management features including portion size
|
|
70
|
+
selection, manual price override, and per-item special instructions.
|
|
71
|
+
|
|
72
|
+
## Changes
|
|
73
|
+
- Added portion size dropdown
|
|
74
|
+
- Added manual price override
|
|
75
|
+
TICKET
|
|
76
|
+
|
|
77
|
+
source "$HELPER"
|
|
78
|
+
extract_release_metadata "REQ-089"
|
|
79
|
+
assert_eq "Title from Requirement line" \
|
|
80
|
+
"Admin order management: portion size selection, manual price override, per-item special instructions, stock validation" \
|
|
81
|
+
"$RELEASE_TITLE"
|
|
82
|
+
assert_eq "Summary extracted" \
|
|
83
|
+
"This release implements admin order management features including portion size
|
|
84
|
+
selection, manual price override, and per-item special instructions." \
|
|
85
|
+
"$RELEASE_SUMMARY"
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
# --- Test 2: Fallback to H1 when no **Requirement:** line ---
|
|
89
|
+
echo "--- Test 2: Fallback to normalised H1 ---"
|
|
90
|
+
make_fixture "$WORK/test2"
|
|
91
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-001.md" <<'TICKET'
|
|
92
|
+
# Release Ticket — REQ-001
|
|
93
|
+
|
|
94
|
+
## Summary
|
|
95
|
+
Some summary here.
|
|
96
|
+
TICKET
|
|
97
|
+
|
|
98
|
+
source "$HELPER"
|
|
99
|
+
extract_release_metadata "REQ-001"
|
|
100
|
+
assert_eq "H1 fallback strips 'Release Ticket —' prefix" \
|
|
101
|
+
"REQ-001" \
|
|
102
|
+
"$RELEASE_TITLE"
|
|
103
|
+
echo ""
|
|
104
|
+
|
|
105
|
+
# --- Test 3: Empty title when no ticket exists ---
|
|
106
|
+
echo "--- Test 3: Empty title when no ticket ---"
|
|
107
|
+
make_fixture "$WORK/test3"
|
|
108
|
+
source "$HELPER"
|
|
109
|
+
extract_release_metadata "REQ-999"
|
|
110
|
+
assert_empty "Title empty when no ticket" "$RELEASE_TITLE"
|
|
111
|
+
assert_empty "Summary empty when no ticket" "$RELEASE_SUMMARY"
|
|
112
|
+
echo ""
|
|
113
|
+
|
|
114
|
+
# --- Test 4: Summary with placeholder text is cleared ---
|
|
115
|
+
echo "--- Test 4: Placeholder summary is cleared ---"
|
|
116
|
+
make_fixture "$WORK/test4"
|
|
117
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-002.md" <<'TICKET'
|
|
118
|
+
# Release Ticket — REQ-002
|
|
119
|
+
|
|
120
|
+
**Requirement:** REQ-002 — Some feature
|
|
121
|
+
|
|
122
|
+
## Summary
|
|
123
|
+
TODO: Add summary
|
|
124
|
+
|
|
125
|
+
## Changes
|
|
126
|
+
- Stuff
|
|
127
|
+
TICKET
|
|
128
|
+
|
|
129
|
+
source "$HELPER"
|
|
130
|
+
extract_release_metadata "REQ-002"
|
|
131
|
+
assert_eq "Title still extracted" "Some feature" "$RELEASE_TITLE"
|
|
132
|
+
assert_empty "Placeholder summary cleared" "$RELEASE_SUMMARY"
|
|
133
|
+
echo ""
|
|
134
|
+
|
|
135
|
+
# --- Test 5: No Summary section → empty summary ---
|
|
136
|
+
echo "--- Test 5: No Summary section ---"
|
|
137
|
+
make_fixture "$WORK/test5"
|
|
138
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-003.md" <<'TICKET'
|
|
139
|
+
# Release Ticket — REQ-003
|
|
140
|
+
|
|
141
|
+
**Requirement:** REQ-003 — Another feature
|
|
142
|
+
|
|
143
|
+
## Changes
|
|
144
|
+
- Change A
|
|
145
|
+
TICKET
|
|
146
|
+
|
|
147
|
+
source "$HELPER"
|
|
148
|
+
extract_release_metadata "REQ-003"
|
|
149
|
+
assert_eq "Title extracted" "Another feature" "$RELEASE_TITLE"
|
|
150
|
+
assert_empty "Summary empty when no section" "$RELEASE_SUMMARY"
|
|
151
|
+
echo ""
|
|
152
|
+
|
|
153
|
+
# --- Test 6: Summary stops at next ## heading ---
|
|
154
|
+
echo "--- Test 6: Summary stops at next ## ---"
|
|
155
|
+
make_fixture "$WORK/test6"
|
|
156
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-004.md" <<'TICKET'
|
|
157
|
+
# Release Ticket — REQ-004
|
|
158
|
+
|
|
159
|
+
**Requirement:** REQ-004 — Feature four
|
|
160
|
+
|
|
161
|
+
## Summary
|
|
162
|
+
Line one of summary.
|
|
163
|
+
Line two of summary.
|
|
164
|
+
|
|
165
|
+
## Changes
|
|
166
|
+
This should NOT be in summary.
|
|
167
|
+
TICKET
|
|
168
|
+
|
|
169
|
+
source "$HELPER"
|
|
170
|
+
extract_release_metadata "REQ-004"
|
|
171
|
+
assert_eq "Title" "Feature four" "$RELEASE_TITLE"
|
|
172
|
+
assert_eq "Summary stops at next ##" \
|
|
173
|
+
"Line one of summary.
|
|
174
|
+
Line two of summary." \
|
|
175
|
+
"$RELEASE_SUMMARY"
|
|
176
|
+
echo ""
|
|
177
|
+
|
|
178
|
+
# --- Test 7: Approved-releases fallback when no pending ticket ---
|
|
179
|
+
echo "--- Test 7: Approved-releases fallback ---"
|
|
180
|
+
make_fixture "$WORK/test7"
|
|
181
|
+
cat > "compliance/approved-releases/RELEASE-TICKET-REQ-005.md" <<'TICKET'
|
|
182
|
+
# Release Ticket — REQ-005
|
|
183
|
+
|
|
184
|
+
**Requirement:** REQ-005 — Approved feature
|
|
185
|
+
|
|
186
|
+
## Summary
|
|
187
|
+
Summary from approved ticket.
|
|
188
|
+
TICKET
|
|
189
|
+
|
|
190
|
+
source "$HELPER"
|
|
191
|
+
extract_release_metadata "REQ-005"
|
|
192
|
+
assert_eq "Title from approved-releases" "Approved feature" "$RELEASE_TITLE"
|
|
193
|
+
assert_eq "Summary from approved-releases" "Summary from approved ticket." "$RELEASE_SUMMARY"
|
|
194
|
+
echo ""
|
|
195
|
+
|
|
196
|
+
# --- Test 8: Em-dash variant in Requirement line ---
|
|
197
|
+
echo "--- Test 8: Em-dash variant ---"
|
|
198
|
+
make_fixture "$WORK/test8"
|
|
199
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-006.md" <<'TICKET'
|
|
200
|
+
# Release Ticket — REQ-006
|
|
201
|
+
|
|
202
|
+
**Requirement:** REQ-006 — Feature with em-dash
|
|
203
|
+
|
|
204
|
+
## Summary
|
|
205
|
+
Summary.
|
|
206
|
+
TICKET
|
|
207
|
+
|
|
208
|
+
source "$HELPER"
|
|
209
|
+
extract_release_metadata "REQ-006"
|
|
210
|
+
assert_eq "Em-dash title" "Feature with em-dash" "$RELEASE_TITLE"
|
|
211
|
+
echo ""
|
|
212
|
+
|
|
213
|
+
# --- Test 9: H1 with human title (not just REQ-XXX) ---
|
|
214
|
+
echo "--- Test 9: H1 with human title fallback ---"
|
|
215
|
+
make_fixture "$WORK/test9"
|
|
216
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-REQ-007.md" <<'TICKET'
|
|
217
|
+
# Fix login redirect bug
|
|
218
|
+
|
|
219
|
+
## Summary
|
|
220
|
+
Fixes the redirect issue.
|
|
221
|
+
TICKET
|
|
222
|
+
|
|
223
|
+
source "$HELPER"
|
|
224
|
+
extract_release_metadata "REQ-007"
|
|
225
|
+
assert_eq "H1 human title fallback" "Fix login redirect bug" "$RELEASE_TITLE"
|
|
226
|
+
echo ""
|
|
227
|
+
|
|
228
|
+
# --- Test 10: Summary with leading/trailing blank lines trimmed ---
|
|
229
|
+
echo "--- Test 10: Summary blank line trimming ---"
|
|
230
|
+
make_fixture "$WORK/test10"
|
|
231
|
+
printf '%s\n' \
|
|
232
|
+
'# Release Ticket — REQ-008' \
|
|
233
|
+
'' \
|
|
234
|
+
'**Requirement:** REQ-008 — Feature eight' \
|
|
235
|
+
'' \
|
|
236
|
+
'## Summary' \
|
|
237
|
+
'' \
|
|
238
|
+
'' \
|
|
239
|
+
'Actual summary content.' \
|
|
240
|
+
'' \
|
|
241
|
+
'' \
|
|
242
|
+
'## Changes' \
|
|
243
|
+
'- Change' > "compliance/pending-releases/RELEASE-TICKET-REQ-008.md"
|
|
244
|
+
|
|
245
|
+
source "$HELPER"
|
|
246
|
+
extract_release_metadata "REQ-008"
|
|
247
|
+
assert_eq "Title" "Feature eight" "$RELEASE_TITLE"
|
|
248
|
+
assert_eq "Summary trimmed" "Actual summary content." "$RELEASE_SUMMARY"
|
|
249
|
+
echo ""
|
|
250
|
+
|
|
251
|
+
# --- Test 11: RTM issue title fallback without release ticket ---
|
|
252
|
+
echo "--- Test 11: RTM issue title fallback without release ticket ---"
|
|
253
|
+
make_fixture "$WORK/test11"
|
|
254
|
+
cat > compliance/RTM.md <<'RTM'
|
|
255
|
+
| REQ-ID | Issue | Risk | Evidence | Status |
|
|
256
|
+
| ------- | ----- | ---- | -------- | ------ |
|
|
257
|
+
| REQ-011 | #456 | LOW | n/a | DRAFT |
|
|
258
|
+
RTM
|
|
259
|
+
mkdir -p bin
|
|
260
|
+
cat > bin/gh <<'GH'
|
|
261
|
+
#!/usr/bin/env bash
|
|
262
|
+
if [ "$1" = "issue" ] && [ "$2" = "view" ] && [ "$3" = "456" ]; then
|
|
263
|
+
printf '%s\n' "Issue title from RTM"
|
|
264
|
+
exit 0
|
|
265
|
+
fi
|
|
266
|
+
exit 1
|
|
267
|
+
GH
|
|
268
|
+
chmod +x bin/gh
|
|
269
|
+
PATH="$PWD/bin:$PATH"
|
|
270
|
+
source "$HELPER"
|
|
271
|
+
extract_release_metadata "REQ-011"
|
|
272
|
+
assert_eq "RTM issue title fallback without ticket" "Issue title from RTM" "$RELEASE_TITLE"
|
|
273
|
+
assert_empty "Summary empty without ticket" "$RELEASE_SUMMARY"
|
|
274
|
+
echo ""
|
|
275
|
+
|
|
276
|
+
echo ""
|
|
277
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
278
|
+
exit $FAIL
|
|
@@ -133,6 +133,43 @@ for REQ in $REQUIREMENTS; do
|
|
|
133
133
|
EXIT_CODE=1
|
|
134
134
|
fi
|
|
135
135
|
|
|
136
|
+
# Check 7: If E2E gate result is SKIPPED, require an ## Accepted skips section (devaudit-installer#279)
|
|
137
|
+
if grep -qi '| *e2e.*| *skipped' "$SUMMARY" 2>/dev/null; then
|
|
138
|
+
if ! grep -qi '## Accepted skips' "$SUMMARY" 2>/dev/null; then
|
|
139
|
+
echo " ERROR: E2E gate result is SKIPPED but no '## Accepted skips' section found (devaudit-installer#279)"
|
|
140
|
+
echo " Add an '## Accepted skips' section with a table row for each accepted skip."
|
|
141
|
+
EXIT_CODE=1
|
|
142
|
+
else
|
|
143
|
+
# Extract the Accepted skips section and check for data rows
|
|
144
|
+
ACCEPTED_SKIPS_SECTION=$(awk '/^## Accepted skips/{found=1; next} /^## /{found=0} found' "$SUMMARY")
|
|
145
|
+
# Count rows that look like table data (start with |, not separator, not header)
|
|
146
|
+
# Separator rows contain only dashes and pipes: |------|------|
|
|
147
|
+
# Header rows contain column names: | Spec | Test | ...
|
|
148
|
+
# Data rows contain actual values: | e2e/foo.spec.ts | ... |
|
|
149
|
+
DATA_ROWS=$(echo "$ACCEPTED_SKIPS_SECTION" | grep '^|' | grep -v '^|[[:space:]]*-' | grep -v 'Spec.*Test.*REQ/AC' | wc -l || true)
|
|
150
|
+
if [ "$DATA_ROWS" -lt 1 ] 2>/dev/null; then
|
|
151
|
+
echo " ERROR: '## Accepted skips' section exists but has no table data rows (devaudit-installer#279)"
|
|
152
|
+
echo " Add at least one row with: Spec, Test, REQ/AC, Classification, Resolution attempted, Approved by, Rationale"
|
|
153
|
+
EXIT_CODE=1
|
|
154
|
+
else
|
|
155
|
+
# Check that each data row has a non-empty "Approved by" column (6th content column = field 7 with awk -F|)
|
|
156
|
+
SKIP_FAIL=0
|
|
157
|
+
echo "$ACCEPTED_SKIPS_SECTION" | grep '^|' | grep -v '^|[[:space:]]*-' | grep -v 'Spec.*Test.*REQ/AC' | while IFS= read -r row; do
|
|
158
|
+
APPROVED_BY=$(echo "$row" | awk -F'|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$7); print $7}')
|
|
159
|
+
if [ -z "$APPROVED_BY" ]; then
|
|
160
|
+
echo " ERROR: Accepted skip row has empty 'Approved by' value (devaudit-installer#279)"
|
|
161
|
+
echo " Row: $row"
|
|
162
|
+
fi
|
|
163
|
+
done | grep -q ERROR && SKIP_FAIL=1
|
|
164
|
+
if [ "$SKIP_FAIL" -eq 1 ]; then
|
|
165
|
+
EXIT_CODE=1
|
|
166
|
+
else
|
|
167
|
+
echo " OK: E2E gate result is SKIPPED with '## Accepted skips' section"
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
|
|
136
173
|
echo ""
|
|
137
174
|
done
|
|
138
175
|
|