@metasession.co/devaudit-cli 0.1.1 → 0.1.3

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.
Files changed (69) hide show
  1. package/README.md +13 -10
  2. package/dist/index.js +17 -5
  3. package/dist/index.js.map +1 -1
  4. package/package.json +9 -5
  5. package/scripts/upload-evidence.sh +225 -0
  6. package/sdlc/CLAUDE.md +73 -0
  7. package/sdlc/HOST_ADAPTER.md +127 -0
  8. package/sdlc/SKILLS.md +137 -0
  9. package/sdlc/STACK_ADAPTER.md +130 -0
  10. package/sdlc/ai-rules/INSTRUCTIONS-SDLC.md +172 -0
  11. package/sdlc/ai-rules/README.md +103 -0
  12. package/sdlc/ai-rules/SDLC_RULES.md +584 -0
  13. package/sdlc/ai-rules/claude/CLAUDE.md +192 -0
  14. package/sdlc/ai-rules/cursor/.cursorrules +167 -0
  15. package/sdlc/ai-rules/windsurf/.windsurfrules +167 -0
  16. package/sdlc/article.md +219 -0
  17. package/sdlc/files/_common/0-project-setup.md +410 -0
  18. package/sdlc/files/_common/1-plan-requirement.md +381 -0
  19. package/sdlc/files/_common/2-implement-and-test.md +276 -0
  20. package/sdlc/files/_common/3-compile-evidence.md +603 -0
  21. package/sdlc/files/_common/4-submit-for-review.md +362 -0
  22. package/sdlc/files/_common/5-deploy-main.md +251 -0
  23. package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +169 -0
  24. package/sdlc/files/_common/README_TEMPLATE.md +441 -0
  25. package/sdlc/files/_common/Test_Architecture.md +461 -0
  26. package/sdlc/files/_common/Test_Plan_TEMPLATE.md +311 -0
  27. package/sdlc/files/_common/Test_Policy.md +277 -0
  28. package/sdlc/files/_common/Test_Strategy.md +359 -0
  29. package/sdlc/files/_common/github/ISSUE_TEMPLATE/bug.yml +75 -0
  30. package/sdlc/files/_common/github/ISSUE_TEMPLATE/config.yml +11 -0
  31. package/sdlc/files/_common/github/ISSUE_TEMPLATE/requirement.yml +75 -0
  32. package/sdlc/files/_common/github/ISSUE_TEMPLATE/task.yml +48 -0
  33. package/sdlc/files/_common/github/pull_request_template.md +69 -0
  34. package/sdlc/files/_common/implementing-an-sdlc-issue.md +413 -0
  35. package/sdlc/files/_common/scripts/derive-release-version.sh +40 -0
  36. package/sdlc/files/_common/scripts/derive-release-version.test.sh +98 -0
  37. package/sdlc/files/_common/scripts/submit-for-uat-review.sh +162 -0
  38. package/sdlc/files/_common/scripts/validate-commits.sh +83 -0
  39. package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +202 -0
  40. package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +202 -0
  41. package/sdlc/files/_common/skills/_schema/skill.schema.json +36 -0
  42. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +254 -0
  43. package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +244 -0
  44. package/sdlc/files/_common/skills/e2e-test-engineer/references/evidence.ts +40 -0
  45. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +189 -0
  46. package/sdlc/files/_common/skills/sdlc-implementer/references/call-graph.md +64 -0
  47. package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +192 -0
  48. package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +81 -0
  49. package/sdlc/files/ci/check-release-approval.yml.template +201 -0
  50. package/sdlc/files/ci/ci-status-fallback.yml.template +41 -0
  51. package/sdlc/files/ci/ci.yml.template +390 -0
  52. package/sdlc/files/ci/compliance-evidence.yml.template +161 -0
  53. package/sdlc/files/ci/compliance-validation.yml.template +34 -0
  54. package/sdlc/files/ci/post-deploy-prod.yml.template +159 -0
  55. package/sdlc/files/ci/python/ci.yml.template +335 -0
  56. package/sdlc/files/hosts/_schema/adapter.schema.json +103 -0
  57. package/sdlc/files/hosts/railway/adapter.json +32 -0
  58. package/sdlc/files/sdlc-config.example.json +74 -0
  59. package/sdlc/files/stacks/_schema/adapter.schema.json +151 -0
  60. package/sdlc/files/stacks/node/adapter.json +54 -0
  61. package/sdlc/files/stacks/node/hooks/.prettierrc.json +9 -0
  62. package/sdlc/files/stacks/node/hooks/commit-msg +7 -0
  63. package/sdlc/files/stacks/node/hooks/commitlint.config.mjs +64 -0
  64. package/sdlc/files/stacks/node/hooks/lint-staged.config.mjs +16 -0
  65. package/sdlc/files/stacks/node/hooks/pre-commit +13 -0
  66. package/sdlc/files/stacks/node/hooks/pre-push +15 -0
  67. package/sdlc/files/stacks/node/scripts/check-requirement-jsdoc.sh +54 -0
  68. package/sdlc/files/stacks/python/adapter.json +36 -0
  69. package/sdlc/files/stacks/python/hooks/.pre-commit-config.yaml +51 -0
