create-claude-workspace 1.1.117 → 1.1.118

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": "create-claude-workspace",
3
- "version": "1.1.117",
3
+ "version": "1.1.118",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,197 +0,0 @@
1
- // ─── Integration tests: agent tag + color in real Claude CLI stream ───
2
- // Spawns actual `claude` process and verifies agent tags appear in formatted output.
3
- import { describe, it, expect, beforeEach } from 'vitest';
4
- import { execSync, spawn } from 'node:child_process';
5
- import { resetStreamState, setAgentTag, simplifyModelName, colorizeAgent, formatStreamEvent, formatStreamEventTracked, } from './claude-runner.mjs';
6
- // ─── ANSI helpers ───
7
- function stripAnsi(s) {
8
- return s.replace(/\x1b\[\d+m/g, '');
9
- }
10
- // ─── Check if claude CLI is available ───
11
- function claudeAvailable() {
12
- try {
13
- execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
14
- return true;
15
- }
16
- catch {
17
- return false;
18
- }
19
- }
20
- const hasClaude = claudeAvailable();
21
- function spawnClaude(prompt, maxTurns, extraFlags = []) {
22
- const isWin = process.platform === 'win32';
23
- const flags = [
24
- '-p',
25
- '--output-format', 'stream-json',
26
- '--verbose',
27
- '--include-partial-messages',
28
- '--max-turns', String(maxTurns),
29
- ...extraFlags,
30
- ];
31
- const child = isWin
32
- ? spawn(process.env.ComSpec ?? 'cmd.exe', ['/c', 'claude', ...flags], {
33
- stdio: ['pipe', 'pipe', 'pipe'],
34
- windowsHide: true,
35
- })
36
- : spawn('claude', [...flags, prompt], {
37
- stdio: ['ignore', 'pipe', 'pipe'],
38
- });
39
- if (isWin && child.stdin) {
40
- child.stdin.write(prompt);
41
- child.stdin.end();
42
- }
43
- setAgentTag('orchestrator');
44
- const rawEvents = [];
45
- const formattedLines = [];
46
- let detectedModel = '';
47
- return new Promise((resolve) => {
48
- let buffer = '';
49
- child.stdout.on('data', (chunk) => {
50
- buffer += chunk.toString();
51
- const lines = buffer.split('\n');
52
- buffer = lines.pop();
53
- for (const rawLine of lines) {
54
- const line = rawLine.replace(/\r/g, '');
55
- if (!line.trim())
56
- continue;
57
- let event;
58
- try {
59
- event = JSON.parse(line);
60
- }
61
- catch {
62
- continue;
63
- }
64
- rawEvents.push(event);
65
- if (event.type === 'system' && typeof event.model === 'string') {
66
- detectedModel = simplifyModelName(event.model);
67
- setAgentTag('orchestrator', detectedModel);
68
- }
69
- const formatted = formatStreamEventTracked(event);
70
- if (formatted) {
71
- formattedLines.push(formatted);
72
- process.stdout.write(formatted);
73
- }
74
- }
75
- });
76
- child.stderr.on('data', () => { });
77
- child.on('close', () => resolve({ rawEvents, formattedLines, detectedModel }));
78
- });
79
- }
80
- // ─── Tests ───
81
- describe('agent tag integration: real Claude CLI', () => {
82
- beforeEach(() => {
83
- resetStreamState();
84
- });
85
- it.skipIf(!hasClaude)('spawns claude and verifies agent tag in stream output', async () => {
86
- const result = await spawnClaude('Respond with exactly: ok', 1);
87
- // System event with model
88
- const systemEvent = result.rawEvents.find(e => e.type === 'system');
89
- expect(systemEvent, 'system event should be present').toBeDefined();
90
- expect(systemEvent.model, 'system event should contain model').toBeDefined();
91
- console.log(`\nDetected model: ${systemEvent.model} → simplified: ${result.detectedModel}`);
92
- // Model simplified correctly
93
- expect(result.detectedModel).toMatch(/^\w+-\d+(\.\d+)?$/);
94
- // Formatted output contains agent tag with model
95
- const allPlain = result.formattedLines.map(stripAnsi).join('');
96
- expect(allPlain).toContain('orchestrator');
97
- expect(allPlain).toContain(result.detectedModel);
98
- // At least one line has full tag format
99
- const hasFullTag = result.formattedLines.some(line => stripAnsi(line).includes(`orchestrator/${result.detectedModel}`));
100
- expect(hasFullTag, `expected "orchestrator/${result.detectedModel}" in output`).toBe(true);
101
- console.log(`\n═══ ${result.rawEvents.length} events, ${result.formattedLines.length} formatted lines ═══`);
102
- console.log(`Event types: ${[...new Set(result.rawEvents.map(e => e.type))].join(', ')}`);
103
- }, 60_000);
104
- it.skipIf(!hasClaude)('shows sub-agent name when Agent tool is used', async () => {
105
- // Use a custom agent defined in .claude/agents/test-dummy.md (model: haiku)
106
- // This mirrors how orchestrator delegates: subagent_type="test-dummy", model="haiku"
107
- const prompt = [
108
- 'You MUST use the Agent tool exactly once.',
109
- 'Call it with: subagent_type "test-dummy", model "haiku", description "find ts files",',
110
- 'prompt "Use Glob with pattern **/*.mts to find TypeScript files. Return the list."',
111
- 'After the Agent tool returns, respond with: done',
112
- ].join(' ');
113
- const result = await spawnClaude(prompt, 5, ['--dangerously-skip-permissions']);
114
- // Find assistant events with Agent tool_use
115
- const agentToolEvents = result.rawEvents.filter(e => {
116
- if (e.type !== 'assistant')
117
- return false;
118
- const msg = e.message;
119
- if (!msg || typeof msg === 'string')
120
- return false;
121
- const content = msg.content;
122
- return content?.some(b => b.type === 'tool_use' && b.name === 'Agent');
123
- });
124
- console.log(`\n═══ Sub-agent test: ${result.rawEvents.length} events, ${agentToolEvents.length} Agent tool calls ═══`);
125
- // Verify Agent tool was called
126
- expect(agentToolEvents.length, 'expected at least one Agent tool_use event').toBeGreaterThanOrEqual(1);
127
- // Verify formatted output contains the robot icon and sub-agent identifier
128
- const allPlain = result.formattedLines.map(stripAnsi).join('');
129
- expect(allPlain, 'expected 🤖 icon for Agent tool call').toContain('🤖');
130
- expect(allPlain, 'expected → arrow for sub-agent delegation').toContain('→');
131
- // Verify the delegation line shows orchestrator (parent) + agent name
132
- const agentLine = result.formattedLines.find(l => stripAnsi(l).includes('🤖'));
133
- expect(agentLine, 'expected a formatted line with 🤖').toBeDefined();
134
- const agentLinePlain = stripAnsi(agentLine);
135
- console.log(`Delegation line: ${agentLinePlain.trim()}`);
136
- // Delegation line should show orchestrator as the caller
137
- expect(agentLinePlain).toContain(`orchestrator/${result.detectedModel}`);
138
- // Should contain agent name "test-dummy" with model "haiku"
139
- expect(agentLinePlain, 'expected "test-dummy" in delegation line')
140
- .toContain('test-dummy');
141
- expect(agentLinePlain, 'expected "haiku" model in delegation line')
142
- .toContain('haiku');
143
- // Verify sub-agent's internal tool calls show sub-agent name + model (not orchestrator)
144
- const plainLines = result.formattedLines.map(stripAnsi);
145
- const delegationIdx = plainLines.findIndex(l => l.includes('🤖'));
146
- const subAgentLines = plainLines.slice(delegationIdx + 1).filter(l => l.includes('▶') || l.includes('💭'));
147
- if (subAgentLines.length > 0) {
148
- console.log(`Sub-agent internal tool calls (${subAgentLines.length}):`);
149
- for (const line of subAgentLines) {
150
- console.log(` ${line.trim()}`);
151
- // Internal tool calls should show "test-dummy/haiku", NOT "orchestrator"
152
- expect(line, 'sub-agent tool call should show test-dummy')
153
- .toContain('test-dummy');
154
- expect(line, 'sub-agent tool call should show haiku model')
155
- .toContain('haiku');
156
- expect(line, 'sub-agent tool call should NOT show orchestrator')
157
- .not.toContain(`orchestrator/${result.detectedModel}`);
158
- }
159
- }
160
- // After Agent returns, verify orchestrator tag is restored
161
- const postAgentLines = plainLines.slice(delegationIdx + 1).filter(l => !l.includes('▶') && !l.includes('💭') && !l.includes('✓') && !l.includes('⏳') && l.trim());
162
- if (postAgentLines.length > 0) {
163
- const lastLine = postAgentLines[postAgentLines.length - 1];
164
- console.log(`Post-agent line: ${lastLine.trim()}`);
165
- expect(lastLine, 'after Agent returns, orchestrator tag should be restored')
166
- .toContain(`orchestrator/${result.detectedModel}`);
167
- }
168
- console.log(`Event types: ${[...new Set(result.rawEvents.map(e => e.type))].join(', ')}`);
169
- console.log('═══ End ═══\n');
170
- }, 120_000);
171
- it.skipIf(!hasClaude)('agent colors are visible in real formatted output', () => {
172
- const agents = [
173
- 'orchestrator', 'ui-engineer', 'backend-ts-architect',
174
- 'senior-code-reviewer', 'test-engineer', 'product-owner',
175
- 'technical-planner', 'devops-integrator', 'deployment-engineer',
176
- 'project-initializer',
177
- ];
178
- console.log('\n═══ Agent colors (visual check) ═══');
179
- for (const agent of agents) {
180
- setAgentTag(agent, 'opus-4.6');
181
- const line = formatStreamEvent({
182
- type: 'assistant',
183
- message: { content: [{ type: 'text', text: `Working on task #42...` }] },
184
- });
185
- process.stdout.write(line);
186
- resetStreamState();
187
- }
188
- console.log('═══ End of agent colors ═══\n');
189
- const orchColor = colorizeAgent('orchestrator').match(/\x1b\[\d+m/)?.[0];
190
- for (const agent of agents.slice(1)) {
191
- const color = colorizeAgent(agent).match(/\x1b\[\d+m/)?.[0];
192
- if (agent !== 'devops-integrator') {
193
- expect(color, `${agent} should differ from orchestrator`).not.toBe(orchColor);
194
- }
195
- }
196
- });
197
- });