@@ -5,10 +5,15 @@ package code
55
66import (
77 "context"
8+ "fmt"
89 "os"
10+ "path/filepath"
11+ "strings"
912
1013 corecode "github.com/codefly-dev/core/code"
14+ basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0"
1115 codev0 "github.com/codefly-dev/core/generated/go/codefly/services/code/v0"
16+ runners "github.com/codefly-dev/core/runners/base"
1217
1318 pythonservice "github.com/codefly-dev/service-python/pkg/service"
1419)
@@ -38,15 +43,15 @@ func New(svc *pythonservice.Service) *Code {
3843}
3944
4045// SourceDir returns the directory to operate on. Resolution order:
41- // explicit Service.SourceLocation → CODEFLY_AGENT_WORKDIR → Service.Location.
46+ // explicit Service.SourceLocation → CODEFLY_AGENT_WORKDIR/source-dir → Service.Location/source-dir .
4247func (c * Code ) SourceDir () string {
4348 if c .Service .SourceLocation != "" {
4449 return c .Service .SourceLocation
4550 }
4651 if wd := os .Getenv ("CODEFLY_AGENT_WORKDIR" ); wd != "" {
47- return wd
52+ return filepath . Join ( wd , c . Service . Settings . PythonSourceDir ())
4853 }
49- return c .Service .Location
54+ return filepath . Join ( c .Service .Location , c . Service . Settings . PythonSourceDir ())
5055}
5156
5257// EnsureInit lazily swaps in a PythonCodeServer pointed at the resolved
@@ -55,23 +60,68 @@ func (c *Code) SourceDir() string {
5560func (c * Code ) EnsureInit () {
5661 if ! c .initialized {
5762 c .PythonCodeServer = corecode .NewPythonCodeServer (c .SourceDir (), nil )
63+ c .SetSourceFixer (c .fixPython )
5864 c .initialized = true
5965 }
6066}
6167
62- // ── Lazy-init RPC wrappers ───────────────────────────────
63-
64- func (c * Code ) GetProjectInfo (ctx context.Context , req * codev0.GetProjectInfoRequest ) (* codev0.GetProjectInfoResponse , error ) {
68+ func (c * Code ) Execute (ctx context.Context , req * codev0.CodeRequest ) (* codev0.CodeResponse , error ) {
6569 c .EnsureInit ()
66- return c .PythonCodeServer .GetProjectInfo (ctx , req )
70+ return c .PythonCodeServer .Execute (ctx , req )
6771}
6872
69- func (c * Code ) ListDependencies (ctx context.Context , req * codev0.ListDependenciesRequest ) (* codev0.ListDependenciesResponse , error ) {
70- c .EnsureInit ()
71- return c .PythonCodeServer .ListDependencies (ctx , req )
73+ // fixPython applies only project-configured Ruff safe fixes, then Ruff's
74+ // formatter. Both stages operate on stdin and return source on stdout, so the
75+ // core Code server remains the sole writer and can honor VFS/dry-run semantics.
76+ func (c * Code ) fixPython (ctx context.Context , input corecode.FixInput ) (corecode.FixResult , error ) {
77+ checkArgs := []string {"check" , "--fix-only" }
78+ if input .Mode == basev0 .FixMode_FIX_MODE_AGGRESSIVE {
79+ checkArgs = append (checkArgs , "--unsafe-fixes" )
80+ }
81+ checkArgs = append (checkArgs , "--stdin-filename" , input .Path , "-" )
82+
83+ // Fix is an authoring primitive, not environment initialization. Invoke a
84+ // provisioned Ruff directly so a dry run cannot create .venv/uv.lock or
85+ // download the project's dependency graph. Factory Nix shells include Ruff,
86+ // the Docker runtime exposes /venv/bin on PATH, and native users may supply
87+ // either a project-local .venv binary or Ruff on the host PATH.
88+ ruff := c .ruffCommand ()
89+ checked , checkLogBytes , err := runners .RunInput (ctx , c .runnerEnvironment (ctx ), c .SourceDir (), input .Content , ruff , checkArgs ... )
90+ checkLog := string (checkLogBytes )
91+ if err != nil {
92+ return corecode.FixResult {}, fmt .Errorf ("ruff check --fix-only (Ruff must already be provisioned): %w: %s" , err , strings .TrimSpace (checkLog ))
93+ }
94+ formatted , formatLogBytes , err := runners .RunInput (ctx , c .runnerEnvironment (ctx ), c .SourceDir (), checked , ruff , "format" , "--stdin-filename" , input .Path , "-" )
95+ formatLog := string (formatLogBytes )
96+ if err != nil {
97+ return corecode.FixResult {}, fmt .Errorf ("ruff format: %w: %s" , err , strings .TrimSpace (formatLog ))
98+ }
99+ output := strings .TrimSpace (strings .Join ([]string {checkLog , formatLog }, "\n " ))
100+ return corecode.FixResult {
101+ Content : formatted ,
102+ Actions : []string {"ruff check --fix-only" , "ruff format" },
103+ Output : output ,
104+ }, nil
72105}
73106
74- func (c * Code ) Execute (ctx context.Context , req * codev0.CodeRequest ) (* codev0.CodeResponse , error ) {
75- c .EnsureInit ()
76- return c .PythonCodeServer .Execute (ctx , req )
107+ func (c * Code ) ruffCommand () string {
108+ if c .Service .ActiveEnv == nil {
109+ candidate := filepath .Join (c .SourceDir (), ".venv" , "bin" , "ruff" )
110+ if info , err := os .Stat (candidate ); err == nil && info .Mode ().IsRegular () && info .Mode ().Perm ()& 0o111 != 0 {
111+ return candidate
112+ }
113+ }
114+ return "ruff"
115+ }
116+
117+ func (c * Code ) runnerEnvironment (ctx context.Context ) runners.RunnerEnvironment {
118+ env := c .Service .ActiveEnv
119+ if env != nil {
120+ return env
121+ }
122+ var runtimeContext * basev0.RuntimeContext
123+ if c .Service .Base != nil && c .Service .Base .Runtime != nil {
124+ runtimeContext = c .Service .Base .Runtime .RuntimeContext
125+ }
126+ return runners .ResolveStandaloneEnvironment (ctx , c .SourceDir (), runtimeContext )
77127}
0 commit comments