@whisperr/wizard 0.7.1 → 0.7.2

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 +35 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2230,6 +2230,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2230
2230
  const seen = /* @__PURE__ */ new Set();
2231
2231
  const rejected = new Set(rejectedCodes);
2232
2232
  let summary = "";
2233
+ let finished = false;
2233
2234
  const proposeTool = {
2234
2235
  name: "propose_event",
2235
2236
  description: "Propose one important, concretely-anchored product event. Returns ok:true when accepted.",
@@ -2291,6 +2292,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2291
2292
  },
2292
2293
  handler: async (input) => {
2293
2294
  summary = String(input.summary ?? "");
2295
+ finished = true;
2294
2296
  return { ok: true };
2295
2297
  }
2296
2298
  };
@@ -2314,6 +2316,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2314
2316
  );
2315
2317
  return {
2316
2318
  outcome: outcome.kind,
2319
+ finished,
2317
2320
  proposed,
2318
2321
  summary,
2319
2322
  surveyReport,
@@ -2596,15 +2599,24 @@ async function runEngine(input) {
2596
2599
  state.tombstonedCodes,
2597
2600
  input.sink
2598
2601
  );
2599
- if (selection.outcome !== "completed" && selection.proposed.length === 0) {
2600
- await failRun(`selection ${selection.outcome}`);
2601
- return { kind: "aborted", reason: `selection ${selection.outcome}` };
2602
+ if (!selection.finished) {
2603
+ const reason = selection.outcome === "completed" ? "selection did not call finish_selection" : `selection ${selection.outcome}`;
2604
+ await failRun(reason);
2605
+ return { kind: "aborted", reason };
2606
+ }
2607
+ if (selection.proposed.length === 0) {
2608
+ await failRun("selection finished with no accepted events");
2609
+ return { kind: "aborted", reason: "selection finished with no accepted events" };
2602
2610
  }
2603
2611
  const approved = await input.callbacks.reviewEvents(selection.proposed);
2604
2612
  if (approved === null) {
2605
2613
  await failRun("selection rejected by user");
2606
2614
  return { kind: "aborted", reason: "selection rejected by user" };
2607
2615
  }
2616
+ if (approved.length === 0) {
2617
+ await failRun("no events accepted for instrumentation");
2618
+ return { kind: "aborted", reason: "no events accepted for instrumentation" };
2619
+ }
2608
2620
  const approvedCodes = new Set(approved.map((event) => event.code));
2609
2621
  for (const event of selection.proposed) {
2610
2622
  if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
@@ -2627,25 +2639,8 @@ async function runEngine(input) {
2627
2639
  input.sink.emit({ phase: "designing", action: `resumed with ${registered.length} registered events` });
2628
2640
  }
2629
2641
  if (registered.length === 0) {
2630
- await patchPhase("reporting", "no events registered for this project");
2631
- try {
2632
- await completeRun(runs, runId, [], /* @__PURE__ */ new Map(), [], true, "unavailable", void 0);
2633
- } catch (error) {
2634
- const reason = error instanceof Error ? error.message : String(error);
2635
- await failRun(`completion failed: ${reason}`);
2636
- return { kind: "aborted", reason: `completion failed: ${reason}` };
2637
- }
2638
- return {
2639
- kind: "completed",
2640
- runId,
2641
- registered,
2642
- eventStatuses: /* @__PURE__ */ new Map(),
2643
- reportEvents: [],
2644
- changedFiles: [],
2645
- identifyWired: false,
2646
- applied: false,
2647
- summary: "No important events found for this project; nothing was instrumented."
2648
- };
2642
+ await failRun("no events registered for this project");
2643
+ return { kind: "aborted", reason: "no events registered for this project" };
2649
2644
  }
2650
2645
  await patchPhase("binding");
2651
2646
  let worktree;
@@ -3193,6 +3188,12 @@ var WRITE_OUTSIDE_REPO_DENIAL = "blocked: writes must stay inside the target rep
3193
3188
  var SENSITIVE_WRITE_DENIAL = "blocked: refusing to write CI/git configuration";
3194
3189
  var BASH_ALLOWLIST_DENIAL = "blocked: command is outside the Bash allowlist - use package install/add commands, mkdir, or git status/diff/log only";
3195
3190
  var CHAINED_COMMAND_DENIAL = "blocked: chained or substituted shell command - run one command at a time";
3191
+ function engineMcpToolName(toolName) {
3192
+ return `mcp__engine__${toolName}`;
3193
+ }
3194
+ function trustedEngineMcpTools(tools) {
3195
+ return new Set(tools.map((tool2) => engineMcpToolName(tool2.name)));
3196
+ }
3196
3197
  var ENV_EXAMPLES = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
3197
3198
  var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".aws", ".ssh", ".gnupg"]);
3198
3199
  var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([".netrc", ".npmrc", ".pypirc"]);
@@ -3217,6 +3218,9 @@ var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]
3217
3218
  var PYTHON_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["pip", "pip3", "poetry", "uv"]);
