@pimmesz/afterburner 1.0.12 → 1.0.14

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/README.md CHANGED
@@ -256,6 +256,7 @@ writes `.mjs` by default. There's a commented example in
256
256
  | `agent.modelByCategory` | sonnet/opus/haiku mix | Per-category model routing. Plain strings, so a new model never breaks the config. |
257
257
  | `agent.maxTaskTokens` | `200_000` | Hard per-task ceiling in Sonnet-equivalent tokens |
258
258
  | `agent.allowFable` | `false` | Explicit opt-in for Fable-family models |
259
+ | `notify.desktop` | `false` | Desktop banner per completed run (macOS/Linux/Windows), opt-in; the PR stays the primary signal |
259
260
  | `taskCategories` | all enabled, weighted | Per-category enable/disable plus a ranking weight |
260
261
 
261
262
  Task categories are `security`, `tests`, `types-lint`, `dead-code`, `perf`, `infra`, and
@@ -72,7 +72,8 @@ const config = {
72
72
  backend: 'dry-run',
73
73
  // Per-category model routing. Plain strings on purpose, so a new model
74
74
  // can't break the config. Omitted categories use the built-in defaults
75
- // (sonnet for most, opus for perf and infra, haiku for the simple end).
75
+ // (sonnet for security and tests, opus for perf and infra, haiku for the
76
+ // simple end: types-lint, dead-code, docs).
76
77
  modelByCategory: {
77
78
  perf: 'claude-opus-4-8',
78
79
  docs: 'claude-haiku-4-5',
@@ -495,7 +495,7 @@ function createBudgetProvider(config, opts = {}) {
495
495
 
496
496
  // src/core/scheduler/gate.ts
497
497
  function shouldIgnite(budget, estCostSonnetTokens, config) {
498
- if (!Number.isFinite(budget.weeklyRemainingPct) || !Number.isFinite(budget.weeklyRemainingTokensEst) || !Number.isFinite(estCostSonnetTokens) || !Number.isFinite(config.safetyMarginTokens)) {
498
+ if (!Number.isFinite(budget.weeklyRemainingPct) || !Number.isFinite(budget.weeklyRemainingTokensEst) || !Number.isFinite(estCostSonnetTokens) || !Number.isFinite(config.safetyMarginTokens) || !Number.isFinite(config.minWeeklyHeadroomPct)) {
499
499
  return { go: false, reason: "budget or estimate is not a finite number; refusing to ignite" };
500
500
  }
501
501
  if (config.requireSessionAvailable && !budget.sessionAvailable) {
@@ -514,7 +514,10 @@ function shouldIgnite(budget, estCostSonnetTokens, config) {
514
514
  reason: `weekly cap: task needs ~${needed.toLocaleString("en-US")} Sonnet-eq tokens (incl. ${config.safetyMarginTokens.toLocaleString("en-US")} safety margin) but only ~${budget.weeklyRemainingTokensEst.toLocaleString("en-US")} remain`
515
515
  };
516
516
  }
517
- return { go: true, reason: "session available and estimated cost fits within weekly headroom" };
517
+ return {
518
+ go: true,
519
+ reason: `${budget.sessionAvailable ? "session available" : "session check waived"} and estimated cost fits within weekly headroom`
520
+ };
518
521
  }
519
522
 
520
523
  // src/core/scheduler/native.ts
package/dist/cli/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  shouldIgnite,
26
26
  startWatch,
27
27
  writeUsageCache
28
- } from "../chunk-4TD2KS3A.js";
28
+ } from "../chunk-HAERJCAI.js";
29
29
  import {
30
30
  MCP_STUB_MESSAGE
31
31
  } from "../chunk-2NSOEZWY.js";
@@ -36,7 +36,7 @@ import { Command } from "commander";
36
36
  // package.json
37
37
  var package_default = {
38
38
  name: "@pimmesz/afterburner",
39
- version: "1.0.12",
39
+ version: "1.0.14",
40
40
  description: "Convert idle Claude subscription quota into shippable engineering work: budget-aware trigger, bounded task selection, PR-only output.",
41
41
  license: "Apache-2.0",
42
42
  publishConfig: {
@@ -485,7 +485,7 @@ ${nextCmd(cmd("doctor"))}`;
485
485
  const repoLine = repoNames.length <= 2 ? repoNames.join(", ") : `${repoNames.slice(0, 2).join(", ")} (and ${repoNames.length - 2} more)`;
486
486
  const engineLine = config.agent.backend === "dry-run" ? "dry-run \u2014 simulation only; set agent.backend: 'claude-code' in the config to do real work" : config.agent.backend === "claude-code" ? "claude-code \u2014 spends subscription quota you already pay for; PRs only with --live" : "api-key \u2014 bills your Anthropic API account per token (real money); PRs only with --live";
487
487
  const budgetLine = config.budget.provider === "manual" ? "manual \u2014 trusts budget.manual (automatic option: `afterburner statusline install`, then budget.provider: 'claude-usage')" : config.budget.provider === "claude-usage" ? "claude-usage \u2014 reads your real usage via the status line hook" : "claude-code-transcripts \u2014 estimates from local Claude Code session logs";
488
- const nextNote = config.agent.backend === "dry-run" ? "previews the next task; nothing is executed or spent" : `previews the next task (live execution ships in a future release; ${cmd("run-once --live")} currently validates and refuses)`;
488
+ const nextNote = config.agent.backend === "dry-run" ? "previews the next task; nothing is executed or spent" : `previews the next task (live execution ships in a future release; ${cmd("run-once --live")} currently arms LIVE then exits with a not-implemented error \u2014 no PR is opened)`;
489
489
  const stopLine = `\`afterburner schedule uninstall\` (or Ctrl-C for watch); ${cmd("log")} lists every past run`;
490
490
  if (opts.summary) {
491
491
  return `${section(emoji.flame, "Ready")}
@@ -922,12 +922,11 @@ ${artifacts.removalHint}`);
922
922
  }
923
923
 
924
924
  // src/cli/commands/init.ts
925
- var BACKENDS = ["dry-run", "claude-code", "api-key"];
925
+ var OFFERED_BACKENDS = ["dry-run", "claude-code"];
926
926
  var BUDGET_PROVIDERS = ["manual", "claude-usage", "claude-code-transcripts"];
927
927
  var ENGINE_CHOICES = {
928
928
  "dry-run": "simulates everything, spends nothing. The safe default.",
929
- "claude-code": "your Claude subscription via the local `claude` login. No extra money; it spends quota you already pay for.",
930
- "api-key": "bills your Anthropic API account per token. Every run costs real money."
929
+ "claude-code": "your Claude subscription via the local `claude` login. No extra money; it spends quota you already pay for."
931
930
  };
932
931
  var ENGINE_NOTES = {
933
932
  "dry-run": "dry-run (simulates everything, spends nothing)",
@@ -1123,13 +1122,13 @@ async function collectAnswers(rl, opts, cwd = process.cwd()) {
1123
1122
  let targetDir = cwd;
1124
1123
  let target = join2(targetDir, "afterburner.config.mjs");
1125
1124
  console.log(
1126
- "Afterburner turns unused Claude subscription quota into small, reviewed pull requests.\nDry-run first: nothing executes or spends until a live engine is set in the config\nAND you pass --live. Three questions, then an optional health check.\n"
1125
+ "Afterburner turns unused Claude subscription quota into small, reviewed pull requests.\nDry-run first: nothing executes or spends until a live engine is set in the config\nAND you pass --live. (No run can open a PR yet \u2014 live execution ships in a future\nrelease.) Three questions, then an optional health check.\n"
1127
1126
  );
1128
1127
  console.log(bold("Step 1 of 3 \u2014 Engine (who does the work, and what it can spend)"));
1129
- BACKENDS.forEach((name, i) => {
1128
+ OFFERED_BACKENDS.forEach((name, i) => {
1130
1129
  console.log(` ${i + 1}. ${name}: ${ENGINE_CHOICES[name]}`);
1131
1130
  });
1132
- const backend = BACKENDS[await askChoice(rl, "Engine [1]: ", BACKENDS.length)] ?? "dry-run";
1131
+ const backend = OFFERED_BACKENDS[await askChoice(rl, "Engine [1]: ", OFFERED_BACKENDS.length)] ?? "dry-run";
1133
1132
  console.log(step("Engine", backend));
1134
1133
  console.log(`
1135
1134
  ${bold("Step 2 of 3 \u2014 Repository (the allowlist of what it may touch)")}`);
@@ -1355,7 +1354,7 @@ function registerMcp(program2) {
1355
1354
  // src/cli/commands/run-once.ts
1356
1355
  function registerRunOnce(program2) {
1357
1356
  program2.command("run-once").description(
1358
- "Run one ignition cycle: pick at most one bounded task per configured repo (dry-run by default)"
1357
+ "Run one ignition cycle: pick at most one bounded task per run (dry-run by default)"
1359
1358
  ).option("--config <path>", "path to a config file").option("--dry-run", "force a dry run (this is the default behavior)").option(
1360
1359
  "--live",
1361
1360
  "arm live execution; only takes effect when agent.backend is also a live engine in the config (two-part opt-in)"
@@ -1443,7 +1442,7 @@ ${section(emoji.rocket, "Next")}`);
1443
1442
  console.log(
1444
1443
  config.agent.backend === "dry-run" ? ` This was a preview. Live execution ships in a future release; once it does,
1445
1444
  set agent.backend: 'claude-code' in ${filepath}, then: ${liveCmd}` : ` This was a preview. Live execution ships in a future release;
1446
- ${liveCmd} currently validates the setup and refuses.`
1445
+ ${liveCmd} currently arms LIVE then exits with a not-implemented error (no PR is opened).`
1447
1446
  );
1448
1447
  }
1449
1448
  });
@@ -1911,7 +1910,7 @@ function renderWelcome(configPath) {
1911
1910
  cmd("afterburner run-once --dry-run", "preview the next task (no changes made)"),
1912
1911
  cmd(
1913
1912
  "afterburner run-once --live",
1914
- "real PRs (stubbed in this version; validates and refuses)"
1913
+ "real PRs (stubbed in this version; arms LIVE then errors out, no PR opened)"
1915
1914
  ),
1916
1915
  cmd("afterburner log", "recent run history")
1917
1916
  );
@@ -53,8 +53,8 @@ declare const budgetConfigSchema: z.ZodPrefault<z.ZodObject<{
53
53
  }, z.core.$strip>>;
54
54
  declare const agentConfigSchema: z.ZodPrefault<z.ZodObject<{
55
55
  backend: z.ZodDefault<z.ZodEnum<{
56
- "dry-run": "dry-run";
57
56
  "claude-code": "claude-code";
57
+ "dry-run": "dry-run";
58
58
  "api-key": "api-key";
59
59
  }>>;
60
60
  modelByCategory: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodEnum<{
@@ -115,8 +115,8 @@ declare const configSchema: z.ZodObject<{
115
115
  }, z.core.$strip>>;
116
116
  agent: z.ZodPrefault<z.ZodObject<{
117
117
  backend: z.ZodDefault<z.ZodEnum<{
118
- "dry-run": "dry-run";
119
118
  "claude-code": "claude-code";
119
+ "dry-run": "dry-run";
120
120
  "api-key": "api-key";
121
121
  }>>;
122
122
  modelByCategory: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodEnum<{
@@ -673,10 +673,12 @@ declare class JsonlRunStore implements RunStore {
673
673
  }
674
674
 
675
675
  /**
676
- * Notification seam. Core ships ONLY ConsoleNotifier: the pull request is the
677
- * real notification and the run store is the audit trail. This interface is
678
- * the documented extension point for a future opt-in generic webhook, vendor
679
- * SDKs are never bundled into core.
676
+ * Notification seam. ConsoleNotifier always runs its line is the audit trail
677
+ * that reaches the scheduler's stdout log and an opt-in DesktopNotifier
678
+ * (OS-native banner, gated by notify.desktop) layers on top via
679
+ * CompositeNotifier/createNotifier. The pull request stays the real
680
+ * notification and the run store the source of truth. Notifiers shell out to
681
+ * platform tools; vendor SDKs are never bundled into core.
680
682
  */
681
683
  interface Notifier {
682
684
  notify(record: RunRecord): Promise<void>;
@@ -748,11 +750,12 @@ interface GateDecision {
748
750
  reason: string;
749
751
  }
750
752
  /**
751
- * Both-caps gate, a non-negotiable rule encoded as a pure function: only fire
752
- * when a session window is available AND the estimated cost plus the safety
753
- * margin fits inside the remaining weekly budget (with the configured
754
- * headroom). Reasons are returned, not just booleans, so every skipped run is
755
- * explainable in the log.
753
+ * Both-caps gate encoded as a pure function: only fire when the estimated cost
754
+ * plus the safety margin fits inside the remaining weekly budget (with the
755
+ * configured headroom) the non-negotiable weekly cap AND, when
756
+ * config.requireSessionAvailable is set (the default), a 5-hour session window
757
+ * is also free. Reasons are returned, not just booleans, so every skipped run
758
+ * is explainable in the log.
756
759
  */
757
760
  declare function shouldIgnite(budget: Budget, estCostSonnetTokens: number, config: GateConfig): GateDecision;
758
761
 
@@ -49,7 +49,7 @@ import {
49
49
  summarizeTranscriptUsage,
50
50
  taskFingerprint,
51
51
  writeUsageCache
52
- } from "../chunk-4TD2KS3A.js";
52
+ } from "../chunk-HAERJCAI.js";
53
53
  export {
54
54
  ApiKeyRunner,
55
55
  BASE_TASK_TOKENS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pimmesz/afterburner",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Convert idle Claude subscription quota into shippable engineering work: budget-aware trigger, bounded task selection, PR-only output.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {