ai-whisper 0.14.0 → 0.15.0

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 (41) hide show
  1. package/dist/bin/companion-agent.js +27 -10
  2. package/dist/bin/whisper.js +117 -29
  3. package/package.json +4 -4
  4. package/skills/ai-whisper-bugfix/SKILL.md +47 -149
  5. package/skills/ai-whisper-bugfix/evals/evals.json +36 -0
  6. package/skills/ai-whisper-bugfix/evals/files/bug-report.md +10 -0
  7. package/skills/ai-whisper-bugfix/evals/triggers.json +21 -0
  8. package/skills/ai-whisper-code-review/SKILL.md +93 -33
  9. package/skills/ai-whisper-code-review/evals/evals.json +62 -0
  10. package/skills/ai-whisper-code-review/evals/triggers.json +25 -0
  11. package/skills/ai-whisper-deliberation/SKILL.md +52 -147
  12. package/skills/ai-whisper-deliberation/evals/evals.json +36 -0
  13. package/skills/ai-whisper-deliberation/evals/files/seed-idea.md +2 -0
  14. package/skills/ai-whisper-deliberation/evals/triggers.json +21 -0
  15. package/skills/ai-whisper-deliberation-craft/SKILL.md +117 -16
  16. package/skills/ai-whisper-deliberation-craft/evals/evals.json +54 -0
  17. package/skills/ai-whisper-deliberation-craft/evals/triggers.json +25 -0
  18. package/skills/ai-whisper-plan-execution/SKILL.md +135 -31
  19. package/skills/ai-whisper-plan-execution/evals/evals.json +50 -0
  20. package/skills/ai-whisper-plan-execution/evals/files/rename-target.ts +5 -0
  21. package/skills/ai-whisper-plan-execution/evals/triggers.json +25 -0
  22. package/skills/ai-whisper-quick-task/SKILL.md +160 -53
  23. package/skills/ai-whisper-quick-task/evals/evals.json +63 -0
  24. package/skills/ai-whisper-quick-task/evals/files/notes.md +11 -0
  25. package/skills/ai-whisper-quick-task/evals/files/pagination.ts +3 -0
  26. package/skills/ai-whisper-quick-task/evals/triggers.json +25 -0
  27. package/skills/ai-whisper-ralph/SKILL.md +48 -149
  28. package/skills/ai-whisper-ralph/evals/evals.json +36 -0
  29. package/skills/ai-whisper-ralph/evals/files/GOAL.md +8 -0
  30. package/skills/ai-whisper-ralph/evals/triggers.json +21 -0
  31. package/skills/ai-whisper-sdd/SKILL.md +46 -141
  32. package/skills/ai-whisper-sdd/evals/evals.json +36 -0
  33. package/skills/ai-whisper-sdd/evals/files/approved-spec.md +9 -0
  34. package/skills/ai-whisper-sdd/evals/triggers.json +21 -0
  35. package/skills/ai-whisper-workflow/SKILL.md +302 -0
  36. package/skills/ai-whisper-workflow/evals/evals.json +47 -0
  37. package/skills/ai-whisper-workflow/evals/files/GOAL.md +8 -0
  38. package/skills/ai-whisper-workflow/evals/files/cache-design-spec.md +9 -0
  39. package/skills/ai-whisper-workflow/evals/files/empty-idea.md +0 -0
  40. package/skills/ai-whisper-workflow/evals/triggers.json +25 -0
  41. package/skills/ai-whisper-workflow/references/workflow-types.md +29 -0
