Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09f33650ec | ||
|
|
3a19c1c534 | ||
|
|
40dc04d328 |
+17
-15
@@ -11,15 +11,23 @@ class GeminiFixer
|
|||||||
|
|
||||||
public function __construct(private readonly ?string $apiKey)
|
public function __construct(private readonly ?string $apiKey)
|
||||||
{
|
{
|
||||||
if ($this->apiKey && class_exists(Gemini::class)) {
|
if ($this->apiKey) {
|
||||||
$this->client = Gemini::client($this->apiKey);
|
try {
|
||||||
|
$this->client = \Gemini::client($this->apiKey);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$this->client = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function suggestFix(Violation $violation): ?string
|
public function suggestFix(Violation $violation): ?string
|
||||||
{
|
{
|
||||||
|
if (!$this->apiKey) {
|
||||||
|
return "Hata: GEMINI_API_KEY bulunamadı.";
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->client) {
|
if (!$this->client) {
|
||||||
return null;
|
return "Hata: Gemini istemcisi başlatılamadı. Kütüphane eksik olabilir.";
|
||||||
}
|
}
|
||||||
|
|
||||||
$prompt = <<<PROMPT
|
$prompt = <<<PROMPT
|
||||||
@@ -30,23 +38,17 @@ Error: {$violation->message}
|
|||||||
Severity: {$violation->severity->value}
|
Severity: {$violation->severity->value}
|
||||||
Standard: {$violation->standard->value}
|
Standard: {$violation->standard->value}
|
||||||
|
|
||||||
Please provide ONLY the fixed version of the HTML snippet.
|
Please provide a short explanation of the fix (in Turkish) and the fixed version of the HTML snippet.
|
||||||
Do not explain, do not add markdown code blocks, just return the corrected HTML.
|
Use the following EXACT format:
|
||||||
If the error is about missing 'alt' text, add a meaningful alt text based on context.
|
EXPLANATION: [A short explanation of what you fixed and why]
|
||||||
If it's about ARIA roles, add the correct ones.
|
FIX: [Only the corrected HTML snippet]
|
||||||
PROMPT;
|
PROMPT;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = $this->client->generativeModel('gemini-2.5-flash')->generateContent($prompt);
|
$result = $this->client->generativeModel('gemini-2.5-flash')->generateContent($prompt);
|
||||||
$suggestion = trim($result->text());
|
return trim($result->text());
|
||||||
|
|
||||||
// Clean up if AI included markdown blocks anyway
|
|
||||||
$suggestion = preg_replace('/^```html\s*|\s*```$/i', '', $suggestion);
|
|
||||||
|
|
||||||
return $suggestion;
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// Log error if needed
|
return "AI Düzeltme Hatası: " . $e->getMessage();
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,9 +79,17 @@ class FixCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get AI Suggestion
|
// Get AI Suggestion
|
||||||
$suggestion = $fixer->suggestFix($violation);
|
$rawSuggestion = $fixer->suggestFix($violation);
|
||||||
if (!$suggestion) {
|
$suggestion = $rawSuggestion;
|
||||||
$io->writeln("<fg=red>Skipped (AI could not generate fix)</>");
|
if ($rawSuggestion && str_contains($rawSuggestion, 'FIX:')) {
|
||||||
|
preg_match('/FIX:(.*)/s', $rawSuggestion, $matches);
|
||||||
|
$suggestion = trim($matches[1] ?? $rawSuggestion);
|
||||||
|
// Clean up potential markdown
|
||||||
|
$suggestion = preg_replace('/^```html\s*|\s*```$/i', '', $suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$suggestion || ($suggestion === $rawSuggestion && str_contains($suggestion, 'EXPLANATION:'))) {
|
||||||
|
$io->writeln("<fg=red>Skipped (AI could not generate a clean fix)</>");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class ScanCommand extends Command
|
|||||||
$io->write(" <fg=gray>AI Fix Suggestion:</> ");
|
$io->write(" <fg=gray>AI Fix Suggestion:</> ");
|
||||||
$suggestion = $fixer->suggestFix($violation);
|
$suggestion = $fixer->suggestFix($violation);
|
||||||
if ($suggestion) {
|
if ($suggestion) {
|
||||||
|
$violation->fixSuggestion = $suggestion;
|
||||||
$io->writeln("<fg=green>" . htmlspecialchars($suggestion) . "</>");
|
$io->writeln("<fg=green>" . htmlspecialchars($suggestion) . "</>");
|
||||||
} else {
|
} else {
|
||||||
$io->writeln("<fg=red>Could not generate suggestion (check API key)</>");
|
$io->writeln("<fg=red>Could not generate suggestion (check API key)</>");
|
||||||
|
|||||||
+22
-2
@@ -4,10 +4,10 @@ namespace YakNet\AccessibilityConsole\Core;
|
|||||||
|
|
||||||
class RuleEngine
|
class RuleEngine
|
||||||
{
|
{
|
||||||
/** @var AbstractRule[] */
|
/** @var array */
|
||||||
private array $rules = [];
|
private array $rules = [];
|
||||||
|
|
||||||
public function addRule(AbstractRule $rule): void
|
public function addRule(mixed $rule): void
|
||||||
{
|
{
|
||||||
$this->rules[] = $rule;
|
$this->rules[] = $rule;
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,28 @@ class RuleEngine
|
|||||||
{
|
{
|
||||||
$allViolations = [];
|
$allViolations = [];
|
||||||
foreach ($this->rules as $rule) {
|
foreach ($this->rules as $rule) {
|
||||||
|
if (method_exists($rule, 'check')) {
|
||||||
|
// Determine if it's a document-level rule or element-level rule
|
||||||
|
$reflection = new \ReflectionMethod($rule, 'check');
|
||||||
|
$params = $reflection->getParameters();
|
||||||
|
|
||||||
|
if (isset($params[0]) && $params[0]->getType() && $params[0]->getType()->getName() === 'DOMDocument') {
|
||||||
|
// Document-level rule (Core\AbstractRule style)
|
||||||
$violations = $rule->check($doc);
|
$violations = $rule->check($doc);
|
||||||
$allViolations = array_merge($allViolations, $violations);
|
$allViolations = array_merge($allViolations, $violations);
|
||||||
|
} else {
|
||||||
|
// Element-level rule (Rules\RuleInterface style)
|
||||||
|
// We need to iterate over all elements
|
||||||
|
$xpath = new \DOMXPath($doc);
|
||||||
|
$elements = $xpath->query('//*');
|
||||||
|
foreach ($elements as $element) {
|
||||||
|
$violation = $rule->check($element);
|
||||||
|
if ($violation) {
|
||||||
|
$allViolations[] = $violation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $allViolations;
|
return $allViolations;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ class Scanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addRule(mixed $rule): void
|
||||||
|
{
|
||||||
|
$this->engine->addRule($rule);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan HTML content for accessibility violations.
|
* Scan HTML content for accessibility violations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace YakNet\AccessibilityConsole\Core;
|
namespace YakNet\AccessibilityConsole\Core;
|
||||||
|
|
||||||
readonly class Violation
|
class Violation
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $ruleId,
|
public string $ruleId,
|
||||||
|
|||||||
@@ -17,11 +17,29 @@ class HtmlDashboardReporter
|
|||||||
$rows = '';
|
$rows = '';
|
||||||
foreach ($violations as $v) {
|
foreach ($violations as $v) {
|
||||||
$severityClass = strtolower($v->severity->value);
|
$severityClass = strtolower($v->severity->value);
|
||||||
|
$aiSuggestion = $v->fixSuggestion;
|
||||||
|
$explanation = '';
|
||||||
|
$fixedCode = '';
|
||||||
|
|
||||||
|
if ($aiSuggestion && str_contains($aiSuggestion, 'FIX:')) {
|
||||||
|
preg_match('/EXPLANATION:(.*)FIX:(.*)/s', $aiSuggestion, $matches);
|
||||||
|
$explanation = trim($matches[1] ?? '');
|
||||||
|
$fixedCode = trim($matches[2] ?? '');
|
||||||
|
// Clean up potential markdown
|
||||||
|
$fixedCode = preg_replace('/^```html\s*|\s*```$/i', '', $fixedCode);
|
||||||
|
} else {
|
||||||
|
$fixedCode = $aiSuggestion;
|
||||||
|
}
|
||||||
|
|
||||||
$rows .= "
|
$rows .= "
|
||||||
<tr class='severity-{$severityClass}'>
|
<tr class='severity-{$severityClass}'>
|
||||||
<td><span class='badge'>{$v->ruleId}</span></td>
|
<td><span class='badge'>{$v->ruleId}</span></td>
|
||||||
<td>{$v->message}</td>
|
<td>{$v->message}</td>
|
||||||
<td><pre><code>" . htmlspecialchars($v->htmlSnippet) . "</code></pre></td>
|
<td><pre><code>" . htmlspecialchars($v->htmlSnippet) . "</code></pre></td>
|
||||||
|
<td>
|
||||||
|
" . ($explanation ? "<div class='ai-explanation'><strong>Öneri:</strong> {$explanation}</div>" : "") . "
|
||||||
|
" . ($fixedCode ? "<pre class='ai-fix'><code>" . htmlspecialchars($fixedCode) . "</code></pre>" : "<span class='text-muted'>Manual fix required</span>") . "
|
||||||
|
</td>
|
||||||
<td>" . ($v->location ? "{$v->location['file']}:{$v->location['line']}" : 'N/A') . "</td>
|
<td>" . ($v->location ? "{$v->location['file']}:{$v->location['line']}" : 'N/A') . "</td>
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
@@ -43,8 +61,11 @@ class HtmlDashboardReporter
|
|||||||
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
|
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
|
||||||
th { background: #f8f9fa; }
|
th { background: #f8f9fa; }
|
||||||
pre { background: #272822; color: #f8f8f2; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
pre { background: #272822; color: #f8f8f2; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
||||||
|
.ai-explanation { margin-bottom: 8px; font-size: 13px; color: #27ae60; line-height: 1.4; }
|
||||||
|
.ai-fix { border-left: 4px solid #2ecc71; background: #1e1e1e; margin-top: 5px; }
|
||||||
.badge { background: #34495e; color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; }
|
.badge { background: #34495e; color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; }
|
||||||
.severity-error { background: #fff5f5; }
|
.severity-error { background: #fff5f5; }
|
||||||
|
.text-muted { color: #95a5a6; font-size: 0.9em; font-style: italic; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -69,6 +90,7 @@ class HtmlDashboardReporter
|
|||||||
<th>Rule ID</th>
|
<th>Rule ID</th>
|
||||||
<th>Issue</th>
|
<th>Issue</th>
|
||||||
<th>Code Snippet</th>
|
<th>Code Snippet</th>
|
||||||
|
<th>AI Recommendation</th>
|
||||||
<th>Source Location</th>
|
<th>Source Location</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>A11y Report - demo/broken.html</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Inter', sans-serif; background: #f4f7f6; color: #333; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); }
|
||||||
|
h1 { color: #2c3e50; margin-top: 0; }
|
||||||
|
.summary { display: flex; gap: 20px; margin-bottom: 30px; }
|
||||||
|
.card { background: #3498db; color: white; padding: 20px; border-radius: 8px; flex: 1; text-align: center; }
|
||||||
|
.card.error { background: #e74c3c; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||||
|
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
|
||||||
|
th { background: #f8f9fa; }
|
||||||
|
pre { background: #272822; color: #f8f8f2; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
||||||
|
.ai-fix { border-left: 4px solid #2ecc71; background: #1e1e1e; }
|
||||||
|
.badge { background: #34495e; color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; }
|
||||||
|
.severity-error { background: #fff5f5; }
|
||||||
|
.text-muted { color: #95a5a6; font-size: 0.9em; font-style: italic; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Accessibility Report</h1>
|
||||||
|
<p>Target: <strong>demo/broken.html</strong> | Generated: 2026-05-13 09:46:04</p>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<div class="card error">
|
||||||
|
<h2>4</h2>
|
||||||
|
<p>Violations Found</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>WCAG 2.1</h2>
|
||||||
|
<p>Standard Level</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Rule ID</th>
|
||||||
|
<th>Issue</th>
|
||||||
|
<th>Code Snippet</th>
|
||||||
|
<th>AI Recommendation</th>
|
||||||
|
<th>Source Location</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr class='severity-error'>
|
||||||
|
<td><span class='badge'>WCAG_3_1_1_LANG</span></td>
|
||||||
|
<td>The html element must have a lang attribute.</td>
|
||||||
|
<td><pre><code><html>
|
||||||
|
<head>
|
||||||
|
<title>Broken Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to a broken page</h1>
|
||||||
|
|
||||||
|
<!-- Missing alt attribute -->
|
||||||
|
<img src="logo.png">
|
||||||
|
|
||||||
|
<!-- Empty link -->
|
||||||
|
<a href="https://google.com"></a>
|
||||||
|
|
||||||
|
<!-- Empty button -->
|
||||||
|
<button type="submit"></button>
|
||||||
|
|
||||||
|
<p>This page has multiple accessibility issues.</p>
|
||||||
|
</body>
|
||||||
|
</html></code></pre></td>
|
||||||
|
<td><pre class='ai-fix'><code>Hata: GEMINI_API_KEY bulunamadı.</code></pre></td>
|
||||||
|
<td>N/A</td>
|
||||||
|
</tr>
|
||||||
|
<tr class='severity-error'>
|
||||||
|
<td><span class='badge'>WCAG_1_1_1_ALT</span></td>
|
||||||
|
<td>Images must have an alt attribute.</td>
|
||||||
|
<td><pre><code><img src="logo.png"></code></pre></td>
|
||||||
|
<td><pre class='ai-fix'><code>Hata: GEMINI_API_KEY bulunamadı.</code></pre></td>
|
||||||
|
<td>N/A</td>
|
||||||
|
</tr>
|
||||||
|
<tr class='severity-error'>
|
||||||
|
<td><span class='badge'>WCAG_2_4_4_LINK_TEXT</span></td>
|
||||||
|
<td>Links must have discernible text.</td>
|
||||||
|
<td><pre><code><a href="https://google.com"></a></code></pre></td>
|
||||||
|
<td><pre class='ai-fix'><code>Hata: GEMINI_API_KEY bulunamadı.</code></pre></td>
|
||||||
|
<td>N/A</td>
|
||||||
|
</tr>
|
||||||
|
<tr class='severity-error'>
|
||||||
|
<td><span class='badge'>WCAG_4_1_2_BUTTON</span></td>
|
||||||
|
<td>Buttons must have discernible text.</td>
|
||||||
|
<td><pre><code><button type="submit"></button></code></pre></td>
|
||||||
|
<td><pre class='ai-fix'><code>Hata: GEMINI_API_KEY bulunamadı.</code></pre></td>
|
||||||
|
<td>N/A</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user