@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.
Files changed (156) hide show
  1. package/dist/agents/skills/claude-code/align-design-system/SKILL.md +174 -0
  2. package/dist/agents/skills/claude-code/align-design-system/skill.yaml +57 -0
  3. package/dist/agents/skills/claude-code/audit-brand-compliance/SKILL.md +189 -0
  4. package/dist/agents/skills/claude-code/audit-brand-compliance/skill.yaml +50 -0
  5. package/dist/agents/skills/claude-code/audit-component-anatomy/SKILL.md +181 -0
  6. package/dist/agents/skills/claude-code/audit-component-anatomy/skill.yaml +51 -0
  7. package/dist/agents/skills/claude-code/copy-craft/SKILL.md +192 -0
  8. package/dist/agents/skills/claude-code/copy-craft/skill.yaml +52 -0
  9. package/dist/agents/skills/claude-code/detect-design-drift/SKILL.md +139 -0
  10. package/dist/agents/skills/claude-code/detect-design-drift/skill.yaml +50 -0
  11. package/dist/agents/skills/claude-code/harness-accessibility/SKILL.md +10 -0
  12. package/dist/agents/skills/claude-code/harness-design-craft/SKILL.md +212 -0
  13. package/dist/agents/skills/claude-code/harness-design-craft/skill.yaml +57 -0
  14. package/dist/agents/skills/claude-code/harness-design-pipeline/SKILL.md +266 -0
  15. package/dist/agents/skills/claude-code/harness-design-pipeline/skill.yaml +70 -0
  16. package/dist/agents/skills/claude-code/knowledge-craft/SKILL.md +180 -0
  17. package/dist/agents/skills/claude-code/knowledge-craft/skill.yaml +49 -0
  18. package/dist/agents/skills/claude-code/naming-craft/SKILL.md +244 -0
  19. package/dist/agents/skills/claude-code/naming-craft/skill.yaml +51 -0
  20. package/dist/agents/skills/claude-code/security-craft/SKILL.md +205 -0
  21. package/dist/agents/skills/claude-code/security-craft/skill.yaml +58 -0
  22. package/dist/agents/skills/claude-code/spec-craft/SKILL.md +184 -0
  23. package/dist/agents/skills/claude-code/spec-craft/skill.yaml +55 -0
  24. package/dist/agents/skills/claude-code/test-craft/SKILL.md +212 -0
  25. package/dist/agents/skills/claude-code/test-craft/skill.yaml +58 -0
  26. package/dist/agents/skills/codex/align-design-system/SKILL.md +174 -0
  27. package/dist/agents/skills/codex/align-design-system/skill.yaml +57 -0
  28. package/dist/agents/skills/codex/audit-brand-compliance/SKILL.md +189 -0
  29. package/dist/agents/skills/codex/audit-brand-compliance/skill.yaml +50 -0
  30. package/dist/agents/skills/codex/audit-component-anatomy/SKILL.md +181 -0
  31. package/dist/agents/skills/codex/audit-component-anatomy/skill.yaml +51 -0
  32. package/dist/agents/skills/codex/copy-craft/SKILL.md +192 -0
  33. package/dist/agents/skills/codex/copy-craft/skill.yaml +52 -0
  34. package/dist/agents/skills/codex/detect-design-drift/SKILL.md +139 -0
  35. package/dist/agents/skills/codex/detect-design-drift/skill.yaml +50 -0
  36. package/dist/agents/skills/codex/harness-accessibility/SKILL.md +10 -0
  37. package/dist/agents/skills/codex/harness-design-craft/SKILL.md +212 -0
  38. package/dist/agents/skills/codex/harness-design-craft/skill.yaml +57 -0
  39. package/dist/agents/skills/codex/harness-design-pipeline/SKILL.md +266 -0
  40. package/dist/agents/skills/codex/harness-design-pipeline/skill.yaml +70 -0
  41. package/dist/agents/skills/codex/knowledge-craft/SKILL.md +180 -0
  42. package/dist/agents/skills/codex/knowledge-craft/skill.yaml +49 -0
  43. package/dist/agents/skills/codex/naming-craft/SKILL.md +244 -0
  44. package/dist/agents/skills/codex/naming-craft/skill.yaml +51 -0
  45. package/dist/agents/skills/codex/security-craft/SKILL.md +205 -0
  46. package/dist/agents/skills/codex/security-craft/skill.yaml +58 -0
  47. package/dist/agents/skills/codex/spec-craft/SKILL.md +184 -0
  48. package/dist/agents/skills/codex/spec-craft/skill.yaml +55 -0
  49. package/dist/agents/skills/codex/test-craft/SKILL.md +212 -0
  50. package/dist/agents/skills/codex/test-craft/skill.yaml +58 -0
  51. package/dist/agents/skills/cursor/align-design-system/SKILL.md +174 -0
  52. package/dist/agents/skills/cursor/align-design-system/skill.yaml +57 -0
  53. package/dist/agents/skills/cursor/audit-brand-compliance/SKILL.md +189 -0
  54. package/dist/agents/skills/cursor/audit-brand-compliance/skill.yaml +50 -0
  55. package/dist/agents/skills/cursor/audit-component-anatomy/SKILL.md +181 -0
  56. package/dist/agents/skills/cursor/audit-component-anatomy/skill.yaml +51 -0
  57. package/dist/agents/skills/cursor/copy-craft/SKILL.md +192 -0
  58. package/dist/agents/skills/cursor/copy-craft/skill.yaml +52 -0
  59. package/dist/agents/skills/cursor/detect-design-drift/SKILL.md +139 -0
  60. package/dist/agents/skills/cursor/detect-design-drift/skill.yaml +50 -0
  61. package/dist/agents/skills/cursor/harness-accessibility/SKILL.md +10 -0
  62. package/dist/agents/skills/cursor/harness-design-craft/SKILL.md +212 -0
  63. package/dist/agents/skills/cursor/harness-design-craft/skill.yaml +57 -0
  64. package/dist/agents/skills/cursor/harness-design-pipeline/SKILL.md +266 -0
  65. package/dist/agents/skills/cursor/harness-design-pipeline/skill.yaml +70 -0
  66. package/dist/agents/skills/cursor/knowledge-craft/SKILL.md +180 -0
  67. package/dist/agents/skills/cursor/knowledge-craft/skill.yaml +49 -0
  68. package/dist/agents/skills/cursor/naming-craft/SKILL.md +244 -0
  69. package/dist/agents/skills/cursor/naming-craft/skill.yaml +51 -0
  70. package/dist/agents/skills/cursor/security-craft/SKILL.md +205 -0
  71. package/dist/agents/skills/cursor/security-craft/skill.yaml +58 -0
  72. package/dist/agents/skills/cursor/spec-craft/SKILL.md +184 -0
  73. package/dist/agents/skills/cursor/spec-craft/skill.yaml +55 -0
  74. package/dist/agents/skills/cursor/test-craft/SKILL.md +212 -0
  75. package/dist/agents/skills/cursor/test-craft/skill.yaml +58 -0
  76. package/dist/agents/skills/gemini-cli/align-design-system/SKILL.md +174 -0
  77. package/dist/agents/skills/gemini-cli/align-design-system/skill.yaml +57 -0
  78. package/dist/agents/skills/gemini-cli/audit-brand-compliance/SKILL.md +189 -0
  79. package/dist/agents/skills/gemini-cli/audit-brand-compliance/skill.yaml +50 -0
  80. package/dist/agents/skills/gemini-cli/audit-component-anatomy/SKILL.md +181 -0
  81. package/dist/agents/skills/gemini-cli/audit-component-anatomy/skill.yaml +51 -0
  82. package/dist/agents/skills/gemini-cli/copy-craft/SKILL.md +192 -0
  83. package/dist/agents/skills/gemini-cli/copy-craft/skill.yaml +52 -0
  84. package/dist/agents/skills/gemini-cli/detect-design-drift/SKILL.md +139 -0
  85. package/dist/agents/skills/gemini-cli/detect-design-drift/skill.yaml +50 -0
  86. package/dist/agents/skills/gemini-cli/harness-accessibility/SKILL.md +10 -0
  87. package/dist/agents/skills/gemini-cli/harness-design-craft/SKILL.md +212 -0
  88. package/dist/agents/skills/gemini-cli/harness-design-craft/skill.yaml +57 -0
  89. package/dist/agents/skills/gemini-cli/harness-design-pipeline/SKILL.md +266 -0
  90. package/dist/agents/skills/gemini-cli/harness-design-pipeline/skill.yaml +70 -0
  91. package/dist/agents/skills/gemini-cli/knowledge-craft/SKILL.md +180 -0
  92. package/dist/agents/skills/gemini-cli/knowledge-craft/skill.yaml +49 -0
  93. package/dist/agents/skills/gemini-cli/naming-craft/SKILL.md +244 -0
  94. package/dist/agents/skills/gemini-cli/naming-craft/skill.yaml +51 -0
  95. package/dist/agents/skills/gemini-cli/security-craft/SKILL.md +205 -0
  96. package/dist/agents/skills/gemini-cli/security-craft/skill.yaml +58 -0
  97. package/dist/agents/skills/gemini-cli/spec-craft/SKILL.md +184 -0
  98. package/dist/agents/skills/gemini-cli/spec-craft/skill.yaml +55 -0
  99. package/dist/agents/skills/gemini-cli/test-craft/SKILL.md +212 -0
  100. package/dist/agents/skills/gemini-cli/test-craft/skill.yaml +58 -0
  101. package/dist/{agents-md-2JUQ4FE2.js → agents-md-FKMKH6ZF.js} +4 -4
  102. package/dist/{analyzer-VY6DJVOU-4AHIJLNS.js → analyzer-H3AHBFSL-KPOPJYRB.js} +8 -8
  103. package/dist/{architecture-CXAVNB5X.js → architecture-LUR2I67H.js} +6 -6
  104. package/dist/{assess-project-M626SSPN.js → assess-project-OJWECOP2.js} +2 -1
  105. package/dist/bin/harness-mcp.js +17 -17
  106. package/dist/bin/harness.js +27 -27
  107. package/dist/{check-phase-gate-XMU6LLUQ.js → check-phase-gate-HS5KYLRO.js} +5 -5
  108. package/dist/{chunk-5XLLIYYL.js → chunk-6UHDQP2N.js} +1 -1
  109. package/dist/{chunk-I6K43QQS.js → chunk-7AF6C5GE.js} +3 -3
  110. package/dist/{chunk-372MGNTI.js → chunk-7PLIWP7U.js} +6899 -133
  111. package/dist/{chunk-VUCGB2KH.js → chunk-B2VAYLYI.js} +1 -1
  112. package/dist/{chunk-HFOB2MPQ.js → chunk-CZPOPMMI.js} +3 -3
  113. package/dist/{chunk-43M3RLFQ.js → chunk-FC4JPM6W.js} +2 -2
  114. package/dist/{chunk-6AX6R3LM.js → chunk-FEKBXX2J.js} +9 -9
  115. package/dist/{chunk-VTTNIZJL.js → chunk-FXZI6NJ2.js} +131 -6
  116. package/dist/{chunk-5JNSFYZU.js → chunk-GGSNWH7P.js} +6 -6
  117. package/dist/{chunk-BCFDGOMA.js → chunk-H2ZJOJ7A.js} +81 -1
  118. package/dist/{chunk-PFZEADQM.js → chunk-INBTDBV2.js} +79 -2
  119. package/dist/{chunk-Q6ZR2L6Q.js → chunk-KN3EMDZ5.js} +2 -2
  120. package/dist/{chunk-JFZOWO7D.js → chunk-LECXPXS3.js} +1 -1
  121. package/dist/{chunk-CQOMVZDC.js → chunk-LGYXR2KZ.js} +2237 -604
  122. package/dist/{chunk-QOKMDDBJ.js → chunk-LYIPI2SJ.js} +6 -6
  123. package/dist/{chunk-L5BCZ5XS.js → chunk-N5NSSLMU.js} +1 -1
  124. package/dist/{chunk-GWE5LL3R.js → chunk-NIVWM7OW.js} +10 -10
  125. package/dist/{chunk-24KCOJJG.js → chunk-O2ASYKA6.js} +1 -1
  126. package/dist/{chunk-K246XQ2L.js → chunk-PTHX545Q.js} +1 -1
  127. package/dist/{chunk-EPUKTTJZ.js → chunk-RC3OI5NK.js} +5 -1
  128. package/dist/{chunk-YP7I25SL.js → chunk-RJ2WEES5.js} +1 -1
  129. package/dist/{chunk-5FDBXUFW.js → chunk-TIH53QXY.js} +1 -1
  130. package/dist/{chunk-OA6SWTU4.js → chunk-VI4IS55S.js} +11 -8
  131. package/dist/{chunk-GWF2OILZ.js → chunk-VSIRUZQP.js} +1 -1
  132. package/dist/{chunk-YYRQCQPZ.js → chunk-WZY5U6NS.js} +178 -109
  133. package/dist/{chunk-SZEFU6XC.js → chunk-Y4T6ENKQ.js} +9 -9
  134. package/dist/{chunk-HMW3VKUF.js → chunk-Z3TMCCZB.js} +10 -10
  135. package/dist/{ci-workflow-3JNDZQYD.js → ci-workflow-MDSTHJOY.js} +4 -4
  136. package/dist/{dist-OHDQBTSO.js → dist-72ID44UE.js} +3 -3
  137. package/dist/{dist-QPWXPCPL.js → dist-R3TOOPJ7.js} +1 -1
  138. package/dist/{docs-5RYMDHN3.js → docs-2RFOJ6XY.js} +6 -6
  139. package/dist/{engine-GKGT5ULT.js → engine-BSFKOGPN.js} +4 -4
  140. package/dist/{entropy-DQTAPSSE.js → entropy-3UX6644G.js} +5 -5
  141. package/dist/{feedback-WH6XPQJS.js → feedback-CVCFYND4.js} +2 -2
  142. package/dist/{generate-agent-definitions-SZA4QIHN.js → generate-agent-definitions-HZ3XENWO.js} +5 -5
  143. package/dist/{glob-helper-XE2UGEOV.js → glob-helper-EX4YZKZO.js} +1 -1
  144. package/dist/{graph-loader-EQBOIHFZ.js → graph-loader-EIFEOUVI.js} +1 -1
  145. package/dist/index.d.ts +1012 -12
  146. package/dist/index.js +27 -27
  147. package/dist/{loader-BNKGM23C.js → loader-6RHWU5FH.js} +4 -4
  148. package/dist/{mcp-YKKPWUMH.js → mcp-OYR26JDI.js} +17 -17
  149. package/dist/{performance-54TMJOEU.js → performance-JJMIRXCH.js} +6 -6
  150. package/dist/{review-pipeline-OTDDVPNY.js → review-pipeline-BHQZIXWZ.js} +4 -4
  151. package/dist/{runtime-W5THSNAL.js → runtime-DALTAVTN.js} +4 -4
  152. package/dist/{scan-4PPP6ZXT.js → scan-LYKLM4EQ.js} +1 -1
  153. package/dist/{security-27HW7CYT.js → security-RMLMYFPB.js} +1 -1
  154. package/dist/{validate-QRVCD4GP.js → validate-R7DZRDFK.js} +5 -5
  155. package/dist/{validate-cross-check-BUAGBAPO.js → validate-cross-check-AUUZFNVL.js} +4 -4
  156. 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