Lines 36-76markdown
36- Namespaced (feature sub-pipelines, multi-area projects): keep the suffix on the LAST segment.
37 - ✅ `TC-FE-001h`, `TC-API-USER-010f`, `TC-E2E-007e`
38 - ❌ `TC-FE001h` (digits glued to namespace letter — verify-run cannot parse)
39 - ❌ `TC-E01` (no separator between namespace and digits, no suffix — silently dropped to skipped_no_marker)
40- **Gate**: `aitri {{SCOPE_VERB}}complete{{SCOPE_ARG}} 3` requires every FR to have ≥1 TC id ending in `h` and ≥1 ending in `f`
42## Output: `{{ARTIFACTS_BASE}}/03_TEST_CASES.json`
44{ test_plan: { strategy, coverage_goal: "<the project's own target, e.g. 80% — your call, nothing keys off this value>", test_types: ["unit","integration","e2e"] },
47 requirement_id: "FR-001",
48 user_story_id: "US-001",
50 title: "Login — valid credentials returns JWT",
52 scenario: "happy_path",
55 given: "user exists with email=test@example.com, password=Test1234!",
56 when: "POST /auth/login { email: 'test@example.com', password: 'Test1234!' }",
MediumSecret Pattern
Hardcoded password in templates/phases/tests.md
templates/phases/tests.mdView on unpkg · L56 57 then: "response status 200, body contains { token: <JWT string>, expiresIn: 3600 }",
58 steps: ["POST /auth/login with valid email + password"],
59 expected_result: "Returns 200 + JWT token",
63## Hard gates — `aitri {{SCOPE_VERB}}complete{{SCOPE_ARG}} 3` REJECTS the artifact without these
64Scan this before writing. These are mechanical (validated by exit code, not advisory). The sections below explain the *why*; this is the *what*:
65- `test_plan` object present (strategy, coverage_goal, test_types).
66- Every test case carries ALL of: `id`, `requirement_id` (or `frs[]`), `user_story_id`, `ac_id`, `type`, `scenario`, `given`, `when`, `then`, `expected_result`.
67- `type` is EXACTLY one of `unit` | `integration` | `e2e` — no `ui`, `performance`, `security`, etc. (security/UX are expressed via `requirement_id` + the type-coverage rules below, not the `type` enum).
68- `scenario` is EXACTLY one of `happy_path` | `edge_case` | `negative`.
69- **TC id suffix ↔ scenario** (there is NO `n` suffix): `happy_path` → id ends in `h`; `negative` → id ends in `f`; `edge_case` → id ends in `e`.
70- Each `requirement_id`/`frs[]` target has **≥3 test cases**, including **≥1 `happy_path` (id …h)** and **≥1 `negative` (id …f)**.
71- **≥2 critical-flow test cases.** If the surface has a UI (any FR of type `ux`/`visual`/`audio`), these must be `type: "e2e"`. If it is **backend-only** (no UX/visual/audio FR), API-level `type: "integration"` tests count as critical-flow coverage — label them `integration` (do NOT mislabel them `e2e` to clear the gate).
72- `requirement_id` is a single id — never comma-separated (use `frs: [...]` for multi-FR).
73- `requirement_id`/`frs[]` and `user_story_id` reference real ids in `01_REQUIREMENTS.json`.
74- `expected_result` is specific — not `"works"`, `"passes"`, `"is correct"`.
75- `ac_id`: required **only when** `01_REQUIREMENTS.json` provides structured acceptance criteria — i.e. `user_stories[].acceptance_criteria` are `{ id, given, when, then }` objects (the legacy `{ id, text }` also counts — only the `id` is the join key). Then every TC must carry an `ac_id` matching one of those ids (the value is cross-validated; the error lists the valid ids). If Phase 1's acceptance_criteria are plain strings with no ids, `ac_id` is **optional** — traceability still holds via `requirement_id` + `user_story_id` (both always required). Do NOT invent an `ac_id` format: use an id ...
Long lines were clipped for display.