-
Notifications
You must be signed in to change notification settings - Fork 418
Added AI commands and instructions for the repo. #652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # FlightPHP/Core Project Instructions | ||
|
|
||
| ## Overview | ||
| This is the main FlightPHP core library for building fast, simple, and extensible PHP web applications. It is dependency-free for core usage and supports PHP 7.4+. | ||
|
|
||
| ## Project Guidelines | ||
| - PHP 7.4 must be supported. PHP 8 or greater also supported, but avoid PHP 8+ only features. | ||
| - Keep the core library dependency-free (no polyfills or interface-only repositories). | ||
| - All Flight projects are meant to be kept simple and fast. Performance is a priority. | ||
| - Flight is extensible and when implementing new features, consider how they can be added as plugins or extensions rather than bloating the core library. | ||
| - Any new features built into the core should be well-documented and tested. | ||
| - Any new features should be added with a focus on simplicity and performance, avoiding unnecessary complexity. | ||
| - This is not a Laravel, Yii, Code Igniter or Symfony clone. It is a simple, fast, and extensible framework that allows you to build applications quickly without the overhead of large frameworks. | ||
|
|
||
| ## Development & Testing | ||
| - Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher) | ||
| - Run test server: `composer test-server` or `composer test-server-v2` | ||
| - Lint code: `composer lint` (uses phpstan/phpstan, level 6) | ||
| - Beautify code: `composer beautify` (uses squizlabs/php_codesniffer, PSR1) | ||
| - Check code style: `composer phpcs` | ||
| - Test coverage: `composer test-coverage` | ||
|
|
||
| ## Coding Standards | ||
| - Follow PSR1 coding standards (enforced by PHPCS) | ||
| - Use strict comparisons (`===`, `!==`) | ||
| - PHPStan level 6 compliance | ||
| - Focus on PHP 7.4 compatibility (avoid PHP 8+ only features) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,4 +8,5 @@ coverage/ | |
| *.sublime* | ||
| clover.xml | ||
| phpcs.xml | ||
| .runway-config.json | ||
| .runway-config.json | ||
| .runway-creds.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace flight\commands; | ||
|
|
||
| use Ahc\Cli\Input\Command; | ||
|
|
||
| /** | ||
| * @property-read ?string $credsFile | ||
| * @property-read ?string $baseDir | ||
| */ | ||
| class AiGenerateInstructionsCommand extends Command | ||
| { | ||
| /** | ||
| * Constructor for the AiGenerateInstructionsCommand class. | ||
| * | ||
| * Initializes a new instance of the command. | ||
| */ | ||
| public function __construct() | ||
| { | ||
| parent::__construct('ai:generate-instructions', 'Generate project-specific AI coding instructions'); | ||
| $this->option('--creds-file', 'Path to .runway-creds.json file', null, ''); | ||
| $this->option('--base-dir', 'Project base directory (for testing or custom use)', null, ''); | ||
| } | ||
|
|
||
| /** | ||
| * Executes the command logic for generating AI instructions. | ||
| * | ||
| * This method is called to perform the main functionality of the | ||
| * AiGenerateInstructionsCommand. It should contain the steps required | ||
| * to generate and output instructions using AI, based on the command's | ||
| * configuration and input. | ||
| * | ||
| * @return int | ||
| */ | ||
| public function execute() | ||
| { | ||
| $io = $this->app()->io(); | ||
| $baseDir = $this->baseDir ? rtrim($this->baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : getcwd() . DIRECTORY_SEPARATOR; | ||
| $runwayCredsFile = $this->credsFile ?: $baseDir . '.runway-creds.json'; | ||
|
|
||
| // Check for runway creds | ||
| if (!file_exists($runwayCredsFile)) { | ||
| $io->error('Missing .runway-creds.json. Please run the \'ai:init\' command first.', true); | ||
| return 1; | ||
| } | ||
|
|
||
| $io->info('Let\'s gather some project details to generate AI coding instructions.', true); | ||
|
|
||
| // Ask questions | ||
| $projectDesc = $io->prompt('Please describe what your project is for?'); | ||
| $database = $io->prompt('What database are you planning on using? (e.g. MySQL, SQLite, PostgreSQL, none)', 'none'); | ||
| $templating = $io->prompt('What HTML templating engine will you plan on using (if any)? (recommend latte)', 'latte'); | ||
| $security = $io->confirm('Is security an important element of this project?', 'y'); | ||
| $performance = $io->confirm('Is performance and speed an important part of this project?', 'y'); | ||
| $composerLibs = $io->prompt('What major composer libraries will you be using if you know them right now?', 'none'); | ||
| $envSetup = $io->prompt('How will you set up your development environment? (e.g. Docker, Vagrant, PHP dev server, other)', 'Docker'); | ||
| $teamSize = $io->prompt('How many developers will be working on this project?', '1'); | ||
| $api = $io->confirm('Will this project expose an API?', 'n'); | ||
| $other = $io->prompt('Any other important requirements or context? (optional)', 'no'); | ||
|
|
||
| // Prepare prompt for LLM | ||
| $contextFile = $baseDir . '.github/copilot-instructions.md'; | ||
| $context = file_exists($contextFile) ? file_get_contents($contextFile) : ''; | ||
| $userDetails = [ | ||
| 'Project Description' => $projectDesc, | ||
| 'Database' => $database, | ||
| 'Templating Engine' => $templating, | ||
| 'Security Important' => $security ? 'yes' : 'no', | ||
| 'Performance Important' => $performance ? 'yes' : 'no', | ||
| 'Composer Libraries' => $composerLibs, | ||
| 'Environment Setup' => $envSetup, | ||
| 'Team Size' => $teamSize, | ||
| 'API' => $api ? 'yes' : 'no', | ||
| 'Other' => $other, | ||
| ]; | ||
| $detailsText = ""; | ||
| foreach ($userDetails as $k => $v) { | ||
| $detailsText .= "$k: $v\n"; | ||
| } | ||
| $prompt = <<<EOT | ||
| You are an AI coding assistant. Update the following project instructions for this FlightPHP project based on the latest user answers. Only output the new instructions, no extra commentary. | ||
| User answers: | ||
| $detailsText | ||
| Current instructions: | ||
| $context | ||
| EOT; | ||
|
|
||
| // Read LLM creds | ||
| $creds = json_decode(file_get_contents($runwayCredsFile), true); | ||
| $apiKey = $creds['api_key'] ?? ''; | ||
| $model = $creds['model'] ?? 'gpt-4o'; | ||
| $baseUrl = $creds['base_url'] ?? 'https://api.openai.com'; | ||
|
|
||
| // Prepare curl call (OpenAI compatible) | ||
| $headers = [ | ||
| 'Content-Type: application/json', | ||
| 'Authorization: Bearer ' . $apiKey, | ||
| ]; | ||
| $data = [ | ||
| 'model' => $model, | ||
| 'messages' => [ | ||
| ['role' => 'system', 'content' => 'You are a helpful AI coding assistant focused on the Flight Framework for PHP. You are up to date with all your knowledge from https://docs.flightphp.com. As an expert into the programming language PHP, you are top notch at architecting out proper instructions for FlightPHP projects.'], | ||
| ['role' => 'user', 'content' => $prompt], | ||
| ], | ||
| 'temperature' => 0.2, | ||
| ]; | ||
| $jsonData = json_encode($data); | ||
|
|
||
| // add info line that this may take a few minutes | ||
| $io->info('Generating AI instructions, this may take a few minutes...', true); | ||
|
|
||
| $result = $this->callLlmApi($baseUrl, $headers, $jsonData, $io); | ||
| if ($result === false) { | ||
| return 1; | ||
| } | ||
| $response = json_decode($result, true); | ||
| $instructions = $response['choices'][0]['message']['content'] ?? ''; | ||
| if (!$instructions) { | ||
| $io->error('No instructions returned from LLM.', true); | ||
| return 1; | ||
| } | ||
|
|
||
| // Write to files | ||
| $io->info('Updating .github/copilot-instructions.md, .cursor/rules/project-overview.mdc, and .windsurfrules...', true); | ||
| if (!is_dir($baseDir . '.github')) { | ||
| mkdir($baseDir . '.github', 0755, true); | ||
| } | ||
| if (!is_dir($baseDir . '.cursor/rules')) { | ||
| mkdir($baseDir . '.cursor/rules', 0755, true); | ||
| } | ||
| file_put_contents($baseDir . '.github/copilot-instructions.md', $instructions); | ||
| file_put_contents($baseDir . '.cursor/rules/project-overview.mdc', $instructions); | ||
| file_put_contents($baseDir . '.windsurfrules', $instructions); | ||
| $io->ok('AI instructions updated successfully.', true); | ||
| return 0; | ||
| } | ||
|
|
||
| /** | ||
| * Make the LLM API call using curl | ||
| * | ||
| * @param string $baseUrl | ||
| * @param array<int,string> $headers | ||
| * @param string $jsonData | ||
| * @param object $io | ||
| * | ||
| * @return string|false | ||
| * | ||
| * @codeCoverageIgnore | ||
| */ | ||
| protected function callLlmApi($baseUrl, $headers, $jsonData, $io) | ||
| { | ||
| $ch = curl_init($baseUrl . '/v1/chat/completions'); | ||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
| curl_setopt($ch, CURLOPT_POST, true); | ||
| curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | ||
| curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); | ||
| $result = curl_exec($ch); | ||
| if (curl_errno($ch)) { | ||
| $io->error('Failed to call LLM API: ' . curl_error($ch), true); | ||
| curl_close($ch); | ||
| return false; | ||
| } | ||
| curl_close($ch); | ||
| return $result; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace flight\commands; | ||
|
|
||
| use Ahc\Cli\Input\Command; | ||
|
|
||
| /** | ||
| * @property-read ?string $gitignoreFile | ||
| * @property-read ?string $credsFile | ||
| */ | ||
| class AiInitCommand extends Command | ||
| { | ||
| /** | ||
| * Constructor for the AiInitCommand class. | ||
| * | ||
| * Initializes the command instance and sets up any required dependencies. | ||
| */ | ||
| public function __construct() | ||
| { | ||
| parent::__construct('ai:init', 'Initialize LLM API credentials and settings'); | ||
| $this | ||
| ->option('--gitignore-file', 'Path to .gitignore file', null, '') | ||
| ->option('--creds-file', 'Path to .runway-creds.json file', null, ''); | ||
| } | ||
|
|
||
| /** | ||
| * Executes the function | ||
| * | ||
| * @return int | ||
| */ | ||
| public function execute() | ||
| { | ||
| $io = $this->app()->io(); | ||
|
|
||
| $io->info('Welcome to AI Init!', true); | ||
|
|
||
| $baseDir = getcwd() . DIRECTORY_SEPARATOR; | ||
| $runwayCredsFile = $this->credsFile ?: $baseDir . '.runway-creds.json'; | ||
| $gitignoreFile = $this->gitignoreFile ?: $baseDir . '.gitignore'; | ||
|
|
||
| // make sure the .runway-creds.json file is not already present | ||
| if (file_exists($runwayCredsFile)) { | ||
| $io->error('.runway-creds.json file already exists. Please remove it before running this command.', true); | ||
| // prompt to overwrite | ||
| $overwrite = $io->confirm('Do you want to overwrite the existing .runway-creds.json file?', 'n'); | ||
| if ($overwrite === false) { | ||
| $io->info('Exiting without changes.', true); | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| // Prompt for API provider with validation | ||
| $allowedApis = [ | ||
| '1' => 'openai', | ||
| '2' => 'grok', | ||
| '3' => 'claude' | ||
| ]; | ||
| $apiChoice = strtolower(trim($io->choice('Which LLM API do you want to use?', $allowedApis, '1'))); | ||
| $api = $allowedApis[$apiChoice] ?? 'openai'; | ||
|
|
||
| // Prompt for base URL with validation | ||
| switch ($api) { | ||
| case 'openai': | ||
| $defaultBaseUrl = 'https://api.openai.com'; | ||
| break; | ||
| case 'grok': | ||
| $defaultBaseUrl = 'https://api.x.ai'; | ||
| break; | ||
| case 'claude': | ||
| $defaultBaseUrl = 'https://api.anthropic.com'; | ||
| break; | ||
| } | ||
| $baseUrl = trim($io->prompt('Enter the base URL for the LLM API', $defaultBaseUrl)); | ||
| if (empty($baseUrl) || !filter_var($baseUrl, FILTER_VALIDATE_URL)) { | ||
| $io->error('Base URL cannot be empty and must be a valid URL.', true); | ||
| return 1; | ||
| } | ||
|
|
||
| // Validate API key input | ||
| $apiKey = trim($io->prompt('Enter your API key for ' . $api)); | ||
| if (empty($apiKey)) { | ||
| $io->error('API key cannot be empty. Please enter a valid API key.', true); | ||
| return 1; | ||
| } | ||
|
|
||
| // Validate model input | ||
| switch ($api) { | ||
| case 'openai': | ||
| $defaultModel = 'gpt-4o'; | ||
| break; | ||
| case 'grok': | ||
| $defaultModel = 'grok-3-beta'; | ||
| break; | ||
| case 'claude': | ||
| $defaultModel = 'claude-3-opus'; | ||
| break; | ||
| } | ||
| $model = trim($io->prompt('Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc)', $defaultModel)); | ||
|
|
||
| $creds = [ | ||
| 'provider' => $api, | ||
| 'api_key' => $apiKey, | ||
| 'model' => $model, | ||
| 'base_url' => $baseUrl, | ||
| ]; | ||
|
|
||
| $json = json_encode($creds, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); | ||
| $file = $runwayCredsFile; | ||
| file_put_contents($file, $json); | ||
|
|
||
| // change permissions to 600 | ||
| chmod($file, 0600); | ||
|
|
||
| $io->ok('Credentials saved to ' . $file, true); | ||
|
|
||
| // run a check to make sure that the creds file is in the .gitignore file | ||
| // use $gitignoreFile instead of hardcoded path | ||
| if (!file_exists($gitignoreFile)) { | ||
| // create the .gitignore file if it doesn't exist | ||
| file_put_contents($gitignoreFile, basename($runwayCredsFile) . "\n"); | ||
| $io->info(basename($gitignoreFile) . ' file created and ' . basename($runwayCredsFile) . ' added to it.', true); | ||
| } else { | ||
| // check if the creds file is already in the .gitignore file | ||
| $gitignoreContents = file_get_contents($gitignoreFile); | ||
| if (strpos($gitignoreContents, basename($runwayCredsFile)) === false) { | ||
| // add the creds file to the .gitignore file | ||
| file_put_contents($gitignoreFile, "\n" . basename($runwayCredsFile) . "\n", FILE_APPEND); | ||
| $io->info(basename($runwayCredsFile) . ' added to ' . basename($gitignoreFile) . ' file.', true); | ||
| } else { | ||
| $io->info(basename($runwayCredsFile) . ' is already in the ' . basename($gitignoreFile) . ' file.', true); | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.