bitfab-cli 0.2.259 → 0.2.260

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 (2) hide show
  1. package/dist/index.js +26 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28412,20 +28412,24 @@ var saveTemplate = {
28412
28412
  description: external_exports.string().optional().describe("Optional description. Pass an empty string to clear an existing description.")
28413
28413
  }
28414
28414
  };
28415
- var createTracePlan = {
28416
- name: "create_trace_plan",
28417
- title: "Create Trace Plan",
28418
- description: "Post a tracing instrumentation plan and get back a URL the user opens to review or adjust it. Send the full call tree (root + children + framework spans + ~10 surrounding callees below each leaf as `pure` context nodes, all of them descendants of the root), the initial recommended captured node ids, replay `analysis` (classification + kind) on every node, and any sample input/output stored on the relevant nodes. Pass `traceFunctionKey` so future Modify cycles for this same key can bootstrap from this plan via get_trace_plan. Returns a URL the user opens in the browser; after they close or update the plan there, call get_trace_plan to read the final captured set and the per-node replay/mock decisions, then act on them.",
28415
+ var saveTracePlan = {
28416
+ name: "save_trace_plan",
28417
+ title: "Save Trace Plan",
28418
+ description: "Create or update a tracing instrumentation plan. CREATE: omit `planId` and send `language`, the full `tree`, `capturedNodeIds`, and usually `traceFunctionKey`; this creates a reviewable plan and returns its id, Studio URL, initial capture count, and expiry. UPDATE: pass `planId`; use targeted `capture` / `uncapture` and/or `mockOnReplayByNodeId` changes without restating the tree, or send `tree` plus `capturedNodeIds` together for a structural replacement. Targeted changes preserve status; a structural change to a confirmed plan reopens it to `awaiting`. Never create a second plan when one already exists for the work in hand: save it by `planId` instead. The captured set must stay one connected sub-tree with exactly one entry point, and that entry point always re-runs live and can never be mocked. Updates return the current plan summary, including the final captured set and replay/mock decisions.",
28419
28419
  inputSchema: {
28420
- language: external_exports.enum(["python", "typescript", "ruby", "go"]).describe("Source language of the user's code"),
28420
+ planId: external_exports.string().optional().describe("Existing trace plan id to update. Omit to create a new plan. Do not send creation-only fields `language`, `source`, or `agentRunId` when this is supplied."),
28421
+ language: external_exports.enum(["python", "typescript", "ruby", "go"]).optional().describe("CREATE mode. Source language of the user's code. Required when `planId` is omitted."),
28421
28422
  // Structured (not z.unknown()) so the tool's JSON Schema shows the agent
28422
28423
  // the per-node `analysis` field instead of burying it in prose, which is
28423
28424
  // how plans arrived with every node unclassified.
28424
- tree: external_exports.preprocess(parseJsonString, tracePlanTreeShape).describe("TracePlanTree: { rootId, nodes: { [id]: TraceNode } }. Each TraceNode has id, name, kind ('manual' | 'auto' | 'pure'), file, line, signature, parentId, childIds, plus optional framework, fields, sampleInput, sampleOutput. Every node, including uncaptured context nodes, must carry `analysis` describing WHAT THAT NODE DOES: { classification, sideEffectKind?, readKind?, innerCall?, mockable?, unmockableReason?, inputSerializable?, outputSerializable? }. Set `mockable` mechanically from the node's `kind`: `kind: 'manual'` (a hand-written `withSpan`/`@span`) is mockable, omit `mockable`; `kind: 'auto'` (captured by a framework handler/processor/stream/collector) gets `mockable: false` + `unmockableReason`, the ONE exception being Vercel AI SDK model spans (its `wrapLanguageModel` middleware routes the call through `withSpan`), which are mockable, omit; the root gets omit (never mockable). Rationale: mocking returns a span's recorded output instead of running the call, which only works when the call goes through a `withSpan` wrapper; `auto` framework spans are observed, not wrapped. Key hazard: an `auto` `external_read`/`side_effect` (a framework tool hitting a DB/HTTP) left mockable promises a mock replay can't deliver; its fix is a manual `withSpan` around that call or a db-snapshot, never a mock. Serializability is two raw facts, distinct from `mockable`: set `outputSerializable: false` when the recorded OUTPUT does not round-trip through serialization, and set `inputSerializable: false` when the recorded INPUT does not (its arguments hold a DB client, an open stream, a callback, or a class instance with no JSON form); omit either when it serializes (the default). Two rules follow. (1) Do NOT choose a root whose input is not serializable: replay re-runs the root against its recorded input, so a non-serializable-input root is not replayable, promote the root to a caller that takes a serializable input instead. (2) Do NOT mock a node whose output is not serializable (replay has no recorded value to return); the server enforces this by forcing any `outputSerializable: false` node unmockable, so you don't need to also set `mockable` for that reason. classification is 'pure' (deterministic local compute), 'model_call' (the span that IS the actual LLM/model call; a wrapper/orchestrator whose model call is represented by a child node, e.g. a LangChain chain.invoke or the root, is 'pure', not 'model_call'; never bubble a child's model_call up to its parent), 'external_read' (reads external mutable state: DB SELECT, outbound GET, vector search, cache read; set readKind), or 'side_effect' (mutates external state: DB write, outbound POST/PUT/DELETE, email, queue, payment, filesystem; set sideEffectKind). The server derives the replay disposition (`mockOnReplay`) and the whole validation summary from this classification and mockability, so do NOT send them. Include ~10 surrounding callees below each leaf as `pure` (uncaptured) context nodes so the user can see what's adjacent in the codebase when they edit in the UI. Every node MUST be a descendant of `rootId`: the plan tree renders downward from the root, so a node above the root or on a side branch off one of those ancestors is stored and counted but never drawn. Never send callers above the root."),
28425
- capturedNodeIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).describe("Initial recommended captured set. Must reference ids present in tree.nodes. Must form a connected sub-tree rooted at one of the nodes (selecting any descendant implies its ancestors). Surrounding `pure` context nodes are not captured by default."),
28425
+ tree: external_exports.preprocess(parseJsonString, tracePlanTreeShape).optional().describe("TracePlanTree: { rootId, nodes: { [id]: TraceNode } }. Each TraceNode has id, name, kind ('manual' | 'auto' | 'pure'), file, line, signature, parentId, childIds, plus optional framework, fields, sampleInput, sampleOutput. Every node, including uncaptured context nodes, must carry `analysis` describing WHAT THAT NODE DOES: { classification, sideEffectKind?, readKind?, innerCall?, mockable?, unmockableReason?, inputSerializable?, outputSerializable? }. Set `mockable` mechanically from the node's `kind`: `kind: 'manual'` (a hand-written `withSpan`/`@span`) is mockable, omit `mockable`; `kind: 'auto'` (captured by a framework handler/processor/stream/collector) gets `mockable: false` + `unmockableReason`, the ONE exception being Vercel AI SDK model spans (its `wrapLanguageModel` middleware routes the call through `withSpan`), which are mockable, omit; the root gets omit (never mockable). Rationale: mocking returns a span's recorded output instead of running the call, which only works when the call goes through a `withSpan` wrapper; `auto` framework spans are observed, not wrapped. Key hazard: an `auto` `external_read`/`side_effect` (a framework tool hitting a DB/HTTP) left mockable promises a mock replay can't deliver; its fix is a manual `withSpan` around that call or a db-snapshot, never a mock. Serializability is two raw facts, distinct from `mockable`: set `outputSerializable: false` when the recorded OUTPUT does not round-trip through serialization, and set `inputSerializable: false` when the recorded INPUT does not (its arguments hold a DB client, an open stream, a callback, or a class instance with no JSON form); omit either when it serializes (the default). Two rules follow. (1) Do NOT choose a root whose input is not serializable: replay re-runs the root against its recorded input, so a non-serializable-input root is not replayable, promote the root to a caller that takes a serializable input instead. (2) Do NOT mock a node whose output is not serializable (replay has no recorded value to return); the server enforces this by forcing any `outputSerializable: false` node unmockable, so you don't need to also set `mockable` for that reason. classification is 'pure' (deterministic local compute), 'model_call' (the span that IS the actual LLM/model call; a wrapper/orchestrator whose model call is represented by a child node, e.g. a LangChain chain.invoke or the root, is 'pure', not 'model_call'; never bubble a child's model_call up to its parent), 'external_read' (reads external mutable state: DB SELECT, outbound GET, vector search, cache read; set readKind), or 'side_effect' (mutates external state: DB write, outbound POST/PUT/DELETE, email, queue, payment, filesystem; set sideEffectKind). The server derives the replay disposition (`mockOnReplay`) and the whole validation summary from this classification and mockability, so do NOT send them. Include ~10 surrounding callees below each leaf as `pure` (uncaptured) context nodes so the user can see what's adjacent in the codebase when they edit in the UI. Every node MUST be a descendant of `rootId`: the plan tree renders downward from the root, so a node above the root or on a side branch off one of those ancestors is stored and counted but never drawn. Never send callers above the root."),
28426
+ capturedNodeIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("CREATE or STRUCTURAL update mode. Absolute captured set for `tree`. Required with `tree`; must reference ids in the tree and form one connected sub-tree."),
28427
+ capture: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("TARGETED update mode. Node ids to add to the existing captured set. Requires `planId`; cannot be combined with `tree` / `capturedNodeIds`."),
28428
+ uncapture: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("TARGETED update mode. Node ids to remove from the existing captured set. Requires `planId`; cannot be combined with `tree` / `capturedNodeIds`."),
28429
+ mockOnReplayByNodeId: external_exports.preprocess(parseJsonString, external_exports.record(external_exports.string(), external_exports.boolean())).optional().describe("UPDATE mode. Per-node replay/mock overrides. Only listed nodes change. The replay entry point and framework-observed spans cannot be mocked."),
28426
28430
  stats: external_exports.preprocess(parseJsonString, external_exports.unknown()).optional().describe("Optional sample-run stats: { durationMs?, tokens?, cost? }"),
28427
- traceFunctionKey: external_exports.string().min(1).optional().describe("Optional trace function key (the string passed to getFunction / get_function / bitfab_function / WithFunctionName). Persisted on the plan so Modify cycles can fetch this plan as the `before` tree via get_trace_plan with the same key."),
28428
- source: external_exports.enum(["interactive", "analyze_repo"]).optional().describe("How this plan was produced. Omit (defaults to 'interactive') for a normal Instrument/Modify cycle the user confirms in the browser. Set 'analyze_repo' ONLY from the non-interactive `analyze-repo` batch mode, so these auto-drafted, unconfirmed plans are marked distinctly from interactively-confirmed ones."),
28431
+ traceFunctionKey: external_exports.string().min(1).optional().describe("Trace function key. Recommended when creating; when updating, pass only to rename a key that was wrong or changed."),
28432
+ source: external_exports.enum(["interactive", "analyze_repo"]).optional().describe("CREATE mode. How this plan was produced. Omit for a normal interactive plan; set 'analyze_repo' only from analyze-repo batch mode."),
28429
28433
  agentRunId: external_exports.uuid().optional()
28430
28434
  }
28431
28435
  };
