@metasession.co/devaudit-cli 0.1.24 → 0.1.25
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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.1.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.1.25",
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"consola": "^3.2.3",
|
|
39
39
|
"env-paths": "^3.0.0",
|
|
@@ -117,11 +117,31 @@ echo "Ticket Status -> RELEASED."
|
|
|
117
117
|
|
|
118
118
|
# ── Flip the RTM row -> RELEASED (preserve any parenthetical note) ───────────
|
|
119
119
|
if [ -f "$RTM" ] && grep -qE "^\| ${REQ_ID} " "$RTM"; then
|
|
120
|
+
# Table-aware Status column resolution (#72): the previous version locked
|
|
121
|
+
# `statuscol` on the FIRST header that contained "Status", which mangled the
|
|
122
|
+
# wrong column when the RTM has a small legend table above the main matrix
|
|
123
|
+
# (e.g. a 2-column legend with `Status | Description` columns → statuscol=2,
|
|
124
|
+
# then the awk overwrote col-1 REQ-ID for every row).
|
|
125
|
+
#
|
|
126
|
+
# Fix: re-evaluate `statuscol` on EVERY header-shaped row (a row whose cells
|
|
127
|
+
# carry the literal header text "Status" + an ID-like column header). The
|
|
128
|
+
# legend has "Status" but no ID-like column → not locked; the main RTM has
|
|
129
|
+
# both → locks correctly. Data rows don't carry the literal "Status" header
|
|
130
|
+
# text in any cell, so they don't re-trigger the lock. Separator rows
|
|
131
|
+
# (`|---|---|…`) are left intact and don't affect `statuscol`.
|
|
120
132
|
awk -v req="$REQ_ID" '
|
|
121
133
|
BEGIN { FS="|"; OFS="|"; statuscol=0 }
|
|
122
|
-
#
|
|
123
|
-
statuscol
|
|
124
|
-
|
|
134
|
+
# Header detection: scan every row; require both a "Status" header cell
|
|
135
|
+
# AND an ID-like header cell in the same row before locking statuscol to
|
|
136
|
+
# this row''s column index.
|
|
137
|
+
{
|
|
138
|
+
cand=0; idseen=0
|
|
139
|
+
for (i=1; i<=NF; i++) {
|
|
140
|
+
c=$i; gsub(/^[[:space:]]+|[[:space:]]+$/, "", c)
|
|
141
|
+
if (c=="Status") cand=i
|
|
142
|
+
if (c=="ID" || c=="REQ-ID" || c=="REQ ID" || c ~ /^Requirement/) idseen=1
|
|
143
|
+
}
|
|
144
|
+
if (cand>0 && idseen) statuscol=cand
|
|
125
145
|
}
|
|
126
146
|
$0 ~ ("^\\| " req " ") && statuscol>0 {
|
|
127
147
|
cell=$statuscol
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# close-out-release.test.sh — Tests for the RTM-flip awk in
|
|
3
|
+
# close-out-release.sh, specifically that it locates the correct
|
|
4
|
+
# Status column when the RTM has multiple markdown tables with
|
|
5
|
+
# different shapes (#72 regression: the prior implementation locked
|
|
6
|
+
# `statuscol` on the FIRST header containing "Status", mangling the
|
|
7
|
+
# wrong column when a legend table appeared above the main RTM).
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/close-out-release.test.sh
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
+
HELPER="$SCRIPT_DIR/close-out-release.sh"
|
|
16
|
+
[ -x "$HELPER" ] || chmod +x "$HELPER"
|
|
17
|
+
|
|
18
|
+
PASS=0
|
|
19
|
+
FAIL=0
|
|
20
|
+
|
|
21
|
+
assert_eq() {
|
|
22
|
+
local desc="$1" want="$2" got="$3"
|
|
23
|
+
if [ "$got" = "$want" ]; then
|
|
24
|
+
echo " PASS: $desc"
|
|
25
|
+
PASS=$((PASS + 1))
|
|
26
|
+
else
|
|
27
|
+
echo " FAIL: $desc"
|
|
28
|
+
echo " want: $want"
|
|
29
|
+
echo " got: $got"
|
|
30
|
+
FAIL=$((FAIL + 1))
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Build a self-contained fixture under $1 with:
|
|
35
|
+
# - compliance/RTM.md containing the legend table + the main RTM
|
|
36
|
+
# - compliance/pending-releases/RELEASE-TICKET-REQ-050.md (so the script
|
|
37
|
+
# can stage the move + flip; close-out aborts early without it)
|
|
38
|
+
# - a `.git` so the `git mv` step doesn't break
|
|
39
|
+
make_fixture() {
|
|
40
|
+
local dir="$1"
|
|
41
|
+
rm -rf "$dir"
|
|
42
|
+
mkdir -p "$dir/compliance/pending-releases" "$dir/compliance/approved-releases"
|
|
43
|
+
cd "$dir"
|
|
44
|
+
git init -q --initial-branch=main
|
|
45
|
+
git config user.email "test@example.com"
|
|
46
|
+
git config user.name "test"
|
|
47
|
+
|
|
48
|
+
# Multi-table RTM: legend table first (Status | Description, 2 cols, the
|
|
49
|
+
# shape that mangled WGB on REQ-048/050), then the main RTM (REQ-ID, …,
|
|
50
|
+
# Status, …, 7 cols).
|
|
51
|
+
cat > compliance/RTM.md <<'EOF'
|
|
52
|
+
# Requirements Traceability Matrix
|
|
53
|
+
|
|
54
|
+
## Status Legend
|
|
55
|
+
|
|
56
|
+
| Status | Description |
|
|
57
|
+
| ---------------------------- | ------------------------------------------ |
|
|
58
|
+
| DRAFT | Requirement captured, planning underway |
|
|
59
|
+
| TESTED - PENDING SIGN-OFF | Implementation merged, awaiting close-out |
|
|
60
|
+
| RELEASED | Promoted to main + portal-released |
|
|
61
|
+
|
|
62
|
+
## Main RTM
|
|
63
|
+
|
|
64
|
+
| REQ-ID | Source | Risk | Evidence | Status | Owner | Date |
|
|
65
|
+
| ------- | ------ | ------ | ------------------------------ | ------------------------- | ------- | ---------- |
|
|
66
|
+
| REQ-049 | #155 | LOW | compliance/evidence/REQ-049/ | RELEASED | thomp@. | 2026-05-24 |
|
|
67
|
+
| REQ-050 | #180 | HIGH | compliance/evidence/REQ-050/ | TESTED - PENDING SIGN-OFF | thomp@. | 2026-05-28 |
|
|
68
|
+
EOF
|
|
69
|
+
|
|
70
|
+
# A minimal release ticket — just enough for the script's pre-checks to pass.
|
|
71
|
+
cat > compliance/pending-releases/RELEASE-TICKET-REQ-050.md <<'EOF'
|
|
72
|
+
# Release Ticket: REQ-050
|
|
73
|
+
|
|
74
|
+
**Status:** TESTED - PENDING SIGN-OFF
|
|
75
|
+
**DevAudit Release:** REQ-050
|
|
76
|
+
EOF
|
|
77
|
+
|
|
78
|
+
git add -A
|
|
79
|
+
git commit -q -m "fixture: pre-close-out state"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# ── Case 1: multi-table RTM, status-column lock disambiguated by ID column ─
|
|
83
|
+
{
|
|
84
|
+
dir="$(mktemp -d)/cli-close-out-fixture-1"
|
|
85
|
+
make_fixture "$dir"
|
|
86
|
+
# Skip the portal probe + the ticket-move so we isolate the RTM-flip path.
|
|
87
|
+
unset DEVAUDIT_API_KEY DEVAUDIT_BASE_URL || true
|
|
88
|
+
# Run the script; tolerate exit on the warnings — the RTM flip should still
|
|
89
|
+
# have happened before any non-fatal warning.
|
|
90
|
+
bash "$HELPER" REQ-050 >/dev/null 2>&1 || true
|
|
91
|
+
# Assert: col-1 stays REQ-050 (NOT overwritten with RELEASED); col-5 flips.
|
|
92
|
+
row=$(grep -m1 -E "^\| REQ-050 " compliance/RTM.md || true)
|
|
93
|
+
col1=$(echo "$row" | awk -F '|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}')
|
|
94
|
+
col5=$(echo "$row" | awk -F '|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$6); print $6}')
|
|
95
|
+
assert_eq "REQ-050 row: col-1 unchanged" "REQ-050" "$col1"
|
|
96
|
+
assert_eq "REQ-050 row: col-5 flipped" "RELEASED" "$col5"
|
|
97
|
+
# And the unrelated REQ-049 row stays untouched (it was already RELEASED
|
|
98
|
+
# before this run; if the awk picked up the wrong table or column, col-1
|
|
99
|
+
# would say RELEASED instead of REQ-049).
|
|
100
|
+
row49=$(grep -m1 -E "^\| REQ-049 " compliance/RTM.md || true)
|
|
101
|
+
col1_49=$(echo "$row49" | awk -F '|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}')
|
|
102
|
+
assert_eq "REQ-049 row: col-1 untouched" "REQ-049" "$col1_49"
|
|
103
|
+
rm -rf "$(dirname "$dir")"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ── Case 2: single-table RTM (the simple shape) — behaviour unchanged ──────
|
|
107
|
+
{
|
|
108
|
+
dir="$(mktemp -d)/cli-close-out-fixture-2"
|
|
109
|
+
mkdir -p "$dir/compliance/pending-releases" "$dir/compliance/approved-releases"
|
|
110
|
+
cd "$dir"
|
|
111
|
+
git init -q --initial-branch=main >/dev/null
|
|
112
|
+
git config user.email "test@example.com"
|
|
113
|
+
git config user.name "test"
|
|
114
|
+
cat > compliance/RTM.md <<'EOF'
|
|
115
|
+
# Requirements Traceability Matrix
|
|
116
|
+
|
|
117
|
+
| REQ-ID | Source | Risk | Evidence | Status | Owner | Date |
|
|
118
|
+
| ------- | ------ | ------ | ------------------------------ | ------------------------- | ------- | ---------- |
|
|
119
|
+
| REQ-050 | #180 | HIGH | compliance/evidence/REQ-050/ | TESTED - PENDING SIGN-OFF | thomp@. | 2026-05-28 |
|
|
120
|
+
EOF
|
|
121
|
+
cat > compliance/pending-releases/RELEASE-TICKET-REQ-050.md <<'EOF'
|
|
122
|
+
# Release Ticket: REQ-050
|
|
123
|
+
|
|
124
|
+
**Status:** TESTED - PENDING SIGN-OFF
|
|
125
|
+
**DevAudit Release:** REQ-050
|
|
126
|
+
EOF
|
|
127
|
+
git add -A
|
|
128
|
+
git commit -q -m "fixture: single-table"
|
|
129
|
+
unset DEVAUDIT_API_KEY DEVAUDIT_BASE_URL || true
|
|
130
|
+
bash "$HELPER" REQ-050 >/dev/null 2>&1 || true
|
|
131
|
+
row=$(grep -m1 -E "^\| REQ-050 " compliance/RTM.md || true)
|
|
132
|
+
col1=$(echo "$row" | awk -F '|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}')
|
|
133
|
+
col5=$(echo "$row" | awk -F '|' '{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$6); print $6}')
|
|
134
|
+
assert_eq "single-table: col-1 unchanged" "REQ-050" "$col1"
|
|
135
|
+
assert_eq "single-table: col-5 flipped" "RELEASED" "$col5"
|
|
136
|
+
rm -rf "$(dirname "$dir")"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
echo
|
|
140
|
+
echo "Result: $PASS passed, $FAIL failed"
|
|
141
|
+
[ "$FAIL" = "0" ]
|
|
@@ -34,6 +34,17 @@ jobs:
|
|
|
34
34
|
|
|
35
35
|
steps:
|
|
36
36
|
- uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
# The default `pull_request` checkout is a synthetic merge commit
|
|
39
|
+
# with an empty body, so `derive-release-version.sh` can't see the
|
|
40
|
+
# `[REQ-XXX]` tag on the integration-side commit. Pull the head
|
|
41
|
+
# commit directly + full history so the same derivation script the
|
|
42
|
+
# uploaders use (compliance-evidence.yml, ci.yml's register-release)
|
|
43
|
+
# produces the same release identity here (#81 — release-identity
|
|
44
|
+
# coherence; was hardcoded to `v$(date +%Y.%m.%d)` which never
|
|
45
|
+
# matched the `REQ-XXX` records the uploaders wrote).
|
|
46
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
47
|
+
fetch-depth: 0
|
|
37
48
|
|
|
38
49
|
- name: Resolve DevAudit base URL
|
|
39
50
|
run: |
|
|
@@ -138,8 +149,15 @@ jobs:
|
|
|
138
149
|
id: release
|
|
139
150
|
if: env.BOOTSTRAP_MODE != 'true'
|
|
140
151
|
run: |
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
# Derive the same release prefix the uploaders write to. The script
|
|
153
|
+
# priority is: subject `[REQ-XXX]` → body `Ref: REQ-XXX` → body
|
|
154
|
+
# `[REQ-XXX]` (merge-commit case) → bare-date fallback. Reusing it
|
|
155
|
+
# here makes the gate and the uploaders agree on release identity
|
|
156
|
+
# (#81). The 8 scenarios are covered by derive-release-version.test.sh.
|
|
157
|
+
chmod +x scripts/derive-release-version.sh 2>/dev/null || true
|
|
158
|
+
LOOKUP_PREFIX=$(bash scripts/derive-release-version.sh)
|
|
159
|
+
echo "Resolving release for prefix: ${LOOKUP_PREFIX}"
|
|
160
|
+
RESOLVE_URL="${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${LOOKUP_PREFIX}"
|
|
143
161
|
# Retry: register-release in ci.yml may still be racing with us.
|
|
144
162
|
MAX_ATTEMPTS=6
|
|
145
163
|
WAIT_SECONDS=10
|
|
@@ -151,13 +169,13 @@ jobs:
|
|
|
151
169
|
break
|
|
152
170
|
fi
|
|
153
171
|
if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
|
|
154
|
-
echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}: no release for ${
|
|
172
|
+
echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}: no release for ${LOOKUP_PREFIX} yet — waiting ${WAIT_SECONDS}s..."
|
|
155
173
|
sleep $WAIT_SECONDS
|
|
156
174
|
fi
|
|
157
175
|
done
|
|
158
176
|
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
159
177
|
if [ -z "$VERSION" ]; then
|
|
160
|
-
echo "::error::No release found for ${
|
|
178
|
+
echo "::error::No release found for ${LOOKUP_PREFIX} after ${MAX_ATTEMPTS} attempts. Push to develop first to create the release."
|
|
161
179
|
exit 1
|
|
162
180
|
fi
|
|
163
181
|
RELEASE_ID=$(echo "$RESP" | jq -r '.latest.id')
|