Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules
2 changes: 1 addition & 1 deletion .claude/skills
2 changes: 1 addition & 1 deletion .cursor/rules
2 changes: 1 addition & 1 deletion .cursor/skills
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
```
bin/cli.ts # CLI entry point
src/ # TypeScript source
.agents/skills/ # Agent skills
.agents/rules/ # Shared rules (bundled)
skills/ # Agent skills
rules/ # Shared rules (bundled)
```

**Test CLI:** `bun bin/cli.ts <command>`
Expand All @@ -38,7 +38,7 @@ After code changes:
1. `bun run check` - Must pass
2. `bun test` - Must pass
3. `bun bin/cli.ts <cmd>` - Test CLI changes
4. For skills: `bunx @plaited/development-skills validate-skill .agents/skills`
4. For skills: `bunx @plaited/development-skills validate-skill skills`

**Rule compliance:** Read rules below, use verification patterns to self-check

Expand All @@ -59,13 +59,13 @@ After code changes:

Compressed rules with embedded verification patterns:

- @.agents/rules/core.md - TypeScript conventions (type>interface, no any, arrow fns)
- @.agents/rules/testing.md - Test patterns (test>it, no conditional assertions)
- @.agents/rules/modules.md - Module organization (no index.ts, explicit .ts)
- @.agents/rules/workflow.md - Git + GitHub CLI patterns
- @.agents/rules/bun.md - Bun APIs over Node.js
- @.agents/rules/accuracy.md - 95% confidence, verify before stating
- @.agents/rules/documentation.md - TSDoc standards
- @rules/core.md - TypeScript conventions (type>interface, no any, arrow fns)
- @rules/testing.md - Test patterns (test>it, no conditional assertions)
- @rules/modules.md - Module organization (no index.ts, explicit .ts)
- @rules/workflow.md - Git + GitHub CLI patterns
- @rules/bun.md - Bun APIs over Node.js
- @rules/accuracy.md - 95% confidence, verify before stating
- @rules/documentation.md - TSDoc standards

<!-- PLAITED-RULES-END -->

Expand Down
4 changes: 2 additions & 2 deletions bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* bunx @plaited/development-skills lsp-hover src/index.ts 10 5
* bunx @plaited/development-skills lsp-find parseConfig
* bunx @plaited/development-skills validate-skill .claude/skills/my-skill
* bunx @plaited/development-skills scaffold-rules --agent=claude --format=json
* bunx @plaited/development-skills scaffold-rules
*/

import { lspAnalyze } from '../src/lsp-analyze.ts'
Expand Down Expand Up @@ -64,7 +64,7 @@ Examples:
bunx @plaited/development-skills lsp-symbols src/app.ts
bunx @plaited/development-skills lsp-analyze src/app.ts
bunx @plaited/development-skills validate-skill .claude/skills/my-skill
bunx @plaited/development-skills scaffold-rules --agent=claude --format=json
bunx @plaited/development-skills scaffold-rules

Options:
-h, --help Show this help
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"files": [
"bin/",
"src/",
".agents/"
"rules/",
"skills/"
],
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion .agents/rules/accuracy.md → rules/accuracy.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@
- Code review: Read files before commenting
- Patterns: Confirm examples reflect actual usage

See .agents/rules/testing.md for verification in test contexts.
See rules/testing.md for verification in test contexts.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
26 changes: 26 additions & 0 deletions rules/skill-activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Skill Activation

**Evaluate before implementing** - Check available skills for relevance before starting work

**Activation sequence:**

1. **Evaluate** - For each skill in `<available_skills>`, assess: `[skill-name] - YES/NO - [reason]`
2. **Activate** - Call `Skill(skill-name)` for each relevant skill before proceeding
3. **Implement** - Begin work only after activation is complete

*Verify:* Did you check available skills before starting implementation?
*Fix:* Pause, evaluate skills, activate relevant ones, then continue

**Example:**
```
- code-patterns: NO - not writing code
- git-workflow: YES - need commit conventions
- documentation: YES - writing README

> Skill(git-workflow)
> Skill(documentation)
```