@@ -28434,32 +28438,17 @@ var confirmTracePlan = {
28434
28438
  title: "Confirm Trace Plan",
28435
28439
  description: "Confirm a trace plan WITHOUT the browser, for the continue path where the user accepted the ASCII plan in chat, or left the Studio plan page without saving. Persists the plan as the latest *confirmed* plan for its traceFunctionKey (so later get_trace_plan by key, setup view, and setup modify can find it) and returns the final captured set plus per-node replay/mock decisions, exactly like get_trace_plan. Studio's Close/Update button does this for the browser path; call this only when the user continues without Studio, or leaves the plan page without saving. Pass the recommended capturedNodeIds; omit mockOnReplayByNodeId to accept the analysis-derived replay decisions (only pass it to override specific nodes the way a Studio toggle would).",
28436
28440
  inputSchema: {
28437
- planId: external_exports.string().describe("The trace plan id returned by create_trace_plan."),
28441
+ planId: external_exports.string().describe("The trace plan id returned by save_trace_plan."),
28438
28442
  capturedNodeIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string()).min(1)).describe("The captured set to confirm. Must reference ids present in the plan's tree and form a single connected sub-tree with one entry point (its top-most captured span). The entry point does NOT have to be the plan's tree root: untracing the tree root and rooting at a captured descendant is honored and persisted as-is."),
