@whisperr/wizard 0.7.2 → 0.7.3

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 +69 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import open2 from "open";
11
11
 
12
12
  // src/core/config.ts
13
13
  var DEFAULT_API_BASE = "https://api.whisperr.net";
14
- var DEFAULT_MODEL = "claude-sonnet-5";
14
+ var DEFAULT_MODEL = "claude-opus-5";
15
15
  var DEFAULT_PLANNER_MODEL = "claude-opus-5";
16
16
  var DEFAULT_EFFORT = "high";
17
17
  var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
@@ -2063,11 +2063,14 @@ var SURVEY_SYSTEM_PROMPT = [
2063
2063
  "",
2064
2064
  "Report, in plain markdown:",
2065
2065
  "1. Stack: frameworks, package manager, app entry point file.",
2066
- "2. Feature areas: name, root directory, and the 1-3 files where committed user actions happen.",
2066
+ "2. Feature areas: name, root directory, and every relevant file containing",
2067
+ " committed end-user actions or meaningful lifecycle transitions.",
2067
2068
  "3. The end-user identify() anchor: the login/signup success and session-restore call sites for the",
2068
2069
  " PAYING end user \u2014 never admin, staff, operator, or seller paths.",
2069
2070
  "4. Existing analytics calls, if any.",
2070
- "5. Committed-action call sites worth instrumenting: file, function/symbol, and what the user just did.",
2071
+ "5. Exhaustive inventory of integratable call sites: file, function/symbol,",
2072
+ " confirmed outcome or state transition, available non-PII payload fields,",
2073
+ " and whether similar paths exist elsewhere.",
2071
2074
  "Cite concrete file paths for every claim. Do not propose event names yet."
2072
2075
  ].join("\n");
2073
2076
  function surveyUserPrompt(repoPath, onboardingContext) {
@@ -2090,9 +2093,20 @@ var SELECTION_SYSTEM_PROMPT = [
2090
2093
  " streak_broken) \u2014 the server derives those later.",
2091
2094
  "- Emit after confirmed outcomes, not intent: a purchase event fires on confirmed success, not on",
2092
2095
  " button tap; denied permissions are not grants.",
2093
- "- Importance is anchored in the business context: the activation moment, churn-risk and healthy",
2094
- " signals, and how the product charges. A handful of load-bearing events beats wide coverage.",
2095
- "- An empty or small set is a valid outcome. There is no quota. Never invent events to look thorough.",
2096
+ "- Aim for comprehensive coverage of the product\u2019s observable end-user lifecycle.",
2097
+ " Inspect every meaningful feature area and propose every distinct, concretely",
2098
+ " integratable event that could contribute to activation, engagement, retention,",
2099
+ " monetization, or churn analysis.",
2100
+ "- Prefer complete coverage over a short curated list. Include distinct successful",
2101
+ " outcomes, failures, cancellations, retries, state transitions, and meaningful",
2102
+ " feature usage when each has its own concrete call site and analytical value.",
2103
+ "- Let the product\u2019s actual complexity determine the count. A feature-rich product",
2104
+ " may legitimately produce roughly 100\u2013200 events, while a smaller product may",
2105
+ " produce fewer. Do not stop after finding only the most obvious events, and do",
2106
+ " not invent events merely to reach a number.",
2107
+ "- Before finishing, systematically revisit every surveyed feature area and confirm",
2108
+ " that all meaningful committed actions and lifecycle transitions have either",
2109
+ " been proposed or deliberately excluded with a reason.",
2096
2110
  "- Codes are lowercase snake_case, named for what happened (checkout_completed, not do_checkout).",
2097
2111
  "- payloadSchema lists the fields worth capturing with a short description of each \u2014 the important",
2098
2112
  " info only. NEVER include personal identifiers: no email, phone, names, addresses, birth dates,",
@@ -3183,7 +3197,7 @@ function coverageNote(coverage) {
3183
3197
 
3184
3198
  // src/core/toolPolicy.ts
3185
3199
  import { isAbsolute, relative, resolve } from "path";
3186
- var SECRET_MATERIAL_DENIAL = "blocked: secret material - reference the variable name in .env.example instead of reading the real file";
3200
+ var SECRET_MATERIAL_DENIAL = "blocked: secret material must not be accessed";
3187
3201
  var WRITE_OUTSIDE_REPO_DENIAL = "blocked: writes must stay inside the target repository";
3188
3202
  var SENSITIVE_WRITE_DENIAL = "blocked: refusing to write CI/git configuration";
3189
3203
  var BASH_ALLOWLIST_DENIAL = "blocked: command is outside the Bash allowlist - use package install/add commands, mkdir, or git status/diff/log only";
@@ -3194,9 +3208,24 @@ function engineMcpToolName(toolName) {
3194
3208
  function trustedEngineMcpTools(tools) {
3195
3209
  return new Set(tools.map((tool2) => engineMcpToolName(tool2.name)));
3196
3210
  }
3197
- var ENV_EXAMPLES = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
3198
- var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".aws", ".ssh", ".gnupg"]);
3199
- var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([".netrc", ".npmrc", ".pypirc"]);
3211
+ var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([
3212
+ ".aws",
3213
+ ".ssh",
3214
+ ".gnupg",
3215
+ ".azure",
3216
+ ".docker",
3217
+ ".kube",
3218
+ ".secrets",
3219
+ "secrets"
3220
+ ]);
3221
+ var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([
3222
+ ".netrc",
3223
+ ".npmrc",
3224
+ ".pypirc",
3225
+ "auth.json",
3226
+ "google-services.json",
3227
+ "googleservice-info.plist"
3228
+ ]);
3200
3229
  var SENSITIVE_WRITE_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".circleci", ".buildkite"]);