@@ -0,0 +1,161 @@
1
+ # Compliance Evidence Upload — triggers on compliance-only changes
2
+ #
3
+ # Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
4
+ # Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
5
+ #
6
+ # Uploads compliance docs to DevAudit without running quality gates.
7
+ # Complements ci.yml which skips compliance-only pushes via paths-ignore.
8
+ # In sdlc-v1.22.0+ this workflow is the load-bearing path for Stage 3
9
+ # Step 9 (push-early): every commit on develop that touches compliance/
10
+ # fires this and uploads to DevAudit immediately so destination breakage
11
+ # (dead alias, missing token, schema drift) surfaces in seconds, not at
12
+ # the end of Stage 3.
13
+ #
14
+ # Auth: project-scoped API key (uploader role). Issue from
15
+ # DevAudit → Project Settings → API Keys. Set as repo Secret
16
+ # DEVAUDIT_API_KEY. Base URL is read from sdlc-config.json
17
+ # devaudit.base_url; falls back to repo Variable DEVAUDIT_BASE_URL
18
+ # (deprecated, removed in v1.23.0).
19
+
20
+ name: Compliance Evidence Upload
21
+
22
+ on:
23
+ push:
24
+ branches: [develop]
25
+ paths:
26
+ - 'compliance/**'
27
+
28
+ concurrency:
29
+ group: ${{ github.workflow }}-${{ github.ref }}
30
+ cancel-in-progress: true
31
+
32
+ jobs:
33
+ upload-compliance-evidence:
34
+ name: Upload Compliance Evidence
35
+ runs-on: {{RUNNER}}
36
+ env:
37
+ DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
38
+ DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - name: Resolve DevAudit base URL
43
+ id: resolve
44
+ run: |
45
+ # Prefer sdlc-config.json (visible in PR review) over repo Variable.
46
+ CONFIG_URL=""
47
+ if [ -f sdlc-config.json ]; then
48
+ CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
49
+ fi
50
+ if [ -n "$CONFIG_URL" ]; then
51
+ BASE="$CONFIG_URL"
52
+ echo "Using devaudit.base_url from sdlc-config.json: $BASE"
53
+ elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
54
+ BASE="$DEVAUDIT_BASE_URL_VAR"
55
+ echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url for PR-visible config."
56
+ else
57
+ echo "::warning::No DevAudit base URL configured — skipping evidence upload. Set devaudit.base_url in sdlc-config.json."
58
+ echo "skip=true" >> "$GITHUB_OUTPUT"
59
+ exit 0
60
+ fi
61
+ if [ -z "${DEVAUDIT_API_KEY}" ]; then
62
+ echo "::warning::DEVAUDIT_API_KEY not set — skipping evidence upload."
63
+ echo "skip=true" >> "$GITHUB_OUTPUT"
64
+ exit 0
65
+ fi
66
+ # Pre-flight: confirm destination is reachable. Catches dead aliases / wrong hosts early.
67
+ CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 10 -I "${BASE%/}/" || echo "000")
68
+ case "$CODE" in
69
+ 2*|3*)
70
+ echo "DevAudit reachable at ${BASE} (HTTP ${CODE})"
71
+ echo "skip=false" >> "$GITHUB_OUTPUT"
72
+ echo "BASE=${BASE%/}" >> "$GITHUB_ENV"
73
+ # Export for upload-evidence.sh, which reads $DEVAUDIT_BASE_URL directly.
74
+ echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
75
+ ;;
76
+ *)
77
+ echo "::error::DevAudit base URL ${BASE} returned HTTP ${CODE}. Likely a stale alias or wrong host. Check sdlc-config.json devaudit.base_url."
78
+ exit 1
79
+ ;;
80
+ esac
81
+
82
+ - name: Determine release version
83
+ id: version
84
+ if: steps.resolve.outputs.skip != 'true'
85
+ run: |
86
+ # Release identity is the REQ tag on the latest commit
87
+ # ([REQ-XXX] in subject, Ref: REQ-XXX in body), with a bare-date
88
+ # fallback for housekeeping commits. Both this workflow and
89
+ # ci.yml call the same helper so a single feature converges on
90
+ # one release record regardless of whether the push touched
91
+ # code or docs. See DevAudit #310.
92
+ chmod +x scripts/derive-release-version.sh 2>/dev/null || true
93
+ VERSION=$(./scripts/derive-release-version.sh)
94
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
95
+ echo "Release version: ${VERSION}"
96
+
97
+ - name: Upload compliance documents
98
+ if: steps.resolve.outputs.skip != 'true'
99
+ run: |
100
+ chmod +x scripts/upload-evidence.sh 2>/dev/null || true
101
+ FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
102
+ FLAGS="${FLAGS} --release ${{ steps.version.outputs.version }} --create-release-if-missing"
103
+ FLAGS="${FLAGS} --environment uat"
104
+
105
+ # Upload compliance docs (planning category)
106
+ for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md compliance/test-summary-report.md; do
107
+ if [ -f "$DOC" ]; then
108
+ echo "Uploading: $(basename "$DOC")"
109
+ bash scripts/upload-evidence.sh \
110
+ {{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
111
+ --category planning ${FLAGS} || echo "Warning: Failed to upload $(basename "$DOC")"
112
+ fi
113
+ done
114
+
115
+ # Upload release tickets (pending only)
116
+ if [ -d "compliance/pending-releases" ]; then
117
+ for TICKET in compliance/pending-releases/*.md; do
118
+ [ -f "$TICKET" ] || continue
119
+ echo "Uploading: $(basename "$TICKET")"
120
+ bash scripts/upload-evidence.sh \
121
+ {{PROJECT_SLUG}} _compliance-docs compliance_document "$TICKET" \
122
+ --category release_artifact ${FLAGS} || echo "Warning: Failed to upload $(basename "$TICKET")"
123
+ done
124
+ fi
125
+
126
+ # Upload per-requirement evidence — scoped to requirements with a
127
+ # pending release ticket. Without this scoping every historical
128
+ # compliance/evidence/REQ-*/ folder would be re-uploaded on every
129
+ # run, re-populating the release-requirement matrix with the full
130
+ # project catalogue (DevAudit #135, sibling of #133).
131
+ IN_SCOPE_REQS=()
132
+ if [ -d compliance/pending-releases ]; then
133
+ for TICKET in compliance/pending-releases/RELEASE-TICKET-REQ-*.md; do
134
+ [ -f "$TICKET" ] || continue
135
+ REQ_ID=$(basename "$TICKET" .md | sed 's/^RELEASE-TICKET-//')
136
+ IN_SCOPE_REQS+=("$REQ_ID")
137
+ done
138
+ fi
139
+
140
+ if [ ${#IN_SCOPE_REQS[@]} -eq 0 ]; then
141
+ echo "No pending release tickets found — skipping per-requirement evidence upload"
142
+ else
143
+ echo "In-scope requirements for this release: ${IN_SCOPE_REQS[*]}"
144
+ for REQ_ID in "${IN_SCOPE_REQS[@]}"; do
145
+ REQ_DIR="compliance/evidence/${REQ_ID}/"
146
+ if [ ! -d "$REQ_DIR" ]; then
147
+ echo "Warning: pending ticket for ${REQ_ID} but no ${REQ_DIR} on disk"
148
+ continue
149
+ fi
150
+ for ARTIFACT in "$REQ_DIR"*.md; do
151
+ [ -f "$ARTIFACT" ] || continue
152
+ echo "Uploading: ${REQ_ID}/$(basename "$ARTIFACT")"
153
+ bash scripts/upload-evidence.sh \
154
+ {{PROJECT_SLUG}} "${REQ_ID}" compliance_document "$ARTIFACT" \
155
+ --category planning ${FLAGS} || echo "Warning: Failed to upload $(basename "$ARTIFACT")"
156
+ done
157
+ done
158
+ fi
159
+
160
+ - name: Summary
161
+ run: echo "Compliance evidence uploaded for ${{ steps.version.outputs.version }}"
@@ -0,0 +1,34 @@
1
+ # Compliance Validation — runs on PRs to main only
2
+ #
3
+ # Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
4
+ # Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
5
+ #
6
+ # Validates that compliance artifacts (test-scope, test-plan, RTM entries,
7
+ # release tickets) are complete before merging to main. These artifacts are
8
+ # built up progressively during development, so this check only makes sense
9
+ # when the work is ready to merge — not on every push to develop.
10
+
11
+ name: Compliance Validation
12
+
13
+ on:
14
+ pull_request:
15
+ branches: [main]
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ compliance-validation:
23
+ name: Compliance Validation
24
+ runs-on: {{RUNNER}}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 0
29
+
30
+ - name: Validate compliance artifacts
31
+ run: bash scripts/validate-compliance-artifacts.sh origin/main
32
+
33
+ - name: Validate commit conventions
34
+ run: bash scripts/validate-commits.sh origin/main
@@ -0,0 +1,159 @@
1
+ # Post-deploy production verification (read-only)
2
+ #
3
+ # Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
4
+ # Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
5
+ #
6
+ # Production verification is READ-ONLY.
7
+ # No E2E tests, no database operations, no API mutations.
8
+ #
9
+ # In sdlc-v1.22.0+ the terminal release status is configurable via
10
+ # sdlc-config.json `production_review.terminal_status`:
11
+ # - "prod_review" (default, Option A) — stop at prod_review; human in the
12
+ # portal clicks "Approve Production" then "Mark as Released" for an
13
+ # explicit audit trail. Closes #138.
14
+ # - "released" (Option B) — preserves v1.21.x auto-release behaviour.
15
+
16
+ name: Post-Deploy Production Evidence
17
+
18
+ on:
19
+ push:
20
+ branches: [main]
21
+
22
+ jobs:
23
+ production-evidence:
24
+ name: Production Evidence
25
+ runs-on: {{RUNNER}}
26
+ env:
27
+ DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
28
+ DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
29
+ PROD_URL: ${{ secrets.{{PRODUCTION_URL_SECRET}} }}
30
+ PROJECT_SLUG: {{PROJECT_SLUG}}
31
+ GIT_SHA: ${{ github.sha }}
32
+ CI_RUN: ${{ github.run_id }}
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Resolve DevAudit base URL and post-deploy terminal status
38
+ run: |
39
+ # Prefer sdlc-config.json (visible in PR review) over repo Variable.
40
+ CONFIG_URL=""
41
+ TERMINAL_STATUS="prod_review"
42
+ if [ -f sdlc-config.json ]; then
43
+ CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
44
+ CONFIG_TERMINAL=$(jq -r '.production_review.terminal_status // empty' sdlc-config.json 2>/dev/null || true)
45
+ if [ -n "$CONFIG_TERMINAL" ]; then
46
+ TERMINAL_STATUS="$CONFIG_TERMINAL"
47
+ fi
48
+ fi
49
+ if [ -n "$CONFIG_URL" ]; then
50
+ BASE="$CONFIG_URL"
51
+ echo "Using devaudit.base_url from sdlc-config.json: $BASE"
52
+ elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
53
+ BASE="$DEVAUDIT_BASE_URL_VAR"
54
+ echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url."
55
+ else
56
+ echo "::error::No DevAudit base URL configured. Set devaudit.base_url in sdlc-config.json."
57
+ exit 1
58
+ fi
59
+ if [ -z "${DEVAUDIT_API_KEY}" ]; then
60
+ echo "::error::DEVAUDIT_API_KEY secret must be set."
61
+ exit 1
62
+ fi
63
+ case "$TERMINAL_STATUS" in
64
+ prod_review|released) ;;
65
+ *)
66
+ echo "::error::Invalid production_review.terminal_status '${TERMINAL_STATUS}'. Must be 'prod_review' or 'released'."
67
+ exit 1
68
+ ;;
69
+ esac
70
+ echo "Post-deploy terminal status: ${TERMINAL_STATUS}"
71
+ echo "BASE=${BASE%/}" >> "$GITHUB_ENV"
72
+ # Export for upload-evidence.sh, which reads $DEVAUDIT_BASE_URL directly.
73
+ echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
74
+ echo "TERMINAL_STATUS=${TERMINAL_STATUS}" >> "$GITHUB_ENV"
75
+
76
+ - name: Resolve current release
77
+ id: release
78
+ run: |
79
+ DATE_PREFIX="v$(date +%Y.%m.%d)"
80
+ RESP=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
81
+ "${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${DATE_PREFIX}")
82
+ VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
83
+ if [ -z "$VERSION" ]; then
84
+ VERSION="${DATE_PREFIX}"
85
+ fi
86
+ RELEASE_ID=$(echo "$RESP" | jq -r '.latest.id // empty')
87
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
88
+ echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
89
+ echo "Release version: ${VERSION}"
90
+
91
+ - name: Wait for production deployment
92
+ run: |
93
+ for i in $(seq 1 30); do
94
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/" || echo "000")
95
+ if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
96
+ echo "Production is up (HTTP ${HTTP_CODE})"
97
+ break
98
+ fi
99
+ echo "Attempt ${i}/30: HTTP ${HTTP_CODE} — waiting 10s..."
100
+ sleep 10
101
+ done
102
+
103
+ - name: Production smoke tests (read-only)
104
+ run: |
105
+ echo "=== Production Smoke Tests ==="
106
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/")
107
+ echo "Health check: HTTP ${HTTP_CODE}"
108
+ if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 400 ]; then
109
+ echo "::error::Production health check failed"
110
+ exit 1
111
+ fi
112
+ curl -s -I "${PROD_URL}/" | grep -iE 'x-frame-options|x-content-type|strict-transport|content-security' || true
113
+ echo "=== Smoke Tests Passed ==="
114
+ cat > prod-smoke-results.json << RESULTS_EOF
115
+ {
116
+ "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
117
+ "production_url": "${PROD_URL}",
118
+ "git_sha": "${GIT_SHA}",
119
+ "tests": [
120
+ {"name": "health_check", "status": "passed"},
121
+ {"name": "security_headers", "status": "checked"}
122
+ ]
123
+ }
124
+ RESULTS_EOF
125
+
126
+ - name: Upload production evidence
127
+ run: |
128
+ chmod +x scripts/upload-evidence.sh 2>/dev/null || true
129
+ VERSION="${{ steps.release.outputs.version }}"
130
+ FLAGS="--release ${VERSION} --create-release-if-missing --environment production --category test_report"
131
+ FLAGS="${FLAGS} --git-sha ${GIT_SHA} --ci-run-id ${CI_RUN} --branch main"
132
+ if [ -f prod-smoke-results.json ]; then
133
+ bash scripts/upload-evidence.sh \
134
+ "${PROJECT_SLUG}" "_compliance-docs" "test_report" prod-smoke-results.json \
135
+ ${FLAGS} || echo "Warning: Failed to upload smoke results"
136
+ fi
137
+
138
+ - name: Advance release status (post-deploy)
139
+ run: |
140
+ RELEASE_ID="${{ steps.release.outputs.release_id }}"
141
+ if [ -z "$RELEASE_ID" ]; then
142
+ echo "::warning::No release_id resolved — skipping status patch"
143
+ exit 0
144
+ fi
145
+ curl -s -o /dev/null -w "Release status patch: HTTP %{http_code}\n" \
146
+ -X PATCH "${BASE}/api/ci/releases/${RELEASE_ID}" \
147
+ -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
148
+ -H "Content-Type: application/json" \
149
+ -d "{\"status\":\"${TERMINAL_STATUS}\"}"
150
+ case "$TERMINAL_STATUS" in
151
+ prod_review)
152
+ echo "Release ${{ steps.release.outputs.version }} → prod_review."
153
+ echo "Next: a human in the portal clicks 'Approve Production' (→ prod_approved), then 'Mark as Released' (→ released)."
154
+ echo "Audit trail captures both events with reviewer identity per compliance/risk-register.md."
155
+ ;;
156
+ released)
157
+ echo "Release ${{ steps.release.outputs.version }} → released (Option B: auto-release with no post-deploy human gate)."
158
+ ;;
159
+ esac
@@ -0,0 +1,335 @@
1
+ # CI Pipeline — all gates on every code push to develop (Python stack)
2
+ #
3
+ # Generated by `devaudit install` / `devaudit update` from sdlc-config.json + stacks/python/adapter.json.
4
+ # Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
5
+ #
6
+ # Single consolidated job. Order: install → ruff → mypy → semgrep → pip-audit → pytest → build.
7
+ #
8
+ # PRs to main inherit commit status via branch protection.
9
+ # Compliance validation runs separately on PRs (compliance-validation.yml).
10
+
11
+ name: CI Pipeline
12
+
13
+ on:
14
+ workflow_dispatch:
15
+ push:
16
+ branches: [develop]
17
+ paths-ignore:
18
+ {{PATHS_IGNORE}} - 'sdlc-config.json'
19
+
20
+ concurrency:
21
+ group: ${{ github.workflow }}-${{ github.ref }}
22
+ cancel-in-progress: true
23
+
24
+ jobs:
25
+ quality-gates:
26
+ name: Quality Gates
27
+ runs-on: {{RUNNER}}
28
+
29
+ # `working-directory` lets monorepo / subdir Python projects (e.g. META-AGENT
30
+ # with mission-control-api/pyproject.toml) run gates from the right place
31
+ # without prefixing every command. Defaults to '.' (repo root) when
32
+ # sdlc-config.json doesn't set working_directory.
33
+ defaults:
34
+ run:
35
+ working-directory: {{WORKING_DIRECTORY}}
36
+
37
+ services:
38
+ {{DATABASE_SERVICE}}:
39
+ image: {{DATABASE_IMAGE}}
40
+ ports:
41
+ - {{DATABASE_PORT}}
42
+
43
+ env:
44
+ {{DATABASE_ENV}}
45
+ {{APP_ENV}}
46
+
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: '{{PYTHON_VERSION}}'
53
+ cache: pip
54
+
55
+ - name: Install dependencies
56
+ run: |
57
+ python -m pip install --upgrade pip
58
+ pip install -e ".[dev]"
59
+ pip install semgrep pip-audit build
60
+
61
+ # ── Gate 1: Lint + format (ruff) ──
62
+
63
+ - name: Ruff lint
64
+ run: ruff check {{SOURCE_DIRS}}
65
+
66
+ - name: Ruff format check
67
+ run: ruff format --check {{SOURCE_DIRS}}
68
+
69
+ # ── Gate 2: Type check (mypy --strict) ──
70
+
71
+ - name: Type Check (mypy)
72
+ run: mypy {{SOURCE_DIRS}}
73
+
74
+ # ── Gate 3: SAST (Semgrep) ──
75
+
76
+ - name: SAST Scan
77
+ run: |
78
+ mkdir -p ci-evidence
79
+ semgrep scan --config auto {{SOURCE_DIRS}} \
80
+ --severity ERROR --severity WARNING \
81
+ --json > ci-evidence/sast-results.json 2>&1 || true
82
+ FINDINGS=$(python3 -c "
83
+ import json
84
+ with open('ci-evidence/sast-results.json') as f:
85
+ data = json.load(f)
86
+ print(len(data.get('results', [])))
87
+ " 2>/dev/null || echo "0")
88
+ echo "SAST findings: $FINDINGS"
89
+ BASELINE={{SAST_BASELINE}}
90
+ if [ "$FINDINGS" -gt "$BASELINE" ]; then
91
+ echo "::error::New SAST findings ($FINDINGS > baseline $BASELINE)."
92
+ exit 1
93
+ fi
94
+
95
+ # ── Gate 4: Dependency Audit (pip-audit) ──
96
+
97
+ - name: Dependency Audit
98
+ run: |
99
+ mkdir -p ci-evidence
100
+ pip-audit --format=json > ci-evidence/dependency-audit.json 2>&1 || true
101
+ ACCEPTED="{{ACCEPTED_DEP_RISKS}}"
102
+ UNACCEPTED=$(python3 -c "
103
+ import json
104
+ with open('ci-evidence/dependency-audit.json') as f:
105
+ data = json.load(f)
106
+ accepted = set('${ACCEPTED}'.split()) if '${ACCEPTED}' else set()
107
+ deps = data.get('dependencies', []) if isinstance(data, dict) else data
108
+ issues = []
109
+ for dep in deps:
110
+ name = dep.get('name', '') if isinstance(dep, dict) else ''
111
+ vulns = dep.get('vulns', []) if isinstance(dep, dict) else []
112
+ if vulns and name and name not in accepted:
113
+ issues.append(name)
114
+ print(len(issues))
115
+ " 2>/dev/null || echo "unknown")
116
+ echo "Unaccepted vulnerable packages: $UNACCEPTED"
117
+ if [ "$UNACCEPTED" != "0" ] && [ "$UNACCEPTED" != "unknown" ]; then
118
+ echo "::error::$UNACCEPTED unaccepted vulnerable package(s)."
119
+ exit 1
120
+ fi
121
+
122
+ # ── Gate 5: Tests (pytest) ──
123
+
124
+ {{DATABASE_URI_STEP}}
125
+
126
+ - name: Tests
127
+ run: |
128
+ mkdir -p ci-evidence
129
+ pytest --junit-xml=ci-evidence/junit.xml --tb=short
130
+
131
+ # ── Gate 6: Build ──
132
+
133
+ - name: Build Check
134
+ run: python -m build --sdist --wheel
135
+ env:
136
+ {{BUILD_ENV}}
137
+
138
+ # ── Upload artifacts ──
139
+
140
+ # actions/upload-artifact@v4 doesn't honour the job's `working-directory`;
141
+ # paths are workspace-relative. Prefix with WORKING_DIR_PREFIX so artifacts
142
+ # uploaded from a subdir project (e.g. mission-control-api/) include the
143
+ # subdir in their stored path, matching where the gate steps wrote them.
144
+ - uses: actions/upload-artifact@v4
145
+ if: always()
146
+ continue-on-error: true
147
+ with:
148
+ name: ci-results
149
+ path: |
150
+ {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json
151
+ {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json
152
+ {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml
153
+ {{WORKING_DIR_PREFIX}}dist/
154
+ retention-days: 90
155
+
156
+ register-release:
157
+ name: Register Release
158
+ runs-on: {{RUNNER}}
159
+ if: ${{ vars.DEVAUDIT_BASE_URL != '' }}
160
+ outputs:
161
+ version: ${{ steps.version.outputs.version }}
162
+ env:
163
+ DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
164
+ DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
165
+ steps:
166
+ - uses: actions/checkout@v4
167
+
168
+ - name: Validate DevAudit env
169
+ run: |
170
+ if [ -z "${DEVAUDIT_BASE_URL}" ] || [ -z "${DEVAUDIT_API_KEY}" ]; then
171
+ echo "::error::DEVAUDIT_BASE_URL (variable) and DEVAUDIT_API_KEY (secret) must both be set."
172
+ exit 1
173
+ fi
174
+ echo "BASE=${DEVAUDIT_BASE_URL%/}" >> "$GITHUB_ENV"
175
+
176
+ - name: Determine release version
177
+ id: version
178
+ run: |
179
+ DATE_PREFIX="v$(date +%Y.%m.%d)"
180
+ RESP=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
181
+ "${BASE}/api/ci/releases/resolve?projectSlug={{PROJECT_SLUG}}&versionPrefix=${DATE_PREFIX}")
182
+ VERSION=$(echo "$RESP" | jq -r '.nextSequenceVersion // empty')
183
+ if [ -z "$VERSION" ]; then
184
+ VERSION="${DATE_PREFIX}"
185
+ fi
186
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
187
+ echo "Release version: ${VERSION}"
188
+
189
+ - name: Ensure release exists
190
+ run: |
191
+ chmod +x scripts/upload-evidence.sh 2>/dev/null || true
192
+ bash scripts/upload-evidence.sh \
193
+ {{PROJECT_SLUG}} _compliance-docs compliance_document README.md \
194
+ --release ${{ steps.version.outputs.version }} --create-release-if-missing \
195
+ --environment uat --category planning \
196
+ --git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
197
+
198
+ - name: Sync known requirements from RTM
199
+ run: |
200
+ if [ -f "compliance/RTM.md" ]; then
201
+ REQS=$(grep -oP 'REQ-\d+' compliance/RTM.md | sort -t- -k2 -n -u)
202
+ if [ -n "$REQS" ]; then
203
+ JSON_ARRAY=$(echo "$REQS" | jq -R -s -c 'split("\n") | map(select(length > 0))')
204
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
205
+ -X PATCH "${BASE}/api/ci/projects/{{PROJECT_SLUG}}/known-requirements" \
206
+ -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
207
+ -H "Content-Type: application/json" \
208
+ -d "{\"requirements\": ${JSON_ARRAY}}")
209
+ echo "known_requirements sync: HTTP ${HTTP_CODE}"
210
+ echo "Synced $(echo "$REQS" | wc -w) requirements from RTM.md"
211
+ fi
212
+ fi
213
+
214
+ upload-evidence:
215
+ name: Upload Evidence
216
+ runs-on: {{RUNNER}}
217
+ needs: [quality-gates, register-release]
218
+ if: ${{ !failure() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' }}
219
+ env:
220
+ DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
221
+ DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
222
+ steps:
223
+ - uses: actions/checkout@v4
224
+
225
+ # Download to workspace root: upload-artifact@v4 preserves the file's
226
+ # workspace-relative path (e.g. mission-control-api/ci-evidence/sast.json
227
+ # for a subdir project). Downloading with path: '.' restores files to
228
+ # those exact paths so the upload-evidence.sh references below resolve
229
+ # without nesting.
230
+ - name: Download CI gate artifacts
231
+ uses: actions/download-artifact@v4
232
+ continue-on-error: true
233
+ with:
234
+ name: ci-results
235
+ path: .
236
+
237
+ - name: Generate and upload gate evidence
238
+ if: env.DEVAUDIT_BASE_URL != ''
239
+ run: |
240
+ chmod +x scripts/upload-evidence.sh 2>/dev/null || true
241
+ FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
242
+ FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
243
+ FLAGS="${FLAGS} --environment uat"
244
+
245
+ UPLOAD_FAILURES=0
246
+ upload() {
247
+ local label="$1"; shift
248
+ echo "Uploading: ${label}"
249
+ if ! bash scripts/upload-evidence.sh "$@"; then
250
+ echo "::error::Failed to upload ${label}"
251
+ UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
252
+ fi
253
+ }
254
+
255
+ mkdir -p {{WORKING_DIR_PREFIX}}ci-evidence
256
+
257
+ if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json ]; then
258
+ upload sast-results.json \
259
+ {{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json \
260
+ --category security_scan ${FLAGS}
261
+ fi
262
+
263
+ if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json ]; then
264
+ upload dependency-audit.json \
265
+ {{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json \
266
+ --category security_scan ${FLAGS}
267
+ fi
268
+
269
+ # pytest junit.xml is the Python equivalent of e2e-results.json — same `e2e_result` evidence type
270
+ if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml ]; then
271
+ upload junit.xml \
272
+ {{PROJECT_SLUG}} _compliance-docs e2e_result {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml \
273
+ --category ci_pipeline ${FLAGS}
274
+ fi
275
+
276
+ if [ -f "compliance/test-summary-report.md" ]; then
277
+ upload test-summary-report.md \
278
+ {{PROJECT_SLUG}} _compliance-docs compliance_document compliance/test-summary-report.md \
279
+ --category test_report ${FLAGS}
280
+ fi
281
+
282
+ for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md; do
283
+ if [ -f "$DOC" ]; then
284
+ upload "$(basename "$DOC")" \
285
+ {{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
286
+ --category planning ${FLAGS}
287
+ fi
288
+ done
289
+
290
+ for DIR in compliance/pending-releases; do
291
+ if [ -d "$DIR" ]; then
292
+ for TICKET in "$DIR"/*.md; do
293
+ [ -f "$TICKET" ] || continue
294
+ upload "$(basename "$TICKET")" \
295
+ {{PROJECT_SLUG}} _compliance-docs compliance_document "$TICKET" \
296
+ --category release_artifact ${FLAGS}
297
+ done
298
+ fi
299
+ done
300
+
301
+ IN_SCOPE_REQS=()
302
+ if [ -d compliance/pending-releases ]; then
303
+ for TICKET in compliance/pending-releases/RELEASE-TICKET-REQ-*.md; do
304
+ [ -f "$TICKET" ] || continue
305
+ REQ_ID=$(basename "$TICKET" .md | sed 's/^RELEASE-TICKET-//')
306
+ IN_SCOPE_REQS+=("$REQ_ID")
307
+ done
308
+ fi
309
+
310
+ if [ ${#IN_SCOPE_REQS[@]} -eq 0 ]; then
311
+ echo "No pending release tickets found — skipping per-requirement evidence upload"
312
+ else
313
+ echo "In-scope requirements for this release: ${IN_SCOPE_REQS[*]}"
314
+ for REQ_ID in "${IN_SCOPE_REQS[@]}"; do
315
+ REQ_DIR="compliance/evidence/${REQ_ID}/"
316
+ if [ ! -d "$REQ_DIR" ]; then
317
+ echo "Warning: pending ticket for ${REQ_ID} but no ${REQ_DIR} on disk"
318
+ continue
319
+ fi
320
+ for ARTIFACT in "$REQ_DIR"*.md; do
321
+ [ -f "$ARTIFACT" ] || continue
322
+ upload "${REQ_ID}/$(basename "$ARTIFACT")" \
323
+ {{PROJECT_SLUG}} "${REQ_ID}" compliance_document "$ARTIFACT" \
324
+ --category planning ${FLAGS}
325
+ done
326
+ done
327
+ fi
328
+
329
+ if [ "$UPLOAD_FAILURES" -gt 0 ]; then
330
+ echo "::error::${UPLOAD_FAILURES} evidence upload(s) failed — release is missing gate evidence and cannot pass UAT review"
331
+ exit 1
332
+ fi
333
+
334
+ - name: Summary
335
+ run: echo "Evidence uploaded for ${{ needs.register-release.outputs.version }} (UAT)"