vibebusiness 1.2.65 → 1.2.68

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 (56) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +19 -19
  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/investor-updates/generate/route.js +1 -1
  10. package/.next/standalone/.next/server/app/api/investor-updates/generate/route.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js +1 -1
  12. package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/settings.html +1 -1
  24. package/.next/standalone/.next/server/app/settings.rsc +1 -1
  25. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/social.html +1 -1
  27. package/.next/standalone/.next/server/app/social.rsc +1 -1
  28. package/.next/standalone/.next/server/app/updates/[id]/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/updates/new/page.js +1 -1
  30. package/.next/standalone/.next/server/app/updates/new/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/.next/server/app/updates/new.html +1 -1
  32. package/.next/standalone/.next/server/app/updates/new.rsc +2 -2
  33. package/.next/standalone/.next/server/app/updates/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app-paths-manifest.json +13 -13
  35. package/.next/standalone/.next/server/chunks/{7599.js → 3794.js} +33 -25
  36. package/.next/standalone/.next/server/pages/404.html +1 -1
  37. package/.next/standalone/.next/server/pages/500.html +1 -1
  38. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  39. package/.next/standalone/data/business-context.json +1 -1
  40. package/.next/standalone/data/goals.json +93 -12
  41. package/.next/standalone/data/ideas.json +58 -17
  42. package/.next/standalone/data/implementations.json +53 -0
  43. package/.next/standalone/data/social.json +49 -1
  44. package/.next/standalone/package.json +1 -1
  45. package/.next/standalone/scripts/investor-update.ts +249 -12
  46. package/.next/standalone/scripts/skills/social-media.ts +239 -34
  47. package/.next/static/chunks/app/updates/new/page-dcc67ffca587dcc2.js +1 -0
  48. package/dist/scripts/heartbeat.js +205 -30
  49. package/dist/scripts/social-routine.js +205 -30
  50. package/package.json +1 -1
  51. package/.next/standalone/.next/server/chunks/1685.js +0 -6
  52. package/.next/standalone/.next/server/chunks/7564.js +0 -81
  53. package/.next/standalone/scripts/skills/investor-update.ts +0 -476
  54. package/.next/static/chunks/app/updates/new/page-c518fba0d9ffae98.js +0 -1
  55. /package/.next/static/{yXi8K8u58qskYid0oHU5f → Gsf15cMKd-7htpbN5eZHd}/_buildManifest.js +0 -0
  56. /package/.next/static/{yXi8K8u58qskYid0oHU5f → Gsf15cMKd-7htpbN5eZHd}/_ssgManifest.js +0 -0
@@ -58,7 +58,7 @@ import {
58
58
  analyzeEngagement,
59
59
  type TweetTopic,
60
60
  } from '../lib/social/ai-content-generator';
61
- import type { SocialConfig, SocialDraft, SocialDraftSource, SocialReplyTarget, TweetEngagement, BusinessGoal, PositioningData } from '../../src/lib/types';
61
+ import type { SocialConfig, SocialDraft, SocialDraftSource, SocialReplyTarget, TweetEngagement, BusinessGoal, PositioningData, BusinessIdea } from '../../src/lib/types';
62
62
 
63
63
  // ─── State ───────────────────────────────────────────────────────────────────
64
64
 
@@ -222,16 +222,16 @@ export async function executeSocialTask(
222
222
  return executeDraftShipVisual();
223
223
  }
224
224
  if (taskId === 'social-draft-ship') {
225
- return executeDraftShip();
225
+ return await executeDraftShip();
226
226
  }
227
227
  if (taskId === 'social-draft-update') {
228
- return executeDraftUpdate();
228
+ return await executeDraftUpdate();
229
229
  }
230
230
  if (taskId === 'social-draft-milestone') {
231
- return executeDraftMilestone();
231
+ return await executeDraftMilestone();
232
232
  }
233
233
  if (taskId === 'social-draft-digest') {
234
- return executeDraftDigest();
234
+ return await executeDraftDigest();
235
235
  }
236
236
  if (taskId.startsWith('social-draft-viral-')) {
237
237
  const topic = taskId.replace('social-draft-viral-', '') as TweetTopic;
@@ -672,31 +672,64 @@ async function executeSetupX(): Promise<{ success: boolean; output: string }> {
672
672
  };
673
673
  }
674
674
 