3201
3230
  var SENSITIVE_WRITE_EXACT_FILES = /* @__PURE__ */ new Set([
3202
3231
  ".gitlab-ci.yml",
@@ -3212,6 +3241,7 @@ var SECRET_SUFFIXES = [
3212
3241
  ".pfx",
3213
3242
  ".jks",
3214
3243
  ".keystore",
3244
+ ".kdbx",
3215
3245
  ".tfvars"
3216
3246
  ];
3217
3247
  var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
@@ -3245,7 +3275,6 @@ function isSecretPathLike(value) {
3245
3275
  for (const rawPart of parts) {
3246
3276
  const part = stripOuterQuotes(rawPart.trim().toLowerCase());
3247
3277
  if (!part || part === "." || part === "**") continue;
3248
- if (ENV_EXAMPLES.has(part)) continue;
3249
3278
  if (SECRET_DIRECTORIES.has(part)) return true;
3250
3279
  if (SECRET_EXACT_FILES.has(part)) return true;
3251
3280
  if (part === ".env" || part === ".envrc" || part.startsWith(".env.") || part.startsWith(".env*")) {
@@ -3256,6 +3285,7 @@ function isSecretPathLike(value) {
3256
3285
  }
3257
3286
  if (part.includes("credentials")) return true;
3258
3287
  if (part.startsWith("secrets.")) return true;
3288
+ if (part.startsWith("service-account") && part.endsWith(".json")) return true;
3259
3289
  if (SECRET_SUFFIXES.some((suffix) => part.endsWith(suffix))) return true;
3260
3290
  }
3261
3291
  return false;
@@ -3291,6 +3321,9 @@ function evaluateWrite(input, context) {
3291
3321
  if (!isPathInsideRepo(pathValue, context.repoPath)) {
3292
3322
  return { behavior: "deny", message: WRITE_OUTSIDE_REPO_DENIAL };
3293
3323
  }
3324
+ if (isSecretPathLike(pathValue)) {
3325
+ return { behavior: "deny", message: SECRET_MATERIAL_DENIAL };
3326
+ }
3294
3327
  if (isSensitiveWritePath(pathValue, context.repoPath)) {
3295
3328
  return { behavior: "deny", message: SENSITIVE_WRITE_DENIAL };
3296
3329
  }
@@ -4349,6 +4382,10 @@ function short(s, max = 60) {
4349
4382
  // src/engine/providers/claude.ts
4350
4383
  var READ_ONLY_TOOLS2 = ["Read", "Glob", "Grep"];
4351
4384
  var EDIT_TOOLS = ["Read", "Edit", "Write", "Bash", "Glob", "Grep"];
4385
+ var FAST_MODE_MODELS = /* @__PURE__ */ new Set(["claude-opus-5", "claude-opus-4-8"]);
4386
+ function supportsClaudeFastMode(model) {
4387
+ return FAST_MODE_MODELS.has(model);
4388
+ }
4352
4389
  function createClaudeProvider(config, session) {
4353
4390
  return {
4354
4391
  name: "claude",
@@ -4384,6 +4421,7 @@ function createClaudeProvider(config, session) {
4384
4421
  hooks: buildToolPolicyHooks(invocation.cwd, trustedTools),
4385
4422
  canUseTool: createToolPermissionCallback(invocation.cwd, trustedTools),
4386
4423
  maxTurns: roleConfig.maxTurns,
4424
+ ...supportsClaudeFastMode(roleConfig.model) ? { settings: { fastMode: true } } : {},
4387
4425
  ...invocation.resumeSessionId ? { resume: invocation.resumeSessionId } : {},
4388
4426
  settingSources: []
4389
4427
  }
@@ -4444,6 +4482,18 @@ function jailedPath(cwd, candidate) {
4444
4482
  }
4445
4483
  return resolved;
4446
4484
  }
4485
+ function guardedPath(cwd, toolName, candidate) {
4486
+ const path = jailedPath(cwd, candidate);
4487
+ const decision = evaluateToolUse(
4488
+ toolName,
4489
+ toolName === "Glob" ? { path: candidate, pattern: "*" } : { file_path: candidate },
4490
+ { repoPath: cwd }
4491
+ );
4492
+ if (decision.behavior === "deny") {
4493
+ throw new Error(decision.message);
4494
+ }
4495
+ return path;
4496
+ }
4447
4497
  function fileTools(cwd, allowWrite) {
4448
4498
  const tools = [
4449
4499
  {
@@ -4456,7 +4506,8 @@ function fileTools(cwd, allowWrite) {
4456
4506
  required: ["path"]
4457
4507
  },
4458
4508
  handler: async (input) => {
4459
- const content = readFileSync3(jailedPath(cwd, String(input.path ?? "")), "utf8");
4509
+ const path = guardedPath(cwd, "Read", String(input.path ?? ""));
4510
+ const content = readFileSync3(path, "utf8");
4460
4511
  return { content: content.slice(0, 4e4) };
4461
4512
  }
4462
4513
  },
@@ -4470,8 +4521,8 @@ function fileTools(cwd, allowWrite) {
4470
4521
  required: ["path"]
4471
4522
  },
4472
4523
  handler: async (input) => {
4473
- const target9 = jailedPath(cwd, String(input.path ?? "."));
4474
- const entries = readdirSync(target9).map((entry) => {
4524
+ const target9 = guardedPath(cwd, "Glob", String(input.path ?? "."));
4525
+ const entries = readdirSync(target9).filter((entry) => !isSecretPathLike(entry)).map((entry) => {
4475
4526
  const kind = statSync(join8(target9, entry)).isDirectory() ? "dir" : "file";
4476
4527
  return `${kind}:${entry}`;
4477
4528
  });
@@ -4491,7 +4542,8 @@ function fileTools(cwd, allowWrite) {
4491
4542
  required: ["path", "content"]
4492
4543
  },
4493
4544
  handler: async (input) => {
4494
- writeFileSync2(jailedPath(cwd, String(input.path ?? "")), String(input.content ?? ""));
4545
+ const path = guardedPath(cwd, "Write", String(input.path ?? ""));
4546
+ writeFileSync2(path, String(input.content ?? ""));
4495
4547
  return { ok: true };
4496
4548
  }
4497
4549
  },
@@ -4509,7 +4561,7 @@ function fileTools(cwd, allowWrite) {
4509
4561
  required: ["path", "oldText", "newText"]
4510
4562
  },
4511
4563
  handler: async (input) => {
4512
- const path = jailedPath(cwd, String(input.path ?? ""));
4564
+ const path = guardedPath(cwd, "Edit", String(input.path ?? ""));
4513
4565
  const content = readFileSync3(path, "utf8");
4514
4566
  const oldText = String(input.oldText ?? "");
4515
4567
  if (!content.includes(oldText)) {
@@ -4679,7 +4731,7 @@ var MINUTE = 6e4;
4679
4731
  function engineModelConfig(config) {
4680
4732
  const model = (role, fallback) => process.env[`WHISPERR_WIZARD_ENGINE_${role.toUpperCase()}_MODEL`]?.trim() || fallback;
4681
4733
  return {
4682
- survey: { model: model("survey", "claude-sonnet-5"), effort: "medium", maxTurns: 40, maxMs: 12 * MINUTE },
4734
+ survey: { model: model("survey", config.model), effort: "medium", maxTurns: 40, maxMs: 12 * MINUTE },
4683
4735
  design: { model: model("design", config.plannerModel), effort: "high", maxTurns: 40, maxMs: 15 * MINUTE },
4684
4736
  edit: { model: model("edit", config.model), effort: config.effort, maxTurns: 50, maxMs: 15 * MINUTE },
4685
4737
  review: { model: model("review", config.model), effort: "medium", maxTurns: 15, maxMs: 8 * MINUTE }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whisperr/wizard",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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",