contract-driven-delivery 1.0.1 → 1.7.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.
Files changed (58) hide show
  1. package/README.md +96 -1
  2. package/assets/CLAUDE.template.md +59 -3
  3. package/assets/agents/backend-engineer.md +43 -0
  4. package/assets/agents/change-classifier.md +40 -0
  5. package/assets/agents/ci-cd-gatekeeper.md +53 -4
  6. package/assets/agents/contract-reviewer.md +49 -3
  7. package/assets/agents/dependency-security-reviewer.md +95 -0
  8. package/assets/agents/e2e-resilience-engineer.md +42 -1
  9. package/assets/agents/frontend-engineer.md +44 -1
  10. package/assets/agents/monkey-test-engineer.md +40 -1
  11. package/assets/agents/qa-reviewer.md +52 -0
  12. package/assets/agents/repo-context-scanner.md +40 -0
  13. package/assets/agents/spec-architect.md +77 -3
  14. package/assets/agents/spec-drift-auditor.md +40 -0
  15. package/assets/agents/stress-soak-engineer.md +42 -0
  16. package/assets/agents/test-strategist.md +44 -1
  17. package/assets/agents/ui-ux-reviewer.md +41 -1
  18. package/assets/agents/visual-reviewer.md +41 -1
  19. package/assets/ci/github-actions/contract-driven-gates.yml +50 -5
  20. package/assets/ci-templates/bun.yml +5 -0
  21. package/assets/ci-templates/conda.yml +11 -0
  22. package/assets/ci-templates/go.yml +12 -0
  23. package/assets/ci-templates/npm.yml +6 -0
  24. package/assets/ci-templates/pip.yml +10 -0
  25. package/assets/ci-templates/pnpm.yml +9 -0
  26. package/assets/ci-templates/poetry.yml +12 -0
  27. package/assets/ci-templates/rust.yml +12 -0
  28. package/assets/ci-templates/unknown.yml +4 -0
  29. package/assets/ci-templates/uv.yml +12 -0
  30. package/assets/ci-templates/yarn.yml +6 -0
  31. package/assets/contracts/CHANGELOG.md +27 -0
  32. package/assets/contracts/api/api-contract.md +7 -0
  33. package/assets/contracts/business/business-rules.md +7 -0
  34. package/assets/contracts/ci/ci-gate-contract.md +7 -0
  35. package/assets/contracts/css/css-contract.md +7 -0
  36. package/assets/contracts/data/data-shape-contract.md +7 -0
  37. package/assets/contracts/env/env-contract.md +7 -0
  38. package/assets/hooks/pre-commit +23 -0
  39. package/assets/skill/SKILL.md +20 -4
  40. package/assets/skill/scripts/detect_project_profile.py +68 -1
  41. package/assets/skill/scripts/generate_change_scaffold.py +2 -2
  42. package/assets/skill/scripts/validate_api_semantic.py +162 -0
  43. package/assets/skill/scripts/validate_ci_gates.py +34 -6
  44. package/assets/skill/scripts/validate_contract_versions.py +385 -0
  45. package/assets/skill/scripts/validate_contracts.py +25 -1
  46. package/assets/skill/scripts/validate_env_contract.py +3 -1
  47. package/assets/skill/scripts/validate_env_semantic.py +182 -0
  48. package/assets/skill/scripts/validate_spec_traceability.py +34 -8
  49. package/assets/tests-templates/soak/k6-example.js +19 -0
  50. package/assets/tests-templates/soak/locust-example.py +21 -0
  51. package/assets/tests-templates/soak/soak-profile.md +16 -0
  52. package/assets/tests-templates/stress/artillery-example.yml +27 -0
  53. package/assets/tests-templates/stress/k6-example.js +22 -0
  54. package/assets/tests-templates/stress/load-profile.md +14 -0
  55. package/assets/tests-templates/stress/locust-example.py +21 -0
  56. package/dist/cli/index.js +593 -106
  57. package/package.json +6 -3
  58. 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 main():
7
- ap=argparse.ArgumentParser(); ap.add_argument('change_dir')
8
- args=ap.parse_args(); d=Path(args.change_dir)
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: print('Missing required change artifacts: '+', '.join(missing)); sys.exit(1)
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")