@plaited/development-skills 0.5.0 → 0.6.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 (32) hide show
  1. package/{.claude → .plaited}/rules/accuracy.md +3 -10
  2. package/{.claude → .plaited}/rules/code-review.md +3 -10
  3. package/.plaited/rules/documentation.md +41 -0
  4. package/.plaited/rules/git-workflow.md +31 -0
  5. package/{.claude → .plaited}/rules/github.md +4 -4
  6. package/{.claude → .plaited}/rules/module-organization.md +0 -7
  7. package/{.claude → .plaited}/rules/testing.md +0 -5
  8. package/package.json +2 -2
  9. package/src/lsp-analyze.ts +1 -1
  10. package/src/lsp-find.ts +1 -1
  11. package/src/lsp-hover.ts +1 -1
  12. package/src/lsp-references.ts +1 -1
  13. package/src/lsp-symbols.ts +1 -1
  14. package/src/scaffold-rules.ts +146 -205
  15. package/src/tests/scaffold-rules.spec.ts +239 -110
  16. package/.claude/commands/lsp-analyze.md +0 -66
  17. package/.claude/commands/lsp-find.md +0 -74
  18. package/.claude/commands/lsp-hover.md +0 -57
  19. package/.claude/commands/lsp-refs.md +0 -64
  20. package/.claude/commands/scaffold-rules.md +0 -221
  21. package/.claude/commands/validate-skill.md +0 -29
  22. package/.claude/rules/git-workflow.md +0 -66
  23. package/.claude/skills/code-documentation/SKILL.md +0 -47
  24. package/.claude/skills/code-documentation/references/internal-templates.md +0 -113
  25. package/.claude/skills/code-documentation/references/maintenance.md +0 -164
  26. package/.claude/skills/code-documentation/references/public-api-templates.md +0 -100
  27. package/.claude/skills/code-documentation/references/type-documentation.md +0 -116
  28. package/.claude/skills/code-documentation/references/workflow.md +0 -60
  29. package/.claude/skills/scaffold-rules/SKILL.md +0 -104
  30. package/.claude/skills/typescript-lsp/SKILL.md +0 -249
  31. package/.claude/skills/validate-skill/SKILL.md +0 -105
  32. /package/{.claude → .plaited}/rules/bun-apis.md +0 -0
@@ -9,12 +9,9 @@ type Template = {
9
9
  }
10
10
 
11
11
  type ScaffoldOutput = {
12
- agent: string
13
12
  rulesPath: string
14
- agentsMdPath: string
15
- format: 'multi-file' | 'agents-md'
16
- supportsAgentsMd: boolean
17
- agentsMdContent?: string
13
+ agentsMdSection: string
14
+ claudeMdReference: string
18
15
  templates: Record<string, Template>
19
16
  }
20
17
 
@@ -22,7 +19,7 @@ const binDir = join(import.meta.dir, '../../bin')
22
19
 
