contract-driven-delivery 1.0.1 → 1.6.0
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/README.md +96 -1
- package/assets/CLAUDE.template.md +59 -3
- package/assets/agents/backend-engineer.md +43 -0
- package/assets/agents/change-classifier.md +40 -0
- package/assets/agents/ci-cd-gatekeeper.md +53 -4
- package/assets/agents/contract-reviewer.md +49 -3
- package/assets/agents/dependency-security-reviewer.md +95 -0
- package/assets/agents/e2e-resilience-engineer.md +42 -1
- package/assets/agents/frontend-engineer.md +44 -1
- package/assets/agents/monkey-test-engineer.md +40 -1
- package/assets/agents/qa-reviewer.md +52 -0
- package/assets/agents/repo-context-scanner.md +40 -0
- package/assets/agents/spec-architect.md +77 -3
- package/assets/agents/spec-drift-auditor.md +40 -0
- package/assets/agents/stress-soak-engineer.md +42 -0
- package/assets/agents/test-strategist.md +44 -1
- package/assets/agents/ui-ux-reviewer.md +41 -1
- package/assets/agents/visual-reviewer.md +41 -1
- package/assets/ci/github-actions/contract-driven-gates.yml +50 -5
- package/assets/ci-templates/bun.yml +5 -0
- package/assets/ci-templates/conda.yml +11 -0
- package/assets/ci-templates/go.yml +12 -0
- package/assets/ci-templates/npm.yml +6 -0
- package/assets/ci-templates/pip.yml +10 -0
- package/assets/ci-templates/pnpm.yml +9 -0
- package/assets/ci-templates/poetry.yml +12 -0
- package/assets/ci-templates/rust.yml +12 -0
- package/assets/ci-templates/unknown.yml +4 -0
- package/assets/ci-templates/uv.yml +12 -0
- package/assets/ci-templates/yarn.yml +6 -0
- package/assets/contracts/CHANGELOG.md +27 -0
- package/assets/contracts/api/api-contract.md +7 -0
- package/assets/contracts/business/business-rules.md +7 -0
- package/assets/contracts/ci/ci-gate-contract.md +7 -0
- package/assets/contracts/css/css-contract.md +7 -0
- package/assets/contracts/data/data-shape-contract.md +7 -0
- package/assets/contracts/env/env-contract.md +7 -0
- package/assets/hooks/pre-commit +23 -0
- package/assets/skill/SKILL.md +20 -4
- package/assets/skill/scripts/detect_project_profile.py +68 -1
- package/assets/skill/scripts/generate_change_scaffold.py +2 -2
- package/assets/skill/scripts/validate_api_semantic.py +162 -0
- package/assets/skill/scripts/validate_ci_gates.py +34 -6
- package/assets/skill/scripts/validate_contract_versions.py +385 -0
- package/assets/skill/scripts/validate_contracts.py +25 -1
- package/assets/skill/scripts/validate_env_contract.py +3 -1
- package/assets/skill/scripts/validate_env_semantic.py +182 -0
- package/assets/skill/scripts/validate_spec_traceability.py +34 -8
- package/assets/tests-templates/soak/k6-example.js +19 -0
- package/assets/tests-templates/soak/locust-example.py +21 -0
- package/assets/tests-templates/soak/soak-profile.md +16 -0
- package/assets/tests-templates/stress/artillery-example.yml +27 -0
- package/assets/tests-templates/stress/k6-example.js +22 -0
- package/assets/tests-templates/stress/load-profile.md +14 -0
- package/assets/tests-templates/stress/locust-example.py +21 -0
- package/dist/cli/index.js +593 -106
- package/package.json +6 -3
- package/assets/skill/agents/openai.yaml +0 -2
|
@@ -2,17 +2,43 @@
|
|
|
2
2
|
"""Coarse traceability check for a change folder."""
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import argparse, sys
|
|
5
|
-
REQUIRED=['classification.md','test-plan.md','ci-gates.md','tasks.md']
|
|
6
|
-
def
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if not d.exists(): print(f'{d} not found'); sys.exit(1)
|
|
5
|
+
REQUIRED=['change-classification.md','test-plan.md','ci-gates.md','tasks.md']
|
|
6
|
+
def check_change_dir(d):
|
|
7
|
+
"""Check one change directory. Returns list of error strings (empty = pass)."""
|
|
8
|
+
errors=[]
|
|
10
9
|
missing=[f for f in REQUIRED if not (d/f).exists()]
|
|
11
|
-
if missing:
|
|
10
|
+
if missing:
|
|
11
|
+
errors.append(f'{d.name}: missing required artifacts: '+', '.join(missing))
|
|
12
|
+
return errors
|
|
12
13
|
text='\n'.join((d/f).read_text(encoding='utf-8', errors='ignore') for f in REQUIRED)
|
|
13
14
|
warnings=[]
|
|
14
15
|
for term in ['contract','test','ci','gate']:
|
|
15
16
|
if term not in text.lower(): warnings.append(term)
|
|
16
|
-
if warnings: print('Warning: weak traceability terms: '+', '.join(warnings))
|
|
17
|
-
print('Change traceability basic validation passed.')
|
|
17
|
+
if warnings: print(f'Warning: {d.name}: weak traceability terms: '+', '.join(warnings))
|
|
18
|
+
print(f'Change traceability basic validation passed: {d.name}')
|
|
19
|
+
return errors
|
|
20
|
+
def main():
|
|
21
|
+
ap=argparse.ArgumentParser(); ap.add_argument('change_dir', nargs='?', default=None)
|
|
22
|
+
args=ap.parse_args()
|
|
23
|
+
if args.change_dir is not None:
|
|
24
|
+
d=Path(args.change_dir)
|
|
25
|
+
if not d.exists(): print(f'{d} not found'); sys.exit(1)
|
|
26
|
+
errors=check_change_dir(d)
|
|
27
|
+
if errors: [print(e) for e in errors]; sys.exit(1)
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
# No argument: scan specs/changes/*/
|
|
30
|
+
changes_root=Path('specs/changes')
|
|
31
|
+
if not changes_root.exists() or not any(True for _ in changes_root.iterdir() if changes_root.exists()):
|
|
32
|
+
print('Warning: specs/changes/ not found or empty -- skipping spec traceability validation.')
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
subdirs=[p for p in changes_root.iterdir() if p.is_dir()]
|
|
35
|
+
if not subdirs:
|
|
36
|
+
print('Warning: specs/changes/ is empty -- skipping spec traceability validation.')
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
all_errors=[]
|
|
39
|
+
for d in sorted(subdirs):
|
|
40
|
+
all_errors.extend(check_change_dir(d))
|
|
41
|
+
if all_errors:
|
|
42
|
+
[print(e) for e in all_errors]; sys.exit(1)
|
|
43
|
+
sys.exit(0)
|
|
18
44
|
if __name__=='__main__': main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
export const options = {
|
|
5
|
+
vus: 5, // low constant load
|
|
6
|
+
duration: '4h', // long-running
|
|
7
|
+
thresholds: {
|
|
8
|
+
http_req_duration: ['p(95)<800'], // looser threshold for soak
|
|
9
|
+
http_req_failed: ['rate<0.005'], // tighter error rate over long run
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
|
|
14
|
+
|
|
15
|
+
export default function () {
|
|
16
|
+
const res = http.get(`${BASE_URL}/api/health`);
|
|
17
|
+
check(res, { 'status is 200': (r) => r.status === 200 });
|
|
18
|
+
sleep(2); // slower cadence; we are looking for leaks, not throughput
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Locust example: soak profile (long-running, low load).
|
|
2
|
+
|
|
3
|
+
Run: locust -f tests/templates/soak/locust-example.py \
|
|
4
|
+
--headless -u 5 -r 1 --run-time 4h \
|
|
5
|
+
--host http://localhost:3000
|
|
6
|
+
|
|
7
|
+
Soak focuses on stability over time, not peak throughput.
|
|
8
|
+
Watch for: memory growth, connection exhaustion, queue backlog,
|
|
9
|
+
latency drift, error rate creep.
|
|
10
|
+
"""
|
|
11
|
+
from locust import HttpUser, task, between
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SoakUser(HttpUser):
|
|
15
|
+
wait_time = between(2.0, 5.0)
|
|
16
|
+
|
|
17
|
+
@task
|
|
18
|
+
def health_check(self):
|
|
19
|
+
with self.client.get("/api/health", catch_response=True) as r:
|
|
20
|
+
if r.status_code != 200:
|
|
21
|
+
r.failure(f"unexpected status {r.status_code}")
|
|
@@ -13,3 +13,19 @@
|
|
|
13
13
|
## Failure Thresholds
|
|
14
14
|
|
|
15
15
|
## Artifact Retention
|
|
16
|
+
|
|
17
|
+
## Runner Config (REQUIRED — fill before running)
|
|
18
|
+
|
|
19
|
+
- runner: k6 | locust | artillery
|
|
20
|
+
- config file: tests/soak/<scenario>.<ext>
|
|
21
|
+
- target environment:
|
|
22
|
+
- constant load (VUs or arrival rate):
|
|
23
|
+
- duration: <!-- soak runs are typically 2h–24h+ -->
|
|
24
|
+
- pass criteria (must include leak/drift signals):
|
|
25
|
+
- memory growth: <!-- e.g., RSS < 1.2× baseline after 4h -->
|
|
26
|
+
- latency drift: <!-- e.g., p95 within ±10% of hour-1 baseline -->
|
|
27
|
+
- error rate: <!-- e.g., < 0.5% sustained -->
|
|
28
|
+
- artifacts:
|
|
29
|
+
|
|
30
|
+
### Reference templates
|
|
31
|
+
See `tests/templates/soak/k6-example.js` or `locust-example.py`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Run: npx artillery run tests/templates/stress/artillery-example.yml
|
|
2
|
+
config:
|
|
3
|
+
target: "http://localhost:3000"
|
|
4
|
+
phases:
|
|
5
|
+
- duration: 30
|
|
6
|
+
arrivalRate: 5
|
|
7
|
+
name: warm-up
|
|
8
|
+
- duration: 120
|
|
9
|
+
arrivalRate: 20
|
|
10
|
+
name: sustain
|
|
11
|
+
- duration: 30
|
|
12
|
+
arrivalRate: 5
|
|
13
|
+
name: cool-down
|
|
14
|
+
ensure:
|
|
15
|
+
p95: 500 # 95% under 500ms
|
|
16
|
+
maxErrorRate: 1 # < 1% errors
|
|
17
|
+
|
|
18
|
+
scenarios:
|
|
19
|
+
- name: health-then-list
|
|
20
|
+
flow:
|
|
21
|
+
- get:
|
|
22
|
+
url: "/api/health"
|
|
23
|
+
expect:
|
|
24
|
+
- statusCode: 200
|
|
25
|
+
- think: 1
|
|
26
|
+
- get:
|
|
27
|
+
url: "/api/items"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
export const options = {
|
|
5
|
+
stages: [
|
|
6
|
+
{ duration: '30s', target: 20 }, // ramp up to 20 VUs
|
|
7
|
+
{ duration: '2m', target: 20 }, // hold 20 VUs
|
|
8
|
+
{ duration: '30s', target: 0 }, // ramp down
|
|
9
|
+
],
|
|
10
|
+
thresholds: {
|
|
11
|
+
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
|
|
12
|
+
http_req_failed: ['rate<0.01'], // <1% errors
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
|
|
17
|
+
|
|
18
|
+
export default function () {
|
|
19
|
+
const res = http.get(`${BASE_URL}/api/health`);
|
|
20
|
+
check(res, { 'status is 200': (r) => r.status === 200 });
|
|
21
|
+
sleep(1);
|
|
22
|
+
}
|
|
@@ -13,3 +13,17 @@
|
|
|
13
13
|
## Metrics
|
|
14
14
|
|
|
15
15
|
## Thresholds
|
|
16
|
+
|
|
17
|
+
## Runner Config (REQUIRED — fill before running)
|
|
18
|
+
|
|
19
|
+
- runner: k6 | locust | artillery <!-- pick one -->
|
|
20
|
+
- config file: tests/stress/<scenario>.<ext> <!-- e.g., tests/stress/checkout-load.js -->
|
|
21
|
+
- target environment: <!-- staging | preprod | local -->
|
|
22
|
+
- VUs / arrival rate: <!-- e.g., 50 VUs, or 20 req/s -->
|
|
23
|
+
- duration: <!-- e.g., 3m -->
|
|
24
|
+
- pass criteria: <!-- must reference an SLO, e.g., p95 < 500ms, error rate < 1% -->
|
|
25
|
+
- artifacts: <!-- where stdout/HTML report is stored, e.g., ci/artifacts/stress/<run-id>/ -->
|
|
26
|
+
|
|
27
|
+
### Reference templates
|
|
28
|
+
See `tests/templates/stress/k6-example.js`, `locust-example.py`, or
|
|
29
|
+
`artillery-example.yml` for runner-specific starting points.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Locust example: stress profile.
|
|
2
|
+
|
|
3
|
+
Run: locust -f tests/templates/stress/locust-example.py \
|
|
4
|
+
--headless -u 50 -r 5 --run-time 3m \
|
|
5
|
+
--host http://localhost:3000
|
|
6
|
+
"""
|
|
7
|
+
from locust import HttpUser, task, between
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StressUser(HttpUser):
|
|
11
|
+
wait_time = between(0.5, 2.0)
|
|
12
|
+
|
|
13
|
+
@task(3)
|
|
14
|
+
def health_check(self):
|
|
15
|
+
with self.client.get("/api/health", catch_response=True) as r:
|
|
16
|
+
if r.status_code != 200:
|
|
17
|
+
r.failure(f"unexpected status {r.status_code}")
|
|
18
|
+
|
|
19
|
+
@task(1)
|
|
20
|
+
def list_items(self):
|
|
21
|
+
self.client.get("/api/items")
|