cc-dev-template 0.1.73 → 0.1.75

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 (26) hide show
  1. package/package.json +1 -1
  2. package/src/commands/done.md +29 -2
  3. package/src/scripts/task-output-guard.js +4 -66
  4. package/src/skills/agent-browser/SKILL.md +0 -1
  5. package/src/skills/creating-agent-skills/references/create-step-1-understand.md +9 -0
  6. package/src/skills/creating-agent-skills/references/create-step-2-design.md +24 -0
  7. package/src/skills/creating-agent-skills/references/create-step-3-write.md +9 -0
  8. package/src/skills/creating-agent-skills/references/create-step-5-install.md +29 -5
  9. package/src/skills/creating-agent-skills/scripts/validate-skill.js +39 -13
  10. package/src/skills/creating-agent-skills/templates/router-skill.md +6 -51
  11. package/src/skills/creating-agent-skills/templates/simple-skill.md +0 -10
  12. package/src/skills/creating-sub-agents/SKILL.md +18 -0
  13. package/src/skills/creating-sub-agents/references/create-step-1-understand.md +46 -0
  14. package/src/skills/creating-sub-agents/references/create-step-2-design.md +181 -0
  15. package/src/skills/creating-sub-agents/references/create-step-3-write.md +154 -0
  16. package/src/skills/creating-sub-agents/references/create-step-4-review.md +67 -0
  17. package/src/skills/creating-sub-agents/references/create-step-5-install.md +76 -0
  18. package/src/skills/creating-sub-agents/references/fix-step-1-diagnose.md +99 -0
  19. package/src/skills/creating-sub-agents/references/fix-step-2-apply.md +77 -0
  20. package/src/skills/creating-sub-agents/references/fix-step-3-validate.md +52 -0
  21. package/src/skills/creating-sub-agents/references/principles.md +157 -0
  22. package/src/skills/creating-sub-agents/scripts/find-subagents.js +217 -0
  23. package/src/skills/creating-sub-agents/scripts/validate-subagent.js +336 -0
  24. package/src/skills/creating-sub-agents/templates/basic-subagent.md +21 -0
  25. package/src/skills/creating-sub-agents/templates/domain-specialist.md +52 -0
  26. package/src/skills/creating-sub-agents/templates/restricted-subagent.md +29 -0
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * validate-subagent.js
5
+ *
6
+ * Validates a Claude Code sub-agent definition against best practices.
7
+ * Returns detailed findings with severity levels.
8
+ *
9
+ * Usage: node validate-subagent.js <agent-file-path> [--json]
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const args = process.argv.slice(2);
16
+ const agentPath = args.find(a => !a.startsWith('--'));
17
+ const jsonOutput = args.includes('--json');
18
+
19
+ if (!agentPath) {
20
+ console.error('Usage: node validate-subagent.js <agent-file-path> [--json]');
21
+ process.exit(1);
22
+ }
23
+
24
+ const resolvedPath = path.resolve(agentPath);
25
+
26
+ if (!fs.existsSync(resolvedPath)) {
27
+ console.error(`Error: File not found at ${resolvedPath}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!resolvedPath.endsWith('.md')) {
32
+ console.error(`Error: Sub-agent file must be a .md file`);
33
+ process.exit(1);
34
+ }
35
+
36
+ const findings = [];
37
+
38
+ function addFinding(category, severity, message, line = null, suggestion = null) {
39
+ findings.push({ category, severity, message, line, suggestion });
40
+ }
41
+
42
+ function parseFrontmatter(content) {
43
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
44
+ if (!match) return { valid: false, data: null, raw: null };
45
+
46
+ const raw = match[1];
47
+ const data = {};
48
+ const lines = raw.split('\n');
49
+
50
+ for (const line of lines) {
51
+ const colonIndex = line.indexOf(':');
52
+ if (colonIndex > 0 && !line.startsWith(' ') && !line.startsWith('\t')) {
53
+ const key = line.slice(0, colonIndex).trim();
54
+ let value = line.slice(colonIndex + 1).trim();
55
+
56
+ if (value === '' || value === '|' || value === '>') continue;
57
+
58
+ if ((value.startsWith('"') && value.endsWith('"')) ||
59
+ (value.startsWith("'") && value.endsWith("'"))) {
60
+ value = value.slice(1, -1);
61
+ }
62
+
63
+ data[key] = value;
64
+ }
65
+ }
66
+
67
+ return { valid: true, data, raw };
68
+ }
69
+
70
+ function getBody(content) {
71
+ const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
72
+ return match ? match[1] : content;
73
+ }
74
+
75
+ const content = fs.readFileSync(resolvedPath, 'utf-8');
76
+ const frontmatter = parseFrontmatter(content);
77
+ const body = getBody(content);
78
+ const lines = content.split('\n');
79
+
80
+ // === VALIDATION CHECKS ===
81
+
82
+ // Check 1: Frontmatter exists
83
+ if (!frontmatter.valid) {
84
+ addFinding('STRUCTURE', 'error', 'Missing or invalid YAML frontmatter');
85
+ } else {
86
+ // Check 2: Required fields
87
+ if (!frontmatter.data.name) {
88
+ addFinding('STRUCTURE', 'error', 'Missing required field: name');
89
+ } else {
90
+ const namePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
91
+ if (!namePattern.test(frontmatter.data.name)) {
92
+ addFinding('STRUCTURE', 'error',
93
+ `Invalid name format: "${frontmatter.data.name}" - must be hyphen-case`);
94
+ }
95
+
96
+ if (frontmatter.data.name.length > 64) {
97
+ addFinding('STRUCTURE', 'error',
98
+ `Name exceeds 64 characters (${frontmatter.data.name.length})`);
99
+ }
100
+
101
+ // Check name matches filename
102
+ const fileName = path.basename(resolvedPath, '.md');
103
+ if (frontmatter.data.name !== fileName) {
104
+ addFinding('STRUCTURE', 'warning',
105
+ `Name "${frontmatter.data.name}" doesn't match filename "${fileName}.md"`);
106
+ }
107
+ }
108
+
109
+ if (!frontmatter.data.description) {
110
+ addFinding('STRUCTURE', 'error', 'Missing required field: description');
111
+ } else {
112
+ if (frontmatter.data.description.length < 10) {
113
+ addFinding('DESCRIPTION', 'warning',
114
+ 'Description is very short — may not provide enough context for delegation');
115
+ }
116
+
117
+ const desc = frontmatter.data.description.toLowerCase();
118
+ if (desc.startsWith('a ') || desc.startsWith('an ') || desc.startsWith('the ')) {
119
+ addFinding('DESCRIPTION', 'info',
120
+ 'Description could be more actionable — consider starting with a verb or role');
121
+ }
122
+ }
123
+
124
+ // Check model validity
125
+ if (frontmatter.data.model) {
126
+ const validModels = ['haiku', 'sonnet', 'opus', 'inherit'];
127
+ if (!validModels.includes(frontmatter.data.model)) {
128
+ addFinding('STRUCTURE', 'warning',
129
+ `Model "${frontmatter.data.model}" may not be valid. Expected: ${validModels.join(', ')}`);
130
+ }
131
+ }
132
+
133
+ // Check permission mode validity
134
+ if (frontmatter.data.permissionMode) {
135
+ const validModes = ['default', 'acceptEdits', 'dontAsk', 'delegate', 'bypassPermissions', 'plan'];
136
+ if (!validModes.includes(frontmatter.data.permissionMode)) {
137
+ addFinding('STRUCTURE', 'error',
138
+ `Invalid permissionMode: "${frontmatter.data.permissionMode}". Valid: ${validModes.join(', ')}`);
139
+ }
140
+
141
+ if (frontmatter.data.permissionMode === 'bypassPermissions') {
142
+ addFinding('SECURITY', 'warning',
143
+ 'Using bypassPermissions — ensure tools are restricted to limit risk');
144
+ }
145
+ }
146
+
147
+ // Check for both tools and disallowedTools
148
+ if (frontmatter.data.tools && frontmatter.data.disallowedTools) {
149
+ addFinding('STRUCTURE', 'warning',
150
+ 'Both tools and disallowedTools are set — use one approach, not both');
151
+ }
152
+
153
+ // Check for XML in frontmatter
154
+ if (frontmatter.raw && /[<>]/.test(frontmatter.raw)) {
155
+ addFinding('STRUCTURE', 'error',
156
+ 'Frontmatter contains XML angle brackets (< >) — move XML tags to the body');
157
+ }
158
+ }
159
+
160
+ // Check 3: Body quality
161
+ const wordCount = body.split(/\s+/).filter(w => w.length > 0).length;
162
+ const bodyLineCount = body.split('\n').length;
163
+
164
+ if (wordCount < 10) {
165
+ addFinding('CONTENT', 'error',
166
+ 'System prompt body is nearly empty — sub-agent needs instructions');
167
+ }
168
+
169
+ if (bodyLineCount > 200) {
170
+ addFinding('SIZE', 'warning',
171
+ `System prompt is ${bodyLineCount} lines — consider splitting scope or using skills preloading`,
172
+ null,
173
+ 'Move domain knowledge into skills and preload with the skills field');
174
+ }
175
+
176
+ // Check 4: Output format defined
177
+ const hasOutputSection = /##?\s*(output|return|result|format|response)/i.test(body);
178
+ const hasOutputGuidance = /(return|provide|report|output|summarize)\s+(only|a |the |concise|structured)/i.test(body);
179
+
180
+ if (!hasOutputSection && !hasOutputGuidance) {
181
+ addFinding('CONTENT', 'warning',
182
+ 'No output format guidance found — sub-agent may return verbose or unstructured results',
183
+ null,
184
+ 'Add an "## Output" section defining what the sub-agent should return');
185
+ }
186
+
187
+ // Check 5: Role statement
188
+ const firstNonEmpty = body.split('\n').find(l => l.trim().length > 0);
189
+ if (firstNonEmpty && !/(you are|as a|expert|specialist|senior|responsible for)/i.test(firstNonEmpty)) {
190
+ addFinding('CONTENT', 'info',
191
+ 'Consider starting with a role statement (e.g., "You are a senior code reviewer...")');
192
+ }
193
+
194
+ // Check 6: Second person violations
195
+ const secondPersonPatterns = [
196
+ /\byou should\b/gi,
197
+ /\byou can\b/gi,
198
+ /\byou need\b/gi,
199
+ /\byou might\b/gi
200
+ ];
201
+
202
+ let secondPersonCount = 0;
203
+ const secondPersonExamples = [];
204
+
205
+ for (let i = 0; i < lines.length; i++) {
206
+ const line = lines[i];
207
+ if (i < 3) continue;
208
+
209
+ for (const pattern of secondPersonPatterns) {
210
+ const matches = line.match(pattern);
211
+ if (matches) {
212
+ secondPersonCount += matches.length;
213
+ if (secondPersonExamples.length < 3) {
214
+ secondPersonExamples.push({ line: i + 1, text: line.trim().slice(0, 60) });
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ if (secondPersonCount > 3) {
221
+ addFinding('STYLE', 'warning',
222
+ `Found ${secondPersonCount} instance(s) of "you should/can/need/might" — prefer imperative form`,
223
+ secondPersonExamples[0]?.line,
224
+ '"You should check..." becomes "Check..."');
225
+ }
226
+
227
+ // Check 7: Negative framing
228
+ const negativePatterns = [
229
+ /\bdon't\b/gi,
230
+ /\bdo not\b/gi,
231
+ /\bnever\b/gi,
232
+ /\bavoid\b/gi
233
+ ];
234
+
235
+ let negativeCount = 0;
236
+
237
+ for (let i = 0; i < lines.length; i++) {
238
+ if (i < 3) continue;
239
+ for (const pattern of negativePatterns) {
240
+ const matches = lines[i].match(pattern);
241
+ if (matches) negativeCount += matches.length;
242
+ }
243
+ }
244
+
245
+ if (negativeCount > 5) {
246
+ addFinding('STYLE', 'warning',
247
+ `Found ${negativeCount} negative framing instances — prefer positive framing`,
248
+ null,
249
+ '"Don\'t modify X" becomes "Only modify Y"');
250
+ }
251
+
252
+ // Check 8: Memory instructions
253
+ if (frontmatter.valid && frontmatter.data.memory) {
254
+ const hasMemoryInstructions = /memory|MEMORY\.md|agent-memory/i.test(body);
255
+ if (!hasMemoryInstructions) {
256
+ addFinding('CONTENT', 'warning',
257
+ 'Memory is enabled but no memory instructions found in system prompt',
258
+ null,
259
+ 'Add instructions for reading/updating the memory directory');
260
+ }
261
+ }
262
+
263
+ // Check 9: Skills existence check
264
+ if (frontmatter.valid && frontmatter.raw && /skills:/i.test(frontmatter.raw)) {
265
+ addFinding('CONTENT', 'info',
266
+ 'Skills preloading is configured — verify listed skills exist at user or project level');
267
+ }
268
+
269
+ // === OUTPUT ===
270
+
271
+ const result = {
272
+ agent: {
273
+ path: resolvedPath,
274
+ name: frontmatter.data?.name || path.basename(resolvedPath, '.md'),
275
+ wordCount,
276
+ lineCount: bodyLineCount
277
+ },
278
+ findings,
279
+ summary: {
280
+ errors: findings.filter(f => f.severity === 'error').length,
281
+ warnings: findings.filter(f => f.severity === 'warning').length,
282
+ info: findings.filter(f => f.severity === 'info').length,
283
+ passed: findings.filter(f => f.severity === 'error').length === 0
284
+ }
285
+ };
286
+
287
+ if (jsonOutput) {
288
+ console.log(JSON.stringify(result, null, 2));
289
+ } else {
290
+ console.log(`\n=== Sub-Agent Validation: ${result.agent.name} ===\n`);
291
+ console.log(`Path: ${result.agent.path}`);
292
+ console.log(`Words: ${result.agent.wordCount}`);
293
+ console.log(`Lines: ${result.agent.lineCount}`);
294
+ console.log();
295
+
296
+ if (findings.length === 0) {
297
+ console.log('All checks passed!\n');
298
+ } else {
299
+ const errors = findings.filter(f => f.severity === 'error');
300
+ const warnings = findings.filter(f => f.severity === 'warning');
301
+ const infos = findings.filter(f => f.severity === 'info');
302
+
303
+ if (errors.length > 0) {
304
+ console.log('ERRORS:');
305
+ for (const f of errors) {
306
+ console.log(` [${f.category}] ${f.message}`);
307
+ if (f.line) console.log(` Line ${f.line}`);
308
+ if (f.suggestion) console.log(` Suggestion: ${f.suggestion}`);
309
+ }
310
+ console.log();
311
+ }
312
+
313
+ if (warnings.length > 0) {
314
+ console.log('WARNINGS:');
315
+ for (const f of warnings) {
316
+ console.log(` ! [${f.category}] ${f.message}`);
317
+ if (f.line) console.log(` Line ${f.line}`);
318
+ if (f.suggestion) console.log(` Suggestion: ${f.suggestion}`);
319
+ }
320
+ console.log();
321
+ }
322
+
323
+ if (infos.length > 0) {
324
+ console.log('INFO:');
325
+ for (const f of infos) {
326
+ console.log(` i [${f.category}] ${f.message}`);
327
+ }
328
+ console.log();
329
+ }
330
+
331
+ console.log(`Summary: ${result.summary.errors} error(s), ${result.summary.warnings} warning(s), ${result.summary.info} info(s)`);
332
+ console.log(`Status: ${result.summary.passed ? 'PASSED' : 'FAILED'}\n`);
333
+ }
334
+ }
335
+
336
+ process.exit(result.summary.passed ? 0 : 1);
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: {{AGENT_NAME}}
3
+ description: {{What it does and when Claude should delegate to it}}
4
+ tools: {{Tool list, e.g., Read, Grep, Glob, Bash}}
5
+ model: {{haiku|sonnet|opus|inherit}}
6
+ ---
7
+
8
+ You are a {{role description}}.
9
+
10
+ When invoked:
11
+ 1. {{First action}}
12
+ 2. {{Second action}}
13
+ 3. {{Third action}}
14
+
15
+ ## Guidelines
16
+
17
+ {{Domain knowledge, conventions, constraints}}
18
+
19
+ ## Output
20
+
21
+ {{What to return — format, structure, level of detail}}
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: {{AGENT_NAME}}
3
+ description: "Domain specialist for {{domain description}}. Owns and maintains code quality for the {{domain-name}} domain."
4
+ tools: Read, Write, Edit, Grep, Glob, Bash, LSP
5
+ memory: project
6
+ ---
7
+
8
+ You are the **{{domain-name}}** domain specialist on a development team.
9
+
10
+ <role>
11
+ You are a software engineer who owns the {{domain-name}} domain. You write code, maintain tests, enforce quality, and deeply understand your part of the codebase. You are responsible for the code quality and architectural integrity of your domain.
12
+ </role>
13
+
14
+ <domain>
15
+ **Name:** {{domain-name}}
16
+ **Description:** {{What this domain covers}}
17
+ **Ownership:** {{Directory paths this agent owns}}
18
+ </domain>
19
+
20
+ <memory>
21
+ **On startup, read your memory file at `.claude/agent-memory/{{AGENT_NAME}}/MEMORY.md`.**
22
+ This contains accumulated knowledge from previous sessions. Update it when you learn something that would help future sessions.
23
+
24
+ Memory is tribal knowledge — patterns, gotchas, decisions, integration points. Not documentation of what code does, but how to work effectively in this codebase.
25
+
26
+ Review and curate your memory. Remove entries that are no longer accurate. Keep it under 100 lines.
27
+ </memory>
28
+
29
+ <team>
30
+ You are part of a team coordinated by a team lead.
31
+
32
+ - **Team Lead** (team-lead): Assigns work, coordinates between domains, reviews output
33
+ - **Other domain specialists**: Message them directly when your work affects their domain
34
+
35
+ Always refer to teammates by name when using SendMessage.
36
+ </team>
37
+
38
+ <responsibilities>
39
+ 1. Deeply understand your domain before making changes
40
+ 2. Write high-quality code that follows established patterns
41
+ 3. Write and maintain tests for changes
42
+ 4. Coordinate with the team lead for cross-domain changes
43
+ 5. Update your memory file with tribal knowledge
44
+ 6. Mark tasks as completed via TaskUpdate when finished
45
+ 7. After completing a task, check TaskList for available work
46
+ </responsibilities>
47
+
48
+ <boundaries>
49
+ - Only modify files within your domain's ownership zone
50
+ - Coordinate with the team lead for changes outside your domain
51
+ - All user communication goes through the team lead
52
+ </boundaries>
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: {{AGENT_NAME}}
3
+ description: {{What it does and when Claude should delegate to it}}
4
+ tools: {{Restricted tool list, e.g., Read, Grep, Glob}}
5
+ model: {{haiku|sonnet|opus|inherit}}
6
+ permissionMode: dontAsk
7
+ hooks:
8
+ PreToolUse:
9
+ - matcher: "Bash"
10
+ hooks:
11
+ - type: command
12
+ command: "{{./path/to/validation-script.sh}}"
13
+ ---
14
+
15
+ You are a {{role description}} with restricted access.
16
+
17
+ When invoked:
18
+ 1. {{First action}}
19
+ 2. {{Second action}}
20
+ 3. {{Third action}}
21
+
22
+ ## Constraints
23
+
24
+ - Only access files within {{scope}}
25
+ - {{Additional restrictions}}
26
+
27
+ ## Output
28
+
29
+ {{What to return — format, structure, level of detail}}