feat: integrate local CAD stack and runtime safeguards
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
python-stable:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Bootstrap repo-local venv
|
||||
run: bash tools/bootstrap_python_env.sh
|
||||
|
||||
- name: Run stable Python suite
|
||||
run: bash tools/test_python.sh --suite stable
|
||||
@@ -5,6 +5,11 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Version tag to publish (for example v1.2.3)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -15,10 +20,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Resolve release tag
|
||||
id: resolve_tag
|
||||
env:
|
||||
INPUT_RELEASE_TAG: ${{ github.event.inputs.release_tag || '' }}
|
||||
run: |
|
||||
RELEASE_TAG="${INPUT_RELEASE_TAG:-${GITHUB_REF_NAME:-}}"
|
||||
case "$RELEASE_TAG" in
|
||||
v*) ;;
|
||||
*)
|
||||
echo "release_tag must start with 'v': $RELEASE_TAG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
||||
- name: Build artefacts
|
||||
run: |
|
||||
mkdir -p build
|
||||
printf 'Kill_LIFE release %s\n' "${GITHUB_REF_NAME:-dev}" > build/artefact.bin
|
||||
printf 'Kill_LIFE release %s\n' "${{ steps.resolve_tag.outputs.release_tag }}" > build/artefact.bin
|
||||
- name: Sign artefacts (cosign)
|
||||
env:
|
||||
COSIGN_KEY: ${{ secrets.SIGNING_KEY }}
|
||||
@@ -32,6 +51,7 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: build/artefact.bin
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: "Release ${{ github.ref_name }}"
|
||||
tag_name: ${{ steps.resolve_tag.outputs.release_tag }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
name: "Release ${{ steps.resolve_tag.outputs.release_tag }}"
|
||||
body: "Release automatisée avec artefacts signés."
|
||||
|
||||
@@ -91,6 +91,7 @@ node_modules/
|
||||
*.pid
|
||||
docker-compose.override.yml
|
||||
.cad-home/
|
||||
.runtime-home/
|
||||
|
||||
# Autres outils CI/CD
|
||||
coverage/
|
||||
|
||||
@@ -50,6 +50,40 @@ cd Kill_LIFE
|
||||
bash install_kill_life.sh
|
||||
```
|
||||
|
||||
### Bootstrap Python repo-local
|
||||
|
||||
```bash
|
||||
bash tools/bootstrap_python_env.sh
|
||||
```
|
||||
|
||||
Options utiles:
|
||||
- `--venv-dir /tmp/kill-life-venv` pour verifier le bootstrap sur une machine ou un environnement vierge
|
||||
- `--reinstall` pour recreer proprement le venv cible
|
||||
|
||||
Le chemin supporte pour le Python du repo est `./.venv/bin/python`.
|
||||
|
||||
### Tests Python repo-local
|
||||
|
||||
```bash
|
||||
bash tools/test_python.sh
|
||||
```
|
||||
|
||||
Ce chemin couvre la suite Python repo-locale stable (`setup_repo`, `mcp_runtime_status`, `openclaw_sanitizer`, `apply_safe_patch`, `validate_specs`, `tools/hw/schops/tests`) sans dependre du `python3` systeme.
|
||||
Les checks dependants du mirror specs ou des runtimes MCP restent des commandes d'integration separees.
|
||||
|
||||
Options utiles:
|
||||
- `--suite stable` pour le chemin repo-local supporte par defaut
|
||||
- `--suite mcp` pour les tests MCP locaux (`notion`, `github-dispatch`, `nexar`)
|
||||
- `--suite all` pour enchainer les deux
|
||||
- `--bootstrap` pour creer le venv cible avant de lancer les tests
|
||||
- `--list` pour afficher exactement les commandes couvertes
|
||||
|
||||
Exemple de verification sur un venv temporaire:
|
||||
|
||||
```bash
|
||||
bash tools/test_python.sh --bootstrap --venv-dir /tmp/kill-life-venv --suite stable
|
||||
```
|
||||
|
||||
### Verifications utiles
|
||||
|
||||
```bash
|
||||
@@ -78,10 +112,21 @@ La stack CAD est documentee dans [`deploy/cad/README.md`](deploy/cad/README.md)
|
||||
|
||||
## Ecosysteme
|
||||
|
||||
- [`crazy_life`](https://github.com/electron-rare/crazy_life) : frontend + backend web/devops
|
||||
- [`mascarade`](https://github.com/electron-rare/mascarade) : repo compagnon et bridge historique
|
||||
- [`crazy_life`](https://github.com/electron-rare/crazy_life) : repo canonique web/devops
|
||||
- [`mascarade`](https://github.com/electron-rare/mascarade) : repo compagnon/orchestration et bridge historique optionnel
|
||||
- [`docs/MASCARADE_BRIDGE.md`](docs/MASCARADE_BRIDGE.md) : articulation locale entre les depots
|
||||
|
||||
Contrat multi-repo:
|
||||
- `crazy_life` publie la surface web/devops et le workflow editor.
|
||||
- `Kill_LIFE` reste la source de verite pour `workflows/*.json`, le runtime, les evidence packs, le firmware, le CAD et la compliance.
|
||||
- `mascarade` ne redevient pas la source canonique de release web; le bridge reste un mecanisme de sync seulement.
|
||||
|
||||
## CI et release
|
||||
|
||||
- `.github/workflows/ci.yml` porte le gate repo-local stable: bootstrap Python + `bash tools/test_python.sh --suite stable`.
|
||||
- `.github/workflows/release_signing.yml` reste le workflow de release versionnee; il attend un tag `v*` ou un `workflow_dispatch` avec `release_tag` explicite.
|
||||
- GitHub Pages n'est pas un gate canonique pour la release `Crazy Lane`; les workflows Pages de `Kill_LIFE` restent des surfaces secondaires docs/evidence.
|
||||
|
||||
## Licence
|
||||
|
||||
MIT. Voir [`licenses/MIT.txt`](licenses/MIT.txt).
|
||||
|
||||
@@ -117,7 +117,7 @@ Format:
|
||||
- Evidence: message `[budget] message length ... exceeds ...`.
|
||||
- Status: `2026-02-21` done (`ZEROCLAW_WEBHOOK_MAX_CHARS=5`, exit 11).
|
||||
|
||||
## Integrations Open WebUI + n8n
|
||||
## Integrations n8n
|
||||
|
||||
- [x] T-401 - Ajouter API alerts + autotriage backend
|
||||
- AC: endpoints `/api/alerts`, `/api/alerts/ack`, `/api/pr/autotriage` disponibles.
|
||||
@@ -127,23 +127,17 @@ Format:
|
||||
- AC: panneau Alerts + Integrations status + action `PR autotriage now`.
|
||||
- Evidence: `tools/ai/zeroclaw_stack_up.sh`.
|
||||
|
||||
- [x] T-403 - Refactor Pipe Open WebUI en mapping mixte
|
||||
- AC: pipe expose `zc.router` + modeles dedies et parsing `/run`/`/pr`.
|
||||
- Evidence: `tools/ai/integrations/openwebui/zeroclaw_orchestrator_pipe.py`.
|
||||
|
||||
- [x] T-404 - Ajouter workflow n8n PR autotriage
|
||||
- AC: workflow avec Webhook + Cron fallback + alert/question outputs.
|
||||
- Evidence: `tools/ai/integrations/n8n/zeroclaw_pr_autotriage_workflow.json`.
|
||||
|
||||
- [x] T-405 - Ajouter provisioning local Docker integrations
|
||||
- AC: compose + scripts `up/down/status` + import n8n + assist Open WebUI.
|
||||
- AC: compose + scripts `up/down/status` + import n8n.
|
||||
- Evidence:
|
||||
- `tools/ai/integrations/docker/docker-compose.openwebui-n8n.yml`
|
||||
- `tools/ai/zeroclaw_integrations_up.sh`
|
||||
- `tools/ai/zeroclaw_integrations_down.sh`
|
||||
- `tools/ai/zeroclaw_integrations_status.sh`
|
||||
- `tools/ai/zeroclaw_integrations_import_n8n.sh`
|
||||
- `tools/ai/zeroclaw_integrations_openwebui_assist.sh`
|
||||
|
||||
## Definition of done
|
||||
|
||||
|
||||
@@ -53,13 +53,15 @@ Format:
|
||||
- AC: un état `ready / degraded / failed` est visible sans lecture manuelle des logs.
|
||||
|
||||
- [ ] K-012 — Rejouer la validation host-native sur une machine avec `pcbnew`
|
||||
- AC: le smoke passe aussi sur le chemin hôte, pas seulement via le fallback conteneur.
|
||||
- AC: le smoke passe aussi sur le chemin hote, pas seulement via le fallback conteneur.
|
||||
- Helper pret: `python3 tools/hw/kicad_host_mcp_smoke.py --json --quick` degrade proprement si `pcbnew` est absent.
|
||||
|
||||
- [x] K-013 — Décider du statut final des micro-serveurs `kicad_kic_ai`
|
||||
- AC: `component_database`, `kicad_tools` et `nexar_api` sont explicitement promus en surfaces auxiliaires supportées.
|
||||
|
||||
- [ ] K-014 — Valider le mode live de `nexar_api`
|
||||
- AC: un run avec `NEXAR_TOKEN` confirme le comportement réel et le distingue du mode démo.
|
||||
- AC: un run avec `NEXAR_TOKEN` confirme le comportement reel et le distingue du mode demo.
|
||||
- Helper pret: `python3 tools/nexar_mcp_smoke.py --json --live` degrade ou echoue proprement tant que le token ou le mode live manquent.
|
||||
|
||||
- [x] K-015 — Implémenter le MCP `Notion`
|
||||
- AC: `tools/run_notion_mcp.sh` expose `search_pages`, `read_page`, `append_to_page`, `create_page` sans retirer le bridge HTTP en V1.
|
||||
@@ -72,3 +74,12 @@ Format:
|
||||
|
||||
- [x] K-018 — Etendre l'observabilite MCP a plusieurs serveurs
|
||||
- AC: `/api/ops/summary` expose un etat agrege et le detail par serveur pour `kicad`, `validate-specs`, `notion` et `github-dispatch`.
|
||||
|
||||
- [x] K-019 — Ajouter un helper de readiness host-native KiCad
|
||||
- AC: `python3 tools/hw/kicad_host_mcp_smoke.py --json --quick` retourne `ready` ou `degraded` sans ambiguite.
|
||||
|
||||
- [x] K-020 — Ajouter un smoke dedie pour `nexar_api`
|
||||
- AC: `python3 tools/nexar_mcp_smoke.py --json` distingue mode demo et mode live.
|
||||
|
||||
- [x] K-021 — Ajouter un rapport MCP local synthetique
|
||||
- AC: `python3 tools/mcp_runtime_status.py --json` agrege les smokes supportes et rend visibles les blocages K-012/K-014.
|
||||
|
||||
@@ -58,12 +58,11 @@ Last updated: 2026-02-21
|
||||
- [x] E-003 - Dashboard live usable for continuous supervision.
|
||||
- [x] E-004 - Prometheus target scrape confirmed on gateway metrics.
|
||||
|
||||
## Integrations (Open WebUI + n8n)
|
||||
## Integrations (n8n)
|
||||
|
||||
- [x] I-201 - Add mixed Open WebUI Pipe mapping (`zc.router` + dedicated models).
|
||||
- [x] I-202 - Add n8n PR autotriage workflow (webhook + cron fallback).
|
||||
- [x] I-203 - Add local docker compose stack for Open WebUI (`3001`) and n8n (`5678`).
|
||||
- [x] I-204 - Add runtime scripts (`integrations_up/down/status`, `import_n8n`, `openwebui_assist`).
|
||||
- [x] I-203 - Add local docker compose stack for n8n (`5678`).
|
||||
- [x] I-204 - Add runtime scripts (`integrations_up/down/status`, `import_n8n`).
|
||||
- [ ] I-205 - Validate end-to-end import + activation on local Docker runtime.
|
||||
- command: `tools/ai/zeroclaw_integrations_up.sh`
|
||||
- command: `tools/ai/zeroclaw_integrations_status.sh`
|
||||
|
||||
+20
-22
@@ -1,30 +1,28 @@
|
||||
# Configuration GitHub Pages
|
||||
# GitHub Pages Posture
|
||||
|
||||
Pour que GitHub Pages serve correctement les artefacts et endpoints JSON :
|
||||
GitHub Pages is not a canonical release surface for `Crazy Lane`.
|
||||
|
||||
1. **Branche à publier** : Configurez GitHub Pages pour utiliser la branche `gh-pages`.
|
||||
2. **Dossier racine** : Choisissez `/` (racine) comme dossier source.
|
||||
3. **Vérification** : Après exécution du workflow, vérifiez que les fichiers sont bien présents sur https://electron-rare.github.io/Kill_LIFE/
|
||||
Current contract:
|
||||
- `crazy_life` owns the canonical web/devops release path.
|
||||
- `Kill_LIFE` keeps runtime, workflow JSON, evidence, firmware, CAD and compliance as source of truth.
|
||||
- Any Pages publication in `Kill_LIFE` is secondary and should be treated as docs/evidence preview only.
|
||||
|
||||
## Procédure
|
||||
## What is canonical today
|
||||
|
||||
- Allez dans **Settings > Pages** du dépôt GitHub.
|
||||
- Sélectionnez :
|
||||
- **Source** : `gh-pages`
|
||||
- **Dossier** : `/` (root)
|
||||
- Sauvegardez.
|
||||
- Relancez le workflow Pages valide (`static.yml`) si besoin.
|
||||
- Release gate: repo-local stable CI + versioned release workflow.
|
||||
- Stable CI: `.github/workflows/ci.yml`
|
||||
- Versioned release: `.github/workflows/release_signing.yml`
|
||||
|
||||
## Points d’attention
|
||||
- Le workflow doit créer le dossier `public/` et le publier sur `gh-pages`.
|
||||
- Les fichiers JSON, endpoints.md, evidence pack doivent être dans `public/`.
|
||||
- Shields.io doit pointer vers : `https://electron-rare.github.io/Kill_LIFE/docs/badges/<badge>.json`
|
||||
## What Pages means here
|
||||
|
||||
---
|
||||
If Pages is still enabled for `Kill_LIFE`, use it only for lightweight docs/evidence preview.
|
||||
Do not use it as a proxy for:
|
||||
- cockpit release readiness
|
||||
- workflow editor release readiness
|
||||
- runtime release gating
|
||||
|
||||
> Si le dossier public n’est pas à la racine, adaptez le chemin dans le workflow et la configuration Pages.
|
||||
> Pour une vérification rapide, utilisez : `curl https://electron-rare.github.io/Kill_LIFE/docs/badges/supply_chain_badge.json`
|
||||
## Legacy note
|
||||
|
||||
---
|
||||
|
||||
**Astuce** : Pour une publication immédiate, utilisez l’action `workflow_dispatch` dans GitHub Actions.
|
||||
The repository still contains more than one historical Pages workflow shape.
|
||||
Until one of them is explicitly simplified or removed, treat Pages as non-blocking
|
||||
and secondary to the canonical CI/release path above.
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Local Change Bundles — 2026-03-08
|
||||
|
||||
But: reduire la derive locale de `Kill_LIFE` en regroupant le worktree en lots revus/publies par sujet.
|
||||
|
||||
## Etat courant
|
||||
|
||||
Le worktree local n'est plus un melange de dizaines de sujets heterogenes. Il se lit maintenant en `2` lots principaux:
|
||||
|
||||
1. `mcp-runtime`
|
||||
2. `python-local`
|
||||
|
||||
Le helper de revue associe est:
|
||||
|
||||
```bash
|
||||
bash tools/review_local_change_bundle.sh <bundle> [status|diff|paths]
|
||||
```
|
||||
|
||||
## Lot 1 — `mcp-runtime`
|
||||
|
||||
Objet:
|
||||
|
||||
- `runtime home` local explicite pour les launchers MCP et CAD
|
||||
- smokes MCP alignes sur les modes d'auth reels
|
||||
- doc operateur associee
|
||||
|
||||
Fichiers:
|
||||
|
||||
- `.gitignore`
|
||||
- `ai-agentic-embedded-base/specs/mcp_tasks.md`
|
||||
- `docs/QUICKSTART.md`
|
||||
- `docs/index.md`
|
||||
- `docs/RUNTIME_HOME.md`
|
||||
- `tools/github_dispatch_mcp_smoke.py`
|
||||
- `tools/hw/kicad_cli.sh`
|
||||
- `tools/hw/run_kicad_mcp.sh`
|
||||
- `tools/lib/runtime_home.sh`
|
||||
- `tools/notion_mcp.py`
|
||||
- `tools/notion_mcp_smoke.py`
|
||||
- `tools/run_github_dispatch_mcp.sh`
|
||||
- `tools/run_nexar_mcp.sh`
|
||||
- `tools/run_notion_mcp.sh`
|
||||
|
||||
Revue:
|
||||
|
||||
```bash
|
||||
bash tools/review_local_change_bundle.sh mcp-runtime status
|
||||
bash tools/review_local_change_bundle.sh mcp-runtime diff
|
||||
```
|
||||
|
||||
## Lot 2 — `python-local`
|
||||
|
||||
Objet:
|
||||
|
||||
- bootstrap Python repo-local stable
|
||||
- commande de tests repo-locale stable
|
||||
- harness de tests ajustes pour ce chemin supporte
|
||||
|
||||
Fichiers:
|
||||
|
||||
- `README.md`
|
||||
- `test/test_openclaw_sanitizer.py`
|
||||
- `tools/bootstrap_python_env.sh`
|
||||
- `tools/hw/schops/tests/test_rules_engine.py`
|
||||
- `tools/test_python.sh`
|
||||
|
||||
Revue:
|
||||
|
||||
```bash
|
||||
bash tools/review_local_change_bundle.sh python-local status
|
||||
bash tools/review_local_change_bundle.sh python-local diff
|
||||
```
|
||||
|
||||
Validation repo-locale:
|
||||
|
||||
```bash
|
||||
bash tools/bootstrap_python_env.sh
|
||||
bash tools/test_python.sh --suite stable
|
||||
```
|
||||
|
||||
## Ordre recommande
|
||||
|
||||
1. publier ou reviewer `mcp-runtime`
|
||||
2. publier ou reviewer `python-local`
|
||||
|
||||
Ce decoupage evite de melanger:
|
||||
|
||||
- la hygiene runtime des launchers MCP
|
||||
- le contrat operateur Python repo-local
|
||||
|
||||
## Note
|
||||
|
||||
Les tests MCP d'integration (`--suite mcp`) restent un lot de verification distinct du chemin repo-local stable. Ils dependent encore des launchers et des runtimes compagnons, donc ils ne servent pas de gate minimale pour le lot `python-local`.
|
||||
@@ -76,6 +76,8 @@ tools/hw/cad_stack.sh pio system info
|
||||
|
||||
Le workspace monté est `Kill_LIFE` par défaut.
|
||||
|
||||
Les wrappers locaux qui lancent des runtimes Python/Node ou des conteneurs remappés utilisent un `HOME` explicite local au repo. La règle est documentée dans [RUNTIME_HOME.md](RUNTIME_HOME.md).
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contribuer
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Runtime Home
|
||||
|
||||
Ce document fixe la règle pour les launchers locaux `Kill_LIFE` qui exécutent :
|
||||
|
||||
- des outils Python
|
||||
- des outils Node
|
||||
- des conteneurs Docker remappés avec `--user uid:gid`
|
||||
|
||||
## Problème visé
|
||||
|
||||
Sans `HOME` explicite, certains runtimes essaient d’utiliser un home implicite comme `/home/<user>`.
|
||||
|
||||
En environnement local ou conteneurisé, cela peut produire des erreurs du type :
|
||||
|
||||
```text
|
||||
EACCES: permission denied, mkdir '/home/clems'
|
||||
```
|
||||
|
||||
Le pattern est typique quand :
|
||||
|
||||
- le process ne tourne pas en root
|
||||
- le parent de `HOME` n’est pas writable
|
||||
- le runtime tente d’écrire un cache, une config ou un état local
|
||||
|
||||
## Règle projet
|
||||
|
||||
Tout launcher `Kill_LIFE` qui peut écrire localement doit définir explicitement :
|
||||
|
||||
- `HOME`
|
||||
- `XDG_CONFIG_HOME`
|
||||
- `XDG_CACHE_HOME`
|
||||
|
||||
Ces chemins doivent pointer vers un répertoire local au repo, jamais vers `/home/<user>` implicite.
|
||||
|
||||
## Emplacements utilisés
|
||||
|
||||
Deux familles sont utilisées dans le repo :
|
||||
|
||||
- `./.runtime-home/<tool>` pour les wrappers host-side génériques
|
||||
- `./.cad-home/<tool>` pour les launchers CAD/EDA
|
||||
|
||||
Ces dossiers sont ignorés par Git.
|
||||
|
||||
## Helper commun
|
||||
|
||||
Le helper shell commun est :
|
||||
|
||||
- [tools/lib/runtime_home.sh](../tools/lib/runtime_home.sh)
|
||||
|
||||
Fonctions exposées :
|
||||
|
||||
- `kill_life_runtime_home_init <root_dir> <runtime_name> [base_dir]`
|
||||
- `kill_life_runtime_home_ensure`
|
||||
|
||||
## Exemple minimal
|
||||
|
||||
```bash
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$ROOT_DIR/tools/lib/runtime_home.sh"
|
||||
|
||||
kill_life_runtime_home_init "$ROOT_DIR" "my-tool"
|
||||
kill_life_runtime_home_ensure
|
||||
|
||||
exec python3 my_tool.py "$@"
|
||||
```
|
||||
|
||||
## Cas Docker remappé
|
||||
|
||||
Si un conteneur est lancé avec `--user "$(id -u):$(id -g)"`, il faut aussi propager ces variables :
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-e HOME="$HOME" \
|
||||
-e XDG_CONFIG_HOME="$XDG_CONFIG_HOME" \
|
||||
-e XDG_CACHE_HOME="$XDG_CACHE_HOME" \
|
||||
image \
|
||||
sh -lc 'mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"; exec tool "$@"' sh "$@"
|
||||
```
|
||||
|
||||
## Scripts déjà alignés
|
||||
|
||||
- [tools/hw/kicad_cli.sh](../tools/hw/kicad_cli.sh)
|
||||
- [tools/hw/run_kicad_mcp.sh](../tools/hw/run_kicad_mcp.sh)
|
||||
- [tools/run_notion_mcp.sh](../tools/run_notion_mcp.sh)
|
||||
- [tools/run_github_dispatch_mcp.sh](../tools/run_github_dispatch_mcp.sh)
|
||||
- [tools/run_nexar_mcp.sh](../tools/run_nexar_mcp.sh)
|
||||
|
||||
## Revue rapide avant merge
|
||||
|
||||
Avant d’ajouter un nouveau launcher :
|
||||
|
||||
- vérifier s’il peut écrire des caches/configs
|
||||
- ne pas supposer que `HOME` existe déjà
|
||||
- ne pas dépendre d’un home hôte implicite
|
||||
- créer explicitement les dossiers runtime nécessaires
|
||||
- ajouter le répertoire d’état à `.gitignore` si besoin
|
||||
@@ -11,6 +11,7 @@ Ce repo est un **socle** :
|
||||
- Installation : `docs/INSTALL.md`
|
||||
- Runbook opérateur : `docs/RUNBOOK.md`
|
||||
- FAQ : `docs/FAQ.md`
|
||||
- Runtime home : `docs/RUNTIME_HOME.md`
|
||||
- Pont vers le repo compagnon d’exécution : `docs/MASCARADE_BRIDGE.md`
|
||||
|
||||
## Workflows professionnels (opérationnels)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# Rituel — Release
|
||||
|
||||
## Pré-requis
|
||||
- Tous gates CI OK
|
||||
- Gate repo-local stable OK
|
||||
- Evidence pack complet
|
||||
- Notes de release prêtes
|
||||
- Tag de release prepare (`v*`)
|
||||
|
||||
## Étapes
|
||||
- Bump version
|
||||
- Tag
|
||||
- Publier artifacts
|
||||
- Créer le tag `v*`
|
||||
- Publier les artefacts via `release_signing.yml`
|
||||
- chemin canonique: push du tag
|
||||
- fallback opérateur: `workflow_dispatch` avec `release_tag` explicite
|
||||
- Changelog
|
||||
|
||||
## Post-release
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Publication automatique des artefacts et endpoints sur GitHub Pages
|
||||
|
||||
> Exemple secondaire seulement. Ce flux ne remplace pas le contrat canonique de
|
||||
> release, qui reste `crazy_life` pour le web/devops et `Kill_LIFE` pour le
|
||||
> runtime/evidence/versioning.
|
||||
|
||||
## Objectif
|
||||
Rendre accessibles publiquement les endpoints JSON des badges dynamiques et les artefacts CI/CD via GitHub Pages.
|
||||
|
||||
|
||||
+2
-8
@@ -117,7 +117,7 @@ Format:
|
||||
- Evidence: message `[budget] message length ... exceeds ...`.
|
||||
- Status: `2026-02-21` done (`ZEROCLAW_WEBHOOK_MAX_CHARS=5`, exit 11).
|
||||
|
||||
## Integrations Open WebUI + n8n
|
||||
## Integrations n8n
|
||||
|
||||
- [x] T-401 - Ajouter API alerts + autotriage backend
|
||||
- AC: endpoints `/api/alerts`, `/api/alerts/ack`, `/api/pr/autotriage` disponibles.
|
||||
@@ -127,23 +127,17 @@ Format:
|
||||
- AC: panneau Alerts + Integrations status + action `PR autotriage now`.
|
||||
- Evidence: `tools/ai/zeroclaw_stack_up.sh`.
|
||||
|
||||
- [x] T-403 - Refactor Pipe Open WebUI en mapping mixte
|
||||
- AC: pipe expose `zc.router` + modeles dedies et parsing `/run`/`/pr`.
|
||||
- Evidence: `tools/ai/integrations/openwebui/zeroclaw_orchestrator_pipe.py`.
|
||||
|
||||
- [x] T-404 - Ajouter workflow n8n PR autotriage
|
||||
- AC: workflow avec Webhook + Cron fallback + alert/question outputs.
|
||||
- Evidence: `tools/ai/integrations/n8n/zeroclaw_pr_autotriage_workflow.json`.
|
||||
|
||||
- [x] T-405 - Ajouter provisioning local Docker integrations
|
||||
- AC: compose + scripts `up/down/status` + import n8n + assist Open WebUI.
|
||||
- AC: compose + scripts `up/down/status` + import n8n.
|
||||
- Evidence:
|
||||
- `tools/ai/integrations/docker/docker-compose.openwebui-n8n.yml`
|
||||
- `tools/ai/zeroclaw_integrations_up.sh`
|
||||
- `tools/ai/zeroclaw_integrations_down.sh`
|
||||
- `tools/ai/zeroclaw_integrations_status.sh`
|
||||
- `tools/ai/zeroclaw_integrations_import_n8n.sh`
|
||||
- `tools/ai/zeroclaw_integrations_openwebui_assist.sh`
|
||||
|
||||
## Definition of done
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ Last updated: 2026-02-21
|
||||
- [x] E-003 - Dashboard live usable for continuous supervision.
|
||||
- [x] E-004 - Prometheus target scrape confirmed on gateway metrics.
|
||||
|
||||
## Integrations (Open WebUI + n8n)
|
||||
## Integrations (n8n)
|
||||
|
||||
- [x] I-201 - Add mixed Open WebUI Pipe mapping (`zc.router` + dedicated models).
|
||||
- [x] I-202 - Add n8n PR autotriage workflow (webhook + cron fallback).
|
||||
- [x] I-203 - Add local docker compose stack for Open WebUI (`3001`) and n8n (`5678`).
|
||||
- [x] I-204 - Add runtime scripts (`integrations_up/down/status`, `import_n8n`, `openwebui_assist`).
|
||||
- [x] I-203 - Add local docker compose stack for n8n (`5678`).
|
||||
- [x] I-204 - Add runtime scripts (`integrations_up/down/status`, `import_n8n`).
|
||||
- [ ] I-205 - Validate end-to-end import + activation on local Docker runtime.
|
||||
- status: Deferred (post-stabilization)
|
||||
- command: `tools/ai/zeroclaw_integrations_up.sh`
|
||||
- command: `tools/ai/zeroclaw_integrations_status.sh`
|
||||
- command: `tools/ai/zeroclaw_integrations_import_n8n.sh`
|
||||
|
||||
@@ -4,30 +4,33 @@ Test automatisé pour la sanitisation des commentaires OpenClaw.
|
||||
Vérifie que le sanitizer retire les patterns à risque et ne laisse aucun secret, code ou mention.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools" / "ai"))
|
||||
from sanitize_issue import sanitize_text
|
||||
|
||||
def test_sanitize_basic():
|
||||
input_text = """
|
||||
|
||||
class OpenClawSanitizerTests(unittest.TestCase):
|
||||
def test_sanitize_basic(self):
|
||||
input_text = """
|
||||
Hello @user, see #123.
|
||||
```rm -rf /home/user```
|
||||
Contact: user@example.com
|
||||
Visit https://evil.com
|
||||
!dangerous command
|
||||
<secret>password</secret>
|
||||
"""
|
||||
sanitized = sanitize_text(input_text)
|
||||
assert "@user" not in sanitized
|
||||
assert "#123" not in sanitized
|
||||
assert "rm -rf" not in sanitized
|
||||
assert "user@example.com" not in sanitized
|
||||
assert "https://evil.com" not in sanitized
|
||||
assert "!dangerous" not in sanitized
|
||||
assert "password" not in sanitized
|
||||
assert "secret" not in sanitized
|
||||
print("Sanitization test passed.")
|
||||
"""
|
||||
sanitized = sanitize_text(input_text)
|
||||
self.assertNotIn("@user", sanitized)
|
||||
self.assertNotIn("#123", sanitized)
|
||||
self.assertNotIn("rm -rf", sanitized)
|
||||
self.assertNotIn("user@example.com", sanitized)
|
||||
self.assertNotIn("https://evil.com", sanitized)
|
||||
self.assertNotIn("!dangerous", sanitized)
|
||||
self.assertNotIn("password", sanitized)
|
||||
self.assertNotIn("secret", sanitized)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_sanitize_basic()
|
||||
unittest.main()
|
||||
|
||||
@@ -48,7 +48,9 @@ class ValidateSpecsTests(unittest.TestCase):
|
||||
self.assertIn("compliance", payload)
|
||||
self.assertIn("rfc2119", payload)
|
||||
self.assertIn("mirror_sync", payload)
|
||||
self.assertTrue(payload["mirror_sync"]["ok"])
|
||||
self.assertTrue(payload["ok"])
|
||||
self.assertFalse(payload["require_mirror_sync"])
|
||||
self.assertTrue(payload["mirror_sync"]["mirror_exists"])
|
||||
|
||||
def test_mcp_server_supports_initialize_and_tools_list(self):
|
||||
proc = subprocess.Popen(
|
||||
|
||||
@@ -1036,10 +1036,8 @@ cat >"$INDEX_FILE" <<EOF
|
||||
<a href="/orchestrator.log">/orchestrator.log</a>
|
||||
<a href="/prometheus.yml">/prometheus.yml</a>
|
||||
<a href="/integrations/README.md">/integrations/README.md</a>
|
||||
<a href="/integrations/openwebui/zeroclaw_orchestrator_pipe.py">openwebui pipe</a>
|
||||
<a href="/integrations/n8n/zeroclaw_orchestrator_workflow.json">n8n workflow</a>
|
||||
<a href="/integrations/n8n/zeroclaw_pr_autotriage_workflow.json">n8n pr-autotriage workflow</a>
|
||||
<a href="/integrations/docker/docker-compose.openwebui-n8n.yml">compose openwebui+n8n</a>
|
||||
<a href="/integrations/langgraph/README.md">langgraph runbook</a>
|
||||
<a href="/integrations/autogen/README.md">autogen runbook</a>
|
||||
<a href="http://$GATEWAY_HOST:$GATEWAY_PORT/health">/health</a>
|
||||
@@ -1162,7 +1160,6 @@ cat >"$INDEX_FILE" <<EOF
|
||||
<div class="body">
|
||||
<pre id="integrationsStatus">(integrations status)</pre>
|
||||
<div class="btn-row">
|
||||
<button onclick="window.open('http://127.0.0.1:3001','_blank')">Open WebUI</button>
|
||||
<button onclick="window.open('http://127.0.0.1:5678','_blank')">Open n8n</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1196,8 +1193,7 @@ cat >"$INDEX_FILE" <<EOF
|
||||
<pre id="hubQuickStart">1) tools/ai/zeroclaw_stack_up.sh
|
||||
2) tools/ai/zeroclaw_integrations_up.sh
|
||||
3) Ouvrir http://127.0.0.1:8788/ (monitoring)
|
||||
4) Ouvrir http://127.0.0.1:3001/ (Open WebUI)
|
||||
5) Ouvrir http://127.0.0.1:5678/ (n8n)</pre>
|
||||
4) Ouvrir http://127.0.0.1:5678/ (n8n)</pre>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -2060,7 +2056,6 @@ cat >"$INDEX_FILE" <<EOF
|
||||
" paired=" + boolText(health.paired) +
|
||||
" prom_ok=" + boolText(health.prometheus_ok) +
|
||||
" prom_status=" + safeText(health.prometheus_status) +
|
||||
" openwebui_ok=" + boolText(health.openwebui_ok) +
|
||||
" n8n_ok=" + boolText(health.n8n_ok) +
|
||||
"\nactive_alerts: " + safeText(alerts.active_count || 0) +
|
||||
"\nlast_convo: " + safeText(last.conversation || "(none)") +
|
||||
@@ -2068,10 +2063,7 @@ cat >"$INDEX_FILE" <<EOF
|
||||
document.getElementById("opsStatus").textContent = ops;
|
||||
|
||||
const integrationsText =
|
||||
"openwebui_url=" + safeText(integrations.openwebui_url || "http://127.0.0.1:3001") +
|
||||
" status=" + safeText(health.openwebui_status) +
|
||||
" ok=" + boolText(health.openwebui_ok) +
|
||||
"\nn8n_url=" + safeText(integrations.n8n_url || "http://127.0.0.1:5678") +
|
||||
"n8n_url=" + safeText(integrations.n8n_url || "http://127.0.0.1:5678") +
|
||||
" status=" + safeText(health.n8n_status) +
|
||||
" ok=" + boolText(health.n8n_ok);
|
||||
document.getElementById("integrationsStatus").textContent = integrationsText;
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VENV_DIR="${ROOT_DIR}/.venv"
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||
REINSTALL=false
|
||||
REQ_FILE="${ROOT_DIR}/tools/compliance/requirements.txt"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash tools/bootstrap_python_env.sh [options]
|
||||
|
||||
Create or refresh the supported repo-local Python environment for Kill_LIFE.
|
||||
|
||||
Options:
|
||||
--python BIN Python interpreter to use (default: PYTHON_BIN or python3)
|
||||
--venv-dir PATH Virtualenv directory to create/reuse (default: ./.venv)
|
||||
--reinstall Remove and recreate the target virtualenv before install
|
||||
-h, --help Show this help
|
||||
|
||||
Notes:
|
||||
- The stable repo-local suite uses unittest and a minimal dependency set.
|
||||
- PyYAML is installed because tools/validate_specs.py depends on compliance YAML helpers.
|
||||
- jsonschema is installed because test_apply_safe_patch exercises tools/mistral/apply_safe_patch.py.
|
||||
|
||||
Examples:
|
||||
bash tools/bootstrap_python_env.sh
|
||||
bash tools/bootstrap_python_env.sh --venv-dir /tmp/kill-life-venv --reinstall
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--python)
|
||||
shift
|
||||
[[ $# -gt 0 ]] || { echo "Missing value for --python" >&2; usage >&2; exit 2; }
|
||||
PYTHON_BIN="$1"
|
||||
;;
|
||||
--venv-dir)
|
||||
shift
|
||||
[[ $# -gt 0 ]] || { echo "Missing value for --venv-dir" >&2; usage >&2; exit 2; }
|
||||
VENV_DIR="$1"
|
||||
;;
|
||||
--reinstall)
|
||||
REINSTALL=true
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
||||
echo "Python interpreter not found: ${PYTHON_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${REINSTALL}" == true && -d "${VENV_DIR}" ]]; then
|
||||
echo "[bootstrap-python] removing ${VENV_DIR}"
|
||||
rm -rf "${VENV_DIR}"
|
||||
fi
|
||||
|
||||
if [[ ! -x "${VENV_DIR}/bin/python" ]]; then
|
||||
echo "[bootstrap-python] creating ${VENV_DIR}"
|
||||
"${PYTHON_BIN}" -m venv "${VENV_DIR}"
|
||||
else
|
||||
echo "[bootstrap-python] reusing ${VENV_DIR}"
|
||||
fi
|
||||
|
||||
if ! "${VENV_DIR}/bin/python" -c "import yaml, jsonschema" >/dev/null 2>&1; then
|
||||
echo "[bootstrap-python] installing minimal test dependencies"
|
||||
"${VENV_DIR}/bin/python" -m pip install --upgrade pip setuptools wheel
|
||||
"${VENV_DIR}/bin/python" -m pip install -r "${REQ_FILE}"
|
||||
"${VENV_DIR}/bin/python" -m pip install 'jsonschema>=4.21.0'
|
||||
fi
|
||||
|
||||
echo "[bootstrap-python] python: ${VENV_DIR}/bin/python"
|
||||
echo "[bootstrap-python] test: bash tools/test_python.sh --venv-dir ${VENV_DIR}"
|
||||
@@ -22,6 +22,19 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
SERVER = ROOT / "tools" / "run_github_dispatch_mcp.sh"
|
||||
|
||||
|
||||
def github_dispatch_auth_configured() -> bool:
|
||||
auth_mode = os.getenv("GITHUB_DISPATCH_AUTH_MODE", "token").strip().lower()
|
||||
if auth_mode == "app":
|
||||
return bool(
|
||||
os.getenv("GITHUB_APP_ID", "").strip()
|
||||
and os.getenv("GITHUB_APP_PRIVATE_KEY", "").strip()
|
||||
and os.getenv("GITHUB_APP_INSTALLATION_ID", "").strip()
|
||||
)
|
||||
return bool(
|
||||
os.getenv("KILL_LIFE_GITHUB_TOKEN") or os.getenv("GITHUB_TOKEN")
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--timeout", type=float, default=15.0)
|
||||
@@ -42,9 +55,7 @@ def parse_args() -> argparse.Namespace:
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
token_configured = bool(
|
||||
os.getenv("KILL_LIFE_GITHUB_TOKEN") or os.getenv("GITHUB_TOKEN")
|
||||
)
|
||||
token_configured = github_dispatch_auth_configured()
|
||||
proc = spawn_server(["bash", str(SERVER)], ROOT)
|
||||
payload = {
|
||||
"status": "failed",
|
||||
@@ -78,7 +89,7 @@ def main() -> int:
|
||||
else:
|
||||
payload["status"] = "degraded"
|
||||
payload["live_validation"] = "missing_secret"
|
||||
payload["error"] = "KILL_LIFE_GITHUB_TOKEN or GITHUB_TOKEN missing"
|
||||
payload["error"] = "GitHub dispatch auth missing"
|
||||
return emit_payload(payload, json_output=args.json)
|
||||
|
||||
list_result = call_tool(
|
||||
@@ -109,7 +120,7 @@ def main() -> int:
|
||||
payload["checks"].append("dispatch_workflow_missing_secret")
|
||||
payload["status"] = "degraded"
|
||||
payload["live_validation"] = "missing_secret"
|
||||
payload["error"] = "KILL_LIFE_GITHUB_TOKEN or GITHUB_TOKEN missing"
|
||||
payload["error"] = "GitHub dispatch auth missing"
|
||||
return emit_payload(payload, json_output=args.json)
|
||||
|
||||
if not args.live:
|
||||
|
||||
Regular → Executable
+14
-1
@@ -9,6 +9,9 @@ set -euo pipefail
|
||||
# KICAD_CLI_BIN: override local path
|
||||
# KICAD_DOCKER_IMAGE: override docker image (default: kicad/kicad:nightly)
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$ROOT_DIR/lib/runtime_home.sh"
|
||||
|
||||
BIN="${KICAD_CLI_BIN:-}"
|
||||
if [[ -z "$BIN" ]]; then
|
||||
if command -v kicad-cli >/dev/null 2>&1; then
|
||||
@@ -33,5 +36,15 @@ IMG="${KICAD_DOCKER_IMAGE:-kicad/kicad:nightly}"
|
||||
# run as current user to avoid root-owned artifacts
|
||||
UIDGID="$(id -u):$(id -g)"
|
||||
WORKDIR="$(pwd)"
|
||||
kill_life_runtime_home_init "$WORKDIR" "kicad-cli" "$WORKDIR/.cad-home"
|
||||
kill_life_runtime_home_ensure
|
||||
|
||||
exec docker run --rm -u "$UIDGID" -v "$WORKDIR:$WORKDIR" -w "$WORKDIR" "$IMG" kicad-cli "$@"
|
||||
exec docker run --rm \
|
||||
-u "$UIDGID" \
|
||||
-v "$WORKDIR:$WORKDIR" \
|
||||
-w "$WORKDIR" \
|
||||
-e HOME="$HOME" \
|
||||
-e XDG_CONFIG_HOME="$XDG_CONFIG_HOME" \
|
||||
-e XDG_CACHE_HOME="$XDG_CACHE_HOME" \
|
||||
"$IMG" \
|
||||
sh -lc 'set -e; mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"; exec kicad-cli "$@"' sh "$@"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/tools/lib/runtime_home.sh"
|
||||
COMPOSE_FILE="$ROOT_DIR/deploy/cad/docker-compose.yml"
|
||||
VERBOSE=0
|
||||
DOCTOR=0
|
||||
@@ -187,9 +188,13 @@ MASCARADE_DIR="${MASCARADE_DIR:-$REPO_PARENT/mascarade}"
|
||||
SERVER_DIR="$MASCARADE_DIR/finetune/kicad_mcp_server"
|
||||
ENTRYPOINT="${KICAD_MCP_ENTRYPOINT:-$SERVER_DIR/dist/index.js}"
|
||||
NODE_BIN="${NODE_BIN:-node}"
|
||||
MCP_HOME="${KICAD_MCP_HOME:-$ROOT_DIR/.cad-home/kicad-mcp}"
|
||||
CONFIG_HOME="${XDG_CONFIG_HOME:-$MCP_HOME/.config}"
|
||||
CACHE_HOME="${XDG_CACHE_HOME:-$MCP_HOME/.cache}"
|
||||
if [ -n "${KICAD_MCP_HOME:-}" ] && [ -z "${KILL_LIFE_RUNTIME_HOME:-}" ]; then
|
||||
export KILL_LIFE_RUNTIME_HOME="$KICAD_MCP_HOME"
|
||||
fi
|
||||
kill_life_runtime_home_init "$ROOT_DIR" "kicad-mcp" "$ROOT_DIR/.cad-home"
|
||||
MCP_HOME="$HOME"
|
||||
CONFIG_HOME="$XDG_CONFIG_HOME"
|
||||
CACHE_HOME="$XDG_CACHE_HOME"
|
||||
DATA_DIR="${KICAD_MCP_DATA_DIR:-$MCP_HOME/data}"
|
||||
PYTHONPATH_VALUE="$(build_pythonpath)"
|
||||
PROBE_PYTHON="$(resolve_probe_python)"
|
||||
@@ -248,7 +253,8 @@ fi
|
||||
[ -d "$MASCARADE_DIR" ] || die "companion repo not found: $MASCARADE_DIR"
|
||||
[ -n "$PROBE_PYTHON" ] || die "no Python executable found for KiCad MCP runtime"
|
||||
|
||||
mkdir -p "$MCP_HOME" "$CONFIG_HOME" "$CACHE_HOME" "$DATA_DIR"
|
||||
kill_life_runtime_home_ensure
|
||||
mkdir -p "$DATA_DIR"
|
||||
|
||||
export HOME="$MCP_HOME"
|
||||
export XDG_CONFIG_HOME="$CONFIG_HOME"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
@@ -8,6 +9,7 @@ def load_schops_module():
|
||||
spec = importlib.util.spec_from_file_location("schops", schops_path)
|
||||
assert spec and spec.loader
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = mod
|
||||
spec.loader.exec_module(mod) # type: ignore
|
||||
return mod
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
kill_life_runtime_home_init() {
|
||||
local root_dir="$1"
|
||||
local runtime_name="$2"
|
||||
local base_dir="${3:-$root_dir/.runtime-home}"
|
||||
|
||||
RUNTIME_HOME="${KILL_LIFE_RUNTIME_HOME:-$base_dir/$runtime_name}"
|
||||
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$RUNTIME_HOME/.config}"
|
||||
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$RUNTIME_HOME/.cache}"
|
||||
HOME="$RUNTIME_HOME"
|
||||
|
||||
export RUNTIME_HOME HOME XDG_CONFIG_HOME XDG_CACHE_HOME
|
||||
}
|
||||
|
||||
kill_life_runtime_home_ensure() {
|
||||
mkdir -p "$RUNTIME_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
|
||||
}
|
||||
+10
-5
@@ -26,8 +26,10 @@ MASCARADE_CORE_DIR = MASCARADE_DIR / "core"
|
||||
if str(MASCARADE_CORE_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(MASCARADE_CORE_DIR))
|
||||
|
||||
from mascarade.config import is_secret_configured, settings # noqa: E402
|
||||
from mascarade.integrations.notion import NotionClient # noqa: E402
|
||||
from mascarade.integrations.notion import ( # noqa: E402
|
||||
NotionClient,
|
||||
notion_auth_configured,
|
||||
)
|
||||
|
||||
|
||||
TOOLS = [
|
||||
@@ -95,15 +97,18 @@ def _missing_secret_payload() -> dict[str, Any]:
|
||||
"ok": False,
|
||||
"error": {
|
||||
"code": "missing_secret",
|
||||
"message": "Notion non configure (NOTION_API_KEY manquant)",
|
||||
"message": (
|
||||
"Notion non configure "
|
||||
"(NOTION_API_KEY ou credentials OAuth Notion manquants)"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _with_client(callback):
|
||||
if not is_secret_configured(settings.notion_api_key):
|
||||
if not notion_auth_configured():
|
||||
return error_tool_result(
|
||||
"Notion non configure (NOTION_API_KEY manquant)",
|
||||
"Notion non configure (NOTION_API_KEY ou credentials OAuth Notion manquants)",
|
||||
_missing_secret_payload(),
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,20 @@ SERVER = ROOT / "tools" / "run_notion_mcp.sh"
|
||||
PAGE_ID_ENV = "NOTION_MCP_SMOKE_PAGE_ID"
|
||||
|
||||
|
||||
def notion_auth_configured() -> bool:
|
||||
auth_mode = os.getenv("NOTION_AUTH_MODE", "api_key").strip().lower()
|
||||
if auth_mode == "oauth_oidc":
|
||||
return bool(
|
||||
os.getenv("NOTION_OAUTH_CLIENT_ID", "").strip()
|
||||
and os.getenv("NOTION_OAUTH_CLIENT_SECRET", "").strip()
|
||||
and (
|
||||
os.getenv("NOTION_OAUTH_ACCESS_TOKEN", "").strip()
|
||||
or os.getenv("NOTION_OAUTH_REFRESH_TOKEN", "").strip()
|
||||
)
|
||||
)
|
||||
return bool(os.getenv("NOTION_API_KEY"))
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--timeout", type=float, default=15.0)
|
||||
@@ -34,7 +48,7 @@ def parse_args() -> argparse.Namespace:
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
secret_configured = bool(os.getenv("NOTION_API_KEY"))
|
||||
secret_configured = notion_auth_configured()
|
||||
proc = spawn_server(["bash", str(SERVER)], ROOT)
|
||||
payload = {
|
||||
"status": "failed",
|
||||
@@ -67,7 +81,7 @@ def main() -> int:
|
||||
else:
|
||||
payload["status"] = "degraded"
|
||||
payload["live_validation"] = "missing_secret"
|
||||
payload["error"] = "NOTION_API_KEY missing"
|
||||
payload["error"] = "NOTION auth missing"
|
||||
return emit_payload(payload, json_output=args.json)
|
||||
|
||||
search_result = call_tool(
|
||||
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MODE="status"
|
||||
BUNDLE=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash tools/review_local_change_bundle.sh <bundle> [mode]
|
||||
|
||||
Bundles:
|
||||
mcp-runtime Runtime-home, MCP launchers, MCP smokes, docs MCP associees
|
||||
python-local Bootstrap/test Python repo-local et harness associes
|
||||
all Ensemble des lots suivis localement
|
||||
|
||||
Modes:
|
||||
status git status cible sur le bundle (default)
|
||||
diff git diff cible sur le bundle
|
||||
paths liste exacte des fichiers du bundle
|
||||
|
||||
Examples:
|
||||
bash tools/review_local_change_bundle.sh mcp-runtime
|
||||
bash tools/review_local_change_bundle.sh python-local diff
|
||||
bash tools/review_local_change_bundle.sh all paths
|
||||
EOF
|
||||
}
|
||||
|
||||
bundle_paths() {
|
||||
case "$1" in
|
||||
mcp-runtime)
|
||||
cat <<'EOF'
|
||||
.gitignore
|
||||
ai-agentic-embedded-base/specs/mcp_tasks.md
|
||||
docs/QUICKSTART.md
|
||||
docs/index.md
|
||||
docs/RUNTIME_HOME.md
|
||||
tools/github_dispatch_mcp_smoke.py
|
||||
tools/hw/kicad_cli.sh
|
||||
tools/hw/run_kicad_mcp.sh
|
||||
tools/lib/runtime_home.sh
|
||||
tools/notion_mcp.py
|
||||
tools/notion_mcp_smoke.py
|
||||
tools/run_github_dispatch_mcp.sh
|
||||
tools/run_nexar_mcp.sh
|
||||
tools/run_notion_mcp.sh
|
||||
EOF
|
||||
;;
|
||||
python-local)
|
||||
cat <<'EOF'
|
||||
README.md
|
||||
test/test_openclaw_sanitizer.py
|
||||
tools/bootstrap_python_env.sh
|
||||
tools/hw/schops/tests/test_rules_engine.py
|
||||
tools/test_python.sh
|
||||
EOF
|
||||
;;
|
||||
all)
|
||||
{
|
||||
bundle_paths mcp-runtime
|
||||
bundle_paths python-local
|
||||
} | awk '!seen[$0]++'
|
||||
;;
|
||||
*)
|
||||
echo "Unknown bundle: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
BUNDLE="$1"
|
||||
if [[ $# -eq 2 ]]; then
|
||||
MODE="$2"
|
||||
fi
|
||||
|
||||
mapfile -t PATHS < <(bundle_paths "$BUNDLE")
|
||||
|
||||
case "$MODE" in
|
||||
status)
|
||||
exec git -C "$ROOT_DIR" status --short -- "${PATHS[@]}"
|
||||
;;
|
||||
diff)
|
||||
exec git -C "$ROOT_DIR" diff -- "${PATHS[@]}"
|
||||
;;
|
||||
paths)
|
||||
printf '%s\n' "${PATHS[@]}"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown mode: $MODE" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -4,6 +4,8 @@ set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MASCARADE_DIR="${MASCARADE_DIR:-$ROOT_DIR/../mascarade}"
|
||||
SERVER_SCRIPT="$ROOT_DIR/tools/github_dispatch_mcp.py"
|
||||
source "$ROOT_DIR/tools/lib/runtime_home.sh"
|
||||
kill_life_runtime_home_init "$ROOT_DIR" "github-dispatch-mcp"
|
||||
|
||||
detect_core_python() {
|
||||
local candidate="${MASCARADE_CORE_PYTHON:-}"
|
||||
@@ -36,6 +38,9 @@ ROOT_DIR=$ROOT_DIR
|
||||
MASCARADE_DIR=$MASCARADE_DIR
|
||||
CORE_PYTHON=$CORE_PYTHON
|
||||
SERVER_SCRIPT=$SERVER_SCRIPT
|
||||
HOME=$RUNTIME_HOME
|
||||
XDG_CONFIG_HOME=$XDG_CONFIG_HOME
|
||||
XDG_CACHE_HOME=$XDG_CACHE_HOME
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
@@ -45,5 +50,6 @@ fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
kill_life_runtime_home_ensure
|
||||
export PYTHONPATH="$MASCARADE_DIR/core${PYTHONPATH:+:$PYTHONPATH}"
|
||||
exec "$CORE_PYTHON" "$SERVER_SCRIPT" "$@"
|
||||
|
||||
@@ -6,6 +6,8 @@ MASCARADE_DIR="${MASCARADE_DIR:-$ROOT_DIR/../mascarade}"
|
||||
KICAD_KIC_AI_DIR="${KICAD_KIC_AI_DIR:-$MASCARADE_DIR/finetune/kicad_kic_ai}"
|
||||
PYTHON_BIN="${NEXAR_MCP_PYTHON:-}"
|
||||
TOKEN_CONFIGURED="no"
|
||||
source "$ROOT_DIR/tools/lib/runtime_home.sh"
|
||||
kill_life_runtime_home_init "$ROOT_DIR" "nexar-mcp"
|
||||
|
||||
if [ -n "${NEXAR_TOKEN:-${NEXAR_API_KEY:-}}" ]; then
|
||||
TOKEN_CONFIGURED="yes"
|
||||
@@ -27,6 +29,9 @@ KICAD_KIC_AI_DIR=$KICAD_KIC_AI_DIR
|
||||
NEXAR_MCP_PYTHON=$PYTHON_BIN
|
||||
SERVER_MODULE=mcp_servers.nexar
|
||||
NEXAR_TOKEN_CONFIGURED=$TOKEN_CONFIGURED
|
||||
HOME=$RUNTIME_HOME
|
||||
XDG_CONFIG_HOME=$XDG_CONFIG_HOME
|
||||
XDG_CACHE_HOME=$XDG_CACHE_HOME
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
@@ -41,6 +46,7 @@ fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
kill_life_runtime_home_ensure
|
||||
if [ -n "${PYTHONPATH:-}" ]; then
|
||||
export PYTHONPATH="$KICAD_KIC_AI_DIR:$PYTHONPATH"
|
||||
else
|
||||
|
||||
@@ -4,6 +4,8 @@ set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MASCARADE_DIR="${MASCARADE_DIR:-$ROOT_DIR/../mascarade}"
|
||||
SERVER_SCRIPT="$ROOT_DIR/tools/notion_mcp.py"
|
||||
source "$ROOT_DIR/tools/lib/runtime_home.sh"
|
||||
kill_life_runtime_home_init "$ROOT_DIR" "notion-mcp"
|
||||
|
||||
detect_core_python() {
|
||||
local candidate="${MASCARADE_CORE_PYTHON:-}"
|
||||
@@ -36,6 +38,9 @@ ROOT_DIR=$ROOT_DIR
|
||||
MASCARADE_DIR=$MASCARADE_DIR
|
||||
CORE_PYTHON=$CORE_PYTHON
|
||||
SERVER_SCRIPT=$SERVER_SCRIPT
|
||||
HOME=$RUNTIME_HOME
|
||||
XDG_CONFIG_HOME=$XDG_CONFIG_HOME
|
||||
XDG_CACHE_HOME=$XDG_CACHE_HOME
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
@@ -45,5 +50,6 @@ fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
kill_life_runtime_home_ensure
|
||||
export PYTHONPATH="$MASCARADE_DIR/core${PYTHONPATH:+:$PYTHONPATH}"
|
||||
exec "$CORE_PYTHON" "$SERVER_SCRIPT" "$@"
|
||||
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VENV_DIR="${ROOT_DIR}/.venv"
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||
BOOTSTRAP=false
|
||||
SUITE="stable"
|
||||
LIST_ONLY=false
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash tools/test_python.sh [options]
|
||||
|
||||
Run the supported Kill_LIFE repo-local Python suites through the repo-local venv.
|
||||
|
||||
Options:
|
||||
--suite NAME stable | mcp | all (default: stable)
|
||||
--bootstrap Create/install the venv first if missing
|
||||
--python BIN Python interpreter forwarded to bootstrap if needed
|
||||
--venv-dir PATH Virtualenv directory to use (default: ./.venv)
|
||||
--list Print the covered commands and exit
|
||||
-h, --help Show this help
|
||||
|
||||
Suites:
|
||||
stable Repo-local unittest suite without companion runtimes.
|
||||
mcp MCP integration tests that exercise local launchers.
|
||||
all stable + mcp.
|
||||
EOF
|
||||
}
|
||||
|
||||
run_discover() {
|
||||
local start_dir="$1"
|
||||
local pattern="$2"
|
||||
(
|
||||
cd "${ROOT_DIR}"
|
||||
"${VENV_DIR}/bin/python" -m unittest discover -s "${start_dir}" -p "${pattern}"
|
||||
)
|
||||
}
|
||||
|
||||
print_suite() {
|
||||
case "$1" in
|
||||
stable)
|
||||
cat <<'EOF'
|
||||
stable:
|
||||
test/test_setup_repo_dry_run.py
|
||||
test/test_mcp_runtime_status.py
|
||||
test/test_openclaw_sanitizer.py
|
||||
test/test_apply_safe_patch.py
|
||||
test/test_validate_specs.py
|
||||
tools/hw/schops/tests/test_*.py
|
||||
EOF
|
||||
;;
|
||||
mcp)
|
||||
cat <<'EOF'
|
||||
mcp:
|
||||
test/test_notion_mcp.py
|
||||
test/test_github_dispatch_mcp.py
|
||||
test/test_nexar_mcp.py
|
||||
EOF
|
||||
;;
|
||||
all)
|
||||
print_suite stable
|
||||
print_suite mcp
|
||||
;;
|
||||
*)
|
||||
echo "Unknown suite: $1" >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--suite)
|
||||
shift
|
||||
[[ $# -gt 0 ]] || { echo "Missing value for --suite" >&2; usage >&2; exit 2; }
|
||||
SUITE="$1"
|
||||
;;
|
||||
--bootstrap)
|
||||
BOOTSTRAP=true
|
||||
;;
|
||||
--python)
|
||||
shift
|
||||
[[ $# -gt 0 ]] || { echo "Missing value for --python" >&2; usage >&2; exit 2; }
|
||||
PYTHON_BIN="$1"
|
||||
;;
|
||||
--venv-dir)
|
||||
shift
|
||||
[[ $# -gt 0 ]] || { echo "Missing value for --venv-dir" >&2; usage >&2; exit 2; }
|
||||
VENV_DIR="$1"
|
||||
;;
|
||||
--list)
|
||||
LIST_ONLY=true
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
case "${SUITE}" in
|
||||
stable|mcp|all)
|
||||
;;
|
||||
*)
|
||||
echo "Unknown suite: ${SUITE}" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${LIST_ONLY}" == true ]]; then
|
||||
print_suite "${SUITE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${BOOTSTRAP}" == true ]]; then
|
||||
bash "${ROOT_DIR}/tools/bootstrap_python_env.sh" --python "${PYTHON_BIN}" --venv-dir "${VENV_DIR}"
|
||||
elif [[ ! -x "${VENV_DIR}/bin/python" ]]; then
|
||||
echo "Missing ${VENV_DIR}. Run: bash tools/bootstrap_python_env.sh --venv-dir ${VENV_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export PYTHONPATH="${ROOT_DIR}${PYTHONPATH:+:${PYTHONPATH}}"
|
||||
|
||||
if [[ "${SUITE}" == "stable" || "${SUITE}" == "all" ]]; then
|
||||
run_discover test 'test_setup_repo_dry_run.py'
|
||||
run_discover test 'test_mcp_runtime_status.py'
|
||||
run_discover test 'test_openclaw_sanitizer.py'
|
||||
run_discover test 'test_apply_safe_patch.py'
|
||||
run_discover test 'test_validate_specs.py'
|
||||
run_discover tools/hw/schops/tests 'test_*.py'
|
||||
fi
|
||||
|
||||
if [[ "${SUITE}" == "mcp" || "${SUITE}" == "all" ]]; then
|
||||
run_discover test 'test_notion_mcp.py'
|
||||
run_discover test 'test_github_dispatch_mcp.py'
|
||||
run_discover test 'test_nexar_mcp.py'
|
||||
fi
|
||||
+27
-4
@@ -100,7 +100,9 @@ def compare_spec_mirror() -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def validate_specs(strict: bool = False) -> Dict[str, Any]:
|
||||
def validate_specs(
|
||||
strict: bool = False, require_mirror_sync: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
required_files = [
|
||||
"specs/03_plan.md",
|
||||
"specs/04_tasks.md",
|
||||
@@ -116,11 +118,17 @@ def validate_specs(strict: bool = False) -> Dict[str, Any]:
|
||||
rfc2119 = scan_rfc2119()
|
||||
mirror_sync = compare_spec_mirror()
|
||||
|
||||
ok = not missing_files and compliance["ok"] and rfc2119["ok"] and mirror_sync["ok"]
|
||||
ok = (
|
||||
not missing_files
|
||||
and compliance["ok"]
|
||||
and rfc2119["ok"]
|
||||
and (mirror_sync["ok"] or not require_mirror_sync)
|
||||
)
|
||||
return {
|
||||
"ok": ok,
|
||||
"missing_files": missing_files,
|
||||
"strict": strict,
|
||||
"require_mirror_sync": require_mirror_sync,
|
||||
"compliance": compliance,
|
||||
"rfc2119": rfc2119,
|
||||
"mirror_sync": mirror_sync,
|
||||
@@ -169,7 +177,10 @@ def format_cli_summary(result: Dict[str, Any]) -> str:
|
||||
|
||||
def tool_validate_specs(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
||||
strict = bool(arguments.get("strict", False))
|
||||
result = validate_specs(strict=strict)
|
||||
require_mirror_sync = bool(arguments.get("require_mirror_sync", False))
|
||||
result = validate_specs(
|
||||
strict=strict, require_mirror_sync=require_mirror_sync
|
||||
)
|
||||
return {
|
||||
"content": [{"type": "text", "text": format_cli_summary(result)}],
|
||||
"structuredContent": result,
|
||||
@@ -204,6 +215,11 @@ TOOLS = [
|
||||
"type": "boolean",
|
||||
"description": "Enable strict evidence checks from tools/compliance/validate.py.",
|
||||
"default": False,
|
||||
},
|
||||
"require_mirror_sync": {
|
||||
"type": "boolean",
|
||||
"description": "Fail if ai-agentic-embedded-base/specs is not synced with specs/.",
|
||||
"default": False,
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -312,6 +328,11 @@ def serve_mcp() -> int:
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Validate Kill_LIFE specs")
|
||||
parser.add_argument("--strict", action="store_true", help="Enable strict compliance evidence validation.")
|
||||
parser.add_argument(
|
||||
"--require-mirror-sync",
|
||||
action="store_true",
|
||||
help="Fail if ai-agentic-embedded-base/specs is not synchronized with specs/.",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Print JSON output in CLI mode.")
|
||||
parser.add_argument("--mcp", action="store_true", help="Run as an MCP stdio server.")
|
||||
return parser.parse_args()
|
||||
@@ -322,7 +343,9 @@ def main() -> int:
|
||||
if args.mcp:
|
||||
return serve_mcp()
|
||||
|
||||
result = validate_specs(strict=args.strict)
|
||||
result = validate_specs(
|
||||
strict=args.strict, require_mirror_sync=args.require_mirror_sync
|
||||
)
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
"checklist": [
|
||||
"Release notes drafted",
|
||||
"Evidence reviewed",
|
||||
"Branch protections green"
|
||||
"Branch protections green",
|
||||
"Release tag chosen"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -87,7 +88,8 @@
|
||||
"workflow_file": "release_signing.yml",
|
||||
"ref": "main",
|
||||
"inputs": {
|
||||
"source": "mascarade-kill-life-editor"
|
||||
"source": "crazy-life-kill-life-editor",
|
||||
"release_tag": "v0.0.0-manual"
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
"checklist": [
|
||||
"Release notes drafted",
|
||||
"Evidence reviewed",
|
||||
"Branch protections green"
|
||||
"Branch protections green",
|
||||
"Release tag chosen"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -87,7 +88,8 @@
|
||||
"workflow_file": "release_signing.yml",
|
||||
"ref": "main",
|
||||
"inputs": {
|
||||
"source": "crazy-life-kill-life-editor"
|
||||
"source": "crazy-life-kill-life-editor",
|
||||
"release_tag": "v0.0.0-manual"
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
|
||||
Reference in New Issue
Block a user