shapeup-sdlc 1.6.2
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/.claude/settings.local.example.json +20 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.env.shapeup.example +14 -0
- package/AGENTS.md +133 -0
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/SECURITY.md +72 -0
- package/bin/init.mjs +329 -0
- package/commands/build.md +14 -0
- package/commands/eval.md +15 -0
- package/commands/hammer.md +12 -0
- package/commands/orient.md +11 -0
- package/commands/qa.md +12 -0
- package/commands/retro.md +13 -0
- package/commands/scopes.md +14 -0
- package/commands/shape.md +12 -0
- package/commands/ship.md +53 -0
- package/commands/wire.md +11 -0
- package/hooks/anti-rationalization.mjs +244 -0
- package/hooks/compact-snapshot.mjs +47 -0
- package/hooks/gate-deadline.mjs +151 -0
- package/hooks/gate-intake.mjs +110 -0
- package/hooks/gate-l2.mjs +161 -0
- package/hooks/gate-zerowork.mjs +264 -0
- package/hooks/hooks.json +118 -0
- package/hooks/lib/decision.mjs +183 -0
- package/hooks/safety-spine.mjs +296 -0
- package/hooks/sandbox-guard.mjs +172 -0
- package/hooks/session-rehydrate.mjs +109 -0
- package/hooks/slop-cleaner.mjs +176 -0
- package/oracles/_shared.mjs +46 -0
- package/oracles/http-oracle.mjs +155 -0
- package/oracles/index.mjs +36 -0
- package/oracles/process-oracle.mjs +146 -0
- package/oracles/snapshot-oracle.mjs +119 -0
- package/oracles/test-oracle.mjs +138 -0
- package/package.json +49 -0
- package/skills/advisor-protocol/SKILL.md +171 -0
- package/skills/ba-pitch-analyzer/SKILL.md +175 -0
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/assets/templates/assess-report.tmpl.md +127 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/be-service.contract.tmpl.md +62 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/offline-storage.contract.tmpl.md +92 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +66 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/context-map.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/event-choreography.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +104 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/domain-model.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/feedback.tmpl.md +87 -0
- package/skills/ba-pitch-analyzer/assets/templates/integration.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/run-state.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +215 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-board.tmpl.md +35 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-spike.tmpl.md +86 -0
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/usecase.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/ux-behavior.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +152 -0
- package/skills/ba-pitch-analyzer/references/ddd-patterns.md +173 -0
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +275 -0
- package/skills/ba-pitch-analyzer/references/integration-analysis.md +152 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +623 -0
- package/skills/ba-pitch-analyzer/references/test-surface.md +102 -0
- package/skills/ba-pitch-analyzer/references/ux-behavior-patterns.md +269 -0
- package/skills/ba-pitch-analyzer/scripts/board-derive.mjs +241 -0
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +239 -0
- package/skills/coach/SKILL.md +202 -0
- package/skills/orient/SKILL.md +262 -0
- package/skills/qa-edge-hunter/SKILL.md +369 -0
- package/skills/scope-architect/SKILL.md +111 -0
- package/skills/scope-hammer/SKILL.md +186 -0
- package/skills/shapeup/SKILL.md +403 -0
- package/skills/shapeup/resources/breadboard-reflection.md +88 -0
- package/skills/shapeup/resources/breadboarding.md +334 -0
- package/skills/shapeup/resources/context-compaction.md +240 -0
- package/skills/shapeup/resources/framing-doc.md +92 -0
- package/skills/shapeup/resources/kickoff-doc.md +95 -0
- package/skills/shapeup/resources/shaping.md +194 -0
- package/skills/shapeup/resources/spike.md +86 -0
- package/skills/solution-architect/SKILL.md +129 -0
- package/skills/spec-evaluator/README.md +93 -0
- package/skills/spec-evaluator/SKILL.md +213 -0
- package/skills/spec-evaluator/references/anti-leniency.md +50 -0
- package/skills/spec-evaluator/references/dimension-contract.md +126 -0
- package/skills/spec-evaluator/references/dimensions/_registry.md +40 -0
- package/skills/spec-evaluator/references/dimensions/completeness.md +102 -0
- package/skills/spec-evaluator/references/dimensions/integration.md +129 -0
- package/skills/spec-evaluator/references/dimensions/performance.md +48 -0
- package/skills/spec-evaluator/references/dimensions/security.md +60 -0
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +134 -0
- package/skills/spec-evaluator/references/dimensions/tdd-surface.md +110 -0
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +100 -0
- package/skills/spec-evaluator/references/probing.md +177 -0
- package/skills/spec-evaluator/references/report-schema.md +101 -0
- package/skills/spec-evaluator/references/verdict-ledger.md +92 -0
- package/skills/spec-evaluator/scripts/verdict-ledger.mjs +166 -0
- package/skills/task-executor/SKILL.md +194 -0
- package/skills/tech-lead/README.md +71 -0
- package/skills/tech-lead/SKILL.md +459 -0
- package/skills/tech-lead/references/delegation.md +254 -0
- package/skills/tech-lead/references/gates.md +379 -0
- package/skills/tech-lead/references/invocation.md +45 -0
- package/skills/tech-lead/references/ledger-schema.md +214 -0
- package/skills/tech-lead/references/round-protocol.md +184 -0
- package/skills/tech-lead/references/state-model.md +66 -0
- package/skills/tech-lead/references/tiny-lane.md +52 -0
- package/skills/tech-lead/schemas/domain.schema.json +2294 -0
- package/skills/tech-lead/schemas/gate-answers.schema.json +92 -0
- package/skills/tech-lead/schemas/work-order.schema.json +21 -0
- package/skills/tech-lead/schemas/work-result.schema.json +40 -0
- package/skills/tech-lead/scripts/aegis-digest.mjs +124 -0
- package/skills/tech-lead/scripts/budget-check.mjs +156 -0
- package/skills/tech-lead/scripts/compile-order.mjs +518 -0
- package/skills/tech-lead/scripts/fit-check.mjs +196 -0
- package/skills/tech-lead/scripts/gate-answers.mjs +338 -0
- package/skills/tech-lead/scripts/ingest-result.mjs +270 -0
- package/skills/tech-lead/scripts/init-run.mjs +326 -0
- package/skills/tech-lead/scripts/lib/argv.mjs +224 -0
- package/skills/tech-lead/scripts/lib/contract-md.mjs +481 -0
- package/skills/tech-lead/scripts/lib/is-main.mjs +82 -0
- package/skills/tech-lead/scripts/lib/paths.mjs +278 -0
- package/skills/tech-lead/scripts/lib/ratchet-tree.mjs +112 -0
- package/skills/tech-lead/scripts/run-snapshot.mjs +269 -0
- package/skills/tech-lead/scripts/ship-report.mjs +275 -0
- package/skills/tech-lead/scripts/stats.mjs +423 -0
- package/skills/tech-lead/scripts/t0-verify.mjs +470 -0
- package/skills/tech-lead/scripts/trace-lint.mjs +367 -0
- package/skills/tech-lead/scripts/validate-envelope.mjs +283 -0
- package/skills/translator/README.md +66 -0
- package/skills/translator/SKILL.md +258 -0
- package/skills/translator/references/preservation-rules.md +102 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-evaluator
|
|
3
|
+
description: "Use this skill whenever the user wants to evaluate, QA, or verify that an implemented task actually matches its spec and acceptance criteria — the judge in a planner→generator→evaluator harness. Trigger on: \"evaluate task TASK-NNN\", \"QA TASK-NNN\", \"verify against spec\", \"check acceptance criteria\", \"does this match the spec\", \"grade this build\", \"run evaluator\", or a tech-lead --order dispatch. Skeptical by default — absence of evidence is a FAIL; probes the running app and files file:line bugs."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spec Evaluator (the single judge, pure worker v1.0)
|
|
7
|
+
|
|
8
|
+
**Assume broken until proven working. Grade evidence, not claims. Return data, not writes.**
|
|
9
|
+
|
|
10
|
+
The **judge** in a planner → generator → evaluator loop. It reads the committed spec, exercises
|
|
11
|
+
the **running** app, and returns a hard-threshold verdict plus a file:line bug list — as a
|
|
12
|
+
WorkResult envelope the orchestrator ingests. The generator fixes; the evaluator re-runs.
|
|
13
|
+
|
|
14
|
+
**Core guarantee — skeptical by default.** Out-of-the-box an LLM is a lenient QA: it finds a
|
|
15
|
+
real defect, then talks itself into approving anyway. This skill inverts that posture. A
|
|
16
|
+
criterion with no collected evidence is a **FAIL**, never a pass-by-assumption.
|
|
17
|
+
|
|
18
|
+
> **Anti-leniency protocol** → `references/anti-leniency.md` — read before printing any verdict.
|
|
19
|
+
> **Verdict ledger (re-probe + confidence + flip detection)** → `references/verdict-ledger.md`.
|
|
20
|
+
> **Dimension contract (injection interface)** → `references/dimension-contract.md`.
|
|
21
|
+
> Where any reference file describes *writing* shared state (task files, `.verdicts` ledger,
|
|
22
|
+
> run-state), the pure-worker contract overrides it: that data returns in the WorkResult and
|
|
23
|
+
> the orchestrator's ingest script performs the write. Old gate names in reference files map
|
|
24
|
+
> 1:1 onto the core process below: GATE V0/V0.5 = input contract + dimension resolution,
|
|
25
|
+
> GATE V1 = CONTRACT, Phase A = PROBE, GATE V2 = VERDICT, Phase B = REPORT; GATE V3
|
|
26
|
+
> (sign-off) is retired — pausing is the caller's `interaction` policy.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Input contract — the WorkOrder
|
|
31
|
+
|
|
32
|
+
Invoked as `--order <path>`. Fields you may rely on (absent = unknown, never inferred):
|
|
33
|
+
|
|
34
|
+
| Field | What it is |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `payload.spec_folder` | The committed grading truth: `usecases/` + `domain-model.md` (+ `contracts/`, `scope-summary.md`, `_index.md`). No `usecases/` → HARD STOP, nothing to grade against |
|
|
37
|
+
| `payload.feature` | Feature slug — scopes the probe and names the report |
|
|
38
|
+
| `payload.dimensions[]` | The active dimension set (the caller resolved precedence). Absent → `[spec-conformance]` + the auto-enable rules below |
|
|
39
|
+
| `payload.run_cmd` | How to start the running app. Absent standalone → ask; absent orchestrated → ESCALATE, do not guess |
|
|
40
|
+
| `payload.t0_artifacts[]` | Per-scope T0 verdict paths for this round (scoped specs). An artifact listed but missing/red on disk, or a scoped spec with none listed → the round is NOT gradeable: return `status: failed` naming the scope — a structural precondition, not a criterion |
|
|
41
|
+
| `payload.browser` | `cli` (default, ~4x cheaper) \| `mcp` \| `none` |
|
|
42
|
+
| `payload.tasks[]` | Traceability only (which UCs a task claims): NEVER a grading source — the committed UC text is the criterion, a paraphrase mismatch is a finding |
|
|
43
|
+
| `substrate.allowed` | Your only write surface: `.shapeup/<slug>/evaluation/**` (the report + evidence) |
|
|
44
|
+
|
|
45
|
+
**Grading source of truth.** `spec-conformance` grades against the committed `usecases/UC-*.md`
|
|
46
|
+
(Steps, Error Cases, Invariants, Test Surface) and `domain-model.md` — never against a task
|
|
47
|
+
file's own AC paraphrase. Task boards are LOCAL, regenerable bookkeeping the judge never touches.
|
|
48
|
+
|
|
49
|
+
**Dimension resolution (craft, kept).** Base `[spec-conformance]` + always-on `tdd-surface` +
|
|
50
|
+
`integration` (`.be`/`.e2e`); auto-enable `completeness` when any UC has `## Invariants`,
|
|
51
|
+
`test-surface-conformance` when any UC has `## Test Surface`; an explicit `dimensions[]` list
|
|
52
|
+
overrides. Each active dimension's file must satisfy `references/dimension-contract.md` — a
|
|
53
|
+
half-formed dimension is SKIPPED with a warning, never run. Disabled dimensions are out of
|
|
54
|
+
scope; findings there are not graded (no silent widening).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Core process
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
CONTRACT extract every criterion from the committed spec; classify each probe
|
|
62
|
+
[cmd] | [ui] | [data] | [manual]; a [manual]/ambiguous criterion is a spec
|
|
63
|
+
defect to surface, never a silent pass
|
|
64
|
+
PROBE exercise the RUNNING app; collect evidence only — no grading yet
|
|
65
|
+
VERDICT grade each criterion vs its dimension's hard threshold, evidence-only;
|
|
66
|
+
re-probe every FAIL once; flips force confidence low
|
|
67
|
+
REPORT write EVAL report (your substrate) + return the WorkResult envelope
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**CONTRACT.** Criteria come from: UC `## Steps` / `## Error Cases` / `## Invariants` /
|
|
71
|
+
`## Test Surface` rows for every UC in scope; `domain-model.md` rules for touched aggregates;
|
|
72
|
+
the contract triplet (Request/Response/Error) for repository work; `scope-summary.md`
|
|
73
|
+
Done-when statements; `_index.md` Non-Go list. Which UCs are in scope comes from
|
|
74
|
+
`payload.tasks[]` traceability or, standalone, from the user (max 2 questions).
|
|
75
|
+
|
|
76
|
+
**PROBE (evidence, not grades)** — `references/probing.md`:
|
|
77
|
+
- `[cmd]`: run it, capture stdout/stderr + exit code.
|
|
78
|
+
- `[ui]`: drive the app (Playwright CLI preferred). **Affordance-only assertions**: with an
|
|
79
|
+
`affordance_manifest` in play, target `test_id`/`role` + `data-state` transitions — NEVER
|
|
80
|
+
color, font, spacing, or pixel position (Layer-3 is frozen; grading it would resurrect the
|
|
81
|
+
freeze through the judge). Ugly-but-correct PASSes; pretty-but-wrong-`data-state` FAILs.
|
|
82
|
+
- `[data]`: query the DB/storage, capture actual state.
|
|
83
|
+
- Contract work: send real requests, compare field-by-field.
|
|
84
|
+
- No evidence collected = recorded "NO EVIDENCE" → FAILs at verdict.
|
|
85
|
+
|
|
86
|
+
**VERDICT.**
|
|
87
|
+
- PASS only if Phase-probe evidence directly confirms; FAIL on defect evidence or no evidence.
|
|
88
|
+
- Re-probe every FAIL once before finalizing: agree → confidence high; disagree → keep FAIL
|
|
89
|
+
(no stable pass = FAIL), confidence low, note flaky.
|
|
90
|
+
- Read any existing `.verdicts-*.jsonl` (read-only) to detect flips vs prior runs — a flip
|
|
91
|
+
forces confidence low and a stability note. The new lines return in your envelope; ingest
|
|
92
|
+
appends them (never rewrite history).
|
|
93
|
+
- Dimension threshold from its file (spec-conformance: 100% of [cmd]/[ui]/[data] criteria +
|
|
94
|
+
contract triplet + Non-Go). Overall PASS only if ALL active dimensions pass — the halo
|
|
95
|
+
effect is banned; a strong dimension never lifts a failing one.
|
|
96
|
+
- **T0 citation (scoped specs).** Recompute each cited artifact's sha256 from disk — never
|
|
97
|
+
trust a handed hash. A verdict on a scoped spec without a T0 citation is structurally
|
|
98
|
+
invalid, regardless of how convincing your own probing looked; generator prose ("tests
|
|
99
|
+
pass", "verified locally") is never admissible evidence.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Anti-rationalization table
|
|
104
|
+
|
|
105
|
+
| Excuse | Reality |
|
|
106
|
+
|---|---|
|
|
107
|
+
| "The code clearly implements it, no need to run it" | Apps that look right still break when used. Probe the running app. |
|
|
108
|
+
| "It failed, but the feature mostly works" | One FAIL fails the dimension. Thresholds are hard. |
|
|
109
|
+
| "The generator says tests pass" | Generator prose is not evidence. Your probe or the T0 artifact is. |
|
|
110
|
+
| "This criterion isn't really testable, count it as pass" | Untestable AC = spec defect → surface it; it blocks a clean PASS unless explicitly waived. |
|
|
111
|
+
| "The other dimensions are strong, round up" | Halo effect banned. Dimensions never average. |
|
|
112
|
+
| "The task file's checklist says done" | The checklist is the generator's paraphrase. Grade the committed UC text. |
|
|
113
|
+
| "Re-probing is a waste, the FAIL is obvious" | A single non-deterministic snapshot lies. Re-probe; report the flip honestly. |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Output contract — the WorkResult
|
|
118
|
+
|
|
119
|
+
1. Write the report `.shapeup/<slug>/evaluation/EVAL-FEATURE-<slug>.md` (or
|
|
120
|
+
`EVAL-<task_id>.md` for a per-task run) per `references/report-schema.md`: verdict,
|
|
121
|
+
per-dimension criteria table with confidence, stability block (flips), bug list (severity,
|
|
122
|
+
criterion, `file:line`, repro, expected vs actual), NEXT ACTION, and — scoped specs — the
|
|
123
|
+
T0 citations. A scoped report with no citation field is malformed; do not write it.
|
|
124
|
+
2. Write `.shapeup/<slug>/results/<order-suffix>.json`:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"schema_version": 1,
|
|
129
|
+
"order_id": "<copied>",
|
|
130
|
+
"worker": "spec-evaluator",
|
|
131
|
+
"status": "done",
|
|
132
|
+
"verdict": {
|
|
133
|
+
"overall": "PASS | FAIL",
|
|
134
|
+
"report_path": ".shapeup/<slug>/evaluation/EVAL-FEATURE-<slug>.md",
|
|
135
|
+
"t0_citations": [ { "scope_id": "cart", "path": "…/t0/verdicts/r2-a3.json", "sha256": "…" } ],
|
|
136
|
+
"criteria": [ { "criterion": "UC-01 step 3", "dimension": "spec-conformance",
|
|
137
|
+
"verdict": "FAIL", "confidence": "high", "reprobed": true,
|
|
138
|
+
"evidence": "Pay click throws — apps/web/checkout/Pay.tsx:84" } ],
|
|
139
|
+
"refuted": [ { "task_id": "TASK-007", "ac": "<the checkbox text your evidence disproves>" } ],
|
|
140
|
+
"bugs": [ /* report-schema bug entries */ ]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Every FAIL criterion's `evidence` MUST carry a `file:line` locator** — schema-enforced, not
|
|
146
|
+
advice: `validate-envelope` rejects the whole result before ingest sees it. A PASS may cite plain
|
|
147
|
+
output. (Measured: a run returned a correct FAIL with `bugs: null` and no locator anywhere, which
|
|
148
|
+
is unactionable without re-investigating. The rule used to be repeated five times in this prompt
|
|
149
|
+
and enforced nowhere; it is now stated once and enforced by `domain.schema.json`.)
|
|
150
|
+
|
|
151
|
+
The orchestrator's ingest appends the verdict ledger, un-ticks the `refuted` boxes, and sets
|
|
152
|
+
`eval_verdict` frontmatter. You never touch a task file, a board, or run-state — and you
|
|
153
|
+
NEVER set `status: done`: the judge issues verdicts; closure belongs elsewhere. That
|
|
154
|
+
separation is the whole point of the architecture.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Verification checklist
|
|
159
|
+
|
|
160
|
+
- [ ] Every criterion traces to committed spec text (UC/domain-model/contract/Done-when/Non-Go)
|
|
161
|
+
- [ ] Every PASS cites a confirming probe; every FAIL cites evidence or "NO EVIDENCE"
|
|
162
|
+
- [ ] Every FAIL was re-probed once; confidence assigned per the ledger rule
|
|
163
|
+
- [ ] Scoped spec → T0 citations present with recomputed sha256 (else the run returned `failed`)
|
|
164
|
+
- [ ] Report written inside `evaluation/**` only; no other file touched
|
|
165
|
+
- [ ] `refuted[]` lists exactly the boxes your evidence disproves (un-ticking is ingest's act)
|
|
166
|
+
- [ ] The WorkResult validates against `work-result.schema.json`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Dimension model — how future injection works
|
|
171
|
+
|
|
172
|
+
The core loops over a **set of dimensions**; each is a self-contained file satisfying
|
|
173
|
+
`references/dimension-contract.md` (id, weight, hard_threshold, applies_to, criteria[],
|
|
174
|
+
bug_template). Adding one (e.g. security) = write `references/dimensions/security.md`, flip
|
|
175
|
+
`enabled: true` in `references/dimensions/_registry.md` (or pass it in `dimensions[]`), re-run
|
|
176
|
+
— zero core edits. Disabled security/performance stubs ship as worked examples.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Invocation
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Orchestrated (once per round, after GATE L2) — the canonical form
|
|
184
|
+
/spec-evaluator --order .shapeup/checkout-vnpay/orders/evaluate-r2.json
|
|
185
|
+
|
|
186
|
+
# Standalone — the preamble shim compiles a minimal order, then the single code path runs:
|
|
187
|
+
# node "${CLAUDE_PLUGIN_ROOT}/skills/tech-lead/scripts/compile-order.mjs" --operation evaluate --slug <slug> \
|
|
188
|
+
# --worker spec-evaluator [--payload '{"dimensions": [...], "run_cmd": "..."}']
|
|
189
|
+
/spec-evaluator --spec shapeup/checkout-vnpay/spec/ --task TASK-007
|
|
190
|
+
/spec-evaluator --spec shapeup/checkout-vnpay/spec/ --feature checkout-vnpay --single-pass
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Standalone keeps `--task` (per-task check, not round-gated) and `--single-pass` (feature-level)
|
|
194
|
+
— the shim maps them onto the order's payload; missing run command → ask. After writing the
|
|
195
|
+
WorkResult, run `node "${CLAUDE_PLUGIN_ROOT}/skills/tech-lead/scripts/ingest-result.mjs" <result path>` and show its
|
|
196
|
+
summary — standalone has no orchestrator to ingest for you.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Hard Rules (never override without explicit user instruction)
|
|
201
|
+
|
|
202
|
+
| Rule | Rationale |
|
|
203
|
+
|------|-----------|
|
|
204
|
+
| Absence of evidence = FAIL | Kills pass-by-assumption, the core QA failure mode |
|
|
205
|
+
| Halo effect banned | A strong dimension never lifts a failing one |
|
|
206
|
+
| Disabled dimensions are out of scope | No silent scope creep |
|
|
207
|
+
| Evaluator never sets `status: done`, never edits task files/boards | Judge ≠ doer; refuted boxes return as data, ingest writes |
|
|
208
|
+
| Untestable AC blocks a clean PASS | Forces the spec to be verifiable, not vibes |
|
|
209
|
+
| Probe the RUNNING app, not the source alone | Apps that look right still break when used |
|
|
210
|
+
| Re-probe every FAIL; flip ⇒ confidence low | A single snapshot lies; the ledger makes it visible |
|
|
211
|
+
| Verdict-ledger lines are returned, appended by ingest, never rewritten | Verdict history is how a single-snapshot judge becomes measurable |
|
|
212
|
+
| A verdict on a scoped spec without a T0 citation is structurally invalid | T0 is a machine fact the generator cannot fabricate (DD-7, PA4) |
|
|
213
|
+
| UI assertions target affordances only (test_id/role/data-state) | Layer-3 styling is frozen; grading it resurrects the freeze through the judge |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Anti-Leniency Protocol
|
|
2
|
+
|
|
3
|
+
Read this immediately before GATE V2. It exists because of a documented failure mode: an
|
|
4
|
+
LLM asked to grade work — even another agent's — finds real defects and then talks itself
|
|
5
|
+
into approving anyway, and tests superficially rather than probing edge cases. This skill
|
|
6
|
+
counteracts that. The evaluator's job is to be the skeptic the generator cannot be about
|
|
7
|
+
its own output.
|
|
8
|
+
|
|
9
|
+
## The posture
|
|
10
|
+
**Assume broken until proven working.** A criterion is FAIL by default and only earns PASS
|
|
11
|
+
when Phase A produced evidence that directly confirms it. The burden of proof is on the
|
|
12
|
+
build, not on the evaluator to find a reason to fail it.
|
|
13
|
+
|
|
14
|
+
## Evidence rules
|
|
15
|
+
1. **No evidence → FAIL.** If Phase A collected nothing for a criterion (forgot to probe,
|
|
16
|
+
app wouldn't start, probe inconclusive), it is a FAIL labeled `NO EVIDENCE` — never a
|
|
17
|
+
pass-by-assumption. Re-probe if you want a different verdict.
|
|
18
|
+
2. **Every PASS cites the confirming probe.** "PASS — pnpm --filter api test → 12 passed."
|
|
19
|
+
A PASS with no citation is invalid.
|
|
20
|
+
3. **Every FAIL cites concrete evidence** at a locator: file:line, endpoint, or
|
|
21
|
+
screen+element, plus the output/console/DB state observed. A FAIL with no evidence is
|
|
22
|
+
not actionable → re-probe to localize it.
|
|
23
|
+
4. **Probe behavior, not code presence.** A function named `fillRectangle` existing is not
|
|
24
|
+
evidence the rectangle fill works. Exercise it. Code that looks correct but is wired
|
|
25
|
+
wrong is the exact defect class this skill targets.
|
|
26
|
+
|
|
27
|
+
## Banned moves
|
|
28
|
+
- **Halo effect.** A strong dimension or a mostly-working build never lifts a failing
|
|
29
|
+
criterion. Grade each criterion in isolation, then AND.
|
|
30
|
+
- **Self-negotiation.** Do not reason "this is a minor issue, probably acceptable." If it
|
|
31
|
+
violates the criterion, it FAILS at the severity it warrants; acceptability is the user's
|
|
32
|
+
call at GATE V3, not the grader's at V2.
|
|
33
|
+
- **Superficial probing.** Don't stop at the happy path when the criterion implies edge
|
|
34
|
+
cases (error codes, empty states, invalid input). Under-probing produces false PASSes.
|
|
35
|
+
|
|
36
|
+
## Forbidden phrases in a verdict
|
|
37
|
+
These are the tells of lenient self-grading. If one appears, the verdict is not done:
|
|
38
|
+
- "looks good" / "looks correct" / "seems to work"
|
|
39
|
+
- "probably works" / "should be fine" / "likely passes"
|
|
40
|
+
- "minor issue, acceptable" / "good enough" / "close enough"
|
|
41
|
+
- "I'll assume" / "presumably" / "in principle this should…"
|
|
42
|
+
|
|
43
|
+
Replace each with an evidence-anchored statement or a FAIL.
|
|
44
|
+
|
|
45
|
+
## Calibration toward skepticism
|
|
46
|
+
If across a session the verdicts feel generous, recalibrate by re-reading the FAILs: each
|
|
47
|
+
must survive the test "could the generator act on this without asking a single follow-up
|
|
48
|
+
question?" If not, it is under-specified — tighten the locator and the expected-vs-actual.
|
|
49
|
+
Tuning the grader to be skeptical is more tractable than making the generator self-critical;
|
|
50
|
+
that is the entire reason the judge is a separate agent.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Dimension Contract
|
|
2
|
+
|
|
3
|
+
The injection interface. Every file in `dimensions/` must satisfy this contract so the
|
|
4
|
+
core can load and run it without modification. This is the *only* thing the core knows
|
|
5
|
+
about a dimension. Adding a new evaluation concern = writing one file that conforms here.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Required frontmatter
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
---
|
|
13
|
+
id: spec-conformance # unique, kebab-case. Matches filename.
|
|
14
|
+
title: "Spec conformance" # human label printed in verdicts
|
|
15
|
+
enabled: true # core loads it only if true (or named in --dimensions)
|
|
16
|
+
weight: 1.0 # informational; thresholds are hard, not weighted averages
|
|
17
|
+
hard_threshold: all-pass # see "Threshold vocabulary" below
|
|
18
|
+
applies_to: # scoping — omit a key to mean "all"
|
|
19
|
+
lens: [lite, standard] # which ba-pitch-analyzer lenses
|
|
20
|
+
package: any # apps/api | apps/web | apps/mobile | packages/shared | any
|
|
21
|
+
variant: any # shared | be | web | mobile | e2e | any
|
|
22
|
+
requires_browser: true # does this dimension need the running app in a browser?
|
|
23
|
+
---
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The core (GATE V0.5) validates these fields. A dimension missing `id`, `enabled`,
|
|
27
|
+
`hard_threshold`, or a malformed `criteria` block is **skipped with a warning** — never
|
|
28
|
+
run half-formed.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Required body sections
|
|
33
|
+
|
|
34
|
+
### `## Criteria`
|
|
35
|
+
A list. Each criterion is the atomic unit the evaluator grades. Schema per criterion:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
- id: SC-1 # unique within the dimension
|
|
39
|
+
statement: "Every AC checkbox in the task passes by command or UI probe."
|
|
40
|
+
probe: cmd | ui | data | static # how Phase A collects evidence for it
|
|
41
|
+
evidence_required: true # if true, NO EVIDENCE → automatic FAIL
|
|
42
|
+
pass_rule: > # the objective condition for PASS, evidence-based
|
|
43
|
+
All "- [ ]" items verified PASS via their probe; zero failures.
|
|
44
|
+
source: usecases | index | task | contract | scope-summary | code # where the criterion is read from
|
|
45
|
+
# (usecases/domain-model.md are the grading source of truth
|
|
46
|
+
# since v0.9 — task is traceability-only, never substituted)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`probe` values map directly to Phase A handlers:
|
|
50
|
+
- `cmd` → run a shell command, capture output + exit code
|
|
51
|
+
- `ui` → drive the running app (Playwright CLI by default)
|
|
52
|
+
- `data` → query DB / inspect storage state
|
|
53
|
+
- `static` → read code/files (use sparingly; prefer probing the running app)
|
|
54
|
+
|
|
55
|
+
### `## Threshold`
|
|
56
|
+
State the hard threshold in prose + the vocabulary token. The core ANDs all active
|
|
57
|
+
dimensions; within a dimension, the threshold decides PASS/FAIL from its criteria.
|
|
58
|
+
|
|
59
|
+
### `## Bug template`
|
|
60
|
+
A fenced block the report uses for every FAIL this dimension produces. Must include at
|
|
61
|
+
least: `severity`, `criterion`, `location (file:line or endpoint)`, `repro`,
|
|
62
|
+
`expected`, `actual`. Keeps generator handoff uniform across dimensions.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Threshold vocabulary
|
|
67
|
+
|
|
68
|
+
| Token | Meaning |
|
|
69
|
+
|-------|---------|
|
|
70
|
+
| `all-pass` | Every criterion must PASS. One FAIL → dimension FAILS. (Use for correctness.) |
|
|
71
|
+
| `no-critical` | FAILs allowed only below `critical` severity. Any `critical` → dimension FAILS. |
|
|
72
|
+
| `count<=N` | At most N FAILs of any severity tolerated. |
|
|
73
|
+
| `score>=N` | For genuinely graded/subjective dimensions: a 0–100 rubric score ≥ N. |
|
|
74
|
+
|
|
75
|
+
`spec-conformance` uses `all-pass` — correctness is not negotiable and leniency is the
|
|
76
|
+
enemy. A subjective dimension (e.g. `visual`) would use `score>=N` with a calibrated rubric.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## What the core promises a dimension
|
|
81
|
+
|
|
82
|
+
1. It reads `applies_to` and runs the dimension only on matching tasks.
|
|
83
|
+
2. It calls the right Phase A handler for each criterion's `probe`.
|
|
84
|
+
3. It enforces `evidence_required` (no evidence → FAIL) before applying `pass_rule`.
|
|
85
|
+
4. It applies the dimension's own `hard_threshold` independently, then ANDs across dimensions.
|
|
86
|
+
5. It renders FAILs using the dimension's own `## Bug template`.
|
|
87
|
+
|
|
88
|
+
A dimension author therefore writes *criteria + probes + threshold + bug shape* — never
|
|
89
|
+
touches the gate logic, the loop, or the report assembler.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Minimal valid dimension (copy to start a new one)
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
---
|
|
97
|
+
id: my-dimension
|
|
98
|
+
title: "My dimension"
|
|
99
|
+
enabled: false
|
|
100
|
+
weight: 1.0
|
|
101
|
+
hard_threshold: all-pass
|
|
102
|
+
applies_to: { variant: web }
|
|
103
|
+
requires_browser: true
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Criteria
|
|
107
|
+
- id: MD-1
|
|
108
|
+
statement: "<the thing to verify>"
|
|
109
|
+
probe: ui
|
|
110
|
+
evidence_required: true
|
|
111
|
+
pass_rule: "<objective, evidence-based PASS condition>"
|
|
112
|
+
source: task
|
|
113
|
+
|
|
114
|
+
## Threshold
|
|
115
|
+
all-pass — every criterion must PASS.
|
|
116
|
+
|
|
117
|
+
## Bug template
|
|
118
|
+
```
|
|
119
|
+
severity: [critical|major|minor]
|
|
120
|
+
criterion: MD-1
|
|
121
|
+
location: <file:line | endpoint | screen>
|
|
122
|
+
repro: <steps>
|
|
123
|
+
expected: <from criterion>
|
|
124
|
+
actual: <observed evidence>
|
|
125
|
+
```
|
|
126
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Dimension Registry
|
|
2
|
+
|
|
3
|
+
The core reads this at GATE V0.5 to decide which dimensions to load. `--dimensions` on the
|
|
4
|
+
command line overrides this table for a single run.
|
|
5
|
+
|
|
6
|
+
## Active set
|
|
7
|
+
|
|
8
|
+
| id | enabled | file | applies_to (summary) | notes |
|
|
9
|
+
|----|---------|------|----------------------|-------|
|
|
10
|
+
| `spec-conformance` | ✅ true | `dimensions/spec-conformance.md` | all tasks | baseline correctness: AC + Done-when + contract shapes + non-go |
|
|
11
|
+
| `tdd-surface` | ✅ true | `dimensions/tdd-surface.md` | all tasks | suite green + companion test files for new code; TDD-1/2 critical, TDD-3 advisory |
|
|
12
|
+
| `integration` | ✅ true | `dimensions/integration.md` | `.be` and `.e2e` variants only | full-stack integration test + auth boundary + RLS-JWT pattern; runs only when variant matches |
|
|
13
|
+
| `completeness` | ⚙️ auto | `dimensions/completeness.md` | all tasks (lens lite/standard) | auto-ON when spec has UC `## Invariants` (v2.8+); no-op on older specs |
|
|
14
|
+
| `test-surface-conformance` | ⚙️ auto | `dimensions/test-surface-conformance.md` | all tasks (lens lite/standard) | auto-ON when spec has UC `## Test Surface` (v2.9+ or `--surface-only` retrofit); no-op otherwise. Report must list every TS row probed — qa-edge-hunter's negative-space input |
|
|
15
|
+
| `security` | ⛔ false | `dimensions/security.md` | be / shared | stub — flip on when ready |
|
|
16
|
+
| `performance` | ⛔ false | `dimensions/performance.md` | be / web | stub — flip on when ready |
|
|
17
|
+
|
|
18
|
+
Default active set = every row with `enabled: true` whose `applies_to` matches the task variant, **plus** each ⚙️ auto dimension when its auto-enable condition holds:
|
|
19
|
+
- All tasks: **`[spec-conformance, tdd-surface]`** (baseline)
|
|
20
|
+
- `.be` / `.e2e` tasks additionally pick up: **`[integration]`**
|
|
21
|
+
- When any UC declares `## Invariants`: also **`[completeness]`**
|
|
22
|
+
- When any UC carries `## Test Surface`: also **`[test-surface-conformance]`**
|
|
23
|
+
|
|
24
|
+
## Load order
|
|
25
|
+
Dimensions run in table order. `spec-conformance` first so a correctness failure is the
|
|
26
|
+
headline; cosmetic / non-functional dimensions report after it. Order does not change the
|
|
27
|
+
overall verdict (all dimensions are ANDed) — it only affects reading order in the report.
|
|
28
|
+
|
|
29
|
+
## How to add a dimension (the injection recipe)
|
|
30
|
+
1. Copy the "Minimal valid dimension" block from `dimension-contract.md` to
|
|
31
|
+
`dimensions/<id>.md` and fill in criteria, probes, threshold, bug template.
|
|
32
|
+
2. Add a row here and set `enabled: true` — or leave it false and turn it on per-run with
|
|
33
|
+
`--dimensions spec-conformance,<id>`.
|
|
34
|
+
3. Run. No core file changes. GATE V0.5 validates it against the contract; if it doesn't
|
|
35
|
+
conform, it is skipped with a warning rather than silently mis-run.
|
|
36
|
+
|
|
37
|
+
## Scoping reminder
|
|
38
|
+
A dimension only grades tasks its `applies_to` matches. Example: `security` with
|
|
39
|
+
`applies_to: { variant: [be, shared] }` is ignored when evaluating `TASK-007.web`, even if
|
|
40
|
+
it is enabled. This keeps the active set honest per task.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: completeness
|
|
3
|
+
title: "Spec completeness"
|
|
4
|
+
enabled: false
|
|
5
|
+
weight: 1.0
|
|
6
|
+
hard_threshold: no-critical
|
|
7
|
+
applies_to:
|
|
8
|
+
lens: [lite, standard]
|
|
9
|
+
package: any
|
|
10
|
+
variant: any
|
|
11
|
+
requires_browser: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Completeness Dimension
|
|
15
|
+
|
|
16
|
+
> **Auto-enable rule (read at GATE V0.5):** This dimension stays `enabled: false` by
|
|
17
|
+
> default so it is a no-op on every spec generated before `ba-pitch-analyzer` v2.8.
|
|
18
|
+
> The core flips it ON for a run **only when** the spec's `usecases/` contains at least
|
|
19
|
+
> one `## Invariants` section. A pre-v2.8 spec has no invariants → this dimension never
|
|
20
|
+
> activates → existing audit scores and verdicts are unchanged (non-regression guarantee).
|
|
21
|
+
> An explicit `--dimensions ...,completeness` always wins over the auto rule.
|
|
22
|
+
|
|
23
|
+
**Why this exists.** `spec-conformance` answers *"does each task pass its acceptance
|
|
24
|
+
criteria?"* — it grades the tasks that exist. It cannot see the task that was never
|
|
25
|
+
written. Shape Up's discovered-task philosophy says the real bulk of work is found while
|
|
26
|
+
building; an invariant declared on a UC but never backed by a regression task is exactly
|
|
27
|
+
that blind spot. This dimension converts "absence of a task" from invisible into a graded
|
|
28
|
+
**GAP** — the judge surfaces it; the planner (a ba-pitch-analyzer reconcile/generate-board order) fills it.
|
|
29
|
+
Judge never fills it itself.
|
|
30
|
+
|
|
31
|
+
**Distinction held:** conformance = *the tasks that exist are correct*; completeness =
|
|
32
|
+
*the tasks that must exist, do exist*. A spec can be 100% conformant and still incomplete.
|
|
33
|
+
Definition of Done for the harness = conformance PASS **and** completeness no-critical.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Criteria
|
|
38
|
+
|
|
39
|
+
- id: CMP-1
|
|
40
|
+
statement: "Every `[INV-NN]` declared in any usecases/UC-*.md ## Invariants section is backed by ≥1 task whose use_case_refs points to that UC AND whose AC references the invariant (by INV id or its assertion)."
|
|
41
|
+
probe: static
|
|
42
|
+
evidence_required: true
|
|
43
|
+
pass_rule: >
|
|
44
|
+
For each INV-NN found across usecases/: a task backs it only if BOTH hold — (a) the
|
|
45
|
+
task lists that UC in use_case_refs, and (b) the task names the invariant unambiguously,
|
|
46
|
+
i.e. cites the INV id (e.g. "INV-02") or links the UC's #Invariants anchor AND its AC
|
|
47
|
+
restates that specific assertion as a command/observable check. Matching a loose keyword
|
|
48
|
+
shared by several invariants is NOT sufficient — require the INV id or a 1:1 assertion
|
|
49
|
+
match to avoid crediting one task for an invariant it does not actually test. Zero
|
|
50
|
+
uncovered invariants → PASS. Any invariant with no backing task → FAIL at `critical`
|
|
51
|
+
severity (a missing must-have regression, not a cosmetic gap).
|
|
52
|
+
source: code
|
|
53
|
+
|
|
54
|
+
- id: CMP-2
|
|
55
|
+
statement: "Every use case has ≥1 task referencing it via use_case_refs (no stranded UC with zero tasks pointing at it)."
|
|
56
|
+
probe: static
|
|
57
|
+
evidence_required: true
|
|
58
|
+
pass_rule: >
|
|
59
|
+
For each usecases/UC-*.md: ≥1 task file lists it in use_case_refs. A UC with zero
|
|
60
|
+
referencing tasks → FAIL at `critical` (this is the Coverage 🔴 condition, re-checked
|
|
61
|
+
from the evaluator side as a completeness gap rather than trusting synthesis alone).
|
|
62
|
+
source: code
|
|
63
|
+
|
|
64
|
+
- id: CMP-3
|
|
65
|
+
statement: "Discovery ledger surprise rate is within a healthy band (shaping-quality signal)."
|
|
66
|
+
probe: static
|
|
67
|
+
evidence_required: false
|
|
68
|
+
pass_rule: >
|
|
69
|
+
If a discovery ledger exists (discovery/ledger.md or a *discovered-tasks*.md whose
|
|
70
|
+
feature matches run-state.feature), count discovered items marked `[+]` vs imagined
|
|
71
|
+
`[ ]`. surprise_ratio = discovered / (imagined + discovered). PASS (no defect) when
|
|
72
|
+
ratio ≤ 0.5. When ratio > 0.5 → emit a `minor` finding "high surprise rate —
|
|
73
|
+
shaping may have under-specified scope; flag to PO", NOT a critical. evidence_required
|
|
74
|
+
is false, so a spec with no ledger simply yields no CMP-3 finding (not a FAIL).
|
|
75
|
+
source: code
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Threshold
|
|
80
|
+
|
|
81
|
+
`no-critical` — `critical` FAILs (CMP-1 uncovered invariant, CMP-2 stranded UC) block the
|
|
82
|
+
dimension. `minor` findings (CMP-3 high surprise) are reported but do not fail the
|
|
83
|
+
dimension. This keeps the gate honest about *missing must-have work* while treating the
|
|
84
|
+
surprise signal as advisory feedback to the PO, not a build blocker.
|
|
85
|
+
|
|
86
|
+
Rationale for `no-critical` rather than `all-pass`: CMP-3 is a deliberately non-blocking
|
|
87
|
+
signal. Using `all-pass` would let an advisory surprise-rate note sink the verdict, which
|
|
88
|
+
would punish healthy discovery — the opposite of the intent.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Bug template
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
severity: [critical|minor]
|
|
96
|
+
criterion: [CMP-1|CMP-2|CMP-3]
|
|
97
|
+
location: [usecases/UC-Name.md#Invariants INV-NN | usecases/UC-Name.md | discovery/ledger.md]
|
|
98
|
+
repro: <how the gap was detected — e.g. "grep use_case_refs across tasks/ found 0 backing TASK for INV-02">
|
|
99
|
+
expected: <e.g. "≥1 regression task referencing UC-ConnectObjects covering INV-02">
|
|
100
|
+
actual: <e.g. "no task references INV-02; invariant declared but unverified">
|
|
101
|
+
next: <handoff — e.g. "a ba-pitch-analyzer reconcile order over <ledger> to generate the missing regression task">
|
|
102
|
+
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration
|
|
3
|
+
title: "System integration"
|
|
4
|
+
enabled: true
|
|
5
|
+
weight: 1.0
|
|
6
|
+
hard_threshold: no-critical
|
|
7
|
+
applies_to:
|
|
8
|
+
lens: [lite, standard]
|
|
9
|
+
package: any
|
|
10
|
+
variant: [be, e2e]
|
|
11
|
+
requires_browser: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# System Integration Dimension
|
|
15
|
+
|
|
16
|
+
**Why this exists.** Unit tests verify isolated logic. `spec-conformance` drives the
|
|
17
|
+
running app and confirms AC at the surface. Neither catches the class of bug where each
|
|
18
|
+
layer works in isolation but the *seam* is broken: wrong auth header forwarded, RLS policy
|
|
19
|
+
not invoked because the transaction is missing, Prisma schema migration not applied in the
|
|
20
|
+
test database, or a contract mismatch between web's api-client and the API controller.
|
|
21
|
+
This dimension fills that seam.
|
|
22
|
+
|
|
23
|
+
**What "system integration" means here** (scoped to the IVS ADR-005 stack):
|
|
24
|
+
the full request path — web api-client → API controller → service → repository (Prisma +
|
|
25
|
+
RLS) → database — exercised by tests that use a real database, not mocked layers.
|
|
26
|
+
An integration test that mocks `PrismaClient` or replaces the repository with an in-memory
|
|
27
|
+
stub is a unit test wearing integration clothing and does not satisfy this dimension.
|
|
28
|
+
|
|
29
|
+
**Project context (read AGENTS.md Section 2 rules 3 & 4 before probing):**
|
|
30
|
+
- All user-scoped queries must run inside a transaction that injects the JWT claims so RLS
|
|
31
|
+
fires (`SET LOCAL request.jwt.claims`). An integration test that queries without this
|
|
32
|
+
transaction bypasses RLS and produces a false-positive PASS.
|
|
33
|
+
- The connection string must point to the **transaction-mode pooler** (port 6543), not the
|
|
34
|
+
session-mode direct connection (port 5432). Session mode allows `SET LOCAL` to leak.
|
|
35
|
+
- Never trust a `userId` sent in the request body — derive it from the verified JWT.
|
|
36
|
+
|
|
37
|
+
**`.e2e` variant:** the e2e suite inherently exercises the full stack. INT-1 (integration
|
|
38
|
+
test existence) maps to the e2e test file(s) added by the task. INT-2 and INT-3 apply if
|
|
39
|
+
the e2e task introduces any auth-scoped endpoint.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Criteria
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
- id: INT-1
|
|
47
|
+
statement: "At least one integration/e2e test exercises the feature's main flow end-to-end with a real database — no mocked repository or Prisma layer."
|
|
48
|
+
probe: static + cmd
|
|
49
|
+
evidence_required: true
|
|
50
|
+
pass_rule: >
|
|
51
|
+
Locate integration test files for this task's package (typically `*.integration.spec.ts`,
|
|
52
|
+
`*.e2e-spec.ts`, or a `test/` directory at the package root). Grep for test cases that
|
|
53
|
+
touch the happy-path scenario the task implements. Verify the test does NOT mock the
|
|
54
|
+
data layer: `grep -rE 'jest\.mock|vi\.mock' <integration-test-file>` should return no
|
|
55
|
+
hits on repository, Prisma, or database imports. Then run `pnpm --filter <pkg> test:e2e`
|
|
56
|
+
(or the integration-specific script) and capture exit code + test count.
|
|
57
|
+
Zero integration test files → FAIL at `critical`. Any integration test that mocks the
|
|
58
|
+
DB layer → FAIL at `critical` (evidence: the jest.mock / vi.mock import path).
|
|
59
|
+
source: code
|
|
60
|
+
|
|
61
|
+
- id: INT-2
|
|
62
|
+
statement: "An unauthorized-access scenario is covered: a request from a different user/org is rejected with the correct error (403 or empty set), exercised by an integration test."
|
|
63
|
+
probe: cmd + data
|
|
64
|
+
evidence_required: true
|
|
65
|
+
pass_rule: >
|
|
66
|
+
Find the integration test that exercises the unauthorized path: a user from a different
|
|
67
|
+
org (or an unauthenticated caller) attempts to read/write the resource this task
|
|
68
|
+
introduces. The test asserts the correct rejection: 403 Forbidden, an empty result set,
|
|
69
|
+
or an RLS-filtered response. If the resource is read-only to viewers (role_tag check),
|
|
70
|
+
also verify a `viewer` role cannot mutate it. Evidence = the test case text + the run
|
|
71
|
+
output showing it passes. No such test → FAIL at `critical` — this is the gap that lets
|
|
72
|
+
real authorization bugs slip through conformance testing.
|
|
73
|
+
source: code
|
|
74
|
+
|
|
75
|
+
- id: INT-3
|
|
76
|
+
statement: "Integration tests inject JWT claims via the RLS transaction pattern (set_config inside a transaction) and target the transaction-mode pooler (port 6543)."
|
|
77
|
+
probe: static
|
|
78
|
+
evidence_required: true
|
|
79
|
+
pass_rule: >
|
|
80
|
+
Inspect the integration test setup (beforeAll / test helper): it must execute
|
|
81
|
+
`SET LOCAL request.jwt.claims` (or the equivalent `$executeRaw\`select set_config(...)\``)
|
|
82
|
+
inside a Prisma `$transaction` block before any user-scoped query. Also check the
|
|
83
|
+
DATABASE_URL used in the test config: it must include port 6543 (transaction pooler),
|
|
84
|
+
not 5432 (session mode). A test that queries without the JWT injection calls the DB as
|
|
85
|
+
the app role without RLS and will PASS rows it should not see → FAIL at `major`.
|
|
86
|
+
A test using port 5432 risks SET LOCAL leaking across pooled connections → FAIL at `major`.
|
|
87
|
+
If both problems are present, report as two separate findings.
|
|
88
|
+
source: code
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Threshold
|
|
94
|
+
|
|
95
|
+
`no-critical` — INT-1 and INT-2 are `critical`: a task that cannot prove end-to-end
|
|
96
|
+
correctness and cannot prove its authorization boundary is not integration-tested and is
|
|
97
|
+
not done. INT-3 (JWT transaction pattern + pooler port) is `major`: incorrect plumbing in
|
|
98
|
+
the test setup produces false-positive passes (the test appears to work without RLS firing),
|
|
99
|
+
which is a confidence-undermining failure — but it is one step removed from observable
|
|
100
|
+
breakage and is reported as major rather than critical.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Bug template
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
severity: [critical|major]
|
|
108
|
+
criterion: [INT-1|INT-2|INT-3]
|
|
109
|
+
location: <test file path | package/test/setup.ts | .env.test | file:line>
|
|
110
|
+
repro: <e.g. "grep -rE 'jest.mock.*repository' test/" | "grep DATABASE_URL .env.test" | "pnpm --filter api test:e2e">
|
|
111
|
+
expected: <e.g. "integration test exists targeting BoardRepository with real DB" | "DATABASE_URL ends with :6543/...">
|
|
112
|
+
actual: <e.g. "no *.integration.spec.ts in apps/api/src/boards/" | "DATABASE_URL uses port 5432" | "vi.mock('../../infrastructure/BoardRepository')">
|
|
113
|
+
fix_hint: <e.g. "add boards/boards.integration.spec.ts; use PrismaService with a real test DB; wrap queries in $transaction with set_config">
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Probing notes
|
|
117
|
+
|
|
118
|
+
- Integration test scripts are often separate from unit tests: `test:e2e` / `test:int` /
|
|
119
|
+
`test:integration` in package.json. Check all scripts, not just `test`.
|
|
120
|
+
- For mocking detection: `grep -rn "jest\.mock\|vi\.mock" <test-file>` and inspect what
|
|
121
|
+
is being mocked. Mocking HTTP clients, external APIs, or filesystem utilities is fine;
|
|
122
|
+
mocking the Prisma client or the repository class defeats integration testing.
|
|
123
|
+
- For port check: the integration DB URL is typically in `.env.test` or `jest.config.ts`
|
|
124
|
+
(globalSetup). Check `DATABASE_URL` and `DIRECT_URL` separately — some setups split them.
|
|
125
|
+
- For `.e2e` tasks: `supertest` or `axios` against the running NestJS server counts as
|
|
126
|
+
an integration test if it hits a real DB. Verify the test server's DB config, not just
|
|
127
|
+
the production server's config.
|
|
128
|
+
- Do not skip INT-3 because the test "seems to work." RLS without `set_config` silently
|
|
129
|
+
returns all rows as the app role — the test passes but the auth boundary is untested.
|