@metasession.co/devaudit-cli 0.1.23 → 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.23",
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.23",
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
- # Locate the "Status" column from the first header row that has one.
123
- statuscol==0 {
124
- for (i=1; i<=NF; i++) { c=$i; gsub(/^[[:space:]]+|[[:space:]]+$/, "", c); if (c=="Status") statuscol=i }
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
- DATE_PREFIX="v$(date +%Y.%m.%d)"
142
- RESOLVE_URL="${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${DATE_PREFIX}"
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 ${DATE_PREFIX} yet — waiting ${WAIT_SECONDS}s..."
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 ${DATE_PREFIX} after ${MAX_ATTEMPTS} attempts. Push to develop first to create the release."
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')
@@ -39,6 +39,12 @@ jobs:
39
39
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
40
40
  steps:
41
41
  - uses: actions/checkout@v4
42
+ with:
43
+ # Full history so `req_meta_args` can `git log --grep "[REQ-XXX]|Ref: REQ-XXX"`
44
+ # against the implementation commits (the merge commit alone never
45
+ # carries the REQ tag in a feature → develop PR flow, so without this
46
+ # the change_type derivation is null nearly everywhere — #77).
47
+ fetch-depth: 0
42
48
 
43
49
  - name: Resolve DevAudit base URL
44
50
  id: resolve
@@ -147,8 +153,12 @@ jobs:
147
153
  CT=$(git log --grep "\[${REQ}\]\|Ref: ${REQ}" --pretty=%s -1 2>/dev/null \
148
154
  | grep -oE '^(feat|fix|refactor|perf|chore|docs|ci|build|test|compliance|revert)' \
149
155
  | head -1 || true)
150
- [ -n "$TITLE" ] && printf -- '--release-title %q ' "$TITLE"
151
- [ -n "$CT" ] && printf -- '--change-type %q ' "$CT"
156
+ # Use if/then/fi (not `[ ] && cmd`) under bash -eo pipefail a
157
+ # trailing `[ ] && cmd` whose test fails returns 1, and the
158
+ # function inherits that exit code; calling via $(req_meta_args …)
159
+ # then aborts the step silently (DevAudit-Installer#77).
160
+ if [ -n "$TITLE" ]; then printf -- '--release-title %q ' "$TITLE"; fi
161
+ if [ -n "$CT" ]; then printf -- '--change-type %q ' "$CT"; fi
152
162
  }
153
163
 
154
164
  # Upload release tickets (pending only)