@@ -8230,12 +8230,19 @@ var TurnGate = class {
8230
8230
  get held() {
8231
8231
  return this.heldCount > 0;
8232
8232
  }
8233
- /** Resolves with a release function once every earlier acquirer released. */
8233
+ /** Resolves with a release function once every earlier acquirer released.
8234
+ * The release is idempotent: calling it twice must not double-decrement
8235
+ * `heldCount` (a corrupted count would report `held === false` while a
8236
+ * later acquirer still holds the gate). */
8234
8237
  acquire() {
8235
8238
  this.heldCount += 1;
8236
8239
  let release;
8237
8240
  const held = new Promise((r) => {
8241
+ let released = false;
8238
8242
  release = () => {
8243
+ if (released)
8244
+ return;
8245
+ released = true;
8239
8246
  this.heldCount -= 1;
8240
8247
  r();
8241
8248
  };
@@ -9283,17 +9290,20 @@ function decidePolicy(name, configPolicy, mode) {
9283
9290
  }
9284
9291
 
9285
9292
  // ../../node_modules/.pnpm/@ai-ezio+mcp-host@file+..+ai-ezio+packages+mcp-host_zod@3.25.76/node_modules/@ai-ezio/mcp-host/dist/host.js
9293
+ var DEFAULT_INJECT_ARGS = ["worktreePath", "path"];
9286
9294
  var McpHost = class {
9287
9295
  opts;
9288
9296
  id = "mcp";
9289
9297
  routes = new RouteMap();
9290
9298
  // reassignable for idempotent init()
9291
9299
  clients = /* @__PURE__ */ new Map();
9300
+ serversByName;
9292
9301
  /** Namespaced name → def (for schema-aware cwd injection). */
9293
9302
  defsByName = /* @__PURE__ */ new Map();
9294
9303
  advertised = [];
9295
9304
  constructor(opts) {
9296
9305
  this.opts = opts;
9306
+ this.serversByName = new Map(opts.servers.map((s) => [s.name, s]));
9297
9307
  }
9298
9308
  /** Connect servers + list tools, building the route map and advertised defs.
9299
9309
  * Idempotent: tears down any prior connection state before reconnecting, so a
@@ -9375,14 +9385,17 @@ var McpHost = class {
9375
9385
  const injected = this.injectCwd(name, args);
9376
9386
  return withTimeout(client.callTool(route.tool, injected), this.opts.callTimeoutMs ?? 6e4, `host call ${name}`);
9377
9387
  }
9378
- /** Force repo-root args (worktreePath/path) to the session cwd. Overrides any
9379
- * model-supplied value (drift-proof) AND fills when omitted — but ONLY for args
9380
- * the tool's own schema declares, so we never add a property the server would
9381
- * reject and never clobber an unrelated `path`. */
9388
+ /** Force repo-root args (worktreePath/path by default) to the session cwd.
9389
+ * Overrides any model-supplied value (drift-proof) AND fills when omitted —
9390
+ * but ONLY for args the tool's own schema declares, so we never add a
9391
+ * property the server would reject. The list resolves per server:
9392
+ * `ServerConfig.injectArgs` ?? the host-level option ?? the ai-* default
9393
+ * ([] at either level disables injection). */
9382
9394
  injectCwd(name, args) {
9383
9395
  const schema = this.defsByName.get(name)?.parametersSchema;
9384
9396
  const props = schema?.properties ?? {};
9385
- const injectArgs = this.opts.injectArgs ?? ["worktreePath", "path"];
9397
+ const server = this.routes.resolve(name)?.server;
9398
+ const injectArgs = (server !== void 0 ? this.serversByName.get(server)?.injectArgs : void 0) ?? this.opts.injectArgs ?? DEFAULT_INJECT_ARGS;
9386
9399
  const out = { ...args };
9387
9400
  for (const arg of injectArgs)
9388
9401
  if (arg in props)
@@ -9430,12 +9443,14 @@ function parseConfig(text) {
9430
9443
  name,
9431
9444
  command: s.command,
9432
9445
  args: s.args ?? [],
9433
- env: s.env
9446
+ env: s.env,
9447
+ injectArgs: s.injectArgs
9434
9448
  }));
9435
9449
  return {
9436
9450
  servers,
9437
9451
  toolPolicy: raw.toolPolicy ?? {},
9438
- hostPrivateTools: raw.hostPrivateTools ?? []
9452
+ hostPrivateTools: raw.hostPrivateTools ?? [],
9453
+ injectArgs: raw.injectArgs
9439
9454
  };
9440
9455
  }
9441
9456
  function loadConfig2(env = process.env) {
@@ -9454,6 +9469,7 @@ function createMcpHost(cfg, opts) {
9454
9469
  servers: cfg.servers,
9455
9470
  toolPolicy: cfg.toolPolicy,
9456
9471
  hostPrivateTools: [.../* @__PURE__ */ new Set([...DEFAULT_HOST_PRIVATE, ...cfg.hostPrivateTools])],
9472
+ injectArgs: cfg.injectArgs,
9457
9473
  confirm: opts.confirm,
9458
9474
  connect: opts.connect
9459
9475
  });
@@ -9637,9 +9653,10 @@ var SubagentHost = class {
9637
9653
  reply(callId, `subagent failed: ${e.message}`, "error");
9638
9654
  }
9639
9655
  }
9640
- async stop() {
9656
+ stop() {
9641
9657
  this.inFlight?.cancel();
9642
9658
  this.inFlight = void 0;
9659
+ return Promise.resolve();
9643
9660
  }
9644
9661
  };
9645
9662
 
@@ -10703,7 +10720,7 @@ async function runResumeFlow(deps) {
10703
10720
  let chosen;
10704
10721
  await deps.runOverlay(async (io) => {
10705
10722
  chosen = await runResumePicker({
10706
- listSessions: async () => JSON.stringify(rows),
10723
+ listSessions: () => Promise.resolve(JSON.stringify(rows)),
10707
10724
  // already parsed+filtered; re-serialize for the picker
10708
10725
  keys: io.keys,
10709
10726
  write: io.write,
@@ -11668,12 +11668,19 @@ var TurnGate = class {
11668
11668
  get held() {
11669
11669
  return this.heldCount > 0;
11670
11670
  }
11671
- /** Resolves with a release function once every earlier acquirer released. */
11671
+ /** Resolves with a release function once every earlier acquirer released.
11672
+ * The release is idempotent: calling it twice must not double-decrement
11673
+ * `heldCount` (a corrupted count would report `held === false` while a
11674
+ * later acquirer still holds the gate). */
11672
11675
  acquire() {
11673
11676
  this.heldCount += 1;
11674
11677
  let release;
11675
11678
  const held = new Promise((r) => {
11679
+ let released = false;
11676
11680
  release = () => {
11681
+ if (released)
11682
+ return;
11683
+ released = true;
11677
11684
  this.heldCount -= 1;
11678
11685
  r();
11679
11686
  };
@@ -12722,17 +12729,20 @@ function decidePolicy(name, configPolicy, mode) {
12722
12729
  }
12723
12730
 
12724
12731
  // ../../node_modules/.pnpm/@ai-ezio+mcp-host@file+..+ai-ezio+packages+mcp-host_zod@3.25.76/node_modules/@ai-ezio/mcp-host/dist/host.js
12732
+ var DEFAULT_INJECT_ARGS = ["worktreePath", "path"];
12725
12733
  var McpHost = class {
12726
12734
  opts;
12727
12735
  id = "mcp";
12728
12736
  routes = new RouteMap();
12729
12737
  // reassignable for idempotent init()
12730
12738
  clients = /* @__PURE__ */ new Map();
12739
+ serversByName;
12731
12740
  /** Namespaced name → def (for schema-aware cwd injection). */
12732
12741
  defsByName = /* @__PURE__ */ new Map();
12733
12742
  advertised = [];
12734
12743
  constructor(opts) {
12735
12744
  this.opts = opts;
12745
+ this.serversByName = new Map(opts.servers.map((s) => [s.name, s]));
12736
12746
  }
12737
12747
  /** Connect servers + list tools, building the route map and advertised defs.
12738
12748
  * Idempotent: tears down any prior connection state before reconnecting, so a
@@ -12814,14 +12824,17 @@ var McpHost = class {
12814
12824
  const injected = this.injectCwd(name, args);
12815
12825
  return withTimeout(client.callTool(route.tool, injected), this.opts.callTimeoutMs ?? 6e4, `host call ${name}`);
12816
12826
  }
12817
- /** Force repo-root args (worktreePath/path) to the session cwd. Overrides any
12818
- * model-supplied value (drift-proof) AND fills when omitted — but ONLY for args
12819
- * the tool's own schema declares, so we never add a property the server would
12820
- * reject and never clobber an unrelated `path`. */
12827
+ /** Force repo-root args (worktreePath/path by default) to the session cwd.
12828
+ * Overrides any model-supplied value (drift-proof) AND fills when omitted —
12829
+ * but ONLY for args the tool's own schema declares, so we never add a
12830
+ * property the server would reject. The list resolves per server:
12831
+ * `ServerConfig.injectArgs` ?? the host-level option ?? the ai-* default
12832
+ * ([] at either level disables injection). */
12821
12833
  injectCwd(name, args) {
12822
12834
  const schema = this.defsByName.get(name)?.parametersSchema;
12823
12835
  const props = schema?.properties ?? {};
12824
- const injectArgs = this.opts.injectArgs ?? ["worktreePath", "path"];
12836
+ const server = this.routes.resolve(name)?.server;
12837
+ const injectArgs = (server !== void 0 ? this.serversByName.get(server)?.injectArgs : void 0) ?? this.opts.injectArgs ?? DEFAULT_INJECT_ARGS;
12825
12838
  const out = { ...args };
12826
12839
  for (const arg of injectArgs)
12827
12840
  if (arg in props)
@@ -12869,12 +12882,14 @@ function parseConfig(text) {
12869
12882
  name,
12870
12883
  command: s.command,
12871
12884
  args: s.args ?? [],
12872
- env: s.env
12885
+ env: s.env,
12886
+ injectArgs: s.injectArgs
12873
12887
  }));
12874
12888
  return {
12875
12889
  servers,
12876
12890
  toolPolicy: raw.toolPolicy ?? {},
12877
- hostPrivateTools: raw.hostPrivateTools ?? []
12891
+ hostPrivateTools: raw.hostPrivateTools ?? [],
12892
+ injectArgs: raw.injectArgs
12878
12893
  };
12879
12894
  }
12880
12895
  function loadConfig2(env = process.env) {
@@ -12893,6 +12908,7 @@ function createMcpHost(cfg, opts) {
12893
12908
  servers: cfg.servers,
12894
12909
  toolPolicy: cfg.toolPolicy,
12895
12910
  hostPrivateTools: [.../* @__PURE__ */ new Set([...DEFAULT_HOST_PRIVATE, ...cfg.hostPrivateTools])],
12911
+ injectArgs: cfg.injectArgs,
12896
12912
  confirm: opts.confirm,
12897
12913
  connect: opts.connect
12898
12914
  });
@@ -13076,9 +13092,10 @@ var SubagentHost = class {
13076
13092
  reply(callId, `subagent failed: ${e.message}`, "error");
13077
13093
  }
13078
13094
  }
13079
- async stop() {
13095
+ stop() {
13080
13096
  this.inFlight?.cancel();
13081
13097
  this.inFlight = void 0;
13098
+ return Promise.resolve();
13082
13099
  }
13083
13100
  };
13084
13101
 
@@ -14142,7 +14159,7 @@ async function runResumeFlow(deps) {
14142
14159
  let chosen;
14143
14160
  await deps.runOverlay(async (io) => {
14144
14161
  chosen = await runResumePicker({
14145
- listSessions: async () => JSON.stringify(rows),
14162
+ listSessions: () => Promise.resolve(JSON.stringify(rows)),
14146
14163
  // already parsed+filtered; re-serialize for the picker
14147
14164
  keys: io.keys,
14148
14165
  write: io.write,
@@ -14958,9 +14975,9 @@ function compareSemver(a, b) {
14958
14975
  // src/generated/ezio-provenance.ts
14959
14976
  var EZIO_PROVENANCE = {
14960
14977
  ezioCliVersion: "0.4.0",
14961
- ezioGitSha: "77a3b48",
14962
- builtAt: "2026-07-06T01:57:14.462Z",
14963
- whisperVersion: "0.14.0"
14978
+ ezioGitSha: "7dd7e90",
14979
+ builtAt: "2026-07-12T18:26:06.860Z",
14980
+ whisperVersion: "0.15.0"
14964
14981
  };
14965
14982
 
14966
14983
  // src/ezio-provenance-types.ts
@@ -16582,8 +16599,8 @@ function applyFreshnessFloor(refs, promptDeliveredAtMs, clockSkewMs) {
16582
16599
  const floor = promptDeliveredAtMs - clockSkewMs;
16583
16600
  return refs.filter((ref) => ref.mtimeMs >= floor);
16584
16601
  }
16585
- function pickActiveTranscript(refs, turnText, readFile, limit) {
16586
- const top = refs.slice(0, limit).map((ref) => ({ path: ref.path, analysis: analyzeTurn(readFile(ref.path)) }));
16602
+ function pickActiveTranscript(refs, turnText, readFile2, limit) {
16603
+ const top = refs.slice(0, limit).map((ref) => ({ path: ref.path, analysis: analyzeTurn(readFile2(ref.path)) }));
16587
16604
  if (top.length === 0) return null;
16588
16605
  const turn = turnText.trim();
16589
16606
  if (turn.length > 0) {
@@ -16607,7 +16624,7 @@ function sha256(text) {
16607
16624
  }
16608
16625
  async function captureCursorHandback(input) {
16609
16626
  const listTranscripts = input.listTranscripts ?? (() => listCursorTranscripts());
16610
- const readFile = input.readFile ?? ((path4) => readFileSync10(path4, "utf8"));
16627
+ const readFile2 = input.readFile ?? ((path4) => readFileSync10(path4, "utf8"));
16611
16628
  const sleep3 = input.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16612
16629
  const budgetMs = input.budgetMs ?? 15e3;
16613
16630
  const pollIntervalMs = input.pollIntervalMs ?? 500;
@@ -16651,11 +16668,11 @@ async function captureCursorHandback(input) {
16651
16668
  let pick;
16652
16669
  try {
16653
16670
  if (expectedPrompt.length > 0) {
16654
- const result = pickByPrompt({ refs, expectedPrompt, readFile, limit });
16671
+ const result = pickByPrompt({ refs, expectedPrompt, readFile: readFile2, limit });
16655
16672
  matchedCount = result.matchedCount;
16656
16673
  pick = result.pick;
16657
16674
  } else {
16658
- pick = pickActiveTranscript(refs, input.turnText, readFile, limit);
16675
+ pick = pickActiveTranscript(refs, input.turnText, readFile2, limit);
16659
16676
  }
16660
16677
  } catch {
16661
16678
  pick = null;
@@ -20203,13 +20220,51 @@ function runWorkflowStats(deps) {
20203
20220
  }
20204
20221
 
20205
20222
  // src/commands/skill/install.ts
20206
- import { cp, mkdir, readdir, stat } from "node:fs/promises";
20223
+ import { cp, mkdir, readdir, readFile, stat } from "node:fs/promises";
20207
20224
  import path3 from "node:path";
20208
20225
  import { fileURLToPath as fileURLToPath6 } from "node:url";
20226
+
20227
+ // src/commands/skill/version.ts
20228
+ var VALID_VERSION_RE = /^\d+(\.\d+){0,2}$/;
20229
+ function parseSkillVersion(content) {
20230
+ const lines = content.split(/\r?\n/);
20231
+ if (lines[0]?.trim() !== "---") return null;
20232
+ for (let i = 1; i < lines.length; i++) {
20233
+ const line = lines[i] ?? "";
20234
+ if (line.trim() === "---") return null;
20235
+ const m = /^version:\s*(.+?)\s*$/.exec(line);
20236
+ if (m) {
20237
+ const raw = (m[1] ?? "").replace(/^["']/, "").replace(/["']$/, "");
20238
+ return VALID_VERSION_RE.test(raw) ? raw : null;
20239
+ }
20240
+ }
20241
+ return null;
20242
+ }
20243
+ function compareSemver2(a, b) {
20244
+ const pa = a.split(".");
20245
+ const pb = b.split(".");
20246
+ for (let i = 0; i < 3; i++) {
20247
+ const da = Number.parseInt(pa[i] ?? "0", 10);
20248
+ const db = Number.parseInt(pb[i] ?? "0", 10);
20249
+ if (da !== db) return da < db ? -1 : 1;
20250
+ }
20251
+ return 0;
20252
+ }
20253
+
20254
+ // src/commands/skill/install.ts
20209
20255
  function defaultBundledSkillsDir() {
20210
20256
  const here = path3.dirname(fileURLToPath6(import.meta.url));
20211
20257
  return path3.resolve(here, "..", "..", "skills");
20212
20258
  }
20259
+ async function readSkillVersion(skillMdPath) {
20260
+ let content;
20261
+ try {
20262
+ content = await readFile(skillMdPath, "utf8");
20263
+ } catch {
20264
+ return null;
20265
+ }
20266
+ return parseSkillVersion(content);
20267
+ }
20213
20268
  function homeForTarget(target, fakeHome) {
20214
20269
  const home = fakeHome ?? process.env.HOME ?? process.env.USERPROFILE ?? "";
20215
20270
  if (!home) throw new Error("Could not determine $HOME for skill install destination");
@@ -20251,6 +20306,7 @@ async function runSkillInstall(input) {
20251
20306
  throw new Error(`No skills found in ${bundledDir}.`);
20252
20307
  }
20253
20308
  const targets = input.target === "all" ? ["claude", "codex", "cursor", "ezio", "agy"] : [input.target];
20309
+ const results = [];
20254
20310
  const installedAt = [];
20255
20311
  for (const t of targets) {
20256
20312
  const destBase = homeForTarget(t, input.fakeHome);
@@ -20265,16 +20321,33 @@ async function runSkillInstall(input) {
20265
20321
  } catch (err) {
20266
20322
  if (err.code !== "ENOENT") throw err;
20267
20323
  }
20268
- if (destExists && !input.force) {
20269
- throw new Error(
20270
- `Skill destination already exists at ${dest}. Re-run with --force to overwrite.`
20271
- );
20324
+ const bundledVersion = await readSkillVersion(path3.join(src, "SKILL.md"));
20325
+ const installedVersion = destExists ? await readSkillVersion(path3.join(dest, "SKILL.md")) : null;
20326
+ let action;
20327
+ if (!destExists || installedVersion === null) {
20328
+ action = "installed";
20329
+ } else {
20330
+ const cmp = compareSemver2(bundledVersion ?? "0.0.0", installedVersion);
20331
+ action = cmp > 0 ? "installed" : cmp === 0 ? "up-to-date" : "skipped-newer";
20332
+ }
20333
+ const forced = action !== "installed" && input.force === true;
20334
+ if (forced) action = "installed";
20335
+ results.push({
20336
+ skill,
20337
+ target: t,
20338
+ dest,
20339
+ action,
20340
+ bundledVersion,
20341
+ installedVersion,
20342
+ ...forced ? { forced: true } : {}
20343
+ });
20344
+ if (action === "installed") {
20345
+ await cp(src, dest, { recursive: true, force: true });
20346
+ installedAt.push(path3.join(dest, "SKILL.md"));
20272
20347
  }
20273
- await cp(src, dest, { recursive: true, force: true });
20274
- installedAt.push(path3.join(dest, "SKILL.md"));
20275
20348
  }
20276
20349
  }
20277
- return { installedAt };
20350
+ return { results, installedAt };
20278
20351
  }
20279
20352
 
20280
20353
  // src/commands/env/report.ts
@@ -20837,13 +20910,28 @@ Not attached (stdin/stdout is not a TTY). To view the tmux session, run:
20837
20910
  "Install the bundled ai-whisper skills into your agent skill directories"
20838
20911
  ).addOption(
20839
20912
  new Option("--target <target>", "Agent install target").choices(["claude", "codex", "cursor", "ezio", "agy", "all"]).default("all")
20840
- ).option("--force", "Overwrite existing skill destinations").action(async (opts) => {
20913
+ ).option(
20914
+ "--force",
20915
+ "Reinstall even when the installed copy is same-version or newer (downgrade)"
20916
+ ).action(async (opts) => {
20841
20917
  const result = await runSkillInstall({
20842
20918
  target: opts.target,
20843
20919
  ...opts.force ? { force: true } : {}
20844
20920
  });
20845
- for (const p of result.installedAt) {
20846
- console.log(`Installed: ${p}`);
20921
+ for (const e of result.results) {
20922
+ if (e.action === "installed" && e.forced) {
20923
+ console.log(
20924
+ `Installed (forced): ${e.skill} \u2192 ${e.dest} (replaced ${e.installedVersion ?? "unversioned"})`
20925
+ );
20926
+ } else if (e.action === "installed") {
20927
+ console.log(`Installed: ${e.skill} \u2192 ${e.dest}`);
20928
+ } else if (e.action === "up-to-date") {
20929
+ console.log(`Up to date: ${e.skill} @ ${e.installedVersion} \u2192 ${e.dest}`);
20930
+ } else {
20931
+ console.log(
20932
+ `Skipped (newer installed): ${e.skill} \u2014 installed ${e.installedVersion} > bundled ${e.bundledVersion ?? "unversioned"}; use --force to downgrade \u2192 ${e.dest}`
20933
+ );
20934
+ }
20847
20935
  }
20848
20936
  });
20849
20937
  cli.command("env").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-whisper",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Terminal-first relay for paired AI coding agents (Claude + Codex), driven by structured workflows.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -52,13 +52,13 @@
52
52
  "@types/better-sqlite3": "^7.6.12",
53
53
  "@types/react": "^19.2.14",
54
54
  "ink-testing-library": "^4.0.0",
55
- "@ai-whisper/adapter-claude": "0.0.0",
56
55
  "@ai-whisper/adapter-ai-ezio": "0.0.0",
56
+ "@ai-whisper/adapter-antigravity": "0.0.0",
57
+ "@ai-whisper/adapter-claude": "0.0.0",
57
58
  "@ai-whisper/adapter-codex": "0.0.0",
58
59
  "@ai-whisper/adapter-cursor": "0.0.0",
59
- "@ai-whisper/broker": "0.0.0",
60
- "@ai-whisper/adapter-antigravity": "0.0.0",
61
60
  "@ai-whisper/companion-core": "0.0.0",
61
+ "@ai-whisper/broker": "0.0.0",
62
62
  "@ai-whisper/shared": "0.0.0"
63
63
  },
64
64
  "files": [
@@ -1,172 +1,70 @@
1
1
  ---
2
2
  name: ai-whisper-bugfix
3
- description: Kick off the complex-bug-fixing workflow on a given bug report. Use when the user says things like "run bugfix on <path>", "fix this bug report <path>", "kick off complex-bug-fixing with <path>", "/aiw-bugfix <path>", "$aiw-bugfix <path>", or otherwise asks to start the complex-bug-fixing workflow on a specific bug report.
3
+ description: Use when the user reaches for the ai-whisper-bugfix skill by name or asks to run bugfix / complex-bug-fixing on a bug report file a name-preserving alias that applies the ai-whisper-workflow skill with workflow type complex-bug-fixing.
4
+ version: 0.1.0
4
5
  ---
5
6
 
6
7
  # ai-whisper-bugfix
7
8
 
8
- Kick off the ai-whisper complex-bug-fixing workflow on a specific bug report. This skill is fire-and-forget: it verifies the collab is ready, runs `whisper workflow start`, and exits. **Do NOT continue polling or narrating after kickoff** — continuous activity from the calling agent keeps it busy, which blocks the broker's idle detection and stalls the workflow. The dashboard (`whisper collab dashboard`) is the inspection surface during the run.
9
+ ## Intent
9
10
 
10
- ## When to invoke
11
+ A name-preserving alias. The pre-collapse kickoff skills became the
12
+ `ai-whisper-workflow` skill; this one keeps the `ai-whisper-bugfix` picker
13
+ name alive and delegates to that skill with workflow type
14
+ `complex-bug-fixing`. No behavior of its own.
11
15
 
12
- Match phrases like:
13
- - *"run bugfix on docs/bug.md"* / *"fix this bug report @docs/bug.md"*
14
- - *"kick off complex-bug-fixing with docs/bug.md"*
15
- - *"/aiw-bugfix docs/bug.md"* (Claude picker form)
16
- - *"$aiw-bugfix docs/bug.md"* (Codex picker form)
16
+ ## Inputs
17
17
 
18
- If the user references a bug report ambiguously (e.g., "run bugfix on the bug we just discussed"), ASK them for the path ONCE before proceeding. Do not guess.
18
+ - The bug report file path (the workflow's `--spec` input): symptoms,
19
+ reproduction steps, expected-vs-actual — not a spec or open-ended goal. An
20
+ `@`-prefixed form is accepted; strip the `@` before resolving.
19
21
 
20
- ## Steps
22
+ ## Preconditions
21
23
 
22
- ### 1. Resolve the bug-report path
24
+ - The `ai-whisper-workflow` skill is installed alongside this one — it is the
25
+ delegation target and owns the full procedure.
26
+ - Its preconditions apply unchanged: `whisper` CLI on PATH, a ready collab
27
+ with two bound agents, a readable bug report.
23
28
 
24
- The user names a path. If it begins with `@`, strip the `@`. Resolve to an absolute path. Verify it's a readable file via the Read tool. If not readable:
29
+ ## Procedure
25
30
 
26
- > Bug report `<path>` is not readable. Check the path and try again.
31
+ 1. Read the `ai-whisper-workflow` skill's SKILL.md it is installed in the
32
+ adjacent directory of the same skills root.
33
+ 2. Apply it with workflow type `complex-bug-fixing` and the given bug-report
34
+ path — vetting, collab readiness, kickoff, and the one-line report all
35
+ follow that skill verbatim.
36
+ 3. Only if the delegation target is not installed, use the inline fallback:
37
+ vet the bug-report path, gate on `whisper collab status --json`, then run:
27
38
 
28
- The file is a **bug report** (markdown or plain text): symptoms, reproduction steps, and expected-vs-actual behavior — not a spec or open-ended goal. A bug report implies a human (dev or QA) already observed and reproduced the bug, so the workflow's implementer is expected to reproduce it too, not theorize from reading code. The better the reproduction in the report, the faster the diagnosis converges. This framing is guidance for preparing the bug report before kickoff; it is not runtime output.
39
+ ```bash
40
+ whisper workflow start --type=complex-bug-fixing --spec=<resolved-absolute-path>
41
+ ```
29
42
 
30
- ### 2. Verify collab readiness
43
+ Report exactly one line — Workflow `<workflowId>` started. Track progress
44
+ with `whisper collab dashboard`. — then stop.
31
45
 
32
- Run:
46
+ ## Output
33
47
 
34
- ```bash
35
- whisper collab status --json
36
- ```
48
+ Identical to ai-whisper-workflow's output contract: exactly one
49
+ workflow-started line on success, or the matching gate error verbatim, and
50
+ nothing more after kickoff.
37
51
 
38
- Parse the JSON. The expected shape is:
52
+ ## Examples
39
53
 
40
- ```json
41
- {
42
- "collabId": "collab_xyz",
43
- "workspaceRoot": "/path",
44
- "status": "active",
45
- "daemon": { "host": "127.0.0.1", "port": 4311, "pid": 12345 },
46
- "agents": [
47
- { "agentType": "codex", "bindingState": "bound" | "pending_attach" | "unbound" | null },
48
- { "agentType": "claude", "bindingState": "bound" | "pending_attach" | "unbound" | null },
49
- { "agentType": "ezio", "bindingState": "bound" | "pending_attach" | "unbound" | null },
50
- { "agentType": "agy", "bindingState": "bound" | "pending_attach" | "unbound" | null },
51
- { "agentType": "cursor", "bindingState": "bound" | "pending_attach" | "unbound" | null }
52
- ],
53
- "recovery": { "state": "normal" | "recovery_required" | "recovered" },
54
- "evaluator": { "ready": true | false, "status": "ready" | "missing_anthropic_key" | "invalid_config" | "disabled" | "unknown" }
55
- }
56
- ```
54
+ Input: the user says "run bugfix on docs/reports/login-crash.md".
57
55
 
58
- Required for readiness:
59
- - `daemon !== null`
60
- - `status === "active"`
61
- - `recovery.state === "normal"`
62
- - **EXACTLY TWO agents bound** — among the supported agent types (`codex`, `claude`,
63
- `ezio`, `agy`, `cursor`), exactly two must have `bindingState === "bound"` (the
64
- implementer + reviewer pair). **`ezio`, `agy`, and `cursor` are replacement
65
- roles**: any of them stands in for `codex` or `claude`, so do NOT require
66
- `codex` and `claude` specifically — any pair of two distinct supported agents
67
- passes. (The displaced slots read `null`/`unbound` and that is expected when a
68
- replacement agent takes a seat.)
69
- - `evaluator.status` is NOT `"missing_anthropic_key"` or `"invalid_config"` (i.e., `ready`, `disabled`, and `unknown` all pass this gate; only the two true-misconfiguration statuses block)
56
+ The agent reads the ai-whisper-workflow skill and applies it with type
57
+ `complex-bug-fixing`; the report is readable, the collab is ready, and the
58
+ kickoff succeeds.
70
59
 
71
- If the JSON has `{ "error": "no_collab_for_cwd", ... }`:
60
+ Output: Workflow `wf_58c2de` started. Track progress with `whisper collab dashboard`.
72
61
 
73
- > No collab found in this workspace. Mount any **two** agents (e.g. `whisper collab mount ezio` in one terminal and `whisper collab mount codex` — or `claude` / `agy` / `cursor` — in another), then re-run this skill.
62
+ ## Anti-patterns
74
63
 
75
- If `recovery.state === "recovery_required"`:
76
-
77
- > The collab is in recovery_required state. Run `whisper collab recover`, then re-run this skill.
78
-
79
- If `recovery.state === "recovered"`:
80
-
81
- > The collab has been recovered and still needs reconnect. Run `whisper collab reconnect <agent>` for each bound agent, then re-run this skill.
82
-
83
- If FEWER than two agents are bound (count `bindingState === "bound"` across
84
- the supported agent types):
85
-
86
- > Only <N> agent(s) bound (<list bound agentTypes>). A workflow needs two — an
87
- > implementer and a reviewer. Mount another agent (`whisper collab mount <codex|claude|ezio|agy|cursor>`)
88
- > in a separate terminal, then re-run this skill. `ezio`, `agy`, or `cursor` may replace `codex` or `claude`.
89
-
90
- (Do NOT append permission flags — mount already spawns the agent in full-permission mode; passing `--dangerously-skip-permissions` / `--dangerously-bypass-approvals-and-sandbox` again can crash the agent on a duplicate-argument error.)
91
-
92
- If `evaluator.status === "missing_anthropic_key"` (i.e., `evaluator.ready === false` AND status is `missing_anthropic_key`):
93
-
94
- > The evaluator has no Anthropic API key. Create `~/.ai-whisper/auth.json` with `{ "ANTHROPIC_API_KEY": "sk-ant-..." }` (chmod 600), then restart the daemon (`whisper collab stop` and re-mount, or restart the broker), and re-run this skill. See the README "Evaluator configuration" section.
95
-
96
- If `evaluator.status === "invalid_config"` (i.e., `evaluator.ready === false` AND status is `invalid_config`):
97
-
98
- > The evaluator config is malformed. Fix the JSON in `~/.ai-whisper/auth.json` or `~/.ai-whisper/config.json`, then restart the daemon and re-run this skill. See the README "Evaluator configuration" section.
99
-
100
- If `evaluator.status === "disabled"`: this means the orchestrator is intentionally off — it is NOT a misconfiguration and does NOT block this skill gate. Proceed to step 3; `workflow start` will surface the orchestrator-disabled error itself.
101
-
102
- (Note: `evaluator.ready` is `false` for `missing_anthropic_key`, `invalid_config`, AND `disabled`; it is `true` only for `ready` and `unknown`. That's why this gate keys off `status` rather than `ready` — so `disabled` does not block the skill while the two true-misconfiguration statuses do.)
103
-
104
- ### 3. Kick off the workflow
105
-
106
- Run:
107
-
108
- ```bash
109
- whisper workflow start --type=complex-bug-fixing --spec=<resolved-absolute-path>
110
- ```
111
-
112
- (No `--implementer` / `--reviewer` needed — the agent triggering this skill becomes the implementer and the other agent the reviewer. Pass `--implementer <agent> --reviewer <agent>` only to override. `--spec` names the bug report.)
113
-
114
- Parse the workflowId from stdout (format: `Workflow started: <workflowId>`).
115
-
116
- ### 4. Report and exit
117
-
118
- Print exactly this one line — it is the **only** runtime output the skill emits after kickoff:
119
-
120
- > Workflow `<workflowId>` started. Track progress with `whisper collab dashboard`.
121
-
122
- Then stop. Do NOT poll `whisper workflow inspect`. Do NOT narrate. Do NOT print the "What it does" documentation below. The workflow runs in the broker driver; your job is done.
123
-
124
- ## What complex-bug-fixing does (static documentation — NEVER printed at runtime)
125
-
126
- This section is reference prose for the invoking agent's understanding. It is documentation, not a runtime step, and must NOT be emitted after kickoff (doing so would violate the exactly-one-line report/exit contract in step 4).
127
-
128
- Once kicked off, the workflow runs a fixed three-phase pipeline: **diagnosis → fix-and-verify → post-mortem**. In diagnosis, the implementer reproduces the bug themselves (a committed RED test is strongly preferred) and writes a diagnosis artifact (root cause + proposed fix + blast radius + residual risks); an adversarial reviewer independently reproduces it and refuses to open the gate until both agree the cause is proven and the fix is net-safe. In fix-and-verify, the implementer applies the approved fix, turns the reproduction GREEN, and verifies across the blast radius, while the reviewer runs an adversarial acceptance review including test-coverage adequacy. In post-mortem, the implementer records cause, fix, coverage gaps, residual risks, and lessons learned. The diagnosis and post-mortem are self-verification artifacts for the human after the run; they live in the gitignored run dir `.ai-whisper/bugfix/<workflowId>/` and are NOT committed — only the fix and the reproduction test land in the repo. Watch all of this on `whisper collab dashboard`; do not babysit it from chat.
129
-
130
- ## Why fire-and-forget
131
-
132
- The broker's relay handoff system uses **idle detection** to know when an agent is ready to receive the next handoff. If this skill polled the workflow's status every few seconds, the calling agent (you) would emit output continuously, the broker would never see you as idle, and the workflow's first handoff couldn't be delivered to you — the workflow stalls. Kick off and exit; observation belongs to the dashboard.
133
-
134
- ## Duo roleplay
135
-
136
- The mount may have assigned you a movie-duo character for this collab session — check the `AI_WHISPER_CHARACTER` / `AI_WHISPER_CHARACTER_ROLE` env vars, or the `[ai-whisper duo]` brief injected at session start, to find out. If so, staying in character is welcome — but **conversational prose only**: chat, status updates, banter with the operator or your teammate.
137
-
138
- Never let character flavor into code, commit messages, PR descriptions, file contents, or the diagnosis/post-mortem artifacts. The reviewer/evaluator protocol output (verdict labels, approve/findings/escalate) stays protocol-exact regardless of who you're playing.
139
-
140
- ## Resume / cancel
141
-
142
- If the user asks to resume a halted workflow, run:
143
-
144
- ```bash
145
- whisper workflow resume <workflowId>
146
- ```
147
-
148
- If they ask to cancel:
149
-
150
- ```bash
151
- whisper workflow cancel <workflowId>
152
- ```
153
-
154
- Same fire-and-forget shape: invoke, report one line, exit.
155
-
156
- ## Pausing the workflow (operator control)
157
-
158
- If the user interrupts you mid-workflow and asks to pause it (e.g. "pause the workflow, I need to fix X"):
159
-
160
- 1. Find the active workflow id: `whisper workflow list`.
161
- 2. Run `whisper workflow pause <workflowId>`.
162
- 3. Acknowledge and **stop working** — do not start the next change.
163
-
164
- The operator edits artifacts while paused, then resumes:
165
-
166
- ```bash
167
- whisper workflow resume <workflowId> --message "what I changed and why"
168
- ```
169
-
170
- On resume the agents receive a notice listing the changed files plus the operator note, and must re-read those files before continuing.
171
-
172
- Provider gotcha: the Codex CLI **exits its session** on Ctrl+C at an idle prompt (a mid-task Ctrl+C only interrupts the running task). The user typically interrupts a *busy* agent before issuing the pause instruction — do not assume Ctrl+C is a safe no-op.
64
+ - Re-implementing the ai-whisper-workflow procedure here — delegate; the
65
+ target owns it.
66
+ - Bending this alias to another workflow type SDD, deliberation, and ralph
67
+ asks go to their own entry points.
68
+ - Fixing the bug directly when the user handed you a bug-report file for the
69
+ workflow — kickoff is the ask; the workflow's implementer does the fixing.
70
+ - Polling or narrating after kickoff fire-and-forget applies unchanged.