Skip to content

Commit b3127bd

Browse files
refactor: make AGENTS workspace policy filterable (#760)
Co-authored-by: homeboy-ci[bot] <266378653+homeboy-ci[bot]@users.noreply.github.com>
1 parent ebb4826 commit b3127bd

2 files changed

Lines changed: 162 additions & 5 deletions

File tree

inc/Runtime/AgentsMdSections.php

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ private static function register_auto_generated_marker( string $wp ): void {
9292
private static function register_datamachine_section( string $wp ): void {
9393
self::register_section(
9494
'AGENTS.md', 'datamachine-code', 10, function () use ( $wp ) {
95-
$workspace_path = self::resolve_workspace_path();
95+
$workspace_path = self::resolve_workspace_path();
96+
$workspace_policy_intro = self::render_workspace_policy_intro($workspace_path);
97+
$workspace_policy_section = self::render_workspace_policy_section($workspace_path, $wp);
9698

9799
// Generate DMC's own command surface from reflection so the lists
98100
// never drift from the registered truth (see #671, #734). The
@@ -118,17 +120,15 @@ private static function register_datamachine_section( string $wp ): void {
118120
return <<<MD
119121
## Data Machine Code
120122
121-
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.
123+
{$workspace_policy_intro}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.
122124
123125
- Workspace root: `{$workspace_path}`
124126
- **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.
125127
- **Worktrees:** `{$wp} datamachine-code workspace worktree {$worktree_subcmds}` — create isolated branches, refresh agent context, attach lifecycle metadata, and clean up safely.
126128
- **GitHub:** `{$wp} datamachine-code github {$github_subcmds}` — list/read GitHub state, manage issues and PRs, install review flows, and comment on reviews.
127129
- **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.
128130
- **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.
129-
- **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.
130-
- **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.
131-
- **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.
131+
{$workspace_policy_section}
132132
MD;
133133
}, array(
134134
'label' => 'Data Machine Code',
@@ -140,6 +140,49 @@ private static function register_datamachine_section( string $wp ): void {
140140
);
141141
}
142142

143+
private static function render_workspace_policy_intro( string $workspace_path ): string {
144+
$default = "All code changes happen in Data Machine Code worktrees under `{$workspace_path}`. ";
145+
146+
/**
147+
* Filters the site-owned workspace policy sentence rendered before DMC command facts.
148+
*
149+
* @param string $default Default policy markdown.
150+
* @param string $workspace_path Resolved DMC workspace root.
151+
*/
152+
$filtered = apply_filters('datamachine_code_workspace_policy_intro', $default, $workspace_path);
153+
if ( ! is_string($filtered) ) {
154+
return $default;
155+
}
156+
157+
return $filtered;
158+
}
159+
160+
private static function render_workspace_policy_section( string $workspace_path, string $wp ): string {
161+
$default = <<<'MD'
162+
- **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.
163+
- **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.
164+
- **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.
165+
MD;
166+
167+
/**
168+
* Filters site-owned workspace policy guidance rendered in AGENTS.md.
169+
*
170+
* Data Machine Code owns the workspace command facts above. Callers own
171+
* local policy, such as primary checkout mutability and read-only source
172+
* directory rules. Return an empty string to omit the policy block.
173+
*
174+
* @param string $default Default policy markdown.
175+
* @param string $workspace_path Resolved DMC workspace root.
176+
* @param string $wp WP-CLI command prefix.
177+
*/
178+
$filtered = apply_filters('datamachine_code_workspace_policy_section', $default, $workspace_path, $wp);
179+
if ( ! is_string($filtered) ) {
180+
return $default;
181+
}
182+
183+
return trim($filtered);
184+
}
185+
143186
private static function register_workspace_inventory_section( string $wp ): void {
144187
self::register_section(
145188
'AGENTS.md', 'workspace-inventory', 15, function () use ( $wp ) {

tests/smoke-agents-md-sections.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DataMachine\Engine\AI {
6+
final class MemoryFileRegistry {
7+
public const LAYER_SHARED = 'shared';
8+
9+
public static array $files = array();
10+
11+
public static function register( string $file, int $priority, array $metadata ): void {
12+
self::$files[] = compact('file', 'priority', 'metadata');
13+
}
14+
}
15+
16+
final class SectionRegistry {
17+
public static array $sections = array();
18+
19+
public static function register( string $file, string $section, int $priority, callable $callback, array $metadata ): void {
20+
self::$sections[ $section ] = compact('file', 'section', 'priority', 'callback', 'metadata');
21+
}
22+
}
23+
}
24+
25+
namespace {
26+
if ( ! defined('ABSPATH') ) {
27+
define('ABSPATH', '/var/www/html');
28+
}
29+
30+
$GLOBALS['datamachine_code_test_filters'] = array();
31+
32+
function datamachine_agents_md_enabled(): bool {
33+
return true;
34+
}
35+
36+
function is_multisite(): bool {
37+
return false;
38+
}
39+
40+
function apply_filters( string $hook_name, mixed $value, mixed ...$args ): mixed {
41+
$filters = $GLOBALS['datamachine_code_test_filters'][ $hook_name ] ?? array();
42+
foreach ( $filters as $filter ) {
43+
$value = $filter($value, ...$args);
44+
}
45+
46+
return $value;
47+
}
48+
49+
function add_test_filter( string $hook_name, callable $callback ): void {
50+
$GLOBALS['datamachine_code_test_filters'][ $hook_name ][] = $callback;
51+
}
52+
53+
function assert_contains( string $needle, string $haystack, string $message ): void {
54+
if ( ! str_contains($haystack, $needle) ) {
55+
throw new RuntimeException($message);
56+
}
57+
}
58+
59+
function assert_not_contains( string $needle, string $haystack, string $message ): void {
60+
if ( str_contains($haystack, $needle) ) {
61+
throw new RuntimeException($message);
62+
}
63+
}
64+
65+
require_once dirname(__DIR__) . '/inc/Runtime/CommandIntrospector.php';
66+
require_once dirname(__DIR__) . '/inc/Runtime/AgentsMdSections.php';
67+
68+
\DataMachineCode\Runtime\AgentsMdSections::register();
69+
70+
$sections = \DataMachine\Engine\AI\SectionRegistry::$sections;
71+
if ( ! isset($sections['datamachine-code']) ) {
72+
throw new RuntimeException('datamachine-code section was not registered');
73+
}
74+
75+
$render = $sections['datamachine-code']['callback'];
76+
$default = $render();
77+
78+
assert_contains(
79+
'All code changes happen in Data Machine Code worktrees under `unavailable; run datamachine-code workspace path to diagnose`. DMC owns workspace lifecycle',
80+
$default,
81+
'default workspace policy intro changed'
82+
);
83+
assert_contains(
84+
'- **Primary is read-only.** Never edit `<workspace>/<repo>` (no `@slug`).',
85+
$default,
86+
'default workspace policy section missing'
87+
);
88+
assert_contains(
89+
'- **Workspace:** `wp datamachine-code workspace adopt|clone|list|show|path|hygiene|remove|worktree|read|write|grep|edit|git|patch|ls`',
90+
$default,
91+
'DMC workspace command facts missing'
92+
);
93+
94+
add_test_filter(
95+
'datamachine_code_workspace_policy_intro',
96+
static function ( string $default, string $workspace_path ): string {
97+
return "Use local project policy for `{$workspace_path}`. ";
98+
}
99+
);
100+
add_test_filter(
101+
'datamachine_code_workspace_policy_section',
102+
static function (): string {
103+
return '- **Local policy:** caller-owned workspace rules.';
104+
}
105+
);
106+
107+
$filtered = $render();
108+
assert_contains('Use local project policy for `unavailable; run datamachine-code workspace path to diagnose`. DMC owns workspace lifecycle', $filtered, 'workspace policy intro filter was not applied');
109+
assert_contains('- **Local policy:** caller-owned workspace rules.', $filtered, 'workspace policy section filter was not applied');
110+
assert_not_contains('- **Primary is read-only.** Never edit `<workspace>/<repo>` (no `@slug`).', $filtered, 'default policy section remained after filter override');
111+
assert_contains('- **Workspace:** `wp datamachine-code workspace adopt|clone|list|show|path|hygiene|remove|worktree|read|write|grep|edit|git|patch|ls`', $filtered, 'DMC command facts changed after policy filter');
112+
113+
fwrite(STDOUT, "agents-md sections smoke passed\n");
114+
}

0 commit comments

Comments
 (0)