23
20
  describe('scaffold-rules', () => {
24
21
  test('outputs JSON with all templates', async () => {
25
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
22
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
26
23
 
27
24
  expect(result).toHaveProperty('templates')
28
25
  expect(result.templates).toBeObject()
@@ -35,10 +32,11 @@ describe('scaffold-rules', () => {
35
32
  expect(templateKeys).toContain('git-workflow')
36
33
  expect(templateKeys).toContain('github')
37
34
  expect(templateKeys).toContain('testing')
35
+ expect(templateKeys).toContain('module-organization')
38
36
  })
39
37
 
40
38
  test('each template has required properties', async () => {
41
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
39
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
42
40
 
43
41
  for (const [ruleId, template] of Object.entries(result.templates)) {
44
42
  expect(template).toHaveProperty('filename')
@@ -52,7 +50,7 @@ describe('scaffold-rules', () => {
52
50
  })
53
51
 
54
52
  test('removes template headers from content', async () => {
55
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
53
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
56
54
 
57
55
  // Check that template headers are removed
58
56
  for (const template of Object.values(result.templates)) {
@@ -62,7 +60,7 @@ describe('scaffold-rules', () => {
62
60
  })
63
61
 
64
62
  test('processes development-skills conditionals', async () => {
65
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
63
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
66
64
 
67
65
  const accuracy = result.templates.accuracy
68
66
  expect(accuracy).toBeDefined()
@@ -78,8 +76,7 @@ describe('scaffold-rules', () => {
78
76
  })
79
77
 
80
78
  test('filters to specific rules when requested', async () => {
81
- const result: ScaffoldOutput =
82
- await $`bun ${binDir}/cli.ts scaffold-rules --rules testing --rules bun-apis --format=json`.json()
79
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules testing --rules bun-apis`.json()
83
80
 
84
81
  const templateKeys = Object.keys(result.templates)
85
82
 
@@ -94,7 +91,7 @@ describe('scaffold-rules', () => {
94
91
  })
95
92
 
96
93
  test('extracts meaningful descriptions', async () => {
97
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
94
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
98
95
 
99
96
  // Check a few descriptions
100
97
  const accuracy = result.templates.accuracy
@@ -108,179 +105,311 @@ describe('scaffold-rules', () => {
108
105
  expect(testing!.description.length).toBeGreaterThan(10)
109
106
  })
110
107
 
111
- test('exits with error for invalid agent', async () => {
112
- const proc = Bun.spawn(['bun', `${binDir}/cli.ts`, 'scaffold-rules', '--agent=invalid'], {
113
- stderr: 'pipe',
114
- })
115
-
116
- const exitCode = await proc.exited
117
- expect(exitCode).not.toBe(0)
118
- })
119
-
120
108
  test('handles missing bundled rules directory gracefully', async () => {
121
109
  // This test ensures the script fails gracefully if templates are missing
122
- // In production, .claude/rules/ should always be bundled with the package
123
- const result = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.nothrow().quiet()
110
+ // In production, .plaited/rules/ should always be bundled with the package
111
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules`.nothrow().quiet()
124
112
 
125
- // Should succeed because .claude/rules/ exists in development
113
+ // Should succeed because .plaited/rules/ exists in development
126
114
  expect(result.exitCode).toBe(0)
127
115
  })
128
116
 
129
- describe('Claude Code target', () => {
130
- test('defaults to Claude Code format', async () => {
131
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
117
+ describe('output structure', () => {
118
+ test('defaults to .plaited/rules path', async () => {
119
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
132
120
 
133
- expect(result.agent).toBe('claude')
134
- expect(result.rulesPath).toBe('.claude/rules')
135
- expect(result.format).toBe('multi-file')
136
- expect(result.supportsAgentsMd).toBe(false)
121
+ expect(result.rulesPath).toBe('.plaited/rules')
137
122
  })
138
123
 
139
- test('processes has-sandbox for Claude (sandbox environment)', async () => {
140
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=claude --format=json`.json()
124
+ test('includes claudeMdReference with markers and @AGENTS.md', async () => {
125
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
141
126
 
142
- const gitWorkflow = result.templates['git-workflow']
143
- expect(gitWorkflow).toBeDefined()
127
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-START -->')
128
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-END -->')
129
+ expect(result.claudeMdReference).toContain('@AGENTS.md')
130
+ expect(result.claudeMdReference).toContain('## Project Rules')
131
+ })
144
132
 
145
- // Claude has sandbox - should include sandbox-specific content
146
- expect(gitWorkflow!.content).toContain('sandbox environment')
147
- expect(gitWorkflow!.content).toContain('single-quoted strings')
133
+ test('includes agentsMdSection with markers and dual format', async () => {
134
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
148
135
 
149
- // Should not have conditional syntax
150
- expect(gitWorkflow!.content).not.toContain('{{#if has-sandbox}}')
151
- expect(gitWorkflow!.content).not.toContain('{{/if}}')
136
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-START -->')
137
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-END -->')
138
+ // Should have @ syntax for Claude Code
139
+ expect(result.agentsMdSection).toContain('@.plaited/rules/')
140
+ // Should have markdown links for other tools
141
+ expect(result.agentsMdSection).toContain('](.plaited/rules/')
142
+ expect(result.agentsMdSection).toContain('## Rules')
152
143
  })
153
144
 
154
- test('processes supports-slash-commands for Claude', async () => {
155
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=claude --format=json`.json()
145
+ test('claudeMdReference does not list individual rules', async () => {
146
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
156
147
 
157
- const accuracy = result.templates.accuracy
158
- expect(accuracy).toBeDefined()
159
-
160
- // Claude supports slash commands
161
- expect(accuracy!.content).toContain('/lsp-hover')
162
- expect(accuracy!.content).toContain('/lsp-find')
148
+ // claudeMdReference should be a simple reference to AGENTS.md, not a list of rules
149
+ expect(result.claudeMdReference).not.toContain('.plaited/rules/')
150
+ expect(result.claudeMdReference).toContain('@AGENTS.md')
163
151
  })
164
152
 
165
- test('generates Claude-style cross-references', async () => {
166
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=claude --format=json`.json()
153
+ test('agentsMdSection lists all selected rules in dual format', async () => {
154
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
167
155
 
168
- const accuracy = result.templates.accuracy
169
- expect(accuracy).toBeDefined()
170
- expect(accuracy!.content).toContain('@.claude/rules/testing.md')
171
- expect(accuracy!.content).not.toContain('{{LINK:testing}}')
156
+ for (const [ruleId, template] of Object.entries(result.templates)) {
157
+ // Each rule should have both @ syntax and markdown link
158
+ expect(result.agentsMdSection).toContain(`@.plaited/rules/${template.filename}`)
159
+ expect(result.agentsMdSection).toContain(`[${ruleId}](.plaited/rules/${template.filename})`)
160
+ }
172
161
  })
173
162
  })
174
163
 
175
- describe('AGENTS.md target (universal format)', () => {
176
- test('supports agents-md format', async () => {
177
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
164
+ describe('claudeMdReference behavior', () => {
165
+ test('has exact expected content structure', async () => {
166
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
167
+
168
+ // Verify the exact structure of claudeMdReference
169
+ const lines = result.claudeMdReference.split('\n')
170
+ expect(lines[0]).toBe('<!-- PLAITED-RULES-START -->')
171
+ expect(lines[1]).toBe('')
172
+ expect(lines[2]).toBe('## Project Rules')
173
+ expect(lines[3]).toBe('')
174
+ expect(lines[4]).toBe('See @AGENTS.md for shared development rules.')
175
+ expect(lines[5]).toBe('')
176
+ expect(lines[6]).toBe('<!-- PLAITED-RULES-END -->')
177
+ })
178
+
179
+ test('is constant regardless of rules selected', async () => {
180
+ const allRules: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
181
+ const oneRule: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules testing`.json()
182
+ const twoRules: ScaffoldOutput =
183
+ await $`bun ${binDir}/cli.ts scaffold-rules --rules testing --rules accuracy`.json()
178
184
 
179
- expect(result.agent).toBe('agents-md')
180
- expect(result.rulesPath).toBe('.plaited/rules')
181
- expect(result.format).toBe('agents-md')
182
- expect(result.supportsAgentsMd).toBe(true)
185
+ // claudeMdReference should be identical in all cases
186
+ expect(oneRule.claudeMdReference).toBe(allRules.claudeMdReference)
187
+ expect(twoRules.claudeMdReference).toBe(allRules.claudeMdReference)
183
188
  })
184
189
 
185
- test('generates AGENTS.md content', async () => {
186
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
190
+ test('is constant regardless of rules-dir', async () => {
191
+ const defaultPath: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
192
+ const customPath: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
193
+ const anotherPath: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=custom/path`.json()
187
194
 
188
- expect(result.agentsMdContent).toBeDefined()
189
- expect(result.agentsMdContent).toContain('# AGENTS.md')
190
- expect(result.agentsMdContent).toContain('.plaited/rules/')
191
- expect(result.agentsMdContent).toContain('## Rules')
195
+ // claudeMdReference should be identical regardless of rules-dir
196
+ expect(customPath.claudeMdReference).toBe(defaultPath.claudeMdReference)
197
+ expect(anotherPath.claudeMdReference).toBe(defaultPath.claudeMdReference)
192
198
  })
193
199
 
194
- test('AGENTS.md links to all rule files', async () => {
195
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
200
+ test('uses shared markers with agentsMdSection', async () => {
201
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
196
202
 
197
- const agentsMd = result.agentsMdContent ?? ''
203
+ // Both sections should use the same markers for consistency
204
+ const startMarker = '<!-- PLAITED-RULES-START -->'
205
+ const endMarker = '<!-- PLAITED-RULES-END -->'
198
206
 
199
- // Should link to each rule file
200
- for (const [ruleId, template] of Object.entries(result.templates)) {
201
- expect(agentsMd).toContain(`[${ruleId}](.plaited/rules/${template.filename})`)
202
- }
207
+ expect(result.claudeMdReference).toContain(startMarker)
208
+ expect(result.claudeMdReference).toContain(endMarker)
209
+ expect(result.agentsMdSection).toContain(startMarker)
210
+ expect(result.agentsMdSection).toContain(endMarker)
211
+ })
212
+ })
213
+
214
+ describe('agentsMdSection with filtered rules', () => {
215
+ test('only includes selected rules in dual format', async () => {
216
+ const result: ScaffoldOutput =
217
+ await $`bun ${binDir}/cli.ts scaffold-rules --rules testing --rules bun-apis`.json()
218
+
219
+ // Should contain selected rules (both @ and markdown formats)
220
+ expect(result.agentsMdSection).toContain('@.plaited/rules/testing.md')
221
+ expect(result.agentsMdSection).toContain('[testing]')
222
+ expect(result.agentsMdSection).toContain('@.plaited/rules/bun-apis.md')
223
+ expect(result.agentsMdSection).toContain('[bun-apis]')
224
+
225
+ // Should NOT contain other rules
226
+ expect(result.agentsMdSection).not.toContain('@.plaited/rules/accuracy.md')
227
+ expect(result.agentsMdSection).not.toContain('[accuracy]')
228
+ expect(result.agentsMdSection).not.toContain('@.plaited/rules/git-workflow.md')
203
229
  })
204
230
 
205
- test('agents-md has no sandbox (uses standard commit format)', async () => {
206
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
231
+ test('has valid structure with single rule in dual format', async () => {
232
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules testing`.json()
233
+
234
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-START -->')
235
+ expect(result.agentsMdSection).toContain('## Rules')
236
+ // Both formats present
237
+ expect(result.agentsMdSection).toContain('@.plaited/rules/testing.md')
238
+ expect(result.agentsMdSection).toContain('[testing](.plaited/rules/testing.md)')
239
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-END -->')
240
+ })
241
+
242
+ test('has empty rules list when no rules match', async () => {
243
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules nonexistent`.nothrow().json()
244
+
245
+ // Should still have valid structure with markers
246
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-START -->')
247
+ expect(result.agentsMdSection).toContain('## Rules')
248
+ expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-END -->')
249
+
250
+ // But no rule references (neither @ nor markdown)
251
+ expect(result.agentsMdSection).not.toMatch(/@\.plaited\/rules\/.*\.md/)
252
+ expect(result.agentsMdSection).not.toMatch(/\[.*\]\(\.plaited\/rules\/.*\.md\)/)
253
+ })
254
+ })
255
+
256
+ describe('template content', () => {
257
+ test('git-workflow uses standard commit format', async () => {
258
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
207
259
 
208
260
  const gitWorkflow = result.templates['git-workflow']
209
261
  expect(gitWorkflow).toBeDefined()
210
- expect(gitWorkflow!.content).not.toContain('sandbox environment')
211
262
  expect(gitWorkflow!.content).toContain('multi-line commit')
263
+ expect(gitWorkflow!.content).toContain('git commit -m')
212
264
  })
213
265
 
214
- test('agents-md uses CLI syntax (no slash commands)', async () => {
215
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
266
+ test('accuracy uses CLI syntax for LSP tools', async () => {
267
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
216
268
 
217
269
  const accuracy = result.templates.accuracy
218
270
  expect(accuracy).toBeDefined()
219
271
 
220
- // Should use CLI instead of slash commands
272
+ // Should use CLI syntax
221
273
  expect(accuracy!.content).toContain('bunx @plaited/development-skills lsp-')
222
- expect(accuracy!.content).not.toContain('/lsp-hover')
223
274
  })
224
275
 
225
- test('generates agents-md-style cross-references', async () => {
226
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
276
+ test('cross-references use path syntax', async () => {
277
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
227
278
 
228
279
  const accuracy = result.templates.accuracy
229
280
  expect(accuracy).toBeDefined()
230
281
  expect(accuracy!.content).toContain('.plaited/rules/testing.md')
282
+ expect(accuracy!.content).not.toContain('{{LINK:testing}}')
231
283
  })
232
284
  })
233
285
 
234
286
  describe('path customization', () => {
235
- test('includes default agentsMdPath in output', async () => {
236
- const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
287
+ test('--rules-dir overrides default rules path', async () => {
288
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
237
289
 
238
- expect(result.agentsMdPath).toBe('AGENTS.md')
290
+ expect(result.rulesPath).toBe('.cursor/rules')
239
291
  })
240
292
 
241
- test('--rules-dir overrides default rules path', async () => {
242
- const result: ScaffoldOutput =
243
- await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --rules-dir=.cursor/rules --format=json`.json()
293
+ test('claudeMdReference is path-independent', async () => {
294
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
244
295
 
245
- expect(result.rulesPath).toBe('.cursor/rules')
246
- // AGENTS.md content should use custom path
247
- expect(result.agentsMdContent).toContain('.cursor/rules/')
248
- expect(result.agentsMdContent).not.toContain('.plaited/rules/')
296
+ // claudeMdReference always references AGENTS.md regardless of rules path
297
+ expect(result.claudeMdReference).toContain('@AGENTS.md')
298
+ expect(result.claudeMdReference).not.toContain('.cursor/rules')
249
299
  })
250
300
 
251
- test('--agents-md-path overrides default AGENTS.md location', async () => {
252
- const result: ScaffoldOutput =
253
- await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --agents-md-path=docs/AGENTS.md --format=json`.json()
301
+ test('agentsMdSection uses custom path in both formats', async () => {
302
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
254
303
 
255
- expect(result.agentsMdPath).toBe('docs/AGENTS.md')
304
+ // Both @ syntax and markdown links should use custom path
305
+ expect(result.agentsMdSection).toContain('@.cursor/rules/')
306
+ expect(result.agentsMdSection).toContain('](.cursor/rules/')
307
+ expect(result.agentsMdSection).not.toContain('.plaited/rules/')
256
308
  })
257
309
 
258
310
  test('cross-references use custom rules-dir', async () => {
259
- const result: ScaffoldOutput =
260
- await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --rules-dir=.factory/rules --format=json`.json()
311
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.factory/rules`.json()
261
312
 
262
313
  const accuracy = result.templates.accuracy
263
314
  expect(accuracy).toBeDefined()
264
315
  expect(accuracy!.content).toContain('.factory/rules/testing.md')
265
316
  })
266
317
 
267
- test('short flags work (-d and -m)', async () => {
268
- const result: ScaffoldOutput =
269
- await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md -d custom/rules -m custom/AGENTS.md --format=json`.json()
318
+ test('short flag -d works', async () => {
319
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules -d custom/rules`.json()
270
320
 
271
321
  expect(result.rulesPath).toBe('custom/rules')
272
- expect(result.agentsMdPath).toBe('custom/AGENTS.md')
273
322
  })
323
+ })
274
324
 
275
- test('claude agent also respects --rules-dir', async () => {
276
- const result: ScaffoldOutput =
277
- await $`bun ${binDir}/cli.ts scaffold-rules --agent=claude --rules-dir=.my-rules --format=json`.json()
325
+ describe('--list flag', () => {
326
+ test('outputs array of available rules', async () => {
327
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules --list`.json()
328
+
329
+ expect(result).toBeArray()
330
+ expect(result.length).toBeGreaterThan(0)
331
+
332
+ // Each entry should have id and filename
333
+ for (const rule of result) {
334
+ expect(rule).toHaveProperty('id')
335
+ expect(rule).toHaveProperty('filename')
336
+ expect(rule.filename).toBe(`${rule.id}.md`)
337
+ }
338
+ })
339
+
340
+ test('includes expected rules', async () => {
341
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules --list`.json()
342
+ const ids = result.map((r: { id: string }) => r.id)
343
+
344
+ expect(ids).toContain('accuracy')
345
+ expect(ids).toContain('testing')
346
+ expect(ids).toContain('bun-apis')
347
+ })
348
+
349
+ test('short flag -l works', async () => {
350
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules -l`.json()
351
+
352
+ expect(result).toBeArray()
353
+ expect(result.length).toBeGreaterThan(0)
354
+ })
355
+ })
356
+
357
+ describe('--rules validation', () => {
358
+ test('warns about unknown rules', async () => {
359
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules --rules nonexistent --rules testing`.nothrow()
360
+
361
+ // Should still succeed but with warning
362
+ expect(result.exitCode).toBe(0)
363
+ expect(result.stderr.toString()).toContain('Warning: Unknown rules: nonexistent')
364
+ })
365
+
366
+ test('shows available rules in warning', async () => {
367
+ const result = await $`bun ${binDir}/cli.ts scaffold-rules --rules fake-rule`.nothrow()
368
+
369
+ expect(result.stderr.toString()).toContain('Available rules:')
370
+ expect(result.stderr.toString()).toContain('testing')
371
+ })
372
+ })
373
+
374
+ describe('edge cases', () => {
375
+ test('handles all rules filtered out gracefully', async () => {
376
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules nonexistent`.nothrow().json()
377
+
378
+ // Should return valid output with empty templates
379
+ expect(result.templates).toEqual({})
380
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-START -->')
381
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-END -->')
382
+ })
383
+
384
+ test('description extraction falls back for heading-only content', async () => {
385
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
386
+
387
+ // All templates should have non-empty descriptions
388
+ for (const template of Object.values(result.templates)) {
389
+ expect(template.description).toBeTruthy()
390
+ expect(template.description.length).toBeGreaterThan(0)
391
+ }
392
+ })
393
+
394
+ test('processes nested conditionals correctly', async () => {
395
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules accuracy`.json()
278
396
 
279
- expect(result.rulesPath).toBe('.my-rules')
280
- // Cross-references should use custom path
281
397
  const accuracy = result.templates.accuracy
282
398
  expect(accuracy).toBeDefined()
283
- expect(accuracy!.content).toContain('@.my-rules/testing.md')
399
+
400
+ // Should not contain any unprocessed conditional syntax
401
+ expect(accuracy!.content).not.toContain('{{#if')
402
+ expect(accuracy!.content).not.toContain('{{^if')
403
+ expect(accuracy!.content).not.toContain('{{/if}}')
404
+ })
405
+
406
+ test('template content has no excessive blank lines', async () => {
407
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
408
+
409
+ for (const template of Object.values(result.templates)) {
410
+ // Should not have 3+ consecutive newlines
411
+ expect(template.content).not.toMatch(/\n{3,}/)
412
+ }
284
413
  })
285
414
  })
286
415
  })
@@ -1,66 +0,0 @@
1
- ---
2
- description: Analyze a TypeScript file structure, exports, and symbols
3
- allowed-tools: Bash
4
- ---
5
-
6
- # LSP Analyze
7
-
8
- Batch analysis of a TypeScript/JavaScript file. Get an overview of exports, symbols, and optionally type info at specific positions.
9
-
10
- **Arguments:** $ARGUMENTS
11
-
12
- ## Usage
13
-
14
- ```
15
- /lsp-analyze <file> [options]
16
- ```
17
-
18
- Options:
19
- - `--symbols` or `-s`: List all symbols
20
- - `--exports` or `-e`: List only exported symbols (default if no options)
21
- - `--hover <line:char>`: Get type info at position (repeatable)
22
- - `--refs <line:char>`: Find references at position (repeatable)
23
- - `--all`: Run symbols + exports analysis
24
-
25
- ## Instructions
26
-
27
- ### Step 1: Parse Arguments
28
-
29
- Extract file path and options from `$ARGUMENTS`.
30
-
31
- If file is missing, show usage:
32
- ```
33
- Usage: /lsp-analyze <file> [options]
34
-
35
- Examples:
36
- /lsp-analyze src/utils/parser.ts --exports
37
- /lsp-analyze src/lib/config.ts --all
38
- /lsp-analyze src/index.ts --hover 50:10 --refs 60:5
39
- ```
40
-
41
- Default to `--exports` if no options provided.
42
-
43
- ### Step 2: Run LSP Analyze
44
-
45
- Execute the development-skills CLI command:
46
- ```bash
47
- bunx @plaited/development-skills lsp-analyze <file> [options]
48
- ```
49
-
50
- ### Step 3: Format Output
51
-
52
- Parse the JSON output and present in a structured format:
53
-
54
- **File:** `<file>`
55
-
56
- **Exports:**
57
- | Name | Kind | Line |
58
- |------|------|------|
59
- | ... | ... | ... |
60
-
61
- **Symbols:** (if requested)
62
- | Name | Kind | Line |
63
- |------|------|------|
64
- | ... | ... | ... |
65
-
66
- For hover/refs results, format as described in the individual commands.
@@ -1,74 +0,0 @@
1
- ---
2
- description: Search for TypeScript SYMBOLS (functions, types, classes) - NOT text
3
- allowed-tools: Bash
4
- ---
5
-
6
- # LSP Find
7
-
8
- Search for TypeScript **symbols** (functions, types, classes, variables) across the codebase.
9
-
10
- **Arguments:** $ARGUMENTS
11
-
12
- ## When to Use Each Tool
13
-
14
- | Tool | Purpose |
15
- |------|---------|
16
- | **Glob** | Find files by pattern |
17
- | **Grep** | Search text content |
18
- | **lsp-find** | Search TypeScript symbols |
19
- | **lsp-hover** | Get type info + TSDoc documentation |
20
-
21
- ## Usage
22
-
23
- ```
24
- /lsp-find <query> <context-file>
25
- ```
26
-
27
- - `query`: TypeScript symbol name (function, type, class, variable)
28
- - `context-file`: Any `.ts` file in the project for LSP context
29
-
30
- ## When NOT to Use
31
-
32
- ```
33
- # ❌ WRONG: Searching for text (use Grep instead)
34
- /lsp-find scaffold
35
- /lsp-find TODO
36
-
37
- # ❌ WRONG: Missing context file
38
- /lsp-find createClient
39
-
40
- # ✅ CORRECT: Symbol search with context file
41
- /lsp-find createClient src/lsp-client.ts
42
- ```
43
-
44
- ## Instructions
45
-
46
- ### Step 1: Parse Arguments
47
-
48
- Extract query and context file from `$ARGUMENTS`.
49
-
50
- If either is missing, show usage:
51
- ```
52
- Usage: /lsp-find <query> <context-file>
53
-
54
- Examples:
55
- /lsp-find LspClient src/lsp-client.ts
56
- /lsp-find createClient src/app.ts
57
- ```
58
-
59
- ### Step 2: Run LSP Find
60
-
61
- Execute the development-skills CLI command:
62
- ```bash
63
- bunx @plaited/development-skills lsp-find <query> <context-file>
64
- ```
65
-
66
- ### Step 3: Format Output
67
-
68
- Parse the JSON output and present results as a table:
69
-
70
- | Symbol | Kind | File | Line |
71
- |--------|------|------|------|
72
- | ... | ... | ... | ... |
73
-
74
- Group results by file if there are many matches. Highlight the most relevant matches (exact name matches first).