From 515c0feae407d38df66ace2417ac93c5d0f7e561 Mon Sep 17 00:00:00 2001 From: Mark Shust Date: Wed, 24 Jun 2026 14:05:03 -0400 Subject: [PATCH] fix: remove orphaned devai bootstrap shim from skeleton src MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skeleton shipped src/Prompts/{DevAiPrompt,PostCreateHook}.php — a half-built "offer to install marko/devai on create-project" bootstrap. It was never wired up: - no scripts.post-create-project-cmd entry to invoke it (never added) - no PSR-4 autoload in the shipped skeleton composer.json, so the classes weren't even loadable in a generated project - only references anywhere were the classes' own unit tests Because src/Prompts/ wasn't export-ignored, composer create-project copied it into every new project as dead code under the user's own src/ namespace. The real devai install machinery already lives in marko/devai (InstallCommand, InstallationOrchestrator, StdinPrompter); this shim duplicated the prompt logic and delegated to `marko devai:install` anyway. Removes the two src classes plus their tests, and drops the now-dead Marko\Skeleton\ src autoload-dev mapping (the directory no longer exists). Co-Authored-By: Claude Opus 4.8 (1M context) --- composer.json | 1 - packages/skeleton/src/Prompts/DevAiPrompt.php | 69 -------- .../skeleton/src/Prompts/PostCreateHook.php | 84 ---------- .../tests/Unit/Prompts/DevAiPromptTest.php | 70 -------- .../tests/Unit/Prompts/PostCreateHookTest.php | 149 ------------------ 5 files changed, 373 deletions(-) delete mode 100644 packages/skeleton/src/Prompts/DevAiPrompt.php delete mode 100644 packages/skeleton/src/Prompts/PostCreateHook.php delete mode 100644 packages/skeleton/tests/Unit/Prompts/DevAiPromptTest.php delete mode 100644 packages/skeleton/tests/Unit/Prompts/PostCreateHookTest.php diff --git a/composer.json b/composer.json index 843303b3..51a4d574 100644 --- a/composer.json +++ b/composer.json @@ -596,7 +596,6 @@ "Marko\\View\\Twig\\Tests\\": "packages/view-twig/tests/", "Marko\\Vite\\Tests\\": "packages/vite/tests/", "Marko\\Webhook\\Tests\\": "packages/webhook/tests/", - "Marko\\Skeleton\\": "packages/skeleton/src/", "Marko\\Skeleton\\Tests\\": "packages/skeleton/tests/" }, "files": [ diff --git a/packages/skeleton/src/Prompts/DevAiPrompt.php b/packages/skeleton/src/Prompts/DevAiPrompt.php deleted file mode 100644 index c1c6d99b..00000000 --- a/packages/skeleton/src/Prompts/DevAiPrompt.php +++ /dev/null @@ -1,69 +0,0 @@ -&1', escapeshellarg($projectRoot)); - passthru($cmd, $exitCode); - - return $exitCode; - } - - /** Record choice in skeleton config to avoid re-prompting */ - public function recordChoice( - string $projectRoot, - bool $accepted, - ): void - { - $configDir = $projectRoot . '/.marko'; - if (!is_dir($configDir)) { - mkdir($configDir, 0755, true); - } - $configPath = $configDir . '/skeleton.json'; - $config = is_file($configPath) ? (json_decode((string) file_get_contents($configPath), true) ?: []) : []; - $config['devaiPromptAnswered'] = true; - $config['devaiAccepted'] = $accepted; - file_put_contents($configPath, json_encode($config, JSON_PRETTY_PRINT)); - } - - public function alreadyAnswered(string $projectRoot): bool - { - $configPath = $projectRoot . '/.marko/skeleton.json'; - if (!is_file($configPath)) { - return false; - } - $config = json_decode((string) file_get_contents($configPath), true); - - return is_array($config) && ($config['devaiPromptAnswered'] ?? false) === true; - } -} diff --git a/packages/skeleton/src/Prompts/PostCreateHook.php b/packages/skeleton/src/Prompts/PostCreateHook.php deleted file mode 100644 index 8d798898..00000000 --- a/packages/skeleton/src/Prompts/PostCreateHook.php +++ /dev/null @@ -1,84 +0,0 @@ -errorStream = $errorStream ?? STDERR; - } - - /** - * Run the post-create hook for $projectRoot. - * - * @param bool $interactive whether to prompt (false = use defaults) - * @return int exit code - */ - public function run( - string $projectRoot, - bool $interactive = true, - ): int { - if ($this->prompt->alreadyAnswered($projectRoot)) { - return 0; - } - - $accept = $interactive ? $this->prompt->ask() : true; - $this->prompt->recordChoice($projectRoot, $accept); - - if (! $accept) { - echo "Skipped marko/devai install.\n"; - echo "Next step: run `composer require --dev marko/devai && marko devai:install` later if you change your mind.\n"; - - return 0; - } - - $installResult = $this->prompt->install($projectRoot); - if ($installResult !== 0) { - fwrite($this->errorStream, "Failed to install marko/devai (exit code: $installResult)\n"); - fwrite( - $this->errorStream, - "marko/devai package was added to composer; you may try `marko devai:install` manually\n" - ); - - return $installResult; - } - - $exitCode = $this->runDevaiInstall($projectRoot, $interactive); - - if ($exitCode !== 0) { - fwrite($this->errorStream, "marko devai:install failed (exit code: $exitCode)\n"); - fwrite($this->errorStream, "marko/devai is installed. You can re-run `marko devai:install` manually.\n"); - - return $exitCode; - } - - return 0; - } - - /** - * Run `marko devai:install` as a subprocess. - * Extracted for testability. - */ - protected function runDevaiInstall( - string $projectRoot, - bool $interactive, - ): int { - $cmd = $interactive - ? sprintf('cd %s && php marko devai:install 2>&1', escapeshellarg($projectRoot)) - : sprintf('cd %s && php marko devai:install --agents=claude-code 2>&1', escapeshellarg($projectRoot)); - passthru($cmd, $devaiInstallCode); - - return $devaiInstallCode; - } -} diff --git a/packages/skeleton/tests/Unit/Prompts/DevAiPromptTest.php b/packages/skeleton/tests/Unit/Prompts/DevAiPromptTest.php deleted file mode 100644 index 6ab72c0b..00000000 --- a/packages/skeleton/tests/Unit/Prompts/DevAiPromptTest.php +++ /dev/null @@ -1,70 +0,0 @@ -prompt = new DevAiPrompt(); - $this->tempRoot = sys_get_temp_dir() . '/skeleton-prompt-' . uniqid(); - mkdir($this->tempRoot, 0755, true); -}); - -afterEach(function (): void { - if (is_dir($this->tempRoot)) { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($this->tempRoot, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST, - ); - foreach ($files as $file) { - $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); - } - rmdir($this->tempRoot); - } -}); - -it('defaults to checked (install recommended)', function (): void { - $input = fopen('php://memory', 'w+'); - fwrite($input, "\n"); // empty line = default - rewind($input); - ob_start(); - $result = $this->prompt->ask($input); - ob_end_clean(); - expect($result)->toBeTrue(); -}); - -it('records the choice so later skeleton updates don\'t re-prompt', function (): void { - $this->prompt->recordChoice($this->tempRoot, true); - expect($this->prompt->alreadyAnswered($this->tempRoot))->toBeTrue(); - $config = json_decode((string) file_get_contents($this->tempRoot . '/.marko/skeleton.json'), true); - expect($config['devaiAccepted'])->toBeTrue(); -}); - -it('skips cleanly when user declines', function (): void { - $input = fopen('php://memory', 'w+'); - fwrite($input, "n\n"); - rewind($input); - ob_start(); - $result = $this->prompt->ask($input); - ob_end_clean(); - expect($result)->toBeFalse(); -}); - -it('runs composer require --dev marko/devai when user accepts', function (): void { - // The install method exists and is callable; we don't actually run composer in tests - expect(method_exists($this->prompt, 'install'))->toBeTrue(); -}); - -it('displays the devai opt-in prompt after project creation', function (): void { - $input = fopen('php://memory', 'w+'); - fwrite($input, "y\n"); - rewind($input); - - ob_start(); - $result = $this->prompt->ask($input); - $output = ob_get_clean(); - - expect($output)->toContain('marko/devai') - ->and($output)->toContain('recommended') - ->and($result)->toBeTrue(); -}); diff --git a/packages/skeleton/tests/Unit/Prompts/PostCreateHookTest.php b/packages/skeleton/tests/Unit/Prompts/PostCreateHookTest.php deleted file mode 100644 index 776772f6..00000000 --- a/packages/skeleton/tests/Unit/Prompts/PostCreateHookTest.php +++ /dev/null @@ -1,149 +0,0 @@ -tempRoot = sys_get_temp_dir() . '/skeleton-postcreate-' . uniqid(); - mkdir($this->tempRoot, 0755, true); -}); - -afterEach(function (): void { - if (is_dir($this->tempRoot)) { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($this->tempRoot, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST, - ); - foreach ($files as $file) { - $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); - } - rmdir($this->tempRoot); - } -}); - -it('runs marko devai:install after devai is added', function (): void { - $stubPrompt = new class () extends DevAiPrompt - { - public bool $installCalled = false; - - public function ask(mixed $input = null): bool - { - return true; - } - - public function install(string $projectRoot): int - { - $this->installCalled = true; - - return 0; - } - }; - - $hook = new class ($stubPrompt) extends PostCreateHook - { - protected function runDevaiInstall( - string $projectRoot, - bool $interactive, - ): int - { - return 0; - } - }; - ob_start(); - $hook->run($this->tempRoot, interactive: false); - ob_end_clean(); - - expect($stubPrompt->installCalled)->toBeTrue(); -}); - -it( - 'respects non-interactive mode by defaulting to sensible agent auto-detection and vec docs driver', - function (): void { - $stubPrompt = new class () extends DevAiPrompt - { - public ?bool $askCalled = null; - - public function ask(mixed $input = null): bool - { - $this->askCalled = true; - - return true; - } - - public function install(string $projectRoot): int - { - return 0; - } - }; - - $hook = new class ($stubPrompt) extends PostCreateHook - { - protected function runDevaiInstall( - string $projectRoot, - bool $interactive, - ): int - { - return 0; - } - }; - ob_start(); - $hook->run($this->tempRoot, interactive: false); - ob_end_clean(); - - expect($stubPrompt->askCalled)->toBeNull(); - }, -); - -it('aborts cleanly if devai:install fails without rolling back composer require', function (): void { - $stubPrompt = new class () extends DevAiPrompt - { - public function ask(mixed $input = null): bool - { - return true; - } - - public function install(string $projectRoot): int - { - return 1; - } - }; - - $errorStream = fopen('php://memory', 'w+'); - $hook = new PostCreateHook($stubPrompt, $errorStream); - ob_start(); - $code = $hook->run($this->tempRoot, interactive: false); - ob_end_clean(); - - rewind($errorStream); - $errors = stream_get_contents($errorStream); - fclose($errorStream); - - expect($code)->not->toBe(0) - ->and($errors)->toContain('Failed to install marko/devai (exit code: 1)'); -}); - -it('prints a clear next-step message if the user skipped devai', function (): void { - $stubPrompt = new class () extends DevAiPrompt - { - public function ask(mixed $input = null): bool - { - return false; - } - - public function install(string $projectRoot): int - { - throw new RuntimeException('Should not be called'); - } - }; - - $hook = new PostCreateHook($stubPrompt); - ob_start(); - $code = $hook->run($this->tempRoot, interactive: true); - $output = ob_get_clean(); - - expect($code)->toBe(0) - ->and($output)->toContain('Skipped marko/devai') - ->and($output)->toContain('marko devai:install'); -});