Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions data-machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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/' ) ) {
Expand Down Expand Up @@ -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
*/
Expand All @@ -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 )
);
}
}

Expand Down
6 changes: 6 additions & 0 deletions inc/Abilities/FileAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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}";
Expand Down
24 changes: 23 additions & 1 deletion inc/Core/FilesRepository/AgentMemory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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()
);
}
}

Expand Down
43 changes: 43 additions & 0 deletions inc/Core/FilesRepository/DirectoryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
9 changes: 9 additions & 0 deletions inc/Engine/AI/Directives/CoreMemoryFilesDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}

Expand Down