102 lines
3.3 KiB
PHP
102 lines
3.3 KiB
PHP
<?php
|
|
/**
|
|
* /api/release-check.php · Release management for multi-Opus coordination
|
|
* Detects recent commits (last 15 min) by other Claude instances to avoid:
|
|
* - Doublons
|
|
* - Régressions
|
|
* - Pertes de code
|
|
* Yacine Mahboub · WEVAL Consulting · 18avr2026
|
|
*/
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
|
|
$REPO = '/var/www/html';
|
|
$WINDOW_MIN = (int)($_GET['window'] ?? 15);
|
|
|
|
$out = [
|
|
'ok' => true,
|
|
'ts' => date('c'),
|
|
'window_min' => $WINDOW_MIN,
|
|
'recent_commits' => [],
|
|
'other_opus_commits' => [],
|
|
'playwright_latest' => [],
|
|
'conflicts' => [],
|
|
'advice' => []
|
|
];
|
|
|
|
// Recent commits (last N minutes)
|
|
$since = date('Y-m-d\TH:i:s', time() - $WINDOW_MIN * 60);
|
|
$cmd = "cd $REPO && git log --since=\"$since\" --pretty=format:'%h|%an|%at|%s' 2>&1 | head -30";
|
|
$logs = @shell_exec($cmd);
|
|
if ($logs) {
|
|
foreach (explode("\n", trim($logs)) as $line) {
|
|
if (!$line) continue;
|
|
$parts = explode('|', $line, 4);
|
|
if (count($parts) < 4) continue;
|
|
$c = [
|
|
'hash' => $parts[0],
|
|
'author' => $parts[1],
|
|
'ts' => date('c', (int)$parts[2]),
|
|
'subject' => $parts[3]
|
|
];
|
|
$out['recent_commits'][] = $c;
|
|
// Flag commits from other Opus (contains "Opus" or starts with version-like)
|
|
if (preg_match('/\b(Opus|opus|V\d+|D9\d|B\d+|autre)/', $parts[3])
|
|
&& !preg_match('/^auto-(sync|commit)/', $parts[3])) {
|
|
$out['other_opus_commits'][] = $c;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Playwright latest results
|
|
foreach (['playwright-dsh-predict-latest.json', 'playwright-latest-e2e.json'] as $f) {
|
|
$p = "$REPO/api/$f";
|
|
if (is_file($p)) {
|
|
$d = @json_decode(@file_get_contents($p), true);
|
|
if (is_array($d)) {
|
|
$pages = $d['pages'] ?? [];
|
|
$out['playwright_latest'][$f] = [
|
|
'ts' => $d['ts'] ?? '?',
|
|
'total_pages' => count($pages),
|
|
'pass' => count(array_filter($pages, fn($p)=>($p['status']??'')==='PASS')),
|
|
'fail' => count(array_filter($pages, fn($p)=>($p['status']??'')==='FAIL'))
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detect potential conflicts: files modified by multiple recent commits
|
|
$conflictCmd = "cd $REPO && git log --since=\"$since\" --name-only --pretty=format:'' 2>&1 | grep -v '^$' | sort | uniq -c | sort -rn | head -10";
|
|
$conflictOut = @shell_exec($conflictCmd);
|
|
if ($conflictOut) {
|
|
foreach (explode("\n", trim($conflictOut)) as $line) {
|
|
$line = trim($line);
|
|
if (!$line) continue;
|
|
if (preg_match('/^(\d+)\s+(.+)$/', $line, $m)) {
|
|
$count = (int)$m[1];
|
|
$file = $m[2];
|
|
if ($count >= 2) {
|
|
$out['conflicts'][] = ['file' => $file, 'modification_count' => $count];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advice
|
|
if (count($out['other_opus_commits']) > 0) {
|
|
$out['advice'][] = count($out['other_opus_commits']) . " other Opus commit(s) in last {$WINDOW_MIN}min - pull before new work";
|
|
}
|
|
if (count($out['conflicts']) > 0) {
|
|
$out['advice'][] = "Files modified by multiple commits: review to avoid doublons";
|
|
}
|
|
$pending_stubs = @glob('/var/www/html/api/wired-pending/intent-opus4-*.php');
|
|
$out['pending_stubs_count'] = count($pending_stubs ?: []);
|
|
if (count($pending_stubs ?: []) > 120) {
|
|
$out['advice'][] = "Pending stubs = " . count($pending_stubs) . " (hig) - consider approving or purging";
|
|
}
|
|
if (empty($out['advice'])) {
|
|
$out['advice'][] = "No conflicts detected - safe to commit";
|
|
}
|
|
|
|
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|