@@ -11,19 +11,21 @@ import (
1111 "github.com/vybdev/vyb/workspace/project"
1212)
1313
14- // buildExtendedUserMessage composes the user-message payload that will be
14+ // buildWorkspaceChangeRequest composes a payload.WorkspaceChangeRequest that will be
1515// sent to the LLM. It prepends module context information — as dictated
16- // by the specification — before the raw file contents. When metadata is
17- // nil or when any contextual information is missing the function falls
18- // back gracefully, emitting only what is available.
19- func buildExtendedUserMessage (rootFS fs.FS , meta * project.Metadata , ec * context.ExecutionContext , filePaths []string ) (string , error ) {
20- // If metadata is missing we revert to the original behaviour – emit
21- // just the files.
22- if meta == nil || meta .Modules == nil {
23- return payload .BuildUserMessage (rootFS , filePaths )
16+ // by the specification — before the raw file contents. Both meta and
17+ // meta.Modules must be non-nil.
18+ func buildWorkspaceChangeRequest (rootFS fs.FS , meta * project.Metadata , ec * context.ExecutionContext , filePaths []string ) (* payload.WorkspaceChangeRequest , error ) {
19+ if meta == nil {
20+ return nil , fmt .Errorf ("metadata cannot be nil" )
2421 }
22+ if meta .Modules == nil {
23+ return nil , fmt .Errorf ("metadata.Modules cannot be nil" )
24+ }
25+
26+ request := & payload.WorkspaceChangeRequest {}
2527
26- // Helper to clean/normalise relative paths.
28+ // Helper to clean/normalise relative paths
2729 rel := func (abs string ) string {
2830 if abs == "" {
2931 return ""
@@ -35,42 +37,43 @@ func buildExtendedUserMessage(rootFS fs.FS, meta *project.Metadata, ec *context.
3537 workingRel := rel (ec .WorkingDir )
3638 targetRel := rel (ec .TargetDir )
3739
40+ request .TargetDirectory = targetRel
41+
42+ // Find modules (metadata is guaranteed to be valid)
3843 workingMod := project .FindModule (meta .Modules , workingRel )
3944 targetMod := project .FindModule (meta .Modules , targetRel )
4045
4146 if workingMod == nil || targetMod == nil {
42- return "" , fmt .Errorf ("failed to locate working and target modules" )
47+ return nil , fmt .Errorf ("failed to locate working and target modules" )
4348 }
4449
45- var sb strings.Builder
50+ // Set target module information
51+ request .TargetModule = targetMod .Name
4652
47- // ------------------------------------------------------------
48- // 1. External context of working module.
49- // ------------------------------------------------------------
50- if ann := workingMod .Annotation ; ann != nil && ann .ExternalContext != "" {
51- sb .WriteString (fmt .Sprintf ("# Module: `%s`\n " , workingMod .Name ))
52- sb .WriteString ("## External Context\n " )
53- sb .WriteString (ann .ExternalContext + "\n " )
53+ // Set target module context (combined internal and external context)
54+ var targetContext strings.Builder
55+ if ann := targetMod .Annotation ; ann != nil {
56+ if ann .ExternalContext != "" {
57+ targetContext .WriteString ("External Context: " )
58+ targetContext .WriteString (ann .ExternalContext )
59+ targetContext .WriteString ("\n \n " )
60+ }
61+ if ann .InternalContext != "" {
62+ targetContext .WriteString ("Internal Context: " )
63+ targetContext .WriteString (ann .InternalContext )
64+ }
5465 }
5566
56- // ------------------------------------------------------------
57- // 2. Internal context of modules between working and target.
58- // ------------------------------------------------------------
59- for m := targetMod .Parent ; m != nil && m != workingMod ; m = m .Parent {
60- if ann := m .Annotation ; ann != nil && ann .InternalContext != "" {
61- sb .WriteString (fmt .Sprintf ("# Module: `%s`\n " , m .Name ))
62- sb .WriteString ("## Internal Context\n " )
63- sb .WriteString (ann .InternalContext + "\n " )
64- }
67+ // Ensure TargetModuleContext is never empty
68+ if targetContext .Len () == 0 {
69+ targetContext .WriteString ("No specific context available for this module." )
6570 }
71+ request .TargetModuleContext = targetContext .String ()
6672
67- // ------------------------------------------------------------
68- // 3. Public context of sibling modules along the path from the
69- // parent of the target module up to (and including) the working
70- // module. This replaces the previous logic that only considered
71- // direct children of the working module.
72- // ------------------------------------------------------------
73+ var parentModuleContexts []payload.ModuleContext
74+ var subModuleContexts []payload.ModuleContext
7375
76+ // Collect parent and sibling module contexts
7477 isAncestor := func (a , b string ) bool {
7578 return a == b || (a != "." && strings .HasPrefix (b , a + "/" ))
7679 }
@@ -82,36 +85,43 @@ func buildExtendedUserMessage(rootFS fs.FS, meta *project.Metadata, ec *context.
8285 continue
8386 }
8487 if ann := child .Annotation ; ann != nil && ann .PublicContext != "" {
85- sb .WriteString (fmt .Sprintf ("# Module: `%s`\n " , child .Name ))
86- sb .WriteString ("## Public Context\n " )
87- sb .WriteString (ann .PublicContext + "\n " )
88+ parentModuleContexts = append (parentModuleContexts , payload.ModuleContext {
89+ Name : child .Name ,
90+ Content : ann .PublicContext ,
91+ })
8892 }
8993 }
9094 if ancestor == workingMod {
9195 break
9296 }
9397 }
9498
95- // ------------------------------------------------------------
96- // 4. Public context of immediate sub-modules of target module.
97- // ------------------------------------------------------------
99+ // Collect immediate sub-modules of target module
98100 for _ , child := range targetMod .Modules {
99101 if ann := child .Annotation ; ann != nil && ann .PublicContext != "" {
100- sb .WriteString (fmt .Sprintf ("# Module: `%s`\n " , child .Name ))
101- sb .WriteString ("## Public Context\n " )
102- sb .WriteString (ann .PublicContext + "\n " )
102+ subModuleContexts = append (subModuleContexts , payload.ModuleContext {
103+ Name : child .Name ,
104+ Content : ann .PublicContext ,
105+ })
103106 }
104107 }
105108
106- // ------------------------------------------------------------
107- // 5. Append file contents (only files from target module were
108- // selected by selector.Select).
109- // ------------------------------------------------------------
110- filesMsg , err := payload .BuildUserMessage (rootFS , filePaths )
111- if err != nil {
112- return "" , err
109+ request .ParentModuleContexts = parentModuleContexts
110+ request .SubModuleContexts = subModuleContexts
111+
112+ // Append file contents
113+ var files []payload.FileContent
114+ for _ , path := range filePaths {
115+ content , err := fs .ReadFile (rootFS , path )
116+ if err != nil {
117+ return nil , fmt .Errorf ("failed to read file %s: %w" , path , err )
118+ }
119+ files = append (files , payload.FileContent {
120+ Path : path ,
121+ Content : string (content ),
122+ })
113123 }
114- sb . WriteString ( filesMsg )
124+ request . Files = files
115125
116- return sb . String () , nil
126+ return request , nil
117127}
0 commit comments