22/**
33 * AGENTS.md Section Generator
44 *
5- * Generates the "Extra Chill CLI" section of the composed AGENTS.md file by
6- * introspecting the real registered command tree instead of hand-maintaining
7- * a heredoc. Walks every command registered in CommandRegistry, reflects over
8- * each command class's public methods, and emits each namespace with its real
9- * subcommands and their PHPDoc short descriptions.
10- *
11- * Context-safe: the section callback runs on `plugins_loaded` in web/cron
12- * compose contexts where the WP-CLI runtime and the plugin's PSR-4 autoloader
13- * (both guarded by `if ( WP_CLI )`) are NOT loaded. This generator therefore
14- * resolves command class files directly from disk via the same PSR-4 layout
15- * and reflects over the class files, never relying on the live WP-CLI runner.
16- *
17- * Pending Extra-Chill/data-machine#2613: once that shared
18- * `CliCommandIntrospector` helper lands, this class can delegate the
19- * reflection/parsing to it and keep only the CommandRegistry map local.
20- *
215 * @package ExtraChill\CLI
226 */
237
248namespace ExtraChill \CLI ;
259
26- use ReflectionClass ;
27- use ReflectionMethod ;
28-
2910if ( ! defined ( 'ABSPATH ' ) ) {
3011 exit ;
3112}
@@ -35,198 +16,28 @@ class AgentsMdSection {
3516 /**
3617 * Build the Markdown body for the Extra Chill CLI AGENTS.md section.
3718 *
38- * @param string $wp The `wp --allow-root --path=...` invocation prefix.
19+ * @param string $wp The composed WP-CLI invocation prefix.
3920 * @return string
4021 */
4122 public static function render ( $ wp ) {
4223 $ lines = array ();
4324 $ lines [] = '### Extra Chill CLI ' ;
4425 $ lines [] = '' ;
45- $ lines [] = 'Platform-specific tooling wrapping common operations into unified commands. ' ;
46- $ lines [] = "Discover everything: ` {$ wp } extrachill --help` " ;
26+ $ lines [] = 'Extra Chill CLI owns the unified WP-CLI surface for Extra Chill platform operations. Feature plugins own the underlying abilities and business behavior. ' ;
4727 $ lines [] = '' ;
48-
49- foreach ( self ::collect_commands () as $ command => $ subcommands ) {
50- // Strip the leading "extrachill " for the human-readable summary,
51- // but keep the full invocation in the code span.
52- $ summary = self ::summarize_subcommands ( $ subcommands );
53-
54- if ( '' !== $ summary ) {
55- $ lines [] = "- ` {$ wp } {$ command }` — {$ summary }" ;
56- } else {
57- $ lines [] = "- ` {$ wp } {$ command }` " ;
58- }
59-
60- foreach ( $ subcommands as $ sub ) {
61- if ( '__default ' === $ sub ['name ' ] ) {
62- continue ;
63- }
64- $ desc = $ sub ['description ' ];
65- if ( '' !== $ desc ) {
66- $ lines [] = " - ` {$ sub ['name ' ]}` — {$ desc }" ;
67- } else {
68- $ lines [] = " - ` {$ sub ['name ' ]}` " ;
69- }
70- }
71- }
72-
28+ $ lines [] = '**Default routing** ' ;
29+ $ lines [] = "- Platform health and analytics: ` {$ wp } extrachill platform health` and ` {$ wp } extrachill analytics summary` " ;
30+ $ lines [] = "- Events and venues: ` {$ wp } extrachill events --help` and ` {$ wp } extrachill venues --help` " ;
31+ $ lines [] = "- Artists, users, and community: ` {$ wp } extrachill artists --help`, ` {$ wp } extrachill users --help`, and ` {$ wp } extrachill community --help` " ;
32+ $ lines [] = "- Content and newsletters: ` {$ wp } extrachill content --help` and ` {$ wp } extrachill newsletter --help` " ;
33+ $ lines [] = "- Diagnostics: ` {$ wp } extrachill analytics errors`, ` {$ wp } extrachill analytics 404 summary`, and ` {$ wp } extrachill cache status` " ;
7334 $ lines [] = '' ;
74- $ lines [] = 'All commands support `--help` for full options and subcommand discovery. ' ;
35+ $ lines [] = '**Multisite** ' ;
36+ $ lines [] = 'Keep the composed WP-CLI prefix and its site target. Use `--url=<site-url>` when a command needs data or abilities owned by another network site; without a site target, WP-CLI uses the main site. ' ;
37+ $ lines [] = '' ;
38+ $ lines [] = '**Discovery** ' ;
39+ $ lines [] = "Use ` {$ wp } extrachill --help` and ` {$ wp } extrachill <namespace> --help` for the complete live command, subcommand, and options contract. Live `--help` is authoritative; this section intentionally does not enumerate every action. " ;
7540
7641 return implode ( "\n" , $ lines );
7742 }
78-
79- /**
80- * Walk the CommandRegistry and reflect each class into its subcommands.
81- *
82- * @return array<string, array<int, array{name:string, description:string}>>
83- * command string => ordered list of subcommands.
84- */
85- private static function collect_commands () {
86- $ out = array ();
87-
88- foreach ( CommandRegistry::map () as $ command => $ class ) {
89- $ subcommands = self ::reflect_subcommands ( $ class );
90- $ out [ $ command ] = $ subcommands ;
91- }
92-
93- return $ out ;
94- }
95-
96- /**
97- * Reflect a command class into its list of public subcommands.
98- *
99- * Public methods are WP-CLI subcommands. The subcommand name is taken from
100- * the `@subcommand <name>` annotation when present, otherwise the method
101- * name with underscores converted to hyphens (WP-CLI's own convention).
102- * `__invoke` / `@subcommand __default` represent the directly-invokable
103- * namespace itself. Private, protected, static, and magic helper methods
104- * are skipped.
105- *
106- * @param class-string $class Command class.
107- * @return array<int, array{name:string, description:string}>
108- */
109- private static function reflect_subcommands ( $ class ) {
110- // The autoloader (registered before this section is composed) resolves
111- // the class file and any traits it uses; class_exists triggers it.
112- if ( ! class_exists ( $ class ) ) {
113- return array ();
114- }
115-
116- try {
117- $ reflection = new ReflectionClass ( $ class );
118- } catch ( \Throwable $ e ) {
119- return array ();
120- }
121-
122- $ subcommands = array ();
123-
124- foreach ( $ reflection ->getMethods ( ReflectionMethod::IS_PUBLIC ) as $ method ) {
125- // Only methods declared on the command class itself.
126- if ( $ method ->getDeclaringClass ()->getName () !== $ reflection ->getName () ) {
127- continue ;
128- }
129-
130- if ( $ method ->isStatic () || $ method ->isConstructor () || $ method ->isDestructor () ) {
131- continue ;
132- }
133-
134- $ method_name = $ method ->getName ();
135- $ doc = $ method ->getDocComment () ?: '' ;
136-
137- // `__invoke` (or any method tagged @subcommand __default) maps to
138- // the namespace itself, so it has no sub-word.
139- $ annotated = self ::parse_subcommand_annotation ( $ doc );
140-
141- if ( '__invoke ' === $ method_name && '' === $ annotated ) {
142- $ annotated = '__default ' ;
143- }
144-
145- if ( '' !== $ annotated ) {
146- $ name = $ annotated ;
147- } else {
148- // Skip other magic methods that are not subcommands.
149- if ( 0 === strpos ( $ method_name , '__ ' ) ) {
150- continue ;
151- }
152- $ name = str_replace ( '_ ' , '- ' , $ method_name );
153- }
154-
155- $ subcommands [] = array (
156- 'name ' => $ name ,
157- 'description ' => self ::parse_short_description ( $ doc ),
158- );
159- }
160-
161- return $ subcommands ;
162- }
163-
164- /**
165- * Build a comma-separated summary of subcommand names for the headline.
166- *
167- * @param array<int, array{name:string, description:string}> $subcommands Subcommands.
168- * @return string
169- */
170- private static function summarize_subcommands ( $ subcommands ) {
171- $ names = array ();
172- foreach ( $ subcommands as $ sub ) {
173- if ( '__default ' === $ sub ['name ' ] ) {
174- continue ;
175- }
176- $ names [] = $ sub ['name ' ];
177- }
178-
179- return implode ( ', ' , $ names );
180- }
181-
182- /**
183- * Parse the `@subcommand <name>` annotation from a docblock.
184- *
185- * @param string $doc Raw docblock.
186- * @return string Subcommand name, or '' when not annotated.
187- */
188- private static function parse_subcommand_annotation ( $ doc ) {
189- if ( preg_match ( '/@subcommand\s+(\S+)/ ' , $ doc , $ m ) ) {
190- return $ m [1 ];
191- }
192- return '' ;
193- }
194-
195- /**
196- * Parse the short description (first prose line) from a docblock.
197- *
198- * Mirrors how WP-CLI derives a command's summary for `--help`: the first
199- * non-empty content line of the docblock, before any `## SECTION` heading.
200- *
201- * @param string $doc Raw docblock.
202- * @return string
203- */
204- private static function parse_short_description ( $ doc ) {
205- if ( '' === $ doc ) {
206- return '' ;
207- }
208-
209- // Strip the comment framing.
210- $ doc = preg_replace ( '#^/\*\*|\*/$# ' , '' , $ doc );
211- $ lines = preg_split ( '/\r\n|\r|\n/ ' , $ doc );
212-
213- foreach ( $ lines as $ line ) {
214- $ line = preg_replace ( '/^\s*\*\s?/ ' , '' , $ line );
215- $ line = trim ( $ line );
216-
217- if ( '' === $ line ) {
218- continue ;
219- }
220-
221- // Stop at structured sections / annotations.
222- if ( 0 === strpos ( $ line , '## ' ) || 0 === strpos ( $ line , '@ ' ) ) {
223- return '' ;
224- }
225-
226- // Drop a trailing period for a tighter inline summary.
227- return rtrim ( $ line , '. ' );
228- }
229-
230- return '' ;
231- }
23243}
0 commit comments