vibebusiness 1.2.77 → 1.2.80

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 (88) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +24 -24
  3. package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/prerender-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  8. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  9. package/.next/standalone/.next/server/app/api/analyze/route.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/ideas/[id]/card/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/social/[id]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/.next/server/app/goals/page.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/hypotheses/page.js +1 -1
  35. package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/ideas/[id]/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/page.js +1 -1
  40. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/roadmap/investors/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/roadmap/investors/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/roadmap/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/roadmap/public/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/roadmap/public/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/settings.html +1 -1
  53. package/.next/standalone/.next/server/app/settings.rsc +1 -1
  54. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/social.html +1 -1
  56. package/.next/standalone/.next/server/app/social.rsc +1 -1
  57. package/.next/standalone/.next/server/app/updates/[id]/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/updates/[id]/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/updates/new/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/updates/new.html +1 -1
  61. package/.next/standalone/.next/server/app/updates/new.rsc +1 -1
  62. package/.next/standalone/.next/server/app/updates/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/updates/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app-paths-manifest.json +20 -20
  65. package/.next/standalone/.next/server/pages/404.html +1 -1
  66. package/.next/standalone/.next/server/pages/500.html +1 -1
  67. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  68. package/.next/standalone/data/business-context.json +1 -1
  69. package/.next/standalone/data/ideas.json +306 -26
  70. package/.next/standalone/data/implementations.json +147 -0
  71. package/.next/standalone/data/marketing-plans/plan-1771885743819-2d9m2nt.json +115 -0
  72. package/.next/standalone/data/marketing-plans/plan-1771886455031-mwdob3k.json +115 -0
  73. package/.next/standalone/data/reports/visuals/idea-first-tweet-card.png +0 -0
  74. package/.next/standalone/data/reports/visuals/idea-lp-hero-specificity-card.png +0 -0
  75. package/.next/standalone/data/reports/visuals/idea-lp-social-proof-card.png +0 -0
  76. package/.next/standalone/data/videos/ad-solo-founder-burnout.mp4 +0 -0
  77. package/.next/standalone/data/videos/staging/ad-solo-founder-burnout.json +54 -0
  78. package/.next/standalone/node_modules/.cache/webpack/remotion-production-4.0.428/48519e014fdef8bfe40a0d447e90c96c/0.pack +0 -0
  79. package/.next/standalone/node_modules/.cache/webpack/remotion-production-4.0.428/48519e014fdef8bfe40a0d447e90c96c/index.pack +0 -0
  80. package/.next/standalone/package.json +1 -1
  81. package/dist/scripts/heartbeat.js +648 -462
  82. package/package.json +1 -1
  83. package/scripts/lib/video/compositions/AdVideo.tsx +7 -3
  84. package/scripts/lib/video/compositions/BackgroundMusic.tsx +41 -0
  85. package/scripts/lib/video/compositions/Root.tsx +17 -6
  86. package/scripts/lib/video/compositions/ShipVideo.tsx +6 -2
  87. /package/.next/static/{_6NFRXGMGhuMnoqW7NVno → 9C8sbF668J83TlKDjSvQm}/_buildManifest.js +0 -0
  88. /package/.next/static/{_6NFRXGMGhuMnoqW7NVno → 9C8sbF668J83TlKDjSvQm}/_ssgManifest.js +0 -0
