@plaited/development-skills 0.6.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.
@@ -13,7 +13,7 @@ Before completing a code review, run these validation scripts:
13
13
 
14
14
  ### AgentSkills Validation
15
15
 
16
- When working with AgentSkills directories (`.claude/skills/`, `.cursor/skills/`, `.factory/skills/`, etc.):
16
+ When working with AgentSkills directories (`.plaited/skills/`, `.claude/skills/`, `.cursor/skills/`, `.factory/skills/`, etc.):
17
17
 
18
18
  {{#if development-skills}}
19
19
  **Validate structure:**
@@ -0,0 +1,41 @@
1
+ # Documentation Standards
2
+
3
+ ## TSDoc Requirements
4
+
5
+ Public APIs require comprehensive TSDoc documentation following these conventions:
6
+
7
+ - **No `@example` sections** - Tests serve as living examples
8
+ - **Use `@internal` marker** - Mark non-public APIs explicitly
9
+ - **Always use `type`** - Prefer type aliases over interfaces
10
+
11
+ ### TSDoc Template
12
+
13
+ ```typescript
14
+ /**
15
+ * Brief description of what this does
16
+ *
17
+ * @remarks
18
+ * Additional context, usage notes, or implementation details.
19
+ *
20
+ * @param options - Description of the parameter
21
+ * @returns Description of return value
22
+ *
23
+ * @public
24
+ */
25
+ ```
26
+
27
+ ## Diagrams
28
+
29
+ Use Mermaid diagrams only (not ASCII art):
30
+
31
+ ```markdown
32
+ \```mermaid
33
+ flowchart TD
34
+ A[Start] --> B[Process]
35
+ B --> C[End]
36
+ \```
37
+ ```
38
+
39
+ **Avoid**: ASCII box-drawing characters (`┌`, `│`, `└`, `─`, etc.)
40
+
41
+ **Rationale:** Token efficiency, clearer semantic meaning, easier maintenance.
@@ -1,8 +1,3 @@
1
- <!--
2
- RULE TEMPLATE - Distributed via scaffold-rules skill
3
- Variables: {{LINK:*}}
4
- -->
5
-
6
1
  # Git Workflow
7
2
 
8
3
  ## Commit Message Format
@@ -58,7 +58,7 @@ gh api repos/<owner>/<repo>/pulls/<number>/comments --jq '
58
58
  **Step 4: Address ALL feedback**
59
59
  Create a checklist and address each item:
60
60
  - [ ] Human reviewer comments
61
- - [ ] Claude Code review comments
61
+ - [ ] Agentic Code review comments
62
62
  - [ ] GitHub Advanced Security alerts (ReDoS, injection, etc.)
63
63
  - [ ] GitHub Code Quality comments (dead code, useless assignments)
64
64
  - [ ] Inline review suggestions
@@ -68,7 +68,7 @@ Create a checklist and address each item:
68
68
  | Source | API/Location | Description |
69
69
  |--------|--------------|-------------|
70
70
  | Human reviewers | `gh pr view --json reviews` | Code owners, team members |
71
- | Claude Code | `gh pr view --json comments` | AI-generated review (login: `claude`) |
71
+ | AI code review | `gh pr view --json comments` | AI-generated review |
72
72
  | GitHub Advanced Security | `gh api .../code-scanning/alerts` | Security vulnerabilities (ReDoS, injection) |
73
73
  | GitHub Code Quality | `gh api .../pulls/.../comments` | Code quality issues (login: `github-code-quality[bot]`) |
74
74
  | Inline suggestions | `gh api .../pulls/.../comments` | Line-specific review comments |
@@ -76,9 +76,9 @@ Create a checklist and address each item:
76
76
  ### Filtering by Author
77
77
 
78
78
  ```bash
79
- # Get all automated reviews from PR
79
+ # Get all automated reviews from PR (adjust regex for your AI reviewer logins)
80
80
  gh pr view <number> --repo <owner>/<repo> --json reviews --jq '
81
- .reviews[] | select(.author.login | test("github-|claude")) | {author: .author.login, state: .state}
81
+ .reviews[] | select(.author.login | test("github-|bot")) | {author: .author.login, state: .state}
82
82
  '
83
83
 
84
84
  # Get specific inline comment by ID
@@ -1,8 +1,3 @@
1
- <!--
2
- RULE TEMPLATE - Distributed via scaffold-rules skill
3
- Variables: {{#if bun}}
4
- -->
5
-
6
1
  # Module Organization
7
2
 
8
3
  ## No Index Files
@@ -43,9 +38,7 @@ Only use `main.ts` if the package truly has multiple co-equal entry points that
43
38
 
44
39
  ## Explicit Import Extensions
45
40
 
46
- {{#if bun}}
47
41
  Always include `.ts` extensions in imports. Bun runs TypeScript natively—no compilation required:
48
- {{/if}}
49
42
 
50
43
  ```typescript
51
44
  // ✅ Good
@@ -1,8 +1,3 @@
1
- <!--
2
- RULE TEMPLATE - Distributed via scaffold-rules skill
3
- Variables: {{#if agent:claude}}
4
- -->
5
-
6
1
  # Testing
7
2
 
8
3
  Use Bun's built-in test runner for unit and integration tests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaited/development-skills",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Development skills for Claude Code - TypeScript LSP, code documentation, and validation tools",
5
5
  "license": "ISC",
6
6
  "engines": {
@@ -6,8 +6,8 @@
6
6
  * and outputs JSON for agent consumption.
7
7
  *
8
8
  * All agents use `.plaited/rules/` as the unified default location.
9
- * The output includes marker-wrapped sections for both CLAUDE.md and AGENTS.md,
10
- * allowing programmatic updates without destroying user content.
9
+ * AGENTS.md serves as the single source of truth for rules content,
10
+ * while CLAUDE.md simply references it via @AGENTS.md syntax.
11
11
  *
12
12
  * Options:
13
13
  * - --rules-dir, -d: Custom rules directory path (overrides default .plaited/rules)
@@ -15,8 +15,8 @@
15
15
  * - --list, -l: List available rules without full output
16
16
  *
17
17
  * Output includes:
18
- * - claudeMdSection: Marker-wrapped section with @ syntax for CLAUDE.md
19
18
  * - agentsMdSection: Marker-wrapped section with markdown links for AGENTS.md
19
+ * - claudeMdReference: Short reference snippet pointing to @AGENTS.md
20
20
  * - templates: Processed rule content for each selected rule
21
21
  *
22
22
  * Template syntax:
@@ -95,10 +95,10 @@ export type ProcessedTemplate = {
95
95
  */
96
96
  export type ScaffoldOutput = {
97
97
  rulesPath: string
98
- /** Marker-wrapped section with @ syntax for CLAUDE.md */
99
- claudeMdSection: string
100
98
  /** Marker-wrapped section with markdown links for AGENTS.md */
101
99
  agentsMdSection: string
100
+ /** Short reference snippet for CLAUDE.md pointing to AGENTS.md */
101
+ claudeMdReference: string
102
102
  templates: Record<string, ProcessedTemplate>
103
103
  }
104
104
 
@@ -214,38 +214,34 @@ const processTemplate = (content: string, context: TemplateContext): string => {
214
214
  }
215
215
 
216
216
  /**
217
- * Generate marker-wrapped section for CLAUDE.md with @ syntax
217
+ * Generate marker-wrapped reference snippet for CLAUDE.md
218
218
  *
219
219
  * @remarks
220
- * Claude Code uses `@path/to/file.md` syntax to reference rule files.
220
+ * Claude Code uses `@file.md` syntax to include file contents.
221
+ * This generates a short reference pointing to AGENTS.md as the single source of truth.
221
222
  * The markers allow this section to be updated without affecting other content.
222
223
  */
223
- const generateClaudeMdSection = (templates: Record<string, ProcessedTemplate>, rulesPath: string): string => {
224
+ const generateClaudeMdReference = (): string => {
224
225
  const lines = [
225
226
  MARKERS.start,
226
227
  '',
227
228
  '## Project Rules',
228
229
  '',
229
- `This project uses modular development rules stored in \`${rulesPath}/\`.`,
230
- 'Each rule file covers a specific topic:',
230
+ 'See @AGENTS.md for shared development rules.',
231
231
  '',
232
+ MARKERS.end,
232
233
  ]
233
234
 
234
- for (const [_ruleId, template] of Object.entries(templates)) {
235
- lines.push(`- @${rulesPath}/${template.filename} - ${template.description}`)
236
- }
237
-
238
- lines.push('')
239
- lines.push(MARKERS.end)
240
-
241
235
  return lines.join('\n')
242
236
  }
243
237
 
244
238
  /**
245
- * Generate marker-wrapped section for AGENTS.md with markdown links
239
+ * Generate marker-wrapped section for AGENTS.md with dual format
246
240
  *
247
241
  * @remarks
248
- * AGENTS.md format uses standard markdown links to reference rule files.
242
+ * AGENTS.md uses both formats for maximum compatibility:
243
+ * - `@path` syntax for Claude Code to load file contents
244
+ * - `[name](path)` markdown links for other tools and GitHub rendering
249
245
  * The markers allow this section to be updated without affecting other content.
250
246
  */
251
247
  const generateAgentsMdSection = (templates: Record<string, ProcessedTemplate>, rulesPath: string): string => {
@@ -260,7 +256,8 @@ const generateAgentsMdSection = (templates: Record<string, ProcessedTemplate>, r
260
256
  ]
261
257
 
262
258
  for (const [ruleId, template] of Object.entries(templates)) {
263
- lines.push(`- [${ruleId}](${rulesPath}/${template.filename}) - ${template.description}`)
259
+ // Dual format: @ syntax for Claude Code, markdown link for other tools
260
+ lines.push(`- @${rulesPath}/${template.filename} - [${ruleId}](${rulesPath}/${template.filename})`)
264
261
  }
265
262
 
266
263
  lines.push('')
@@ -358,15 +355,15 @@ export const scaffoldRules = async (args: string[]): Promise<void> => {
358
355
  }
359
356
  }
360
357
 
361
- // Generate marker-wrapped sections for both CLAUDE.md and AGENTS.md
362
- const claudeMdSection = generateClaudeMdSection(templates, rulesPath)
358
+ // Generate marker-wrapped section for AGENTS.md and reference for CLAUDE.md
363
359
  const agentsMdSection = generateAgentsMdSection(templates, rulesPath)
360
+ const claudeMdReference = generateClaudeMdReference()
364
361
 
365
362
  // Build output
366
363
  const output: ScaffoldOutput = {
367
364
  rulesPath,
368
- claudeMdSection,
369
365
  agentsMdSection,
366
+ claudeMdReference,
370
367
  templates,
371
368
  }
372
369
 
@@ -10,8 +10,8 @@ type Template = {
10
10
 
11
11
  type ScaffoldOutput = {
12
12
  rulesPath: string
13
- claudeMdSection: string
14
13
  agentsMdSection: string
14
+ claudeMdReference: string
15
15
  templates: Record<string, Template>
16
16
  }
17
17
 
@@ -121,42 +121,138 @@ describe('scaffold-rules', () => {
121
121
  expect(result.rulesPath).toBe('.plaited/rules')
122
122
  })
123
123
 
124
- test('includes claudeMdSection with markers and @ syntax', async () => {
124
+ test('includes claudeMdReference with markers and @AGENTS.md', async () => {
125
125
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
126
126
 
127
- expect(result.claudeMdSection).toContain('<!-- PLAITED-RULES-START -->')
128
- expect(result.claudeMdSection).toContain('<!-- PLAITED-RULES-END -->')
129
- expect(result.claudeMdSection).toContain('@.plaited/rules/')
130
- expect(result.claudeMdSection).toContain('## Project Rules')
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
131
  })
132
132
 
133
- test('includes agentsMdSection with markers and markdown links', async () => {
133
+ test('includes agentsMdSection with markers and dual format', async () => {
134
134
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
135
135
 
136
136
  expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-START -->')
137
137
  expect(result.agentsMdSection).toContain('<!-- PLAITED-RULES-END -->')
138
- expect(result.agentsMdSection).toContain('[')
138
+ // Should have @ syntax for Claude Code
139
+ expect(result.agentsMdSection).toContain('@.plaited/rules/')
140
+ // Should have markdown links for other tools
139
141
  expect(result.agentsMdSection).toContain('](.plaited/rules/')
140
142
  expect(result.agentsMdSection).toContain('## Rules')
141
143
  })
142
144
 
143
- test('claudeMdSection lists all selected rules', async () => {
145
+ test('claudeMdReference does not list individual rules', async () => {
144
146
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
145
147
 
146
- for (const template of Object.values(result.templates)) {
147
- expect(result.claudeMdSection).toContain(`@.plaited/rules/${template.filename}`)
148
- }
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')
149
151
  })
150
152
 
151
- test('agentsMdSection lists all selected rules', async () => {
153
+ test('agentsMdSection lists all selected rules in dual format', async () => {
152
154
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
153
155
 
154
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}`)
155
159
  expect(result.agentsMdSection).toContain(`[${ruleId}](.plaited/rules/${template.filename})`)
156
160
  }
157
161
  })
158
162
  })
159
163
 
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()
184
+
185
+ // claudeMdReference should be identical in all cases
186
+ expect(oneRule.claudeMdReference).toBe(allRules.claudeMdReference)
187
+ expect(twoRules.claudeMdReference).toBe(allRules.claudeMdReference)
188
+ })
189
+
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()
194
+
195
+ // claudeMdReference should be identical regardless of rules-dir
196
+ expect(customPath.claudeMdReference).toBe(defaultPath.claudeMdReference)
197
+ expect(anotherPath.claudeMdReference).toBe(defaultPath.claudeMdReference)
198
+ })
199
+
200
+ test('uses shared markers with agentsMdSection', async () => {
201
+ const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
202
+
203
+ // Both sections should use the same markers for consistency
204
+ const startMarker = '<!-- PLAITED-RULES-START -->'
205
+ const endMarker = '<!-- PLAITED-RULES-END -->'
206
+
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')
229
+ })
230
+
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
+
160
256
  describe('template content', () => {
161
257
  test('git-workflow uses standard commit format', async () => {
162
258
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules`.json()
@@ -194,17 +290,20 @@ describe('scaffold-rules', () => {
194
290
  expect(result.rulesPath).toBe('.cursor/rules')
195
291
  })
196
292
 
197
- test('claudeMdSection uses custom path', async () => {
293
+ test('claudeMdReference is path-independent', async () => {
198
294
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
199
295
 
200
- expect(result.claudeMdSection).toContain('@.cursor/rules/')
201
- expect(result.claudeMdSection).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')
202
299
  })
203
300
 
204
- test('agentsMdSection uses custom path', async () => {
301
+ test('agentsMdSection uses custom path in both formats', async () => {
205
302
  const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --rules-dir=.cursor/rules`.json()
206
303
 
207
- expect(result.agentsMdSection).toContain('.cursor/rules/')
304
+ // Both @ syntax and markdown links should use custom path
305
+ expect(result.agentsMdSection).toContain('@.cursor/rules/')
306
+ expect(result.agentsMdSection).toContain('](.cursor/rules/')
208
307
  expect(result.agentsMdSection).not.toContain('.plaited/rules/')
209
308
  })
210
309
 
@@ -278,8 +377,8 @@ describe('scaffold-rules', () => {
278
377
 
279
378
  // Should return valid output with empty templates
280
379
  expect(result.templates).toEqual({})
281
- expect(result.claudeMdSection).toContain('<!-- PLAITED-RULES-START -->')
282
- expect(result.claudeMdSection).toContain('<!-- PLAITED-RULES-END -->')
380
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-START -->')
381
+ expect(result.claudeMdReference).toContain('<!-- PLAITED-RULES-END -->')
283
382
  })
284
383
 
285
384
  test('description extraction falls back for heading-only content', async () => {