Skip to content

Commit 8535ecd

Browse files
authored
refactor: stop narrating core's datamachine namespace in DMC AGENTS.md section (#735)
DMC's AgentsMdSections.php hand-typed core's entire `datamachine` command surface as a static heredoc, a layer inversion: core now registers its own reflected AGENTS.md section (data-machine#2640), and the hand-copy had already drifted (the `datamachine analytics|logs` line was wrong — analytics moved to data-machine-business). - Remove the hand-typed core `datamachine ...` narration (Memory/Automation/ Communication/Content-ops/System bullets). Core owns that section. - Rename the section to `datamachine-code` and keep only the workspace/worktree/ github guidance DMC rightfully owns. - Reflect the `worktree` operation list from its dispatch `match` arms via a new CommandIntrospector::match_arm_pipe_list() helper, mirroring how workspace/ github are already reflected. No hand-typed DMC command lists remain; the reflected list also recovers operations the literal omitted. Refs #734
1 parent edd42e3 commit 8535ecd

2 files changed

Lines changed: 119 additions & 50 deletions

File tree

inc/Runtime/AgentsMdSections.php

Lines changed: 20 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -83,78 +83,48 @@ private static function register_auto_generated_marker( string $wp ): void {
8383

8484
private static function register_datamachine_section( string $wp ): void {
8585
self::register_section(
86-
'AGENTS.md', 'datamachine', 10, function () use ( $wp ) {
86+
'AGENTS.md', 'datamachine-code', 10, function () use ( $wp ) {
8787
$workspace_path = self::resolve_workspace_path();
88-
$agent_slug = self::resolve_agent_slug();
89-
$agent_suffix = '' !== $agent_slug ? ' --agent=' . $agent_slug : '';
90-
$agent_note = '' !== $agent_slug
91-
? "On this site, scope memory commands with `--agent={$agent_slug}` when reading, writing, searching, or composing agent memory."
92-
: 'On multi-agent installs, pass `--agent=<slug>` to memory commands when auto-resolution is ambiguous.';
9388

9489
// Generate DMC's own command surface from reflection so the lists
95-
// never drift from the registered truth (see #671). The fallback
96-
// pipe-lists are only used if a command class is somehow
97-
// unavailable in this compose context.
90+
// never drift from the registered truth (see #671, #734). The
91+
// fallback pipe-lists are only used if a command class is somehow
92+
// unavailable in this compose context. Core's `datamachine`
93+
// namespace is NOT narrated here — core registers its own
94+
// reflected AGENTS.md section (data-machine#2640); hand-copying it
95+
// here is a layer inversion that silently drifts (e.g. the removed
96+
// `datamachine analytics` line, now owned by data-machine-business).
9897
$workspace_subcmds = CommandIntrospector::pipe_list(
9998
'\\DataMachineCode\\Cli\\Commands\\WorkspaceCommand',
10099
'adopt|clone|list|show|path|hygiene|remove|worktree|read|write|grep|edit|git|patch|ls'
101100
);
101+
$worktree_subcmds = CommandIntrospector::match_arm_pipe_list(
102+
'\\DataMachineCode\\Cli\\Commands\\WorkspaceCommand',
103+
'worktree',
104+
'add|list|remove|prune|cleanup|cleanup-artifacts|reconcile-metadata|refresh-context|finalize|mark-cleanup-eligible'
105+
);
102106
$github_subcmds = CommandIntrospector::pipe_list(
103107
'\\DataMachineCode\\Cli\\Commands\\GitHubCommand',
104108
'issues|pulls|repos|status|view|close|review-flow|comment'
105109
);
106110
return <<<MD
107-
## Data Machine
108-
109-
Data Machine is your operating layer — memory, automation, and orchestration via WP-CLI.
110-
111-
Discover the full command surface: `{$wp} datamachine --help`. The groups below are the major command families — always run `--help` on any subcommand to see its options.
112-
113-
**Memory & Agents:** Persistent files across sessions plus agent identity management.
114-
- Memory paths / read / write / search / compose: `{$wp} datamachine memory paths|read|write|search|compose{$agent_suffix}`
115-
- Agent management: `{$wp} datamachine agent list|create|access|token|installed|install|diff` — identities, permissions, bearer tokens, portable bundles
116-
- Update MEMORY.md when you learn something persistent — read it first, append new info.
117-
- {$agent_note}
118-
119-
**Automation:** Self-scheduling workflows that run without human intervention.
120-
- Flows: `{$wp} datamachine flow create|run|list` — scheduled or on-demand tasks
121-
- Pipelines: `{$wp} datamachine pipeline create|list` — multi-step processing chains
122-
- Jobs / pending actions: `{$wp} datamachine jobs list|retry|summary`, `{$wp} datamachine pending-actions` — monitor queued work and approval gates
123-
- Drain due work: `{$wp} datamachine drain` — run due actions until empty or budgeted
124-
- Discover available step types: `{$wp} datamachine step-types list`
125-
- Discover available handlers: `{$wp} datamachine handlers list`
126-
- Processed items (dedupe): `{$wp} datamachine processed-items`
127-
- Retention policies: `{$wp} datamachine retention`
128-
129-
**Communication:** Chat sessions and email I/O.
130-
- Chat: `{$wp} datamachine chat` — multi-turn agent conversations with tool calling
131-
- Email: `{$wp} datamachine email` — IMAP read / SMTP reply (wired to the site's mail stack)
132-
133-
**Content ops:** Post-level and site-wide content tooling.
134-
- Posts / taxonomy / blocks: `{$wp} datamachine post|taxonomy|block`
135-
- SEO helpers: `{$wp} datamachine alt-text|meta-description|image|link|indexnow`
136-
- Analytics & logs: `{$wp} datamachine analytics|logs`
137-
- Settings & auth: `{$wp} datamachine settings|auth`
138-
- External sites & handler tests: `{$wp} datamachine external|test`
139-
140-
**Code (data-machine-code):** All code changes happen in Data Machine Code worktrees under `{$workspace_path}`. DMC owns workspace lifecycle, evidence capture, and GitHub workflow glue; file CRUD inside a worktree uses whatever tool is fastest.
111+
## Data Machine Code
112+
113+
All code changes happen in Data Machine Code worktrees under `{$workspace_path}`. DMC owns workspace lifecycle, evidence capture, and GitHub workflow glue; file CRUD inside a worktree uses whatever tool is fastest. Core's `datamachine` operating layer (memory, automation, communication, content ops, system) is documented in its own AGENTS.md section — run `{$wp} datamachine --help` to discover it.
114+
141115
- Workspace root: `{$workspace_path}`
142116
- **Workspace:** `{$wp} datamachine-code workspace {$workspace_subcmds}` — lifecycle (clone/adopt/list/show/path/hygiene/remove/worktree), plus the file-I/O surface you work through inside a worktree (`read`, `write`, `grep`, `edit`, `patch`, `ls`, `git`). Keeps the on-disk registry consistent and enforces the `<repo>@<slug>` handle convention.
143-
- **Worktrees:** `{$wp} datamachine-code workspace worktree add|list|remove|prune|cleanup|cleanup-artifacts|reconcile-metadata|refresh-context|finalize|mark-cleanup-eligible` — create isolated branches, refresh agent context, attach lifecycle metadata, and clean up safely.
117+
- **Worktrees:** `{$wp} datamachine-code workspace worktree {$worktree_subcmds}` — create isolated branches, refresh agent context, attach lifecycle metadata, and clean up safely.
144118
- **GitHub:** `{$wp} datamachine-code github {$github_subcmds}` — list/read GitHub state, manage issues and PRs, install review flows, and comment on reviews.
145119
- **Editing inside a worktree:** any tool. Local agents on the same disk should use native file I/O and raw `git`; routing edits through workspace abilities is ceremony, not safety.
146120
- **Workspace lifecycle:** use `workspace clone` for primary checkout adoption/cloning and `workspace worktree add` for isolated branches. Use the CLI `--help` output for current flags and subcommands.
147121
- **Primary freshness:** before using a primary checkout for investigation or verification, inspect `workspace list|show|hygiene` freshness metadata. If the primary is stale, run `workspace git pull <repo> --allow-primary-refresh` or create the worktree from an explicit remote ref with `worktree add <repo> <branch> --from=origin/<base>`. Stale primary reads require an explicit `--allow-stale-primary` opt-in. Do not clone a second top-level primary for the same remote just to get fresh code.
148122
- **Primary is read-only.** Never edit `<workspace>/<repo>` (no `@slug`). Safe primary refresh uses `--allow-primary-refresh`; primary commit, push, reset, and rebase require the stronger `--allow-dangerous-primary-mutation` approval. The primary tracks the deployed branch — operate on a worktree.
149123
- **Rule:** Never modify files under `wp-content/plugins/` or `wp-content/themes/` directly. Those paths are **read-only reference**. All code changes go through the workspace so they are tracked in git and reviewed via pull requests.
150-
151-
**System:** `{$wp} datamachine system health|prompts|run` — site health, prompt inspection, diagnostic runs.
152-
153-
Use `--help` on any command to discover options and subcommands.
154124
MD;
155125
}, array(
156-
'label' => 'Data Machine',
157-
'description' => 'Memory, automation, workspace, and system operations.',
126+
'label' => 'Data Machine Code',
127+
'description' => 'Workspace, worktree, and GitHub operations owned by Data Machine Code.',
158128
'owner' => 'data-machine-code',
159129
'freshness' => 'snapshot',
160130
'conditions' => 'Always registered when Data Machine Code and composable memory section registration are available.',

inc/Runtime/CommandIntrospector.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,105 @@ public static function pipe_list( string $command_class, string $fallback = '' )
103103
return implode('|', $names);
104104
}
105105

106+
/**
107+
* Render a `a|b|c` pipe-list of the string-literal `match` arm keys inside a
108+
* command method, in declaration order, de-duplicated.
109+
*
110+
* Some `@subcommand` methods dispatch a second operand (e.g. `workspace
111+
* worktree <op>`) through an internal `match ( $operation ) { ... }` rather
112+
* than separate annotated methods. The arm keys ARE the real operation
113+
* surface, so reflecting them keeps AGENTS.md truthful without hand-typing
114+
* the list (see Extra-Chill/data-machine-code#734).
115+
*
116+
* Falls back to the supplied default string when reflection yields nothing,
117+
* so AGENTS.md never renders an empty command line.
118+
*
119+
* @param string $command_class Fully-qualified command class name.
120+
* @param string $method Method whose body holds the dispatch `match`.
121+
* @param string $fallback Pipe-list to use when reflection is unavailable.
122+
* @return string
123+
*/
124+
public static function match_arm_pipe_list( string $command_class, string $method, string $fallback = '' ): string {
125+
$keys = self::match_arm_keys($command_class, $method);
126+
if ( empty($keys) ) {
127+
return $fallback;
128+
}
129+
130+
return implode('|', $keys);
131+
}
132+
133+
/**
134+
* Extract the string-literal arm keys of the first `match` expression in a
135+
* method body, in source order and de-duplicated.
136+
*
137+
* Reads the method's source range via reflection and scans the `match (...)`
138+
* block for `'literal' =>` / `"literal" =>` arm keys (including comma-grouped
139+
* keys mapping to one result). The `default =>` arm is ignored.
140+
*
141+
* Returns an empty array when the class/method/source is unavailable, so
142+
* callers can fall back gracefully without fatals in any context.
143+
*
144+
* @param string $command_class Fully-qualified command class name.
145+
* @param string $method Method name to inspect.
146+
* @return string[] Ordered, de-duplicated list of match arm keys.
147+
*/
148+
public static function match_arm_keys( string $command_class, string $method ): array {
149+
if ( ! class_exists('WP_CLI_Command', false) ) {
150+
return array();
151+
}
152+
153+
// class_exists() triggers autoloading; once it returns true the
154+
// ReflectionClass constructor cannot throw, so no try/catch is needed.
155+
if ( ! class_exists($command_class) ) {
156+
return array();
157+
}
158+
159+
$reflection = new \ReflectionClass($command_class);
160+
if ( ! $reflection->hasMethod($method) ) {
161+
return array();
162+
}
163+
164+
$reflection_method = $reflection->getMethod($method);
165+
$file = $reflection_method->getFileName();
166+
$start_line = $reflection_method->getStartLine();
167+
$end_line = $reflection_method->getEndLine();
168+
169+
if ( false === $file || ! is_readable($file) || $start_line < 1 || $end_line < $start_line ) {
170+
return array();
171+
}
172+
173+
$source_lines = file($file, FILE_IGNORE_NEW_LINES);
174+
if ( false === $source_lines ) {
175+
return array();
176+
}
177+
178+
$body = implode("\n", array_slice($source_lines, $start_line - 1, ( $end_line - $start_line ) + 1));
179+
180+
// Isolate the first `match (...)` block so we don't pick up unrelated
181+
// associative arrays elsewhere in the method body.
182+
if ( ! preg_match('/\bmatch\s*\(/', $body, $match_pos, PREG_OFFSET_CAPTURE) ) {
183+
return array();
184+
}
185+
$body = substr($body, (int) $match_pos[0][1]);
186+
187+
// Each arm key is a single- or double-quoted literal immediately
188+
// preceding `=>` (comma-grouped keys each match individually).
189+
if ( ! preg_match_all('/([\'"])([^\'"\\\\]+)\1\s*=>/', $body, $arm_matches) ) {
190+
return array();
191+
}
192+
193+
$keys = array();
194+
foreach ( $arm_matches[2] as $key ) {
195+
$key = trim($key);
196+
if ( '' === $key || in_array($key, $keys, true) ) {
197+
continue;
198+
}
199+
$keys[] = $key;
200+
}
201+
202+
return $keys;
203+
}
204+
106205
/**
107206
* Pull the `@subcommand <name>` value out of a PHPDoc block.
108207
*

0 commit comments

Comments
 (0)