cc-dev-template 0.1.72 → 0.1.74

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
@@ -21,6 +21,7 @@ const { homedir } = require('os');
21
21
  // Usage API cache
22
22
  const USAGE_CACHE_PATH = join(homedir(), '.claude', '.usage-cache.json');
23
23
  const USAGE_CACHE_TTL = 45000; // 45 seconds
24
+ const USAGE_HISTORY_MAX = 20; // ~15 min of readings at 45s intervals
24
25
 
25
26
  // Background refresh mode: fetch usage data and write cache, then exit
26
27
  if (process.argv.includes('--refresh')) {
@@ -72,6 +73,50 @@ function getContextGreyscale(percentage) {
72
73
  return '\x1b[38;5;240m'; // Dark grey - safe
73
74
  }
74
75
 
76
+ /**
77
+ * Compute burn rate (percent per minute) from usage history
78
+ * Returns null if insufficient data
79
+ */
80
+ function getUsageBurnRate(history, key) {
81
+ if (!history || history.length < 2) return null;
82
+
83
+ const oldest = history[0];
84
+ const newest = history[history.length - 1];
85
+ const minutesElapsed = (newest.t - oldest.t) / 60000;
86
+
87
+ // Need at least 2 minutes of data for a stable reading
88
+ if (minutesElapsed < 2) return null;
89
+
90
+ const deltaUtilization = newest[key] - oldest[key];
91
+ return deltaUtilization / minutesElapsed;
92
+ }
93
+
94
+ /**
95
+ * Get color for usage bar based on current level and burn rate trend
96
+ * Returns red/yellow for danger, or greyscale for safe
97
+ */
98
+ function getUsageColor(utilization, burnRate, windowType) {
99
+ const RED = '\x1b[38;5;196m';
100
+ const YELLOW = '\x1b[38;5;220m';
101
+
102
+ // Thresholds differ by window type
103
+ const redMinutes = windowType === '5h' ? 30 : 240; // 30min / 4hr
104
+ const yellowMinutes = windowType === '5h' ? 90 : 720; // 90min / 12hr
105
+
106
+ // Hard thresholds on current utilization
107
+ if (utilization >= 90) return RED;
108
+ if (utilization >= 75) return YELLOW;
109
+
110
+ // Trend-based: project time to hit 100%
111
+ if (burnRate && burnRate > 0) {
112
+ const minutesToLimit = (100 - utilization) / burnRate;
113
+ if (minutesToLimit <= redMinutes) return RED;
114
+ if (minutesToLimit <= yellowMinutes) return YELLOW;
115
+ }
116
+
117
+ return getContextGreyscale(utilization);
118
+ }
119
+
75
120
  /**
76
121
  * Count files in a directory recursively
77
122
  */
@@ -279,9 +324,29 @@ function refreshUsageCache() {
279
324
  if (result.status === 0 && result.stdout) {
280
325
  const data = JSON.parse(result.stdout.trim());
281
326
  if (data.five_hour && data.seven_day) {
327
+ // Load existing history and append new reading
328
+ let history = [];
329
+ try {
330
+ const existing = JSON.parse(readFileSync(USAGE_CACHE_PATH, 'utf-8'));
331
+ if (Array.isArray(existing.history)) history = existing.history;
332
+ } catch {}
333
+
334
+ const now = Date.now();
335
+ history.push({
336
+ t: now,
337
+ five_hour: data.five_hour.utilization,
338
+ seven_day: data.seven_day.utilization,
339
+ });
340
+
341
+ // Keep only the last N readings
342
+ if (history.length > USAGE_HISTORY_MAX) {
343
+ history = history.slice(-USAGE_HISTORY_MAX);
344
+ }
345
+
282
346
  writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
283
- timestamp: Date.now(),
347
+ timestamp: now,
284
348
  data,
349
+ history,
285
350
  }));
286
351
  }
287
352
  }
@@ -295,12 +360,14 @@ function refreshUsageCache() {
295
360
  */
296
361
  function getUsageData() {
297
362
  let cacheData = null;
363
+ let cacheHistory = null;
298
364
  let cacheAge = Infinity;
299
365
 
300
366
  try {
301
367
  const raw = readFileSync(USAGE_CACHE_PATH, 'utf-8');
302
368
  const cache = JSON.parse(raw);
303
369
  cacheData = cache.data;
370
+ cacheHistory = cache.history || null;
304
371
  cacheAge = Date.now() - cache.timestamp;
305
372
  } catch {}
306
373
 
@@ -315,7 +382,7 @@ function getUsageData() {
315
382
  } catch {}
316
383
  }
317
384
 
318
- return cacheData;
385
+ return { data: cacheData, history: cacheHistory };
319
386
  }
