@@ -14,7 +14,7 @@ class GuidelineComposer
1414{
1515    protected  string  $ userGuidelineDir'.ai/guidelines ' ;
1616
17-     /** @var Collection<string, string > */ 
17+     /** @var Collection<string, array > */ 
1818    protected  Collection $ guidelines
1919
2020    protected  GuidelineConfig $ config
@@ -42,18 +42,24 @@ public function compose(): string
4242        return  self ::composeGuidelines ($ this guidelines ());
4343    }
4444
45+     public  function  customGuidelinePath (string  $ path'' ): string 
46+     {
47+         return  base_path ($ this userGuidelineDir .'/ ' .ltrim ($ path'/ ' ));
48+     }
49+ 
4550    /** 
4651     * Static method to compose guidelines from a collection. 
4752     * Can be used without Laravel dependencies. 
4853     * 
49-      * @param Collection<string, string> $guidelines 
54+      * @param Collection<string, array{content:  string, name: string, path: ?string, custom: bool} > $guidelines 
5055     */ 
5156    public  static  function  composeGuidelines (Collection $ guidelinesstring 
5257    {
5358        return  str_replace ("\n\n\n\n" , "\n\n" , trim ($ guidelines
54-             ->filter (fn  ($ contentempty (trim ($ content
55-             ->map (fn  ($ content$ key"\n===  {$ key rules === \n\n" .trim ($ content
56-             ->join ("\n\n" )));
59+             ->filter (fn  ($ guidelineempty (trim ($ guideline'content ' ])))
60+             ->map (fn  ($ guideline$ key"\n===  {$ key rules === \n\n" .trim ($ guideline'content ' ]))
61+             ->join ("\n\n" ))
62+         );
5763    }
5864
5965    /** 
@@ -65,7 +71,7 @@ public function used(): array
6571    }
6672
6773    /** 
68-      * @return Collection<string, string > 
74+      * @return Collection<string, array > 
6975     */ 
7076    public  function  guidelines (): Collection 
7177    {
@@ -79,14 +85,13 @@ public function guidelines(): Collection
7985    /** 
8086     * Key is the 'guideline key' and value is the rendered blade. 
8187     * 
82-      * @return \Illuminate\Support\Collection<string, string > 
88+      * @return \Illuminate\Support\Collection<string, array > 
8389     */ 
8490    protected  function  find (): Collection 
8591    {
8692        $ guidelinescollect ();
8793        $ guidelinesput ('foundation ' , $ this guideline ('foundation ' ));
8894        $ guidelinesput ('boost ' , $ this guideline ('boost/core ' ));
89- 
9095        $ guidelinesput ('php ' , $ this guideline ('php/core ' ));
9196
9297        // TODO: AI-48: Use composer target version, not PHP version. Production could be 8.1, but local is 8.4 
@@ -119,49 +124,39 @@ protected function find(): Collection
119124                $ guidelineDir'/core ' ,
120125                $ this guideline ($ guidelineDir'/core ' )
121126            ); // Always add package core 
122- 
123-             $ guidelinesput (
124-                 $ guidelineDir'/v ' .$ packagemajorVersion (),
125-                 $ this guidelinesDir ($ guidelineDir'/ ' .$ packagemajorVersion ())
126-             );
127+             $ packageGuidelines$ this guidelinesDir ($ guidelineDir'/ ' .$ packagemajorVersion ());
128+             foreach  ($ packageGuidelinesas  $ guideline
129+                 $ suffix$ guideline'name ' ] == 'core '  ? ''  : '/ ' .$ guideline'name ' ];
130+                 $ guidelinesput (
131+                     $ guidelineDir'/v ' .$ packagemajorVersion ().$ suffix
132+                     $ guideline
133+                 );
134+             }
127135        }
128136
129137        if  ($ this config ->enforceTests ) {
130138            $ guidelinesput ('tests ' , $ this guideline ('enforce-tests ' ));
131139        }
132140
133-         $ userGuidelines$ this guidelineFilesInDir (base_path ($ this userGuidelineDir ));
141+         $ userGuidelines$ this guidelinesDir ($ this customGuidelinePath ());
142+         $ pathsUsed$ guidelinespluck ('path ' );
134143
135144        foreach  ($ userGuidelinesas  $ guideline
136-             $ guidelineKey'.ai/ ' .$ guidelinegetBasename ('.blade.php ' );
137-             $ guidelinesput ($ guidelineKey$ this guideline ($ guidelinegetPathname ()));
145+             if  ($ pathsUsedcontains ($ guideline'path ' ])) {
146+                 continue ; // Don't include this twice if it's an override 
147+             }
148+             $ guidelinesput ('.ai/ ' .$ guideline'name ' ], $ guideline
138149        }
139150
140151        return  $ guidelines
141-             ->whereNotNull ()
142-             ->where (fn  (string  $ guidelineempty (trim ($ guideline
152+             ->where (fn  (array  $ guidelineempty (trim ($ guideline'content ' ])));
143153    }
144154
145155    /** 
146-      * @return Collection<string, \Symfony\Component\Finder\SplFileInfo> 
156+      * @param string $dirPath 
157+      * @return array<array{content: string, name: string, path: ?string, custom: bool}> 
147158     */ 
148-     protected  function  guidelineFilesInDir (string  $ dirPathCollection 
149-     {
150-         if  (! is_dir ($ dirPath
151-             $ dirPathstr_replace ('/ ' , DIRECTORY_SEPARATOR , __DIR__ .'/../../.ai/ ' .$ dirPath
152-         }
153- 
154-         try  {
155-             return  collect (iterator_to_array (Finder::create ()
156-                 ->files ()
157-                 ->in ($ dirPath
158-                 ->name ('*.blade.php ' )));
159-         } catch  (DirectoryNotFoundException $ e
160-             return  collect ();
161-         }
162-     }
163- 
164-     protected  function  guidelinesDir (string  $ dirPathstring 
159+     protected  function  guidelinesDir (string  $ dirPatharray 
165160    {
166161        if  (! is_dir ($ dirPath
167162            $ dirPathstr_replace ('/ ' , DIRECTORY_SEPARATOR , __DIR__ .'/../../.ai/ ' .$ dirPath
@@ -173,27 +168,21 @@ protected function guidelinesDir(string $dirPath): ?string
173168                ->in ($ dirPath
174169                ->name ('*.blade.php ' );
175170        } catch  (DirectoryNotFoundException $ e
176-             return  null ;
171+             return  [] ;
177172        }
178173
179-         $ guidelines'' ;
180-         foreach  ($ finderas  $ file
181-             $ guidelines$ this guideline ($ filegetRealPath ()) ?? '' ;
182-             $ guidelinesPHP_EOL ;
183-         }
184- 
185-         return  $ guidelines
174+         return  array_map (fn  ($ file$ this guideline ($ filegetRealPath ()), iterator_to_array ($ finder
186175    }
187176
188-     protected  function  guideline (string  $ pathstring 
177+     /** 
178+      * @param string $path 
179+      * @return array{content: string, name: string, path: ?string, custom: bool} 
180+      */ 
181+     protected  function  guideline (string  $ patharray 
189182    {
190-         if  (! file_exists ($ path
191-             $ pathpreg_replace ('/\.blade\.php$/ ' , '' , $ path
192-             $ pathstr_replace ('/ ' , DIRECTORY_SEPARATOR , __DIR__ .'/../../.ai/ ' .$ path'.blade.php ' );
193-         }
194- 
195-         if  (! file_exists ($ path
196-             return  null ;
183+         $ path$ this guidelinePath ($ path
184+         if  (is_null ($ path
185+             return  ['content '  => '' , 'name '  => '' , 'path '  => null , 'custom '  => false ];
197186        }
198187
199188        $ contentfile_get_contents ($ path
@@ -214,7 +203,12 @@ protected function guideline(string $path): ?string
214203        $ renderedstr_replace (array_keys ($ this storedSnippets ), array_values ($ this storedSnippets ), $ rendered
215204        $ this storedSnippets  = []; // Clear for next use 
216205
217-         return  trim ($ rendered
206+         return  [
207+             'content '  => trim ($ rendered
208+             'name '  => str_replace ('.blade.php ' , '' , basename ($ path
209+             'path '  => $ path
210+             'custom '  => str_contains ($ path$ this customGuidelinePath ()),
211+         ];
218212    }
219213
220214    private  array  $ storedSnippets
@@ -233,4 +227,44 @@ private function processBoostSnippets(string $content): string
233227            return  $ placeholder
234228        }, $ content
235229    }
230+ 
231+     protected  function  prependPackageGuidelinePath (string  $ pathstring 
232+     {
233+         $ pathpreg_replace ('/\.blade\.php$/ ' , '' , $ path
234+         $ pathstr_replace ('/ ' , DIRECTORY_SEPARATOR , __DIR__ .'/../../.ai/ ' .$ path'.blade.php ' );
235+ 
236+         return  $ path
237+     }
238+ 
239+     protected  function  prependUserGuidelinePath (string  $ pathstring 
240+     {
241+         $ pathpreg_replace ('/\.blade\.php$/ ' , '' , $ path
242+         $ pathstr_replace ('/ ' , DIRECTORY_SEPARATOR , $ this customGuidelinePath ($ path'.blade.php ' ));
243+ 
244+         return  $ path
245+     }
246+ 
247+     protected  function  guidelinePath (string  $ pathstring 
248+     {
249+         // Relative path, prepend our package path to it 
250+         if  (! file_exists ($ path
251+             $ path$ this prependPackageGuidelinePath ($ path
252+             if  (! file_exists ($ path
253+                 return  null ;
254+             }
255+         }
256+ 
257+         $ pathrealpath ($ path
258+ 
259+         // If this is a custom guideline, return it unchanged 
260+         if  (str_contains ($ path$ this customGuidelinePath ())) {
261+             return  $ path
262+         }
263+ 
264+         // The path is not a custom guideline, check if the user has an override for this 
265+         $ relativePathltrim (str_replace ([realpath (__DIR__ .'/../../ ' ), '.ai/ ' ], '' , $ path'/ ' );
266+         $ customPath$ this prependUserGuidelinePath ($ relativePath
267+ 
268+         return  file_exists ($ customPath$ customPath$ path
269+     }
236270}
0 commit comments