@dianzhong/create-harness-app 0.1.2 → 0.1.4

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 (39) hide show
  1. package/dist/index.mjs +4 -0
  2. package/package.json +1 -1
  3. package/templates/harness/full/.claude/agents/code-reviewer.md +1 -1
  4. package/templates/harness/full/.claude/agents/harness-reviewer.md +0 -2
  5. package/templates/harness/full/.claude/rules/skills-mcp.md +2 -3
  6. package/templates/harness/full/.claude/settings.json +1 -1
  7. package/templates/harness/full/docs/ai-harness.md +4 -6
  8. package/templates/harness/full/docs/harness-quick-reference.md +1 -1
  9. package/templates/harness/full/docs/review-checklist.md +1 -1
  10. package/templates/harness/full/scripts/verify-skills.mjs +6 -61
  11. package/templates/harness/full/scripts/verify-skills.test.mjs +1 -11
  12. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +0 -143
  13. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +0 -21
  14. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +0 -155
  15. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +0 -5
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +0 -258
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +0 -287
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +0 -99
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +0 -313
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +0 -179
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +0 -139
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +0 -226
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +0 -231
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +0 -110
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +0 -131
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +0 -135
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +0 -303
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +0 -168
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +0 -177
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +0 -185
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +0 -182
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +0 -178
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +0 -371
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +0 -227
  35. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +0 -355
  36. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +0 -138
  37. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +0 -193
  38. package/templates/harness/full/AGENTS.md +0 -3
  39. package/templates/harness/full/GEMINI.md +0 -3
