@harness-engineering/cli 2.6.1 → 2.7.1
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/dist/agents/skills/claude-code/align-design-system/SKILL.md +174 -0
- package/dist/agents/skills/claude-code/align-design-system/skill.yaml +57 -0
- package/dist/agents/skills/claude-code/audit-brand-compliance/SKILL.md +189 -0
- package/dist/agents/skills/claude-code/audit-brand-compliance/skill.yaml +50 -0
- package/dist/agents/skills/claude-code/audit-component-anatomy/SKILL.md +181 -0
- package/dist/agents/skills/claude-code/audit-component-anatomy/skill.yaml +51 -0
- package/dist/agents/skills/claude-code/copy-craft/SKILL.md +192 -0
- package/dist/agents/skills/claude-code/copy-craft/skill.yaml +52 -0
- package/dist/agents/skills/claude-code/detect-design-drift/SKILL.md +139 -0
- package/dist/agents/skills/claude-code/detect-design-drift/skill.yaml +50 -0
- package/dist/agents/skills/claude-code/harness-accessibility/SKILL.md +10 -0
- package/dist/agents/skills/claude-code/harness-design-craft/SKILL.md +212 -0
- package/dist/agents/skills/claude-code/harness-design-craft/skill.yaml +57 -0
- package/dist/agents/skills/claude-code/harness-design-pipeline/SKILL.md +266 -0
- package/dist/agents/skills/claude-code/harness-design-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/claude-code/knowledge-craft/SKILL.md +180 -0
- package/dist/agents/skills/claude-code/knowledge-craft/skill.yaml +49 -0
- package/dist/agents/skills/claude-code/naming-craft/SKILL.md +244 -0
- package/dist/agents/skills/claude-code/naming-craft/skill.yaml +51 -0
- package/dist/agents/skills/claude-code/security-craft/SKILL.md +205 -0
- package/dist/agents/skills/claude-code/security-craft/skill.yaml +58 -0
- package/dist/agents/skills/claude-code/spec-craft/SKILL.md +184 -0
- package/dist/agents/skills/claude-code/spec-craft/skill.yaml +55 -0
- package/dist/agents/skills/claude-code/test-craft/SKILL.md +212 -0
- package/dist/agents/skills/claude-code/test-craft/skill.yaml +58 -0
- package/dist/agents/skills/codex/align-design-system/SKILL.md +174 -0
- package/dist/agents/skills/codex/align-design-system/skill.yaml +57 -0
- package/dist/agents/skills/codex/audit-brand-compliance/SKILL.md +189 -0
- package/dist/agents/skills/codex/audit-brand-compliance/skill.yaml +50 -0
- package/dist/agents/skills/codex/audit-component-anatomy/SKILL.md +181 -0
- package/dist/agents/skills/codex/audit-component-anatomy/skill.yaml +51 -0
- package/dist/agents/skills/codex/copy-craft/SKILL.md +192 -0
- package/dist/agents/skills/codex/copy-craft/skill.yaml +52 -0
- package/dist/agents/skills/codex/detect-design-drift/SKILL.md +139 -0
- package/dist/agents/skills/codex/detect-design-drift/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-accessibility/SKILL.md +10 -0
- package/dist/agents/skills/codex/harness-design-craft/SKILL.md +212 -0
- package/dist/agents/skills/codex/harness-design-craft/skill.yaml +57 -0
- package/dist/agents/skills/codex/harness-design-pipeline/SKILL.md +266 -0
- package/dist/agents/skills/codex/harness-design-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/codex/knowledge-craft/SKILL.md +180 -0
- package/dist/agents/skills/codex/knowledge-craft/skill.yaml +49 -0
- package/dist/agents/skills/codex/naming-craft/SKILL.md +244 -0
- package/dist/agents/skills/codex/naming-craft/skill.yaml +51 -0
- package/dist/agents/skills/codex/security-craft/SKILL.md +205 -0
- package/dist/agents/skills/codex/security-craft/skill.yaml +58 -0
- package/dist/agents/skills/codex/spec-craft/SKILL.md +184 -0
- package/dist/agents/skills/codex/spec-craft/skill.yaml +55 -0
- package/dist/agents/skills/codex/test-craft/SKILL.md +212 -0
- package/dist/agents/skills/codex/test-craft/skill.yaml +58 -0
- package/dist/agents/skills/cursor/align-design-system/SKILL.md +174 -0
- package/dist/agents/skills/cursor/align-design-system/skill.yaml +57 -0
- package/dist/agents/skills/cursor/audit-brand-compliance/SKILL.md +189 -0
- package/dist/agents/skills/cursor/audit-brand-compliance/skill.yaml +50 -0
- package/dist/agents/skills/cursor/audit-component-anatomy/SKILL.md +181 -0
- package/dist/agents/skills/cursor/audit-component-anatomy/skill.yaml +51 -0
- package/dist/agents/skills/cursor/copy-craft/SKILL.md +192 -0
- package/dist/agents/skills/cursor/copy-craft/skill.yaml +52 -0
- package/dist/agents/skills/cursor/detect-design-drift/SKILL.md +139 -0
- package/dist/agents/skills/cursor/detect-design-drift/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-accessibility/SKILL.md +10 -0
- package/dist/agents/skills/cursor/harness-design-craft/SKILL.md +212 -0
- package/dist/agents/skills/cursor/harness-design-craft/skill.yaml +57 -0
- package/dist/agents/skills/cursor/harness-design-pipeline/SKILL.md +266 -0
- package/dist/agents/skills/cursor/harness-design-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/cursor/knowledge-craft/SKILL.md +180 -0
- package/dist/agents/skills/cursor/knowledge-craft/skill.yaml +49 -0
- package/dist/agents/skills/cursor/naming-craft/SKILL.md +244 -0
- package/dist/agents/skills/cursor/naming-craft/skill.yaml +51 -0
- package/dist/agents/skills/cursor/security-craft/SKILL.md +205 -0
- package/dist/agents/skills/cursor/security-craft/skill.yaml +58 -0
- package/dist/agents/skills/cursor/spec-craft/SKILL.md +184 -0
- package/dist/agents/skills/cursor/spec-craft/skill.yaml +55 -0
- package/dist/agents/skills/cursor/test-craft/SKILL.md +212 -0
- package/dist/agents/skills/cursor/test-craft/skill.yaml +58 -0
- package/dist/agents/skills/gemini-cli/align-design-system/SKILL.md +174 -0
- package/dist/agents/skills/gemini-cli/align-design-system/skill.yaml +57 -0
- package/dist/agents/skills/gemini-cli/audit-brand-compliance/SKILL.md +189 -0
- package/dist/agents/skills/gemini-cli/audit-brand-compliance/skill.yaml +50 -0
- package/dist/agents/skills/gemini-cli/audit-component-anatomy/SKILL.md +181 -0
- package/dist/agents/skills/gemini-cli/audit-component-anatomy/skill.yaml +51 -0
- package/dist/agents/skills/gemini-cli/copy-craft/SKILL.md +192 -0
- package/dist/agents/skills/gemini-cli/copy-craft/skill.yaml +52 -0
- package/dist/agents/skills/gemini-cli/detect-design-drift/SKILL.md +139 -0
- package/dist/agents/skills/gemini-cli/detect-design-drift/skill.yaml +50 -0
- package/dist/agents/skills/gemini-cli/harness-accessibility/SKILL.md +10 -0
- package/dist/agents/skills/gemini-cli/harness-design-craft/SKILL.md +212 -0
- package/dist/agents/skills/gemini-cli/harness-design-craft/skill.yaml +57 -0
- package/dist/agents/skills/gemini-cli/harness-design-pipeline/SKILL.md +266 -0
- package/dist/agents/skills/gemini-cli/harness-design-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/gemini-cli/knowledge-craft/SKILL.md +180 -0
- package/dist/agents/skills/gemini-cli/knowledge-craft/skill.yaml +49 -0
- package/dist/agents/skills/gemini-cli/naming-craft/SKILL.md +244 -0
- package/dist/agents/skills/gemini-cli/naming-craft/skill.yaml +51 -0
- package/dist/agents/skills/gemini-cli/security-craft/SKILL.md +205 -0
- package/dist/agents/skills/gemini-cli/security-craft/skill.yaml +58 -0
- package/dist/agents/skills/gemini-cli/spec-craft/SKILL.md +184 -0
- package/dist/agents/skills/gemini-cli/spec-craft/skill.yaml +55 -0
- package/dist/agents/skills/gemini-cli/test-craft/SKILL.md +212 -0
- package/dist/agents/skills/gemini-cli/test-craft/skill.yaml +58 -0
- package/dist/{agents-md-2JUQ4FE2.js → agents-md-FKMKH6ZF.js} +4 -4
- package/dist/{analyzer-VY6DJVOU-4AHIJLNS.js → analyzer-H3AHBFSL-KPOPJYRB.js} +8 -8
- package/dist/{architecture-CXAVNB5X.js → architecture-LUR2I67H.js} +6 -6
- package/dist/{assess-project-M626SSPN.js → assess-project-OJWECOP2.js} +2 -1
- package/dist/bin/harness-mcp.js +17 -17
- package/dist/bin/harness.js +27 -27
- package/dist/{check-phase-gate-XMU6LLUQ.js → check-phase-gate-HS5KYLRO.js} +5 -5
- package/dist/{chunk-5XLLIYYL.js → chunk-6UHDQP2N.js} +1 -1
- package/dist/{chunk-I6K43QQS.js → chunk-7AF6C5GE.js} +3 -3
- package/dist/{chunk-372MGNTI.js → chunk-7PLIWP7U.js} +6899 -133
- package/dist/{chunk-VUCGB2KH.js → chunk-B2VAYLYI.js} +1 -1
- package/dist/{chunk-HFOB2MPQ.js → chunk-CZPOPMMI.js} +3 -3
- package/dist/{chunk-43M3RLFQ.js → chunk-FC4JPM6W.js} +2 -2
- package/dist/{chunk-6AX6R3LM.js → chunk-FEKBXX2J.js} +9 -9
- package/dist/{chunk-VTTNIZJL.js → chunk-FXZI6NJ2.js} +131 -6
- package/dist/{chunk-5JNSFYZU.js → chunk-GGSNWH7P.js} +6 -6
- package/dist/{chunk-BCFDGOMA.js → chunk-H2ZJOJ7A.js} +81 -1
- package/dist/{chunk-PFZEADQM.js → chunk-INBTDBV2.js} +79 -2
- package/dist/{chunk-Q6ZR2L6Q.js → chunk-KN3EMDZ5.js} +2 -2
- package/dist/{chunk-JFZOWO7D.js → chunk-LECXPXS3.js} +1 -1
- package/dist/{chunk-CQOMVZDC.js → chunk-LGYXR2KZ.js} +2237 -604
- package/dist/{chunk-QOKMDDBJ.js → chunk-LYIPI2SJ.js} +6 -6
- package/dist/{chunk-L5BCZ5XS.js → chunk-N5NSSLMU.js} +1 -1
- package/dist/{chunk-GWE5LL3R.js → chunk-NIVWM7OW.js} +10 -10
- package/dist/{chunk-24KCOJJG.js → chunk-O2ASYKA6.js} +1 -1
- package/dist/{chunk-K246XQ2L.js → chunk-PTHX545Q.js} +1 -1
- package/dist/{chunk-EPUKTTJZ.js → chunk-RC3OI5NK.js} +5 -1
- package/dist/{chunk-YP7I25SL.js → chunk-RJ2WEES5.js} +1 -1
- package/dist/{chunk-5FDBXUFW.js → chunk-TIH53QXY.js} +1 -1
- package/dist/{chunk-OA6SWTU4.js → chunk-VI4IS55S.js} +11 -8
- package/dist/{chunk-GWF2OILZ.js → chunk-VSIRUZQP.js} +1 -1
- package/dist/{chunk-YYRQCQPZ.js → chunk-WZY5U6NS.js} +178 -109
- package/dist/{chunk-SZEFU6XC.js → chunk-Y4T6ENKQ.js} +9 -9
- package/dist/{chunk-HMW3VKUF.js → chunk-Z3TMCCZB.js} +10 -10
- package/dist/{ci-workflow-3JNDZQYD.js → ci-workflow-MDSTHJOY.js} +4 -4
- package/dist/{dist-OHDQBTSO.js → dist-72ID44UE.js} +3 -3
- package/dist/{dist-QPWXPCPL.js → dist-R3TOOPJ7.js} +1 -1
- package/dist/{docs-5RYMDHN3.js → docs-2RFOJ6XY.js} +6 -6
- package/dist/{engine-GKGT5ULT.js → engine-BSFKOGPN.js} +4 -4
- package/dist/{entropy-DQTAPSSE.js → entropy-3UX6644G.js} +5 -5
- package/dist/{feedback-WH6XPQJS.js → feedback-CVCFYND4.js} +2 -2
- package/dist/{generate-agent-definitions-SZA4QIHN.js → generate-agent-definitions-HZ3XENWO.js} +5 -5
- package/dist/{glob-helper-XE2UGEOV.js → glob-helper-EX4YZKZO.js} +1 -1
- package/dist/{graph-loader-EQBOIHFZ.js → graph-loader-EIFEOUVI.js} +1 -1
- package/dist/index.d.ts +1012 -12
- package/dist/index.js +27 -27
- package/dist/{loader-BNKGM23C.js → loader-6RHWU5FH.js} +4 -4
- package/dist/{mcp-YKKPWUMH.js → mcp-OYR26JDI.js} +17 -17
- package/dist/{performance-54TMJOEU.js → performance-JJMIRXCH.js} +6 -6
- package/dist/{review-pipeline-OTDDVPNY.js → review-pipeline-BHQZIXWZ.js} +4 -4
- package/dist/{runtime-W5THSNAL.js → runtime-DALTAVTN.js} +4 -4
- package/dist/{scan-4PPP6ZXT.js → scan-LYKLM4EQ.js} +1 -1
- package/dist/{security-27HW7CYT.js → security-RMLMYFPB.js} +1 -1
- package/dist/{validate-QRVCD4GP.js → validate-R7DZRDFK.js} +5 -5
- package/dist/{validate-cross-check-BUAGBAPO.js → validate-cross-check-AUUZFNVL.js} +4 -4
- package/package.json +8 -7
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Test Craft
|
|
2
|
+
|
|
3
|
+
> LLM-judgment critique of test quality across vitest / jest / mocha / playwright. Fourth member of the craft-pipeline initiative. Per-`it`/`test` block critique with best-effort source pairing for contract-vs-implementation rubrics. Tests are often the worst-written code in a codebase precisely because the rule-based floor (coverage threshold) is so easy to clear. Emits 3-axis findings (tier × impact × confidence per ADR 0019).
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- During PR review on code that adds or changes tests
|
|
8
|
+
- After ramping up coverage to audit whether new tests actually add signal
|
|
9
|
+
- When onboarding a contributor — audit tests they introduced
|
|
10
|
+
- Periodically to catch accumulated low-signal tests + redundant fixtures
|
|
11
|
+
- NOT for coverage analysis (use `vitest --coverage` or harness-tdd — that's the floor)
|
|
12
|
+
- NOT for autofix / rewriting (v2's `align-test` may add safe renames)
|
|
13
|
+
- NOT for `.test-d.ts` type tests in v1 (v1.x — different rubric vocabulary)
|
|
14
|
+
- NOT for fixture / helper / mock files in v1 (v1.x)
|
|
15
|
+
- NOT for snapshot tests in v1 (different correctness criteria; v1.x)
|
|
16
|
+
- NOT for non-TS/JS languages in v1 (v1.x)
|
|
17
|
+
|
|
18
|
+
## Process
|
|
19
|
+
|
|
20
|
+
### Phase 1: DISCOVER — Test files + framework
|
|
21
|
+
|
|
22
|
+
1. **Read project configuration.** Check `harness.config.json` for:
|
|
23
|
+
- `craft.test.enabled` — gate (default `true`)
|
|
24
|
+
- `craft.test.maxFiles` (default 100), `craft.test.maxTestsPerFile` (default 20)
|
|
25
|
+
- `craft.test.frameworks` — restrict to subset (default: all four)
|
|
26
|
+
- `craft.test.sourcePair` — toggle source-pairing (default true)
|
|
27
|
+
|
|
28
|
+
2. **Glob test files** under project root: `**/*.{test,spec}.{ts,tsx,js,jsx}`. Skip `node_modules`, `dist`, `build`, `coverage`, dotdirs.
|
|
29
|
+
|
|
30
|
+
3. **Detect framework per file** via import signatures (order matters; most-specific first):
|
|
31
|
+
- `@playwright/test` → playwright
|
|
32
|
+
- `@jest/globals` → jest
|
|
33
|
+
- `vitest` → vitest
|
|
34
|
+
- `import 'mocha'` → mocha
|
|
35
|
+
- Fallback: vitest (most common; jest-with-globals projects still extract correctly because the AST shape matches)
|
|
36
|
+
|
|
37
|
+
### Phase 2: EXTRACT — Per-test AST walk
|
|
38
|
+
|
|
39
|
+
Single TS Compiler API walk per file. For each `CallExpression`:
|
|
40
|
+
|
|
41
|
+
- **describe** — push name onto nesting stack, recurse, pop
|
|
42
|
+
- **it / test** — extract `testName` (first string-literal arg), capture current nesting, capture callback body text (truncated to 1500 chars in prompt)
|
|
43
|
+
- Modifiers handled: `.skip`, `.only`, `.todo` (todo excluded from critique — no body to critique)
|
|
44
|
+
- Non-string-literal test names (computed / templates) skip silently
|
|
45
|
+
|
|
46
|
+
### Phase 3: PAIR — Best-effort source resolution
|
|
47
|
+
|
|
48
|
+
For each test file, try in order:
|
|
49
|
+
|
|
50
|
+
1. **Sibling** — `foo.test.ts` → `foo.ts` (same dir)
|
|
51
|
+
2. **Co-located in src** — `tests/foo.test.ts` → `../src/foo.ts`
|
|
52
|
+
3. **Monorepo-style** — `tests/foo.test.ts` → `../../src/foo.ts`
|
|
53
|
+
|
|
54
|
+
When source resolves, content (truncated to 2000 chars) is added to the LLM prompt under `Source under test:`. This enables `TEST-R007` (contract-not-implementation) to actually compare assertions against the function's public surface. When no source resolves, test-file-only rubrics still fire.
|
|
55
|
+
|
|
56
|
+
### Phase 4: CRITIQUE — Per (test, rubric) loop
|
|
57
|
+
|
|
58
|
+
8 seed rubrics:
|
|
59
|
+
|
|
60
|
+
| Rubric | Description |
|
|
61
|
+
| --------------------------------------- | --------------------------------------------------------------------------- |
|
|
62
|
+
| `TEST-R001` contract-not-narrative-name | Test name describes the contract ("returns null when empty"), not narrative |
|
|
63
|
+
| `TEST-R002` meaningful-assertion | Assertion proves something the implementation could plausibly violate |
|
|
64
|
+
| `TEST-R003` arrange-act-assert | Three visually distinct phases, not interleaved |
|
|
65
|
+
| `TEST-R004` fixture-earns-setup-cost | Heavy beforeEach must be justified by what's asserted |
|
|
66
|
+
| `TEST-R005` single-responsibility | One assertion target per `it`; multiple unrelated assertions split poorly |
|
|
67
|
+
| `TEST-R006` deleting-loses-something | Would removing this test lose specific coverage, or is it redundant? |
|
|
68
|
+
| `TEST-R007` contract-not-implementation | Tests the documented behaviour, not the internal structure |
|
|
69
|
+
| `TEST-R008` explicit-failure-mode | Failure message narrates what went wrong without reading the test |
|
|
70
|
+
|
|
71
|
+
For each (test, rubric) pair:
|
|
72
|
+
|
|
73
|
+
1. Build prompt with rubric + test (name + nesting + body) + optional source + framework label.
|
|
74
|
+
2. LLM returns fenced JSON: `null` (rubric doesn't apply / test is fine) OR `{ tier, impact, confidence, message }`.
|
|
75
|
+
3. On non-null: emit `TestFinding` with `cite.rubricId` for ADR 0020 traceability.
|
|
76
|
+
|
|
77
|
+
### Phase 5: REPORT — Aggregate + cost telemetry
|
|
78
|
+
|
|
79
|
+
Emit `TestCraftOutput`:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
{
|
|
83
|
+
findings: TestFinding[];
|
|
84
|
+
summary: {
|
|
85
|
+
phaseRun: ['critique'];
|
|
86
|
+
durationMs: number;
|
|
87
|
+
llmCalls: { provider, model, count, costUsd };
|
|
88
|
+
catalog: { rubricsApplied: string[] };
|
|
89
|
+
counts: { filesScanned, testsExtracted, testsSkippedOrTodo, sourcePaired };
|
|
90
|
+
frameworksDetected: Record<TestFramework, number>;
|
|
91
|
+
runId: string;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Harness Integration
|
|
97
|
+
|
|
98
|
+
- **`harness test-craft`** — CLI entry. `--files` / `--frameworks` / `--max-files` / `--max-tests-per-file` / `--no-source-pair` / `--json` / `--verbose`.
|
|
99
|
+
- **`mcp__harness__test_craft`** — MCP tool. Same input/output. Consumed by agents.
|
|
100
|
+
- **Cross-cutting API:** `critiqueTestsInFile(file, opts)` exported. Works on a single test file without project walk; honours framework filter and source-pairing toggle.
|
|
101
|
+
- **Shared craft infrastructure:** imports `LlmProvider` + 3-axis types + `derivePriority` from `packages/cli/src/shared/craft/`.
|
|
102
|
+
|
|
103
|
+
## Success Criteria
|
|
104
|
+
|
|
105
|
+
See `docs/changes/craft-pipeline/test-craft/proposal.md` for the full 36 success criteria. Highlights:
|
|
106
|
+
|
|
107
|
+
- 8 seed rubrics ship in `catalog/rubrics/<id>.ts` (file-per-rubric, matches naming/spec/copy-craft)
|
|
108
|
+
- 3-axis output preserved (tier × impact × confidence)
|
|
109
|
+
- `cite.rubricId` populated on every finding (ADR 0020)
|
|
110
|
+
- Framework detection: `@playwright/test` / `@jest/globals` / `vitest` / `mocha` import signatures; vitest fallback
|
|
111
|
+
- Per-test extraction handles `.skip` / `.only` / `.todo` with metadata; describe-chain nesting captured
|
|
112
|
+
- Source pairing best-effort with silent skip when no match
|
|
113
|
+
- Plugin slash-commands pre-generated (avoids CI drift failure pattern)
|
|
114
|
+
|
|
115
|
+
## Examples
|
|
116
|
+
|
|
117
|
+
### Example: Narrative test name
|
|
118
|
+
|
|
119
|
+
**Input:** `src/parse.test.ts`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
describe('parseTokens', () => {
|
|
123
|
+
it('works correctly', () => {
|
|
124
|
+
expect(parseTokens('a,b,c')).toEqual(['a', 'b', 'c']);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Output (mock LLM):**
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
src/parse.test.ts
|
|
133
|
+
TEST-R001 [polish/medium/high] vitest:2
|
|
134
|
+
parseTokens > works correctly
|
|
135
|
+
"works correctly" narrates the test without naming the contract. Try
|
|
136
|
+
"splits comma-separated input into trimmed segments" or "preserves
|
|
137
|
+
order across the split".
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Example: Tautological assertion
|
|
141
|
+
|
|
142
|
+
**Input:**
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
it('returns a user', () => {
|
|
146
|
+
const user = createUser({ name: 'a' });
|
|
147
|
+
expect(user).toBeDefined();
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Output:**
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
TEST-R002 [foundational/medium/high] vitest:8
|
|
155
|
+
createUser > returns a user
|
|
156
|
+
`toBeDefined()` after `createUser(...)` cannot fail — the return is
|
|
157
|
+
always defined by the function's signature. Assert the contract:
|
|
158
|
+
`expect(user.name).toBe('a')` or `expect(user).toMatchObject({...})`.
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Example: Heavy fixture, trivial assertion
|
|
162
|
+
|
|
163
|
+
**Input:**
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
it('handles input', () => {
|
|
167
|
+
// 47 lines of beforeEach setup that mocks 6 collaborators
|
|
168
|
+
const result = handler.handle({});
|
|
169
|
+
expect(result).toBeTruthy();
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Output:**
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
TEST-R004 [polish/large/medium] vitest:142
|
|
177
|
+
handler > handles input
|
|
178
|
+
47-line beforeEach + 6 mocks produce a result that's asserted with
|
|
179
|
+
`toBeTruthy`. The fixture's complexity exceeds the test's signal —
|
|
180
|
+
either drop the fixture and use a simpler stub, or assert the
|
|
181
|
+
specific outcome that justified the setup (e.g., which collaborator
|
|
182
|
+
was called with what arguments).
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Gates
|
|
186
|
+
|
|
187
|
+
- **No autofix.** v2's `align-test`.
|
|
188
|
+
- **No coverage analysis.** That's the floor (`vitest --coverage`).
|
|
189
|
+
- **No `.test-d.ts` type tests** (v1.x).
|
|
190
|
+
- **No fixture / helper / mock file critique** (v1.x).
|
|
191
|
+
- **No snapshot rubrics** (v1.x).
|
|
192
|
+
- **No non-TS/JS language support** (v1.x).
|
|
193
|
+
- **No B' bootstrap.** Same posture as naming/spec/copy-craft.
|
|
194
|
+
- **No graph persistence.** Phase 1 MVP.
|
|
195
|
+
|
|
196
|
+
## Escalation
|
|
197
|
+
|
|
198
|
+
- **When LLM cost is too high:** drop `maxTestsPerFile` (default 20) or scope to specific frameworks with `--frameworks vitest`. Disable source-pairing with `--no-source-pair` to halve prompt size on every test.
|
|
199
|
+
- **When intentionally narrative test names get flagged (e.g., learning tests, examples):** scope via `--files` to exclude. v1.x adds `// test-craft:skip` annotation.
|
|
200
|
+
- **When source-pairing finds the wrong source for ambiguous names:** the LLM gets misleading context for `TEST-R007`. Use `--no-source-pair` to fall back to test-file-only rubrics, or v1.x's `harness.config.json` test→source mapping.
|
|
201
|
+
- **When jest-with-globals tests aren't detected:** the framework defaults to vitest (AST is compatible); critique still runs. If you need jest tagging specifically, add `import { describe, it } from '@jest/globals'` to make the signature explicit.
|
|
202
|
+
- **When `it.each` / `test.each` blocks get critiqued as a single test:** v1 captures the `each` invocation as one item with a generic name pattern. v1.x adds per-iteration critique.
|
|
203
|
+
|
|
204
|
+
## Status
|
|
205
|
+
|
|
206
|
+
**v1 — in implementation.** See:
|
|
207
|
+
|
|
208
|
+
- Spec: `docs/changes/craft-pipeline/test-craft/proposal.md`
|
|
209
|
+
- Roadmap entry: `craft-pipeline sub-project #3`
|
|
210
|
+
- Sibling craft skills: `naming-craft` (#1), `spec-craft` (#6), `copy-craft` (#5)
|
|
211
|
+
- Shared infrastructure: `packages/cli/src/shared/craft/`
|
|
212
|
+
- Future: `align-test` (FIX side, v2), docs-craft (#2), code-craft (#4)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: test-craft
|
|
2
|
+
version: "0.1.0"
|
|
3
|
+
description: LLM-judgment critique of test quality across vitest / jest / mocha / playwright. Fourth craft-pipeline ceiling skill. Per-test critique with best-effort source pairing for contract-vs-implementation rubrics. Tests are often the worst-written code in a codebase precisely because the rule-based floor is so easy to clear.
|
|
4
|
+
stability: draft
|
|
5
|
+
cognitive_mode: constructive-architect
|
|
6
|
+
triggers:
|
|
7
|
+
- manual
|
|
8
|
+
- on_pr
|
|
9
|
+
- on_new_feature
|
|
10
|
+
platforms:
|
|
11
|
+
- claude-code
|
|
12
|
+
tools:
|
|
13
|
+
- Bash
|
|
14
|
+
- Read
|
|
15
|
+
- Glob
|
|
16
|
+
- Grep
|
|
17
|
+
cli:
|
|
18
|
+
command: harness test-craft
|
|
19
|
+
args:
|
|
20
|
+
- name: path
|
|
21
|
+
description: Project root path
|
|
22
|
+
required: false
|
|
23
|
+
- name: files
|
|
24
|
+
description: Optional test file/glob scope
|
|
25
|
+
required: false
|
|
26
|
+
- name: frameworks
|
|
27
|
+
description: Restrict to vitest/jest/mocha/playwright
|
|
28
|
+
required: false
|
|
29
|
+
- name: no-source-pair
|
|
30
|
+
description: Skip source-pairing resolution
|
|
31
|
+
required: false
|
|
32
|
+
mcp:
|
|
33
|
+
tool: test_craft
|
|
34
|
+
input:
|
|
35
|
+
path: string
|
|
36
|
+
type: rigid
|
|
37
|
+
tier: 2
|
|
38
|
+
phases:
|
|
39
|
+
- name: discover
|
|
40
|
+
description: Glob test files; detect framework per-file via import signatures
|
|
41
|
+
required: true
|
|
42
|
+
- name: extract
|
|
43
|
+
description: Walk TS AST for per-it/test block with nesting, skip/todo/only flags
|
|
44
|
+
required: true
|
|
45
|
+
- name: pair
|
|
46
|
+
description: Best-effort resolve source file under test (sibling / ../src / monorepo)
|
|
47
|
+
required: false
|
|
48
|
+
- name: critique
|
|
49
|
+
description: LLM-rubric loop per (test, rubric); 8 seed rubrics
|
|
50
|
+
required: true
|
|
51
|
+
- name: report
|
|
52
|
+
description: Aggregate findings + frameworks-detected counts + cost telemetry
|
|
53
|
+
required: true
|
|
54
|
+
state:
|
|
55
|
+
persistent: false
|
|
56
|
+
depends_on:
|
|
57
|
+
- harness-tdd
|
|
58
|
+
- harness-design-craft
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Align Design System
|
|
2
|
+
|
|
3
|
+
> Apply codemods for safe DRIFT-T001/T002/T003 token-bypass findings (replace hex / font-family / px-spacing literals with token references) and emit precise suggestions for DRIFT-T004 (deprecated tokens) and all DRIFT-P\* (primitive adoption). The FIX half of design-pipeline sub-project #1, paired with detect-design-drift.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- After detect-design-drift reports DRIFT-T001/T002/T003 findings — align replaces literals with token references where the fix is unambiguous
|
|
8
|
+
- Before a PR that touches UI code lands — pair with detect-design-drift to surface AND fix drift in one shot
|
|
9
|
+
- Inside a (future) #5 design-pipeline orchestrator's convergence loop — align is the FIX step the loop runs between DETECT and VERIFY
|
|
10
|
+
- When you want to dry-run the fixes first (`--dry-run`) and review the diff before writing
|
|
11
|
+
- NOT for primitive-adoption codemods — v1 emits suggestions only for DRIFT-P\*; the prop-translation work lives in v1.x
|
|
12
|
+
- NOT for adding new tokens to tokens.json (no auto-add of palette entries; that's a separate intentional act)
|
|
13
|
+
- NOT for non-design-system fixes (use cleanup-dead-code, align-documentation, etc. for their respective domains)
|
|
14
|
+
|
|
15
|
+
## Process
|
|
16
|
+
|
|
17
|
+
### Phase 1: GATHER — Load drift findings
|
|
18
|
+
|
|
19
|
+
1. **Read project configuration.** Check `harness.config.json` for:
|
|
20
|
+
- `design.strictness` — passes through to detect-design-drift
|
|
21
|
+
- `design.audit.driftDetection.*` — passes through to detect
|
|
22
|
+
|
|
23
|
+
2. **In standalone mode (default):** invoke `detect-design-drift` internally with the same project root + strictness; receive the full `DriftFinding[]`.
|
|
24
|
+
|
|
25
|
+
3. **In pipeline mode:** read `.harness/handoff.json` and pull `pipeline.driftFindings` (pre-classified by the orchestrator). Honor `pipeline.fixBatch` if present to limit application to a specific subset of findings (the orchestrator may apply fixes in batches across iterations).
|
|
26
|
+
|
|
27
|
+
### Phase 2: CLASSIFY — Pre-flight safe-codemod vs suggestion
|
|
28
|
+
|
|
29
|
+
For each finding, the pre-flight classifier inspects file context and chooses:
|
|
30
|
+
|
|
31
|
+
- **DRIFT-T001 (hex)** — safe-codemod iff token import already present in the file AND hex is in single string-literal context (not a template literal or concatenation) AND exactly one palette token matches by value. Otherwise → suggestion.
|
|
32
|
+
- **DRIFT-T002 (font-family)** — same shape as T001 against the typography palette.
|
|
33
|
+
- **DRIFT-T003 (px spacing)** — safe-codemod iff token import present AND px matches a spacing token's `$value` EXACTLY (no rounding) AND not in an arithmetic expression. Otherwise → suggestion.
|
|
34
|
+
- **DRIFT-T004 (deprecated)** — always suggestion in v1. Migration target may not be in the token's `$description`.
|
|
35
|
+
- **DRIFT-P\* (primitive adoption)** — always suggestion in v1. Prop translation across `<button>` ⇄ `<Button>` is genuinely ambiguous (event handlers, ref forwarding, class merging) — codemods deferred to v1.x.
|
|
36
|
+
|
|
37
|
+
Token import discovery recognizes three forms:
|
|
38
|
+
|
|
39
|
+
- ES named: `import { tokens } from '...'`
|
|
40
|
+
- ES default: `import tokens from '...'`
|
|
41
|
+
- CJS: `const tokens = require('...')`
|
|
42
|
+
|
|
43
|
+
### Phase 3: APPLY — Codemod or emit suggestion
|
|
44
|
+
|
|
45
|
+
For each finding classified as `safe-codemod`:
|
|
46
|
+
|
|
47
|
+
1. Read the source file (cached per-run to avoid re-reads when multiple findings hit the same file).
|
|
48
|
+
2. Locate the exact match position from the finding's `line` + `evidence.snippet`.
|
|
49
|
+
3. Compute the replacement using file-extension-aware syntax:
|
|
50
|
+
- `.ts` / `.tsx` / `.js` / `.jsx` → `tokens.<dotted.path>`
|
|
51
|
+
- `.css` / `.scss` → `var(--<dotted-path-as-kebab>)`
|
|
52
|
+
4. Replace in-place. Emit `FixOutcome.applied` with a structured diff (file / line / before / after).
|
|
53
|
+
5. If `--dry-run`: compute the diff but DO NOT write to disk.
|
|
54
|
+
6. If the file changed between detect-time and apply-time: skip with `kind: 'skipped-unsafe', reason: 'file changed since finding'`.
|
|
55
|
+
|
|
56
|
+
For each finding classified as `suggestion`: emit a human-readable description plus a preview of the suggested change. No file mutation.
|
|
57
|
+
|
|
58
|
+
### Phase 4: REPORT — Aggregate + (pipeline-mode) handoff writeback
|
|
59
|
+
|
|
60
|
+
1. Aggregate `FixOutcome[]` into a summary: counts by kind (applied / suggestion / skipped / failed) plus files modified and duration.
|
|
61
|
+
2. Build a `catalog` of finding codes that produced codemods and codes that produced suggestions.
|
|
62
|
+
3. In pipeline mode: write `pipeline.fixesApplied: FixOutcome[]` back to `.harness/handoff.json` so the orchestrator can re-verify only the affected findings on the next loop iteration.
|
|
63
|
+
|
|
64
|
+
## Harness Integration
|
|
65
|
+
|
|
66
|
+
- **`harness align-design-system`** — the CLI entry point. `--dry-run` for preview; `--write` is the default. Standard `--json` / `--verbose` / `--quiet` flags.
|
|
67
|
+
- **`harness align-design-system --mode pipeline`** — orchestrator-driven mode. Reads pre-classified findings from handoff.json; writes outcomes back.
|
|
68
|
+
- **`mcp__harness__align_design_system`** — MCP tool for agent consumption. Same input/output shape as the function call.
|
|
69
|
+
- **`detect-design-drift`** — soft dependency. Standalone mode invokes detect internally; pipeline mode trusts the orchestrator to have done it.
|
|
70
|
+
- **`harness check-design`** — composes detect (as 3rd verifier) into a single-pass design check. align is the matching FIX step; together they form the DETECT → FIX cycle that the (future) #5 orchestrator will loop.
|
|
71
|
+
- **`DesignConstraintAdapter`** — align does NOT write to the graph. The graph already tracks `VIOLATES_design` edges (the findings). Re-running detect after align shows the delta — no separate fix-edge needed.
|
|
72
|
+
|
|
73
|
+
## Success Criteria
|
|
74
|
+
|
|
75
|
+
See `docs/changes/design-pipeline/align-design-system/proposal.md` for the full 34 success criteria. Highlights:
|
|
76
|
+
|
|
77
|
+
- T001/T002/T003 codemods write to disk only when pre-flight classifier returns `safe-codemod`
|
|
78
|
+
- Codemods are idempotent — running twice produces zero additional changes on the second run
|
|
79
|
+
- Classifier downgrades to suggestion when token import is missing, when value appears in template/concatenation, or when multiple tokens share the value
|
|
80
|
+
- Classifier ALWAYS returns suggestion for T004 + all P\* findings (no source inspection)
|
|
81
|
+
- Pipeline mode reads `pipeline.driftFindings` from handoff.json; standalone mode runs detect internally
|
|
82
|
+
- Pipeline mode writes `pipeline.fixesApplied` back to handoff.json
|
|
83
|
+
- `--dry-run` produces identical `FixOutcome` shapes but never writes files
|
|
84
|
+
- Re-running detect after align produces strictly fewer T001/T002/T003 findings
|
|
85
|
+
|
|
86
|
+
## Examples
|
|
87
|
+
|
|
88
|
+
### Example: Apply a T001 codemod (hex → token reference)
|
|
89
|
+
|
|
90
|
+
**Input:**
|
|
91
|
+
|
|
92
|
+
`design-system/tokens.json` has:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{ "color": { "brand": { "primary": { "$type": "color", "$value": "#0066cc" } } } }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`src/Card.tsx` has:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { tokens } from '@/design-system/tokens';
|
|
102
|
+
const styles = { color: '#0066cc' }; // raw literal where token exists
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Output:**
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
src/Card.tsx
|
|
109
|
+
✓ DRIFT-T001:2 — Hex color "#0066cc" should use a token reference instead of a raw literal
|
|
110
|
+
before: const styles = { color: "#0066cc" };
|
|
111
|
+
after: const styles = { color: tokens.color.brand.primary };
|
|
112
|
+
|
|
113
|
+
Summary: 1 applied, 0 suggestions, 0 skipped, 0 failed (1 files modified, 5ms)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Example: Downgrade to suggestion (no token import)
|
|
117
|
+
|
|
118
|
+
Same finding as above, but `src/Card.tsx` does NOT import tokens. align emits a suggestion instead:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
src/Card.tsx
|
|
122
|
+
? DRIFT-T001:2 — Hex color "#0066cc" should use a token reference instead of a raw literal
|
|
123
|
+
|
|
124
|
+
Summary: 0 applied, 1 suggestions, 0 skipped, 0 failed (0 files modified, 3ms)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(Run with `--verbose` to see the suggestion text and the classifier's reason for downgrading.)
|
|
128
|
+
|
|
129
|
+
### Example: Suggestion-only for DRIFT-P001
|
|
130
|
+
|
|
131
|
+
**Input:**
|
|
132
|
+
|
|
133
|
+
`design-system/DESIGN.md` registers `Button` in `## Component Registry`. `src/SaveButton.tsx` has:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
export const S = () => <button onClick={() => save()}>Save</button>;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Output:**
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
src/SaveButton.tsx
|
|
143
|
+
? DRIFT-P001:1 — Raw <button> element where the registered component "Button" should be used
|
|
144
|
+
|
|
145
|
+
(use --verbose to see prop-translation suggestion)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
v1 never auto-applies primitive adoption — prop translation across `<button>` ⇄ `<Button>` is the kind of judgment-call that benefits from a human or LLM review.
|
|
149
|
+
|
|
150
|
+
## Gates
|
|
151
|
+
|
|
152
|
+
- **No autofix without classifier approval.** Every codemod application goes through `classifyFinding`. If the classifier returns `suggestion`, NO file write occurs — even for the same finding code in the same file.
|
|
153
|
+
- **No autofix when token import is missing.** Adding the import line is its own ambiguity surface (alias? barrel? relative?). v1 skips with a suggestion.
|
|
154
|
+
- **No autofix for non-exact px matches.** "Round 13px to the nearest 16px" is a design decision the tool shouldn't make.
|
|
155
|
+
- **No autofix for primitive adoption.** v1 has no codemod for DRIFT-P\* — always suggestions.
|
|
156
|
+
- **No autofix in pipeline mode unless a `pipeline.driftFindings` field is present.** Empty handoff = empty run.
|
|
157
|
+
- **No graph writes.** align modifies source files; the graph is read-only from align's perspective.
|
|
158
|
+
|
|
159
|
+
## Escalation
|
|
160
|
+
|
|
161
|
+
- **When a T001 finding has multiple matching tokens (ambiguous):** the classifier downgrades to suggestion. To resolve, declare a primary token in DESIGN.md `## Token Primary Resolution Overrides` (v1.x), OR pick one in the source manually and re-run.
|
|
162
|
+
- **When the codemod corrupts a file (rare):** every application includes a structured diff. Recover via `git checkout <file>`. If the same input repeatedly corrupts, report the case — pre-flight classifier rules are conservative by design.
|
|
163
|
+
- **When pipeline-mode run finds no `pipeline.driftFindings` field:** align exits cleanly with empty outcomes. The orchestrator's contract is to write the field BEFORE invoking align.
|
|
164
|
+
- **When `--dry-run` shows fixes you don't want applied:** scope with `--files <glob>` to apply only specific files, or invoke align in pipeline mode with a curated `pipeline.fixBatch` list.
|
|
165
|
+
- **When you want primitive-adoption fixes today:** apply the suggestion manually. The v1.x sub-project will add prop-translation tables + import resolution + revert-on-test-fail.
|
|
166
|
+
- **When align is invoked without detect having run first (standalone mode):** standalone mode runs detect internally — no manual ordering needed. Pipeline mode trusts the orchestrator to populate findings.
|
|
167
|
+
|
|
168
|
+
## Status
|
|
169
|
+
|
|
170
|
+
**v1 — in implementation.** See:
|
|
171
|
+
|
|
172
|
+
- Spec: `docs/changes/design-pipeline/align-design-system/proposal.md`
|
|
173
|
+
- Roadmap entry: `design-pipeline sub-project #1` (align half) in `docs/roadmap.md`
|
|
174
|
+
- Sibling: `detect-design-drift` (detect half — shipped PR #396)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: align-design-system
|
|
2
|
+
version: "0.1.0"
|
|
3
|
+
description: Apply codemods for safe DRIFT-T001/T002/T003 token-bypass findings; emit precise suggestions for DRIFT-T004 (deprecated tokens) and all DRIFT-P* (primitive adoption). FIX half of design-pipeline sub-project #1; pairs with detect-design-drift.
|
|
4
|
+
stability: draft
|
|
5
|
+
cognitive_mode: constructive-architect
|
|
6
|
+
triggers:
|
|
7
|
+
- manual
|
|
8
|
+
- on_pr
|
|
9
|
+
- on_new_feature
|
|
10
|
+
platforms:
|
|
11
|
+
- claude-code
|
|
12
|
+
tools:
|
|
13
|
+
- Bash
|
|
14
|
+
- Read
|
|
15
|
+
- Write
|
|
16
|
+
- Edit
|
|
17
|
+
- Glob
|
|
18
|
+
- Grep
|
|
19
|
+
cli:
|
|
20
|
+
command: harness align-design-system
|
|
21
|
+
args:
|
|
22
|
+
- name: path
|
|
23
|
+
description: Project root path
|
|
24
|
+
required: false
|
|
25
|
+
- name: dry-run
|
|
26
|
+
description: Compute diffs without writing files
|
|
27
|
+
required: false
|
|
28
|
+
- name: files
|
|
29
|
+
description: Optional file/glob scope (standalone mode only)
|
|
30
|
+
required: false
|
|
31
|
+
- name: mode
|
|
32
|
+
description: standalone (default) or pipeline (read findings from handoff.json)
|
|
33
|
+
required: false
|
|
34
|
+
mcp:
|
|
35
|
+
tool: run_skill
|
|
36
|
+
input:
|
|
37
|
+
skill: align-design-system
|
|
38
|
+
path: string
|
|
39
|
+
type: rigid
|
|
40
|
+
tier: 2
|
|
41
|
+
phases:
|
|
42
|
+
- name: gather
|
|
43
|
+
description: Load drift findings (run detect-design-drift in standalone mode; read pipeline.driftFindings in pipeline mode)
|
|
44
|
+
required: true
|
|
45
|
+
- name: classify
|
|
46
|
+
description: Per-finding pre-flight check decides safe-codemod vs suggestion
|
|
47
|
+
required: true
|
|
48
|
+
- name: apply
|
|
49
|
+
description: Write codemods for safe T001/T002/T003; emit suggestions for everything else
|
|
50
|
+
required: true
|
|
51
|
+
- name: report
|
|
52
|
+
description: Emit FixOutcome[] + aggregate summary; write pipeline.fixesApplied in pipeline mode
|
|
53
|
+
required: true
|
|
54
|
+
state:
|
|
55
|
+
persistent: false
|
|
56
|
+
depends_on:
|
|
57
|
+
- detect-design-drift
|