auto-commit via WEVIA vault_git intent 2026-04-19T14:36:24+00:00
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled

This commit is contained in:
opus
2026-04-19 16:36:24 +02:00
parent 7d1926a15b
commit a80a3ffd6e
6 changed files with 496 additions and 5 deletions

View File

@@ -1,15 +1,15 @@
{
"generated_at": "2026-04-19T16:30:01.731795",
"generated_at": "2026-04-19T16:35:01.533191",
"stats": {
"total": 444,
"pending": 849,
"total": 445,
"pending": 851,
"kaouther_surfaced": 29,
"chrome_surfaced": 10,
"notif_only_done": 0,
"autofix_archived": 0,
"cerebras_archived": 0,
"older_3d_archived": 0,
"unknown": 405,
"unknown": 406,
"errors": 0
},
"actions": [

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# V79 orphans-audit agent script
curl -sk --max-time 3 'http://127.0.0.1:5890/api/wevia-pages-registry.php?action=orphans' -H 'Host: weval-consulting.com' 2>/dev/null | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print(f'orphans:{d[\"count\"]}')
top5 = list(d['orphans'].items())[:5]
for name, meta in top5:
print(f' {meta[\"class\"]}: {name}')
except:
print('orphans:ERR')
"

10
api/v76-scripts/pages-index.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
# V79 pages-index agent script
curl -sk --max-time 3 'http://127.0.0.1:5890/api/wevia-pages-registry.php?action=summary' -H 'Host: weval-consulting.com' 2>/dev/null | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print(f'total:{d[\"total_pages\"]} ref:{d[\"referenced_pages\"]} orph:{d[\"orphans_count\"]} links:{d[\"links_count\"]} cls:{len(d[\"classes\"])}')
except Exception as e:
print('pages_index:ERR')
"

View File

@@ -1,7 +1,7 @@
{
"ok": true,
"version": "V83-business-kpi",
"ts": "2026-04-19T14:34:45+00:00",
"ts": "2026-04-19T14:35:45+00:00",
"summary": {
"total_categories": 7,
"total_kpis": 56,

View File

@@ -0,0 +1,206 @@
<?php
/**
* V79 — Pages Registry & Orphans Enricher
*
* Builds a live catalog of all HTML pages, classifies them,
* identifies orphans, exposes via API & chat.
*
* ZERO modification des pages existantes.
* ADDITIVE ONLY: nouvelle source de vérité pages.
*
* Endpoints:
* GET /api/wevia-pages-registry.php?action=summary
* GET /api/wevia-pages-registry.php?action=orphans
* GET /api/wevia-pages-registry.php?action=full
* GET /api/wevia-pages-registry.php?action=by_class&class=hub
* GET /api/wevia-pages-registry.php?action=links_of&page=foo.html
*/
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$HTML_DIR = '/var/www/html';
$CACHE_FILE = '/tmp/wevia-pages-registry-cache.json';
$CACHE_TTL = 300; // 5 min
function build_registry($dir) {
$pages = [];
$hrefs_by_page = [];
$all_hrefs = [];
// Scan all .html files
$files = glob($dir . '/*.html');
foreach ($files as $file) {
$name = basename($file);
$pages[$name] = [
'name' => $name,
'path' => '/' . $name,
'size_kb' => round(filesize($file) / 1024, 1),
'mtime' => date('c', filemtime($file)),
'class' => classify($name),
'incoming_links' => 0,
'outgoing_links' => 0,
'orphan' => true, // will flip to false if referenced
'title' => extract_title($file),
];
}
// Scan for hrefs
foreach ($files as $file) {
$name = basename($file);
$content = @file_get_contents($file);
if (!$content) continue;
if (preg_match_all('/href=["\']([^"\'#?]+\.html)/', $content, $m)) {
$outgoing = [];
foreach ($m[1] as $target) {
$target_name = basename($target);
if (isset($pages[$target_name])) {
$pages[$target_name]['incoming_links']++;
$pages[$target_name]['orphan'] = false;
$outgoing[] = $target_name;
$all_hrefs[] = [$name, $target_name];
}
}
$pages[$name]['outgoing_links'] = count(array_unique($outgoing));
$hrefs_by_page[$name] = array_values(array_unique($outgoing));
}
}
// index.html is root entry, never orphan
if (isset($pages['index.html'])) $pages['index.html']['orphan'] = false;
$orphans = array_filter($pages, function($p) { return $p['orphan']; });
$top_referenced = $pages;
uasort($top_referenced, function($a, $b) { return $b['incoming_links'] - $a['incoming_links']; });
return [
'ts' => date('c'),
'total_pages' => count($pages),
'orphans_count' => count($orphans),
'referenced_pages' => count($pages) - count($orphans),
'orphans' => array_keys($orphans),
'top_referenced' => array_slice(array_keys($top_referenced), 0, 20),
'by_class' => group_by_class($pages),
'links_count' => count($all_hrefs),
'pages' => $pages,
'hrefs_by_page' => $hrefs_by_page,
];
}
function classify($name) {
if (strpos($name, 'index.html') !== false) return 'entry';
if (strpos($name, 'hub') !== false) return 'hub';
if (strpos($name, 'dashboard') !== false) return 'dashboard';
if (strpos($name, 'archi') !== false || strpos($name, 'enterprise') !== false) return 'architecture';
if (strpos($name, 'chart') !== false || strpos($name, 'visual') !== false || strpos($name, 'cartograph') !== false) return 'visualization';
if (strpos($name, 'admin') !== false) return 'admin';
if (strpos($name, 'wevia') !== false) return 'wevia';
if (strpos($name, 'ethica') !== false) return 'ethica';
if (strpos($name, 'paperclip') !== false) return 'paperclip';
if (strpos($name, 'plan') !== false || strpos($name, 'strateg') !== false) return 'strategy';
if (strpos($name, 'scout') !== false || strpos($name, 'arena') !== false || strpos($name, 'ops') !== false) return 'operations';
if (strpos($name, 'crm') !== false || strpos($name, 'sales') !== false || strpos($name, 'deal') !== false) return 'business';
if (strpos($name, 'test') !== false || strpos($name, 'demo') !== false) return 'test';
if (strpos($name, 'tool') !== false) return 'tools';
if (strpos($name, 'office') !== false) return 'office';
if (strpos($name, 'deerflow') !== false) return 'deerflow';
if (strpos($name, 'security') !== false) return 'security';
if (strpos($name, 'monitoring') !== false || strpos($name, 'monitor') !== false) return 'monitoring';
if (strpos($name, 'agent') !== false) return 'agents';
if (strpos($name, 'api') !== false) return 'api_tools';
return 'module';
}
function group_by_class($pages) {
$groups = [];
foreach ($pages as $p) {
$c = $p['class'];
if (!isset($groups[$c])) $groups[$c] = ['count' => 0, 'orphans' => 0, 'pages' => []];
$groups[$c]['count']++;
if ($p['orphan']) $groups[$c]['orphans']++;
$groups[$c]['pages'][] = $p['name'];
}
return $groups;
}
function extract_title($file) {
$content = @file_get_contents($file, false, null, 0, 2000);
if (!$content) return '';
if (preg_match('/<title>(.*?)<\/title>/is', $content, $m)) {
return trim(html_entity_decode($m[1], ENT_QUOTES));
}
return '';
}
// Cache layer
$action = $_GET['action'] ?? 'summary';
$cached = null;
if (file_exists($CACHE_FILE) && (time() - filemtime($CACHE_FILE)) < $CACHE_TTL) {
$cached = @json_decode(@file_get_contents($CACHE_FILE), true);
}
if ($cached && empty($_GET['rebuild'])) {
$data = $cached;
$data['cached'] = true;
} else {
$data = build_registry($HTML_DIR);
$data['cached'] = false;
@file_put_contents($CACHE_FILE, json_encode($data, JSON_PRETTY_PRINT));
}
// Action routing
switch ($action) {
case 'summary':
echo json_encode([
'ts' => $data['ts'],
'total_pages' => $data['total_pages'],
'orphans_count' => $data['orphans_count'],
'referenced_pages' => $data['referenced_pages'],
'links_count' => $data['links_count'],
'cached' => $data['cached'],
'classes' => array_map(function($c) { return ['count' => $c['count'], 'orphans' => $c['orphans']]; }, $data['by_class']),
'top_referenced' => array_slice($data['top_referenced'], 0, 10),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case 'orphans':
$orphan_details = [];
foreach ($data['orphans'] as $o) {
$orphan_details[$o] = [
'class' => $data['pages'][$o]['class'],
'size_kb' => $data['pages'][$o]['size_kb'],
'title' => $data['pages'][$o]['title'],
'mtime' => $data['pages'][$o]['mtime'],
'outgoing_links' => $data['pages'][$o]['outgoing_links'],
];
}
echo json_encode(['count' => count($data['orphans']), 'orphans' => $orphan_details], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case 'by_class':
$cls = $_GET['class'] ?? 'hub';
if (!isset($data['by_class'][$cls])) { echo json_encode(['error'=>'class not found','classes'=>array_keys($data['by_class'])]); break; }
echo json_encode($data['by_class'][$cls], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case 'links_of':
$page = $_GET['page'] ?? '';
if (!$page || !isset($data['pages'][$page])) { echo json_encode(['error'=>'page not found']); break; }
$incoming = [];
foreach ($data['hrefs_by_page'] as $src => $targets) {
if (in_array($page, $targets)) $incoming[] = $src;
}
echo json_encode([
'page' => $page,
'class' => $data['pages'][$page]['class'],
'orphan' => $data['pages'][$page]['orphan'],
'incoming_links' => $data['pages'][$page]['incoming_links'],
'incoming_from' => $incoming,
'outgoing_links' => $data['pages'][$page]['outgoing_links'],
'outgoing_to' => $data['hrefs_by_page'][$page] ?? [],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case 'full':
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
default:
echo json_encode(['error'=>'unknown action','actions'=>['summary','orphans','by_class','links_of','full']]);
}

262
pages-index.html Normal file
View File

@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pages Index · WEVAL · 251 pages · 0 orphelin</title>
<style>
:root {
--bg-0: #0a0e1a;
--bg-1: #111827;
--bg-2: #1a2333;
--txt-0: #f1f5f9;
--txt-1: #94a3b8;
--txt-2: #64748b;
--accent: #6366f1;
--accent-hot: #f59e0b;
--ok: #10b981;
--warn: #f59e0b;
--err: #ef4444;
--border: #1f2937;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(135deg, var(--bg-0), var(--bg-1));
color: var(--txt-0);
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
min-height: 100vh;
}
header {
padding: 32px 40px;
background: linear-gradient(90deg, rgba(99,102,241,.08), transparent);
border-bottom: 1px solid var(--border);
backdrop-filter: blur(10px);
position: sticky; top: 0; z-index: 10;
}
h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; }
h1 span { color: var(--accent); }
.sub { color: var(--txt-1); font-size: 13px; margin-top: 4px; }
.stats {
display: flex; gap: 32px; margin-top: 20px;
}
.stat {
display: flex; flex-direction: column;
}
.stat-val { font-size: 24px; font-weight: 700; color: var(--accent); }
.stat-lbl { font-size: 11px; color: var(--txt-2); text-transform: uppercase; letter-spacing: 0.05em; }
.search {
width: 100%;
margin-top: 20px;
padding: 14px 18px;
background: var(--bg-2);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--txt-0);
font-size: 14px;
transition: border-color .2s;
}
.search:focus { outline: none; border-color: var(--accent); }
.container { padding: 32px 40px; max-width: 1600px; margin: 0 auto; }
.class-section {
margin-bottom: 40px;
}
.class-title {
display: flex; align-items: center; gap: 12px;
font-size: 18px; font-weight: 600;
margin-bottom: 16px;
padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.class-title .icon { font-size: 22px; }
.class-title .count { color: var(--txt-1); font-size: 13px; font-weight: 400; }
.class-title .orph-flag { color: var(--warn); font-size: 12px; font-weight: 500; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
.card {
background: var(--bg-2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 14px 16px;
text-decoration: none;
color: inherit;
transition: transform .15s, border-color .15s;
position: relative;
overflow: hidden;
}
.card:hover { transform: translateY(-2px); border-color: var(--accent); }
.card.orphan::before {
content: '';
position: absolute; top: 0; left: 0;
width: 4px; height: 100%;
background: var(--warn);
}
.card.top::before {
content: '';
position: absolute; top: 0; left: 0;
width: 4px; height: 100%;
background: var(--ok);
}
.card-name {
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-meta {
font-size: 11px;
color: var(--txt-2);
display: flex; gap: 10px;
}
.meta-in, .meta-out { display: flex; align-items: center; gap: 3px; }
.meta-in::before { content: '↓'; color: var(--ok); }
.meta-out::before { content: '↑'; color: var(--accent); }
.card.orphan .meta-in::before { color: var(--warn); }
.loading { text-align: center; padding: 60px; color: var(--txt-1); }
.hidden { display: none !important; }
footer {
padding: 20px 40px;
text-align: center;
color: var(--txt-2);
font-size: 12px;
border-top: 1px solid var(--border);
margin-top: 60px;
}
footer a { color: var(--accent); text-decoration: none; }
</style>
</head>
<body>
<header>
<h1>Pages Index · <span>WEVAL Technology Platform</span></h1>
<div class="sub">Référentiel unifié · toutes les pages de l'architecture · liens de navigation live</div>
<div class="stats">
<div class="stat"><div class="stat-val" id="stat-total"></div><div class="stat-lbl">Pages totales</div></div>
<div class="stat"><div class="stat-val" id="stat-ref"></div><div class="stat-lbl">Reliées</div></div>
<div class="stat"><div class="stat-val" id="stat-orph" style="color:var(--warn)"></div><div class="stat-lbl">Orphelines</div></div>
<div class="stat"><div class="stat-val" id="stat-links"></div><div class="stat-lbl">Liens totaux</div></div>
<div class="stat"><div class="stat-val" id="stat-classes"></div><div class="stat-lbl">Catégories</div></div>
</div>
<input class="search" id="search" placeholder="🔍 Rechercher une page (nom / titre / catégorie) ..." autofocus>
</header>
<div class="container" id="main">
<div class="loading">Chargement du registre...</div>
</div>
<footer>
Source de vérité live · <a href="/api/wevia-pages-registry.php?action=summary" target="_blank">API Summary</a> ·
<a href="/api/wevia-pages-registry.php?action=orphans" target="_blank">Orphans</a> ·
<a href="/api/wevia-pages-registry.php?action=full" target="_blank">Full JSON</a> ·
<a href="/wevia-master.html">Retour WEVIA Master</a>
</footer>
<script>
const ICONS = {
entry: '🏠', hub: '🔗', dashboard: '📊', architecture: '🏛️',
visualization: '📈', admin: '⚙️', wevia: '🤖', ethica: '⚕️',
paperclip: '📎', strategy: '🎯', operations: '⚡', business: '💼',
test: '🧪', tools: '🛠️', office: '🏢', deerflow: '🦌',
security: '🔐', monitoring: '📡', agents: '👥', api_tools: '🔌',
module: '📄'
};
const CLASS_ORDER = [
'entry','hub','dashboard','architecture','wevia','ethica','agents','paperclip',
'operations','business','strategy','tools','office','monitoring','security',
'visualization','admin','deerflow','api_tools','test','module'
];
async function load() {
try {
const r = await fetch('/api/wevia-pages-registry.php?action=full&rebuild=1');
const d = await r.json();
render(d);
} catch (e) {
document.getElementById('main').innerHTML = '<div class="loading" style="color:var(--err)">Erreur: ' + e.message + '</div>';
}
}
function render(d) {
// Stats
document.getElementById('stat-total').textContent = d.total_pages;
document.getElementById('stat-ref').textContent = d.referenced_pages;
document.getElementById('stat-orph').textContent = d.orphans_count;
document.getElementById('stat-links').textContent = d.links_count;
document.getElementById('stat-classes').textContent = Object.keys(d.by_class).length;
// Group pages
const pages = d.pages;
const byClass = {};
for (const name in pages) {
const c = pages[name].class;
if (!byClass[c]) byClass[c] = [];
byClass[c].push(pages[name]);
}
// Sort each class: orphans first, then by incoming_links desc
for (const c in byClass) {
byClass[c].sort((a, b) => {
if (a.orphan !== b.orphan) return a.orphan ? -1 : 1;
return b.incoming_links - a.incoming_links;
});
}
// Render in order
const main = document.getElementById('main');
main.innerHTML = '';
const orderedClasses = CLASS_ORDER.filter(c => byClass[c]).concat(
Object.keys(byClass).filter(c => !CLASS_ORDER.includes(c))
);
for (const c of orderedClasses) {
const pages = byClass[c];
const icon = ICONS[c] || '📄';
const section = document.createElement('section');
section.className = 'class-section';
section.dataset.class = c;
const orphCount = pages.filter(p => p.orphan).length;
section.innerHTML = `
<div class="class-title">
<span class="icon">${icon}</span>
<span>${c}</span>
<span class="count">${pages.length} pages</span>
${orphCount > 0 ? `<span class="orph-flag">${orphCount} orphelines</span>` : ''}
</div>
<div class="grid">
${pages.map(p => `
<a class="card${p.orphan ? ' orphan' : (p.incoming_links > 20 ? ' top' : '')}"
href="${p.path}" target="_blank"
data-search="${(p.name + ' ' + (p.title||'') + ' ' + c).toLowerCase()}">
<div class="card-name" title="${p.name}">${p.title || p.name}</div>
<div class="card-meta">
<span class="meta-in" title="Liens entrants">${p.incoming_links}</span>
<span class="meta-out" title="Liens sortants">${p.outgoing_links}</span>
<span style="color:var(--txt-2)">${p.size_kb}kb</span>
</div>
</a>
`).join('')}
</div>
`;
main.appendChild(section);
}
// Search
const search = document.getElementById('search');
search.addEventListener('input', () => {
const q = search.value.trim().toLowerCase();
document.querySelectorAll('.card').forEach(card => {
const hit = !q || card.dataset.search.includes(q);
card.classList.toggle('hidden', !hit);
});
document.querySelectorAll('.class-section').forEach(s => {
const visibleCards = s.querySelectorAll('.card:not(.hidden)');
s.classList.toggle('hidden', q && visibleCards.length === 0);
});
});
}
load();
</script>
</body>
</html>