320
387
 
321
388
  /**
@@ -474,14 +541,19 @@ function main() {
474
541
 
475
542
  // Usage limits line (5-hour session + 7-day weekly)
476
543
  const usageLines = [];
477
- const usageData = getUsageData();
478
- if (usageData && usageData.five_hour && usageData.seven_day) {
479
- const pct5h = Math.round(usageData.five_hour.utilization);
480
- const pct7d = Math.round(usageData.seven_day.utilization);
544
+ const { data: usageApiData, history: usageHistory } = getUsageData();
545
+ if (usageApiData && usageApiData.five_hour && usageApiData.seven_day) {
546
+ const pct5h = Math.round(usageApiData.five_hour.utilization);
547
+ const pct7d = Math.round(usageApiData.seven_day.utilization);
481
548
  const bar5h = generateBar(pct5h, 12);
482
549
  const bar7d = generateBar(pct7d, 12);
483
- const color5h = getContextGreyscale(pct5h);
484
- const color7d = getContextGreyscale(pct7d);
550
+
551
+ // Compute burn rates from history for trend-based coloring
552
+ const rate5h = getUsageBurnRate(usageHistory, 'five_hour');
553
+ const rate7d = getUsageBurnRate(usageHistory, 'seven_day');
554
+ const color5h = getUsageColor(pct5h, rate5h, '5h');
555
+ const color7d = getUsageColor(pct7d, rate7d, '7d');
556
+
485
557
  const str5h = pct5h.toString().padStart(3, ' ');
486
558
  const str7d = pct7d.toString().padStart(3, ' ');
487
559
  const usageDisplay = `5HR: ${color5h}[${bar5h}]${str5h}%${DIM_GREY} 7D: ${color7d}[${bar7d}]${str7d}%${DIM_GREY}`;
@@ -2,8 +2,17 @@
2
2
 
3
3
  The skill's quality depends entirely on understanding the domain. Start a conversation with the user.
4
4
 
5
+ ## Try It First
6
+
7
+ Before building a skill, the best approach is to do the task in a live conversation first. Iterate until Claude succeeds at the task, then extract the winning approach into the skill. This grounds the skill in something that actually worked rather than theoretical instructions.
8
+
9
+ Ask: "Have you already done this task successfully with Claude? Can you share that conversation or walk me through what worked?"
10
+
11
+ If the user has a working conversation, use it as the primary source material. If not, consider running the workflow live together before designing the skill.
12
+
5
13
  ## What To Uncover
6
14
 
15
+ - **2-3 concrete use cases** — specific scenarios where this skill would activate. What does the user want to accomplish?
7
16
  - **The task** they want to standardize — what do they do repeatedly that they want encoded?
8
17
  - **Domain knowledge** Claude does not naturally have — terminology, patterns, edge cases, gotchas
9
18
  - **Why certain approaches work** — not just what the steps are, but the reasoning behind them
@@ -8,6 +8,7 @@ Based on what you learned in the conversation, design the skill's structure befo
8
8
  - Prefer gerund form: `processing-pdfs`, `creating-reports`, `reviewing-code`
9
9
  - Must match the directory name
10
10
  - Max 64 characters
11
+ - Reserved prefixes: names starting with "claude" or "anthropic" are not allowed
11
12
 
12
13
  ## Determine the Skill Type
13
14
 
@@ -79,6 +80,12 @@ Every skill has YAML frontmatter. Required and optional fields:
79
80
  | `context` | Set `fork` to run in an isolated sub-agent context |
80
81
  | `agent` | Sub-agent type when `context: fork` is set (`Explore`, `Plan`, `general-purpose`, or custom) |
81
82
  | `hooks` | Lifecycle hooks scoped to this skill |
83
+ | `compatibility` | Environment requirements — intended product, required system packages, network access needs (1-500 chars) |
84
+
85
+ ### Security Restrictions
86
+
87
+ - No XML angle brackets (`<` or `>`) in frontmatter — frontmatter appears in Claude's system prompt
88
+ - Skill names starting with "claude" or "anthropic" are reserved
82
89
 
83
90
  ### String Substitutions Available in Skill Content
84
91
 
@@ -107,6 +114,8 @@ Combine into a description that:
107
114
 
108
115
  **Key insight:** If the description explains too much about WHAT the skill does, Claude believes it already knows enough and will not activate. Keep it about WHEN.
109
116
 
117
+ **Negative triggers:** If the skill could over-trigger on related but wrong queries, add scope boundaries. Example: "Advanced data analysis for CSV files. Use for statistical modeling, regression, clustering. Do NOT use for simple data exploration or visualization."
118
+
110
119
  ### For User-Invoked Only Skills
111
120
 
112
121
  The description just needs to tell the user what the skill does. Keep it minimal:
@@ -139,6 +148,8 @@ skill-name/
139
148
 
140
149
  **When to add `templates/`:** Boilerplate files that get copied or modified by the agent.
141
150
 
151
+ **Do not add:** `README.md` inside the skill folder. All documentation goes in SKILL.md or `references/`.
152
+
142
153
  ## For Procedural Skills: Plan the Steps
143
154
 
144
155
  List out the steps. Each step becomes one markdown file in `references/`. For each step, determine:
@@ -158,6 +169,19 @@ Present the design:
158
169
  - File layout
159
170
  - For procedural: the step breakdown
160
171
 
172
+ ## Define Success Criteria
173
+
174
+ Before writing, establish how the user will know the skill is working:
175
+
176
+ - **Triggering**: Does it activate on relevant queries? (Aim for ~90% of intended triggers)
177
+ - **Precision**: Does it stay silent on unrelated queries?
178
+ - **Completeness**: Does the workflow complete without the user needing to redirect or clarify?
179
+ - **Consistency**: Do repeated runs produce structurally similar, quality results?
180
+
181
+ These are rough benchmarks, not precise thresholds. Ask the user: "What would a successful skill look like for you?"
182
+
183
+ ## Confirm With the User
184
+
161
185
  Ask: "Does this design look right?"
162
186
 
163
187
  ## Next Step
@@ -150,6 +150,15 @@ description: [third person, trigger phrases, WHEN not HOW]
150
150
 
151
151
  Keep it minimal. Only include context that is needed at EVERY activation.
152
152
 
153
+ ### Error Handling
154
+
155
+ If the skill involves tool calls, external services, or operations that can fail, include error handling guidance:
156
+ - Common failure modes and what to do about them
157
+ - MCP connection failures: verify server is connected, check authentication, test independently
158
+ - Validation failures: what to check, how to recover
159
+
160
+ For critical validations, bundle a script rather than relying on language instructions. Code is deterministic; language interpretation is not.
161
+
153
162
  ### MCP Tool References
154
163
 
155
164
  When a skill uses MCP tools, use fully qualified names:
@@ -15,20 +15,44 @@ Copy the skill directory to the target location. Ensure all files are in place
15
15
 
16
16
  ## Test
17
17
 
18
- Guide the user through testing:
18
+ Guide the user through structured testing:
19
+
20
+ ### 1. Triggering Tests
21
+
22
+ Verify the skill loads at the right times.
23
+
24
+ **Should trigger** — test 3-5 phrases that should activate the skill:
25
+ - The obvious request (e.g., "help me plan this sprint")
26
+ - A paraphrased version (e.g., "I need to set up sprint tasks")
27
+ - A domain-specific variant (e.g., "create Linear tickets for Q4")
28
+
29
+ **Should NOT trigger** — test 2-3 unrelated queries:
30
+ - A clearly unrelated request
31
+ - A request in a similar domain that a different skill should handle
32
+
33
+ If the skill under-triggers: add more trigger phrases and keywords to the description.
34
+ If the skill over-triggers: add negative scope boundaries or be more specific.
35
+
36
+ ### 2. Functional Tests
37
+
38
+ Verify the skill produces correct output:
19
39
 
20
40
  1. Start a new Claude Code session (or restart the current one)
21
41
  2. Test explicit invocation: type `/skill-name`
22
42
  3. Test autonomous activation: say one of the trigger phrases from the description
23
- 4. Verify the skill activates and behaves as expected
43
+ 4. Run through the full workflow does it complete without the user needing to redirect or clarify?
44
+ 5. For skills with tool calls: verify calls succeed and outputs are correct
24
45
 
25
- ## If Testing Reveals Issues
46
+ ### 3. Iteration
26
47
 
27
- Iterate. The job is not done until the skill works.
48
+ If testing reveals issues, iterate immediately:
28
49
 
29
- - Instructions wrong? Go back to the write step.
50
+ - Instructions unclear or wrong? Go back to the write step.
30
51
  - Structure wrong? Go back to the design step.
31
52
  - Domain knowledge missing? Go back to the understand step.
53
+ - Description under/over-triggers? Adjust the description and retest.
54
+
55
+ The job is not done until the skill works.
32
56
 
33
57
  ## Completion
34
58
 
@@ -118,6 +118,12 @@ if (!frontmatter.valid) {
118
118
  addFinding('STRUCTURE', 'error',
119
119
  `Name exceeds 64 characters (${frontmatter.data.name.length})`);
120
120
  }
121
+
122
+ // Check reserved name prefixes
123
+ if (frontmatter.data.name.startsWith('claude') || frontmatter.data.name.startsWith('anthropic')) {
124
+ addFinding('STRUCTURE', 'error',
125
+ `Name "${frontmatter.data.name}" uses a reserved prefix ("claude" or "anthropic")`);
126
+ }
121
127
  }
122
128
 
123
129
  if (!frontmatter.data.description) {
@@ -153,6 +159,14 @@ if (!frontmatter.valid) {
153
159
  'Long description without trigger phrases may reduce activation reliability');
154
160
  }
155
161
  }
162
+
163
+ // Check for XML angle brackets in frontmatter
164
+ if (frontmatter.raw && /[<>]/.test(frontmatter.raw)) {
165
+ addFinding('STRUCTURE', 'error',
166
+ 'Frontmatter contains XML angle brackets (< >) — these are forbidden as frontmatter appears in system prompt',
167
+ null,
168
+ 'Remove all < and > characters from frontmatter values');
169
+ }
156
170
  }
157
171
 
158
172
  // Check 3: Second person violations
@@ -162,8 +176,7 @@ const secondPersonPatterns = [
162
176
  /\byou need\b/gi,
163
177
  /\byou might\b/gi,
164
178
  /\byou will\b/gi,
165
- /\byou must\b/gi,
166
- /\byour\b/gi
179
+ /\byou must\b/gi
167
180
  ];
168
181
 
169
182
  let secondPersonCount = 0;
@@ -229,26 +242,39 @@ if (negativeCount > 3) {
229
242
  'Use positive framing: "Don\'t do X" → "Do Y instead"');
230
243
  }
231
244
 
232
- // Check 5: Body size
245
+ // Check 5: Forbidden files
246
+ const readmePath = path.join(resolvedPath, 'README.md');
247
+ if (fs.existsSync(readmePath)) {
248
+ addFinding('STRUCTURE', 'warning',
249
+ 'Skill folder contains README.md — all documentation should be in SKILL.md or references/',
250
+ null,
251
+ 'Move README.md content into SKILL.md or references/ and delete README.md');
252
+ }
253
+
254
+ // Check 6: Body size
233
255
  const wordCount = body.split(/\s+/).filter(w => w.length > 0).length;
234
256
  const lineCount = body.split('\n').length;
235
257
 
236
258
  if (wordCount > 5000) {
237
259
  addFinding('SIZE', 'error',
238
- `SKILL.md body is ${wordCount} words - strongly exceeds 2,000 word recommendation`,
260
+ `SKILL.md body is ${wordCount} words - exceeds 5,000 word limit`,
239
261
  null,
240
262
  'Move detailed content to references/ directory');
241
- } else if (wordCount > 3500) {
263
+ } else if (wordCount > 2000) {
242
264
  addFinding('SIZE', 'warning',
243
- `SKILL.md body is ${wordCount} words - exceeds 2,000 word recommendation`,
265
+ `SKILL.md body is ${wordCount} words - consider moving content to references/`,
244
266
  null,
245
- 'Consider moving detailed content to references/ directory');
246
- } else if (wordCount > 2000) {
247
- addFinding('SIZE', 'info',
248
- `SKILL.md body is ${wordCount} words - at upper limit of recommendation`);
267
+ 'Keep SKILL.md focused; move detailed content to references/ directory');
268
+ }
269
+
270
+ if (lineCount > 300) {
271
+ addFinding('SIZE', 'warning',
272
+ `SKILL.md body is ${lineCount} lines - exceeds 300 line target for informational skills (150 for routers)`,
273
+ null,
274
+ 'Move content to references/ to stay within size targets');
249
275
  }
250
276
 
251
- // Check 6: Progressive disclosure
277
+ // Check 7: Progressive disclosure
252
278
  const hasReferences = fs.existsSync(path.join(resolvedPath, 'references'));
253
279
  const hasScripts = fs.existsSync(path.join(resolvedPath, 'scripts'));
254
280
 
@@ -259,7 +285,7 @@ if (wordCount > 2000 && !hasReferences) {
259
285
  'Create references/ and move detailed content there');
260
286
  }
261
287
 
262
- // Check 7: Reference integrity
288
+ // Check 8: Reference integrity
263
289
  const referencePaths = body.match(/`references\/[^`]+`|references\/[\w.-]+/g) || [];
264
290
  const scriptPaths = body.match(/`scripts\/[^`]+`|scripts\/[\w.-]+/g) || [];
265
291
 
@@ -283,7 +309,7 @@ for (const ref of scriptPaths) {
283
309
  }
284
310
  }
285
311
 
286
- // Check 8: Unreferenced resources
312
+ // Check 9: Unreferenced resources
287
313
  if (hasReferences) {
288
314
  const refDir = path.join(resolvedPath, 'references');
289
315
  const refFiles = fs.readdirSync(refDir);
@@ -7,55 +7,10 @@ description: {{What it does}} Use when {{trigger conditions}}.
7
7
 
8
8
  ## What To Do Now
9
9
 
10
- **Ask the user:**
10
+ Ask the user what they need.
11
11
 
12
- What would you like to do?
13
- 1. {{First option}}
14
- 2. {{Second option}}
15
- 3. {{Third option}}
16
-
17
- **Wait for response before proceeding.**
18
-
19
- ## Route to Workflow
20
-
21
- | Response | Action |
22
- |----------|--------|
23
- | 1, "{{keywords}}" | Read `references/{{first-workflow}}.md` |
24
- | 2, "{{keywords}}" | Read `references/{{second-workflow}}.md` |
25
- | 3, "{{keywords}}" | Read `references/{{third-workflow}}.md` |
26
-
27
- After reading the workflow, follow it exactly.
28
-
29
- ## Essential Principles
30
-
31
- {{Principles that ALWAYS apply, regardless of which workflow runs}}
32
-
33
- ### 1. {{First principle}}
34
-
35
- {{Explanation}}
36
-
37
- ### 2. {{Second principle}}
38
-
39
- {{Explanation}}
40
-
41
- ### 3. {{Third principle}}
42
-
43
- {{Explanation}}
44
-
45
- ## Quick Reference
46
-
47
- {{Brief reference information always useful to have visible}}
48
-
49
- ## Resources
50
-
51
- **References** (in `references/`):
52
- - {{reference-1.md}} - {{purpose}}
53
- - {{reference-2.md}} - {{purpose}}
54
-
55
- **Workflows** (in `references/`):
56
-
57
- | Workflow | Purpose |
58
- |----------|---------|
59
- | {{first-workflow}}.md | {{purpose}} |
60
- | {{second-workflow}}.md | {{purpose}} |
61
- | {{third-workflow}}.md | {{purpose}} |
12
+ | Intent | Action |
13
+ |--------|--------|
14
+ | {{First option}} | Read `references/{{first-workflow}}.md` |
15
+ | {{Second option}} | Read `references/{{second-workflow}}.md` |
16
+ | {{Third option}} | Read `references/{{third-workflow}}.md` |
@@ -22,13 +22,3 @@ description: {{What it does}} Use when {{trigger conditions}}.
22
22
  ### Step 3: {{Third action}}
23
23
 
24
24
  {{Instructions for step 3}}
25
-
26
- ## Completion Checklist
27
-
28
- {{Skill name}} is complete when:
29
-
30
- ```
31
- - [ ] {{First success criterion}}
32
- - [ ] {{Second success criterion}}
33
- - [ ] {{Third success criterion}}
34
- ```