Agent IA planificateur EVCS en MODE OMBRE (Advisor Shadow V1)

- Workflow n8n tdfInZns7cqo8fU2 actif (15 min): snapshot HA compact -> prompt FR
  -> LLM local Spark (modèle dynamique via /v1/models) avec fallback OpenRouter
  claude-haiku-4.5 (credential chiffré, clé hors git) -> validation stricte
  (enums, bornes, rejet total si invalide) -> helpers advisor_* + divergence
  + compteurs accord/divergence + logbook.
- GARANTIE OMBRE structurelle: écritures limitées à advisor_*/logbook,
  vérifiée par tests/advisor/run.mjs (16 tests, bloquants au push).
- Kill-switch input_boolean.evcs_advisor_enabled.
- Extension R2 multi-workflows (lib-node-map WORKFLOWS[], build/extract/push
  par fichier, n8n-create.mjs, gen-advisor-workflow.mjs).
- Helpers HA via package NFS + carte dashboard EVCS Master (backup fait).
- 1re reco réelle: auto_solar_plus, cible 35%, accord avec le réel.
- Fix sécurité n8n-push: plus de fallback d'id implicite vers le workflow EVCS.
This commit is contained in:
Tallagrand Thierry
2026-08-07 22:42:06 +02:00
parent 1ae8129658
commit 63d56bf284
13 changed files with 1282 additions and 69 deletions

View File

@@ -1,38 +1,46 @@
#!/usr/bin/env node
// R2 — BUILD : src/nodes/*.js → workflow JSON (src/ = SOURCE DE VÉRITÉ, src gagne).
// Usage : node tools/n8n-build.mjs [--check]
// --check : vérifie que le JSON est déjà synchronisé avec src/ (exit 1 sinon),
// sans rien écrire.
// R2 — BUILD : src/ → workflows JSON (src = SOURCE DE VÉRITÉ, src gagne).
// Usage : node tools/n8n-build.mjs [--check] [fichierWorkflow]
// --check : vérifie la synchro sans écrire (exit 1 si désynchronisé)
// fichierWorkflow : limite à un workflow (sinon tous ceux du node-map)
import fs from 'fs';
import path from 'path';
import { WORKFLOW_FILE, SRC_DIR, NODE_MAP, getCode, setCode } from './lib-node-map.mjs';
import { WORKFLOWS, getCode, setCode, findWorkflowByFile } from './lib-node-map.mjs';
const CHECK = process.argv.includes('--check');
const wf = JSON.parse(fs.readFileSync(WORKFLOW_FILE, 'utf8'));
const fileArg = process.argv.slice(2).find(a => !a.startsWith('--'));
const targets = fileArg ? [findWorkflowByFile(fileArg)].filter(Boolean) : WORKFLOWS;
if (fileArg && !targets.length) {
console.log(`(${fileArg} non enregistré dans le node-map : rien à builder)`);
process.exit(0);
}
let changes = 0;
for (const { file, node, kind } of NODE_MAP) {
const srcPath = path.join(SRC_DIR, file);
if (!fs.existsSync(srcPath)) throw new Error(`Fichier source manquant: ${srcPath}`);
const srcCode = fs.readFileSync(srcPath, 'utf8');
const n = wf.nodes.find(x => x.name === node);
if (!n) throw new Error(`Node introuvable dans le JSON: ${node}`);
if (getCode(n, kind) !== srcCode) {
changes++;
console.log(`${CHECK ? 'DÉSYNCHRONISÉ' : 'inject'}: ${file} → "${node}"`);
if (!CHECK) setCode(n, kind, srcCode);
let totalChanges = 0;
for (const w of targets) {
if (!fs.existsSync(w.workflowFile)) {
console.log(`(skip ${w.id}: ${w.workflowFile} absent)`);
continue;
}
const wf = JSON.parse(fs.readFileSync(w.workflowFile, 'utf8'));
let changes = 0;
for (const { file, node, kind } of w.nodes) {
const srcPath = path.join(w.srcDir, file);
if (!fs.existsSync(srcPath)) throw new Error(`Fichier source manquant: ${srcPath}`);
const srcCode = fs.readFileSync(srcPath, 'utf8');
const n = wf.nodes.find(x => x.name === node);
if (!n) throw new Error(`Node introuvable dans ${w.workflowFile}: ${node}`);
if (getCode(n, kind) !== srcCode) {
changes++;
console.log(`${CHECK ? 'DÉSYNCHRONISÉ' : 'inject'}: ${w.srcDir}/${file} → "${node}"`);
if (!CHECK) setCode(n, kind, srcCode);
}
}
if (!CHECK && changes) {
fs.writeFileSync(w.workflowFile, JSON.stringify(wf, null, 2) + '\n');
}
totalChanges += changes;
console.log(`${w.id}: ${changes ? changes + ' node(s) ' + (CHECK ? 'désynchronisé(s)' : 'injecté(s)') : 'synchronisé'}`);
}
if (CHECK) {
console.log(changes ? `\n${changes} node(s) désynchronisé(s) : lancer node tools/n8n-build.mjs` : 'JSON synchronisé avec src/nodes/');
process.exit(changes ? 1 : 0);
}
if (changes) {
fs.writeFileSync(WORKFLOW_FILE, JSON.stringify(wf, null, 2) + '\n');
console.log(`\nBuild OK: ${changes} node(s) injecté(s) dans ${WORKFLOW_FILE}`);
} else {
console.log('Build: aucun changement (déjà synchronisé)');
}
if (CHECK) process.exit(totalChanges ? 1 : 0);