@@ -105,8 +105,8 @@ var require_package = __commonJS({
105
105
  var require_main = __commonJS({
106
106
  "node_modules/dotenv/lib/main.js"(exports2, module2) {
107
107
  "use strict";
108
- var fs34 = require("fs");
109
- var path29 = require("path");
108
+ var fs35 = require("fs");
109
+ var path30 = require("path");
110
110
  var os3 = require("os");
111
111
  var crypto5 = require("crypto");
112
112
  var packageJson = require_package();
@@ -244,7 +244,7 @@ var require_main = __commonJS({
244
244
  if (options && options.path && options.path.length > 0) {
245
245
  if (Array.isArray(options.path)) {
246
246
  for (const filepath of options.path) {
247
- if (fs34.existsSync(filepath)) {
247
+ if (fs35.existsSync(filepath)) {
248
248
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
249
249
  }
250
250
  }
@@ -252,15 +252,15 @@ var require_main = __commonJS({
252
252
  possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
253
253
  }
254
254
  } else {
255
- possibleVaultPath = path29.resolve(process.cwd(), ".env.vault");
255
+ possibleVaultPath = path30.resolve(process.cwd(), ".env.vault");
256
256
  }
257
- if (fs34.existsSync(possibleVaultPath)) {
257
+ if (fs35.existsSync(possibleVaultPath)) {
258
258
  return possibleVaultPath;
259
259
  }
260
260
  return null;
261
261
  }
262
262
  function _resolveHome(envPath) {
263
- return envPath[0] === "~" ? path29.join(os3.homedir(), envPath.slice(1)) : envPath;
263
+ return envPath[0] === "~" ? path30.join(os3.homedir(), envPath.slice(1)) : envPath;
264
264
  }
265
265
  function _configVault(options) {
266
266
  const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
@@ -277,7 +277,7 @@ var require_main = __commonJS({
277
277
  return { parsed };
278
278
  }
279
279
  function configDotenv(options) {
280
- const dotenvPath = path29.resolve(process.cwd(), ".env");
280
+ const dotenvPath = path30.resolve(process.cwd(), ".env");
281
281
  let encoding = "utf8";
282
282
  let processEnv = process.env;
283
283
  if (options && options.processEnv != null) {
@@ -305,13 +305,13 @@ var require_main = __commonJS({
305
305
  }
306
306
  let lastError;
307
307
  const parsedAll = {};
308
- for (const path30 of optionPaths) {
308
+ for (const path31 of optionPaths) {
309
309
  try {
310
- const parsed = DotenvModule.parse(fs34.readFileSync(path30, { encoding }));
310
+ const parsed = DotenvModule.parse(fs35.readFileSync(path31, { encoding }));
311
311
  DotenvModule.populate(parsedAll, parsed, options);
312
312
  } catch (e) {
313
313
  if (debug) {
314
- _debug(`Failed to load ${path30} ${e.message}`);
314
+ _debug(`Failed to load ${path31} ${e.message}`);
315
315
  }
316
316
  lastError = e;
317
317
  }
@@ -324,7 +324,7 @@ var require_main = __commonJS({
324
324
  const shortPaths = [];
325
325
  for (const filePath of optionPaths) {
326
326
  try {
327
- const relative = path29.relative(process.cwd(), filePath);
327
+ const relative = path30.relative(process.cwd(), filePath);
328
328
  shortPaths.push(relative);
329
329
  } catch (e) {
330
330
  if (debug) {
@@ -476,6 +476,36 @@ var require_cli_options = __commonJS({
476
476
  }
477
477
  });
478
478
 
479
+ // src/lib/prompts.ts
480
+ function buildBaseContext(businessConfig) {
481
+ if (!businessConfig) {
482
+ return `You are an AI Product Manager.
483
+
484
+ Your role is to analyze the codebase and generate actionable improvement ideas.`;
485
+ }
486
+ const { product } = businessConfig;
487
+ let architectureSection = "";
488
+ if (product.architecture && product.architecture.length > 0) {
489
+ const components = product.architecture.map((c2, i) => {
490
+ const techLine = c2.tech_stack.length > 0 ? `
491
+ Tech: ${c2.tech_stack.join(", ")}` : "";
492
+ return `${i + 1}. **${c2.name} (${c2.repo_name})**: ${c2.description}${techLine}`;
493
+ }).join("\n\n");
494
+ architectureSection = `
495
+
496
+ The product consists of:
497
+ ${components}`;
498
+ }
499
+ return `You are an AI Product Manager for ${product.name}, ${product.summary}.${architectureSection}
500
+
501
+ Your role is to analyze the codebase and generate actionable improvement ideas.`;
502
+ }
503
+ var init_prompts = __esm({
504
+ "src/lib/prompts.ts"() {
505
+ "use strict";
506
+ }
507
+ });
508
+
479
509
  // scripts/lib/paths.ts
480
510
  function findProjectRoot() {
481
511
  const cwd = process.cwd();
@@ -9428,6 +9458,407 @@ var init_esm = __esm({
9428
9458
  }
9429
9459
  });
9430
9460
 
9461
+ // scripts/lib/ai-provider.ts
9462
+ function loadCredentials2() {
9463
+ try {
9464
+ if (fs16.existsSync(CREDENTIALS_FILE)) {
9465
+ return JSON.parse(fs16.readFileSync(CREDENTIALS_FILE, "utf-8"));
9466
+ }
9467
+ } catch {
9468
+ }
9469
+ return {};
9470
+ }
9471
+ function detectClaudeCLI(customPath) {
9472
+ const claudeBin = customPath || "claude";
9473
+ try {
9474
+ const version = (0, import_child_process6.execSync)(`${claudeBin} --version 2>/dev/null`, {
9475
+ timeout: 1e4,
9476
+ encoding: "utf-8"
9477
+ }).trim();
9478
+ return { found: true, path: claudeBin, version };
9479
+ } catch {
9480
+ const commonPaths = [
9481
+ "/usr/local/bin/claude",
9482
+ path11.join(process.env.HOME || "", ".claude", "bin", "claude"),
9483
+ path11.join(process.env.HOME || "", ".local", "bin", "claude")
9484
+ ];
9485
+ for (const p of commonPaths) {
9486
+ try {
9487
+ if (fs16.existsSync(p)) {
9488
+ const version = (0, import_child_process6.execSync)(`${p} --version 2>/dev/null`, {
9489
+ timeout: 1e4,
9490
+ encoding: "utf-8"
9491
+ }).trim();
9492
+ return { found: true, path: p, version };
9493
+ }
9494
+ } catch {
9495
+ continue;
9496
+ }
9497
+ }
9498
+ return { found: false, path: claudeBin };
9499
+ }
9500
+ }
9501
+ function detectBYOKKeys() {
9502
+ if (process.env.ANTHROPIC_API_KEY) {
9503
+ return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
9504
+ }
9505
+ if (process.env.OPENAI_API_KEY) {
9506
+ return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
9507
+ }
9508
+ if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
9509
+ return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
9510
+ }
9511
+ const creds = loadCredentials2();
9512
+ if (creds.ANTHROPIC_API_KEY) {
9513
+ process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
9514
+ return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
9515
+ }
9516
+ if (creds.OPENAI_API_KEY) {
9517
+ process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
9518
+ return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
9519
+ }
9520
+ if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
9521
+ const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
9522
+ process.env.GOOGLE_API_KEY = key;
9523
+ return { provider: "google-api", key, source: "credentials" };
9524
+ }
9525
+ return null;
9526
+ }
9527
+ function detectProvider(config) {
9528
+ const cli = detectClaudeCLI(config?.claudePath);
9529
+ if (cli.found) {
9530
+ return {
9531
+ provider: "claude-cli",
9532
+ version: cli.version,
9533
+ message: `Found Claude Code CLI${cli.version ? ` (${cli.version})` : ""}. Using your Claude subscription for AI reasoning.`
9534
+ };
9535
+ }
9536
+ const byok = detectBYOKKeys();
9537
+ if (byok) {
9538
+ const providerNames = {
9539
+ "claude-cli": "Claude CLI",
9540
+ "anthropic-api": "Anthropic API",
9541
+ "openai-api": "OpenAI API",
9542
+ "google-api": "Google AI API"
9543
+ };
9544
+ return {
9545
+ provider: byok.provider,
9546
+ message: `Using ${providerNames[byok.provider]} key for AI reasoning.`
9547
+ };
9548
+ }
9549
+ return {
9550
+ provider: "claude-cli",
9551
+ // default, will fail at invocation
9552
+ message: "No AI provider found. Install Claude Code CLI or set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY)."
9553
+ };
9554
+ }
9555
+ function loadProviderConfig() {
9556
+ try {
9557
+ if (fs16.existsSync(PROVIDER_CONFIG_FILE)) {
9558
+ return JSON.parse(fs16.readFileSync(PROVIDER_CONFIG_FILE, "utf-8"));
9559
+ }
9560
+ } catch {
9561
+ }
9562
+ return null;
9563
+ }
9564
+ function invokeClaudeCLI(options, claudePath) {
9565
+ const bin = claudePath || "claude";
9566
+ const args2 = ["--print"];
9567
+ if (options.model) {
9568
+ args2.push("--model", options.model);
9569
+ }
9570
+ if (options.jsonSchema) {
9571
+ args2.push("--output-format", "json", "--json-schema", JSON.stringify(options.jsonSchema));
9572
+ }
9573
+ if (options.claudeFlags) {
9574
+ args2.push(...options.claudeFlags);
9575
+ }
9576
+ const useStdin = options.useStdin !== false && options.prompt.length > 1e4;
9577
+ if (!useStdin) {
9578
+ args2.push("-p", options.prompt);
9579
+ }
9580
+ const startTime = Date.now();
9581
+ const timeoutMs = options.timeoutMs || 3e5;
9582
+ return new Promise((resolve3) => {
9583
+ const child = (0, import_child_process6.spawn)(bin, args2, {
9584
+ cwd: options.cwd || process.cwd(),
9585
+ env: process.env,
9586
+ stdio: useStdin ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
9587
+ });
9588
+ let stdout = "";
9589
+ let stderr = "";
9590
+ child.stdout?.on("data", (data) => {
9591
+ stdout += data.toString();
9592
+ });
9593
+ child.stderr?.on("data", (data) => {
9594
+ stderr += data.toString();
9595
+ });
9596
+ if (useStdin && child.stdin) {
9597
+ child.stdin.write(options.prompt);
9598
+ child.stdin.end();
9599
+ }
9600
+ const timeout = setTimeout(() => {
9601
+ child.kill("SIGTERM");
9602
+ resolve3({
9603
+ output: stdout,
9604
+ provider: "claude-cli",
9605
+ durationMs: Date.now() - startTime,
9606
+ error: `Timeout after ${timeoutMs}ms`
9607
+ });
9608
+ }, timeoutMs);
9609
+ child.on("close", (code) => {
9610
+ clearTimeout(timeout);
9611
+ resolve3({
9612
+ output: stdout.trim(),
9613
+ provider: "claude-cli",
9614
+ durationMs: Date.now() - startTime,
9615
+ error: code !== 0 ? `Claude CLI exited with code ${code}: ${stderr}` : void 0
9616
+ });
9617
+ });
9618
+ child.on("error", (err2) => {
9619
+ clearTimeout(timeout);
9620
+ resolve3({
9621
+ output: "",
9622
+ provider: "claude-cli",
9623
+ durationMs: Date.now() - startTime,
9624
+ error: `Failed to spawn Claude CLI: ${err2.message}`
9625
+ });
9626
+ });
9627
+ });
9628
+ }
9629
+ async function invokeAnthropicAPI(options) {
9630
+ const startTime = Date.now();
9631
+ const apiKey = process.env.ANTHROPIC_API_KEY;
9632
+ if (!apiKey) {
9633
+ return {
9634
+ output: "",
9635
+ provider: "anthropic-api",
9636
+ durationMs: 0,
9637
+ error: "ANTHROPIC_API_KEY not set"
9638
+ };
9639
+ }
9640
+ try {
9641
+ const model = options.model || "claude-sonnet-4-6";
9642
+ let prompt = options.prompt;
9643
+ if (options.jsonSchema) {
9644
+ prompt += `
9645
+
9646
+ IMPORTANT: Respond with valid JSON matching this schema:
9647
+ ${JSON.stringify(options.jsonSchema, null, 2)}
9648
+
9649
+ Output ONLY the JSON object, no markdown code blocks or other text.`;
9650
+ } else if (options.expectJson) {
9651
+ prompt += "\n\nIMPORTANT: Respond with valid JSON only. No markdown code blocks or other text.";
9652
+ }
9653
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
9654
+ method: "POST",
9655
+ headers: {
9656
+ "Content-Type": "application/json",
9657
+ "x-api-key": apiKey,
9658
+ "anthropic-version": "2023-06-01"
9659
+ },
9660
+ body: JSON.stringify({
9661
+ model,
9662
+ max_tokens: 16384,
9663
+ messages: [{ role: "user", content: prompt }]
9664
+ }),
9665
+ signal: AbortSignal.timeout(options.timeoutMs || 3e5)
9666
+ });
9667
+ if (!response.ok) {
9668
+ const errorText = await response.text();
9669
+ return {
9670
+ output: "",
9671
+ provider: "anthropic-api",
9672
+ durationMs: Date.now() - startTime,
9673
+ error: `Anthropic API error ${response.status}: ${errorText}`
9674
+ };
9675
+ }
9676
+ const data = await response.json();
9677
+ const text = data.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
9678
+ return {
9679
+ output: text,
9680
+ provider: "anthropic-api",
9681
+ durationMs: Date.now() - startTime
9682
+ };
9683
+ } catch (err2) {
9684
+ return {
9685
+ output: "",
9686
+ provider: "anthropic-api",
9687
+ durationMs: Date.now() - startTime,
9688
+ error: `Anthropic API call failed: ${err2.message}`
9689
+ };
9690
+ }
9691
+ }
9692
+ async function invokeAI(options) {
9693
+ const savedConfig = loadProviderConfig();
9694
+ const detection = detectProvider(savedConfig || void 0);
9695
+ switch (detection.provider) {
9696
+ case "claude-cli":
9697
+ return invokeClaudeCLI(options, savedConfig?.claudePath);
9698
+ case "anthropic-api":
9699
+ return invokeAnthropicAPI(options);
9700
+ case "openai-api":
9701
+ case "google-api":
9702
+ return {
9703
+ output: "",
9704
+ provider: detection.provider,
9705
+ durationMs: 0,
9706
+ error: `${detection.provider} provider not yet implemented. Use Claude CLI or set ANTHROPIC_API_KEY.`
9707
+ };
9708
+ default:
9709
+ return {
9710
+ output: "",
9711
+ provider: "claude-cli",
9712
+ durationMs: 0,
9713
+ error: detection.message
9714
+ };
9715
+ }
9716
+ }
9717
+ function requireClaudeCLI(feature) {
9718
+ const cli = detectClaudeCLI();
9719
+ if (cli.found) {
9720
+ return cli.path;
9721
+ }
9722
+ const featureLabel = feature ? ` (${feature})` : "";
9723
+ const byok = detectBYOKKeys();
9724
+ if (byok) {
9725
+ throw new Error(
9726
+ `This feature${featureLabel} requires Claude Code CLI for file and tool access.
9727
+ Your API key is detected and works for reasoning tasks (heartbeat, idea evaluation),
9728
+ but codebase analysis and implementation need the full CLI.
9729
+ Install it with: npm install -g @anthropic-ai/claude-code`
9730
+ );
9731
+ }
9732
+ throw new Error(
9733
+ `No AI provider found${featureLabel}.
9734
+ Install Claude Code CLI: npm install -g @anthropic-ai/claude-code
9735
+ Or set an API key: export ANTHROPIC_API_KEY=sk-ant-...`
9736
+ );
9737
+ }
9738
+ var import_child_process6, fs16, path11, CREDENTIALS_DIR, CREDENTIALS_FILE, CONFIG_DIR, PROVIDER_CONFIG_FILE;
9739
+ var init_ai_provider = __esm({
9740
+ "scripts/lib/ai-provider.ts"() {
9741
+ "use strict";
9742
+ import_child_process6 = require("child_process");
9743
+ fs16 = __toESM(require("fs"));
9744
+ path11 = __toESM(require("path"));
9745
+ CREDENTIALS_DIR = path11.join(process.env.HOME || "", ".vibebusiness");
9746
+ CREDENTIALS_FILE = path11.join(CREDENTIALS_DIR, "credentials.json");
9747
+ CONFIG_DIR = path11.join(process.env.HOME || "", ".ai-analyst");
9748
+ PROVIDER_CONFIG_FILE = path11.join(CONFIG_DIR, "provider.json");
9749
+ }
9750
+ });
9751
+
9752
+ // scripts/skills/marketing-strategy.ts
9753
+ function detectMarketingTriggers(goals, previousGoals) {
9754
+ const triggers = [];
9755
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9756
+ goals.forEach((goal) => {
9757
+ if (goal.status === "achieved") {
9758
+ return;
9759
+ }
9760
+ goal.kpis.forEach((kpi) => {
9761
+ const stagnation = detectKPIStagnation(kpi, STAGNATION_WINDOW_DAYS);
9762
+ if (stagnation && goal.status === "behind") {
9763
+ triggers.push({
9764
+ id: `trigger-stagnation-${goal.id}-${kpi.id}`,
9765
+ type: "kpi_stagnation",
9766
+ severity: "urgent",
9767
+ description: `KPI "${kpi.name}" has been stagnant at ${kpi.current_value} for ${stagnation.days} days while goal "${goal.title}" is behind schedule`,
9768
+ affected_goal_ids: [goal.id],
9769
+ suggested_action: `Launch marketing campaign to move ${kpi.name} from ${kpi.current_value} toward target of ${kpi.target_value}`,
9770
+ detected_at: now
9771
+ });
9772
+ }
9773
+ });
9774
+ if (previousGoals) {
9775
+ const previousGoal = previousGoals.find((g2) => g2.id === goal.id);
9776
+ if (previousGoal && previousGoal.status === "on_track" && goal.status === "behind") {
9777
+ triggers.push({
9778
+ id: `trigger-regression-${goal.id}`,
9779
+ type: "goal_regression",
9780
+ severity: "high",
9781
+ description: `Goal "${goal.title}" regressed from "on_track" to "behind" schedule`,
9782
+ affected_goal_ids: [goal.id],
9783
+ suggested_action: `Analyze why goal regressed and launch recovery campaign`,
9784
+ detected_at: now
9785
+ });
9786
+ }
9787
+ }
9788
+ });
9789
+ return triggers;
9790
+ }
9791
+ function detectKPIStagnation(kpi, windowDays) {
9792
+ if (kpi.history.length < 2) {
9793
+ return null;
9794
+ }
9795
+ const sortedHistory = [...kpi.history].sort(
9796
+ (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
9797
+ );
9798
+ const latestValue = sortedHistory[0].value;
9799
+ const latestDate = new Date(sortedHistory[0].date);
9800
+ const windowStart = /* @__PURE__ */ new Date();
9801
+ windowStart.setDate(windowStart.getDate() - windowDays);
9802
+ let isStagnant = true;
9803
+ let lastChangeDate = sortedHistory[0].date;
9804
+ for (let i = 0; i < sortedHistory.length; i++) {
9805
+ const entry = sortedHistory[i];
9806
+ const entryDate = new Date(entry.date);
9807
+ if (entryDate < windowStart) {
9808
+ break;
9809
+ }
9810
+ if (entry.value !== latestValue) {
9811
+ isStagnant = false;
9812
+ lastChangeDate = sortedHistory[i - 1]?.date || entry.date;
9813
+ break;
9814
+ }
9815
+ }
9816
+ if (isStagnant && latestDate > windowStart) {
9817
+ const daysDiff = Math.floor(
9818
+ (Date.now() - new Date(lastChangeDate).getTime()) / (1e3 * 60 * 60 * 24)
9819
+ );
9820
+ return {
9821
+ days: daysDiff,
9822
+ last_change_date: lastChangeDate
9823
+ };
9824
+ }
9825
+ return null;
9826
+ }
9827
+ function generateIdeasFromPlan(plan) {
9828
+ const ideas = [];
9829
+ plan.campaigns.forEach((campaign) => {
9830
+ const idea = {
9831
+ title: campaign.name,
9832
+ summary: `Execute ${campaign.name} campaign for ${plan.title}`,
9833
+ category: "growth",
9834
+ priority: campaign.channel === plan.channels[0]?.name ? "high" : "medium",
9835
+ effort: campaign.command_sequence.length > 3 ? "m" : "s",
9836
+ impact: "l",
9837
+ // Marketing campaigns typically have large impact
9838
+ context: `Marketing campaign from plan ${plan.id}. Timeline: ${campaign.timeline.start} to ${campaign.timeline.end}. Budget: $${campaign.budget}.`,
9839
+ rationale: `This campaign targets ${campaign.target_value} ${campaign.success_metric} via ${campaign.channel}. Part of ${plan.strategy}.`,
9840
+ implementation_plan: `**Commands to Execute:**
9841
+ ${campaign.command_sequence.map((cmd) => `- ${cmd.command} ${cmd.args || ""} (${cmd.frequency})`).join("\n")}
9842
+
9843
+ **Success Metric:** ${campaign.success_metric} = ${campaign.target_value}`,
9844
+ success_metrics: [`${campaign.success_metric}: ${campaign.target_value}`, `Campaign ROI: ${campaign.budget > 0 ? "positive" : "N/A"}`],
9845
+ goal_id: plan.goal_id,
9846
+ tags: ["marketing", "campaign", campaign.channel, plan.strategy]
9847
+ };
9848
+ ideas.push(idea);
9849
+ });
9850
+ return ideas;
9851
+ }
9852
+ var STAGNATION_WINDOW_DAYS;
9853
+ var init_marketing_strategy = __esm({
9854
+ "scripts/skills/marketing-strategy.ts"() {
9855
+ "use strict";
9856
+ init_ai_provider();
9857
+ init_prompts();
9858
+ STAGNATION_WINDOW_DAYS = 14;
9859
+ }
9860
+ });
9861
+
9431
9862
  // scripts/lib/screenshot.ts
9432
9863
  var screenshot_exports = {};
9433
9864
  __export(screenshot_exports, {
@@ -9477,6 +9908,97 @@ var init_screenshot = __esm({
9477
9908
  }
9478
9909
  });
9479
9910
 
9911
+ // scripts/skills/marketing-integration.ts
9912
+ var marketing_integration_exports = {};
9913
+ __export(marketing_integration_exports, {
9914
+ addMarketingIdeasToDashboard: () => addMarketingIdeasToDashboard,
9915
+ integrateMarketingPlanToUI: () => integrateMarketingPlanToUI
9916
+ });
9917
+ async function addMarketingIdeasToDashboard(plan, dataDir) {
9918
+ const ideasPath = path28.join(dataDir, "ideas.json");
9919
+ let ideasData = { ideas: [] };
9920
+ if (fs33.existsSync(ideasPath)) {
9921
+ ideasData = JSON.parse(fs33.readFileSync(ideasPath, "utf-8"));
9922
+ }
9923
+ const newIdeasPartial = generateIdeasFromPlan(plan);
9924
+ let added = 0;
9925
+ let skipped = 0;
9926
+ for (const partialIdea of newIdeasPartial) {
9927
+ const exists = ideasData.ideas.some((idea) => idea.title === partialIdea.title);
9928
+ if (exists) {
9929
+ skipped++;
9930
+ continue;
9931
+ }
9932
+ const newIdea = {
9933
+ id: generateIdeaId(),
9934
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
9935
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
9936
+ title: partialIdea.title,
9937
+ summary: partialIdea.summary,
9938
+ category: partialIdea.category,
9939
+ priority: partialIdea.priority,
9940
+ effort: partialIdea.effort,
9941
+ impact: partialIdea.impact,
9942
+ context: partialIdea.context,
9943
+ rationale: partialIdea.rationale,
9944
+ implementation_plan: partialIdea.implementation_plan,
9945
+ success_metrics: partialIdea.success_metrics,
9946
+ stage: "inbox",
9947
+ // Start in inbox
9948
+ source: {
9949
+ type: "manual",
9950
+ session_id: null
9951
+ },
9952
+ implementation: {
9953
+ branch_name: null,
9954
+ pr_url: null,
9955
+ pr_number: null,
9956
+ commits: [],
9957
+ started_at: null,
9958
+ completed_at: null,
9959
+ sub_tasks: []
9960
+ },
9961
+ comments: [],
9962
+ tags: partialIdea.tags || [],
9963
+ related_ideas: [],
9964
+ goal_id: partialIdea.goal_id || null,
9965
+ expected_impact: null,
9966
+ hypothesis_id: null,
9967
+ epic_id: null
9968
+ };
9969
+ ideasData.ideas.push(newIdea);
9970
+ added++;
9971
+ }
9972
+ fs33.writeFileSync(ideasPath, JSON.stringify(ideasData, null, 2));
9973
+ return { added, skipped };
9974
+ }
9975
+ function generateIdeaId() {
9976
+ const timestamp = Date.now().toString(36);
9977
+ const random = Math.random().toString(36).substring(2, 10);
9978
+ return `idea-${timestamp}-${random}`;
9979
+ }
9980
+ async function integrateMarketingPlanToUI(planId, dataDir) {
9981
+ const planPath = path28.join(dataDir, "marketing-plans", `${planId}.json`);
9982
+ if (!fs33.existsSync(planPath)) {
9983
+ throw new Error(`Plan ${planId} not found`);
9984
+ }
9985
+ const plan = JSON.parse(fs33.readFileSync(planPath, "utf-8"));
9986
+ const result = await addMarketingIdeasToDashboard(plan, dataDir);
9987
+ console.log(`\u2713 Added ${result.added} marketing ideas to dashboard`);
9988
+ if (result.skipped > 0) {
9989
+ console.log(` (Skipped ${result.skipped} duplicate ideas)`);
9990
+ }
9991
+ }
9992
+ var fs33, path28;
9993
+ var init_marketing_integration = __esm({
9994
+ "scripts/skills/marketing-integration.ts"() {
9995
+ "use strict";
9996
+ fs33 = __toESM(require("fs"));
9997
+ path28 = __toESM(require("path"));
9998
+ init_marketing_strategy();
9999
+ }
10000
+ });
10001
+
9480
10002
  // node_modules/dotenv/config.js
9481
10003
  (function() {
9482
10004
  require_main().config(
@@ -9489,8 +10011,8 @@ var init_screenshot = __esm({
9489
10011
  })();
9490
10012
 
9491
10013
  // scripts/heartbeat.ts
9492
- var fs33 = __toESM(require("fs"));
9493
- var path28 = __toESM(require("path"));
10014
+ var fs34 = __toESM(require("fs"));
10015
+ var path29 = __toESM(require("path"));
9494
10016
  var import_child_process11 = require("child_process");
9495
10017
  var import_util = require("util");
9496
10018
 
@@ -9857,30 +10379,8 @@ async function fetchAllKPIs(configs, startDate, endDate) {
9857
10379
  };
9858
10380
  }
9859
10381
 
9860
- // src/lib/prompts.ts
9861
- function buildBaseContext(businessConfig) {
9862
- if (!businessConfig) {
9863
- return `You are an AI Product Manager.
9864
-
9865
- Your role is to analyze the codebase and generate actionable improvement ideas.`;
9866
- }
9867
- const { product } = businessConfig;
9868
- let architectureSection = "";
9869
- if (product.architecture && product.architecture.length > 0) {
9870
- const components = product.architecture.map((c2, i) => {
9871
- const techLine = c2.tech_stack.length > 0 ? `
9872
- Tech: ${c2.tech_stack.join(", ")}` : "";
9873
- return `${i + 1}. **${c2.name} (${c2.repo_name})**: ${c2.description}${techLine}`;
9874
- }).join("\n\n");
9875
- architectureSection = `
9876
-
9877
- The product consists of:
9878
- ${components}`;
9879
- }
9880
- return `You are an AI Product Manager for ${product.name}, ${product.summary}.${architectureSection}
9881
-
9882
- Your role is to analyze the codebase and generate actionable improvement ideas.`;
9883
- }
10382
+ // scripts/heartbeat.ts
10383
+ init_prompts();
9884
10384
 
9885
10385
  // scripts/skills/content-marketing.ts
9886
10386
  var import_child_process = require("child_process");
@@ -13134,8 +13634,8 @@ function argument(predicate, message) {
13134
13634
  }
13135
13635
  }
13136
13636
  var check = { fail, argument, assert: argument };
13137
- function getPathDefinition(glyph, path29) {
13138
- var _path = path29 || new Path();
13637
+ function getPathDefinition(glyph, path30) {
13638
+ var _path = path30 || new Path();
13139
13639
  return {
13140
13640
  configurable: true,
13141
13641
  get: function() {
@@ -13358,9 +13858,9 @@ function ttfGlyphLoader(font, index, parseGlyph2, data, position, buildPath2) {
13358
13858
  var glyph = new Glyph({ index, font });
13359
13859
  glyph.path = function() {
13360
13860
  parseGlyph2(glyph, data, position);
13361
- var path29 = buildPath2(font.glyphs, glyph);
13362
- path29.unitsPerEm = font.unitsPerEm;
13363
- return path29;
13861
+ var path30 = buildPath2(font.glyphs, glyph);
13862
+ path30.unitsPerEm = font.unitsPerEm;
13863
+ return path30;
13364
13864
  };
13365
13865
  defineDependentProperty(glyph, "xMin", "_xMin");
13366
13866
  defineDependentProperty(glyph, "xMax", "_xMax");
@@ -13373,9 +13873,9 @@ function cffGlyphLoader(font, index, parseCFFCharstring2, charstring) {
13373
13873
  return function() {
13374
13874
  var glyph = new Glyph({ index, font });
13375
13875
  glyph.path = function() {
13376
- var path29 = parseCFFCharstring2(font, glyph, charstring);
13377
- path29.unitsPerEm = font.unitsPerEm;
13378
- return path29;
13876
+ var path30 = parseCFFCharstring2(font, glyph, charstring);
13877
+ path30.unitsPerEm = font.unitsPerEm;
13878
+ return path30;
13379
13879
  };
13380
13880
  return glyph;
13381
13881
  };
@@ -30494,8 +30994,8 @@ async function postTweet(client, text) {
30494
30994
  };
30495
30995
  }
30496
30996
  async function uploadMedia(client, filePath) {
30497
- const fs34 = require("fs");
30498
- if (!fs34.existsSync(filePath)) {
30997
+ const fs35 = require("fs");
30998
+ if (!fs35.existsSync(filePath)) {
30499
30999
  throw new Error(`Media file not found: ${filePath}`);
30500
31000
  }
30501
31001
  const mediaId = await client.v1.uploadMedia(filePath);
@@ -30746,293 +31246,7 @@ function generateProductUpdateTweet(positioning) {
30746
31246
  // scripts/lib/social/ai-content-generator.ts
30747
31247
  var fs17 = __toESM(require("fs"));
30748
31248
  init_paths();
30749
-
30750
- // scripts/lib/ai-provider.ts
30751
- var import_child_process6 = require("child_process");
30752
- var fs16 = __toESM(require("fs"));
30753
- var path11 = __toESM(require("path"));
30754
- var CREDENTIALS_DIR = path11.join(process.env.HOME || "", ".vibebusiness");
30755
- var CREDENTIALS_FILE = path11.join(CREDENTIALS_DIR, "credentials.json");
30756
- function loadCredentials2() {
30757
- try {
30758
- if (fs16.existsSync(CREDENTIALS_FILE)) {
30759
- return JSON.parse(fs16.readFileSync(CREDENTIALS_FILE, "utf-8"));
30760
- }
30761
- } catch {
30762
- }
30763
- return {};
30764
- }
30765
- function detectClaudeCLI(customPath) {
30766
- const claudeBin = customPath || "claude";
30767
- try {
30768
- const version = (0, import_child_process6.execSync)(`${claudeBin} --version 2>/dev/null`, {
30769
- timeout: 1e4,
30770
- encoding: "utf-8"
30771
- }).trim();
30772
- return { found: true, path: claudeBin, version };
30773
- } catch {
30774
- const commonPaths = [
30775
- "/usr/local/bin/claude",
30776
- path11.join(process.env.HOME || "", ".claude", "bin", "claude"),
30777
- path11.join(process.env.HOME || "", ".local", "bin", "claude")
30778
- ];
30779
- for (const p of commonPaths) {
30780
- try {
30781
- if (fs16.existsSync(p)) {
30782
- const version = (0, import_child_process6.execSync)(`${p} --version 2>/dev/null`, {
30783
- timeout: 1e4,
30784
- encoding: "utf-8"
30785
- }).trim();
30786
- return { found: true, path: p, version };
30787
- }
30788
- } catch {
30789
- continue;
30790
- }
30791
- }
30792
- return { found: false, path: claudeBin };
30793
- }
30794
- }
30795
- function detectBYOKKeys() {
30796
- if (process.env.ANTHROPIC_API_KEY) {
30797
- return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
30798
- }
30799
- if (process.env.OPENAI_API_KEY) {
30800
- return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
30801
- }
30802
- if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
30803
- return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
30804
- }
30805
- const creds = loadCredentials2();
30806
- if (creds.ANTHROPIC_API_KEY) {
30807
- process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
30808
- return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
30809
- }
30810
- if (creds.OPENAI_API_KEY) {
30811
- process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
30812
- return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
30813
- }
30814
- if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
30815
- const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
30816
- process.env.GOOGLE_API_KEY = key;
30817
- return { provider: "google-api", key, source: "credentials" };
30818
- }
30819
- return null;
30820
- }
30821
- function detectProvider(config) {
30822
- const cli = detectClaudeCLI(config?.claudePath);
30823
- if (cli.found) {
30824
- return {
30825
- provider: "claude-cli",
30826
- version: cli.version,
30827
- message: `Found Claude Code CLI${cli.version ? ` (${cli.version})` : ""}. Using your Claude subscription for AI reasoning.`
30828
- };
30829
- }
30830
- const byok = detectBYOKKeys();
30831
- if (byok) {
30832
- const providerNames = {
30833
- "claude-cli": "Claude CLI",
30834
- "anthropic-api": "Anthropic API",
30835
- "openai-api": "OpenAI API",
30836
- "google-api": "Google AI API"
30837
- };
30838
- return {
30839
- provider: byok.provider,
30840
- message: `Using ${providerNames[byok.provider]} key for AI reasoning.`
30841
- };
30842
- }
30843
- return {
30844
- provider: "claude-cli",
30845
- // default, will fail at invocation
30846
- message: "No AI provider found. Install Claude Code CLI or set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY)."
30847
- };
30848
- }
30849
- var CONFIG_DIR = path11.join(process.env.HOME || "", ".ai-analyst");
30850
- var PROVIDER_CONFIG_FILE = path11.join(CONFIG_DIR, "provider.json");
30851
- function loadProviderConfig() {
30852
- try {
30853
- if (fs16.existsSync(PROVIDER_CONFIG_FILE)) {
30854
- return JSON.parse(fs16.readFileSync(PROVIDER_CONFIG_FILE, "utf-8"));
30855
- }
30856
- } catch {
30857
- }
30858
- return null;
30859
- }
30860
- function invokeClaudeCLI(options, claudePath) {
30861
- const bin = claudePath || "claude";
30862
- const args2 = ["--print"];
30863
- if (options.model) {
30864
- args2.push("--model", options.model);
30865
- }
30866
- if (options.jsonSchema) {
30867
- args2.push("--output-format", "json", "--json-schema", JSON.stringify(options.jsonSchema));
30868
- }
30869
- if (options.claudeFlags) {
30870
- args2.push(...options.claudeFlags);
30871
- }
30872
- const useStdin = options.useStdin !== false && options.prompt.length > 1e4;
30873
- if (!useStdin) {
30874
- args2.push("-p", options.prompt);
30875
- }
30876
- const startTime = Date.now();
30877
- const timeoutMs = options.timeoutMs || 3e5;
30878
- return new Promise((resolve3) => {
30879
- const child = (0, import_child_process6.spawn)(bin, args2, {
30880
- cwd: options.cwd || process.cwd(),
30881
- env: process.env,
30882
- stdio: useStdin ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
30883
- });
30884
- let stdout = "";
30885
- let stderr = "";
30886
- child.stdout?.on("data", (data) => {
30887
- stdout += data.toString();
30888
- });
30889
- child.stderr?.on("data", (data) => {
30890
- stderr += data.toString();
30891
- });
30892
- if (useStdin && child.stdin) {
30893
- child.stdin.write(options.prompt);
30894
- child.stdin.end();
30895
- }
30896
- const timeout = setTimeout(() => {
30897
- child.kill("SIGTERM");
30898
- resolve3({
30899
- output: stdout,
30900
- provider: "claude-cli",
30901
- durationMs: Date.now() - startTime,
30902
- error: `Timeout after ${timeoutMs}ms`
30903
- });
30904
- }, timeoutMs);
30905
- child.on("close", (code) => {
30906
- clearTimeout(timeout);
30907
- resolve3({
30908
- output: stdout.trim(),
30909
- provider: "claude-cli",
30910
- durationMs: Date.now() - startTime,
30911
- error: code !== 0 ? `Claude CLI exited with code ${code}: ${stderr}` : void 0
30912
- });
30913
- });
30914
- child.on("error", (err2) => {
30915
- clearTimeout(timeout);
30916
- resolve3({
30917
- output: "",
30918
- provider: "claude-cli",
30919
- durationMs: Date.now() - startTime,
30920
- error: `Failed to spawn Claude CLI: ${err2.message}`
30921
- });
30922
- });
30923
- });
30924
- }
30925
- async function invokeAnthropicAPI(options) {
30926
- const startTime = Date.now();
30927
- const apiKey = process.env.ANTHROPIC_API_KEY;
30928
- if (!apiKey) {
30929
- return {
30930
- output: "",
30931
- provider: "anthropic-api",
30932
- durationMs: 0,
30933
- error: "ANTHROPIC_API_KEY not set"
30934
- };
30935
- }
30936
- try {
30937
- const model = options.model || "claude-sonnet-4-6";
30938
- let prompt = options.prompt;
30939
- if (options.jsonSchema) {
30940
- prompt += `
30941
-
30942
- IMPORTANT: Respond with valid JSON matching this schema:
30943
- ${JSON.stringify(options.jsonSchema, null, 2)}
30944
-
30945
- Output ONLY the JSON object, no markdown code blocks or other text.`;
30946
- } else if (options.expectJson) {
30947
- prompt += "\n\nIMPORTANT: Respond with valid JSON only. No markdown code blocks or other text.";
30948
- }
30949
- const response = await fetch("https://api.anthropic.com/v1/messages", {
30950
- method: "POST",
30951
- headers: {
30952
- "Content-Type": "application/json",
30953
- "x-api-key": apiKey,
30954
- "anthropic-version": "2023-06-01"
30955
- },
30956
- body: JSON.stringify({
30957
- model,
30958
- max_tokens: 16384,
30959
- messages: [{ role: "user", content: prompt }]
30960
- }),
30961
- signal: AbortSignal.timeout(options.timeoutMs || 3e5)
30962
- });
30963
- if (!response.ok) {
30964
- const errorText = await response.text();
30965
- return {
30966
- output: "",
30967
- provider: "anthropic-api",
30968
- durationMs: Date.now() - startTime,
30969
- error: `Anthropic API error ${response.status}: ${errorText}`
30970
- };
30971
- }
30972
- const data = await response.json();
30973
- const text = data.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
30974
- return {
30975
- output: text,
30976
- provider: "anthropic-api",
30977
- durationMs: Date.now() - startTime
30978
- };
30979
- } catch (err2) {
30980
- return {
30981
- output: "",
30982
- provider: "anthropic-api",
30983
- durationMs: Date.now() - startTime,
30984
- error: `Anthropic API call failed: ${err2.message}`
30985
- };
30986
- }
30987
- }
30988
- async function invokeAI(options) {
30989
- const savedConfig = loadProviderConfig();
30990
- const detection = detectProvider(savedConfig || void 0);
30991
- switch (detection.provider) {
30992
- case "claude-cli":
30993
- return invokeClaudeCLI(options, savedConfig?.claudePath);
30994
- case "anthropic-api":
30995
- return invokeAnthropicAPI(options);
30996
- case "openai-api":
30997
- case "google-api":
30998
- return {
30999
- output: "",
31000
- provider: detection.provider,
31001
- durationMs: 0,
31002
- error: `${detection.provider} provider not yet implemented. Use Claude CLI or set ANTHROPIC_API_KEY.`
31003
- };
31004
- default:
31005
- return {
31006
- output: "",
31007
- provider: "claude-cli",
31008
- durationMs: 0,
31009
- error: detection.message
31010
- };
31011
- }
31012
- }
31013
- function requireClaudeCLI(feature) {
31014
- const cli = detectClaudeCLI();
31015
- if (cli.found) {
31016
- return cli.path;
31017
- }
31018
- const featureLabel = feature ? ` (${feature})` : "";
31019
- const byok = detectBYOKKeys();
31020
- if (byok) {
31021
- throw new Error(
31022
- `This feature${featureLabel} requires Claude Code CLI for file and tool access.
31023
- Your API key is detected and works for reasoning tasks (heartbeat, idea evaluation),
31024
- but codebase analysis and implementation need the full CLI.
31025
- Install it with: npm install -g @anthropic-ai/claude-code`
31026
- );
31027
- }
31028
- throw new Error(
31029
- `No AI provider found${featureLabel}.
31030
- Install Claude Code CLI: npm install -g @anthropic-ai/claude-code
31031
- Or set an API key: export ANTHROPIC_API_KEY=sk-ant-...`
31032
- );
31033
- }
31034
-
31035
- // scripts/lib/social/ai-content-generator.ts
31249
+ init_ai_provider();
31036
31250
  function readJsonSafe2(filePath, fallback) {
31037
31251
  try {
31038
31252
  if (!fs17.existsSync(filePath)) return fallback;
@@ -33028,10 +33242,15 @@ async function renderCarousel(carousel, format, outputPath, _depsOverride) {
33028
33242
  console.log(
33029
33243
  "[Renderer] Bundling Remotion compositions (first render \u2014 may take ~1-2 min)..."
33030
33244
  );
33031
- bundleCache = await bundle({ entryPoint });
33245
+ const publicDir = path14.resolve(path14.dirname(entryPoint), "..", "public");
33246
+ bundleCache = await bundle({ entryPoint, publicDir });
33032
33247
  console.log("[Renderer] Bundle complete.");
33033
33248
  }
33034
- const inputProps = { slides: carousel.slides, format };
33249
+ const inputProps = {
33250
+ slides: carousel.slides,
33251
+ format,
33252
+ ...carousel.audio ? { audio: carousel.audio } : {}
33253
+ };
33035
33254
  const composition = await selectComposition({
33036
33255
  serveUrl: bundleCache,
33037
33256
  id: compositionId,
@@ -33307,6 +33526,12 @@ Objection handlers: ${JSON.stringify(copyBank?.objection_handlers ?? [], null, 2
33307
33526
  ## Framework
33308
33527
  Use PAS (Problem \u2192 Agitate \u2192 Solution) or AIDA. Start with a hook that names the pain, build tension, present the solution, close with a high-contrast CTA. Each slide displays for ~3 seconds \u2014 text must be short and punchy.
33309
33528
 
33529
+ ## Audio
33530
+ Pick one background music track that fits the angle's emotional tone:
33531
+ - "corporate-upbeat.mp3" \u2014 energetic, confident, high-momentum (good for burnout relief, 10x moment)
33532
+ - "modern-minimal.mp3" \u2014 clean, calm, professional (good for BYOK, fragmented-tools, trust angles)
33533
+ - "tech-innovation.mp3" \u2014 forward-looking, tech-focused (good for cursor-users, autonomous AI, product angles)
33534
+
33310
33535
  ## Output format
33311
33536
  Return ONLY valid JSON matching this exact schema (no markdown, no explanation):
33312
33537
  {
@@ -33316,6 +33541,10 @@ Return ONLY valid JSON matching this exact schema (no markdown, no explanation):
33316
33541
  "slides": [
33317
33542
  { "type": "<slide_type>", ...slide_props }
33318
33543
  ],
33544
+ "audio": {
33545
+ "src": "<track filename>",
33546
+ "volume": 0.3
33547
+ },
33319
33548
  "theme": {
33320
33549
  "background_style": "gradient",
33321
33550
  "primary_color": "#6366f1",
@@ -33350,6 +33579,7 @@ Return ONLY valid JSON matching this exact schema (no markdown, no explanation):
33350
33579
  { type: "statistic", title: "Results", value: "10x", label: "faster decisions" },
33351
33580
  { type: "cta", title: "Try VibeBusiness", subtitle: "Free to start", cta_text: "Get Started" }
33352
33581
  ],
33582
+ audio: { src: "corporate-upbeat.mp3", volume: 0.3 },
33353
33583
  theme: { background_style: "gradient", primary_color: "#6366f1", text_color: "#ffffff" }
33354
33584
  };
33355
33585
  }
@@ -33740,82 +33970,9 @@ function readCampaignFreshness() {
33740
33970
  };
33741
33971
  }
33742
33972
 
33743
- // scripts/skills/marketing-strategy.ts
33744
- var STAGNATION_WINDOW_DAYS = 14;
33745
- function detectMarketingTriggers(goals, previousGoals) {
33746
- const triggers = [];
33747
- const now = (/* @__PURE__ */ new Date()).toISOString();
33748
- goals.forEach((goal) => {
33749
- if (goal.status === "achieved") {
33750
- return;
33751
- }
33752
- goal.kpis.forEach((kpi) => {
33753
- const stagnation = detectKPIStagnation(kpi, STAGNATION_WINDOW_DAYS);
33754
- if (stagnation && goal.status === "behind") {
33755
- triggers.push({
33756
- id: `trigger-stagnation-${goal.id}-${kpi.id}`,
33757
- type: "kpi_stagnation",
33758
- severity: "urgent",
33759
- description: `KPI "${kpi.name}" has been stagnant at ${kpi.current_value} for ${stagnation.days} days while goal "${goal.title}" is behind schedule`,
33760
- affected_goal_ids: [goal.id],
33761
- suggested_action: `Launch marketing campaign to move ${kpi.name} from ${kpi.current_value} toward target of ${kpi.target_value}`,
33762
- detected_at: now
33763
- });
33764
- }
33765
- });
33766
- if (previousGoals) {
33767
- const previousGoal = previousGoals.find((g2) => g2.id === goal.id);
33768
- if (previousGoal && previousGoal.status === "on_track" && goal.status === "behind") {
33769
- triggers.push({
33770
- id: `trigger-regression-${goal.id}`,
33771
- type: "goal_regression",
33772
- severity: "high",
33773
- description: `Goal "${goal.title}" regressed from "on_track" to "behind" schedule`,
33774
- affected_goal_ids: [goal.id],
33775
- suggested_action: `Analyze why goal regressed and launch recovery campaign`,
33776
- detected_at: now
33777
- });
33778
- }
33779
- }
33780
- });
33781
- return triggers;
33782
- }
33783
- function detectKPIStagnation(kpi, windowDays) {
33784
- if (kpi.history.length < 2) {
33785
- return null;
33786
- }
33787
- const sortedHistory = [...kpi.history].sort(
33788
- (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
33789
- );
33790
- const latestValue = sortedHistory[0].value;
33791
- const latestDate = new Date(sortedHistory[0].date);
33792
- const windowStart = /* @__PURE__ */ new Date();
33793
- windowStart.setDate(windowStart.getDate() - windowDays);
33794
- let isStagnant = true;
33795
- let lastChangeDate = sortedHistory[0].date;
33796
- for (let i = 0; i < sortedHistory.length; i++) {
33797
- const entry = sortedHistory[i];
33798
- const entryDate = new Date(entry.date);
33799
- if (entryDate < windowStart) {
33800
- break;
33801
- }
33802
- if (entry.value !== latestValue) {
33803
- isStagnant = false;
33804
- lastChangeDate = sortedHistory[i - 1]?.date || entry.date;
33805
- break;
33806
- }
33807
- }
33808
- if (isStagnant && latestDate > windowStart) {
33809
- const daysDiff = Math.floor(
33810
- (Date.now() - new Date(lastChangeDate).getTime()) / (1e3 * 60 * 60 * 24)
33811
- );
33812
- return {
33813
- days: daysDiff,
33814
- last_change_date: lastChangeDate
33815
- };
33816
- }
33817
- return null;
33818
- }
33973
+ // scripts/heartbeat.ts
33974
+ init_marketing_strategy();
33975
+ init_ai_provider();
33819
33976
 
33820
33977
  // scripts/lib/run.ts
33821
33978
  var path18 = __toESM(require("path"));
@@ -34714,7 +34871,7 @@ function sleep(ms2) {
34714
34871
  }
34715
34872
  function loadJson(filePath, defaultValue) {
34716
34873
  try {
34717
- const content = fs33.readFileSync(filePath, "utf-8");
34874
+ const content = fs34.readFileSync(filePath, "utf-8");
34718
34875
  return JSON.parse(content);
34719
34876
  } catch {
34720
34877
  return defaultValue;
@@ -34769,7 +34926,7 @@ function isMetaTaskFalseCompletion(output) {
34769
34926
  }
34770
34927
  function reclassifyAsHumanDependent(taskId) {
34771
34928
  try {
34772
- let content = fs33.readFileSync(TODO_FILE, "utf-8");
34929
+ let content = fs34.readFileSync(TODO_FILE, "utf-8");
34773
34930
  const taskPattern = new RegExp(`^- \\[[ x]\\] \`${taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\`.*$`, "m");
34774
34931
  const match = content.match(taskPattern);
34775
34932
  if (!match) return;
@@ -34782,7 +34939,7 @@ function reclassifyAsHumanDependent(taskId) {
34782
34939
  const insertIndex = content.indexOf("\n", blockedIndex) + 1;
34783
34940
  content = content.slice(0, insertIndex) + "\n" + uncheckedLine + content.slice(insertIndex);
34784
34941
  }
34785
- fs33.writeFileSync(TODO_FILE, content);
34942
+ fs34.writeFileSync(TODO_FILE, content);
34786
34943
  log(`Reclassified task ${taskId} as human-dependent (moved to Blocked)`);
34787
34944
  } catch (error) {
34788
34945
  log(`Failed to reclassify task: ${error instanceof Error ? error.message : "Unknown"}`);
@@ -34790,7 +34947,7 @@ function reclassifyAsHumanDependent(taskId) {
34790
34947
  }
34791
34948
  function getMetaTaskFailureCount(taskId) {
34792
34949
  try {
34793
- const content = fs33.readFileSync(TODO_FILE, "utf-8");
34950
+ const content = fs34.readFileSync(TODO_FILE, "utf-8");
34794
34951
  const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34795
34952
  const match = content.match(new RegExp(`\`${escapedId}\`.*\\[failed:(\\d+)\\]`));
34796
34953
  return match ? parseInt(match[1], 10) : 0;
@@ -34800,7 +34957,7 @@ function getMetaTaskFailureCount(taskId) {
34800
34957
  }
34801
34958
  function incrementMetaTaskFailureCount(taskId) {
34802
34959
  try {
34803
- let content = fs33.readFileSync(TODO_FILE, "utf-8");
34960
+ let content = fs34.readFileSync(TODO_FILE, "utf-8");
34804
34961
  const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34805
34962
  const linePattern = new RegExp(`^(- \\[[ x]\\] \`${escapedId}\`.*)$`, "m");
34806
34963
  const lineMatch = content.match(linePattern);
@@ -34816,7 +34973,7 @@ function incrementMetaTaskFailureCount(taskId) {
34816
34973
  newLine = `${line} [failed:${newCount}]`;
34817
34974
  }
34818
34975
  content = content.replace(line, newLine);
34819
- fs33.writeFileSync(TODO_FILE, content);
34976
+ fs34.writeFileSync(TODO_FILE, content);
34820
34977
  log(`Meta-task ${taskId} failure count: ${newCount}`);
34821
34978
  return newCount;
34822
34979
  } catch (error) {
@@ -34826,7 +34983,7 @@ function incrementMetaTaskFailureCount(taskId) {
34826
34983
  }
34827
34984
  function parseTodoFile() {
34828
34985
  try {
34829
- const content = fs33.readFileSync(TODO_FILE, "utf-8");
34986
+ const content = fs34.readFileSync(TODO_FILE, "utf-8");
34830
34987
  const tasks = [];
34831
34988
  let currentSection = "high_priority";
34832
34989
  const lines = content.split("\n");
@@ -35247,18 +35404,18 @@ function autoRecoverDeferredParseFailures() {
35247
35404
  async function generateMissingShipCards(ideas) {
35248
35405
  const shippedIdeas = ideas.filter((i) => i.stage === "shipped");
35249
35406
  if (shippedIdeas.length === 0) return;
35250
- const assetsDir = path28.join(__dirname, "assets");
35251
- const hasRegular = fs33.existsSync(path28.join(assetsDir, "Inter-Regular.ttf"));
35252
- const hasBold = fs33.existsSync(path28.join(assetsDir, "Inter-Bold.ttf"));
35407
+ const assetsDir = path29.join(__dirname, "assets");
35408
+ const hasRegular = fs34.existsSync(path29.join(assetsDir, "Inter-Regular.ttf"));
35409
+ const hasBold = fs34.existsSync(path29.join(assetsDir, "Inter-Bold.ttf"));
35253
35410
  if (!hasRegular && !hasBold) return;
35254
- const visualsDir = path28.join(DATA_DIR, "reports", "visuals");
35411
+ const visualsDir = path29.join(DATA_DIR, "reports", "visuals");
35255
35412
  let generated = 0;
35256
35413
  for (const idea of shippedIdeas) {
35257
- const cardPath = path28.join(visualsDir, `${idea.id}-card.png`);
35258
- if (fs33.existsSync(cardPath)) continue;
35414
+ const cardPath = path29.join(visualsDir, `${idea.id}-card.png`);
35415
+ if (fs34.existsSync(cardPath)) continue;
35259
35416
  if (idea.implementation?.preview_url) {
35260
- const screenshotPath = path28.join(DATA_DIR, "reports", `${idea.id}-screenshot.png`);
35261
- if (!fs33.existsSync(screenshotPath)) {
35417
+ const screenshotPath = path29.join(DATA_DIR, "reports", `${idea.id}-screenshot.png`);
35418
+ if (!fs34.existsSync(screenshotPath)) {
35262
35419
  try {
35263
35420
  const { captureIdeaScreenshot: captureIdeaScreenshot2 } = await Promise.resolve().then(() => (init_screenshot(), screenshot_exports));
35264
35421
  await captureIdeaScreenshot2(idea.id, idea.implementation.preview_url);
@@ -35359,11 +35516,37 @@ function detectMarketingTriggersIfNeeded(goals) {
35359
35516
  }
35360
35517
  return detectMarketingTriggers(goals);
35361
35518
  }
35519
+ async function syncMarketingPlansToIdeas() {
35520
+ const { addMarketingIdeasToDashboard: addMarketingIdeasToDashboard2 } = await Promise.resolve().then(() => (init_marketing_integration(), marketing_integration_exports));
35521
+ const plansDir = path29.join(DATA_DIR, "marketing-plans");
35522
+ if (!fs34.existsSync(plansDir)) {
35523
+ return;
35524
+ }
35525
+ const planFiles = fs34.readdirSync(plansDir).filter((f) => f.endsWith(".json"));
35526
+ let totalAdded = 0;
35527
+ let totalSkipped = 0;
35528
+ for (const file of planFiles) {
35529
+ try {
35530
+ const planPath = path29.join(plansDir, file);
35531
+ const plan = JSON.parse(fs34.readFileSync(planPath, "utf-8"));
35532
+ if (plan.status === "approved" || plan.status === "active") {
35533
+ const result = await addMarketingIdeasToDashboard2(plan, DATA_DIR);
35534
+ totalAdded += result.added;
35535
+ totalSkipped += result.skipped;
35536
+ }
35537
+ } catch (err2) {
35538
+ log(`Failed to sync marketing plan ${file}: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
35539
+ }
35540
+ }
35541
+ if (totalAdded > 0) {
35542
+ log(`\u2713 Added ${totalAdded} marketing campaign ideas to dashboard (${totalSkipped} already existed)`);
35543
+ }
35544
+ }
35362
35545
  function loadCodebaseSnapshot() {
35363
- const snapshotPath = path28.join(DATA_DIR, "codebase-snapshot.json");
35546
+ const snapshotPath = path29.join(DATA_DIR, "codebase-snapshot.json");
35364
35547
  try {
35365
- if (fs33.existsSync(snapshotPath)) {
35366
- return JSON.parse(fs33.readFileSync(snapshotPath, "utf-8"));
35548
+ if (fs34.existsSync(snapshotPath)) {
35549
+ return JSON.parse(fs34.readFileSync(snapshotPath, "utf-8"));
35367
35550
  }
35368
35551
  } catch {
35369
35552
  }
@@ -35918,9 +36101,9 @@ Focus on actionable, specific recommendations. Be concise.`;
35918
36101
  }
35919
36102
  function addTasksToTodo(tasks) {
35920
36103
  if (tasks.length === 0) return;
35921
- if (!fs33.existsSync(TODO_FILE)) return;
36104
+ if (!fs34.existsSync(TODO_FILE)) return;
35922
36105
  try {
35923
- let content = fs33.readFileSync(TODO_FILE, "utf-8");
36106
+ let content = fs34.readFileSync(TODO_FILE, "utf-8");
35924
36107
  if (!content.includes("## High Priority (Do Now)")) {
35925
36108
  log('TODO.md missing "## High Priority (Do Now)" section \u2014 creating it');
35926
36109
  const scheduledIdx = content.indexOf("## Scheduled");
@@ -36020,15 +36203,15 @@ function addTasksToTodo(tasks) {
36020
36203
  }
36021
36204
  log(`Added TODO: ${task.id} \u2014 ${task.description}`);
36022
36205
  }
36023
- fs33.writeFileSync(TODO_FILE, content);
36206
+ fs34.writeFileSync(TODO_FILE, content);
36024
36207
  } catch (error) {
36025
36208
  log(`Failed to add tasks to TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
36026
36209
  }
36027
36210
  }
36028
36211
  function archiveCompletedTodos() {
36029
36212
  try {
36030
- if (!fs33.existsSync(TODO_FILE)) return;
36031
- let content = fs33.readFileSync(TODO_FILE, "utf-8");
36213
+ if (!fs34.existsSync(TODO_FILE)) return;
36214
+ let content = fs34.readFileSync(TODO_FILE, "utf-8");
36032
36215
  const lines = content.split("\n");
36033
36216
  const completedLines = [];
36034
36217
  const newLines = [];
@@ -36054,7 +36237,7 @@ function archiveCompletedTodos() {
36054
36237
  const archiveBlock = completedLines.join("\n") + "\n";
36055
36238
  content = content.slice(0, insertIndex) + archiveBlock + content.slice(insertIndex);
36056
36239
  }
36057
- fs33.writeFileSync(TODO_FILE, content);
36240
+ fs34.writeFileSync(TODO_FILE, content);
36058
36241
  log(`Archived ${completedLines.length} completed TODO(s) to Completed This Week`);
36059
36242
  } catch (error) {
36060
36243
  log(`Failed to archive completed TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -36062,8 +36245,8 @@ function archiveCompletedTodos() {
36062
36245
  }
36063
36246
  function pruneStaleScheduledTodos(state) {
36064
36247
  try {
36065
- if (!fs33.existsSync(TODO_FILE)) return;
36066
- const content = fs33.readFileSync(TODO_FILE, "utf-8");
36248
+ if (!fs34.existsSync(TODO_FILE)) return;
36249
+ const content = fs34.readFileSync(TODO_FILE, "utf-8");
36067
36250
  const lines = content.split("\n");
36068
36251
  const prunedIds = [];
36069
36252
  const terminalIdeaIds = new Set(
@@ -36099,7 +36282,7 @@ function pruneStaleScheduledTodos(state) {
36099
36282
  return true;
36100
36283
  });
36101
36284
  if (prunedIds.length === 0) return;
36102
- fs33.writeFileSync(TODO_FILE, newLines.join("\n"));
36285
+ fs34.writeFileSync(TODO_FILE, newLines.join("\n"));
36103
36286
  log(`Pruned ${prunedIds.length} stale TODO(s): ${prunedIds.join(", ")}`);
36104
36287
  } catch (error) {
36105
36288
  log(`Failed to prune stale TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -36107,8 +36290,8 @@ function pruneStaleScheduledTodos(state) {
36107
36290
  }
36108
36291
  function deduplicateActiveTodos() {
36109
36292
  try {
36110
- if (!fs33.existsSync(TODO_FILE)) return;
36111
- const content = fs33.readFileSync(TODO_FILE, "utf-8");
36293
+ if (!fs34.existsSync(TODO_FILE)) return;
36294
+ const content = fs34.readFileSync(TODO_FILE, "utf-8");
36112
36295
  const lines = content.split("\n");
36113
36296
  const removedIds = [];
36114
36297
  let currentSection = "";
@@ -36145,7 +36328,7 @@ function deduplicateActiveTodos() {
36145
36328
  if (linesToRemove.size === 0) return;
36146
36329
  const newLines = lines.filter((_, i) => !linesToRemove.has(i));
36147
36330
  const cleaned = newLines.join("\n").replace(/\n{3,}/g, "\n\n");
36148
- fs33.writeFileSync(TODO_FILE, cleaned);
36331
+ fs34.writeFileSync(TODO_FILE, cleaned);
36149
36332
  log(`Deduplicated ${removedIds.length} TODO(s): ${removedIds.join(", ")}`);
36150
36333
  } catch (error) {
36151
36334
  log(`Failed to deduplicate TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -36153,8 +36336,8 @@ function deduplicateActiveTodos() {
36153
36336
  }
36154
36337
  function readMemoryContext() {
36155
36338
  try {
36156
- if (!fs33.existsSync(MEMORY_FILE)) return "";
36157
- const content = fs33.readFileSync(MEMORY_FILE, "utf-8");
36339
+ if (!fs34.existsSync(MEMORY_FILE)) return "";
36340
+ const content = fs34.readFileSync(MEMORY_FILE, "utf-8");
36158
36341
  const MAX_TOTAL_LINES = 100;
36159
36342
  const MIN_LEARNING_ENTRIES = 15;
36160
36343
  const learningsSplit = content.indexOf("### Heartbeat Learnings");
@@ -36175,14 +36358,14 @@ function readMemoryContext() {
36175
36358
  }
36176
36359
  function appendToMemory(learnings) {
36177
36360
  if (learnings.length === 0) return;
36178
- if (!fs33.existsSync(MEMORY_FILE)) return;
36361
+ if (!fs34.existsSync(MEMORY_FILE)) return;
36179
36362
  const MAX_LEARNINGS_PER_HEARTBEAT = 2;
36180
36363
  let filtered = learnings.slice(0, MAX_LEARNINGS_PER_HEARTBEAT);
36181
36364
  if (learnings.length > MAX_LEARNINGS_PER_HEARTBEAT) {
36182
36365
  log(`Capped learnings from ${learnings.length} to ${MAX_LEARNINGS_PER_HEARTBEAT}`);
36183
36366
  }
36184
36367
  try {
36185
- let content = fs33.readFileSync(MEMORY_FILE, "utf-8");
36368
+ let content = fs34.readFileSync(MEMORY_FILE, "utf-8");
36186
36369
  const existingLines = content.split("\n").filter((l2) => l2.match(/^- \*\*/));
36187
36370
  const KEY_PHRASE_PATTERN = /(?:idea-[a-f0-9]{8}|idea-[a-z0-9-]+|hyp-[a-z0-9-]+|goal-[a-z0-9-]+|pipeline starvation|critical path|batch triage|cooldown|decompos|timeout|inbox bloat|dedup|meta-task|heartbeat|autonomy gate|false completion)/gi;
36188
36371
  filtered = filtered.filter((learning) => {
@@ -36232,7 +36415,7 @@ function appendToMemory(learnings) {
36232
36415
  const subsectionEnd = subsectionEndMatch ? subsectionIndex + 22 + (subsectionEndMatch.index || 0) : insertPoint;
36233
36416
  content = content.slice(0, subsectionEnd) + newEntries + "\n" + content.slice(subsectionEnd);
36234
36417
  }
36235
- fs33.writeFileSync(MEMORY_FILE, content);
36418
+ fs34.writeFileSync(MEMORY_FILE, content);
36236
36419
  log(`Added ${filtered.length} learning(s) to MEMORY.md`);
36237
36420
  }
36238
36421
  } catch (error) {
@@ -36241,8 +36424,8 @@ function appendToMemory(learnings) {
36241
36424
  }
36242
36425
  function pruneMemoryLearnings() {
36243
36426
  try {
36244
- if (!fs33.existsSync(MEMORY_FILE)) return;
36245
- const content = fs33.readFileSync(MEMORY_FILE, "utf-8");
36427
+ if (!fs34.existsSync(MEMORY_FILE)) return;
36428
+ const content = fs34.readFileSync(MEMORY_FILE, "utf-8");
36246
36429
  const learningsHeader = "### Heartbeat Learnings";
36247
36430
  const learningsIdx = content.indexOf(learningsHeader);
36248
36431
  if (learningsIdx === -1) return;
@@ -36278,7 +36461,7 @@ function pruneMemoryLearnings() {
36278
36461
  if (prunedCount === 0) return;
36279
36462
  const newLearningsBody = headerLine + "\n\n" + finalEntries.join("\n") + "\n";
36280
36463
  const newContent = beforeLearnings + newLearningsBody + afterLearnings;
36281
- fs33.writeFileSync(MEMORY_FILE, newContent);
36464
+ fs34.writeFileSync(MEMORY_FILE, newContent);
36282
36465
  log(`Pruned ${prunedCount} duplicate/stale learnings (${entryLines.length} \u2192 ${finalEntries.length})`);
36283
36466
  } catch (error) {
36284
36467
  log(`Failed to prune memory learnings: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -36397,7 +36580,7 @@ function detectTargetRepo(idea, config) {
36397
36580
  const filesAnalyzed = idea.source.files_analyzed || [];
36398
36581
  for (const file of filesAnalyzed) {
36399
36582
  for (const repo of config.repos) {
36400
- if (file.includes(repo.name) || file.includes(path28.basename(repo.path))) {
36583
+ if (file.includes(repo.name) || file.includes(path29.basename(repo.path))) {
36401
36584
  return repo;
36402
36585
  }
36403
36586
  }
@@ -36690,10 +36873,10 @@ function runTestsForRepo(workspacePath, testCommands, sourceRepoPath) {
36690
36873
  const allOutput = [];
36691
36874
  const env = { ...process.env };
36692
36875
  if (sourceRepoPath) {
36693
- const venvBin = path28.join(sourceRepoPath, ".venv", "bin");
36694
- if (fs33.existsSync(venvBin)) {
36876
+ const venvBin = path29.join(sourceRepoPath, ".venv", "bin");
36877
+ if (fs34.existsSync(venvBin)) {
36695
36878
  env.PATH = `${venvBin}:${env.PATH}`;
36696
- env.VIRTUAL_ENV = path28.join(sourceRepoPath, ".venv");
36879
+ env.VIRTUAL_ENV = path29.join(sourceRepoPath, ".venv");
36697
36880
  log(`Using venv from ${sourceRepoPath}/.venv`);
36698
36881
  }
36699
36882
  }
@@ -36746,7 +36929,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
36746
36929
  });
36747
36930
  const scopeBase64 = Buffer.from(scopePayload).toString("base64");
36748
36931
  const timeoutMs = autonomy.max_sub_task_timeout_ms || 3e5;
36749
- const workspacePath = options?.worktreePath || path28.join(config.workspace_dir, targetRepo.name);
36932
+ const workspacePath = options?.worktreePath || path29.join(config.workspace_dir, targetRepo.name);
36750
36933
  const executionStartTime = Date.now();
36751
36934
  const updateSubTaskWithDetails = (status, updateOpts = {}) => {
36752
36935
  const endTime = Date.now();
@@ -36988,7 +37171,7 @@ async function executeAutonomousImplementation(ideaId) {
36988
37171
  if (autonomy.auto_merge) {
36989
37172
  const branchName2 = idea.implementation.branch_name;
36990
37173
  const defaultBranch2 = targetRepo2.default_branch || "main";
36991
- const workspacePath = path28.join(config.workspace_dir, targetRepo2.name);
37174
+ const workspacePath = path29.join(config.workspace_dir, targetRepo2.name);
36992
37175
  if (branchName2) {
36993
37176
  try {
36994
37177
  log(`Auto-merging branch ${branchName2} into ${defaultBranch2}...`);
@@ -38571,7 +38754,7 @@ async function runAnalysis(type) {
38571
38754
  }
38572
38755
  function updateTodoFile(taskId, completed) {
38573
38756
  try {
38574
- let content = fs33.readFileSync(TODO_FILE, "utf-8");
38757
+ let content = fs34.readFileSync(TODO_FILE, "utf-8");
38575
38758
  const uncheckedPattern = new RegExp(`- \\[ \\] \`${taskId}\``, "g");
38576
38759
  const checkedPattern = new RegExp(`- \\[x\\] \`${taskId}\``, "g");
38577
38760
  if (completed) {
@@ -38579,7 +38762,7 @@ function updateTodoFile(taskId, completed) {
38579
38762
  } else {
38580
38763
  content = content.replace(checkedPattern, `- [ ] \`${taskId}\``);
38581
38764
  }
38582
- fs33.writeFileSync(TODO_FILE, content);
38765
+ fs34.writeFileSync(TODO_FILE, content);
38583
38766
  log(`Updated TODO.md: ${taskId} marked as ${completed ? "completed" : "pending"}`);
38584
38767
  } catch (error) {
38585
38768
  log(`Failed to update TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -38698,8 +38881,8 @@ async function runSingleHeartbeat(beatNumber = 1) {
38698
38881
  if (vibeResult.warning) log(vibeResult.warning);
38699
38882
  log(`Vibe consumed (${vibeResult.vibes_remaining} remaining${vibeResult.plan ? `, plan: ${vibeResult.plan}` : ""}${vibeResult.offline ? ", offline" : ""})`);
38700
38883
  }
38701
- const commandsDir = path28.join(PROJECT_DIR, ".claude", "commands");
38702
- if (!fs33.existsSync(commandsDir)) {
38884
+ const commandsDir = path29.join(PROJECT_DIR, ".claude", "commands");
38885
+ if (!fs34.existsSync(commandsDir)) {
38703
38886
  log("Slash commands missing \u2014 auto-scaffolding...");
38704
38887
  scaffoldSlashCommands(PROJECT_DIR);
38705
38888
  }
@@ -38760,6 +38943,9 @@ async function runSingleHeartbeat(beatNumber = 1) {
38760
38943
  alerts.push(`MARKETING: ${t.description}`);
38761
38944
  });
38762
38945
  }
38946
+ if (!DRY_RUN) {
38947
+ await syncMarketingPlansToIdeas();
38948
+ }
38763
38949
  if (!DRY_RUN) {
38764
38950
  await generateMissingShipCards(state.ideas);
38765
38951
  }
@@ -38909,7 +39095,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
38909
39095
  }
38910
39096
  if (!DRY_RUN) {
38911
39097
  const statusContent = generateStatusContent(state, alerts, taskToDo, reasoning, systemHealth);
38912
- fs33.writeFileSync(STATUS_FILE, statusContent);
39098
+ fs34.writeFileSync(STATUS_FILE, statusContent);
38913
39099
  log("Updated STATUS.md");
38914
39100
  } else {
38915
39101
  log("[DRY RUN] Would update STATUS.md");
@@ -39024,7 +39210,7 @@ ${"=".repeat(60)}
39024
39210
  `);
39025
39211
  }
39026
39212
  function saveSessionToJson(summary) {
39027
- const sessionsFile = path28.join(DATA_DIR, "heartbeat-sessions.json");
39213
+ const sessionsFile = path29.join(DATA_DIR, "heartbeat-sessions.json");
39028
39214
  const sessionsData = loadJson(sessionsFile, { sessions: [] });
39029
39215
  sessionsData.sessions.push(summary);
39030
39216
  if (sessionsData.sessions.length > 50) {