28439
28443
  mockOnReplayByNodeId: external_exports.preprocess(parseJsonString, external_exports.record(external_exports.string(), external_exports.boolean())).optional().describe("Optional per-node overrides of the replay/mock decision (the equivalent of a Studio toggle). Omit to accept the server's analysis-derived defaults. The replay entry point (the top-most captured span) always re-runs live and cannot be mocked.")
28440
28444
  }
28441
28445
  };
28442
- var updateTracePlan = {
28443
- name: "update_trace_plan",
28444
- title: "Update Trace Plan",
28445
- description: "Change an EXISTING trace plan in place. Use this instead of create_trace_plan whenever a plan already exists for the work in hand (a Modify cycle bootstrapped via get_trace_plan, or an adjustment to a plan you just created): re-uploading produces a second plan competing for the same traceFunctionKey. Two mutually exclusive modes. TARGETED: pass `capture` / `uncapture` (node ids to add to or remove from the captured set) and/or `mockOnReplayByNodeId` (per-node replay/mock overrides) to adjust dispositions without restating the tree; deltas apply to the plan's current state, so they do not clobber a capture the user just saved in Studio. STRUCTURAL: pass `tree` plus `capturedNodeIds` together to replace the plan's whole node tree, for when the code changed and nodes must be added or removed. Sending both modes at once is an error. A structural update on a CONFIRMED plan reopens it to `awaiting`, because replacing the tree invalidates the user's acceptance, so you must confirm_trace_plan again afterwards or `setup view`/`setup modify` will not find a confirmed plan for that key; a targeted update leaves the status alone. Any update refreshes the plan's expiry, so it also revives an expired plan. The captured set must stay one connected sub-tree with exactly one entry point, and that entry point always re-runs live and can never be mocked. Returns the resulting plan exactly as get_trace_plan does, including the final captured set and per-node replay/mock decisions.",
28446
- inputSchema: {
28447
- planId: external_exports.string().describe("The trace plan id returned by create_trace_plan."),
28448
- capture: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("TARGETED mode. Node ids to add to the captured set. Must reference ids present in the plan's tree. Cannot be combined with `tree` / `capturedNodeIds`."),
28449
- uncapture: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("TARGETED mode. Node ids to remove from the captured set. Must reference ids present in the plan's tree. Removing a node that joins two captured branches leaves a disconnected capture set and is rejected. Cannot be combined with `tree` / `capturedNodeIds`."),
28450
- mockOnReplayByNodeId: external_exports.preprocess(parseJsonString, external_exports.record(external_exports.string(), external_exports.boolean())).optional().describe("Per-node overrides of the replay/mock decision (the equivalent of a Studio toggle): true serves the span's recorded output on replay, false re-runs it live. Only the listed nodes change. Valid in either mode. The replay entry point cannot be mocked, and neither can a framework-observed (`kind: 'auto'`) span, since replay cannot short-circuit one."),
28451
- tree: external_exports.preprocess(parseJsonString, tracePlanTreeShape).optional().describe("STRUCTURAL mode. The full replacement TracePlanTree, same shape and the same per-node `analysis` requirements as create_trace_plan. Send this only when the plan's nodes themselves change (code moved, a call was added or removed); to merely capture or mock differently use `capture` / `uncapture` / `mockOnReplayByNodeId`. Must be sent together with `capturedNodeIds`. `rootId` cannot be changed; to move the replay entry point, change which nodes are captured instead, an uncaptured tree root is honored and the top-most captured span becomes the entry."),
28452
- capturedNodeIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.string())).optional().describe("STRUCTURAL mode. The absolute captured set for the replacement `tree`, restated in full. Must reference ids present in that tree and form a single connected sub-tree with one entry point. Required with `tree`, and invalid without it."),
28453
- traceFunctionKey: external_exports.string().min(1).optional().describe("Optional replacement trace function key, for when the key the plan was created with was wrong or is being renamed."),
28454
- stats: external_exports.preprocess(parseJsonString, external_exports.unknown()).optional().describe("Optional sample-run stats: { durationMs?, tokens?, cost? }")
28455
- }
28456
- };
28457
28446
  var getTracePlan = {
28458
28447
  name: "get_trace_plan",
28459
28448
  title: "Get Trace Plan",
28460
- description: "Read a trace plan. Two modes: pass `planId` to read a specific plan (use this after create_trace_plan + browser confirmation to learn whether the user confirmed, cancelled, or is still pending); or pass `traceFunctionKey` to fetch the latest *confirmed* plan for that key (use this at the start of a Modify cycle to bootstrap the `before` tree from the prior plan, so you don't have to re-derive it from the code). Returns the full plan including the tree as JSON.",
28449
+ description: "Read a trace plan. Two modes: pass `planId` to read a specific plan (use this after save_trace_plan + browser confirmation to learn whether the user confirmed, cancelled, or is still pending); or pass `traceFunctionKey` to fetch the latest *confirmed* plan for that key (use this at the start of a Modify cycle to bootstrap the `before` tree from the prior plan, so you don't have to re-derive it from the code). Returns the full plan including the tree as JSON.",
28461
28450
  inputSchema: {
28462
- planId: external_exports.string().optional().describe("The trace plan id returned by create_trace_plan."),
28451
+ planId: external_exports.string().optional().describe("The trace plan id returned by save_trace_plan."),
28463
28452
  traceFunctionKey: external_exports.string().optional().describe("Trace function key. Returns the latest confirmed plan for this key in the caller's organization, or a 'no prior plan' message if none exists.")
28464
28453
  }
28465
28454
  };