@@ -1,178 +0,0 @@
1
- ---
2
- title: Vue Plugin Best Practices
3
- impact: MEDIUM
4
- impactDescription: Incorrect plugin structure or injection key strategy causes install failures, collisions, and unsafe APIs
5
- type: best-practice
6
- tags: [vue3, plugins, provide-inject, typescript, dependency-injection]
7
- ---
8
-
9
- # Vue Plugin Best Practices
10
-
11
- **Impact: MEDIUM** - Vue plugins should follow the `app.use()` contract, expose explicit capabilities, and use collision-safe injection keys. This keeps plugin setup predictable and composable across large apps.
12
-
13
- ## Task List
14
-
15
- - Export plugins as an object with `install()` or as an install function
16
- - Use the `app` instance in `install()` to register components/directives/provides
17
- - Type plugin APIs with `Plugin` (and options tuple types when needed)
18
- - Use symbol keys (prefer `InjectionKey<T>`) for `provide/inject` in plugins
19
- - Add a small typed composable wrapper for required injections to fail fast
20
-
21
- ## Structure Plugins for `app.use()`
22
-
23
- A Vue plugin must be either:
24
-
25
- - An object with `install(app, options?)`
26
- - A function with the same signature
27
-
28
- **BAD:**
29
-
30
- ```ts
31
- const notAPlugin = {
32
- doSomething() {},
33
- }
34
-
35
- app.use(notAPlugin)
36
- ```
37
-
38
- **GOOD:**
39
-
40
- ```ts
41
- import type { App } from 'vue'
42
-
43
- interface PluginOptions {
44
- prefix?: string
45
- debug?: boolean
46
- }
47
-
48
- const myPlugin = {
49
- install(app: App, options: PluginOptions = {}) {
50
- const { prefix = 'my', debug = false } = options
51
-
52
- if (debug) {
53
- console.log('Installing myPlugin with prefix:', prefix)
54
- }
55
-
56
- app.provide('myPlugin', { prefix })
57
- },
58
- }
59
-
60
- app.use(myPlugin, { prefix: 'custom', debug: true })
61
- ```
62
-
63
- **GOOD:**
64
-
65
- ```ts
66
- import type { App } from 'vue'
67
-
68
- function simplePlugin(app: App, options?: { message: string }) {
69
- app.config.globalProperties.$greet = () => options?.message ?? 'Hello!'
70
- }
71
-
72
- app.use(simplePlugin, { message: 'Welcome!' })
73
- ```
74
-
75
- ## Register Capabilities Explicitly in `install()`
76
-
77
- Inside `install()`, wire behavior through Vue application APIs:
78
-
79
- - `app.component()` for global components
80
- - `app.directive()` for global directives
81
- - `app.provide()` for injectable services and config
82
- - `app.config.globalProperties` for optional global helpers (sparingly)
83
-
84
- **BAD:**
85
-
86
- ```ts
87
- const uselessPlugin = {
88
- install(app, options) {
89
- const service = createService(options)
90
- },
91
- }
92
- ```
93
-
94
- **GOOD:**
95
-
96
- ```ts
97
- const usefulPlugin = {
98
- install(app, options) {
99
- const service = createService(options)
100
- app.provide(serviceKey, service)
101
- },
102
- }
103
- ```
104
-
105
- ## Type Plugin Contracts
106
-
107
- Use Vue's `Plugin` type to keep install signatures and options type-safe.
108
-
109
- ```ts
110
- import type { App, Plugin } from 'vue'
111
-
112
- interface MyOptions {
113
- apiKey: string
114
- }
115
-
116
- const myPlugin: Plugin<[MyOptions]> = {
117
- install(app: App, options: MyOptions) {
118
- app.provide(apiKeyKey, options.apiKey)
119
- },
120
- }
121
- ```
122
-
123
- ## Use Symbol Injection Keys in Plugins
124
-
125
- String keys can collide (`'http'`, `'config'`, `'i18n'`). Use symbol keys with `InjectionKey<T>` so injections are unique and typed.
126
-
127
- **BAD:**
128
-
129
- ```ts
130
- export default {
131
- install(app) {
132
- app.provide('http', axios)
133
- app.provide('config', appConfig)
134
- },
135
- }
136
- ```
137
-
138
- **GOOD:**
139
-
140
- ```ts
141
- import type { AxiosInstance } from 'axios'
142
- import type { InjectionKey } from 'vue'
143
-
144
- interface AppConfig {
145
- apiUrl: string
146
- timeout: number
147
- }
148
-
149
- export const httpKey: InjectionKey<AxiosInstance> = Symbol('http')
150
- export const configKey: InjectionKey<AppConfig> = Symbol('appConfig')
151
-
152
- export default {
153
- install(app) {
154
- app.provide(httpKey, axios)
155
- app.provide(configKey, { apiUrl: '/api', timeout: 5000 })
156
- },
157
- }
158
- ```
159
-
160
- ## Provide Required Injection Helpers
161
-
162
- Wrap required injections in composables that throw clear setup errors.
163
-
164
- ```ts
165
- import type { AuthService } from '@/injection-keys'
166
-
167
- import { inject } from 'vue'
168
-
169
- import { authKey } from '@/injection-keys'
170
-
171
- export function useAuth(): AuthService {
172
- const auth = inject(authKey)
173
- if (!auth) {
174
- throw new Error('Auth plugin not installed. Did you forget app.use(authPlugin)?')
175
- }
176
- return auth
177
- }
178
- ```
@@ -1,371 +0,0 @@
1
- ---
2
- title: Reactivity Core Patterns (ref, reactive, shallowRef, computed, watch)
3
- impact: MEDIUM
4
- impactDescription: Clear reactivity choices keep state predictable and reduce unnecessary updates in Vue 3 apps
5
- type: efficiency
6
- tags:
7
- [
8
- vue3,
9
- reactivity,
10
- ref,
11
- reactive,
12
- shallowRef,
13
- computed,
14
- watch,
15
- watchEffect,
16
- external-state,
17
- best-practice,
18
- ]
19
- ---
20
-
21
- # Reactivity Core Patterns (ref, reactive, shallowRef, computed, watch)
22
-
23
- **Impact: MEDIUM** - Choose the right reactive primitive first, derive with `computed`, and use watchers only for side effects.
24
-
25
- This reference covers the core reactivity decisions for local state, external data, derived values, and effects.
26
-
27
- ## Task List
28
-
29
- - Declare reactive state correctly
30
- - Always use `shallowRef()` instead of `ref()` for primitive values
31
- - Choose the correct reactive declaration method for objects/arrays/map/set
32
- - Follow best practices for `reactive`
33
- - Avoid destructuring from `reactive()` directly
34
- - Watch correctly for `reactive`
35
- - Follow best practices for `computed`
36
- - Prefer `computed` over watcher-assigned derived refs
37
- - Keep filtered/sorted derivations out of templates
38
- - Use `computed` for reusable class/style logic
39
- - Keep computed getters pure (no side effects) and put side effects in watchers
40
- - Follow best practices for watchers
41
- - Use `immediate: true` instead of duplicate initial calls
42
- - Clean up async effects for watchers
43
-
44
- ## Declare reactive state correctly
45
-
46
- ### Always use `shallowRef()` instead of `ref()` for primitive values (string, number, boolean, null, etc.) for better performance.
47
-
48
- **Incorrect:**
49
-
50
- ```ts
51
- import { ref } from 'vue'
52
-
53
- const count = ref(0)
54
- ```
55
-
56
- **Correct:**
57
-
58
- ```ts
59
- import { shallowRef } from 'vue'
60
-
61
- const count = shallowRef(0)
62
- ```
63
-
64
- ### Choose the correct reactive declaration method for objects/arrays/map/set
65
-
66
- Use `ref()` when you often **replace the entire value** (`state.value = newObj`) and still want deep reactivity inside it, usually used for:
67
-
68
- - Frequently reassigned state (replace fetched object/list, reset to defaults, switch presets).
69
- - Composable return values where updates happen mostly via `.value` reassignment.
70
-
71
- Use `reactive()` when you mainly **mutate properties** and full replacement is uncommon, usually used for:
72
-
73
- - “Single state object” patterns (stores/forms): `state.count++`, `state.items.push(...)`, `state.user.name = ...`.
74
- - Situations where you want to avoid `.value` and update nested fields in place.
75
-
76
- ```ts
77
- import { reactive } from 'vue'
78
-
79
- const state = reactive({
80
- count: 0,
81
- user: { name: 'Alice', age: 30 },
82
- })
83
-
84
- state.count++ // ✅ reactive
85
- state.user.age = 31 // ✅ reactive
86
- // ❌ avoid replacing the reactive object reference:
87
- // state = reactive({ count: 1 })
88
- ```
89
-
90
- Use `shallowRef()` when the value is **opaque / should not be proxied** (class instances, external library objects, very large nested data) and you only want updates to trigger when you **replace** `state.value` (no deep tracking), usually used for:
91
-
92
- - Storing external instances/handles (SDK clients, class instances) without Vue proxying internals.
93
- - Large data where you update by replacing the root reference (immutable-style updates).
94
-
95
- ```ts
96
- import { shallowRef } from 'vue'
97
-
98
- const user = shallowRef({ name: 'Alice', age: 30 })
99
-
100
- user.value.age = 31 // ❌ not reactive
101
- user.value = { name: 'Bob', age: 25 } // ✅ triggers update
102
- ```
103
-
104
- Use `shallowReactive()` when you want **only top-level properties** reactive; nested objects remain raw, usually used for:
105
-
106
- - Container objects where only top-level keys change and nested payloads should stay unmanaged/unproxied.
107
- - Mixed structures where Vue tracks the wrapper object, but not deeply nested or foreign objects.
108
-
109
- ```ts
110
- import { shallowReactive } from 'vue'
111
-
112
- const state = shallowReactive({
113
- count: 0,
114
- user: { name: 'Alice', age: 30 },
115
- })
116
-
117
- state.count++ // ✅ reactive
118
- state.user.age = 31 // ❌ not reactive
119
- ```
120
-
121
- ## Best practices for `reactive`
122
-
123
- ### Avoid destructuring from `reactive()` directly
124
-
125
- **BAD:**
126
-
127
- ```ts
128
- import { reactive } from 'vue'
129
-
130
- const state = reactive({ count: 0 })
131
- const { count } = state // ❌ disconnected from reactivity
132
- ```
133
-
134
- ### Watch correctly for reactive
135
-
136
- **BAD:**
137
-
138
- passing a non-getter value into `watch()`
139
-
140
- ```ts
141
- import { reactive, watch } from 'vue'
142
-
143
- const state = reactive({ count: 0 })
144
-
145
- // ❌ watch expects a getter, ref, reactive object, or array of these
146
- watch(state.count, () => {
147
- /* ... */
148
- })
149
- ```
150
-
151
- **GOOD:**
152
-
153
- preserve reactivity with `toRefs()` and use a getter for `watch()`
154
-
155
- ```ts
156
- import { reactive, toRefs, watch } from 'vue'
157
-
158
- const state = reactive({ count: 0 })
159
- const { count } = toRefs(state) // ✅ count is a ref
160
-
161
- watch(count, () => {
162
- /* ... */
163
- }) // ✅
164
- watch(
165
- () => state.count,
166
- () => {
167
- /* ... */
168
- },
169
- ) // ✅
170
- ```
171
-
172
- ## Best practices for `computed`
173
-
174
- ### Prefer `computed` over watcher-assigned derived refs
175
-
176
- **BAD:**
177
-
178
- ```ts
179
- import { ref, watchEffect } from 'vue'
180
-
181
- const items = ref([{ price: 10 }, { price: 20 }])
182
- const total = ref(0)
183
-
184
- watchEffect(() => {
185
- total.value = items.value.reduce((sum, item) => sum + item.price, 0)
186
- })
187
- ```
188
-
189
- **GOOD:**
190
-
191
- ```ts
192
- import { computed, ref } from 'vue'
193
-
194
- const items = ref([{ price: 10 }, { price: 20 }])
195
- const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0))
196
- ```
197
-
198
- ### Keep filtered/sorted derivations out of templates
199
-
200
- **BAD:**
201
-
202
- ```vue
203
- <script setup>
204
- import { ref } from 'vue'
205
-
206
- const items = ref([
207
- { id: 1, name: 'B', active: true },
208
- { id: 2, name: 'A', active: false },
209
- ])
210
-
211
- function getSortedItems() {
212
- return [...items.value].sort((a, b) => a.name.localeCompare(b.name))
213
- }
214
- </script>
215
-
216
- <template>
217
- <li v-for="item in items.filter((item) => item.active)" :key="item.id">
218
- {{ item.name }}
219
- </li>
220
-
221
- <li v-for="item in getSortedItems()" :key="item.id">
222
- {{ item.name }}
223
- </li>
224
- </template>
225
- ```
226
-
227
- **GOOD:**
228
-
229
- ```vue
230
- <script setup>
231
- import { computed, ref } from 'vue'
232
-
233
- const items = ref([
234
- { id: 1, name: 'B', active: true },
235
- { id: 2, name: 'A', active: false },
236
- ])
237
-
238
- const visibleItems = computed(() =>
239
- items.value.filter((item) => item.active).sort((a, b) => a.name.localeCompare(b.name)),
240
- )
241
- </script>
242
-
243
- <template>
244
- <li v-for="item in visibleItems" :key="item.id">
245
- {{ item.name }}
246
- </li>
247
- </template>
248
- ```
249
-
250
- ### Use `computed` for reusable class/style logic
251
-
252
- **BAD:**
253
-
254
- ```vue
255
- <template>
256
- <button
257
- :class="{ btn: true, 'btn-primary': type === 'primary' && !disabled, 'btn-disabled': disabled }"
258
- >
259
- {{ label }}
260
- </button>
261
- </template>
262
- ```
263
-
264
- **GOOD:**
265
-
266
- ```vue
267
- <script setup>
268
- import { computed } from 'vue'
269
-
270
- const props = defineProps({
271
- type: { type: String, default: 'primary' },
272
- disabled: Boolean,
273
- label: String,
274
- })
275
-
276
- const buttonClasses = computed(() => ({
277
- btn: true,
278
- [`btn-${props.type}`]: !props.disabled,
279
- 'btn-disabled': props.disabled,
280
- }))
281
- </script>
282
-
283
- <template>
284
- <button :class="buttonClasses">
285
- {{ label }}
286
- </button>
287
- </template>
288
- ```
289
-
290
- ### Keep computed getters pure (no side effects) and put side effects in watchers instead
291
-
292
- A computed getter should only derive a value. No mutation, no API calls, no storage writes, no event emits.
293
- ([Reference](https://vuejs.org/guide/essentials/computed.html#best-practices))
294
-
295
- **BAD:**
296
-
297
- side effects inside computed
298
-
299
- ```ts
300
- const count = ref(0)
301
-
302
- const doubled = computed(() => {
303
- // ❌ side effect
304
- if (count.value > 10) console.warn('Too big!')
305
- return count.value * 2
306
- })
307
- ```
308
-
309
- **GOOD:**
310
-
311
- pure computed + `watch()` for side effects
312
-
313
- ```ts
314
- const count = ref(0)
315
- const doubled = computed(() => count.value * 2)
316
-
317
- watch(count, (value) => {
318
- if (value > 10) console.warn('Too big!')
319
- })
320
- ```
321
-
322
- ## Best practices for watchers
323
-
324
- ### Use `immediate: true` instead of duplicate initial calls
325
-
326
- **BAD:**
327
-
328
- ```ts
329
- import { onMounted, ref, watch } from 'vue'
330
-
331
- const userId = ref(1)
332
-
333
- function loadUser(id) {
334
- // ...
335
- }
336
-
337
- onMounted(() => loadUser(userId.value))
338
- watch(userId, (id) => loadUser(id))
339
- ```
340
-
341
- **GOOD:**
342
-
343
- ```ts
344
- import { ref, watch } from 'vue'
345
-
346
- const userId = ref(1)
347
-
348
- watch(userId, (id) => loadUser(id), { immediate: true })
349
- ```
350
-
351
- ### Clean up async effects for watchers
352
-
353
- When reacting to rapid changes (search boxes, filters), cancel the previous request.
354
-
355
- **GOOD:**
356
-
357
- ```ts
358
- const query = ref('')
359
- const results = ref<string[]>([])
360
-
361
- watch(query, async (q, _prev, onCleanup) => {
362
- const controller = new AbortController()
363
- onCleanup(() => controller.abort())
364
-
365
- const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
366
- signal: controller.signal,
367
- })
368
-
369
- results.value = await res.json()
370
- })
371
- ```