@hatchway/cli 0.50.64 → 0.50.66

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/dist/index.js CHANGED
@@ -1462,23 +1462,23 @@ var init_droid_strategy$1 = __esm$1({
1462
1462
  // src/lib/prompts.ts
1463
1463
  var CLAUDE_SYSTEM_PROMPT = `You are an elite coding assistant specialized in building visually stunning, production-ready JavaScript applications.
1464
1464
 
1465
- ## Platform Skills
1465
+ ## Platform Skills (hatchway-platform plugin)
1466
1466
 
1467
- You have access to platform skills that provide critical procedural knowledge. Load and follow these skills for EVERY task:
1467
+ You have platform skills from the \`hatchway-platform\` plugin. These are loaded via the skill system \u2014 invoke each one by name to read its full instructions.
1468
1468
 
1469
- **Always load (required for every build):**
1470
- - \`todo-workflow\` \u2014 You MUST use TodoWrite to track progress. Without it, users see no progress in the UI.
1471
- - \`communication-style\` \u2014 Follow the Hatchway output formatting conventions.
1472
- - \`build-verification\` \u2014 Use the fix-verify loop for all dependency and build errors.
1473
- - \`context-awareness\` \u2014 Read existing code before modifying. Never write blind.
1474
- - \`dependency-management\` \u2014 Install all dependencies upfront in a single operation.
1469
+ **BEFORE doing any work, load ALL 5 of these required skills:**
1470
+ 1. \`hatchway-platform:todo-workflow\` \u2014 You MUST load this FIRST. It defines how to use TodoWrite for progress tracking. Without it, users see no progress in the UI.
1471
+ 2. \`hatchway-platform:communication-style\` \u2014 Defines output formatting for the Hatchway platform.
1472
+ 3. \`hatchway-platform:build-verification\` \u2014 Defines the fix-verify loop for dependency and build errors.
1473
+ 4. \`hatchway-platform:context-awareness\` \u2014 Defines read-before-write discipline.
1474
+ 5. \`hatchway-platform:dependency-management\` \u2014 Defines how to install all dependencies upfront.
1475
1475
 
1476
- **Load when relevant:**
1477
- - \`architectural-thinking\` \u2014 Load when starting a new feature or multi-file change.
1478
- - \`design-excellence\` \u2014 Load when building or styling user-facing UI.
1479
- - \`template-originality\` \u2014 Load only when building a new project from a template scaffold.
1476
+ **Also load these when the task involves them:**
1477
+ - \`hatchway-platform:architectural-thinking\` \u2014 Load for new features or multi-file changes.
1478
+ - \`hatchway-platform:design-excellence\` \u2014 Load when building or styling UI.
1479
+ - \`hatchway-platform:template-originality\` \u2014 Load when building from a template scaffold.
1480
1480
 
1481
- Load each skill by reading its SKILL.md file, then follow its instructions throughout the task.
1481
+ Load each skill at the START of the task before writing any code. Follow the loaded skill instructions throughout the entire task.
1482
1482
 
1483
1483
  ## Plan Mode
1484
1484
 
@@ -4424,16 +4424,26 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
4424
4424
  let messageCount = 0;
4425
4425
  let toolCallCount = 0;
4426
4426
  let textBlockCount = 0;
4427
- // Wrap the entire agent execution in a gen_ai.invoke_agent span for Sentry AI monitoring
4427
+ // Create the gen_ai.invoke_agent span as a child of the current active span.
4428
+ //
4429
+ // We use startInactiveSpan because this is an async generator — we can't use
4430
+ // startSpan/startSpanManual (both require a callback, and yields can't cross
4431
+ // callback boundaries). startInactiveSpan creates a span that inherits the
4432
+ // parent from the current active span (build.runner, restored by engine.ts
4433
+ // via Sentry.withActiveSpan).
4434
+ //
4435
+ // For tool spans, we use Sentry.withActiveSpan(agentSpan, ...) to temporarily
4436
+ // make the agent span active so tool spans become its children.
4428
4437
  const agentSpan = Sentry.startInactiveSpan({
4429
4438
  op: 'gen_ai.invoke_agent',
4430
- name: `Claude Agent (${modelId})`,
4439
+ name: 'invoke_agent hatchway-builder',
4431
4440
  attributes: {
4441
+ 'gen_ai.operation.name': 'invoke_agent',
4432
4442
  'gen_ai.agent.name': 'hatchway-builder',
4433
4443
  'gen_ai.request.model': modelId,
4434
- 'gen_ai.agent.input': finalPrompt.substring(0, 500),
4435
- 'gen_ai.system_prompt.length': appendedSystemPrompt.length,
4436
- 'gen_ai.agent.available_tools': JSON.stringify(['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Task', 'TodoWrite', 'WebFetch']),
4444
+ 'gen_ai.request.messages': JSON.stringify([{ role: 'user', content: finalPrompt.substring(0, 1000) }]),
4445
+ 'gen_ai.request.available_tools': JSON.stringify(['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Task', 'TodoWrite', 'WebFetch']
4446
+ .map(name => ({ name, type: 'function' }))),
4437
4447
  },
4438
4448
  });
4439
4449
  try {
@@ -4449,19 +4459,23 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
4449
4459
  if (block.type === 'tool_use') {
4450
4460
  toolCallCount++;
4451
4461
  debugLog$4(`[runner] [native-sdk] 🔧 Tool call: ${block.name}\n`);
4452
- // Emit a gen_ai.execute_tool span for each tool invocation
4453
- const toolSpan = Sentry.startInactiveSpan({
4454
- op: 'gen_ai.execute_tool',
4455
- name: `Tool: ${block.name}`,
4456
- attributes: {
4457
- 'gen_ai.tool.name': block.name,
4458
- 'gen_ai.tool.call_id': block.id,
4459
- 'gen_ai.tool.input': JSON.stringify(block.input).substring(0, 1000),
4460
- },
4462
+ // Emit a gen_ai.execute_tool span as a child of gen_ai.invoke_agent.
4463
+ // withActiveSpan temporarily makes agentSpan the active span so
4464
+ // the startSpan inside creates a proper child.
4465
+ Sentry.withActiveSpan(agentSpan, () => {
4466
+ Sentry.startSpan({
4467
+ op: 'gen_ai.execute_tool',
4468
+ name: `execute_tool ${block.name}`,
4469
+ attributes: {
4470
+ 'gen_ai.tool.name': block.name,
4471
+ 'gen_ai.tool.call_id': block.id,
4472
+ 'gen_ai.tool.input': JSON.stringify(block.input).substring(0, 1000),
4473
+ 'gen_ai.request.model': modelId,
4474
+ },
4475
+ }, () => {
4476
+ // Span created and ended — marks the tool invocation point
4477
+ });
4461
4478
  });
4462
- // Tool spans are completed immediately since we get input and output
4463
- // as separate messages — the output is captured in the tool_result handler below
4464
- toolSpan?.end();
4465
4479
  }
4466
4480
  else if (block.type === 'text') {
4467
4481
  textBlockCount++;
@@ -4481,6 +4495,11 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
4481
4495
  process.stderr.write(`[native-sdk] SDK init — plugins: ${JSON.stringify(loadedPlugins)}\n`);
4482
4496
  process.stderr.write(`[native-sdk] SDK init — tools: ${toolCount} loaded\n`);
4483
4497
  }
4498
+ // Set discovered skills on the agent span
4499
+ if (agentSpan) {
4500
+ agentSpan.setAttribute('gen_ai.agent.skills', discoveredSkills.join(', '));
4501
+ agentSpan.setAttribute('gen_ai.agent.skill_count', discoveredSkills.length);
4502
+ }
4484
4503
  if (discoveredSkills.length > 0) {
4485
4504
  Sentry.logger.info('SDK initialized with skills', {
4486
4505
  skillCount: String(discoveredSkills.length),
@@ -4514,21 +4533,27 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
4514
4533
  if (sdkMessage.type === 'result') {
4515
4534
  const resultMsg = sdkMessage;
4516
4535
  if (agentSpan) {
4536
+ // Standard gen_ai token usage attributes (Sentry AI Agent Monitoring spec)
4517
4537
  agentSpan.setAttribute('gen_ai.usage.input_tokens', resultMsg.usage?.input_tokens ?? 0);
4518
4538
  agentSpan.setAttribute('gen_ai.usage.output_tokens', resultMsg.usage?.output_tokens ?? 0);
4519
4539
  agentSpan.setAttribute('gen_ai.usage.total_tokens', (resultMsg.usage?.input_tokens ?? 0) + (resultMsg.usage?.output_tokens ?? 0));
4520
- agentSpan.setAttribute('gen_ai.usage.cost_usd', resultMsg.total_cost_usd ?? 0);
4521
- agentSpan.setAttribute('gen_ai.agent.num_turns', resultMsg.num_turns ?? 0);
4522
- agentSpan.setAttribute('gen_ai.agent.num_tool_calls', toolCallCount);
4523
- agentSpan.setAttribute('gen_ai.agent.result', resultMsg.subtype ?? 'unknown');
4524
- agentSpan.setAttribute('gen_ai.agent.duration_ms', resultMsg.duration_ms ?? 0);
4525
- agentSpan.setAttribute('gen_ai.agent.duration_api_ms', resultMsg.duration_api_ms ?? 0);
4526
4540
  if (resultMsg.usage?.cache_read_input_tokens) {
4527
- agentSpan.setAttribute('gen_ai.usage.cache_read_tokens', resultMsg.usage.cache_read_input_tokens);
4541
+ agentSpan.setAttribute('gen_ai.usage.input_tokens.cached', resultMsg.usage.cache_read_input_tokens);
4528
4542
  }
4529
4543
  if (resultMsg.usage?.cache_creation_input_tokens) {
4530
- agentSpan.setAttribute('gen_ai.usage.cache_creation_tokens', resultMsg.usage.cache_creation_input_tokens);
4544
+ agentSpan.setAttribute('gen_ai.usage.input_tokens.cache_write', resultMsg.usage.cache_creation_input_tokens);
4531
4545
  }
4546
+ // Response text (truncated for span safety)
4547
+ if (resultMsg.result) {
4548
+ agentSpan.setAttribute('gen_ai.response.text', JSON.stringify(resultMsg.result.substring(0, 1000)));
4549
+ }
4550
+ // Custom (non-spec) attributes for operational insight
4551
+ agentSpan.setAttribute('hatchway.cost_usd', resultMsg.total_cost_usd ?? 0);
4552
+ agentSpan.setAttribute('hatchway.num_turns', resultMsg.num_turns ?? 0);
4553
+ agentSpan.setAttribute('hatchway.num_tool_calls', toolCallCount);
4554
+ agentSpan.setAttribute('hatchway.result', resultMsg.subtype ?? 'unknown');
4555
+ agentSpan.setAttribute('hatchway.duration_ms', resultMsg.duration_ms ?? 0);
4556
+ agentSpan.setAttribute('hatchway.duration_api_ms', resultMsg.duration_api_ms ?? 0);
4532
4557
  }
4533
4558
  if (resultMsg.subtype === 'success') {
4534
4559
  debugLog$4(`[runner] [native-sdk] ✅ Query complete - ${resultMsg.num_turns} turns, $${resultMsg.total_cost_usd?.toFixed(4)} USD\n`);
@@ -7177,38 +7202,53 @@ async function createBuildStream(options) {
7177
7202
  debugLog$1();
7178
7203
  const generator = query(fullPrompt, actualWorkingDir, systemPrompt, agent, options.codexThreadId, messageParts);
7179
7204
  debugLog$1();
7205
+ // Capture the active Sentry span BEFORE creating the ReadableStream.
7206
+ // The ReadableStream.start() callback runs in a new async context where the
7207
+ // parent build.runner span is no longer active. We restore it with withActiveSpan()
7208
+ // so that gen_ai.invoke_agent spans created inside the query generator are
7209
+ // properly nested as children of the build.runner span.
7210
+ const parentSpan = Sentry.getActiveSpan();
7180
7211
  // Create a ReadableStream from the AsyncGenerator
7181
7212
  const stream = new ReadableStream({
7182
7213
  async start(controller) {
7183
7214
  debugLog$1();
7184
- let chunkCount = 0;
7185
- try {
7186
- for await (const chunk of generator) {
7187
- chunkCount++;
7188
- if (chunkCount % 5 === 0) {
7189
- debugLog$1(`[runner] [build-engine] Processed ${chunkCount} chunks from generator\n`);
7190
- }
7191
- // Convert chunk to appropriate format
7192
- if (typeof chunk === 'string') {
7193
- controller.enqueue(new TextEncoder().encode(chunk));
7194
- }
7195
- else if (chunk instanceof Uint8Array) {
7196
- controller.enqueue(chunk);
7197
- }
7198
- else if (typeof chunk === 'object') {
7199
- controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk)));
7215
+ const consume = async () => {
7216
+ let chunkCount = 0;
7217
+ try {
7218
+ for await (const chunk of generator) {
7219
+ chunkCount++;
7220
+ if (chunkCount % 5 === 0) {
7221
+ debugLog$1(`[runner] [build-engine] Processed ${chunkCount} chunks from generator\n`);
7222
+ }
7223
+ // Convert chunk to appropriate format
7224
+ if (typeof chunk === 'string') {
7225
+ controller.enqueue(new TextEncoder().encode(chunk));
7226
+ }
7227
+ else if (chunk instanceof Uint8Array) {
7228
+ controller.enqueue(chunk);
7229
+ }
7230
+ else if (typeof chunk === 'object') {
7231
+ controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk)));
7232
+ }
7200
7233
  }
7234
+ debugLog$1(`[runner] [build-engine] ✅ Generator exhausted after ${chunkCount} chunks, closing stream\n`);
7235
+ controller.close();
7201
7236
  }
7202
- debugLog$1(`[runner] [build-engine] ✅ Generator exhausted after ${chunkCount} chunks, closing stream\n`);
7203
- controller.close();
7204
- }
7205
- catch (error) {
7206
- debugLog$1();
7207
- controller.error(error);
7237
+ catch (error) {
7238
+ debugLog$1();
7239
+ controller.error(error);
7240
+ }
7241
+ finally {
7242
+ // Restore the original working directory
7243
+ process.chdir(originalCwd);
7244
+ }
7245
+ };
7246
+ // Restore the parent span context so child spans nest correctly
7247
+ if (parentSpan) {
7248
+ await Sentry.withActiveSpan(parentSpan, consume);
7208
7249
  }
7209
- finally {
7210
- // Restore the original working directory
7211
- process.chdir(originalCwd);
7250
+ else {
7251
+ await consume();
7212
7252
  }
7213
7253
  },
7214
7254
  });