@@ -28536,9 +28525,8 @@ var ALL_TOOL_CONTRACTS = [
28536
28525
  getTemplateReference,
28537
28526
  getTemplate,
28538
28527
  saveTemplate,
28539
- createTracePlan,
28528
+ saveTracePlan,
28540
28529
  confirmTracePlan,
28541
- updateTracePlan,
28542
28530
  getTracePlan,
28543
28531
  listTracePlans
28544
28532
  ];
@@ -29630,7 +29618,7 @@ function activityLabelFromEvent(event) {
29630
29618
  }
29631
29619
  function friendlyToolLabel(block) {
29632
29620
  const name = typeof block.name === "string" ? block.name : "";
29633
- if (name.includes("create_trace_plan")) {
29621
+ if (name.includes("save_trace_plan") || name.includes("create_trace_plan")) {
29634
29622
  return "Uploading a draft trace plan";
29635
29623
  }
29636
29624
  if (name.includes("get_bitfab_api_key")) {
@@ -29681,7 +29669,7 @@ function countUploadedTracePlans(logText) {
29681
29669
  continue;
29682
29670
  }
29683
29671
  const b = block;
29684
- if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && b.name.includes("create_trace_plan")) {
29672
+ if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && (b.name.includes("save_trace_plan") || b.name.includes("create_trace_plan"))) {
29685
29673
  planToolUseIds.add(b.id);
29686
29674
  }
29687
29675
  if (b.type === "tool_result" && typeof b.tool_use_id === "string" && b.is_error !== true) {
@@ -29962,7 +29950,7 @@ function codexActivityLabelFromEvent(event) {
29962
29950
  if (!item) {
29963
29951
  return null;
29964
29952
  }
29965
- if (isMcpToolItem(item, "create_trace_plan")) {
29953
+ if (isMcpToolItem(item, "save_trace_plan") || isMcpToolItem(item, "create_trace_plan")) {
29966
29954
  return "Uploading a draft trace plan";
29967
29955
  }
29968
29956
  if (isMcpToolItem(item, "get_bitfab_api_key")) {
@@ -29995,7 +29983,7 @@ function countUploadedTracePlans2(logText) {
29995
29983
  continue;
29996
29984
  }
29997
29985
  const item = eventItem(event);
29998
- if (item && isMcpToolItem(item, "create_trace_plan")) {
29986
+ if (item && (isMcpToolItem(item, "save_trace_plan") || isMcpToolItem(item, "create_trace_plan"))) {
29999
29987
  const status = typeof item.status === "string" ? item.status : "";
30000
29988
  if (status === "completed" || status === "" && eventType(event) === "item.completed") {
30001
29989
  completedPlanCalls++;
@@ -30006,7 +29994,7 @@ function countUploadedTracePlans2(logText) {
30006
29994
  continue;
30007
29995
  }
30008
29996
  const b = block;
30009
- if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && b.name.includes("create_trace_plan")) {
29997
+ if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && (b.name.includes("save_trace_plan") || b.name.includes("create_trace_plan"))) {
30010
29998
  planToolUseIds.add(b.id);
30011
29999
  }
30012
30000
  if (b.type === "tool_result" && typeof b.tool_use_id === "string" && b.is_error !== true) {
@@ -30343,7 +30331,7 @@ function cursorActivityLabelFromEvent(event) {
30343
30331
  return null;
30344
30332
  }
30345
30333
  const mcpToolName = mcpToolCallName(toolCall);
30346
- if (mcpToolName?.includes("create_trace_plan")) {
30334
+ if (mcpToolName?.includes("save_trace_plan") || mcpToolName?.includes("create_trace_plan")) {
30347
30335
  return "Uploading a draft trace plan";
30348
30336
  }
30349
30337
  if (mcpToolName?.includes("get_bitfab_api_key")) {
@@ -30390,7 +30378,7 @@ function countUploadedTracePlans3(logText) {
30390
30378
  } catch {
30391
30379
  continue;
30392
30380
  }
30393
- if (isSuccessfulCreateTracePlanEvent(event)) {
30381
+ if (isSuccessfulSaveTracePlanEvent(event)) {
30394
30382
  completedPlanCalls++;
30395
30383
  }
30396
30384
  }
@@ -30399,7 +30387,7 @@ function countUploadedTracePlans3(logText) {
30399
30387
  }
30400
30388
  return 0;
30401
30389
  }
30402
- function isSuccessfulCreateTracePlanEvent(event) {
30390
+ function isSuccessfulSaveTracePlanEvent(event) {
30403
30391
  if (typeof event !== "object" || event === null) {
30404
30392
  return false;
30405
30393
  }
@@ -30423,7 +30411,7 @@ function isSuccessfulCreateTracePlanEvent(event) {
30423
30411
  typeof args === "object" && args !== null ? args.name : void 0
30424
30412
  ];
30425
30413
  return names.some(
30426
- (name) => typeof name === "string" && name.includes("create_trace_plan")
30414
+ (name) => typeof name === "string" && (name.includes("save_trace_plan") || name.includes("create_trace_plan"))
30427
30415
  ) && hasSuccessfulToolResult(call.result);
30428
30416
  }
30429
30417
  function hasSuccessfulToolResult(result) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.259",
3
+ "version": "0.2.260",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",