@plaited/development-skills 0.6.5 → 0.8.0
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/README.md +3 -3
- package/bin/cli.ts +2 -2
- package/package.json +3 -3
- package/{.plaited/rules → rules}/accuracy.md +1 -1
- package/{.plaited/rules → rules}/modules.md +10 -5
- package/rules/skill-activation.md +26 -0
- package/{.plaited/rules → rules}/testing.md +1 -1
- package/{.plaited/skills → skills}/scaffold-rules/SKILL.md +24 -18
- package/src/scaffold-rules.ts +48 -91
- package/src/tests/resolve-file-path.spec.ts +0 -21
- package/src/tests/scaffold-rules.spec.ts +77 -124
- /package/{.plaited/rules → rules}/bun.md +0 -0
- /package/{.plaited/rules → rules}/core.md +0 -0
- /package/{.plaited/rules → rules}/documentation.md +0 -0
- /package/{.plaited/rules → rules}/workflow.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/SKILL.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/references/internal-templates.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/references/maintenance.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/references/public-api-templates.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/references/type-documentation.md +0 -0
- /package/{.plaited/skills → skills}/code-documentation/references/workflow.md +0 -0
- /package/{.plaited/skills → skills}/optimize-agents-md/SKILL.md +0 -0
- /package/{.plaited/skills → skills}/typescript-lsp/SKILL.md +0 -0
- /package/{.plaited/skills → skills}/validate-skill/SKILL.md +0 -0
package/README.md
CHANGED
|
@@ -42,11 +42,11 @@ LSP understands re-exports, aliases, and type relationships.
|
|
|
42
42
|
## Install for AI Agents
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
|
|
45
|
+
npx skills add plaited/development-skills
|
|
46
|
+
# or
|
|
47
|
+
bunx skills add plaited/development-skills
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
**Agents:** `claude` · `cursor` · `copilot` · `codex` · `gemini` · `windsurf` · `opencode` · `amp` · `goose` · `factory`
|
|
49
|
-
|
|
50
50
|
## Commands
|
|
51
51
|
|
|
52
52
|
| Command | What it does |
|
package/bin/cli.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* bunx @plaited/development-skills lsp-hover src/index.ts 10 5
|
|
21
21
|
* bunx @plaited/development-skills lsp-find parseConfig
|
|
22
22
|
* bunx @plaited/development-skills validate-skill .claude/skills/my-skill
|
|
23
|
-
* bunx @plaited/development-skills scaffold-rules
|
|
23
|
+
* bunx @plaited/development-skills scaffold-rules
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { lspAnalyze } from '../src/lsp-analyze.ts'
|
|
@@ -64,7 +64,7 @@ Examples:
|
|
|
64
64
|
bunx @plaited/development-skills lsp-symbols src/app.ts
|
|
65
65
|
bunx @plaited/development-skills lsp-analyze src/app.ts
|
|
66
66
|
bunx @plaited/development-skills validate-skill .claude/skills/my-skill
|
|
67
|
-
bunx @plaited/development-skills scaffold-rules
|
|
67
|
+
bunx @plaited/development-skills scaffold-rules
|
|
68
68
|
|
|
69
69
|
Options:
|
|
70
70
|
-h, --help Show this help
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaited/development-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Development skills for Claude Code - TypeScript LSP, code documentation, and validation tools",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"engines": {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"bin/",
|
|
23
23
|
"src/",
|
|
24
|
-
"
|
|
24
|
+
"rules/",
|
|
25
|
+
"skills/"
|
|
25
26
|
],
|
|
26
27
|
"publishConfig": {
|
|
27
28
|
"access": "public"
|
|
@@ -48,7 +49,6 @@
|
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@biomejs/biome": "2.3.11",
|
|
51
|
-
"@modelcontextprotocol/sdk": "1.22.0",
|
|
52
52
|
"@types/bun": "1.3.6",
|
|
53
53
|
"format-package": "7.0.0",
|
|
54
54
|
"lint-staged": "16.2.7",
|
|
@@ -9,11 +9,16 @@
|
|
|
9
9
|
*Fix:* Add `.ts` extension
|
|
10
10
|
|
|
11
11
|
**Re-export at boundaries** - Parent `feature.ts` re-exports from `feature/feature.ts`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
graph TD
|
|
15
|
+
A[src/] --> B[feature/]
|
|
16
|
+
A --> C[feature.ts]
|
|
17
|
+
B --> D[feature.ts]
|
|
18
|
+
B --> E[tests/]
|
|
19
|
+
E --> F[feature.spec.ts]
|
|
20
|
+
|
|
21
|
+
C -.Re-exports.-> D
|
|
17
22
|
```
|
|
18
23
|
|
|
19
24
|
**File organization within modules:**
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Skill Activation
|
|
2
|
+
|
|
3
|
+
**Evaluate before implementing** - Check available skills for relevance before starting work
|
|
4
|
+
|
|
5
|
+
**Activation sequence:**
|
|
6
|
+
|
|
7
|
+
1. **Evaluate** - For each skill in `<available_skills>`, assess: `[skill-name] - YES/NO - [reason]`
|
|
8
|
+
2. **Activate** - Call `Skill(skill-name)` for each relevant skill before proceeding
|
|
9
|
+
3. **Implement** - Begin work only after activation is complete
|
|
10
|
+
|
|
11
|
+
*Verify:* Did you check available skills before starting implementation?
|
|
12
|
+
*Fix:* Pause, evaluate skills, activate relevant ones, then continue
|
|
13
|
+
|
|
14
|
+
**Example:**
|
|
15
|
+
```
|
|
16
|
+
- code-patterns: NO - not writing code
|
|
17
|
+
- git-workflow: YES - need commit conventions
|
|
18
|
+
- documentation: YES - writing README
|
|
19
|
+
|
|
20
|
+
> Skill(git-workflow)
|
|
21
|
+
> Skill(documentation)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Activation before implementation** - Evaluating skills without calling `Skill()` provides no benefit
|
|
25
|
+
*Verify:* Check that `Skill()` was called for each YES evaluation
|
|
26
|
+
*Fix:* Call `Skill(skill-name)` for skipped activations
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
**Use real dependencies** - Prefer installed packages over mocks when testing module resolution
|
|
16
16
|
*Verify:* Review test imports for fake paths
|
|
17
|
-
*Fix:* Use actual package like
|
|
17
|
+
*Fix:* Use actual package like `typescript`
|
|
18
18
|
|
|
19
19
|
**Organize with describe** - Group related tests in `describe('feature', () => {...})`
|
|
20
20
|
*Verify:* Check for flat test structure
|
|
@@ -34,9 +34,8 @@ bunx @plaited/development-skills scaffold-rules
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
This will:
|
|
37
|
-
1.
|
|
38
|
-
2.
|
|
39
|
-
3. Fallback: append links to `AGENTS.md` if no agent directories found
|
|
37
|
+
1. Write rules into `AGENTS.md` (creates if missing, updates between markers if present)
|
|
38
|
+
2. Add `@AGENTS.md` reference to `CLAUDE.md` if it exists without one
|
|
40
39
|
|
|
41
40
|
### Step 3: Report to User
|
|
42
41
|
|
|
@@ -51,25 +50,32 @@ Tell the user what was created based on the `actions` output.
|
|
|
51
50
|
|
|
52
51
|
## How It Works
|
|
53
52
|
|
|
53
|
+
Rules are written directly into `AGENTS.md` between markers:
|
|
54
|
+
|
|
54
55
|
```
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
<!-- PLAITED-RULES-START -->
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
|
|
60
|
+
(rule content inlined here)
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
.cursor/rules -> ../.plaited/rules ← Symlink (if .cursor/ exists)
|
|
62
|
+
<!-- PLAITED-RULES-END -->
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
65
|
+
- **No AGENTS.md**: Creates one with rules section
|
|
66
|
+
- **AGENTS.md without markers**: Appends rules section with markers
|
|
67
|
+
- **AGENTS.md with markers**: Replaces content between markers (preserves user content outside)
|
|
68
|
+
- **CLAUDE.md exists**: Adds `@AGENTS.md` reference if not already present
|
|
69
|
+
|
|
70
|
+
## Troubleshooting
|
|
71
|
+
|
|
72
|
+
| Issue | Cause | Fix |
|
|
73
|
+
|-------|-------|-----|
|
|
74
|
+
| Rules duplicated in AGENTS.md | Markers were manually deleted | Remove duplicate section, re-run `scaffold-rules` |
|
|
75
|
+
| Update didn't apply | Only one marker present (start or end) | Ensure both `<!-- PLAITED-RULES-START -->` and `<!-- PLAITED-RULES-END -->` exist, or delete both to get a fresh append |
|
|
76
|
+
| `@AGENTS.md` not added to CLAUDE.md | CLAUDE.md doesn't exist | Create CLAUDE.md first, then re-run |
|
|
77
|
+
|
|
78
|
+
**Do not** manually edit content between the `PLAITED-RULES-START` and `PLAITED-RULES-END` markers — it will be overwritten on next run.
|
|
73
79
|
|
|
74
80
|
## Related Skills
|
|
75
81
|
|
package/src/scaffold-rules.ts
CHANGED
|
@@ -1,46 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Scaffold development rules
|
|
3
|
+
* Scaffold development rules into AGENTS.md
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* and falls back to appending links in `AGENTS.md` if no agent dirs exist.
|
|
5
|
+
* Writes rules into AGENTS.md (creates if missing, uses markers for updates).
|
|
6
|
+
* Adds `@AGENTS.md` reference to CLAUDE.md if it exists without one.
|
|
8
7
|
*
|
|
9
8
|
* @throws When source rules directory cannot be read
|
|
10
|
-
* @throws When target directory cannot be created (permissions)
|
|
11
|
-
* @throws When symlink creation fails (existing file, not directory)
|
|
12
9
|
*/
|
|
13
10
|
|
|
14
|
-
import {
|
|
11
|
+
import { readdir } from 'node:fs/promises'
|
|
15
12
|
import { join } from 'node:path'
|
|
16
13
|
import { parseArgs } from 'node:util'
|
|
17
14
|
|
|
18
|
-
/**
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
/** All supported agent directories (including .plaited which gets direct copy) */
|
|
22
|
-
const ALL_AGENTS = ['.plaited', ...SYMLINK_AGENTS] as const
|
|
23
|
-
|
|
24
|
-
/** Canonical rules location */
|
|
25
|
-
const TARGET_RULES = '.plaited/rules' as const
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* NOTE: This tool only scaffolds RULES, not skills.
|
|
29
|
-
* Skills symlinks (.claude/skills -> ../.plaited/skills) are managed separately
|
|
30
|
-
* via the skills-installer or manual setup.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Check if path is a directory
|
|
35
|
-
*/
|
|
36
|
-
const isDirectory = async (path: string): Promise<boolean> => {
|
|
37
|
-
try {
|
|
38
|
-
const s = await stat(path)
|
|
39
|
-
return s.isDirectory()
|
|
40
|
-
} catch {
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
}
|
|
15
|
+
/** Markers for the rules section in AGENTS.md */
|
|
16
|
+
const RULES_START = '<!-- PLAITED-RULES-START -->'
|
|
17
|
+
const RULES_END = '<!-- PLAITED-RULES-END -->'
|
|
44
18
|
|
|
45
19
|
/**
|
|
46
20
|
* Main scaffold-rules function
|
|
@@ -59,7 +33,7 @@ export const scaffoldRules = async (args: string[]): Promise<void> => {
|
|
|
59
33
|
const dryRun = values['dry-run'] as boolean | undefined
|
|
60
34
|
const listOnly = values.list as boolean | undefined
|
|
61
35
|
|
|
62
|
-
const sourceRules = join(import.meta.dir, '
|
|
36
|
+
const sourceRules = join(import.meta.dir, '../rules')
|
|
63
37
|
const cwd = process.cwd()
|
|
64
38
|
|
|
65
39
|
// Get available rules
|
|
@@ -74,77 +48,60 @@ export const scaffoldRules = async (args: string[]): Promise<void> => {
|
|
|
74
48
|
|
|
75
49
|
const actions: string[] = []
|
|
76
50
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for (const agent of ALL_AGENTS) {
|
|
81
|
-
if (await isDirectory(join(cwd, agent))) {
|
|
82
|
-
hadAgentDirBeforeScaffold = true
|
|
83
|
-
break
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 1. Copy rules to .plaited/rules/ (canonical location, serves .plaited agent)
|
|
88
|
-
const targetDir = join(cwd, TARGET_RULES)
|
|
89
|
-
if (!dryRun) {
|
|
90
|
-
await mkdir(targetDir, { recursive: true })
|
|
91
|
-
}
|
|
51
|
+
// 1. Write rules into AGENTS.md
|
|
52
|
+
const agentsMdPath = join(cwd, 'AGENTS.md')
|
|
53
|
+
const agentsMd = Bun.file(agentsMdPath)
|
|
92
54
|
|
|
55
|
+
const ruleEntries: string[] = []
|
|
93
56
|
for (const file of rules) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
if (!dryRun) {
|
|
97
|
-
await Bun.write(dest, await Bun.file(src).text())
|
|
98
|
-
}
|
|
99
|
-
actions.push(`copy: ${TARGET_RULES}/${file}`)
|
|
57
|
+
const content = await Bun.file(join(sourceRules, file)).text()
|
|
58
|
+
ruleEntries.push(content)
|
|
100
59
|
}
|
|
60
|
+
const rulesContent = ruleEntries.join('\n\n')
|
|
61
|
+
const rulesSection = `${RULES_START}\n\n## Rules\n\n${rulesContent}\n\n${RULES_END}`
|
|
101
62
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const rulesLink = join(agentDir, 'rules')
|
|
107
|
-
|
|
108
|
-
// Check if symlink already exists and points to right place
|
|
109
|
-
try {
|
|
110
|
-
const existing = await readlink(rulesLink)
|
|
111
|
-
if (existing === '../.plaited/rules') {
|
|
112
|
-
actions.push(`skip: ${agent}/rules (symlink exists)`)
|
|
113
|
-
continue
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
// Doesn't exist or not a symlink - proceed to create
|
|
117
|
-
}
|
|
63
|
+
if (await agentsMd.exists()) {
|
|
64
|
+
const content = await agentsMd.text()
|
|
65
|
+
const startIdx = content.indexOf(RULES_START)
|
|
66
|
+
const endIdx = content.indexOf(RULES_END)
|
|
118
67
|
|
|
68
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
69
|
+
const before = content.slice(0, startIdx)
|
|
70
|
+
const after = content.slice(endIdx + RULES_END.length)
|
|
119
71
|
if (!dryRun) {
|
|
120
|
-
await
|
|
72
|
+
await Bun.write(agentsMdPath, `${before}${rulesSection}${after}`)
|
|
121
73
|
}
|
|
122
|
-
actions.push(
|
|
74
|
+
actions.push('update: AGENTS.md (rules section)')
|
|
75
|
+
} else {
|
|
76
|
+
if (!dryRun) {
|
|
77
|
+
await Bun.write(agentsMdPath, `${content}\n${rulesSection}\n`)
|
|
78
|
+
}
|
|
79
|
+
actions.push('append: AGENTS.md (rules section)')
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
if (!dryRun) {
|
|
83
|
+
await Bun.write(agentsMdPath, `# AGENTS\n\n${rulesSection}\n`)
|
|
123
84
|
}
|
|
85
|
+
actions.push('create: AGENTS.md (rules section)')
|
|
124
86
|
}
|
|
125
87
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const section = `\n## Rules\n\n${links}\n`
|
|
138
|
-
|
|
139
|
-
if (!dryRun) {
|
|
140
|
-
await Bun.write(agentsMdPath, content + section)
|
|
141
|
-
}
|
|
142
|
-
actions.push('append: AGENTS.md (rules section)')
|
|
88
|
+
// 2. Add @AGENTS.md reference to CLAUDE.md if it exists without one
|
|
89
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md')
|
|
90
|
+
const claudeMd = Bun.file(claudeMdPath)
|
|
91
|
+
|
|
92
|
+
if (await claudeMd.exists()) {
|
|
93
|
+
const content = await claudeMd.text()
|
|
94
|
+
if (/^@AGENTS\.md/m.test(content)) {
|
|
95
|
+
actions.push('skip: CLAUDE.md (already references @AGENTS.md)')
|
|
96
|
+
} else {
|
|
97
|
+
if (!dryRun) {
|
|
98
|
+
await Bun.write(claudeMdPath, `@AGENTS.md\n\n${content}`)
|
|
143
99
|
}
|
|
100
|
+
actions.push('update: CLAUDE.md (added @AGENTS.md reference)')
|
|
144
101
|
}
|
|
145
102
|
}
|
|
146
103
|
|
|
147
|
-
console.log(JSON.stringify({ dryRun: !!dryRun,
|
|
104
|
+
console.log(JSON.stringify({ dryRun: !!dryRun, actions }, null, 2))
|
|
148
105
|
}
|
|
149
106
|
|
|
150
107
|
// CLI entry point
|
|
@@ -75,27 +75,6 @@ describe('resolveFilePath', () => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
describe('scoped package specifiers', () => {
|
|
78
|
-
test('resolves scoped package subpath export', () => {
|
|
79
|
-
// @modelcontextprotocol/sdk/client is a defined export
|
|
80
|
-
const result = resolveFilePath('@modelcontextprotocol/sdk/client')
|
|
81
|
-
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
82
|
-
expect(result).toContain('client')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('resolves scoped package server subpath', () => {
|
|
86
|
-
const result = resolveFilePath('@modelcontextprotocol/sdk/server')
|
|
87
|
-
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
88
|
-
expect(result).toContain('server')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('resolves scoped package deep subpath with extension', () => {
|
|
92
|
-
// Deep subpath with .js extension via wildcard export "./*"
|
|
93
|
-
const path = '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js'
|
|
94
|
-
const result = resolveFilePath(path)
|
|
95
|
-
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
96
|
-
expect(result).toContain('bearerAuth')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
78
|
test('falls back to cwd for non-existent scoped package', () => {
|
|
100
79
|
const scopedPkg = '@nonexistent/pkg/src/file.ts'
|
|
101
80
|
const result = resolveFilePath(scopedPkg)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, rm } from 'node:fs/promises'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
5
|
|
|
@@ -9,7 +9,6 @@ type ListOutput = {
|
|
|
9
9
|
|
|
10
10
|
type ScaffoldOutput = {
|
|
11
11
|
dryRun: boolean
|
|
12
|
-
targetRules: string
|
|
13
12
|
actions: string[]
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -19,13 +18,11 @@ describe('scaffold-rules', () => {
|
|
|
19
18
|
let testDir: string
|
|
20
19
|
|
|
21
20
|
beforeEach(async () => {
|
|
22
|
-
// Create a temp directory for each test
|
|
23
21
|
testDir = join(import.meta.dir, `test-scaffold-${Date.now()}`)
|
|
24
22
|
await mkdir(testDir, { recursive: true })
|
|
25
23
|
})
|
|
26
24
|
|
|
27
25
|
afterEach(async () => {
|
|
28
|
-
// Clean up temp directory
|
|
29
26
|
await rm(testDir, { recursive: true, force: true })
|
|
30
27
|
})
|
|
31
28
|
|
|
@@ -63,26 +60,14 @@ describe('scaffold-rules', () => {
|
|
|
63
60
|
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules --dry-run`.json()
|
|
64
61
|
|
|
65
62
|
expect(result.dryRun).toBe(true)
|
|
66
|
-
expect(result.targetRules).toBe('.plaited/rules')
|
|
67
63
|
expect(result.actions).toBeArray()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
test('shows copy actions for each rule', async () => {
|
|
71
|
-
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules --dry-run`.json()
|
|
72
|
-
|
|
73
|
-
const copyActions = result.actions.filter((a) => a.startsWith('copy:'))
|
|
74
|
-
expect(copyActions.length).toBeGreaterThan(0)
|
|
75
|
-
|
|
76
|
-
// Should include our compressed rules
|
|
77
|
-
expect(copyActions.some((a) => a.includes('core.md'))).toBe(true)
|
|
78
|
-
expect(copyActions.some((a) => a.includes('testing.md'))).toBe(true)
|
|
64
|
+
expect(result.actions).toContain('create: AGENTS.md (rules section)')
|
|
79
65
|
})
|
|
80
66
|
|
|
81
67
|
test('does not create files in dry-run mode', async () => {
|
|
82
68
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules --dry-run`.json()
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
expect(await rulesDir.exists()).toBe(false)
|
|
70
|
+
expect(await Bun.file(join(testDir, 'AGENTS.md')).exists()).toBe(false)
|
|
86
71
|
})
|
|
87
72
|
|
|
88
73
|
test('short flag -n works', async () => {
|
|
@@ -92,109 +77,114 @@ describe('scaffold-rules', () => {
|
|
|
92
77
|
})
|
|
93
78
|
})
|
|
94
79
|
|
|
95
|
-
describe('
|
|
96
|
-
test('
|
|
80
|
+
describe('AGENTS.md behavior', () => {
|
|
81
|
+
test('creates AGENTS.md if it does not exist', async () => {
|
|
97
82
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
98
83
|
|
|
99
|
-
const
|
|
100
|
-
expect(
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
85
|
+
expect(content).toContain('# AGENTS')
|
|
86
|
+
expect(content).toContain('## Rules')
|
|
87
|
+
expect(content).toContain('<!-- PLAITED-RULES-START -->')
|
|
88
|
+
expect(content).toContain('<!-- PLAITED-RULES-END -->')
|
|
103
89
|
expect(content).toContain('# Core Conventions')
|
|
104
90
|
})
|
|
105
91
|
|
|
106
|
-
test('
|
|
107
|
-
await
|
|
108
|
-
|
|
109
|
-
const expectedRules = ['accuracy', 'bun', 'core', 'documentation', 'modules', 'testing', 'workflow']
|
|
110
|
-
|
|
111
|
-
for (const rule of expectedRules) {
|
|
112
|
-
const ruleFile = Bun.file(join(testDir, `.plaited/rules/${rule}.md`))
|
|
113
|
-
expect(await ruleFile.exists()).toBe(true)
|
|
114
|
-
}
|
|
115
|
-
})
|
|
92
|
+
test('appends rules with markers to existing AGENTS.md', async () => {
|
|
93
|
+
await Bun.write(join(testDir, 'AGENTS.md'), '# My Project\n\nCustom content\n')
|
|
116
94
|
|
|
117
|
-
test('creates .plaited/rules directory if missing', async () => {
|
|
118
95
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
119
96
|
|
|
120
|
-
const
|
|
121
|
-
expect(
|
|
97
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
98
|
+
expect(content).toStartWith('# My Project\n\nCustom content\n')
|
|
99
|
+
expect(content).toContain('<!-- PLAITED-RULES-START -->')
|
|
100
|
+
expect(content).toContain('## Rules')
|
|
101
|
+
expect(content).toContain('<!-- PLAITED-RULES-END -->')
|
|
122
102
|
})
|
|
123
|
-
})
|
|
124
103
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
await
|
|
104
|
+
test('updates existing markers without overwriting user content', async () => {
|
|
105
|
+
const initial =
|
|
106
|
+
'# Project\n\nUser notes\n\n<!-- PLAITED-RULES-START -->\n\n## Rules\n\n- old rule\n\n<!-- PLAITED-RULES-END -->\n\nMore user content\n'
|
|
107
|
+
await Bun.write(join(testDir, 'AGENTS.md'), initial)
|
|
129
108
|
|
|
130
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.
|
|
109
|
+
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
131
110
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
expect(
|
|
111
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
112
|
+
expect(content).toContain('User notes')
|
|
113
|
+
expect(content).toContain('More user content')
|
|
114
|
+
expect(content).not.toContain('old rule')
|
|
115
|
+
expect(content).toContain('# Core Conventions')
|
|
116
|
+
expect(result.actions).toContain('update: AGENTS.md (rules section)')
|
|
135
117
|
})
|
|
136
118
|
|
|
137
|
-
test('
|
|
138
|
-
|
|
139
|
-
await
|
|
119
|
+
test('appends when only start marker exists (no end marker)', async () => {
|
|
120
|
+
const malformed = '# Project\n\n<!-- PLAITED-RULES-START -->\n\nOrphan content\n'
|
|
121
|
+
await Bun.write(join(testDir, 'AGENTS.md'), malformed)
|
|
140
122
|
|
|
141
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.
|
|
123
|
+
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
142
124
|
|
|
143
|
-
const
|
|
144
|
-
expect(
|
|
125
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
126
|
+
expect(content).toContain('# Core Conventions')
|
|
127
|
+
expect(result.actions).toContain('append: AGENTS.md (rules section)')
|
|
145
128
|
})
|
|
146
129
|
|
|
147
|
-
test('
|
|
148
|
-
|
|
130
|
+
test('appends when markers are reversed (end before start)', async () => {
|
|
131
|
+
const reversed =
|
|
132
|
+
'# Project\n\n<!-- PLAITED-RULES-END -->\n\nMiddle\n\n<!-- PLAITED-RULES-START -->\n\nOld rules\n'
|
|
133
|
+
await Bun.write(join(testDir, 'AGENTS.md'), reversed)
|
|
149
134
|
|
|
150
|
-
// Run twice
|
|
151
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
152
135
|
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
153
136
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
expect(
|
|
137
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
138
|
+
expect(content).toContain('# Core Conventions')
|
|
139
|
+
expect(result.actions).toContain('append: AGENTS.md (rules section)')
|
|
157
140
|
})
|
|
158
141
|
|
|
159
|
-
test('
|
|
142
|
+
test('appends when only end marker exists (no start marker)', async () => {
|
|
143
|
+
const malformed = '# Project\n\nSome content\n\n<!-- PLAITED-RULES-END -->\n'
|
|
144
|
+
await Bun.write(join(testDir, 'AGENTS.md'), malformed)
|
|
145
|
+
|
|
160
146
|
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
161
147
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expect(
|
|
148
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
149
|
+
expect(content).toContain('# Core Conventions')
|
|
150
|
+
expect(result.actions).toContain('append: AGENTS.md (rules section)')
|
|
165
151
|
})
|
|
166
152
|
})
|
|
167
153
|
|
|
168
|
-
describe('
|
|
169
|
-
test('
|
|
170
|
-
|
|
171
|
-
await Bun.write(join(testDir, 'AGENTS.md'), '# AGENTS\n\nSome content\n')
|
|
154
|
+
describe('CLAUDE.md behavior', () => {
|
|
155
|
+
test('adds @AGENTS.md reference to existing CLAUDE.md', async () => {
|
|
156
|
+
await Bun.write(join(testDir, 'CLAUDE.md'), '# Claude Config\n\nSome settings\n')
|
|
172
157
|
|
|
173
158
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
174
159
|
|
|
175
|
-
const content = await Bun.file(join(testDir, '
|
|
176
|
-
expect(content).
|
|
177
|
-
expect(content).toContain('
|
|
160
|
+
const content = await Bun.file(join(testDir, 'CLAUDE.md')).text()
|
|
161
|
+
expect(content).toStartWith('@AGENTS.md\n\n')
|
|
162
|
+
expect(content).toContain('# Claude Config')
|
|
178
163
|
})
|
|
179
164
|
|
|
180
|
-
test('
|
|
181
|
-
await Bun.write(join(testDir, '
|
|
182
|
-
await mkdir(join(testDir, '.plaited'), { recursive: true })
|
|
165
|
+
test('skips CLAUDE.md if @AGENTS.md reference already exists', async () => {
|
|
166
|
+
await Bun.write(join(testDir, 'CLAUDE.md'), '@AGENTS.md\n\n# Claude Config\n')
|
|
183
167
|
|
|
184
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.
|
|
168
|
+
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
185
169
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
expect(content).not.toContain('## Rules')
|
|
170
|
+
const skipAction = result.actions.find((a) => a.includes('CLAUDE.md') && a.includes('skip'))
|
|
171
|
+
expect(skipAction).toBeDefined()
|
|
189
172
|
})
|
|
190
173
|
|
|
191
|
-
test('
|
|
192
|
-
await Bun.write(join(testDir, '
|
|
174
|
+
test('adds reference when @AGENTS.md only appears inline (not at start of line)', async () => {
|
|
175
|
+
await Bun.write(join(testDir, 'CLAUDE.md'), '# Config\n\nSee `@AGENTS.md` for details\n')
|
|
193
176
|
|
|
194
177
|
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
195
178
|
|
|
196
|
-
const
|
|
197
|
-
expect(
|
|
179
|
+
const content = await Bun.file(join(testDir, 'CLAUDE.md')).text()
|
|
180
|
+
expect(content).toStartWith('@AGENTS.md\n\n')
|
|
181
|
+
expect(result.actions).toContain('update: CLAUDE.md (added @AGENTS.md reference)')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('does not create CLAUDE.md if it does not exist', async () => {
|
|
185
|
+
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
186
|
+
|
|
187
|
+
expect(await Bun.file(join(testDir, 'CLAUDE.md')).exists()).toBe(false)
|
|
198
188
|
})
|
|
199
189
|
})
|
|
200
190
|
|
|
@@ -203,60 +193,25 @@ describe('scaffold-rules', () => {
|
|
|
203
193
|
const result: ScaffoldOutput = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.json()
|
|
204
194
|
|
|
205
195
|
expect(result).toHaveProperty('dryRun')
|
|
206
|
-
expect(result).toHaveProperty('targetRules')
|
|
207
196
|
expect(result).toHaveProperty('actions')
|
|
208
|
-
|
|
209
197
|
expect(result.dryRun).toBe(false)
|
|
210
|
-
expect(result.targetRules).toBe('.plaited/rules')
|
|
211
198
|
expect(result.actions).toBeArray()
|
|
212
199
|
})
|
|
213
200
|
})
|
|
214
201
|
|
|
215
|
-
describe('symlink readability', () => {
|
|
216
|
-
test('rules are readable through .claude symlink', async () => {
|
|
217
|
-
await mkdir(join(testDir, '.claude'), { recursive: true })
|
|
218
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
219
|
-
|
|
220
|
-
// Read through symlink path
|
|
221
|
-
const content = await Bun.file(join(testDir, '.claude/rules/core.md')).text()
|
|
222
|
-
expect(content).toContain('# Core Conventions')
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
test('rules are readable through .cursor symlink', async () => {
|
|
226
|
-
await mkdir(join(testDir, '.cursor'), { recursive: true })
|
|
227
|
-
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
228
|
-
|
|
229
|
-
// Read through symlink path
|
|
230
|
-
const content = await Bun.file(join(testDir, '.cursor/rules/core.md')).text()
|
|
231
|
-
expect(content).toContain('# Core Conventions')
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
describe('error handling', () => {
|
|
236
|
-
test('fails when existing file blocks symlink creation', async () => {
|
|
237
|
-
await mkdir(join(testDir, '.claude'), { recursive: true })
|
|
238
|
-
// Create a file where symlink should go
|
|
239
|
-
await Bun.write(join(testDir, '.claude/rules'), 'not a directory')
|
|
240
|
-
|
|
241
|
-
// Should fail when trying to create symlink over existing file
|
|
242
|
-
const result = await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules 2>&1`.nothrow().text()
|
|
243
|
-
expect(result).toContain('EEXIST')
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
|
|
247
202
|
describe('rule content', () => {
|
|
248
|
-
test('
|
|
203
|
+
test('AGENTS.md contains TypeScript conventions', async () => {
|
|
249
204
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
250
205
|
|
|
251
|
-
const content = await Bun.file(join(testDir, '.
|
|
206
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
252
207
|
expect(content).toContain('Type over interface')
|
|
253
208
|
expect(content).toContain('Arrow functions')
|
|
254
209
|
})
|
|
255
210
|
|
|
256
|
-
test('
|
|
211
|
+
test('AGENTS.md contains test conventions', async () => {
|
|
257
212
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
258
213
|
|
|
259
|
-
const content = await Bun.file(join(testDir, '.
|
|
214
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
260
215
|
expect(content).toContain('test not it')
|
|
261
216
|
expect(content).toContain('No conditional assertions')
|
|
262
217
|
})
|
|
@@ -264,7 +219,7 @@ describe('scaffold-rules', () => {
|
|
|
264
219
|
test('rules include verification patterns', async () => {
|
|
265
220
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
266
221
|
|
|
267
|
-
const content = await Bun.file(join(testDir, '.
|
|
222
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
268
223
|
expect(content).toContain('*Verify:*')
|
|
269
224
|
expect(content).toContain('*Fix:*')
|
|
270
225
|
})
|
|
@@ -272,9 +227,7 @@ describe('scaffold-rules', () => {
|
|
|
272
227
|
test('rules are compressed (no verbose examples)', async () => {
|
|
273
228
|
await $`cd ${testDir} && bun ${binDir}/cli.ts scaffold-rules`.quiet()
|
|
274
229
|
|
|
275
|
-
const content = await Bun.file(join(testDir, '.
|
|
276
|
-
|
|
277
|
-
// Should not have verbose code blocks with Good/Avoid patterns
|
|
230
|
+
const content = await Bun.file(join(testDir, 'AGENTS.md')).text()
|
|
278
231
|
expect(content).not.toContain('// ✅ Good')
|
|
279
232
|
expect(content).not.toContain('// ❌ Avoid')
|
|
280
233
|
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|