@metasession.co/devaudit-cli 0.1.12 → 0.1.14
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 +72 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/5-deploy-main.md +9 -0
- package/sdlc/files/_common/implementing-an-sdlc-issue.md +12 -5
- package/sdlc/files/_common/scripts/close-out-release.sh +143 -0
- package/sdlc/files/ci/close-out-release.yml.template +96 -0
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.14",
|
|
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.14",
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"consola": "^3.2.3",
|
|
39
39
|
"env-paths": "^3.0.0",
|
|
@@ -151,6 +151,15 @@ If the smoke results look wrong or a manual verification fails, click **Reject**
|
|
|
151
151
|
|
|
152
152
|
### Step 6: Finalize Compliance (Tracked Requirements Only)
|
|
153
153
|
|
|
154
|
+
> **Automated path (preferred).** Run the synced helper instead of doing this by hand — it flips the ticket Status → `RELEASED` (+ backlinks the release PR and records the sign-off), flips the matching `RTM.md` row, and `git mv`s the ticket to `approved-releases/`, then stages the changes for you to commit:
|
|
155
|
+
>
|
|
156
|
+
> ```bash
|
|
157
|
+
> ./scripts/close-out-release.sh REQ-XXX --release-pr <release-PR-#>
|
|
158
|
+
> git add -A && git commit -m "docs(compliance): close out REQ-XXX release ticket (RELEASED)" && git push origin develop
|
|
159
|
+
> ```
|
|
160
|
+
>
|
|
161
|
+
> It is **idempotent** (a no-op if already closed out) and, when `DEVAUDIT_API_KEY` + `DEVAUDIT_BASE_URL` are set, **refuses** unless the portal reports the release as `released` (so you can't flip the local tree ahead of the Production approval). The `close-out-release.yml` workflow runs the same script automatically when the portal marks a release released (and is `workflow_dispatch`-able for catch-up). The manual steps below are the fallback / reference for what the script does.
|
|
162
|
+
|
|
154
163
|
```bash
|
|
155
164
|
mv compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md compliance/approved-releases/
|
|
156
165
|
```
|
|
@@ -36,23 +36,30 @@ For typo fixes, formatting changes, dependency bumps, and other zero-risk chores
|
|
|
36
36
|
|
|
37
37
|
If you're not sure whether your change is trivial, treat it as non-trivial (cheaper than discovering mid-PR that an auditor needs evidence).
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Default mode: the `sdlc-implementer` skill
|
|
40
40
|
|
|
41
|
-
The [`sdlc-implementer`](#skills-inventory) skill
|
|
41
|
+
The [`sdlc-implementer`](#skills-inventory) skill is the **default way to implement a tracked change** — it is shipped and synced into this repo at `.claude/skills/sdlc-implementer/`. Give it one GitHub issue and the whole walkthrough below collapses to:
|
|
42
42
|
|
|
43
43
|
```text
|
|
44
44
|
> Implement issue #N under the SDLC.
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
It runs Phases 1–4 unattended (with a plan-approval pause for HIGH/CRITICAL risk, or always-on via `--require-plan-approval`): classify risk, assign the next `REQ-XXX`, write the implementation plan, update `RTM.md`, implement, delegate all end-to-end / visual test work to [`e2e-test-engineer`](#skills-inventory), run the gates, compile evidence, open the PR, and submit for UAT review on the portal. It then **halts** at the UAT gate. After a reviewer approves on the portal:
|
|
48
48
|
|
|
49
49
|
```text
|
|
50
50
|
> Resume REQ-XXX.
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
It runs Phase 5: merge, monitor post-deploy, confirm production smoke evidence, advance the release. If changes are requested at UAT instead of approval, it addresses them and re-submits for UAT re-review. It **refuses** issues that decompose into multiple requirements (split them first).
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
**When it is NOT used:**
|
|
56
|
+
|
|
57
|
+
- **Trivial / housekeeping changes** — see the escape hatch above. Docs, formatting, dependency bumps, CI tweaks (`docs:` / `chore:` / `ci:` …) don't need a requirement and don't go through the skill. (Note: `feat` / `fix` / `refactor` / `perf` commits **do** require a `[REQ-XXX]` / `Ref: REQ-XXX` and are rejected by commitlint + `validate-commits.sh` without one.)
|
|
58
|
+
- **Stage-1 planning in isolation, or e2e test work alone** — run the manual walkthrough / invoke `e2e-test-engineer` directly.
|
|
59
|
+
- **Cross-issue refactors** spanning multiple `REQ-XXX` scopes — out of the one-issue contract.
|
|
60
|
+
- **When the orchestration can't apply** (unusual repo state, partial work mid-stream) — fall back to the manual walkthrough below.
|
|
61
|
+
|
|
62
|
+
The manual walkthrough below is the **operational reference** for exactly what the skill does at each stage — and the fallback when the skill isn't the right fit. (For an audience-level walkthrough with sample AI prompts, see the portal's [`implementing-an-sdlc-issue.md`](https://github.com/metasession-dev/devaudit/blob/main/docs/implementing-an-sdlc-issue.md).)
|
|
56
63
|
|
|
57
64
|
---
|
|
58
65
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# close-out-release.sh — Reconcile the local compliance tree after a release
|
|
3
|
+
# is marked `released` on the DevAudit portal.
|
|
4
|
+
#
|
|
5
|
+
# For one requirement it: flips the release ticket Status -> RELEASED (backlinks
|
|
6
|
+
# the release PR + records the sign-off), flips the matching compliance/RTM.md
|
|
7
|
+
# row -> RELEASED, and moves the ticket from compliance/pending-releases/ to
|
|
8
|
+
# compliance/approved-releases/. It stages the changes but does NOT commit — the
|
|
9
|
+
# caller (the close-out workflow, or a human) commits/opens the PR.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ./scripts/close-out-release.sh <REQ-ID> [--release-pr <url-or-number>]
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
# ./scripts/close-out-release.sh REQ-046 --release-pr 138
|
|
16
|
+
#
|
|
17
|
+
# Optional environment (portal safety check — recommended in CI):
|
|
18
|
+
# DEVAUDIT_API_KEY + DEVAUDIT_BASE_URL — when both are set, the script
|
|
19
|
+
# confirms the portal reports the release as `released` before flipping
|
|
20
|
+
# anything, refusing otherwise (prevents local "RELEASED" while the portal
|
|
21
|
+
# is still at prod_review). When unset, it warns and proceeds (manual mode).
|
|
22
|
+
#
|
|
23
|
+
# Idempotent: if the ticket is already in approved-releases/ with Status
|
|
24
|
+
# RELEASED (and the RTM row already RELEASED), it exits 0 as a no-op.
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
REQ_ID="${1:-}"
|
|
29
|
+
RELEASE_PR=""
|
|
30
|
+
shift || true
|
|
31
|
+
while [ $# -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--release-pr) RELEASE_PR="${2:-}"; shift 2 ;;
|
|
34
|
+
*) echo "Unknown option: $1" >&2; exit 2 ;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if ! printf '%s' "$REQ_ID" | grep -qE '^REQ-[0-9]{3,}$'; then
|
|
39
|
+
echo "Usage: $0 <REQ-ID> [--release-pr <url-or-number>] (REQ-ID like REQ-046)" >&2
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
PENDING="compliance/pending-releases/RELEASE-TICKET-${REQ_ID}.md"
|
|
44
|
+
APPROVED_DIR="compliance/approved-releases"
|
|
45
|
+
APPROVED="${APPROVED_DIR}/RELEASE-TICKET-${REQ_ID}.md"
|
|
46
|
+
RTM="compliance/RTM.md"
|
|
47
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
48
|
+
|
|
49
|
+
# ── Optional portal safety check ─────────────────────────────────────────────
|
|
50
|
+
if [ -n "${DEVAUDIT_API_KEY:-}" ] && [ -n "${DEVAUDIT_BASE_URL:-}" ]; then
|
|
51
|
+
BASE="${DEVAUDIT_BASE_URL%/}"
|
|
52
|
+
SLUG="$(jq -r '.devaudit.project_slug // .project_slug // empty' sdlc-config.json 2>/dev/null || true)"
|
|
53
|
+
STATUS="$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
54
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${SLUG}&versionPrefix=${REQ_ID}" 2>/dev/null \
|
|
55
|
+
| jq -r '.latest.status // empty' 2>/dev/null || true)"
|
|
56
|
+
if [ -n "$STATUS" ] && [ "$STATUS" != "released" ]; then
|
|
57
|
+
echo "::error::Portal reports ${REQ_ID} as '${STATUS}', not 'released'. Refusing to close out." >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
[ "$STATUS" = "released" ] && echo "Portal confirms ${REQ_ID} is released."
|
|
61
|
+
else
|
|
62
|
+
echo "::warning::DEVAUDIT_API_KEY/DEVAUDIT_BASE_URL not set — skipping portal status check (manual mode)."
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# ── Idempotency ──────────────────────────────────────────────────────────────
|
|
66
|
+
if [ ! -f "$PENDING" ] && [ -f "$APPROVED" ]; then
|
|
67
|
+
if grep -qE '^\*\*Status:\*\*[[:space:]]*RELEASED' "$APPROVED"; then
|
|
68
|
+
echo "${REQ_ID} already closed out (ticket in approved-releases/, Status RELEASED) — no-op."
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
if [ ! -f "$PENDING" ] && [ ! -f "$APPROVED" ]; then
|
|
73
|
+
echo "::error::No RELEASE-TICKET-${REQ_ID}.md in pending-releases/ or approved-releases/." >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# ── Move ticket pending -> approved (if still pending) ───────────────────────
|
|
78
|
+
mkdir -p "$APPROVED_DIR"
|
|
79
|
+
if [ -f "$PENDING" ]; then
|
|
80
|
+
git mv "$PENDING" "$APPROVED" 2>/dev/null || mv "$PENDING" "$APPROVED"
|
|
81
|
+
echo "Moved ticket -> ${APPROVED}"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Flip ticket Status + backlink + sign-off (edit AFTER the move, then stage —
|
|
85
|
+
# avoids leaving content edits unstaged behind a rename) ────────────────────
|
|
86
|
+
TMP="$(mktemp)"
|
|
87
|
+
SIGN_OFF="**Sign-off (dual-actor):** UAT approved + Production approved on the DevAudit portal (\`released\`); post-deploy production smoke evidence captured. Closed out ${TODAY}."
|
|
88
|
+
PR_LINE=""
|
|
89
|
+
if [ -n "$RELEASE_PR" ]; then
|
|
90
|
+
case "$RELEASE_PR" in
|
|
91
|
+
http*) PR_LINE="**Release PR:** ${RELEASE_PR}" ;;
|
|
92
|
+
*) PR_LINE="**Release PR:** #${RELEASE_PR}" ;;
|
|
93
|
+
esac
|
|
94
|
+
fi
|
|
95
|
+
awk -v signoff="$SIGN_OFF" -v prline="$PR_LINE" '
|
|
96
|
+
BEGIN { status_done=0; signoff_added=0 }
|
|
97
|
+
# Flip the first Status line.
|
|
98
|
+
/^\*\*Status:\*\*/ && status_done==0 { print "**Status:** RELEASED"; status_done=1; next }
|
|
99
|
+
# Replace a placeholder Release PR line if present and a PR was supplied.
|
|
100
|
+
/^\*\*Release PR:\*\*/ && prline!="" { print prline; next }
|
|
101
|
+
{ print }
|
|
102
|
+
# After the DevAudit Release line, append the sign-off (once) if not already present.
|
|
103
|
+
/^\*\*DevAudit Release:\*\*/ && signoff_added==0 {
|
|
104
|
+
print signoff; signoff_added=1
|
|
105
|
+
}
|
|
106
|
+
' "$APPROVED" > "$TMP"
|
|
107
|
+
# Only add a Release PR line if there was no existing one to replace.
|
|
108
|
+
if [ -n "$PR_LINE" ] && ! grep -qE '^\*\*Release PR:\*\*' "$TMP"; then
|
|
109
|
+
awk -v prline="$PR_LINE" '
|
|
110
|
+
/^\*\*DevAudit Release:\*\*/ { print prline }
|
|
111
|
+
{ print }
|
|
112
|
+
' "$TMP" > "${TMP}.2" && mv "${TMP}.2" "$TMP"
|
|
113
|
+
fi
|
|
114
|
+
mv "$TMP" "$APPROVED"
|
|
115
|
+
git add "$APPROVED" 2>/dev/null || true
|
|
116
|
+
echo "Ticket Status -> RELEASED."
|
|
117
|
+
|
|
118
|
+
# ── Flip the RTM row -> RELEASED (preserve any parenthetical note) ───────────
|
|
119
|
+
if [ -f "$RTM" ] && grep -qE "^\| ${REQ_ID} " "$RTM"; then
|
|
120
|
+
awk -v req="$REQ_ID" '
|
|
121
|
+
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 }
|
|
125
|
+
}
|
|
126
|
+
$0 ~ ("^\\| " req " ") && statuscol>0 {
|
|
127
|
+
cell=$statuscol
|
|
128
|
+
note=""
|
|
129
|
+
if (match(cell, /\(/)) note=substr(cell, RSTART) # preserve any " (requirement note)"
|
|
130
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", note)
|
|
131
|
+
$statuscol = (note != "" ? " RELEASED " note " " : " RELEASED ")
|
|
132
|
+
print; next
|
|
133
|
+
}
|
|
134
|
+
{ print }
|
|
135
|
+
' "$RTM" > "$TMP" && mv "$TMP" "$RTM"
|
|
136
|
+
git add "$RTM" 2>/dev/null || true
|
|
137
|
+
echo "RTM row ${REQ_ID} -> RELEASED."
|
|
138
|
+
else
|
|
139
|
+
echo "::warning::No RTM row for ${REQ_ID} in ${RTM} — skipped RTM flip."
|
|
140
|
+
rm -f "$TMP"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
echo "Close-out staged for ${REQ_ID}. Commit + open a PR to develop to land it."
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Release close-out — reconcile the local compliance tree after a release is
|
|
2
|
+
# marked `released` on the DevAudit portal.
|
|
3
|
+
#
|
|
4
|
+
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
|
|
5
|
+
# Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
|
|
6
|
+
#
|
|
7
|
+
# Triggers:
|
|
8
|
+
# - repository_dispatch (type: release-closed) — sent by the DevAudit portal
|
|
9
|
+
# when it advances a release to `released`. client_payload: { release,
|
|
10
|
+
# release_pr }.
|
|
11
|
+
# - workflow_dispatch — manual catch-up for an already-released requirement.
|
|
12
|
+
#
|
|
13
|
+
# It runs scripts/close-out-release.sh for the requirement (flip ticket +
|
|
14
|
+
# RTM, move ticket to approved-releases/), then opens a PR to develop. The
|
|
15
|
+
# script is idempotent, so a no-op produces no PR.
|
|
16
|
+
|
|
17
|
+
name: Release Close-out
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
inputs:
|
|
22
|
+
release:
|
|
23
|
+
description: 'REQ-XXX to close out (e.g. REQ-046)'
|
|
24
|
+
required: true
|
|
25
|
+
release_pr:
|
|
26
|
+
description: 'Release PR number or URL to backlink (optional)'
|
|
27
|
+
required: false
|
|
28
|
+
repository_dispatch:
|
|
29
|
+
types: [release-closed]
|
|
30
|
+
|
|
31
|
+
permissions:
|
|
32
|
+
contents: write
|
|
33
|
+
pull-requests: write
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
36
|
+
close-out:
|
|
37
|
+
name: Close out release
|
|
38
|
+
runs-on: {{RUNNER}}
|
|
39
|
+
env:
|
|
40
|
+
GH_TOKEN: ${{ github.token }}
|
|
41
|
+
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
with:
|
|
45
|
+
ref: develop
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
|
|
48
|
+
- name: Resolve inputs
|
|
49
|
+
id: in
|
|
50
|
+
run: |
|
|
51
|
+
REQ="${{ github.event.inputs.release }}${{ github.event.client_payload.release }}"
|
|
52
|
+
PR="${{ github.event.inputs.release_pr }}${{ github.event.client_payload.release_pr }}"
|
|
53
|
+
if ! printf '%s' "$REQ" | grep -qE '^REQ-[0-9]{3,}$'; then
|
|
54
|
+
echo "::error::No valid REQ-XXX supplied (inputs.release / client_payload.release)."
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
echo "req=${REQ}" >> "$GITHUB_OUTPUT"
|
|
58
|
+
echo "pr=${PR}" >> "$GITHUB_OUTPUT"
|
|
59
|
+
# Portal base URL for the safety check (read from sdlc-config.json).
|
|
60
|
+
BASE=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
61
|
+
echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
|
|
62
|
+
|
|
63
|
+
- name: Run close-out
|
|
64
|
+
id: closeout
|
|
65
|
+
run: |
|
|
66
|
+
chmod +x scripts/close-out-release.sh 2>/dev/null || true
|
|
67
|
+
ARGS="${{ steps.in.outputs.req }}"
|
|
68
|
+
[ -n "${{ steps.in.outputs.pr }}" ] && ARGS="${ARGS} --release-pr ${{ steps.in.outputs.pr }}"
|
|
69
|
+
bash scripts/close-out-release.sh ${ARGS}
|
|
70
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
71
|
+
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
72
|
+
else
|
|
73
|
+
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
74
|
+
echo "Nothing to close out (idempotent no-op) — no PR opened."
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
- name: Open close-out PR
|
|
78
|
+
if: steps.closeout.outputs.changed == 'true'
|
|
79
|
+
run: |
|
|
80
|
+
REQ="${{ steps.in.outputs.req }}"
|
|
81
|
+
BRANCH="chore/close-out-${REQ}"
|
|
82
|
+
git config user.name "devaudit-bot"
|
|
83
|
+
git config user.email "devaudit-bot@users.noreply.github.com"
|
|
84
|
+
git checkout -b "$BRANCH"
|
|
85
|
+
git add -A
|
|
86
|
+
git commit -m "docs(compliance): close out ${REQ} release ticket (RELEASED)
|
|
87
|
+
|
|
88
|
+
Automated reconciliation after the DevAudit portal marked ${REQ} released.
|
|
89
|
+
Ticket Status -> RELEASED + moved to approved-releases/; RTM row -> RELEASED.
|
|
90
|
+
|
|
91
|
+
Ref: ${REQ}"
|
|
92
|
+
git push -u origin "$BRANCH" --force-with-lease
|
|
93
|
+
gh pr create --base develop --head "$BRANCH" \
|
|
94
|
+
--title "docs(compliance): close out ${REQ} release ticket (RELEASED)" \
|
|
95
|
+
--body "Automated close-out — the DevAudit portal marked **${REQ}** \`released\`. This moves the ticket to \`approved-releases/\`, flips its Status and the RTM row to \`RELEASED\`. Review + merge to develop, then promote to main." \
|
|
96
|
+
|| echo "::warning::PR may already exist for ${BRANCH}."
|