From 8958adc284aa0912ab62dfa5b7a02d3557bd88eb Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Wed, 25 Feb 2026 07:41:48 +0000 Subject: [PATCH 1/2] Include network-activated plugins in multisite scaffolding get_option('active_plugins') only returns subsite-specific plugins on multisite. Network-activated plugins are stored separately in active_sitewide_plugins site option. Merges both lists so the scaffolded SOUL.md reflects all plugins the agent will encounter. --- data-machine.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data-machine.php b/data-machine.php index 506efe2f6..8d16aab17 100644 --- a/data-machine.php +++ b/data-machine.php @@ -418,7 +418,14 @@ function datamachine_get_scaffold_defaults(): array { // --- Active plugins (exclude Data Machine itself) --- $active_plugins = get_option( 'active_plugins', array() ); - $plugin_names = array(); + + // On multisite, include network-activated plugins too. + if ( is_multisite() ) { + $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) ); + $active_plugins = array_unique( array_merge( $active_plugins, $network_plugins ) ); + } + + $plugin_names = array(); foreach ( $active_plugins as $plugin_file ) { if ( 0 === strpos( $plugin_file, 'data-machine/' ) ) { From 5325cf4f4c6393db1dd64f097ee68081f760ed14 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Wed, 25 Feb 2026 08:03:59 +0000 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20self-healing=20scaffolding=20?= =?UTF-8?q?=E2=80=94=20recreate=20missing=20agent=20files=20on=20first=20r?= =?UTF-8?q?ead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent files (SOUL.md, USER.md, MEMORY.md) were only created during the WordPress activation hook. If the plugin was deployed via Homeboy, rsync, or git pull without triggering activation, the agent silently operated without its identity files. Add lazy self-healing via DirectoryManager::ensure_agent_files() — a static once-per-request check that recreates missing files from scaffold defaults. Hooked into all read paths: - CoreMemoryFilesDirective (every AI call) - AgentMemory constructor (CLI/API memory operations) - FileAbilities listAgentFiles/getAgentFile (REST API) Also upgrades AgentMemory::ensure_file_exists() to use rich scaffold content instead of a bare stub, and adds logging for both creation events and persistent missing-file warnings. Closes #408 --- data-machine.php | 12 +++++- inc/Abilities/FileAbilities.php | 6 +++ inc/Core/FilesRepository/AgentMemory.php | 24 ++++++++++- inc/Core/FilesRepository/DirectoryManager.php | 43 +++++++++++++++++++ .../Directives/CoreMemoryFilesDirective.php | 9 ++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/data-machine.php b/data-machine.php index 8d16aab17..037b5bdb7 100644 --- a/data-machine.php +++ b/data-machine.php @@ -570,8 +570,9 @@ function datamachine_get_scaffold_defaults(): array { /** * Create default agent memory files if they don't exist. * - * Called on activation to ensure fresh installs have starter templates - * for all default memory files. Existing files are never overwritten. + * Called on activation and lazily on any request that reads agent files + * (via DirectoryManager::ensure_agent_files()). Existing files are never + * overwritten — only missing files are recreated from scaffold defaults. * * @since 0.30.0 */ @@ -598,6 +599,13 @@ function datamachine_ensure_default_memory_files() { } $fs->put_contents( $filepath, $content . "\n", FS_CHMOD_FILE ); + + do_action( + 'datamachine_log', + 'notice', + sprintf( 'Self-healing: created missing agent file %s with scaffold defaults.', $filename ), + array( 'filename' => $filename ) + ); } } diff --git a/inc/Abilities/FileAbilities.php b/inc/Abilities/FileAbilities.php index 0e7de6691..7db133de0 100644 --- a/inc/Abilities/FileAbilities.php +++ b/inc/Abilities/FileAbilities.php @@ -906,6 +906,9 @@ private function deleteFileFromFlow( string $filename, string $flow_step_id ): a * @return array Result with files. */ private function listAgentFiles(): array { + // Self-heal: ensure agent files exist before listing. + DirectoryManager::ensure_agent_files(); + $directory_manager = new DirectoryManager(); $agent_dir = $directory_manager->get_agent_directory(); @@ -972,6 +975,9 @@ private function listAgentFiles(): array { * @return array Result with file data. */ private function getAgentFile( string $filename ): array { + // Self-heal: ensure agent files exist before retrieval. + DirectoryManager::ensure_agent_files(); + $directory_manager = new DirectoryManager(); $agent_dir = $directory_manager->get_agent_directory(); $filepath = "{$agent_dir}/{$filename}"; diff --git a/inc/Core/FilesRepository/AgentMemory.php b/inc/Core/FilesRepository/AgentMemory.php index 4d4490fe7..d6d4a9902 100644 --- a/inc/Core/FilesRepository/AgentMemory.php +++ b/inc/Core/FilesRepository/AgentMemory.php @@ -39,6 +39,9 @@ public function __construct() { $this->directory_manager = new DirectoryManager(); $agent_dir = $this->directory_manager->get_agent_directory(); $this->file_path = "{$agent_dir}/MEMORY.md"; + + // Self-heal: ensure agent files exist on first use. + DirectoryManager::ensure_agent_files(); } /** @@ -367,13 +370,32 @@ private function replace_section_content( string $file_content, array $position, /** * Ensure the memory file and directory exist. + * + * Uses scaffold defaults when available instead of a bare stub, + * so a recreated MEMORY.md includes the standard sections. */ private function ensure_file_exists(): void { $agent_dir = $this->directory_manager->get_agent_directory(); $this->directory_manager->ensure_directory_exists( $agent_dir ); if ( ! file_exists( $this->file_path ) ) { - file_put_contents( $this->file_path, "# Agent Memory\n" ); + $content = "# Agent Memory\n"; + + if ( function_exists( 'datamachine_get_scaffold_defaults' ) ) { + $defaults = datamachine_get_scaffold_defaults(); + if ( isset( $defaults['MEMORY.md'] ) ) { + $content = $defaults['MEMORY.md'] . "\n"; + } + } + + file_put_contents( $this->file_path, $content ); + + do_action( + 'datamachine_log', + 'notice', + 'Self-healing: created missing MEMORY.md on write path.', + array() + ); } } diff --git a/inc/Core/FilesRepository/DirectoryManager.php b/inc/Core/FilesRepository/DirectoryManager.php index 420323843..6db17b30f 100644 --- a/inc/Core/FilesRepository/DirectoryManager.php +++ b/inc/Core/FilesRepository/DirectoryManager.php @@ -23,6 +23,49 @@ class DirectoryManager { */ private const REPOSITORY_DIR = 'datamachine-files'; + /** + * Whether agent file scaffolding has been verified this request. + * + * @var bool + */ + private static bool $agent_files_ensured = false; + + /** + * Ensure default agent files exist (SOUL.md, USER.md, MEMORY.md). + * + * Lazy self-healing: runs at most once per PHP request on the first + * call. If any registered memory files are missing, recreates them + * from scaffold defaults without overwriting existing files. + * + * Call this from any read path that depends on agent files existing + * (directives, REST API, CLI, etc.). The static flag makes repeated + * calls free. + * + * @since 0.32.0 + * @return void + */ + public static function ensure_agent_files(): void { + if ( self::$agent_files_ensured ) { + return; + } + + self::$agent_files_ensured = true; + + if ( function_exists( 'datamachine_ensure_default_memory_files' ) ) { + datamachine_ensure_default_memory_files(); + } + } + + /** + * Reset the ensure-agent-files flag. For testing only. + * + * @since 0.32.0 + * @return void + */ + public static function reset_ensure_flag(): void { + self::$agent_files_ensured = false; + } + /** * Get pipeline directory path * diff --git a/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php b/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php index 1a5a4e390..9e1de3293 100644 --- a/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php +++ b/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php @@ -45,6 +45,9 @@ public static function get_outputs( string $provider_name, array $tools, ?string return array(); } + // Self-heal: ensure agent files exist before reading. + DirectoryManager::ensure_agent_files(); + $directory_manager = new DirectoryManager(); $agent_dir = $directory_manager->get_agent_directory(); $outputs = array(); @@ -53,6 +56,12 @@ public static function get_outputs( string $provider_name, array $tools, ?string $filepath = "{$agent_dir}/{$filename}"; if ( ! file_exists( $filepath ) ) { + do_action( + 'datamachine_log', + 'warning', + sprintf( 'Core memory file %s missing from agent directory after scaffolding check.', $filename ), + array( 'filename' => $filename ) + ); continue; }