Skip to content

Commit dcb7ff9

Browse files
committed
Added AI commands and instructions for the repo.
1 parent 11806e9 commit dcb7ff9

File tree

4 files changed

+288
-1
lines changed

4 files changed

+288
-1
lines changed

.github/copilot-instructions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# FlightPHP/Core Project Instructions
2+
3+
## Overview
4+
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+.
5+
6+
## Project Guidelines
7+
- PHP 7.4 must be supported. PHP8 or greater also supported, but avoid PHP8+ only features.
8+
- Keep the core library dependency-free (no polyfills or interface-only repositories).
9+
- All Flight projects are meant to be kept simple and fast. Performance is a priority.
10+
- Flight is extensible and when implementing new features, consider how they can be added as plugins or extensions rather than bloating the core library.
11+
- Any new features built into the core should be well-documented and tested.
12+
- Any new features should be added with a focus on simplicity and performance, avoiding unnecessary complexity.
13+
- 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.
14+
15+
## Development & Testing
16+
- Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher)
17+
- Run test server: `composer test-server` or `composer test-server-v2`
18+
- Lint code: `composer lint` (uses phpstan/phpstan, level 6)
19+
- Beautify code: `composer beautify` (uses squizlabs/php_codesniffer, PSR1)
20+
- Check code style: `composer phpcs`
21+
- Test coverage: `composer test-coverage`
22+
23+
## Coding Standards
24+
- Follow PSR1 coding standards (enforced by PHPCS)
25+
- Use strict comparisons (`===`, `!==`)
26+
- PHPStan level 6 compliance
27+
- Focus on PHP 7.4 compatibility (avoid PHP 8+ only features)

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ coverage/
88
*.sublime*
99
clover.xml
1010
phpcs.xml
11-
.runway-config.json
11+
.runway-config.json
12+
.runway-creds.json
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
namespace app\commands;
3+
4+
use Ahc\Cli\Input\Command;
5+
6+
class AiGenerateInstructionsCommand extends Command
7+
{
8+
public function __construct()
9+
{
10+
parent::__construct('ai:generate-instructions', 'Generate project-specific AI coding instructions');
11+
}
12+
13+
public function execute()
14+
{
15+
$io = $this->app()->io();
16+
$baseDir = getcwd() . DIRECTORY_SEPARATOR;
17+
$runwayCredsFile = $baseDir . '.runway-creds.json';
18+
19+
// Check for runway creds
20+
if (!file_exists($runwayCredsFile)) {
21+
$io->error('Missing .runway-creds.json. Please run the \'ai:init\' command first.', true);
22+
return 1;
23+
}
24+
25+
$io->info('Let\'s gather some project details to generate AI coding instructions.', true);
26+
27+
// Ask questions
28+
$projectDesc = $io->prompt('Please describe what your project is for?');
29+
$database = $io->prompt('What database are you planning on using? (e.g. MySQL, SQLite, PostgreSQL, none)', 'none');
30+
$templating = $io->prompt('What HTML templating engine will you plan on using (if any)? (recommend latte)', 'latte');
31+
$security = $io->confirm('Is security an important element of this project?', 'y');
32+
$performance = $io->confirm('Is performance and speed an important part of this project?', 'y');
33+
$composerLibs = $io->prompt('What major composer libraries will you be using if you know them right now?', 'none');
34+
$envSetup = $io->prompt('How will you set up your development environment? (e.g. Docker, Vagrant, PHP dev server, other)', 'Docker');
35+
$teamSize = $io->prompt('How many developers will be working on this project?', '1');
36+
$api = $io->confirm('Will this project expose an API?', 'n');
37+
$other = $io->prompt('Any other important requirements or context? (optional)', 'no');
38+
39+
// Prepare prompt for LLM
40+
$contextFile = $baseDir . '.github/copilot-instructions.md';
41+
$context = file_exists($contextFile) ? file_get_contents($contextFile) : '';
42+
$userDetails = [
43+
'Project Description' => $projectDesc,
44+
'Database' => $database,
45+
'Templating Engine' => $templating,
46+
'Security Important' => $security ? 'yes' : 'no',
47+
'Performance Important' => $performance ? 'yes' : 'no',
48+
'Composer Libraries' => $composerLibs,
49+
'Environment Setup' => $envSetup,
50+
'Team Size' => $teamSize,
51+
'API' => $api ? 'yes' : 'no',
52+
'Other' => $other,
53+
];
54+
$detailsText = "";
55+
foreach ($userDetails as $k => $v) {
56+
$detailsText .= "$k: $v\n";
57+
}
58+
$prompt = "" .
59+
"You are an AI coding assistant. Update the following project instructions for this FlightPHP project based on the latest user answers. " .
60+
"Only output the new instructions, no extra commentary.\n" .
61+
"User answers:\n$detailsText\n" .
62+
"Current instructions:\n$context\n";
63+
64+
// Read LLM creds
65+
$creds = json_decode(file_get_contents($runwayCredsFile), true);
66+
$apiKey = $creds['api_key'] ?? '';
67+
$model = $creds['model'] ?? 'gpt-4o';
68+
$baseUrl = $creds['base_url'] ?? 'https://api.openai.com';
69+
70+
// Prepare curl call (OpenAI compatible)
71+
$headers = [
72+
'Content-Type: application/json',
73+
'Authorization: Bearer ' . $apiKey,
74+
];
75+
$data = [
76+
'model' => $model,
77+
'messages' => [
78+
['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.'],
79+
['role' => 'user', 'content' => $prompt],
80+
],
81+
'temperature' => 0.2,
82+
];
83+
$jsonData = json_encode($data);
84+
85+
// add info line that this may take a few minutes
86+
$io->info('Generating AI instructions, this may take a few minutes...', true);
87+
88+
$ch = curl_init($baseUrl . '/v1/chat/completions');
89+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
90+
curl_setopt($ch, CURLOPT_POST, true);
91+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
92+
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
93+
$result = curl_exec($ch);
94+
if (curl_errno($ch)) {
95+
$io->error('Failed to call LLM API: ' . curl_error($ch), true);
96+
return 1;
97+
}
98+
curl_close($ch);
99+
$response = json_decode($result, true);
100+
$instructions = $response['choices'][0]['message']['content'] ?? '';
101+
if (!$instructions) {
102+
$io->error('No instructions returned from LLM.', true);
103+
return 1;
104+
}
105+
106+
// Write to files
107+
$io->info('Updating .github/copilot-instructions.md, .cursor/rules/project-overview.mdc, and .windsurfrules...', true);
108+
if (!is_dir($baseDir . '.github')) {
109+
mkdir($baseDir . '.github', 0755, true);
110+
}
111+
if (!is_dir($baseDir . '.cursor/rules')) {
112+
mkdir($baseDir . '.cursor/rules', 0755, true);
113+
}
114+
file_put_contents($baseDir . '.github/copilot-instructions.md', $instructions);
115+
file_put_contents($baseDir . '.cursor/rules/project-overview.mdc', $instructions);
116+
file_put_contents($baseDir . '.windsurfrules', $instructions);
117+
$io->ok('AI instructions updated successfully.', true);
118+
return 0;
119+
}
120+
}

