cc-dev-template 0.1.8 → 0.1.10

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.
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "qa-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server that spawns a QA sub-agent for frontend visual inspection",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "qa-server": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts"
14
+ },
15
+ "dependencies": {
16
+ "@anthropic-ai/claude-agent-sdk": "^0.1.30",
17
+ "@modelcontextprotocol/sdk": "^1.0.0",
18
+ "zod": "^3.24.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.0.0",
22
+ "tsx": "^4.7.0",
23
+ "typescript": "^5.3.0"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ }
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * QA Server - MCP server that spawns a QA sub-agent for frontend visual inspection
4
+ *
5
+ * This server exposes a single `inspect_frontend` tool that:
6
+ * 1. Takes a URL and task description
7
+ * 2. Spawns a Claude sub-agent with chrome-devtools MCP access
8
+ * 3. The sub-agent navigates, screenshots, clicks, and inspects
9
+ * 4. Returns a structured QA report
10
+ *
11
+ * Token savings: ~20k (26 browser tools) → ~500 (1 tool)
12
+ */
13
+
14
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import {
17
+ CallToolRequestSchema,
18
+ ListToolsRequestSchema,
19
+ } from "@modelcontextprotocol/sdk/types.js";
20
+ import { query } from "@anthropic-ai/claude-agent-sdk";
21
+
22
+ // QA Agent system prompt - follows ADR-002: explain WHY, positive framing, clear context
23
+ const QA_SYSTEM_PROMPT = `You are a QA engineer helping developers verify their frontend works correctly.
24
+
25
+ **Why this matters:** Catching visual bugs, console errors, and broken interactions early saves significant debugging time. Your detailed assessment helps developers ship with confidence.
26
+
27
+ **Your capabilities:** You can navigate pages, take screenshots to see the visual state, click elements, fill forms, and read console messages. Screenshots show you exactly what users see.
28
+
29
+ **What makes a great QA report:**
30
+ - Visual Assessment: Describe the layout, colors, and overall appearance
31
+ - Console Errors: List any errors found (or confirm none exist)
32
+ - Task Result: What happened when you performed the requested task
33
+ - Issues Found: Any problems discovered during inspection
34
+ - Overall: PASS or FAIL with a clear reason
35
+
36
+ Focus on what the developer needs to know: visual issues, broken functionality, and console errors. Be thorough in your inspection and clear in your reporting.`;
37
+
38
+ // Create MCP server
39
+ const server = new Server(
40
+ {
41
+ name: "qa-server",
42
+ version: "1.0.0",
43
+ },
44
+ {
45
+ capabilities: {
46
+ tools: {},
47
+ },
48
+ }
49
+ );
50
+
51
+ // List available tools
52
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
53
+ return {
54
+ tools: [
55
+ {
56
+ name: "inspect_frontend",
57
+ description:
58
+ "Spawn a QA sub-agent to visually inspect a frontend. The sub-agent can navigate, take screenshots, click elements, fill forms, and check console errors. Returns a structured QA report. Uses Opus for best visual understanding.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ url: {
63
+ type: "string",
64
+ description: "URL to inspect (e.g., http://localhost:3000, https://example.com)",
65
+ },
66
+ task: {
67
+ type: "string",
68
+ description:
69
+ "What to inspect or test (e.g., 'Verify the login form works', 'Check for visual regressions on the homepage', 'Test the checkout flow')",
70
+ },
71
+ },
72
+ required: ["url", "task"],
73
+ },
74
+ },
75
+ ],
76
+ };
77
+ });
78
+
79
+ // Handle tool calls
80
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
+ if (request.params.name !== "inspect_frontend") {
82
+ throw new Error(`Unknown tool: ${request.params.name}`);
83
+ }
84
+
85
+ const { url, task } = request.params.arguments as { url: string; task: string };
86
+
87
+ if (!url || !task) {
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: "Error: Both 'url' and 'task' parameters are required",
93
+ },
94
+ ],
95
+ isError: true,
96
+ };
97
+ }
98
+
99
+ console.error(`[QA Server] Starting inspection of ${url}`);
100
+ console.error(`[QA Server] Task: ${task}`);
101
+
102
+ try {
103
+ // Build the task prompt - follows ADR-002: purpose-driven, positive framing
104
+ const taskPrompt = `
105
+ **QA Inspection Request**
106
+
107
+ URL: ${url}
108
+ Task: ${task}
109
+
110
+ **Context:** A developer needs verification that this frontend is working correctly. Your assessment helps them catch issues before users do.
111
+
112
+ **Available tools:**
113
+ - navigate_page: Go to the URL
114
+ - take_screenshot: See the visual state (use this to understand what the page looks like)
115
+ - take_snapshot: Get element references for clicking/filling
116
+ - list_console_messages: Check for JavaScript errors
117
+ - click, fill, fill_form: Interact with elements
118
+
119
+ **Success criteria:** Provide a clear QA report covering visual state, console errors, task completion, and overall pass/fail assessment.
120
+ `;
121
+
122
+ // Spawn sub-agent with chrome-devtools MCP access
123
+ const result = query({
124
+ prompt: taskPrompt,
125
+ options: {
126
+ model: "claude-opus-4-5",
127
+ cwd: process.cwd(),
128
+ systemPrompt: QA_SYSTEM_PROMPT,
129
+ mcpServers: {
130
+ "chrome-devtools": {
131
+ command: "npx",
132
+ args: ["chrome-devtools-mcp@latest", "--headless"],
133
+ },
134
+ },
135
+ allowedTools: [
136
+ // Navigation
137
+ "mcp__chrome-devtools__navigate_page",
138
+ "mcp__chrome-devtools__new_page",
139
+ "mcp__chrome-devtools__list_pages",
140
+ "mcp__chrome-devtools__select_page",
141
+ "mcp__chrome-devtools__wait_for",
142
+ // Inspection
143
+ "mcp__chrome-devtools__take_screenshot",
144
+ "mcp__chrome-devtools__take_snapshot",
145
+ "mcp__chrome-devtools__list_console_messages",
146
+ "mcp__chrome-devtools__get_console_message",
147
+ // Interaction
148
+ "mcp__chrome-devtools__click",
149
+ "mcp__chrome-devtools__fill",
150
+ "mcp__chrome-devtools__fill_form",
151
+ "mcp__chrome-devtools__hover",
152
+ "mcp__chrome-devtools__press_key",
153
+ "mcp__chrome-devtools__handle_dialog",
154
+ ],
155
+ },
156
+ });
157
+
158
+ // Collect the agent's final response
159
+ let report = "";
160
+ let totalCost = 0;
161
+
162
+ for await (const message of result) {
163
+ // Log progress to stderr (visible in server logs, not returned to client)
164
+ if (message.type === "system" && message.subtype === "init") {
165
+ console.error(`[QA Server] Agent initialized with tools: ${message.tools.length} tools`);
166
+ }
167
+
168
+ if (message.type === "assistant") {
169
+ // Extract text responses from assistant messages
170
+ const textBlocks = message.message.content.filter(
171
+ (c: any) => c.type === "text"
172
+ );
173
+ for (const block of textBlocks) {
174
+ report = block.text; // Keep latest text as the report
175
+ }
176
+
177
+ // Log tool calls
178
+ const toolCalls = message.message.content.filter(
179
+ (c: any) => c.type === "tool_use"
180
+ );
181
+ for (const tool of toolCalls) {
182
+ console.error(`[QA Server] Tool call: ${tool.name}`);
183
+ }
184
+ }
185
+
186
+ if (message.type === "result") {
187
+ totalCost = message.total_cost_usd;
188
+ console.error(`[QA Server] Completed. Cost: $${totalCost.toFixed(4)}`);
189
+ }
190
+ }
191
+
192
+ // Return the QA report
193
+ return {
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: report || "No report generated. The agent may have encountered an issue.",
198
+ },
199
+ ],
200
+ };
201
+ } catch (error) {
202
+ const errorMessage = error instanceof Error ? error.message : String(error);
203
+ console.error(`[QA Server] Error: ${errorMessage}`);
204
+
205
+ return {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: `QA inspection failed: ${errorMessage}`,
210
+ },
211
+ ],
212
+ isError: true,
213
+ };
214
+ }
215
+ });
216
+
217
+ // Start the server
218
+ async function main() {
219
+ const transport = new StdioServerTransport();
220
+ await server.connect(transport);
221
+ console.error("[QA Server] Started and ready for connections");
222
+ }
223
+
224
+ main().catch((error) => {
225
+ console.error("[QA Server] Fatal error:", error);
226
+ process.exit(1);
227
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -8,94 +8,14 @@
8
8
  * - Line 1: Git branch + status
9
9
  * - Line 2: Context window usage bar with percentage and tokens
10
10
  *
11
- * Input: JSON on stdin with transcript_path
11
+ * Input: JSON on stdin with context_window data
12
12
  * Output: Formatted status to stdout
13
13
  */
14
14
 
15
- const { existsSync, readFileSync, readdirSync, statSync } = require('fs');
15
+ const { readFileSync, readdirSync, statSync } = require('fs');
16
16
  const { join, basename } = require('path');
17
17
  const { execSync } = require('child_process');
18
18
 
19
- /**
20
- * Get model token limit based on model ID
21
- */
22
- function getModelTokenLimit(modelId) {
23
- // Claude model context windows
24
- if (modelId.includes('claude-3-5-sonnet') || modelId.includes('claude-sonnet-4')) {
25
- return 200000;
26
- }
27
- if (modelId.includes('claude-3-opus')) {
28
- return 200000;
29
- }
30
- if (modelId.includes('claude-3-haiku')) {
31
- return 200000;
32
- }
33
- // Default fallback
34
- return 200000;
35
- }
36
-
37
- /**
38
- * Calculate context window usage from transcript
39
- * Uses MOST RECENT message only (each message already reports full context)
40
- */
41
- function calculateTokenUsage(transcriptPath) {
42
- try {
43
- if (!existsSync(transcriptPath)) {
44
- return null;
45
- }
46
-
47
- const content = readFileSync(transcriptPath, 'utf-8');
48
- const lines = content.trim().split('\n');
49
-
50
- // Parse all messages
51
- const messages = [];
52
- for (const line of lines) {
53
- if (!line.trim()) continue;
54
- try {
55
- messages.push(JSON.parse(line));
56
- } catch {
57
- continue;
58
- }
59
- }
60
-
61
- // Find most recent main-chain message with usage data
62
- const mainChainMessages = messages.filter((m) => m.isSidechain === false);
63
- if (mainChainMessages.length === 0) {
64
- return null;
65
- }
66
-
67
- // Sort by timestamp descending (most recent first)
68
- mainChainMessages.sort(
69
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
70
- );
71
-
72
- // Find first message with usage data
73
- let contextLength = 0;
74
- let modelId = '';
75
-
76
- for (const msg of mainChainMessages) {
77
- if (msg.message?.usage) {
78
- const usage = msg.message.usage;
79
- // Context = input + cache_read + cache_creation
80
- // (each message already reports FULL context at that point)
81
- contextLength =
82
- (usage.input_tokens || 0) +
83
- (usage.cache_read_input_tokens || 0) +
84
- (usage.cache_creation_input_tokens || 0);
85
- modelId = msg.message.model || '';
86
- break; // Stop after most recent usage!
87
- }
88
- }
89
-
90
- const limit = getModelTokenLimit(modelId);
91
- const percentage = Math.min(100, Math.round((contextLength / limit) * 100));
92
-
93
- return { used: contextLength, limit, percentage };
94
- } catch {
95
- return null;
96
- }
97
- }
98
-
99
19
  /**
100
20
  * Format number as K (e.g., 84000 -> "084K")
101
21
  */
@@ -269,10 +189,20 @@ function main() {
269
189
  const DIM_GREY = '\x1b[38;5;245m';
270
190
  const RESET = '\x1b[0m';
271
191
 
272
- // Get token usage from transcript (use transcript_path directly from input)
192
+ // Get token usage from context_window (available in Claude Code v2.0.70+)
273
193
  let tokenData = null;
274
- if (data.transcript_path) {
275
- tokenData = calculateTokenUsage(data.transcript_path);
194
+ if (data.context_window?.current_usage) {
195
+ const usage = data.context_window.current_usage;
196
+ const used =
197
+ (usage.input_tokens || 0) +
198
+ (usage.cache_read_input_tokens || 0) +
199
+ (usage.cache_creation_input_tokens || 0);
200
+ const limit = data.context_window.context_window_size || 200000;
201
+ tokenData = {
202
+ used,
203
+ limit,
204
+ percentage: Math.min(100, Math.round((used / limit) * 100)),
205
+ };
276
206
  }
277
207
 
278
208
  // Generate context display
@@ -81,6 +81,27 @@ After writing SKILL.md, ask: **"If Claude reads this right now, does it know wha
81
81
 
82
82
  If the answer is "it would understand what the skill is about" but not "it would know what to do," rewrite it.
83
83
 
84
+ ### Agent Prompts Are Different
85
+
86
+ The red flags above apply to SKILL.md files, not agent prompts. Agent prompts and skills have different characteristics:
87
+
88
+ | Aspect | Agent Prompt | SKILL.md |
89
+ |--------|--------------|----------|
90
+ | When read | Once per spawn | Repeatedly during session |
91
+ | Purpose | Provide context for focused task | Route to workflows |
92
+ | WHY/WHO/SUCCESS | Appropriate—crucial context | Red flag—wastes tokens |
93
+ | Lifespan | Single invocation | Entire user session |
94
+
95
+ Agent prompts benefit from a Purpose section with WHY/WHO/SUCCESS because:
96
+ - Agents spawn for a single focused task and need full context upfront
97
+ - The context is read once, not repeatedly
98
+ - Understanding purpose helps agents make better decisions
99
+
100
+ Skills should route to actions immediately because:
101
+ - Users interact with skills throughout a session
102
+ - Meta-descriptions consume tokens on every activation
103
+ - Instructions are more valuable than context in repeated interactions
104
+
84
105
  ## Progressive Disclosure
85
106
 
86
107
  ### The Problem Without It
@@ -129,6 +129,11 @@ function getPlanInfo(planDir, slug) {
129
129
  const taskCounts = countTasks(manifest);
130
130
  if (taskCounts) {
131
131
  planInfo.tasks = taskCounts;
132
+
133
+ // Derive completion status: if all tasks are completed, the plan is completed
134
+ if (taskCounts.total > 0 && taskCounts.completed === taskCounts.total) {
135
+ planInfo.status = 'completed';
136
+ }
132
137
  }
133
138
  }
134
139