**Activation before implementation** - Evaluating skills without calling `Skill()` provides no benefit
*Verify:* Check that `Skill()` was called for each YES evaluation
*Fix:* Call `Skill(skill-name)` for skipped activations
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ bunx @plaited/development-skills scaffold-rules
```

This will:
1. Copy rules to `.agents/rules/` (canonical location)
2. Create symlinks in `.claude/rules` and `.cursor/rules` (if those directories exist)
3. Fallback: append links to `AGENTS.md` if no agent directories found
1. Write rules into `AGENTS.md` (creates if missing, updates between markers if present)
2. Add `@AGENTS.md` reference to `CLAUDE.md` if it exists without one

### Step 3: Report to User

Expand All @@ -51,25 +50,32 @@ Tell the user what was created based on the `actions` output.

## How It Works

Rules are written directly into `AGENTS.md` between markers:

```
.agents/rules/ ← Canonical location (files copied here)
├── testing.md
├── bun.md
└── ...
<!-- PLAITED-RULES-START -->

## Rules

(rule content inlined here)

.claude/rules -> ../.agents/rules ← Symlink (if .claude/ exists)
.cursor/rules -> ../.agents/rules ← Symlink (if .cursor/ exists)
<!-- PLAITED-RULES-END -->
```

| Project has... | Copy | Symlinks | AGENTS.md |
|----------------|------|----------|-----------|
| `.agents/` only | ✓ | None | No |
| `.claude/` only | ✓ | `.claude/rules` | No |
| `.cursor/` only | ✓ | `.cursor/rules` | No |
| `.agents/` + `.claude/` | ✓ | `.claude/rules` | No |
| `.agents/` + `.cursor/` | ✓ | `.cursor/rules` | No |
| `.agents/` + `.claude/` + `.cursor/` | ✓ | Both | No |
| None of the above | ✓ | None | ✓ Append links |
- **No AGENTS.md**: Creates one with rules section
- **AGENTS.md without markers**: Appends rules section with markers
- **AGENTS.md with markers**: Replaces content between markers (preserves user content outside)
- **CLAUDE.md exists**: Adds `@AGENTS.md` reference if not already present

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| Rules duplicated in AGENTS.md | Markers were manually deleted | Remove duplicate section, re-run `scaffold-rules` |
| 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 |
| `@AGENTS.md` not added to CLAUDE.md | CLAUDE.md doesn't exist | Create CLAUDE.md first, then re-run |

**Do not** manually edit content between the `PLAITED-RULES-START` and `PLAITED-RULES-END` markers — it will be overwritten on next run.

## Related Skills

Expand Down
139 changes: 48 additions & 91 deletions src/scaffold-rules.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,20 @@
#!/usr/bin/env bun
/**
* Scaffold development rules - Copy bundled rules and create symlinks
* Scaffold development rules into AGENTS.md
*
* Copies rules from the package to `.agents/rules/` (canonical location),
* creates symlinks for `.claude/` and `.cursor/` agent directories,
* and falls back to appending links in `AGENTS.md` if no agent dirs exist.
* Writes rules into AGENTS.md (creates if missing, uses markers for updates).
* Adds `@AGENTS.md` reference to CLAUDE.md if it exists without one.
*
* @throws When source rules directory cannot be read
* @throws When target directory cannot be created (permissions)
* @throws When symlink creation fails (existing file, not directory)
*/

import { mkdir, readdir, readlink, stat, symlink } from 'node:fs/promises'
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
import { parseArgs } from 'node:util'

/** Agents that get symlinks to .agents/rules (not .agents itself) */
const SYMLINK_AGENTS = ['.claude', '.cursor'] as const

/** All supported agent directories (including .agents which gets direct copy) */
const ALL_AGENTS = ['.agents', ...SYMLINK_AGENTS] as const

/** Canonical rules location */
const TARGET_RULES = '.agents/rules' as const

/**
* NOTE: This tool only scaffolds RULES, not skills.
* Skills symlinks (.claude/skills -> ../.agents/skills) are managed separately
* via the skills-installer or manual setup.
*/

/**
* Check if path is a directory
*/
const isDirectory = async (path: string): Promise<boolean> => {
try {
const s = await stat(path)
return s.isDirectory()
} catch {
return false
}
}
/** Markers for the rules section in AGENTS.md */
const RULES_START = '<!-- PLAITED-RULES-START -->'
const RULES_END = '<!-- PLAITED-RULES-END -->'

/**
* Main scaffold-rules function
Expand All @@ -59,7 +33,7 @@ export const scaffoldRules = async (args: string[]): Promise<void> => {
const dryRun = values['dry-run'] as boolean | undefined
const listOnly = values.list as boolean | undefined

const sourceRules = join(import.meta.dir, '../.agents/rules')
const sourceRules = join(import.meta.dir, '../rules')
const cwd = process.cwd()

// Get available rules
Expand All @@ -74,77 +48,60 @@ export const scaffoldRules = async (args: string[]): Promise<void> => {

const actions: string[] = []

// Check for agent directories BEFORE copying (since copy creates .agents/)
// This determines whether to fall back to AGENTS.md append
let hadAgentDirBeforeScaffold = false
for (const agent of ALL_AGENTS) {
if (await isDirectory(join(cwd, agent))) {
hadAgentDirBeforeScaffold = true
break
}
}

// 1. Copy rules to .agents/rules/ (canonical location, serves .agents agent)
const targetDir = join(cwd, TARGET_RULES)
if (!dryRun) {
await mkdir(targetDir, { recursive: true })
}
// 1. Write rules into AGENTS.md
const agentsMdPath = join(cwd, 'AGENTS.md')
const agentsMd = Bun.file(agentsMdPath)

const ruleEntries: string[] = []
for (const file of rules) {
const src = join(sourceRules, file)
const dest = join(targetDir, file)
if (!dryRun) {
await Bun.write(dest, await Bun.file(src).text())
}
actions.push(`copy: ${TARGET_RULES}/${file}`)
const content = await Bun.file(join(sourceRules, file)).text()
ruleEntries.push(content)
}
const rulesContent = ruleEntries.join('\n\n')
const rulesSection = `${RULES_START}\n\n## Rules\n\n${rulesContent}\n\n${RULES_END}`

