@metasession.co/devaudit-cli 0.1.0 → 0.1.2
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/README.md +23 -11
- package/dist/index.js +21 -5
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/scripts/upload-evidence.sh +225 -0
- package/sdlc/.claude/settings.local.json +11 -0
- package/sdlc/CLAUDE.md +73 -0
- package/sdlc/HOST_ADAPTER.md +127 -0
- package/sdlc/SKILLS.md +137 -0
- package/sdlc/STACK_ADAPTER.md +130 -0
- package/sdlc/ai-rules/INSTRUCTIONS-SDLC.md +172 -0
- package/sdlc/ai-rules/README.md +103 -0
- package/sdlc/ai-rules/SDLC_RULES.md +584 -0
- package/sdlc/ai-rules/claude/CLAUDE.md +192 -0
- package/sdlc/ai-rules/cursor/.cursorrules +167 -0
- package/sdlc/ai-rules/windsurf/.windsurfrules +167 -0
- package/sdlc/article.md +219 -0
- package/sdlc/files/_common/0-project-setup.md +410 -0
- package/sdlc/files/_common/1-plan-requirement.md +381 -0
- package/sdlc/files/_common/2-implement-and-test.md +276 -0
- package/sdlc/files/_common/3-compile-evidence.md +603 -0
- package/sdlc/files/_common/4-submit-for-review.md +362 -0
- package/sdlc/files/_common/5-deploy-main.md +251 -0
- package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +169 -0
- package/sdlc/files/_common/README_TEMPLATE.md +441 -0
- package/sdlc/files/_common/Test_Architecture.md +461 -0
- package/sdlc/files/_common/Test_Plan_TEMPLATE.md +311 -0
- package/sdlc/files/_common/Test_Policy.md +277 -0
- package/sdlc/files/_common/Test_Strategy.md +359 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/bug.yml +75 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/config.yml +11 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/requirement.yml +75 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/task.yml +48 -0
- package/sdlc/files/_common/github/pull_request_template.md +69 -0
- package/sdlc/files/_common/implementing-an-sdlc-issue.md +413 -0
- package/sdlc/files/_common/scripts/derive-release-version.sh +40 -0
- package/sdlc/files/_common/scripts/derive-release-version.test.sh +98 -0
- package/sdlc/files/_common/scripts/submit-for-uat-review.sh +162 -0
- package/sdlc/files/_common/scripts/validate-commits.sh +83 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +202 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +202 -0
- package/sdlc/files/_common/skills/_schema/skill.schema.json +36 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +254 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +244 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/references/evidence.ts +40 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +189 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/call-graph.md +64 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +192 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +81 -0
- package/sdlc/files/ci/check-release-approval.yml.template +201 -0
- package/sdlc/files/ci/ci-status-fallback.yml.template +41 -0
- package/sdlc/files/ci/ci.yml.template +390 -0
- package/sdlc/files/ci/compliance-evidence.yml.template +161 -0
- package/sdlc/files/ci/compliance-validation.yml.template +34 -0
- package/sdlc/files/ci/post-deploy-prod.yml.template +159 -0
- package/sdlc/files/ci/python/ci.yml.template +335 -0
- package/sdlc/files/hosts/_schema/adapter.schema.json +103 -0
- package/sdlc/files/hosts/railway/adapter.json +32 -0
- package/sdlc/files/sdlc-config.example.json +74 -0
- package/sdlc/files/stacks/_schema/adapter.schema.json +151 -0
- package/sdlc/files/stacks/node/adapter.json +54 -0
- package/sdlc/files/stacks/node/hooks/.prettierrc.json +9 -0
- package/sdlc/files/stacks/node/hooks/commit-msg +7 -0
- package/sdlc/files/stacks/node/hooks/commitlint.config.mjs +64 -0
- package/sdlc/files/stacks/node/hooks/lint-staged.config.mjs +16 -0
- package/sdlc/files/stacks/node/hooks/pre-commit +13 -0
- package/sdlc/files/stacks/node/hooks/pre-push +15 -0
- package/sdlc/files/stacks/node/scripts/check-requirement-jsdoc.sh +54 -0
- package/sdlc/files/stacks/python/adapter.json +36 -0
- package/sdlc/files/stacks/python/hooks/.pre-commit-config.yaml +51 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# submit-for-uat-review.sh — Run readiness checks, then submit a release for UAT review.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/submit-for-uat-review.sh <project-slug> <version-prefix>
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# ./scripts/submit-for-uat-review.sh wawagardenbar-app v2026.05.14
|
|
9
|
+
#
|
|
10
|
+
# Required environment:
|
|
11
|
+
# DEVAUDIT_USER_TOKEN — Personal Access Token (mctok_…) issued from
|
|
12
|
+
# /settings/tokens. Attributes the submission
|
|
13
|
+
# to the issuing user so the four-eyes approval
|
|
14
|
+
# control is preserved (the submitter cannot
|
|
15
|
+
# approve their own release).
|
|
16
|
+
# DEVAUDIT_API_KEY — project-scoped API key (existing) used to
|
|
17
|
+
# resolve the release id and read its current
|
|
18
|
+
# status. Distinct from the PAT.
|
|
19
|
+
# DEVAUDIT_BASE_URL — DevAudit base URL (e.g. https://devaudit.metasession.co).
|
|
20
|
+
#
|
|
21
|
+
# What it does:
|
|
22
|
+
# 1. Verify working tree is clean and develop is up-to-date with origin.
|
|
23
|
+
# 2. Verify the release ticket exists in compliance/pending-releases/.
|
|
24
|
+
# 3. Verify CI gates green on the current develop HEAD via gh CLI.
|
|
25
|
+
# 4. Resolve the release id from (projectSlug, versionPrefix) via /api/ci/releases/resolve.
|
|
26
|
+
# 5. If status is draft → POST /api/releases/<id>/submit-review with the PAT.
|
|
27
|
+
# If status is uat_review → idempotent no-op (exit 0, "already submitted").
|
|
28
|
+
# If status is uat_approved → idempotent no-op (exit 0, "already approved").
|
|
29
|
+
# Any other status → refuse with an explanatory message.
|
|
30
|
+
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
PROJECT_SLUG="${1:-}"
|
|
34
|
+
VERSION_PREFIX="${2:-}"
|
|
35
|
+
|
|
36
|
+
if [ -z "$PROJECT_SLUG" ] || [ -z "$VERSION_PREFIX" ]; then
|
|
37
|
+
echo "Usage: $0 <project-slug> <version-prefix>" >&2
|
|
38
|
+
echo "Example: $0 wawagardenbar-app v2026.05.14" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
: "${DEVAUDIT_USER_TOKEN:?DEVAUDIT_USER_TOKEN must be set (issue from /settings/tokens)}"
|
|
43
|
+
: "${DEVAUDIT_API_KEY:?DEVAUDIT_API_KEY must be set (project API key)}"
|
|
44
|
+
: "${DEVAUDIT_BASE_URL:?DEVAUDIT_BASE_URL must be set (e.g. https://devaudit.metasession.co)}"
|
|
45
|
+
|
|
46
|
+
BASE_URL="${DEVAUDIT_BASE_URL%/}"
|
|
47
|
+
|
|
48
|
+
FAILED=0
|
|
49
|
+
note() { printf ' - %s\n' "$*"; }
|
|
50
|
+
fail() { printf ' ✗ %s\n' "$*"; FAILED=$((FAILED + 1)); }
|
|
51
|
+
ok() { printf ' ✓ %s\n' "$*"; }
|
|
52
|
+
|
|
53
|
+
echo "Readiness checks for ${PROJECT_SLUG} ${VERSION_PREFIX}"
|
|
54
|
+
|
|
55
|
+
# 1. Working tree clean
|
|
56
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
57
|
+
fail "Working tree has uncommitted changes — commit or stash before submitting."
|
|
58
|
+
else
|
|
59
|
+
ok "Working tree clean."
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# 2. develop up-to-date with origin
|
|
63
|
+
git fetch origin develop --quiet
|
|
64
|
+
LOCAL_SHA="$(git rev-parse develop 2>/dev/null || echo '')"
|
|
65
|
+
REMOTE_SHA="$(git rev-parse origin/develop 2>/dev/null || echo '')"
|
|
66
|
+
if [ -z "$LOCAL_SHA" ] || [ -z "$REMOTE_SHA" ]; then
|
|
67
|
+
fail "Cannot resolve develop SHA — is the develop branch present?"
|
|
68
|
+
elif [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then
|
|
69
|
+
fail "Local develop (${LOCAL_SHA:0:7}) does not match origin/develop (${REMOTE_SHA:0:7}) — push first."
|
|
70
|
+
else
|
|
71
|
+
ok "develop is up-to-date with origin (${LOCAL_SHA:0:7})."
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# 3. Release ticket exists somewhere under compliance/pending-releases/
|
|
75
|
+
TICKETS=$(find compliance/pending-releases -maxdepth 1 -name 'RELEASE-TICKET-*.md' 2>/dev/null | head -5 || true)
|
|
76
|
+
if [ -z "$TICKETS" ]; then
|
|
77
|
+
fail "No RELEASE-TICKET-*.md found in compliance/pending-releases/."
|
|
78
|
+
else
|
|
79
|
+
ok "Release ticket present:$(echo "$TICKETS" | sed 's|.*/| |g' | tr '\n' ' ')"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# 4. CI gates green on current develop HEAD (if gh CLI available)
|
|
83
|
+
if command -v gh >/dev/null 2>&1; then
|
|
84
|
+
CI_CONCLUSIONS=$(gh run list --branch develop --commit "$REMOTE_SHA" --limit 10 --json conclusion,status,name 2>/dev/null || echo '[]')
|
|
85
|
+
if [ "$CI_CONCLUSIONS" = '[]' ]; then
|
|
86
|
+
fail "No CI runs found for ${REMOTE_SHA:0:7} — has the push reached GitHub Actions yet?"
|
|
87
|
+
else
|
|
88
|
+
INCOMPLETE=$(echo "$CI_CONCLUSIONS" | jq -r '[.[] | select(.status != "completed")] | length')
|
|
89
|
+
FAILED_RUNS=$(echo "$CI_CONCLUSIONS" | jq -r '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled")] | length')
|
|
90
|
+
if [ "$INCOMPLETE" -gt 0 ]; then
|
|
91
|
+
fail "CI still running on ${REMOTE_SHA:0:7} (${INCOMPLETE} in-flight)."
|
|
92
|
+
elif [ "$FAILED_RUNS" -gt 0 ]; then
|
|
93
|
+
fail "CI failed on ${REMOTE_SHA:0:7} (${FAILED_RUNS} runs failed)."
|
|
94
|
+
else
|
|
95
|
+
ok "CI gates green on ${REMOTE_SHA:0:7}."
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
note "gh CLI not available — skipping CI-green check. Verify in GitHub UI."
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# 5. Resolve release id from (projectSlug, versionPrefix)
|
|
103
|
+
RESOLVE_URL="${BASE_URL}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${VERSION_PREFIX}"
|
|
104
|
+
RESOLVE=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" "$RESOLVE_URL")
|
|
105
|
+
RELEASE_ID=$(echo "$RESOLVE" | jq -r '.latest.id // empty')
|
|
106
|
+
RELEASE_STATUS=$(echo "$RESOLVE" | jq -r '.latest.status // empty')
|
|
107
|
+
RELEASE_VERSION=$(echo "$RESOLVE" | jq -r '.latest.version // empty')
|
|
108
|
+
|
|
109
|
+
if [ -z "$RELEASE_ID" ]; then
|
|
110
|
+
fail "No release found in DevAudit for ${PROJECT_SLUG} ${VERSION_PREFIX} (has CI uploaded evidence yet?)."
|
|
111
|
+
else
|
|
112
|
+
ok "Release found: ${RELEASE_VERSION} (id ${RELEASE_ID:0:8}…, status ${RELEASE_STATUS})."
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if [ "$FAILED" -gt 0 ]; then
|
|
116
|
+
echo ""
|
|
117
|
+
echo "Refusing to submit — ${FAILED} readiness check(s) failed."
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# 6. Idempotent dispatch on release status
|
|
122
|
+
case "$RELEASE_STATUS" in
|
|
123
|
+
draft)
|
|
124
|
+
echo ""
|
|
125
|
+
echo "Submitting ${RELEASE_VERSION} for UAT review…"
|
|
126
|
+
SUBMIT_URL="${BASE_URL}/api/releases/${RELEASE_ID}/submit-review"
|
|
127
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
|
128
|
+
-H "X-DevAudit-Token: ${DEVAUDIT_USER_TOKEN}" \
|
|
129
|
+
-H "Content-Type: application/json" \
|
|
130
|
+
"$SUBMIT_URL")
|
|
131
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
132
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
133
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
134
|
+
NEW_STATUS=$(echo "$BODY" | jq -r '.status')
|
|
135
|
+
echo " Status: draft → ${NEW_STATUS}"
|
|
136
|
+
echo " Review page: ${BASE_URL}/projects/${PROJECT_SLUG}/releases/${RELEASE_ID}?env=uat"
|
|
137
|
+
else
|
|
138
|
+
echo " Submission failed (HTTP ${HTTP_CODE}): $(echo "$BODY" | jq -r '.error // .')" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
;;
|
|
142
|
+
uat_review)
|
|
143
|
+
echo ""
|
|
144
|
+
echo "Release already submitted for UAT review (status: uat_review). No action taken."
|
|
145
|
+
echo "Review page: ${BASE_URL}/projects/${PROJECT_SLUG}/releases/${RELEASE_ID}?env=uat"
|
|
146
|
+
;;
|
|
147
|
+
uat_approved | prod_review | prod_approved | released)
|
|
148
|
+
echo ""
|
|
149
|
+
echo "Release has progressed past UAT review (status: ${RELEASE_STATUS}). No action taken."
|
|
150
|
+
echo "Release page: ${BASE_URL}/projects/${PROJECT_SLUG}/releases/${RELEASE_ID}"
|
|
151
|
+
;;
|
|
152
|
+
uat_rejected | prod_rejected)
|
|
153
|
+
echo ""
|
|
154
|
+
echo "Release is in a rejected state (status: ${RELEASE_STATUS}). Address review comments and push a fix before resubmitting." >&2
|
|
155
|
+
exit 2
|
|
156
|
+
;;
|
|
157
|
+
*)
|
|
158
|
+
echo ""
|
|
159
|
+
echo "Release in unexpected status: ${RELEASE_STATUS}. Refusing to submit." >&2
|
|
160
|
+
exit 2
|
|
161
|
+
;;
|
|
162
|
+
esac
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# validate-commits.sh — Validates commit conventions on PRs.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/validate-commits.sh [base-branch]
|
|
6
|
+
#
|
|
7
|
+
# Checks all commits in the PR for:
|
|
8
|
+
# - Conventional Commits format (type: description or type(scope): description)
|
|
9
|
+
# - Co-Authored-By tag on commits touching code files (warning)
|
|
10
|
+
# - Ref: REQ-XXX on commits related to tracked requirements (warning)
|
|
11
|
+
#
|
|
12
|
+
# Install: cp this file to your project's scripts/ directory && chmod +x scripts/validate-commits.sh
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
BASE_BRANCH="${1:-origin/main}"
|
|
17
|
+
EXIT_CODE=0
|
|
18
|
+
WARN_COUNT=0
|
|
19
|
+
|
|
20
|
+
echo "=== Commit Convention Validation ==="
|
|
21
|
+
echo "Comparing: $BASE_BRANCH...HEAD"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# Conventional Commit regex: type(optional-scope): description
|
|
25
|
+
CC_REGEX='^(feat|fix|docs|test|refactor|chore|compliance|security|perf|ci|build|revert)(\([a-zA-Z0-9_-]+\))?!?: .+'
|
|
26
|
+
|
|
27
|
+
COMMITS=$(git log "$BASE_BRANCH"..HEAD --format='%H' || true)
|
|
28
|
+
|
|
29
|
+
if [ -z "$COMMITS" ]; then
|
|
30
|
+
echo "No commits found between $BASE_BRANCH and HEAD."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
TOTAL=0
|
|
35
|
+
FAILED=0
|
|
36
|
+
|
|
37
|
+
while IFS= read -r sha; do
|
|
38
|
+
TOTAL=$((TOTAL + 1))
|
|
39
|
+
SUBJECT=$(git log -1 --format='%s' "$sha")
|
|
40
|
+
BODY=$(git log -1 --format='%B' "$sha")
|
|
41
|
+
SHORT=$(git log -1 --format='%h' "$sha")
|
|
42
|
+
|
|
43
|
+
# Check Conventional Commits format
|
|
44
|
+
if ! echo "$SUBJECT" | grep -qE "$CC_REGEX"; then
|
|
45
|
+
# Allow merge commits
|
|
46
|
+
if echo "$SUBJECT" | grep -q '^Merge '; then
|
|
47
|
+
continue
|
|
48
|
+
fi
|
|
49
|
+
echo "ERROR [$SHORT]: Not Conventional Commits format: \"$SUBJECT\""
|
|
50
|
+
FAILED=$((FAILED + 1))
|
|
51
|
+
EXIT_CODE=1
|
|
52
|
+
continue
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Check Co-Authored-By on commits that touch code files
|
|
56
|
+
CODE_FILES=$(git diff-tree --no-commit-id --name-only -r "$sha" -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.py' 2>/dev/null || true)
|
|
57
|
+
if [ -n "$CODE_FILES" ]; then
|
|
58
|
+
if ! echo "$BODY" | grep -qi 'Co-Authored-By:'; then
|
|
59
|
+
echo "WARNING [$SHORT]: Touches code files but missing Co-Authored-By tag"
|
|
60
|
+
WARN_COUNT=$((WARN_COUNT + 1))
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Check Ref: REQ-XXX on requirement-related commits
|
|
65
|
+
if echo "$BODY" | grep -qi 'compliance\|requirement\|REQ-'; then
|
|
66
|
+
if ! echo "$BODY" | grep -qP 'Ref:\s*REQ-\d+'; then
|
|
67
|
+
echo "WARNING [$SHORT]: Appears requirement-related but missing Ref: REQ-XXX"
|
|
68
|
+
WARN_COUNT=$((WARN_COUNT + 1))
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
done <<< "$COMMITS"
|
|
73
|
+
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Checked $TOTAL commits: $FAILED errors, $WARN_COUNT warnings."
|
|
76
|
+
|
|
77
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
78
|
+
echo "=== Commit convention check passed ==="
|
|
79
|
+
else
|
|
80
|
+
echo "=== FAILED: $FAILED commits do not follow Conventional Commits format ==="
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
exit $EXIT_CODE
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# validate-compliance-artifacts.sh — Validates compliance artifacts exist on PRs to main.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/validate-compliance-artifacts.sh [base-branch]
|
|
6
|
+
#
|
|
7
|
+
# Checks that PRs containing tracked requirement commits have the required
|
|
8
|
+
# compliance artifacts (test-scope, RTM entry, release ticket, etc.).
|
|
9
|
+
# Designed to run as a CI job on PRs to main.
|
|
10
|
+
#
|
|
11
|
+
# Install: cp this file to your project's scripts/ directory && chmod +x scripts/validate-compliance-artifacts.sh
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
BASE_BRANCH="${1:-origin/main}"
|
|
16
|
+
EXIT_CODE=0
|
|
17
|
+
|
|
18
|
+
echo "=== Compliance Artifact Validation ==="
|
|
19
|
+
echo "Comparing: $BASE_BRANCH...HEAD"
|
|
20
|
+
echo ""
|
|
21
|
+
|
|
22
|
+
# Extract REQ-XXX references from commits in this PR.
|
|
23
|
+
#
|
|
24
|
+
# Requires ≥3 digits so placeholder patterns like `REQ-0XX` (used in
|
|
25
|
+
# commit-body templates referring to a batch of sub-REQs) don't create
|
|
26
|
+
# phantom IDs that block CI — the loose `\d+` would match `REQ-0` from
|
|
27
|
+
# `REQ-0XX` and then ERROR on a missing evidence dir for the phantom
|
|
28
|
+
# `REQ-0`. The project's stable id format is REQ-001 onwards (#232).
|
|
29
|
+
REQUIREMENTS=$(git log "$BASE_BRANCH"..HEAD --format='%B' | grep -oP 'REQ-\d{3,}' | sort -u || true)
|
|
30
|
+
|
|
31
|
+
if [ -z "$REQUIREMENTS" ]; then
|
|
32
|
+
echo "No REQ-XXX references found in PR commits — skipping artifact validation."
|
|
33
|
+
echo "If this PR contains tracked requirements, ensure commits include 'Ref: REQ-XXX'."
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "Requirements found in PR commits: $REQUIREMENTS"
|
|
38
|
+
echo ""
|
|
39
|
+
|
|
40
|
+
for REQ in $REQUIREMENTS; do
|
|
41
|
+
echo "--- Checking $REQ ---"
|
|
42
|
+
|
|
43
|
+
# Skip REQs that have no RTM row — they're forward-references or
|
|
44
|
+
# design-discussion mentions in commit bodies, not tracked
|
|
45
|
+
# requirements for this release. Two practical cases (#232):
|
|
46
|
+
# - "REQ-033 is a prereq for REQ-034" — REQ-034 hasn't started.
|
|
47
|
+
# - REQ scaffolded then descoped — RTM row removed but old
|
|
48
|
+
# `Ref: REQ-XXX` commits still grep-match.
|
|
49
|
+
# The `| $REQ ` pattern (leading pipe + trailing space) matches the
|
|
50
|
+
# markdown table row used in RTM, so a substring elsewhere doesn't
|
|
51
|
+
# accidentally satisfy it.
|
|
52
|
+
if ! grep -q "| $REQ " compliance/RTM.md 2>/dev/null; then
|
|
53
|
+
echo " INFO: $REQ is referenced in commits but has no RTM row — skipping (forward-reference, not a tracked requirement for this release)"
|
|
54
|
+
continue
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Check evidence directory exists
|
|
58
|
+
if [ ! -d "compliance/evidence/$REQ" ]; then
|
|
59
|
+
echo " ERROR: Evidence directory missing: compliance/evidence/$REQ/"
|
|
60
|
+
EXIT_CODE=1
|
|
61
|
+
continue
|
|
62
|
+
fi
|
|
63
|
+
echo " OK: Evidence directory exists"
|
|
64
|
+
|
|
65
|
+
# Check test-scope.md exists
|
|
66
|
+
if [ ! -f "compliance/evidence/$REQ/test-scope.md" ]; then
|
|
67
|
+
echo " ERROR: Test scope missing: compliance/evidence/$REQ/test-scope.md"
|
|
68
|
+
EXIT_CODE=1
|
|
69
|
+
else
|
|
70
|
+
echo " OK: test-scope.md exists"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Check test-plan.md exists
|
|
74
|
+
if [ ! -f "compliance/evidence/$REQ/test-plan.md" ]; then
|
|
75
|
+
echo " ERROR: Test plan missing: compliance/evidence/$REQ/test-plan.md"
|
|
76
|
+
EXIT_CODE=1
|
|
77
|
+
else
|
|
78
|
+
echo " OK: test-plan.md exists"
|
|
79
|
+
|
|
80
|
+
# Verify test files referenced in test-plan.md exist in the tree.
|
|
81
|
+
# Two alternatives: directory-prefixed paths (captured whole), and bare
|
|
82
|
+
# filenames containing .test. / .spec. (captured from the stem, not the
|
|
83
|
+
# dot — without the leading [\w./-]+ the match would start at `.test.`
|
|
84
|
+
# and drop the filename entirely). See DevAudit #133.
|
|
85
|
+
#
|
|
86
|
+
# mapfile + quoted expansion: without this, tokens like `__tests__/**`
|
|
87
|
+
# get pathname-expanded by the for-loop and produce phantom "missing"
|
|
88
|
+
# errors for every real subdirectory. See DevAudit #137.
|
|
89
|
+
mapfile -t TEST_FILES < <(
|
|
90
|
+
grep -oP '(?:__tests__/|tests?/|e2e/|spec/)\S+|[\w./-]+\.(?:test|spec)\.\S+' \
|
|
91
|
+
"compliance/evidence/$REQ/test-plan.md" 2>/dev/null \
|
|
92
|
+
| sed 's/[`),.;:]*$//' | grep -v '/$' | sort -u || true
|
|
93
|
+
)
|
|
94
|
+
if [ "${#TEST_FILES[@]}" -gt 0 ]; then
|
|
95
|
+
MISSING_TESTS=0
|
|
96
|
+
for TF in "${TEST_FILES[@]}"; do
|
|
97
|
+
# Prose glob references like `__tests__/**` describe a directory
|
|
98
|
+
# set, not a specific file — skip instead of treating as missing.
|
|
99
|
+
if [[ "$TF" == *[*?]* ]]; then
|
|
100
|
+
echo " INFO: Skipping glob reference (not a file path): $TF"
|
|
101
|
+
continue
|
|
102
|
+
fi
|
|
103
|
+
# Try exact path; otherwise search the repo by basename, skipping
|
|
104
|
+
# node_modules and build/coverage outputs. The previous
|
|
105
|
+
# `compgen -G "**/X"` form relied on bash globstar to recurse,
|
|
106
|
+
# but globstar is OFF by default — `**` collapsed to a single
|
|
107
|
+
# `*` (depth-1 wildcard), so any bare-filename reference whose
|
|
108
|
+
# actual file lived at depth ≥2 was falsely reported as missing.
|
|
109
|
+
if [ ! -f "$TF" ] && ! find . \
|
|
110
|
+
-type d \( -name node_modules -o -name .next -o -name .git \
|
|
111
|
+
-o -name playwright-report -o -name coverage \
|
|
112
|
+
-o -name dist -o -name build -o -name 'test-results' \) -prune \
|
|
113
|
+
-o -type f -name "$(basename "$TF")" -print 2>/dev/null \
|
|
114
|
+
| grep -q .; then
|
|
115
|
+
echo " ERROR: Test file referenced in test-plan.md not found: $TF"
|
|
116
|
+
MISSING_TESTS=$((MISSING_TESTS + 1))
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
if [ "$MISSING_TESTS" -gt 0 ]; then
|
|
120
|
+
echo " ERROR: $MISSING_TESTS test file(s) from test-plan.md missing — tests must be written before merge"
|
|
121
|
+
EXIT_CODE=1
|
|
122
|
+
else
|
|
123
|
+
echo " OK: All test files referenced in test-plan.md exist"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Check test-execution-summary.md exists
|
|
129
|
+
if [ ! -f "compliance/evidence/$REQ/test-execution-summary.md" ]; then
|
|
130
|
+
echo " WARNING: Test execution summary missing: compliance/evidence/$REQ/test-execution-summary.md"
|
|
131
|
+
else
|
|
132
|
+
echo " OK: test-execution-summary.md exists"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Check RTM entry exists and has an accepted terminal status. Accepts
|
|
136
|
+
# SUPERSEDED as a valid terminal state — used when a REQ is replaced
|
|
137
|
+
# by a successor (e.g. REQ-030 superseded by REQ-031). Without this
|
|
138
|
+
# branch every PR with a commit referencing a SUPERSEDED REQ blocks
|
|
139
|
+
# CI (#232).
|
|
140
|
+
if grep -q "$REQ" compliance/RTM.md 2>/dev/null; then
|
|
141
|
+
if grep "$REQ" compliance/RTM.md | grep -q "TESTED - PENDING SIGN-OFF"; then
|
|
142
|
+
echo " OK: RTM status is TESTED - PENDING SIGN-OFF"
|
|
143
|
+
elif grep "$REQ" compliance/RTM.md | grep -q "APPROVED"; then
|
|
144
|
+
echo " OK: RTM status is APPROVED"
|
|
145
|
+
elif grep "$REQ" compliance/RTM.md | grep -q "SUPERSEDED"; then
|
|
146
|
+
echo " OK: RTM status is SUPERSEDED"
|
|
147
|
+
else
|
|
148
|
+
echo " WARNING: RTM entry exists but status is not TESTED - PENDING SIGN-OFF"
|
|
149
|
+
fi
|
|
150
|
+
else
|
|
151
|
+
echo " ERROR: No RTM entry found for $REQ in compliance/RTM.md"
|
|
152
|
+
EXIT_CODE=1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Check release ticket exists. Accepts the superseded-releases/
|
|
156
|
+
# location as a valid terminal home (mirrors pending- and
|
|
157
|
+
# approved-releases/) so SUPERSEDED REQs don't block CI (#232).
|
|
158
|
+
TICKET_PATTERN="compliance/pending-releases/RELEASE-TICKET-${REQ}*"
|
|
159
|
+
APPROVED_PATTERN="compliance/approved-releases/RELEASE-TICKET-${REQ}*"
|
|
160
|
+
SUPERSEDED_PATTERN="compliance/superseded-releases/RELEASE-TICKET-${REQ}*"
|
|
161
|
+
if compgen -G "$TICKET_PATTERN" > /dev/null 2>&1 \
|
|
162
|
+
|| compgen -G "$APPROVED_PATTERN" > /dev/null 2>&1 \
|
|
163
|
+
|| compgen -G "$SUPERSEDED_PATTERN" > /dev/null 2>&1; then
|
|
164
|
+
echo " OK: Release ticket exists"
|
|
165
|
+
else
|
|
166
|
+
echo " ERROR: Release ticket missing: compliance/pending-releases/RELEASE-TICKET-${REQ}.md"
|
|
167
|
+
EXIT_CODE=1
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Check implementation-plan.md for MEDIUM/HIGH risk (check RTM for risk level)
|
|
171
|
+
if grep "$REQ" compliance/RTM.md 2>/dev/null | grep -qiE 'MEDIUM|HIGH'; then
|
|
172
|
+
if [ ! -f "compliance/evidence/$REQ/implementation-plan.md" ]; then
|
|
173
|
+
echo " ERROR: Implementation plan missing for MEDIUM/HIGH risk: compliance/evidence/$REQ/implementation-plan.md"
|
|
174
|
+
EXIT_CODE=1
|
|
175
|
+
else
|
|
176
|
+
echo " OK: implementation-plan.md exists (MEDIUM/HIGH risk)"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Check AI prompt log for MEDIUM/HIGH with AI involvement
|
|
180
|
+
if [ -f "compliance/evidence/$REQ/ai-use-note.md" ] && [ ! -f "compliance/evidence/$REQ/ai-prompts.md" ]; then
|
|
181
|
+
echo " WARNING: AI use noted but ai-prompts.md missing for MEDIUM/HIGH risk"
|
|
182
|
+
fi
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
echo ""
|
|
186
|
+
done
|
|
187
|
+
|
|
188
|
+
# Check that RTM was updated in this PR
|
|
189
|
+
if git diff --name-only "$BASE_BRANCH"...HEAD | grep -q 'compliance/RTM.md'; then
|
|
190
|
+
echo "OK: compliance/RTM.md was updated in this PR"
|
|
191
|
+
else
|
|
192
|
+
echo "WARNING: compliance/RTM.md was not modified in this PR"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
echo ""
|
|
196
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
197
|
+
echo "=== All compliance artifact checks passed ==="
|
|
198
|
+
else
|
|
199
|
+
echo "=== FAILED: Missing required compliance artifacts ==="
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
exit $EXIT_CODE
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# validate-compliance-artifacts.test.sh — fixture-based tests for #232
|
|
3
|
+
# hardenings (regex tightening, no-RTM-row skip, SUPERSEDED handling).
|
|
4
|
+
#
|
|
5
|
+
# Builds a throwaway git repo per case, populates compliance/ files,
|
|
6
|
+
# runs validate-compliance-artifacts.sh against it, asserts on the exit
|
|
7
|
+
# code and selected log lines.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/validate-compliance-artifacts.test.sh
|
|
11
|
+
#
|
|
12
|
+
# The script is hermetic: it does NOT touch the host repo; everything
|
|
13
|
+
# runs inside a mktemp'd directory that's torn down at the end. Suitable
|
|
14
|
+
# for CI invocation, no extra deps beyond bash + git + grep.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
VALIDATOR="$SCRIPT_DIR/validate-compliance-artifacts.sh"
|
|
20
|
+
[ -x "$VALIDATOR" ] || chmod +x "$VALIDATOR"
|
|
21
|
+
|
|
22
|
+
PASS=0
|
|
23
|
+
FAIL=0
|
|
24
|
+
|
|
25
|
+
# --- helpers ---
|
|
26
|
+
|
|
27
|
+
# Build a fresh git fixture under $1 and cd into it. Initial commit on
|
|
28
|
+
# a `base` branch, then a single feature commit on `feature` whose body
|
|
29
|
+
# the caller seeds via the second argument.
|
|
30
|
+
make_fixture() {
|
|
31
|
+
local dir="$1" feat_msg="$2"
|
|
32
|
+
rm -rf "$dir"
|
|
33
|
+
mkdir -p "$dir"
|
|
34
|
+
cd "$dir"
|
|
35
|
+
git init -q --initial-branch=base
|
|
36
|
+
git config user.email "test@example.com"
|
|
37
|
+
git config user.name "test"
|
|
38
|
+
mkdir -p compliance/pending-releases compliance/approved-releases compliance/superseded-releases compliance/evidence
|
|
39
|
+
printf '# RTM\n\n| ID | Description | Status |\n| --- | --- | --- |\n' > compliance/RTM.md
|
|
40
|
+
git add . && git commit -q -m "init"
|
|
41
|
+
git checkout -q -b feature
|
|
42
|
+
echo "feature change" > feature.txt
|
|
43
|
+
git add . && git commit -q -m "feat: feature commit
|
|
44
|
+
|
|
45
|
+
$feat_msg"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Run the validator against the current fixture's HEAD vs base. Captures
|
|
49
|
+
# stdout to $OUT_FILE and exit code to $EXIT_CODE_VAR.
|
|
50
|
+
run_validator() {
|
|
51
|
+
set +e
|
|
52
|
+
OUT_FILE=$(mktemp)
|
|
53
|
+
bash "$VALIDATOR" base > "$OUT_FILE" 2>&1
|
|
54
|
+
LAST_EXIT=$?
|
|
55
|
+
set -e
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
assert_grep() {
|
|
59
|
+
local desc="$1" pattern="$2" want_match="$3"
|
|
60
|
+
if grep -qE "$pattern" "$OUT_FILE"; then
|
|
61
|
+
found=1
|
|
62
|
+
else
|
|
63
|
+
found=0
|
|
64
|
+
fi
|
|
65
|
+
if [ "$found" = "$want_match" ]; then
|
|
66
|
+
echo " PASS: $desc"
|
|
67
|
+
PASS=$((PASS + 1))
|
|
68
|
+
else
|
|
69
|
+
echo " FAIL: $desc (want match=$want_match, got=$found, pattern=$pattern)"
|
|
70
|
+
echo " --- output ---"
|
|
71
|
+
sed 's/^/ /' "$OUT_FILE"
|
|
72
|
+
echo " ---"
|
|
73
|
+
FAIL=$((FAIL + 1))
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
assert_exit() {
|
|
78
|
+
local desc="$1" want="$2"
|
|
79
|
+
if [ "$LAST_EXIT" = "$want" ]; then
|
|
80
|
+
echo " PASS: $desc (exit=$LAST_EXIT)"
|
|
81
|
+
PASS=$((PASS + 1))
|
|
82
|
+
else
|
|
83
|
+
echo " FAIL: $desc (want=$want, got=$LAST_EXIT)"
|
|
84
|
+
echo " --- output ---"
|
|
85
|
+
sed 's/^/ /' "$OUT_FILE"
|
|
86
|
+
echo " ---"
|
|
87
|
+
FAIL=$((FAIL + 1))
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
WORKDIR=$(mktemp -d -t validate-compliance-test-XXXX)
|
|
92
|
+
trap 'rm -rf "$WORKDIR"' EXIT
|
|
93
|
+
|
|
94
|
+
# --- case 1: REQ-0XX placeholder is ignored (\d{3,} regex) ---
|
|
95
|
+
|
|
96
|
+
echo "Case 1: REQ-0XX placeholder doesn't create phantom REQ-0"
|
|
97
|
+
make_fixture "$WORKDIR/case1" "Ref: REQ-0XX/test-scope.md (placeholder for sub-REQs)"
|
|
98
|
+
run_validator
|
|
99
|
+
assert_grep "no phantom REQ-0 surfaced" '^Requirements found in PR commits: REQ-0\b' 0
|
|
100
|
+
assert_grep "skip-validation message present" 'No REQ-XXX references found' 1
|
|
101
|
+
assert_exit "validator exits 0 on placeholder-only commit body" 0
|
|
102
|
+
cd "$WORKDIR"
|
|
103
|
+
|
|
104
|
+
# --- case 2: REQ-099 mentioned but absent from RTM → INFO + skip ---
|
|
105
|
+
|
|
106
|
+
echo "Case 2: forward-reference REQ skipped when RTM row absent"
|
|
107
|
+
make_fixture "$WORKDIR/case2" "Ref: REQ-099 (this is a forward-reference, no RTM row)"
|
|
108
|
+
run_validator
|
|
109
|
+
assert_grep "INFO line emitted for forward-reference" 'INFO: REQ-099 is referenced.*no RTM row' 1
|
|
110
|
+
assert_grep "no ERROR for missing evidence dir" 'ERROR: Evidence directory missing.*REQ-099' 0
|
|
111
|
+
assert_exit "validator exits 0 when only forward-references present" 0
|
|
112
|
+
cd "$WORKDIR"
|
|
113
|
+
|
|
114
|
+
# --- case 3: SUPERSEDED RTM status is accepted ---
|
|
115
|
+
|
|
116
|
+
echo "Case 3: SUPERSEDED RTM status passes"
|
|
117
|
+
make_fixture "$WORKDIR/case3" "Ref: REQ-030"
|
|
118
|
+
# RTM with SUPERSEDED status; full evidence + ticket present
|
|
119
|
+
{
|
|
120
|
+
echo '# RTM'
|
|
121
|
+
echo
|
|
122
|
+
echo '| ID | Description | Status |'
|
|
123
|
+
echo '| --- | --- | --- |'
|
|
124
|
+
echo '| REQ-030 | Replaced by REQ-031 | SUPERSEDED by REQ-031 |'
|
|
125
|
+
} > compliance/RTM.md
|
|
126
|
+
mkdir -p compliance/evidence/REQ-030
|
|
127
|
+
echo "scope" > compliance/evidence/REQ-030/test-scope.md
|
|
128
|
+
echo "plan" > compliance/evidence/REQ-030/test-plan.md
|
|
129
|
+
touch compliance/pending-releases/RELEASE-TICKET-REQ-030.md
|
|
130
|
+
git add . && git commit -q --amend --no-edit
|
|
131
|
+
run_validator
|
|
132
|
+
assert_grep "RTM SUPERSEDED accepted" 'OK: RTM status is SUPERSEDED' 1
|
|
133
|
+
assert_exit "validator exits 0 with SUPERSEDED RTM status" 0
|
|
134
|
+
cd "$WORKDIR"
|
|
135
|
+
|
|
136
|
+
# --- case 4: SUPERSEDED ticket location is accepted ---
|
|
137
|
+
|
|
138
|
+
echo "Case 4: superseded-releases/ ticket location accepted"
|
|
139
|
+
make_fixture "$WORKDIR/case4" "Ref: REQ-031"
|
|
140
|
+
{
|
|
141
|
+
echo '# RTM'
|
|
142
|
+
echo
|
|
143
|
+
echo '| ID | Description | Status |'
|
|
144
|
+
echo '| --- | --- | --- |'
|
|
145
|
+
echo '| REQ-031 | Successor of REQ-030 | SUPERSEDED by REQ-032 |'
|
|
146
|
+
} > compliance/RTM.md
|
|
147
|
+
mkdir -p compliance/evidence/REQ-031
|
|
148
|
+
echo "scope" > compliance/evidence/REQ-031/test-scope.md
|
|
149
|
+
echo "plan" > compliance/evidence/REQ-031/test-plan.md
|
|
150
|
+
# Only the superseded location — neither pending- nor approved-releases.
|
|
151
|
+
touch compliance/superseded-releases/RELEASE-TICKET-REQ-031.md
|
|
152
|
+
git add . && git commit -q --amend --no-edit
|
|
153
|
+
run_validator
|
|
154
|
+
assert_grep "ticket in superseded-releases/ accepted" 'OK: Release ticket exists' 1
|
|
155
|
+
assert_grep "no missing-ticket ERROR" 'ERROR: Release ticket missing' 0
|
|
156
|
+
assert_exit "validator exits 0 with SUPERSEDED ticket location" 0
|
|
157
|
+
cd "$WORKDIR"
|
|
158
|
+
|
|
159
|
+
# --- case 5: bare-filename reference resolves to file at depth ≥2 ---
|
|
160
|
+
#
|
|
161
|
+
# Regression for the broken `compgen -G "**/$TF"` search: bash globstar
|
|
162
|
+
# is off by default, so `**` only matched depth-1 paths. A test plan
|
|
163
|
+
# referencing a bare filename (e.g. `inventory-service.list-by-kind.test.ts`)
|
|
164
|
+
# whose actual file lived at `__tests__/services/...` was reported as
|
|
165
|
+
# missing even though it existed. Fix uses `find -name` instead.
|
|
166
|
+
|
|
167
|
+
echo "Case 5: bare-filename reference resolves to depth-2 test file"
|
|
168
|
+
make_fixture "$WORKDIR/case5" "Ref: REQ-200"
|
|
169
|
+
{
|
|
170
|
+
echo '# RTM'
|
|
171
|
+
echo
|
|
172
|
+
echo '| ID | Description | Status |'
|
|
173
|
+
echo '| --- | --- | --- |'
|
|
174
|
+
echo '| REQ-200 | Bare-filename ref test | IN PROGRESS |'
|
|
175
|
+
} > compliance/RTM.md
|
|
176
|
+
mkdir -p compliance/evidence/REQ-200
|
|
177
|
+
echo "scope" > compliance/evidence/REQ-200/test-scope.md
|
|
178
|
+
# Plan references the file by BARE filename (no directory prefix).
|
|
179
|
+
{
|
|
180
|
+
echo '# Test Plan — REQ-200'
|
|
181
|
+
echo
|
|
182
|
+
echo '| AC | Test | Notes |'
|
|
183
|
+
echo '| --- | --- | --- |'
|
|
184
|
+
echo '| AC1 | `foo-service.list-by-kind.test.ts` | unit test |'
|
|
185
|
+
} > compliance/evidence/REQ-200/test-plan.md
|
|
186
|
+
# Actual test file lives at depth 2.
|
|
187
|
+
mkdir -p __tests__/services
|
|
188
|
+
echo "// stub" > __tests__/services/foo-service.list-by-kind.test.ts
|
|
189
|
+
echo "summary" > compliance/evidence/REQ-200/test-execution-summary.md
|
|
190
|
+
touch compliance/pending-releases/RELEASE-TICKET-REQ-200.md
|
|
191
|
+
git add . && git commit -q --amend --no-edit
|
|
192
|
+
run_validator
|
|
193
|
+
assert_grep "depth-2 bare filename resolved" 'OK: All test files referenced in test-plan.md exist' 1
|
|
194
|
+
assert_grep "no missing-test ERROR" 'ERROR: Test file referenced in test-plan.md not found' 0
|
|
195
|
+
assert_exit "validator exits 0 with depth-2 bare-filename reference" 0
|
|
196
|
+
cd "$WORKDIR"
|
|
197
|
+
|
|
198
|
+
# --- summary ---
|
|
199
|
+
|
|
200
|
+
echo
|
|
201
|
+
echo "=== validate-compliance-artifacts.test.sh: $PASS passed, $FAIL failed ==="
|
|
202
|
+
[ "$FAIL" -eq 0 ]
|