@metasession.co/devaudit-cli 0.1.20 → 0.1.21
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.21",
|
|
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.21",
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"consola": "^3.2.3",
|
|
39
39
|
"env-paths": "^3.0.0",
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
# --category <cat> Evidence category: ci_pipeline, local_dev,
|
|
18
18
|
# planning, test_report, security_scan,
|
|
19
19
|
# release_artifact
|
|
20
|
+
# --release-title <text> Human title for the release row (e.g. the
|
|
21
|
+
# release-ticket H1). Forwarded as
|
|
22
|
+
# `releaseTitle`; the portal no-clobbers
|
|
23
|
+
# existing non-null values.
|
|
24
|
+
# --change-type <type> Conventional-commit prefix (feat / fix /
|
|
25
|
+
# refactor / perf / chore / docs / ci /
|
|
26
|
+
# build / test / compliance / revert) for
|
|
27
|
+
# the release row. Unknown values are
|
|
28
|
+
# silently dropped server-side.
|
|
20
29
|
#
|
|
21
30
|
# Required environment variables:
|
|
22
31
|
# DEVAUDIT_BASE_URL e.g. https://meta-comply-production.up.railway.app
|
|
@@ -54,6 +63,8 @@ RELEASE_VERSION=""
|
|
|
54
63
|
CREATE_RELEASE_IF_MISSING=false
|
|
55
64
|
ENVIRONMENT=""
|
|
56
65
|
EVIDENCE_CATEGORY=""
|
|
66
|
+
RELEASE_TITLE=""
|
|
67
|
+
CHANGE_TYPE=""
|
|
57
68
|
|
|
58
69
|
while [ "$#" -gt 0 ]; do
|
|
59
70
|
case "$1" in
|
|
@@ -64,6 +75,11 @@ while [ "$#" -gt 0 ]; do
|
|
|
64
75
|
--create-release-if-missing) CREATE_RELEASE_IF_MISSING=true; shift ;;
|
|
65
76
|
--environment) ENVIRONMENT="$2"; shift 2 ;;
|
|
66
77
|
--category) EVIDENCE_CATEGORY="$2"; shift 2 ;;
|
|
78
|
+
# Descriptive title + conventional-commit change type passed through to
|
|
79
|
+
# the portal's findOrCreateRelease no-clobber backfill. Both optional;
|
|
80
|
+
# unknown change-type values are dropped server-side, not 400'd.
|
|
81
|
+
--release-title) RELEASE_TITLE="$2"; shift 2 ;;
|
|
82
|
+
--change-type) CHANGE_TYPE="$2"; shift 2 ;;
|
|
67
83
|
*) echo "Unknown option: $1"; exit 1 ;;
|
|
68
84
|
esac
|
|
69
85
|
done
|
|
@@ -163,6 +179,8 @@ for FILE in "${FILES[@]}"; do
|
|
|
163
179
|
[ -n "$BRANCH" ] && CURL_ARGS+=(-F "releaseBranch=${BRANCH}")
|
|
164
180
|
[ -n "$ENVIRONMENT" ] && CURL_ARGS+=(-F "environment=${ENVIRONMENT}")
|
|
165
181
|
[ -n "$EVIDENCE_CATEGORY" ] && CURL_ARGS+=(-F "evidenceCategory=${EVIDENCE_CATEGORY}")
|
|
182
|
+
[ -n "$RELEASE_TITLE" ] && CURL_ARGS+=(-F "releaseTitle=${RELEASE_TITLE}")
|
|
183
|
+
[ -n "$CHANGE_TYPE" ] && CURL_ARGS+=(-F "changeType=${CHANGE_TYPE}")
|
|
166
184
|
|
|
167
185
|
ATTEMPT=1
|
|
168
186
|
BACKOFF=$INITIAL_BACKOFF_SECONDS
|
|
@@ -212,20 +212,64 @@ jobs:
|
|
|
212
212
|
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
|
|
213
213
|
|
|
214
214
|
- name: Sync known requirements from RTM
|
|
215
|
+
env:
|
|
216
|
+
GH_TOKEN: ${{ github.token }}
|
|
215
217
|
run: |
|
|
216
|
-
if [ -f "compliance/RTM.md" ]; then
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
218
|
+
if [ ! -f "compliance/RTM.md" ]; then exit 0; fi
|
|
219
|
+
|
|
220
|
+
# Per REQ row in RTM we look up: title (release-ticket H1, else the
|
|
221
|
+
# linked issue's title via `gh`), and risk_class (the first LOW /
|
|
222
|
+
# MEDIUM / HIGH / CRITICAL token in the row). Both are optional —
|
|
223
|
+
# the portal renders gracefully with nulls. The portal endpoint
|
|
224
|
+
# accepts either a bare string[] (legacy) or rich rows.
|
|
225
|
+
REQS_JSON='[]'
|
|
226
|
+
while IFS= read -r REQ; do
|
|
227
|
+
[ -z "$REQ" ] && continue
|
|
228
|
+
ROW=$(grep -m1 -E "^\| ${REQ} " compliance/RTM.md || true)
|
|
229
|
+
TITLE=""
|
|
230
|
+
# Title: release-ticket H1 first (pending then approved)
|
|
231
|
+
for FILE in "compliance/pending-releases/RELEASE-TICKET-${REQ}.md" \
|
|
232
|
+
"compliance/approved-releases/RELEASE-TICKET-${REQ}.md"; do
|
|
233
|
+
if [ -f "$FILE" ]; then
|
|
234
|
+
TITLE=$(grep -m1 '^# ' "$FILE" \
|
|
235
|
+
| sed -E 's/^# *(REQ-[0-9]+)?[[:space:]]*[—:-]?[[:space:]]*//')
|
|
236
|
+
break
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
# Title fallback: first issue # found anywhere in the row
|
|
240
|
+
if [ -z "$TITLE" ] && [ -n "$ROW" ]; then
|
|
241
|
+
ISSUE_NUM=$(echo "$ROW" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
|
|
242
|
+
if [ -n "$ISSUE_NUM" ]; then
|
|
243
|
+
TITLE=$(gh issue view "$ISSUE_NUM" --json title --jq .title 2>/dev/null || true)
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
# Risk: first LOW/MEDIUM/HIGH/CRITICAL token in the row
|
|
247
|
+
RISK=""
|
|
248
|
+
if [ -n "$ROW" ]; then
|
|
249
|
+
RISK=$(echo "$ROW" | grep -ioE '\b(LOW|MEDIUM|HIGH|CRITICAL)\b' | head -1 \
|
|
250
|
+
| tr 'a-z' 'A-Z')
|
|
227
251
|
fi
|
|
252
|
+
REQS_JSON=$(echo "$REQS_JSON" | jq \
|
|
253
|
+
--arg id "$REQ" --arg title "$TITLE" --arg risk "$RISK" \
|
|
254
|
+
'. + [{
|
|
255
|
+
id: $id,
|
|
256
|
+
title: (if $title == "" then null else $title end),
|
|
257
|
+
riskClass: (if $risk == "" then null else $risk end)
|
|
258
|
+
}]')
|
|
259
|
+
done < <(grep -oE 'REQ-[0-9]+' compliance/RTM.md | sort -t- -k2 -n -u)
|
|
260
|
+
|
|
261
|
+
COUNT=$(echo "$REQS_JSON" | jq length)
|
|
262
|
+
if [ "$COUNT" = "0" ]; then
|
|
263
|
+
echo "No REQ-XXX rows in RTM.md — skipping sync"
|
|
264
|
+
exit 0
|
|
228
265
|
fi
|
|
266
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
267
|
+
-X PATCH "${BASE}/api/ci/projects/{{PROJECT_SLUG}}/known-requirements" \
|
|
268
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
269
|
+
-H "Content-Type: application/json" \
|
|
270
|
+
-d "{\"requirements\": ${REQS_JSON}}")
|
|
271
|
+
echo "known_requirements sync: HTTP ${HTTP_CODE}"
|
|
272
|
+
echo "Synced ${COUNT} requirements from RTM.md (titles + risk_class)"
|
|
229
273
|
|
|
230
274
|
# ──────────────────────────────────────────────
|
|
231
275
|
# Upload Evidence to DevAudit (after gates pass)
|
|
@@ -357,6 +401,20 @@ jobs:
|
|
|
357
401
|
echo "Uploading ${#SHOTS[@]} evidence screenshot(s) for: ${SHOT_REQS[*]}"
|
|
358
402
|
SHOT_TMP="$(mktemp -d)"
|
|
359
403
|
for REQ in "${SHOT_REQS[@]}"; do
|
|
404
|
+
# Per-REQ release metadata for the portal (no-clobbered on existing rows):
|
|
405
|
+
# release title from the pending ticket's H1; change type from the
|
|
406
|
+
# latest commit that references the REQ.
|
|
407
|
+
REQ_TITLE=""
|
|
408
|
+
if [ -f "compliance/pending-releases/RELEASE-TICKET-${REQ}.md" ]; then
|
|
409
|
+
REQ_TITLE=$(grep -m1 '^# ' "compliance/pending-releases/RELEASE-TICKET-${REQ}.md" \
|
|
410
|
+
| sed -E 's/^# *(REQ-[0-9]+)?[[:space:]]*[—:-]?[[:space:]]*//')
|
|
411
|
+
fi
|
|
412
|
+
REQ_CT=$(git log --grep "\[${REQ}\]\|Ref: ${REQ}" --pretty=%s -1 2>/dev/null \
|
|
413
|
+
| grep -oE '^(feat|fix|refactor|perf|chore|docs|ci|build|test|compliance|revert)' \
|
|
414
|
+
| head -1 || true)
|
|
415
|
+
REQ_META=()
|
|
416
|
+
[ -n "$REQ_TITLE" ] && REQ_META+=(--release-title "$REQ_TITLE")
|
|
417
|
+
[ -n "$REQ_CT" ] && REQ_META+=(--change-type "$REQ_CT")
|
|
360
418
|
for PNG in "${SHOTS[@]}"; do
|
|
361
419
|
# The folder is the (SRS) requirement id, the basename is the AC
|
|
362
420
|
# slug (ACn-…). Upload as <srs-req>-<slug>.png so the reviewer can
|
|
@@ -367,6 +425,7 @@ jobs:
|
|
|
367
425
|
bash scripts/upload-evidence.sh \
|
|
368
426
|
{{PROJECT_SLUG}} "$REQ" screenshot "$NAMED" \
|
|
369
427
|
--category test_report ${FLAGS} --release "$REQ" \
|
|
428
|
+
"${REQ_META[@]}" \
|
|
370
429
|
|| echo "::warning::evidence screenshot upload failed: ${PNG} -> ${REQ}"
|
|
371
430
|
done
|
|
372
431
|
done
|
|
@@ -110,16 +110,47 @@ jobs:
|
|
|
110
110
|
FLAGS="${FLAGS} --create-release-if-missing --environment uat"
|
|
111
111
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
112
112
|
|
|
113
|
+
# Derive change_type for the bare-date (housekeeping) DERIVED_RELEASE:
|
|
114
|
+
# the prefix of the most recent commit's subject. No-op for tracked
|
|
115
|
+
# releases — they get per-REQ derivation in the loop below.
|
|
116
|
+
DERIVED_CT=$(git log -1 --pretty=%s 2>/dev/null \
|
|
117
|
+
| grep -oE '^(feat|fix|refactor|perf|chore|docs|ci|build|test|compliance|revert)' \
|
|
118
|
+
| head -1 || true)
|
|
119
|
+
DERIVED_META=()
|
|
120
|
+
[ -n "$DERIVED_CT" ] && DERIVED_META+=(--change-type "$DERIVED_CT")
|
|
121
|
+
|
|
113
122
|
# Upload compliance docs (planning category)
|
|
114
123
|
for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md compliance/test-summary-report.md; do
|
|
115
124
|
if [ -f "$DOC" ]; then
|
|
116
125
|
echo "Uploading: $(basename "$DOC")"
|
|
117
126
|
bash scripts/upload-evidence.sh \
|
|
118
127
|
{{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
|
|
119
|
-
--category planning ${FLAGS} --release "${DERIVED_RELEASE}"
|
|
128
|
+
--category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
129
|
+
"${DERIVED_META[@]}" \
|
|
130
|
+
|| echo "Warning: Failed to upload $(basename "$DOC")"
|
|
120
131
|
fi
|
|
121
132
|
done
|
|
122
133
|
|
|
134
|
+
# Helper: emit a `--release-title …` `--change-type …` pair for a given
|
|
135
|
+
# REQ, derived from its pending release-ticket H1 and the most recent
|
|
136
|
+
# commit attributed to that REQ. Empty pair when neither is available.
|
|
137
|
+
req_meta_args() {
|
|
138
|
+
local REQ="$1"; local TITLE=""; local CT=""
|
|
139
|
+
for FILE in "compliance/pending-releases/RELEASE-TICKET-${REQ}.md" \
|
|
140
|
+
"compliance/approved-releases/RELEASE-TICKET-${REQ}.md"; do
|
|
141
|
+
if [ -f "$FILE" ]; then
|
|
142
|
+
TITLE=$(grep -m1 '^# ' "$FILE" \
|
|
143
|
+
| sed -E 's/^# *(REQ-[0-9]+)?[[:space:]]*[—:-]?[[:space:]]*//')
|
|
144
|
+
break
|
|
145
|
+
fi
|
|
146
|
+
done
|
|
147
|
+
CT=$(git log --grep "\[${REQ}\]\|Ref: ${REQ}" --pretty=%s -1 2>/dev/null \
|
|
148
|
+
| grep -oE '^(feat|fix|refactor|perf|chore|docs|ci|build|test|compliance|revert)' \
|
|
149
|
+
| head -1 || true)
|
|
150
|
+
[ -n "$TITLE" ] && printf -- '--release-title %q ' "$TITLE"
|
|
151
|
+
[ -n "$CT" ] && printf -- '--change-type %q ' "$CT"
|
|
152
|
+
}
|
|
153
|
+
|
|
123
154
|
# Upload release tickets (pending only)
|
|
124
155
|
if [ -d "compliance/pending-releases" ]; then
|
|
125
156
|
for TICKET in compliance/pending-releases/*.md; do
|
|
@@ -130,14 +161,18 @@ jobs:
|
|
|
130
161
|
case "$TICKET_BASE" in
|
|
131
162
|
RELEASE-TICKET-REQ-*)
|
|
132
163
|
TICKET_REQ="${TICKET_BASE#RELEASE-TICKET-}"
|
|
133
|
-
TICKET_OWNER="$TICKET_REQ"; TICKET_RELEASE="$TICKET_REQ"
|
|
164
|
+
TICKET_OWNER="$TICKET_REQ"; TICKET_RELEASE="$TICKET_REQ"
|
|
165
|
+
TICKET_META_ARGS=$(req_meta_args "$TICKET_REQ") ;;
|
|
134
166
|
*)
|
|
135
|
-
TICKET_OWNER="_compliance-docs"; TICKET_RELEASE="$DERIVED_RELEASE"
|
|
167
|
+
TICKET_OWNER="_compliance-docs"; TICKET_RELEASE="$DERIVED_RELEASE"
|
|
168
|
+
TICKET_META_ARGS="" ;;
|
|
136
169
|
esac
|
|
137
170
|
echo "Uploading: $(basename "$TICKET") -> release ${TICKET_RELEASE}"
|
|
138
|
-
bash scripts/upload-evidence.sh \
|
|
139
|
-
{{PROJECT_SLUG}} "${TICKET_OWNER}" compliance_document "$TICKET" \
|
|
140
|
-
--category release_artifact ${FLAGS} --release "${TICKET_RELEASE}"
|
|
171
|
+
eval "bash scripts/upload-evidence.sh \
|
|
172
|
+
{{PROJECT_SLUG}} \"${TICKET_OWNER}\" compliance_document \"$TICKET\" \
|
|
173
|
+
--category release_artifact ${FLAGS} --release \"${TICKET_RELEASE}\" \
|
|
174
|
+
${TICKET_META_ARGS}" \
|
|
175
|
+
|| echo "Warning: Failed to upload $(basename "$TICKET")"
|
|
141
176
|
done
|
|
142
177
|
fi
|
|
143
178
|
|
|
@@ -165,12 +200,15 @@ jobs:
|
|
|
165
200
|
echo "Warning: pending ticket for ${REQ_ID} but no ${REQ_DIR} on disk"
|
|
166
201
|
continue
|
|
167
202
|
fi
|
|
203
|
+
REQ_META_ARGS=$(req_meta_args "$REQ_ID")
|
|
168
204
|
for ARTIFACT in "$REQ_DIR"*.md; do
|
|
169
205
|
[ -f "$ARTIFACT" ] || continue
|
|
170
206
|
echo "Uploading: ${REQ_ID}/$(basename "$ARTIFACT")"
|
|
171
|
-
bash scripts/upload-evidence.sh \
|
|
172
|
-
{{PROJECT_SLUG}} "${REQ_ID}" compliance_document "$ARTIFACT" \
|
|
173
|
-
--category planning ${FLAGS} --release "${REQ_ID}"
|
|
207
|
+
eval "bash scripts/upload-evidence.sh \
|
|
208
|
+
{{PROJECT_SLUG}} \"${REQ_ID}\" compliance_document \"$ARTIFACT\" \
|
|
209
|
+
--category planning ${FLAGS} --release \"${REQ_ID}\" \
|
|
210
|
+
${REQ_META_ARGS}" \
|
|
211
|
+
|| echo "Warning: Failed to upload $(basename "$ARTIFACT")"
|
|
174
212
|
done
|
|
175
213
|
done
|
|
176
214
|
fi
|
|
@@ -196,20 +196,61 @@ jobs:
|
|
|
196
196
|
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
|
|
197
197
|
|
|
198
198
|
- name: Sync known requirements from RTM
|
|
199
|
+
env:
|
|
200
|
+
GH_TOKEN: ${{ github.token }}
|
|
199
201
|
run: |
|
|
200
|
-
if [ -f "compliance/RTM.md" ]; then
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
if [ ! -f "compliance/RTM.md" ]; then exit 0; fi
|
|
203
|
+
|
|
204
|
+
# Per REQ row in RTM we look up: title (release-ticket H1, else the
|
|
205
|
+
# linked issue's title via `gh`), and risk_class (the first LOW /
|
|
206
|
+
# MEDIUM / HIGH / CRITICAL token in the row). Both are optional —
|
|
207
|
+
# the portal renders gracefully with nulls. The portal endpoint
|
|
208
|
+
# accepts either a bare string[] (legacy) or rich rows.
|
|
209
|
+
REQS_JSON='[]'
|
|
210
|
+
while IFS= read -r REQ; do
|
|
211
|
+
[ -z "$REQ" ] && continue
|
|
212
|
+
ROW=$(grep -m1 -E "^\| ${REQ} " compliance/RTM.md || true)
|
|
213
|
+
TITLE=""
|
|
214
|
+
for FILE in "compliance/pending-releases/RELEASE-TICKET-${REQ}.md" \
|
|
215
|
+
"compliance/approved-releases/RELEASE-TICKET-${REQ}.md"; do
|
|
216
|
+
if [ -f "$FILE" ]; then
|
|
217
|
+
TITLE=$(grep -m1 '^# ' "$FILE" \
|
|
218
|
+
| sed -E 's/^# *(REQ-[0-9]+)?[[:space:]]*[—:-]?[[:space:]]*//')
|
|
219
|
+
break
|
|
220
|
+
fi
|
|
221
|
+
done
|
|
222
|
+
if [ -z "$TITLE" ] && [ -n "$ROW" ]; then
|
|
223
|
+
ISSUE_NUM=$(echo "$ROW" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
|
|
224
|
+
if [ -n "$ISSUE_NUM" ]; then
|
|
225
|
+
TITLE=$(gh issue view "$ISSUE_NUM" --json title --jq .title 2>/dev/null || true)
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
RISK=""
|
|
229
|
+
if [ -n "$ROW" ]; then
|
|
230
|
+
RISK=$(echo "$ROW" | grep -ioE '\b(LOW|MEDIUM|HIGH|CRITICAL)\b' | head -1 \
|
|
231
|
+
| tr 'a-z' 'A-Z')
|
|
211
232
|
fi
|
|
233
|
+
REQS_JSON=$(echo "$REQS_JSON" | jq \
|
|
234
|
+
--arg id "$REQ" --arg title "$TITLE" --arg risk "$RISK" \
|
|
235
|
+
'. + [{
|
|
236
|
+
id: $id,
|
|
237
|
+
title: (if $title == "" then null else $title end),
|
|
238
|
+
riskClass: (if $risk == "" then null else $risk end)
|
|
239
|
+
}]')
|
|
240
|
+
done < <(grep -oE 'REQ-[0-9]+' compliance/RTM.md | sort -t- -k2 -n -u)
|
|
241
|
+
|
|
242
|
+
COUNT=$(echo "$REQS_JSON" | jq length)
|
|
243
|
+
if [ "$COUNT" = "0" ]; then
|
|
244
|
+
echo "No REQ-XXX rows in RTM.md — skipping sync"
|
|
245
|
+
exit 0
|
|
212
246
|
fi
|
|
247
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
248
|
+
-X PATCH "${BASE}/api/ci/projects/{{PROJECT_SLUG}}/known-requirements" \
|
|
249
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
250
|
+
-H "Content-Type: application/json" \
|
|
251
|
+
-d "{\"requirements\": ${REQS_JSON}}")
|
|
252
|
+
echo "known_requirements sync: HTTP ${HTTP_CODE}"
|
|
253
|
+
echo "Synced ${COUNT} requirements from RTM.md (titles + risk_class)"
|
|
213
254
|
|
|
214
255
|
upload-evidence:
|
|
215
256
|
name: Upload Evidence
|