// 2. Symlink for other agents (.claude, .cursor)
for (const agent of SYMLINK_AGENTS) {
const agentDir = join(cwd, agent)
if (await isDirectory(agentDir)) {
const rulesLink = join(agentDir, 'rules')

// Check if symlink already exists and points to right place
try {
const existing = await readlink(rulesLink)
if (existing === '../.agents/rules') {
actions.push(`skip: ${agent}/rules (symlink exists)`)
continue
}
} catch {
// Doesn't exist or not a symlink - proceed to create
}
if (await agentsMd.exists()) {
const content = await agentsMd.text()
const startIdx = content.indexOf(RULES_START)
const endIdx = content.indexOf(RULES_END)

if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
const before = content.slice(0, startIdx)
const after = content.slice(endIdx + RULES_END.length)
if (!dryRun) {
await symlink('../.agents/rules', rulesLink)
await Bun.write(agentsMdPath, `${before}${rulesSection}${after}`)
}
actions.push(`symlink: ${agent}/rules -> ../.agents/rules`)
actions.push('update: AGENTS.md (rules section)')
} else {
if (!dryRun) {
await Bun.write(agentsMdPath, `${content}\n${rulesSection}\n`)
}
actions.push('append: AGENTS.md (rules section)')
}
} else {
if (!dryRun) {
await Bun.write(agentsMdPath, `# AGENTS\n\n${rulesSection}\n`)
}
actions.push('create: AGENTS.md (rules section)')
}

// 3. Fallback: append to AGENTS.md only if NO agent directories existed before copy
if (!hadAgentDirBeforeScaffold) {
const agentsMdPath = join(cwd, 'AGENTS.md')
const agentsMd = Bun.file(agentsMdPath)

if (await agentsMd.exists()) {
const content = await agentsMd.text()
if (content.includes('.agents/rules')) {
actions.push('skip: AGENTS.md (already has rules)')
} else {
const links = rules.map((f) => `- [${f.replace('.md', '')}](${TARGET_RULES}/${f})`).join('\n')
const section = `\n## Rules\n\n${links}\n`

if (!dryRun) {
await Bun.write(agentsMdPath, content + section)
}
actions.push('append: AGENTS.md (rules section)')
// 2. Add @AGENTS.md reference to CLAUDE.md if it exists without one
const claudeMdPath = join(cwd, 'CLAUDE.md')
const claudeMd = Bun.file(claudeMdPath)

if (await claudeMd.exists()) {
const content = await claudeMd.text()
if (/^@AGENTS\.md/m.test(content)) {
actions.push('skip: CLAUDE.md (already references @AGENTS.md)')
} else {
if (!dryRun) {
await Bun.write(claudeMdPath, `@AGENTS.md\n\n${content}`)
}
actions.push('update: CLAUDE.md (added @AGENTS.md reference)')
}
}

console.log(JSON.stringify({ dryRun: !!dryRun, targetRules: TARGET_RULES, actions }, null, 2))
console.log(JSON.stringify({ dryRun: !!dryRun, actions }, null, 2))
}

// CLI entry point
Expand Down
Loading