@plaited/development-skills 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/lsp-analyze.md +66 -0
- package/.claude/commands/lsp-find.md +51 -0
- package/.claude/commands/lsp-hover.md +48 -0
- package/.claude/commands/lsp-refs.md +55 -0
- package/.claude/commands/scaffold-rules.md +221 -0
- package/.claude/commands/validate-skill.md +29 -0
- package/.claude/rules/accuracy.md +64 -0
- package/.claude/rules/bun-apis.md +80 -0
- package/.claude/rules/code-review.md +276 -0
- package/.claude/rules/git-workflow.md +66 -0
- package/.claude/rules/github.md +154 -0
- package/.claude/rules/testing.md +125 -0
- package/.claude/settings.local.json +47 -0
- package/.claude/skills/code-documentation/SKILL.md +47 -0
- package/.claude/skills/code-documentation/references/internal-templates.md +113 -0
- package/.claude/skills/code-documentation/references/maintenance.md +164 -0
- package/.claude/skills/code-documentation/references/public-api-templates.md +100 -0
- package/.claude/skills/code-documentation/references/type-documentation.md +116 -0
- package/.claude/skills/code-documentation/references/workflow.md +60 -0
- package/.claude/skills/scaffold-rules/SKILL.md +97 -0
- package/.claude/skills/typescript-lsp/SKILL.md +239 -0
- package/.claude/skills/validate-skill/SKILL.md +105 -0
- package/LICENSE +15 -0
- package/README.md +149 -0
- package/bin/cli.ts +109 -0
- package/package.json +57 -0
- package/src/lsp-analyze.ts +223 -0
- package/src/lsp-client.ts +400 -0
- package/src/lsp-find.ts +100 -0
- package/src/lsp-hover.ts +87 -0
- package/src/lsp-references.ts +83 -0
- package/src/lsp-symbols.ts +73 -0
- package/src/resolve-file-path.ts +28 -0
- package/src/scaffold-rules.ts +435 -0
- package/src/tests/fixtures/sample.ts +27 -0
- package/src/tests/lsp-client.spec.ts +180 -0
- package/src/tests/resolve-file-path.spec.ts +33 -0
- package/src/tests/scaffold-rules.spec.ts +286 -0
- package/src/tests/validate-skill.spec.ts +231 -0
- package/src/validate-skill.ts +492 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample fixture for LSP tests
|
|
3
|
+
*/
|
|
4
|
+
export type Config = {
|
|
5
|
+
name: string
|
|
6
|
+
value: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const parseConfig = (input: string): Config => {
|
|
10
|
+
return { name: input, value: 42 }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const validateInput = (input: unknown): input is string => {
|
|
14
|
+
return typeof input === 'string'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ConfigManager {
|
|
18
|
+
#config: Config | null = null
|
|
19
|
+
|
|
20
|
+
load(input: string): void {
|
|
21
|
+
this.#config = parseConfig(input)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(): Config | null {
|
|
25
|
+
return this.#config
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { LspClient } from '../lsp-client.ts'
|
|
3
|
+
|
|
4
|
+
const rootUri = `file://${process.cwd()}`
|
|
5
|
+
const testFile = `${import.meta.dir}/fixtures/sample.ts`
|
|
6
|
+
const testUri = `file://${testFile}`
|
|
7
|
+
|
|
8
|
+
describe('LspClient', () => {
|
|
9
|
+
let client: LspClient
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
client = new LspClient({ rootUri })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterAll(async () => {
|
|
16
|
+
if (client.isRunning()) {
|
|
17
|
+
await client.stop()
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('initializes with rootUri', () => {
|
|
22
|
+
expect(client).toBeDefined()
|
|
23
|
+
expect(client.isRunning()).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('starts and stops LSP server', async () => {
|
|
27
|
+
await client.start()
|
|
28
|
+
expect(client.isRunning()).toBe(true)
|
|
29
|
+
|
|
30
|
+
await client.stop()
|
|
31
|
+
expect(client.isRunning()).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('throws when starting already running server', async () => {
|
|
35
|
+
await client.start()
|
|
36
|
+
expect(client.isRunning()).toBe(true)
|
|
37
|
+
|
|
38
|
+
await expect(client.start()).rejects.toThrow('LSP server already running')
|
|
39
|
+
|
|
40
|
+
await client.stop()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('handles stop on non-running server gracefully', async () => {
|
|
44
|
+
expect(client.isRunning()).toBe(false)
|
|
45
|
+
await client.stop()
|
|
46
|
+
expect(client.isRunning()).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('LSP operations', () => {
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
await client.start()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
afterAll(async () => {
|
|
55
|
+
await client.stop()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('opens and closes document', async () => {
|
|
59
|
+
const text = await Bun.file(testFile).text()
|
|
60
|
+
|
|
61
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
62
|
+
client.closeDocument(testUri)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('gets hover information', async () => {
|
|
66
|
+
const text = await Bun.file(testFile).text()
|
|
67
|
+
|
|
68
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
69
|
+
|
|
70
|
+
// Find 'export' keyword position for reliable hover
|
|
71
|
+
const lines = text.split('\n')
|
|
72
|
+
let line = 0
|
|
73
|
+
let char = 0
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
if (lines[i]?.startsWith('export')) {
|
|
76
|
+
line = i
|
|
77
|
+
char = 0
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = await client.hover(testUri, line, char)
|
|
83
|
+
expect(result).toBeDefined()
|
|
84
|
+
|
|
85
|
+
client.closeDocument(testUri)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('gets document symbols', async () => {
|
|
89
|
+
const text = await Bun.file(testFile).text()
|
|
90
|
+
|
|
91
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
92
|
+
|
|
93
|
+
const result = await client.documentSymbols(testUri)
|
|
94
|
+
|
|
95
|
+
expect(result).toBeDefined()
|
|
96
|
+
expect(Array.isArray(result)).toBe(true)
|
|
97
|
+
|
|
98
|
+
client.closeDocument(testUri)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('searches workspace symbols', async () => {
|
|
102
|
+
// Open a document first so LSP has a project context
|
|
103
|
+
const text = await Bun.file(testFile).text()
|
|
104
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
105
|
+
|
|
106
|
+
const result = await client.workspaceSymbols('parseConfig')
|
|
107
|
+
|
|
108
|
+
expect(result).toBeDefined()
|
|
109
|
+
expect(Array.isArray(result)).toBe(true)
|
|
110
|
+
|
|
111
|
+
client.closeDocument(testUri)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('finds references', async () => {
|
|
115
|
+
const text = await Bun.file(testFile).text()
|
|
116
|
+
|
|
117
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
118
|
+
|
|
119
|
+
// Find an exported symbol
|
|
120
|
+
const lines = text.split('\n')
|
|
121
|
+
let line = 0
|
|
122
|
+
let char = 0
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const currentLine = lines[i]
|
|
125
|
+
if (!currentLine) continue
|
|
126
|
+
const match = currentLine.match(/export\s+const\s+(\w+)/)
|
|
127
|
+
if (match?.[1]) {
|
|
128
|
+
line = i
|
|
129
|
+
char = currentLine.indexOf(match[1])
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = await client.references(testUri, line, char)
|
|
135
|
+
expect(result).toBeDefined()
|
|
136
|
+
|
|
137
|
+
client.closeDocument(testUri)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('gets definition', async () => {
|
|
141
|
+
const text = await Bun.file(testFile).text()
|
|
142
|
+
|
|
143
|
+
client.openDocument(testUri, 'typescript', 1, text)
|
|
144
|
+
|
|
145
|
+
// Find an import to get definition for
|
|
146
|
+
const lines = text.split('\n')
|
|
147
|
+
let line = 0
|
|
148
|
+
let char = 0
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
const currentLine = lines[i]
|
|
151
|
+
if (!currentLine) continue
|
|
152
|
+
const match = currentLine.match(/import\s+.*{\s*(\w+)/)
|
|
153
|
+
if (match?.[1]) {
|
|
154
|
+
line = i
|
|
155
|
+
char = currentLine.indexOf(match[1])
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const result = await client.definition(testUri, line, char)
|
|
161
|
+
expect(result).toBeDefined()
|
|
162
|
+
|
|
163
|
+
client.closeDocument(testUri)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('error handling', () => {
|
|
168
|
+
test('throws on request when server not running', async () => {
|
|
169
|
+
const notRunningClient = new LspClient({ rootUri })
|
|
170
|
+
|
|
171
|
+
await expect(notRunningClient.hover('file:///test.ts', 0, 0)).rejects.toThrow('LSP server not running')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('throws on notify when server not running', () => {
|
|
175
|
+
const notRunningClient = new LspClient({ rootUri })
|
|
176
|
+
|
|
177
|
+
expect(() => notRunningClient.notify('test')).toThrow('LSP server not running')
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { resolveFilePath } from '../resolve-file-path.ts'
|
|
3
|
+
|
|
4
|
+
describe('resolveFilePath', () => {
|
|
5
|
+
test('returns absolute path as-is', async () => {
|
|
6
|
+
const absolutePath = '/Users/test/file.ts'
|
|
7
|
+
const result = await resolveFilePath(absolutePath)
|
|
8
|
+
expect(result).toBe(absolutePath)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('resolves relative path from cwd', async () => {
|
|
12
|
+
const relativePath = './plugin/skills/typescript-lsp/scripts/tests/fixtures/sample.ts'
|
|
13
|
+
const result = await resolveFilePath(relativePath)
|
|
14
|
+
expect(result).toBe(`${process.cwd()}/${relativePath}`)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('resolves package export path via Bun.resolve', async () => {
|
|
18
|
+
// Use typescript package which is installed as devDependency
|
|
19
|
+
const packagePath = 'typescript'
|
|
20
|
+
const result = await resolveFilePath(packagePath)
|
|
21
|
+
|
|
22
|
+
// Should resolve to node_modules/typescript/...
|
|
23
|
+
expect(result).toContain('node_modules/typescript')
|
|
24
|
+
expect(result.startsWith('/')).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('falls back to cwd for non-existent package', async () => {
|
|
28
|
+
const invalidPath = 'nonexistent-package/file.ts'
|
|
29
|
+
const result = await resolveFilePath(invalidPath)
|
|
30
|
+
|
|
31
|
+
expect(result).toBe(`${process.cwd()}/${invalidPath}`)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
|
|
5
|
+
type Template = {
|
|
6
|
+
filename: string
|
|
7
|
+
content: string
|
|
8
|
+
description: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type ScaffoldOutput = {
|
|
12
|
+
agent: string
|
|
13
|
+
rulesPath: string
|
|
14
|
+
agentsMdPath: string
|
|
15
|
+
format: 'multi-file' | 'agents-md'
|
|
16
|
+
supportsAgentsMd: boolean
|
|
17
|
+
agentsMdContent?: string
|
|
18
|
+
templates: Record<string, Template>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const binDir = join(import.meta.dir, '../../bin')
|
|
22
|
+
|
|
23
|
+
describe('scaffold-rules', () => {
|
|
24
|
+
test('outputs JSON with all templates', async () => {
|
|
25
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
|
|
26
|
+
|
|
27
|
+
expect(result).toHaveProperty('templates')
|
|
28
|
+
expect(result.templates).toBeObject()
|
|
29
|
+
|
|
30
|
+
// Check that we have the expected templates
|
|
31
|
+
const templateKeys = Object.keys(result.templates)
|
|
32
|
+
expect(templateKeys).toContain('accuracy')
|
|
33
|
+
expect(templateKeys).toContain('bun-apis')
|
|
34
|
+
expect(templateKeys).toContain('code-review')
|
|
35
|
+
expect(templateKeys).toContain('git-workflow')
|
|
36
|
+
expect(templateKeys).toContain('github')
|
|
37
|
+
expect(templateKeys).toContain('testing')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('each template has required properties', async () => {
|
|
41
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
|
|
42
|
+
|
|
43
|
+
for (const [ruleId, template] of Object.entries(result.templates)) {
|
|
44
|
+
expect(template).toHaveProperty('filename')
|
|
45
|
+
expect(template).toHaveProperty('content')
|
|
46
|
+
expect(template).toHaveProperty('description')
|
|
47
|
+
|
|
48
|
+
expect(template.filename).toBe(`${ruleId}.md`)
|
|
49
|
+
expect(template.content).toBeString()
|
|
50
|
+
expect(template.content.length).toBeGreaterThan(0)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('removes template headers from content', async () => {
|
|
55
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
|
|
56
|
+
|
|
57
|
+
// Check that template headers are removed
|
|
58
|
+
for (const template of Object.values(result.templates)) {
|
|
59
|
+
expect(template.content).not.toContain('<!-- RULE TEMPLATE')
|
|
60
|
+
expect(template.content).not.toContain('Variables:')
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('processes development-skills conditionals', async () => {
|
|
65
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
|
|
66
|
+
|
|
67
|
+
const accuracy = result.templates.accuracy
|
|
68
|
+
expect(accuracy).toBeDefined()
|
|
69
|
+
|
|
70
|
+
// Should include development-skills content (always true when using CLI)
|
|
71
|
+
expect(accuracy!.content).toContain('TypeScript/JavaScript projects')
|
|
72
|
+
expect(accuracy!.content).toContain('lsp-find')
|
|
73
|
+
expect(accuracy!.content).toContain('lsp-hover')
|
|
74
|
+
|
|
75
|
+
// Should not have conditional syntax
|
|
76
|
+
expect(accuracy!.content).not.toContain('{{#if development-skills}}')
|
|
77
|
+
expect(accuracy!.content).not.toContain('{{/if}}')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
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()
|
|
83
|
+
|
|
84
|
+
const templateKeys = Object.keys(result.templates)
|
|
85
|
+
|
|
86
|
+
// Should only include requested rules
|
|
87
|
+
expect(templateKeys).toHaveLength(2)
|
|
88
|
+
expect(templateKeys).toContain('testing')
|
|
89
|
+
expect(templateKeys).toContain('bun-apis')
|
|
90
|
+
|
|
91
|
+
// Should not include other rules
|
|
92
|
+
expect(templateKeys).not.toContain('accuracy')
|
|
93
|
+
expect(templateKeys).not.toContain('git-workflow')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('extracts meaningful descriptions', async () => {
|
|
97
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --format=json`.json()
|
|
98
|
+
|
|
99
|
+
// Check a few descriptions
|
|
100
|
+
const accuracy = result.templates.accuracy
|
|
101
|
+
expect(accuracy).toBeDefined()
|
|
102
|
+
expect(accuracy!.description).toBeString()
|
|
103
|
+
expect(accuracy!.description.length).toBeGreaterThan(10)
|
|
104
|
+
|
|
105
|
+
const testing = result.templates.testing
|
|
106
|
+
expect(testing).toBeDefined()
|
|
107
|
+
expect(testing!.description).toBeString()
|
|
108
|
+
expect(testing!.description.length).toBeGreaterThan(10)
|
|
109
|
+
})
|
|
110
|
+
|
|
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
|
+
test('handles missing bundled rules directory gracefully', async () => {
|
|
121
|
+
// 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()
|
|
124
|
+
|
|
125
|
+
// Should succeed because .claude/rules/ exists in development
|
|
126
|
+
expect(result.exitCode).toBe(0)
|
|
127
|
+
})
|
|
128
|
+
|
|
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()
|
|
132
|
+
|
|
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)
|
|
137
|
+
})
|
|
138
|
+
|
|
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()
|
|
141
|
+
|
|
142
|
+
const gitWorkflow = result.templates['git-workflow']
|
|
143
|
+
expect(gitWorkflow).toBeDefined()
|
|
144
|
+
|
|
145
|
+
// Claude has sandbox - should include sandbox-specific content
|
|
146
|
+
expect(gitWorkflow!.content).toContain('sandbox environment')
|
|
147
|
+
expect(gitWorkflow!.content).toContain('single-quoted strings')
|
|
148
|
+
|
|
149
|
+
// Should not have conditional syntax
|
|
150
|
+
expect(gitWorkflow!.content).not.toContain('{{#if has-sandbox}}')
|
|
151
|
+
expect(gitWorkflow!.content).not.toContain('{{/if}}')
|
|
152
|
+
})
|
|
153
|
+
|
|
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()
|
|
156
|
+
|
|
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')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('generates Claude-style cross-references', async () => {
|
|
166
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=claude --format=json`.json()
|
|
167
|
+
|
|
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}}')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
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()
|
|
178
|
+
|
|
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)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('generates AGENTS.md content', async () => {
|
|
186
|
+
const result: ScaffoldOutput = await $`bun ${binDir}/cli.ts scaffold-rules --agent=agents-md --format=json`.json()
|
|
187
|
+
|
|
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')
|
|
192
|
+
})
|
|
193
|
+
|
|
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()
|
|
196
|
+
|
|
197
|
+
const agentsMd = result.agentsMdContent ?? ''
|
|
198
|
+
|
|
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
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
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()
|
|
207
|
+
|
|
208
|
+
const gitWorkflow = result.templates['git-workflow']
|
|
209
|
+
expect(gitWorkflow).toBeDefined()
|
|
210
|
+
expect(gitWorkflow!.content).not.toContain('sandbox environment')
|
|
211
|
+
expect(gitWorkflow!.content).toContain('multi-line commit')
|
|
212
|
+
})
|
|
213
|
+
|
|
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()
|
|
216
|
+
|
|
217
|
+
const accuracy = result.templates.accuracy
|
|
218
|
+
expect(accuracy).toBeDefined()
|
|
219
|
+
|
|
220
|
+
// Should use CLI instead of slash commands
|
|
221
|
+
expect(accuracy!.content).toContain('bunx @plaited/development-skills lsp-')
|
|
222
|
+
expect(accuracy!.content).not.toContain('/lsp-hover')
|
|
223
|
+
})
|
|
224
|
+
|
|
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()
|
|
227
|
+
|
|
228
|
+
const accuracy = result.templates.accuracy
|
|
229
|
+
expect(accuracy).toBeDefined()
|
|
230
|
+
expect(accuracy!.content).toContain('.plaited/rules/testing.md')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
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()
|
|
237
|
+
|
|
238
|
+
expect(result.agentsMdPath).toBe('AGENTS.md')
|
|
239
|
+
})
|
|
240
|
+
|
|
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()
|
|
244
|
+
|
|
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/')
|
|
249
|
+
})
|
|
250
|
+
|
|
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()
|
|
254
|
+
|
|
255
|
+
expect(result.agentsMdPath).toBe('docs/AGENTS.md')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
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()
|
|
261
|
+
|
|
262
|
+
const accuracy = result.templates.accuracy
|
|
263
|
+
expect(accuracy).toBeDefined()
|
|
264
|
+
expect(accuracy!.content).toContain('.factory/rules/testing.md')
|
|
265
|
+
})
|
|
266
|
+
|
|
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()
|
|
270
|
+
|
|
271
|
+
expect(result.rulesPath).toBe('custom/rules')
|
|
272
|
+
expect(result.agentsMdPath).toBe('custom/AGENTS.md')
|
|
273
|
+
})
|
|
274
|
+
|
|
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()
|
|
278
|
+
|
|
279
|
+
expect(result.rulesPath).toBe('.my-rules')
|
|
280
|
+
// Cross-references should use custom path
|
|
281
|
+
const accuracy = result.templates.accuracy
|
|
282
|
+
expect(accuracy).toBeDefined()
|
|
283
|
+
expect(accuracy!.content).toContain('@.my-rules/testing.md')
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
})
|