@@ -797,6 +797,161 @@ Here's a detailed market brief with positioning opportunities and whitespace are
797797 } ) ;
798798} ) ;
799799
800+ // ---------------------------------------------------------------------------
801+ // extractArtifacts: conversational preamble title cleaning
802+ // ---------------------------------------------------------------------------
803+ describe ( "extractArtifacts — conversational preamble cleaning" , ( ) => {
804+ function createMockDeps ( ) {
805+ const createdArtifacts : unknown [ ] = [ ] ;
806+ const artifactService = {
807+ createArtifact : vi . fn ( ) . mockImplementation ( ( _paths : unknown , opts : unknown ) => {
808+ const record = { artifactId : `art-${ createdArtifacts . length + 1 } ` , ...( opts as object ) } ;
809+ createdArtifacts . push ( record ) ;
810+ return Promise . resolve ( record ) ;
811+ } ) ,
812+ createBundle : vi . fn ( ) . mockImplementation ( ( _paths : unknown , opts : unknown ) => {
813+ return Promise . resolve ( { bundleId : "bnd-mock-1" , ...( opts as object ) } ) ;
814+ } ) ,
815+ } ;
816+ const opengoatPaths = { homeDir : "/tmp/test" } ;
817+ return { artifactService, opengoatPaths, createdArtifacts } ;
818+ }
819+
820+ const baseContext : ExtractionContext = {
821+ specialistId : "market-intel" ,
822+ agentId : "proj-1" ,
823+ sessionId : "sess-1" ,
824+ messageIndex : 0 ,
825+ } ;
826+
827+ it ( "strips conversational heading and uses content heading instead" , async ( ) => {
828+ const { artifactService, opengoatPaths } = createMockDeps ( ) ;
829+ const text = `Some intro text
830+
831+ ## Here's a structured readout of the main messaging gaps
832+
833+ After analyzing the competitive landscape, the key gaps are clear.
834+
835+ ### Messaging Gap Analysis
836+
837+ | Gap Area | Your Position | Competitor Position |
838+ |---|---|---|
839+ | AI-native | Strong | Weak |
840+ | Developer DX | Moderate | Strong |
841+
842+ This analysis shows significant opportunities in the AI-native space.
843+ ` ;
844+
845+ const specialist = {
846+ id : "market-intel" ,
847+ name : "Market Intel" ,
848+ outputTypes : [ "competitor messaging matrix" , "community shortlist" , "market brief" ] ,
849+ } ;
850+
851+ const result = await extractArtifacts ( text , baseContext , {
852+ artifactService : artifactService as any ,
853+ opengoatPaths : opengoatPaths as any ,
854+ specialist : specialist as any ,
855+ } ) ;
856+
857+ // The heading was conversational; should fall back to content heading or type
858+ expect ( result . artifacts ) . toHaveLength ( 1 ) ;
859+ const callArgs = artifactService . createArtifact . mock . calls [ 0 ] [ 1 ] ;
860+ expect ( callArgs . title ) . toBe ( "Messaging Gap Analysis" ) ;
861+ } ) ;
862+
863+ it ( "falls back to humanized artifact type when no content heading exists" , async ( ) => {
864+ const { artifactService, opengoatPaths } = createMockDeps ( ) ;
865+ // Heading is conversational but contains enough matching tokens ("competitor", "messaging")
866+ const text = `## Here's the competitor messaging breakdown you asked for
867+
868+ A detailed competitor messaging matrix showing positioning gaps across all major players in the B2B SaaS market for marketing intelligence tools.
869+
870+ | Competitor | Positioning | Key Claim | Weakness |
871+ |---|---|---|---|
872+ | Acme Corp | "All-in-one platform" | Speed | No customization |
873+ | Beta Inc | "Enterprise-grade" | Security | Expensive |
874+ ` ;
875+
876+ const specialist = {
877+ id : "market-intel" ,
878+ name : "Market Intel" ,
879+ outputTypes : [ "competitor messaging matrix" , "community shortlist" ] ,
880+ } ;
881+
882+ const result = await extractArtifacts ( text , baseContext , {
883+ artifactService : artifactService as any ,
884+ opengoatPaths : opengoatPaths as any ,
885+ specialist : specialist as any ,
886+ } ) ;
887+
888+ expect ( result . artifacts ) . toHaveLength ( 1 ) ;
889+ const callArgs = artifactService . createArtifact . mock . calls [ 0 ] [ 1 ] ;
890+ // No content heading and section heading is conversational → fallback to type label
891+ expect ( callArgs . title ) . toBe ( "Matrix" ) ;
892+ } ) ;
893+
894+ it ( "preserves clean descriptive headings unchanged" , async ( ) => {
895+ const { artifactService, opengoatPaths } = createMockDeps ( ) ;
896+ const text = `## Competitor Messaging Matrix
897+
898+ | Competitor | Positioning | Key Claim | Weakness |
899+ |---|---|---|---|
900+ | Acme Corp | "All-in-one platform" | Speed | No customization |
901+ | Beta Inc | "Enterprise-grade" | Security | Expensive |
902+
903+ Key gaps: None of your competitors emphasize the "AI-native" angle.
904+ ` ;
905+
906+ const specialist = {
907+ id : "market-intel" ,
908+ name : "Market Intel" ,
909+ outputTypes : [ "competitor messaging matrix" , "community shortlist" ] ,
910+ } ;
911+
912+ const result = await extractArtifacts ( text , baseContext , {
913+ artifactService : artifactService as any ,
914+ opengoatPaths : opengoatPaths as any ,
915+ specialist : specialist as any ,
916+ } ) ;
917+
918+ expect ( result . artifacts ) . toHaveLength ( 1 ) ;
919+ const callArgs = artifactService . createArtifact . mock . calls [ 0 ] [ 1 ] ;
920+ expect ( callArgs . title ) . toBe ( "Competitor Messaging Matrix" ) ;
921+ } ) ;
922+
923+ it ( "strips markdown bold from heading" , async ( ) => {
924+ const { artifactService, opengoatPaths } = createMockDeps ( ) ;
925+ const text = `## **Hero Rewrite Options**
926+
927+ Here are three hero rewrite options for your landing page:
928+
929+ 1. **Option A** — "Ship faster with AI-powered workflows"
930+ 2. **Option B** — "Your team's second brain for shipping"
931+ 3. **Option C** — "From idea to production in minutes"
932+ ` ;
933+
934+ const specialist = {
935+ id : "website-conversion" ,
936+ name : "Website Conversion" ,
937+ outputTypes : [ "hero rewrite bundle" , "CTA options" ] ,
938+ } ;
939+
940+ const result = await extractArtifacts ( text , {
941+ ...baseContext ,
942+ specialistId : "website-conversion" ,
943+ } , {
944+ artifactService : artifactService as any ,
945+ opengoatPaths : opengoatPaths as any ,
946+ specialist : specialist as any ,
947+ } ) ;
948+
949+ expect ( result . artifacts ) . toHaveLength ( 1 ) ;
950+ const callArgs = artifactService . createArtifact . mock . calls [ 0 ] [ 1 ] ;
951+ expect ( callArgs . title ) . toBe ( "Hero Rewrite Options" ) ;
952+ } ) ;
953+ } ) ;
954+
800955// ---------------------------------------------------------------------------
801956// Manual extraction route: POST /extract
802957// ---------------------------------------------------------------------------
0 commit comments