flight/commands/AiInitCommand.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
namespace app\commands;
3+
4+
use Ahc\Cli\Input\Option;
5+
use Ahc\Cli\Output\Writer;
6+
use Ahc\Cli\IO\Interactor;
7+
use Ahc\Cli\Input\Command;
8+
9+
class AiInitCommand extends Command
10+
{
11+
public function __construct()
12+
{
13+
parent::__construct('ai:init', 'Initialize LLM API credentials and settings');
14+
}
15+
16+
/**
17+
* Executes the function
18+
*
19+
* @return void
20+
*/
21+
public function execute()
22+
{
23+
$io = $this->app()->io();
24+
25+
$io->info('Welcome to AI Init!', true);
26+
27+
// if runway creds already exist, prompt to overwrite
28+
$baseDir = getcwd() . DIRECTORY_SEPARATOR;
29+
$runwayCredsFile = $baseDir . '.runway-creds.json';
30+
31+
// make sure the .runway-creds.json file is not already present
32+
if (file_exists($runwayCredsFile)) {
33+
$io->error('.runway-creds.json file already exists. Please remove it before running this command.', true);
34+
// prompt to overwrite
35+
$overwrite = $io->confirm('Do you want to overwrite the existing .runway-creds.json file?', 'n');
36+
if ($overwrite === false) {
37+
$io->info('Exiting without changes.', true);
38+
return 0;
39+
}
40+
}
41+
42+
// Prompt for API provider with validation
43+
do {
44+
$api = $io->prompt('Which LLM API do you want to use? (openai, grok, claude) [openai]', 'openai');
45+
$api = strtolower(trim($api));
46+
if (!in_array($api, ['openai', 'grok', 'claude'], true)) {
47+
$io->error('Invalid API provider. Please enter one of: openai, grok, claude.', true);
48+
$api = '';
49+
}
50+
} while (empty($api));
51+
52+
// Prompt for base URL with validation
53+
do {
54+
switch($api) {
55+
case 'openai':
56+
$defaultBaseUrl = 'https://api.openai.com';
57+
break;
58+
case 'grok':
59+
$defaultBaseUrl = 'https://api.x.ai';
60+
break;
61+
case 'claude':
62+
$defaultBaseUrl = 'https://api.anthropic.com';
63+
break;
64+
default:
65+
$defaultBaseUrl = '';
66+
}
67+
$baseUrl = $io->prompt('Enter the base URL for the LLM API', $defaultBaseUrl);
68+
$baseUrl = trim($baseUrl);
69+
if (empty($baseUrl) || !filter_var($baseUrl, FILTER_VALIDATE_URL)) {
70+
$io->error('Base URL cannot be empty and must be a valid URL.', true);
71+
$baseUrl = '';
72+
}
73+
} while (empty($baseUrl));
74+
75+
// Validate API key input
76+
do {
77+
$apiKey = $io->prompt('Enter your API key for ' . $api);
78+
if (empty(trim($apiKey))) {
79+
$io->error('API key cannot be empty. Please enter a valid API key.', true);
80+
}
81+
} while (empty(trim($apiKey)));
82+
83+
// Validate model input
84+
do {
85+
switch($api) {
86+
case 'openai':
87+
$defaultModel = 'gpt-4o';
88+
break;
89+
case 'grok':
90+
$defaultModel = 'grok-3-beta';
91+
break;
92+
case 'claude':
93+
$defaultModel = 'claude-3-opus';
94+
break;
95+
default:
96+
$defaultModel = '';
97+
}
98+
$model = $io->prompt('Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc)', $defaultModel);
99+
if (empty(trim($model))) {
100+
$io->error('Model name cannot be empty. Please enter a valid model name.', true);
101+
}
102+
} while (empty(trim($model)));
103+
104+
$creds = [
105+
'provider' => $api,
106+
'api_key' => $apiKey,
107+
'model' => $model,
108+
'base_url' => $baseUrl,
109+
];
110+
111+
$json = json_encode($creds, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
112+
$file = $runwayCredsFile;
113+
if (file_put_contents($file, $json) === false) {
114+
$io->error('Failed to write credentials to ' . $file, true);
115+
return 1;
116+
}
117+
$io->ok('Credentials saved to ' . $file, true);
118+
119+
// run a check to make sure that the creds file is in the .gitignore file
120+
$gitignoreFile = $baseDir . '.gitignore';
121+
if (!file_exists($gitignoreFile)) {
122+
// create the .gitignore file if it doesn't exist
123+
file_put_contents($gitignoreFile, ".runway-creds.json\n");
124+
$io->info('.gitignore file created and .runway-creds.json added to it.', true);
125+
} else {
126+
// check if the .runway-creds.json file is already in the .gitignore file
127+
$gitignoreContents = file_get_contents($gitignoreFile);
128+
if (strpos($gitignoreContents, '.runway-creds.json') === false) {
129+
// add the .runway-creds.json file to the .gitignore file
130+
file_put_contents($gitignoreFile, "\n.runway-creds.json\n", FILE_APPEND);
131+
$io->info('.runway-creds.json added to .gitignore file.', true);
132+
} else {
133+
$io->info('.runway-creds.json is already in the .gitignore file.', true);
134+
}
135+
}
136+
137+
return 0;
138+
}
139+
}

0 commit comments

Comments
 (0)