675
- function executeDraftShip(): { success: boolean; output: string } {
675
+ async function executeDraftShip(): Promise<{ success: boolean; output: string }> {
676
676
  const state = loadState();
677
- const text = generateShipTweet();
678
- if (!text) {
679
- return { success: false, output: 'No shipped ideas found to generate a tweet from.' };
680
- }
681
677
 
682
- // Dedup: check if latest shipped idea already has a draft
683
- const { ideas } = JSON.parse(fs.readFileSync(
684
- path.join(path.dirname(SOCIAL_FILE), 'ideas.json'),
685
- 'utf-8',
686
- )) as { ideas: Array<{ id: string; stage: string; updated_at: string }> };
678
+ // Load all shipped ideas to find the latest
679
+ let allIdeas: BusinessIdea[] = [];
680
+ try {
681
+ const { ideas } = JSON.parse(fs.readFileSync(IDEAS_FILE, 'utf-8')) as { ideas: BusinessIdea[] };
682
+ allIdeas = ideas;
683
+ } catch {
684
+ return { success: false, output: 'Could not read ideas.json.' };
685
+ }
687
686
 
688
- const latestShipped = ideas
687
+ const latestShipped = allIdeas
689
688
  .filter((i) => i.stage === 'shipped')
690
689
  .sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
691
690
 
692
- if (latestShipped && hasDraftOrPublished(state, latestShipped.id)) {
691
+ if (!latestShipped) {
692
+ return { success: false, output: 'No shipped ideas found to generate a tweet from.' };
693
+ }
694
+
695
+ if (hasDraftOrPublished(state, latestShipped.id)) {
693
696
  return { success: false, output: `Draft already exists for shipped idea ${latestShipped.id}` };
694
697
  }
695
698
 
696
- const draft = createDraft(state, text, 'ship', latestShipped?.id || null);
699
+ // Build rich context and call AI
700
+ let text: string;
701
+ let hookType: string | undefined;
702
+ let estimatedEngagement: string | undefined;
703
+
704
+ try {
705
+ const context = buildShipContext(latestShipped);
706
+ let positioning: PositioningData | null = null;
707
+ try {
708
+ if (fs.existsSync(POSITIONING_FILE)) {
709
+ positioning = JSON.parse(fs.readFileSync(POSITIONING_FILE, 'utf-8'));
710
+ }
711
+ } catch { /* ignore */ }
712
+ const pastPerformance = collectEngagementHistory(state);
713
+ const result = await generateViralTweet({ topic: 'ship', context, positioning, pastPerformance });
714
+ text = result.text;
715
+ hookType = result.hook_type;
716
+ estimatedEngagement = result.estimated_engagement;
717
+ } catch {
718
+ // Fallback to template
719
+ const fallback = generateShipTweet();
720
+ if (!fallback) {
721
+ return { success: false, output: 'No shipped ideas found to generate a tweet from.' };
722
+ }
723
+ text = fallback;
724
+ }
725
+
726
+ const draft = createDraft(state, text, 'ship', latestShipped.id);
697
727
  saveState(state);
698
728
 
699
- return { success: true, output: `Created ship draft: "${draft.text}" (${draft.id})` };
729
+ const outputParts = [`Created ship draft: "${draft.text}" (${draft.id})`];
730
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
731
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
732
+ return { success: true, output: outputParts.join('\n') };
700
733
  }
701
734
 
702
735
  function executeDraftShipVisual(): { success: boolean; output: string } {
@@ -752,43 +785,195 @@ function executeDraftShipVisual(): { success: boolean; output: string } {
752
785
  };
753
786
  }
754
787
 
755
- function executeDraftUpdate(): { success: boolean; output: string } {
788
+ async function executeDraftUpdate(): Promise<{ success: boolean; output: string }> {
756
789
  const state = loadState();
757
- const text = generateProductUpdateTweet();
758
- if (!text) {
759
- return { success: false, output: 'No positioning data found to generate update tweet.' };
790
+
791
+ let text: string;
792
+ let hookType: string | undefined;
793
+ let estimatedEngagement: string | undefined;
794
+
795
+ try {
796
+ let positioning: PositioningData | null = null;
797
+ try {
798
+ if (fs.existsSync(POSITIONING_FILE)) {
799
+ positioning = JSON.parse(fs.readFileSync(POSITIONING_FILE, 'utf-8'));
800
+ }
801
+ } catch { /* ignore */ }
802
+
803
+ const contextParts: string[] = [];
804
+ if (positioning?.current?.tagline) contextParts.push(`Tagline: ${positioning.current.tagline}`);
805
+ if (positioning?.current?.value_proposition) {
806
+ const s = positioning.current.value_proposition.split(/[.!]/)[0]?.trim();
807
+ if (s) contextParts.push(`Value prop: ${s}`);
808
+ }
809
+ if (positioning?.copy_bank?.pain_points?.length) {
810
+ const pains = positioning.copy_bank.pain_points.slice(0, 2).map((p) => p.pain);
811
+ contextParts.push(`Pain points: ${pains.join(', ')}`);
812
+ }
813
+ if (positioning?.current?.differentiators?.length) {
814
+ contextParts.push(`Differentiator: ${positioning.current.differentiators[0]}`);
815
+ }
816
+
817
+ if (contextParts.length === 0) {
818
+ throw new Error('No positioning context available');
819
+ }
820
+
821
+ const context = contextParts.join('\n');
822
+ const pastPerformance = collectEngagementHistory(state);
823
+ const result = await generateViralTweet({ topic: 'update', context, positioning, pastPerformance });
824
+ text = result.text;
825
+ hookType = result.hook_type;
826
+ estimatedEngagement = result.estimated_engagement;
827
+ } catch {
828
+ // Fallback to template
829
+ const fallback = generateProductUpdateTweet();
830
+ if (!fallback) {
831
+ return { success: false, output: 'No positioning data found to generate update tweet.' };
832
+ }
833
+ text = fallback;
760
834
  }
761
835
 
762
836
  const draft = createDraft(state, text, 'update', null);
763
837
  saveState(state);
764
838
 
765
- return { success: true, output: `Created update draft: "${draft.text}" (${draft.id})` };
839
+ const outputParts = [`Created update draft: "${draft.text}" (${draft.id})`];
840
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
841
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
842
+ return { success: true, output: outputParts.join('\n') };
766
843
  }
767
844
 
768
- function executeDraftMilestone(): { success: boolean; output: string } {
845
+ async function executeDraftMilestone(): Promise<{ success: boolean; output: string }> {
769
846
  const state = loadState();
770
- const text = generateMilestoneTweet();
771
- if (!text) {
772
- return { success: false, output: 'No KPI milestones found to celebrate.' };
847
+
848
+ let text: string;
849
+ let hookType: string | undefined;
850
+ let estimatedEngagement: string | undefined;
851
+
852
+ try {
853
+ // Auto-detect KPI that hit or is approaching its target
854
+ let kpiContext = '';
855
+ try {
856
+ if (fs.existsSync(GOALS_FILE)) {
857
+ const { goals } = JSON.parse(fs.readFileSync(GOALS_FILE, 'utf-8')) as { goals: BusinessGoal[] };
858
+ for (const goal of goals) {
859
+ for (const kpi of goal.kpis) {
860
+ if (kpi.current_value != null && kpi.target_value != null) {
861
+ const pct = kpi.current_value / kpi.target_value;
862
+ if (pct >= 0.8) {
863
+ kpiContext = [
864
+ `KPI: ${kpi.name}`,
865
+ `Current: ${kpi.current_value} ${kpi.unit}`,
866
+ `Target: ${kpi.target_value} ${kpi.unit}`,
867
+ `Goal: ${goal.title}`,
868
+ ].join('\n');
869
+ break;
870
+ }
871
+ }
872
+ }
873
+ if (kpiContext) break;
874
+ }
875
+ }
876
+ } catch { /* ignore */ }
877
+
878
+ if (!kpiContext) {
879
+ throw new Error('No milestone KPI context available');
880
+ }
881
+
882
+ const pastPerformance = collectEngagementHistory(state);
883
+ const result = await generateViralTweet({ topic: 'milestone', context: kpiContext, pastPerformance });
884
+ text = result.text;
885
+ hookType = result.hook_type;
886
+ estimatedEngagement = result.estimated_engagement;
887
+ } catch {
888
+ // Fallback to template
889
+ const fallback = generateMilestoneTweet();
890
+ if (!fallback) {
891
+ return { success: false, output: 'No KPI milestones found to celebrate.' };
892
+ }
893
+ text = fallback;
773
894
  }
774
895
 
775
896
  const draft = createDraft(state, text, 'milestone', null);
776
897
  saveState(state);
777
898
 
778
- return { success: true, output: `Created milestone draft: "${draft.text}" (${draft.id})` };
899
+ const outputParts = [`Created milestone draft: "${draft.text}" (${draft.id})`];
900
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
901
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
902
+ return { success: true, output: outputParts.join('\n') };
779
903
  }
780
904
 
781
- function executeDraftDigest(): { success: boolean; output: string } {
905
+ async function executeDraftDigest(): Promise<{ success: boolean; output: string }> {
782
906
  const state = loadState();
783
- const text = generateDigestTweet();
784
- if (!text) {
785
- return { success: false, output: 'No recent activity to summarize in a digest.' };
907
+
908
+ let text: string;
909
+ let hookType: string | undefined;
910
+ let estimatedEngagement: string | undefined;
911
+
912
+ try {
913
+ // Find ideas shipped in the last 7 days
914
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
915
+ let recentShips: Array<{ title: string; category: string }> = [];
916
+ let kpiMovement = '';
917
+
918
+ try {
919
+ if (fs.existsSync(IDEAS_FILE)) {
920
+ const { ideas } = JSON.parse(fs.readFileSync(IDEAS_FILE, 'utf-8')) as { ideas: BusinessIdea[] };
921
+ recentShips = ideas
922
+ .filter((i) => i.stage === 'shipped' && i.updated_at > sevenDaysAgo)
923
+ .sort((a, b) => b.updated_at.localeCompare(a.updated_at))
924
+ .slice(0, 5)
925
+ .map((i) => ({ title: i.title, category: i.category }));
926
+ }
927
+ } catch { /* ignore */ }
928
+
929
+ try {
930
+ if (fs.existsSync(GOALS_FILE)) {
931
+ const { goals } = JSON.parse(fs.readFileSync(GOALS_FILE, 'utf-8')) as { goals: BusinessGoal[] };
932
+ const highlights: string[] = [];
933
+ for (const goal of goals) {
934
+ for (const kpi of goal.kpis) {
935
+ if (kpi.current_value != null && kpi.target_value != null) {
936
+ const pct = Math.round((kpi.current_value / kpi.target_value) * 100);
937
+ highlights.push(`${kpi.name}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${pct}%)`);
938
+ }
939
+ }
940
+ }
941
+ if (highlights.length > 0) kpiMovement = highlights.slice(0, 3).join(', ');
942
+ }
943
+ } catch { /* ignore */ }
944
+
945
+ if (recentShips.length === 0) {
946
+ throw new Error('No recent ships for digest context');
947
+ }
948
+
949
+ const contextParts = [
950
+ `Shipped this week (${recentShips.length} items): ${recentShips.map((s) => s.title).join(', ')}`,
951
+ `Categories: ${Array.from(new Set(recentShips.map((s) => s.category))).join(', ')}`,
952
+ ];
953
+ if (kpiMovement) contextParts.push(`KPI movement: ${kpiMovement}`);
954
+
955
+ const context = contextParts.join('\n');
956
+ const pastPerformance = collectEngagementHistory(state);
957
+ const result = await generateViralTweet({ topic: 'update', context, pastPerformance });
958
+ text = result.text;
959
+ hookType = result.hook_type;
960
+ estimatedEngagement = result.estimated_engagement;
961
+ } catch {
962
+ // Fallback to template
963
+ const fallback = generateDigestTweet();
964
+ if (!fallback) {
965
+ return { success: false, output: 'No recent activity to summarize in a digest.' };
966
+ }
967
+ text = fallback;
786
968
  }
787
969
 
788
970
  const draft = createDraft(state, text, 'digest', null);
789
971
  saveState(state);
790
972
 
791
- return { success: true, output: `Created digest draft: "${draft.text}" (${draft.id})` };
973
+ const outputParts = [`Created digest draft: "${draft.text}" (${draft.id})`];
974
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
975
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
976
+ return { success: true, output: outputParts.join('\n') };
792
977
  }
793
978
 
794
979
  async function executePublish(draftId: string): Promise<{ success: boolean; output: string }> {
@@ -1544,6 +1729,26 @@ async function executeEngagementReport(): Promise<{ success: boolean; output: st
1544
1729
 
1545
1730
  // ─── Helpers ────────────────────────────────────────────────────────────────
1546
1731
 
1732
+ /**
1733
+ * Build a compact context string from a BusinessIdea for use in AI tweet generation.
1734
+ * Keeps only the first sentence of long fields to stay within Haiku's sweet spot (~300-500 chars).
1735
+ */
1736
+ function buildShipContext(idea: BusinessIdea): string {
1737
+ const parts: string[] = [`Title: ${idea.title}`];
1738
+ if (idea.context) {
1739
+ const s = idea.context.split(/[.!]/)[0]?.trim();
1740
+ if (s) parts.push(`Background: ${s}`);
1741
+ }
1742
+ if (idea.rationale) {
1743
+ const s = idea.rationale.split(/[.!]/)[0]?.trim();
1744
+ if (s) parts.push(`Why it matters: ${s}`);
1745
+ }
1746
+ if (idea.summary) parts.push(`What was shipped: ${idea.summary}`);
1747
+ if (idea.success_metrics?.[0]) parts.push(`Success target: ${idea.success_metrics[0]}`);
1748
+ if (idea.category) parts.push(`Category: ${idea.category}`);
1749
+ return parts.join('\n');
1750
+ }
1751
+
1547
1752
  /**
1548
1753
  * Collect the latest engagement snapshot from each published draft.
1549
1754
  */
@@ -0,0 +1 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[940],{1554:function(e,t,s){Promise.resolve().then(s.bind(s,9525))},9205:function(e,t,s){"use strict";s.d(t,{Z:function(){return c}});var a=s(2265);let r=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),n=function(){for(var e=arguments.length,t=Array(e),s=0;s<e;s++)t[s]=arguments[s];return t.filter((e,t,s)=>!!e&&s.indexOf(e)===t).join(" ")};var l={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let i=(0,a.forwardRef)((e,t)=>{let{color:s="currentColor",size:r=24,strokeWidth:i=2,absoluteStrokeWidth:c,className:d="",children:o,iconNode:m,...h}=e;return(0,a.createElement)("svg",{ref:t,...l,width:r,height:r,stroke:s,strokeWidth:c?24*Number(i)/Number(r):i,className:n("lucide",d),...h},[...m.map(e=>{let[t,s]=e;return(0,a.createElement)(t,s)}),...Array.isArray(o)?o:[o]])}),c=(e,t)=>{let s=(0,a.forwardRef)((s,l)=>{let{className:c,...d}=s;return(0,a.createElement)(i,{ref:l,iconNode:t,className:n("lucide-".concat(r(e)),c),...d})});return s.displayName="".concat(e),s}},2660:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]])},505:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("Calendar",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]])},401:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]])},8867:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]])},2735:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]])},8736:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]])},2023:function(e,t,s){"use strict";s.d(t,{Z:function(){return a}});let a=(0,s(9205).Z)("Sparkles",[["path",{d:"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",key:"4pj2yx"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M22 5h-4",key:"1gvqau"}],["path",{d:"M4 17v2",key:"vumght"}],["path",{d:"M5 18H3",key:"zchphs"}]])},7648:function(e,t,s){"use strict";s.d(t,{default:function(){return r.a}});var a=s(2972),r=s.n(a)},9376:function(e,t,s){"use strict";var a=s(5475);s.o(a,"usePathname")&&s.d(t,{usePathname:function(){return a.usePathname}}),s.o(a,"useRouter")&&s.d(t,{useRouter:function(){return a.useRouter}})},9525:function(e,t,s){"use strict";s.r(t),s.d(t,{default:function(){return u}});var a=s(7437),r=s(2265),n=s(2660),l=s(505),i=s(2023),c=s(8736),d=s(401),o=s(8867),m=s(2735),h=s(7648),x=s(9376);function u(){let e=(0,x.useRouter)(),[t,s]=(0,r.useState)(!1),[u,p]=(0,r.useState)("last_30"),[g,j]=(0,r.useState)(!0),[f,b]=(0,r.useState)(null),[y,w]=(0,r.useState)(!1),[N,v]=(0,r.useState)(null),k=async()=>{s(!0),v(null);try{let e=await fetch("/api/investor-updates/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({dateRange:u,includeAI:g})});if(!e.ok){let t=(await e.json()).error||"Failed to generate update";console.error("API error:",t),v(t);return}let t=await e.json();b(t)}catch(e){console.error("Error generating update:",e),v(e instanceof Error?e.message:"Network error. Please try again.")}finally{s(!1)}},Z=async()=>{f&&(await navigator.clipboard.writeText(f.markdown),w(!0),setTimeout(()=>w(!1),2e3))};return(0,a.jsxs)("div",{className:"min-h-screen bg-slate-50",children:[(0,a.jsx)("div",{className:"bg-white border-b border-slate-200 px-6 py-4",children:(0,a.jsx)("div",{className:"max-w-7xl mx-auto flex items-center justify-between",children:(0,a.jsxs)("div",{className:"flex items-center gap-4",children:[(0,a.jsx)(h.default,{href:"/updates",className:"text-slate-600 hover:text-slate-900 transition-colors",children:(0,a.jsx)(n.Z,{className:"w-5 h-5"})}),(0,a.jsxs)("div",{children:[(0,a.jsx)("h1",{className:"text-xl font-bold text-slate-900",children:"Generate Investor Update"}),(0,a.jsx)("p",{className:"text-sm text-slate-500",children:"Create a compelling monthly update with AI narrative"})]})]})})}),(0,a.jsx)("div",{className:"max-w-7xl mx-auto p-6",children:(0,a.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-6",children:[(0,a.jsxs)("div",{className:"bg-white rounded-lg border border-slate-200 p-6",children:[(0,a.jsx)("h2",{className:"text-lg font-semibold text-slate-900 mb-4",children:"Configuration"}),(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsxs)("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:[(0,a.jsx)(l.Z,{className:"w-4 h-4 inline mr-1"}),"Time Period"]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer",children:[(0,a.jsx)("input",{type:"radio",name:"dateRange",value:"last_30",checked:"last_30"===u,onChange:e=>p(e.target.value),className:"w-4 h-4 text-emerald-600"}),(0,a.jsx)("span",{className:"text-sm text-slate-700",children:"Last 30 Days (Monthly)"})]}),(0,a.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer",children:[(0,a.jsx)("input",{type:"radio",name:"dateRange",value:"last_90",checked:"last_90"===u,onChange:e=>p(e.target.value),className:"w-4 h-4 text-emerald-600"}),(0,a.jsx)("span",{className:"text-sm text-slate-700",children:"Last 90 Days (Quarterly)"})]}),(0,a.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer opacity-50",children:[(0,a.jsx)("input",{type:"radio",name:"dateRange",value:"custom",disabled:!0,className:"w-4 h-4 text-emerald-600"}),(0,a.jsx)("span",{className:"text-sm text-slate-700",children:"Custom Range (Coming Soon)"})]})]})]}),(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer",children:[(0,a.jsx)("input",{type:"checkbox",checked:g,onChange:e=>j(e.target.checked),className:"w-4 h-4 text-emerald-600 rounded"}),(0,a.jsx)(i.Z,{className:"w-4 h-4 text-emerald-500"}),(0,a.jsx)("span",{className:"text-sm font-medium text-slate-700",children:"Include AI Narrative (Recommended)"})]}),(0,a.jsx)("p",{className:"text-xs text-slate-500 ml-6 mt-1",children:"Generates executive summary, strategic context, and asks rationale"})]}),N&&(0,a.jsxs)("div",{className:"mb-4 p-4 bg-red-50 border border-red-200 rounded-lg",children:[(0,a.jsx)("p",{className:"text-sm text-red-700 font-medium",children:"Error"}),(0,a.jsx)("p",{className:"text-xs text-red-600 mt-1",children:N})]}),(0,a.jsx)("button",{onClick:k,disabled:t,className:"w-full flex items-center justify-center gap-2 px-4 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors disabled:bg-slate-300 disabled:cursor-not-allowed",children:t?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("div",{className:"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"}),(0,a.jsx)("span",{children:"Generating..."})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(i.Z,{className:"w-4 h-4"}),(0,a.jsx)("span",{children:"Generate Update"})]})}),(0,a.jsxs)("div",{className:"mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg",children:[(0,a.jsx)("h3",{className:"text-sm font-semibold text-blue-900 mb-2",children:"What's included:"}),(0,a.jsxs)("ul",{className:"text-xs text-blue-700 space-y-1",children:[(0,a.jsx)("li",{children:"• Completed epics and shipped features"}),(0,a.jsx)("li",{children:"• Goal metrics with month-over-month growth"}),(0,a.jsx)("li",{children:"• Upcoming roadmap with strategic priorities"}),(0,a.jsx)("li",{children:"• Strategic insights from MEMORY.md"}),(0,a.jsx)("li",{children:"• AI-generated narrative (if enabled)"})]})]})]}),(0,a.jsxs)("div",{className:"bg-white rounded-lg border border-slate-200 p-6",children:[(0,a.jsx)("h2",{className:"text-lg font-semibold text-slate-900 mb-4",children:"Preview"}),f?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"flex items-center gap-2 mb-4",children:[(0,a.jsx)("button",{onClick:Z,className:"flex items-center gap-1 px-3 py-2 text-sm bg-slate-100 hover:bg-slate-200 rounded-lg transition-colors",children:y?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(d.Z,{className:"w-4 h-4 text-emerald-600"}),(0,a.jsx)("span",{children:"Copied!"})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o.Z,{className:"w-4 h-4"}),(0,a.jsx)("span",{children:"Copy Markdown"})]})}),(0,a.jsxs)("button",{onClick:()=>{if(!f)return;let e=new Blob([f.markdown],{type:"text/markdown"}),t=URL.createObjectURL(e),s=document.createElement("a");s.href=t,s.download="investor-update-".concat(new Date().toISOString().split("T")[0],".md"),s.click(),URL.revokeObjectURL(t)},className:"flex items-center gap-1 px-3 py-2 text-sm bg-slate-100 hover:bg-slate-200 rounded-lg transition-colors",children:[(0,a.jsx)(m.Z,{className:"w-4 h-4"}),(0,a.jsx)("span",{children:"Download"})]}),(0,a.jsxs)("button",{onClick:()=>{f&&e.push("/updates/".concat(f.id))},className:"flex items-center gap-1 px-3 py-2 text-sm bg-emerald-600 text-white hover:bg-emerald-700 rounded-lg transition-colors ml-auto",children:[(0,a.jsx)(c.Z,{className:"w-4 h-4"}),(0,a.jsx)("span",{children:"View Details"})]})]}),(0,a.jsx)("div",{className:"prose prose-sm max-w-none overflow-auto max-h-[600px] p-4 bg-slate-50 rounded-lg border border-slate-200",children:(0,a.jsx)("pre",{className:"whitespace-pre-wrap text-xs font-mono",children:f.markdown})})]}):(0,a.jsxs)("div",{className:"flex flex-col items-center justify-center h-64 text-center",children:[(0,a.jsx)(c.Z,{className:"w-12 h-12 text-slate-300 mb-4"}),(0,a.jsxs)("p",{className:"text-sm text-slate-500",children:["Configure your update and click Generate",(0,a.jsx)("br",{}),"to see a preview here"]})]})]})]})})]})}}},function(e){e.O(0,[972,971,117,744],function(){return e(e.s=1554)}),_N_E=e.O()}]);
@@ -31612,16 +31612,16 @@ async function executeSocialTask(taskId, description, _businessContext) {
31612
31612
  return executeDraftShipVisual();
31613
31613
  }