3218
3219
  var RUBY_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["gem", "bundle"]);
3219
3220
  function evaluateToolUse(toolName, input, context) {
3221
+ if (context.trustedTools?.has(toolName)) {
3222
+ return { behavior: "allow" };
3223
+ }
3220
3224
  switch (toolName) {
3221
3225
  case "Read":
3222
3226
  return evaluateReadLike(readPathInputs(input), readPathInputs(input), context);
@@ -4248,7 +4252,7 @@ async function runPass(opts) {
4248
4252
  }
4249
4253
  return { summary, costUsd, ok, maxedOut };
4250
4254
  }
4251
- function buildToolPolicyHooks(repoPath) {
4255
+ function buildToolPolicyHooks(repoPath, trustedTools) {
4252
4256
  return {
4253
4257
  PreToolUse: [
4254
4258
  {
@@ -4256,7 +4260,8 @@ function buildToolPolicyHooks(repoPath) {
4256
4260
  async (input) => {
4257
4261
  if (input.hook_event_name !== "PreToolUse") return { continue: true };
4258
4262
  const decision = evaluateToolUse(input.tool_name, toolInputRecord(input.tool_input), {
4259
- repoPath
4263
+ repoPath,
4264
+ trustedTools
4260
4265
  });
4261
4266
  if (decision.behavior === "allow") {
4262
4267
  return { continue: true };
@@ -4274,9 +4279,9 @@ function buildToolPolicyHooks(repoPath) {
4274
4279
  ]
4275
4280
  };
4276
4281
  }
4277
- function createToolPermissionCallback(repoPath) {
4282
+ function createToolPermissionCallback(repoPath, trustedTools) {
4278
4283
  return async (toolName, input, options) => {
4279
- const decision = evaluateToolUse(toolName, input, { repoPath });
4284
+ const decision = evaluateToolUse(toolName, input, { repoPath, trustedTools });
4280
4285
  return decision.behavior === "allow" ? { behavior: "allow", toolUseID: options.toolUseID } : {
4281
4286
  behavior: "deny",
4282
4287
  message: decision.message,
@@ -4355,9 +4360,10 @@ function createClaudeProvider(config, session) {
4355
4360
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
4356
4361
  })
4357
4362
  );
4363
+ const trustedTools = trustedEngineMcpTools(invocation.tools);
4358
4364
  const allowedTools = [
4359
4365
  ...invocation.allowRepoWrite ? EDIT_TOOLS : READ_ONLY_TOOLS2,
4360
- ...invocation.tools.map((engineTool) => `mcp__engine__${engineTool.name}`)
4366
+ ...trustedTools
4361
4367
  ];
4362
4368
  let sessionId = invocation.resumeSessionId;
4363
4369
  let costUsd = 0;
@@ -4375,8 +4381,8 @@ function createClaudeProvider(config, session) {
4375
4381
  ...roleConfig.effort ? { effort: roleConfig.effort } : {},
4376
4382
  tools: [...allowedTools],
4377
4383
  ...hostTools.length > 0 ? { mcpServers: { engine: createSdkMcpServer({ name: "engine", tools: hostTools }) } } : {},
4378
- hooks: buildToolPolicyHooks(invocation.cwd),
4379
- canUseTool: createToolPermissionCallback(invocation.cwd),
4384
+ hooks: buildToolPolicyHooks(invocation.cwd, trustedTools),
4385
+ canUseTool: createToolPermissionCallback(invocation.cwd, trustedTools),
4380
4386
  maxTurns: roleConfig.maxTurns,
4381
4387
  ...invocation.resumeSessionId ? { resume: invocation.resumeSessionId } : {},
4382
4388
  settingSources: []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whisperr/wizard",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Whisperr Wizard \u2014 one command to integrate the Whisperr SDK into your app. Authenticates with your onboarded account and uses an AI coding agent to wire up identify() and your business events automatically.",
5
5
  "repository": {
6
6
  "type": "git",