diff --git a/data-machine.php b/data-machine.php index 506efe2f6..037b5bdb7 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/' ) ) { @@ -563,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 */ @@ -591,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; }