31614
31614
  if (taskId === "social-draft-ship") {
31615
- return executeDraftShip();
31615
+ return await executeDraftShip();
31616
31616
  }
31617
31617
  if (taskId === "social-draft-update") {
31618
- return executeDraftUpdate();
31618
+ return await executeDraftUpdate();
31619
31619
  }
31620
31620
  if (taskId === "social-draft-milestone") {
31621
- return executeDraftMilestone();
31621
+ return await executeDraftMilestone();
31622
31622
  }
31623
31623
  if (taskId === "social-draft-digest") {
31624
- return executeDraftDigest();
31624
+ return await executeDraftDigest();
31625
31625
  }
31626
31626
  if (taskId.startsWith("social-draft-viral-")) {
31627
31627
  const topic = taskId.replace("social-draft-viral-", "");
@@ -31946,23 +31946,52 @@ async function executeSetupX() {
31946
31946
  output: `Connected to X as ${userInfo.username} (${userInfo.followersCount ?? 0} followers)`
31947
31947
  };
31948
31948
  }
31949
- function executeDraftShip() {
31949
+ async function executeDraftShip() {
31950
31950
  const state = loadState3();
31951
- const text = generateShipTweet();
31952
- if (!text) {
31951
+ let allIdeas = [];
31952
+ try {
31953
+ const { ideas } = JSON.parse(fs18.readFileSync(IDEAS_FILE, "utf-8"));
31954
+ allIdeas = ideas;
31955
+ } catch {
31956
+ return { success: false, output: "Could not read ideas.json." };
31957
+ }
31958
+ const latestShipped = allIdeas.filter((i) => i.stage === "shipped").sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
31959
+ if (!latestShipped) {
31953
31960
  return { success: false, output: "No shipped ideas found to generate a tweet from." };
31954
31961
  }
31955
- const { ideas } = JSON.parse(fs18.readFileSync(
31956
- path12.join(path12.dirname(SOCIAL_FILE), "ideas.json"),
31957
- "utf-8"
31958
- ));
31959
- const latestShipped = ideas.filter((i) => i.stage === "shipped").sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
31960
- if (latestShipped && hasDraftOrPublished(state, latestShipped.id)) {
31962
+ if (hasDraftOrPublished(state, latestShipped.id)) {
31961
31963
  return { success: false, output: `Draft already exists for shipped idea ${latestShipped.id}` };
31962
31964
  }
31963
- const draft = createDraft(state, text, "ship", latestShipped?.id || null);
31965
+ let text;
31966
+ let hookType;
31967
+ let estimatedEngagement;
31968
+ try {
31969
+ const context = buildShipContext(latestShipped);
31970
+ let positioning = null;
31971
+ try {
31972
+ if (fs18.existsSync(POSITIONING_FILE)) {
31973
+ positioning = JSON.parse(fs18.readFileSync(POSITIONING_FILE, "utf-8"));
31974
+ }
31975
+ } catch {
31976
+ }
31977
+ const pastPerformance = collectEngagementHistory(state);
31978
+ const result = await generateViralTweet({ topic: "ship", context, positioning, pastPerformance });
31979
+ text = result.text;
31980
+ hookType = result.hook_type;
31981
+ estimatedEngagement = result.estimated_engagement;
31982
+ } catch {
31983
+ const fallback = generateShipTweet();
31984
+ if (!fallback) {
31985
+ return { success: false, output: "No shipped ideas found to generate a tweet from." };
31986
+ }
31987
+ text = fallback;
31988
+ }
31989
+ const draft = createDraft(state, text, "ship", latestShipped.id);
31964
31990
  saveState3(state);
31965
- return { success: true, output: `Created ship draft: "${draft.text}" (${draft.id})` };
31991
+ const outputParts = [`Created ship draft: "${draft.text}" (${draft.id})`];
31992
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
31993
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
31994
+ return { success: true, output: outputParts.join("\n") };
31966
31995
  }
31967
31996
  function executeDraftShipVisual() {
31968
31997
  const state = loadState3();
@@ -32003,35 +32032,166 @@ function executeDraftShipVisual() {
32003
32032
  output: `Created ship draft with media: "${draft.text}" (${draft.id}) \u2014 ${mediaPaths.length} file(s) attached`
32004
32033
  };
32005
32034
  }
32006
- function executeDraftUpdate() {
32035
+ async function executeDraftUpdate() {
32007
32036
  const state = loadState3();
32008
- const text = generateProductUpdateTweet();
32009
- if (!text) {
32010
- return { success: false, output: "No positioning data found to generate update tweet." };
32037
+ let text;
32038
+ let hookType;
32039
+ let estimatedEngagement;
32040
+ try {
32041
+ let positioning = null;
32042
+ try {
32043
+ if (fs18.existsSync(POSITIONING_FILE)) {
32044
+ positioning = JSON.parse(fs18.readFileSync(POSITIONING_FILE, "utf-8"));
32045
+ }
32046
+ } catch {
32047
+ }
32048
+ const contextParts = [];
32049
+ if (positioning?.current?.tagline) contextParts.push(`Tagline: ${positioning.current.tagline}`);
32050
+ if (positioning?.current?.value_proposition) {
32051
+ const s = positioning.current.value_proposition.split(/[.!]/)[0]?.trim();
32052
+ if (s) contextParts.push(`Value prop: ${s}`);
32053
+ }
32054
+ if (positioning?.copy_bank?.pain_points?.length) {
32055
+ const pains = positioning.copy_bank.pain_points.slice(0, 2).map((p) => p.pain);
32056
+ contextParts.push(`Pain points: ${pains.join(", ")}`);
32057
+ }
32058
+ if (positioning?.current?.differentiators?.length) {
32059
+ contextParts.push(`Differentiator: ${positioning.current.differentiators[0]}`);
32060
+ }
32061
+ if (contextParts.length === 0) {
32062
+ throw new Error("No positioning context available");
32063
+ }
32064
+ const context = contextParts.join("\n");
32065
+ const pastPerformance = collectEngagementHistory(state);
32066
+ const result = await generateViralTweet({ topic: "update", context, positioning, pastPerformance });
32067
+ text = result.text;
32068
+ hookType = result.hook_type;
32069
+ estimatedEngagement = result.estimated_engagement;
32070
+ } catch {
32071
+ const fallback = generateProductUpdateTweet();
32072
+ if (!fallback) {
32073
+ return { success: false, output: "No positioning data found to generate update tweet." };
32074
+ }
32075
+ text = fallback;
32011
32076
  }
32012
32077
  const draft = createDraft(state, text, "update", null);
32013
32078
  saveState3(state);
32014
- return { success: true, output: `Created update draft: "${draft.text}" (${draft.id})` };
32079
+ const outputParts = [`Created update draft: "${draft.text}" (${draft.id})`];
32080
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
32081
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
32082
+ return { success: true, output: outputParts.join("\n") };
32015
32083
  }
32016
- function executeDraftMilestone() {
32084
+ async function executeDraftMilestone() {
32017
32085
  const state = loadState3();
32018
- const text = generateMilestoneTweet();
32019
- if (!text) {
32020
- return { success: false, output: "No KPI milestones found to celebrate." };
32086
+ let text;
32087
+ let hookType;
32088
+ let estimatedEngagement;
32089
+ try {
32090
+ let kpiContext = "";
32091
+ try {
32092
+ if (fs18.existsSync(GOALS_FILE)) {
32093
+ const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
32094
+ for (const goal of goals) {
32095
+ for (const kpi of goal.kpis) {
32096
+ if (kpi.current_value != null && kpi.target_value != null) {
32097
+ const pct = kpi.current_value / kpi.target_value;
32098
+ if (pct >= 0.8) {
32099
+ kpiContext = [
32100
+ `KPI: ${kpi.name}`,
32101
+ `Current: ${kpi.current_value} ${kpi.unit}`,
32102
+ `Target: ${kpi.target_value} ${kpi.unit}`,
32103
+ `Goal: ${goal.title}`
32104
+ ].join("\n");
32105
+ break;
32106
+ }
32107
+ }
32108
+ }
32109
+ if (kpiContext) break;
32110
+ }
32111
+ }
32112
+ } catch {
32113
+ }
32114
+ if (!kpiContext) {
32115
+ throw new Error("No milestone KPI context available");
32116
+ }
32117
+ const pastPerformance = collectEngagementHistory(state);
32118
+ const result = await generateViralTweet({ topic: "milestone", context: kpiContext, pastPerformance });
32119
+ text = result.text;
32120
+ hookType = result.hook_type;
32121
+ estimatedEngagement = result.estimated_engagement;
32122
+ } catch {
32123
+ const fallback = generateMilestoneTweet();
32124
+ if (!fallback) {
32125
+ return { success: false, output: "No KPI milestones found to celebrate." };
32126
+ }
32127
+ text = fallback;
32021
32128
  }
32022
32129
  const draft = createDraft(state, text, "milestone", null);
32023
32130
  saveState3(state);
32024
- return { success: true, output: `Created milestone draft: "${draft.text}" (${draft.id})` };
32131
+ const outputParts = [`Created milestone draft: "${draft.text}" (${draft.id})`];
32132
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
32133
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
32134
+ return { success: true, output: outputParts.join("\n") };
32025
32135
  }
32026
- function executeDraftDigest() {
32136
+ async function executeDraftDigest() {
32027
32137
  const state = loadState3();
32028
- const text = generateDigestTweet();
32029
- if (!text) {
32030
- return { success: false, output: "No recent activity to summarize in a digest." };
32138
+ let text;
32139
+ let hookType;
32140
+ let estimatedEngagement;
32141
+ try {
32142
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
32143
+ let recentShips = [];
32144
+ let kpiMovement = "";
32145
+ try {
32146
+ if (fs18.existsSync(IDEAS_FILE)) {
32147
+ const { ideas } = JSON.parse(fs18.readFileSync(IDEAS_FILE, "utf-8"));
32148
+ recentShips = ideas.filter((i) => i.stage === "shipped" && i.updated_at > sevenDaysAgo).sort((a, b) => b.updated_at.localeCompare(a.updated_at)).slice(0, 5).map((i) => ({ title: i.title, category: i.category }));
32149
+ }
32150
+ } catch {
32151
+ }
32152
+ try {
32153
+ if (fs18.existsSync(GOALS_FILE)) {
32154
+ const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
32155
+ const highlights = [];
32156
+ for (const goal of goals) {
32157
+ for (const kpi of goal.kpis) {
32158
+ if (kpi.current_value != null && kpi.target_value != null) {
32159
+ const pct = Math.round(kpi.current_value / kpi.target_value * 100);
32160
+ highlights.push(`${kpi.name}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${pct}%)`);
32161
+ }
32162
+ }
32163
+ }
32164
+ if (highlights.length > 0) kpiMovement = highlights.slice(0, 3).join(", ");
32165
+ }
32166
+ } catch {
32167
+ }
32168
+ if (recentShips.length === 0) {
32169
+ throw new Error("No recent ships for digest context");
32170
+ }
32171
+ const contextParts = [
32172
+ `Shipped this week (${recentShips.length} items): ${recentShips.map((s) => s.title).join(", ")}`,
32173
+ `Categories: ${Array.from(new Set(recentShips.map((s) => s.category))).join(", ")}`
32174
+ ];
32175
+ if (kpiMovement) contextParts.push(`KPI movement: ${kpiMovement}`);
32176
+ const context = contextParts.join("\n");
32177
+ const pastPerformance = collectEngagementHistory(state);
32178
+ const result = await generateViralTweet({ topic: "update", context, pastPerformance });
32179
+ text = result.text;
32180
+ hookType = result.hook_type;
32181
+ estimatedEngagement = result.estimated_engagement;
32182
+ } catch {
32183
+ const fallback = generateDigestTweet();
32184
+ if (!fallback) {
32185
+ return { success: false, output: "No recent activity to summarize in a digest." };
32186
+ }
32187
+ text = fallback;
32031
32188
  }
32032
32189
  const draft = createDraft(state, text, "digest", null);
32033
32190
  saveState3(state);
32034
- return { success: true, output: `Created digest draft: "${draft.text}" (${draft.id})` };
32191
+ const outputParts = [`Created digest draft: "${draft.text}" (${draft.id})`];
32192
+ if (hookType) outputParts.push(`Hook type: ${hookType}`);
32193
+ if (estimatedEngagement) outputParts.push(`Estimated engagement: ${estimatedEngagement}`);
32194
+ return { success: true, output: outputParts.join("\n") };
32035
32195
  }
32036
32196
  async function executePublish(draftId) {
32037
32197
  const state = loadState3();
@@ -32576,6 +32736,21 @@ async function executeEngagementReport() {
32576
32736
  }
32577
32737
  return { success: true, output: lines.join("\n") };
32578
32738
  }
32739
+ function buildShipContext(idea) {
32740
+ const parts = [`Title: ${idea.title}`];
32741
+ if (idea.context) {
32742
+ const s = idea.context.split(/[.!]/)[0]?.trim();
32743
+ if (s) parts.push(`Background: ${s}`);
32744
+ }
32745
+ if (idea.rationale) {
32746
+ const s = idea.rationale.split(/[.!]/)[0]?.trim();
32747
+ if (s) parts.push(`Why it matters: ${s}`);
32748
+ }
32749
+ if (idea.summary) parts.push(`What was shipped: ${idea.summary}`);
32750
+ if (idea.success_metrics?.[0]) parts.push(`Success target: ${idea.success_metrics[0]}`);
32751
+ if (idea.category) parts.push(`Category: ${idea.category}`);
32752
+ return parts.join("\n");
32753
+ }
32579
32754
  function collectEngagementHistory(state) {
32580
32755
  const result = [];
32581
32756
  for (const draft of state.drafts) {