feat(mistral): introduce Mistral integration for safe patch generation and indexing
- Added new tools for generating and applying safe patches using Mistral. - Implemented a local embedding index for repository files to facilitate search functionality. - Created a schema for validating patch JSON structure. - Developed various prompts for different agent roles (Architect, PM, QA, etc.) to guide Mistral's output. - Established a set of scope allowlists to ensure safe application of patches. - Included documentation for agents and systems correspondence. - Added requirements for Mistral dependencies in requirements-mistral.txt.
@@ -0,0 +1,75 @@
|
||||
|
||||
# Fichiers et dossiers à ignorer pour Kill_LIFE
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# PlatformIO
|
||||
.pio/
|
||||
.pioenvs/
|
||||
.build/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
.history/
|
||||
|
||||
# MacOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Logs et artefacts
|
||||
*.log
|
||||
artifacts/
|
||||
|
||||
# Fichiers générés ou temporaires
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*.swo
|
||||
*.orig
|
||||
|
||||
# Documentation générée
|
||||
site/
|
||||
|
||||
# Assets prompts (spécifique projet)
|
||||
KIKIFOU/assets_prompts.md
|
||||
|
||||
# Secrets (à adapter si besoin)
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Node/npm (si utilisé)
|
||||
node_modules/
|
||||
|
||||
# Docker
|
||||
*.pid
|
||||
docker-compose.override.yml
|
||||
|
||||
# Autres outils CI/CD
|
||||
coverage/
|
||||
*.cover
|
||||
*.coverage
|
||||
|
||||
# Fichiers de patchs temporaires
|
||||
*.patch
|
||||
*.rej
|
||||
|
||||
# Fichiers de configuration utilisateur
|
||||
*.user
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# Fichiers de sauvegarde d'éditeurs
|
||||
*~
|
||||
|
||||
# Fichiers de test
|
||||
firmware/test/.pioenvs/
|
||||
firmware/test/.pio/
|
||||
@@ -1,6 +1,6 @@
|
||||
# Kill_LIFE 🚀 — AI-Native Embedded Project Template
|
||||
|
||||

|
||||

|
||||
|
||||
Bienvenue dans **Kill_LIFE**, le template open source pour systèmes embarqués IA, où les specs rêvent d’électron-ironique, les agents QA se prennent pour des réplicants (voir [Le Réplicant](https://fr.wikipedia.org/wiki/Blade_Runner)), et le pipeline ne panique jamais grâce à sa serviette (voir [Don’t Panic](https://hitchhikers.fandom.com/wiki/Don%27t_Panic)), même en cas d’apocalypse. Ici, chaque commit est validé, chaque gate est passé, et chaque evidence pack est rangé comme dans [le meilleur des mondes](https://fr.wikipedia.org/wiki/Le_Meilleur_des_mondes). Si tu cherches la réponse à la question ultime du développement embarqué IA, c’est probablement [42](https://fr.wikipedia.org/wiki/42_(nombre))… ou un bulk edit réussi ([Bulk Edit Party FX](https://www.youtube.com/watch?v=dQw4w9WgXcQ)).
|
||||
|
||||
@@ -265,15 +265,21 @@ A : Ajoute des profils, améliore les scripts, enrichis les standards, et suis l
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🥚 Easter Eggs Kill_LIFE
|
||||
|
||||
- **Ne panique jamais** :  [Playlist apocalypse](https://www.youtube.com/playlist?list=PLApocalypse42)
|
||||
- **Bulk Edit Party** : [Fête technique](https://www.youtube.com/watch?v=dQw4w9WgXcQ) — robots IA dansant sur du code PCB.
|
||||
- **Citation dystopique** : _« J’ai vu des evidence packs briller dans l’obscurité près des gates S1… »_
|
||||
- **Badge 42** :  — clique pour une blague mathématique.
|
||||
- **Badge 42** :  — clique pour une blague mathématique.
|
||||
- **Mini-jeu Gate Runner** : [Gate Runner](https://gate-runner.example.com) — passe les gates, évite les bugs.
|
||||
- **Le meilleur des mondes** : Générateur de phrases dystopiques pour motiver les contributeurs.
|
||||
- **OpenClaw interactif** :  — change de couleur selon le statut CI/CD.
|
||||
- **OpenClaw interactif** :
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- **Spec Generator FX** : [Spec aléatoire](https://spec-generator.example.com) — science-fiction automatisée.
|
||||
- **Sanitizer Challenge** : Trouve la phrase supprimée par le sanitizer, score affiché.
|
||||
- **Agent QA Replicant** : _« Un evidence pack peut-il rêver de conformité ? »_
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Cartographie agentique Kill_LIFE
|
||||
|
||||
Ce diagramme illustre les interactions entre les agents, les artefacts et les evidence packs du projet.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Architect[Architect Agent]
|
||||
Doc[Doc Agent]
|
||||
Firmware[Firmware Agent]
|
||||
HW[HW Schematic Agent]
|
||||
PM[PM Agent]
|
||||
QA[QA Agent]
|
||||
Specs[specs/]
|
||||
Docs[docs/]
|
||||
FirmwareDir[firmware/]
|
||||
HardwareDir[hardware/]
|
||||
Compliance[compliance/]
|
||||
Evidence[evidence packs]
|
||||
|
||||
Architect --> Specs
|
||||
Architect --> PM
|
||||
Architect --> HW
|
||||
Architect --> Firmware
|
||||
Architect --> Doc
|
||||
PM --> Architect
|
||||
PM --> HW
|
||||
PM --> Firmware
|
||||
PM --> QA
|
||||
PM --> Doc
|
||||
HW --> HardwareDir
|
||||
HW --> Compliance
|
||||
HW --> Evidence
|
||||
HW --> Architect
|
||||
HW --> PM
|
||||
Firmware --> FirmwareDir
|
||||
Firmware --> Specs
|
||||
Firmware --> QA
|
||||
Firmware --> PM
|
||||
QA --> Evidence
|
||||
QA --> Compliance
|
||||
QA --> Firmware
|
||||
QA --> HW
|
||||
QA --> PM
|
||||
Doc --> Docs
|
||||
Doc --> Architect
|
||||
Doc --> PM
|
||||
Doc --> QA
|
||||
|
||||
Compliance --> Evidence
|
||||
Compliance --> HW
|
||||
Compliance --> QA
|
||||
|
||||
Evidence --> QA
|
||||
Evidence --> Compliance
|
||||
Evidence --> HW
|
||||
```
|
||||
|
||||
> Diagramme généré automatiquement (GPT-4.1)
|
||||
@@ -0,0 +1,25 @@
|
||||
# VS Code Continue + Mistral (Codestral)
|
||||
|
||||
Continue supports Mistral as a model provider.
|
||||
|
||||
## Config (YAML)
|
||||
Create `~/.continue/config.yaml`:
|
||||
|
||||
```yaml
|
||||
name: My Config
|
||||
version: 0.0.1
|
||||
schema: v1
|
||||
models:
|
||||
- name: Codestral
|
||||
provider: mistral
|
||||
model: codestral-latest
|
||||
apiKey: <YOUR_MISTRAL_API_KEY>
|
||||
apiBase: https://codestral.mistral.ai/v1
|
||||
roles:
|
||||
- chat
|
||||
- autocomplete
|
||||
```
|
||||
|
||||
## Important
|
||||
Continue notes that the API key for `codestral.mistral.ai` is different from `api.mistral.ai`
|
||||
and you should set `apiBase` accordingly.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Mistral integration pack
|
||||
|
||||
This template can use Mistral for:
|
||||
- **Code**: Codestral / Devstral
|
||||
- **Specs/Docs**: Mistral Small/Medium/Large
|
||||
- **RAG**: `mistral-embed` embeddings
|
||||
|
||||
## Keys & endpoints
|
||||
Set:
|
||||
- `MISTRAL_API_KEY` (required)
|
||||
- `MISTRAL_API_BASE` (optional)
|
||||
|
||||
Some setups use a different base URL for Codestral. See `docs/CONTINUE_SETUP.md`.
|
||||
|
||||
## Recommended models by role
|
||||
- PM/Doc: `mistral-small-latest` or `mistral-medium-latest`
|
||||
- Architect: `mistral-large-latest`
|
||||
- Firmware/Implementation: `codestral-latest`
|
||||
- QA (tests/refactors): `devstral-*` or `codestral-latest`
|
||||
|
||||
## Safe-output approach (critical)
|
||||
This pack uses **JSON-mode structured patches**:
|
||||
1) generate `SafePatch` JSON (no repo writes)
|
||||
2) validate schema + scope allowlists
|
||||
3) apply patch (writes files only; never runs commands)
|
||||
|
||||
See `docs/MISTRAL_AGENTIC.md`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Mistral Agentic Workflow (Safe Outputs)
|
||||
|
||||
## Goal
|
||||
Use Mistral models for agentic tasks **without giving the model unsafe agency**.
|
||||
|
||||
## Pattern used
|
||||
1) Sanitize untrusted inputs (issues/comments/logs)
|
||||
2) Ask the model for a structured **SafePatch JSON**
|
||||
3) Validate JSON against a schema
|
||||
4) Apply changes with scope allowlists + deny globs
|
||||
5) Verify manually/CI (never auto-execute model output)
|
||||
|
||||
## Why JSON mode
|
||||
Mistral supports JSON mode by setting `response_format={"type":"json_object"}` which enforces valid JSON.
|
||||
|
||||
## Commands
|
||||
Generate:
|
||||
```bash
|
||||
python tools/mistral/mistral_generate_patch.py --model codestral-latest --scope ai:spec --request "..." --out /tmp/patch.json
|
||||
```
|
||||
|
||||
Apply:
|
||||
```bash
|
||||
python tools/mistral/apply_safe_patch.py --scope ai:spec --patch /tmp/patch.json
|
||||
```
|
||||
|
||||
## Tool calling (optional)
|
||||
Mistral supports function calling. If you use it:
|
||||
- keep tools read-only by default
|
||||
- allow writes only through SafePatch
|
||||
- never expose arbitrary shell execution as a tool
|
||||
@@ -0,0 +1,17 @@
|
||||
# Mistral RAG (local index)
|
||||
|
||||
This pack includes a simple local semantic index using Mistral embeddings.
|
||||
|
||||
## Build the index
|
||||
```bash
|
||||
python tools/mistral/index_repo.py
|
||||
```
|
||||
|
||||
## Search
|
||||
```bash
|
||||
python tools/mistral/search_index.py --query "scope guard" --topk 5
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Minimal numpy index (cosine similarity).
|
||||
- For large repos, consider a vector DB later.
|
||||
|
After Width: | Height: | Size: 460 KiB |
|
After Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 6.1 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 556 KiB |
|
After Width: | Height: | Size: 694 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 660 KiB |
|
After Width: | Height: | Size: 653 KiB |
|
After Width: | Height: | Size: 677 KiB |
|
After Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 641 KiB |
|
After Width: | Height: | Size: 493 KiB |
|
After Width: | Height: | Size: 475 KiB |
@@ -0,0 +1,27 @@
|
||||
# Correspondance agents & systèmes – Kill_LIFE
|
||||
|
||||
## Agents humains
|
||||
- Définis dans agents/, ai-agentic-embedded-base/agents/, .github/agents/
|
||||
- Rôles : Architect, Doc, Firmware, HW Schematic, PM, QA
|
||||
- Artefacts : schémas, plans, tests, documentation, evidence packs
|
||||
|
||||
## Agents AI & systèmes
|
||||
- **Copilot** : audit, documentation, validation automatisée (instructions dans .github/copilot-instructions.md, prompts dans tools/mistral/prompts/)
|
||||
- **Codex** : génération de code, automatisation (tools/ai/compose_codex_prompt.py, prompts codex)
|
||||
- **OpenAI** : génération de texte, evidence packs, validation specs (prompts QA, Doc, Architect)
|
||||
- **Mistral** : prompts firmware, QA, documentation, orchestration, evidence packs (requirements-mistral.txt, tools/mistral/prompts/)
|
||||
|
||||
## Correspondance fichiers/dossiers
|
||||
- agents/, ai-agentic-embedded-base/agents/, .github/agents/ : rôles humains
|
||||
- tools/mistral/prompts/, tools/ai/, .github/prompts/ : prompts/scripts pour agents AI
|
||||
- docs/assets/rapport/, SYNTHESE_AGENTIQUE.md : synthèse, rapport, diagramme, audit
|
||||
- firmware/, hardware/, compliance/, specs/ : artefacts produits/consommés
|
||||
|
||||
## Interactions
|
||||
- Agents humains : orchestration, validation, documentation
|
||||
- Agents AI : automatisation, génération, evidence packs
|
||||
- Systèmes intégrés via prompts/scripts pour traçabilité, conformité, automatisation
|
||||
|
||||
---
|
||||
|
||||
> Synthèse générée automatiquement (GPT-4.1)
|
||||
@@ -0,0 +1,4 @@
|
||||
mistralai>=1.0.0
|
||||
jsonschema>=4.21.0
|
||||
pydantic>=2.6.0
|
||||
numpy>=1.26.0
|
||||
@@ -0,0 +1,15 @@
|
||||
# Mistral pack helper
|
||||
venv:
|
||||
python -m venv .venv && . .venv/bin/activate && pip install -r requirements-mistral.txt
|
||||
|
||||
patch-spec:
|
||||
python tools/mistral/mistral_generate_patch.py --model codestral-latest --scope ai:spec --request "$(REQ)" --out /tmp/patch.json
|
||||
|
||||
apply-spec:
|
||||
python tools/mistral/apply_safe_patch.py --scope ai:spec --patch /tmp/patch.json
|
||||
|
||||
index:
|
||||
python tools/mistral/index_repo.py
|
||||
|
||||
search:
|
||||
python tools/mistral/search_index.py --query "$(Q)" --topk 5
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Apply a SafePatch JSON to the repo with scope allowlists + deny globs.
|
||||
|
||||
This script never runs shell commands. It only writes files.
|
||||
|
||||
Example:
|
||||
python tools/mistral/apply_safe_patch.py --scope ai:spec --patch /tmp/patch.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
from tools.mistral.scope_allowlists import is_path_allowed, explain_scope
|
||||
|
||||
SCHEMA_PATH = Path(__file__).parent / "schemas" / "safe_patch.schema.json"
|
||||
|
||||
|
||||
def safe_write(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--scope", required=True, help="ai:spec|ai:plan|ai:tasks|ai:impl|ai:qa|ai:docs")
|
||||
ap.add_argument("--patch", required=True)
|
||||
ap.add_argument("--allow-delete", action="store_true")
|
||||
ap.add_argument("--root", default=".", help="repo root (default: .)")
|
||||
args = ap.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
patch_path = Path(args.patch)
|
||||
patch = json.loads(patch_path.read_text(encoding="utf-8"))
|
||||
|
||||
v = Draft202012Validator(schema)
|
||||
errors = sorted(v.iter_errors(patch), key=lambda e: e.path)
|
||||
if errors:
|
||||
msg = "\n".join([f"- {list(e.path)}: {e.message}" for e in errors])
|
||||
raise SystemExit(f"Patch JSON does not match schema:\n{msg}")
|
||||
|
||||
edits = patch.get("edits", [])
|
||||
blocked = []
|
||||
for e in edits:
|
||||
p = e["path"].replace("\\", "/").lstrip("/")
|
||||
if not is_path_allowed(args.scope, p):
|
||||
blocked.append(p)
|
||||
|
||||
if blocked:
|
||||
raise SystemExit(
|
||||
"Blocked paths for scope "
|
||||
+ args.scope
|
||||
+ ":\n - "
|
||||
+ "\n - ".join(blocked)
|
||||
+ "\n\n"
|
||||
+ explain_scope(args.scope)
|
||||
)
|
||||
|
||||
applied = []
|
||||
for e in edits:
|
||||
rel = e["path"].replace("\\", "/").lstrip("/")
|
||||
action = e["action"]
|
||||
dst = root / rel
|
||||
|
||||
if action in ("create", "update"):
|
||||
safe_write(dst, e.get("content", ""))
|
||||
applied.append(f"{action}: {rel}")
|
||||
elif action == "delete":
|
||||
if not args.allow_delete:
|
||||
raise SystemExit(f"Refusing to delete {rel} (pass --allow-delete)")
|
||||
if dst.exists():
|
||||
dst.unlink()
|
||||
applied.append(f"delete: {rel}")
|
||||
else:
|
||||
raise SystemExit(f"Unknown action: {action}")
|
||||
|
||||
print("Applied edits:")
|
||||
for a in applied:
|
||||
print(" -", a)
|
||||
|
||||
if patch.get("commands"):
|
||||
print("\nSuggested verify commands (NOT executed):")
|
||||
for c in patch["commands"]:
|
||||
print(" -", c)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create a simple local embedding index for this repo (specs/docs by default)
|
||||
using Mistral embeddings.
|
||||
|
||||
Output:
|
||||
.mistral_index/
|
||||
meta.jsonl
|
||||
vectors.npy
|
||||
|
||||
Search:
|
||||
python tools/mistral/search_index.py --query "scope guard" --topk 5
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
|
||||
from tools.mistral.mistral_client import get_client
|
||||
|
||||
DEFAULT_GLOBS = ["specs/**/*.md", "docs/**/*.md", "README.md"]
|
||||
|
||||
|
||||
def iter_files(root: Path, globs: List[str]) -> List[Path]:
|
||||
out: List[Path] = []
|
||||
for g in globs:
|
||||
out.extend(root.glob(g))
|
||||
out = sorted({p for p in out if p.is_file()})
|
||||
return out
|
||||
|
||||
|
||||
def chunk_text(text: str, max_chars: int = 3000) -> List[str]:
|
||||
chunks = []
|
||||
i = 0
|
||||
while i < len(text):
|
||||
chunks.append(text[i:i + max_chars])
|
||||
i += max_chars
|
||||
return chunks
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--root", default=".")
|
||||
ap.add_argument("--model", default="mistral-embed")
|
||||
ap.add_argument("--globs", nargs="*", default=DEFAULT_GLOBS)
|
||||
ap.add_argument("--out", default=".mistral_index")
|
||||
args = ap.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
out_dir = root / args.out
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
files = iter_files(root, args.globs)
|
||||
if not files:
|
||||
raise SystemExit("No files matched.")
|
||||
|
||||
client = get_client()
|
||||
meta_path = out_dir / "meta.jsonl"
|
||||
vec_path = out_dir / "vectors.npy"
|
||||
|
||||
metas = []
|
||||
vectors = []
|
||||
|
||||
for p in files:
|
||||
rel = str(p.relative_to(root)).replace("\\", "/")
|
||||
text = p.read_text(encoding="utf-8", errors="ignore")
|
||||
for idx, chunk in enumerate(chunk_text(text)):
|
||||
metas.append({"path": rel, "chunk": idx, "chars": len(chunk)})
|
||||
resp = client.embeddings.create(model=args.model, inputs=[chunk])
|
||||
vectors.append(resp.data[0].embedding)
|
||||
|
||||
np.save(vec_path, np.array(vectors, dtype=np.float32))
|
||||
with meta_path.open("w", encoding="utf-8") as f:
|
||||
for m in metas:
|
||||
f.write(json.dumps(m, ensure_ascii=False) + "\n")
|
||||
|
||||
print(f"Indexed {len(metas)} chunks from {len(files)} files into {out_dir}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Minimal Mistral client wrapper for safe-output patch generation.
|
||||
|
||||
- Uses official `mistralai` SDK.
|
||||
- Supports JSON Mode (`response_format={"type":"json_object"}`) to enforce valid JSON.
|
||||
- Optional API base override (Codestral uses a different API base in some setups).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from mistralai import Mistral
|
||||
|
||||
|
||||
def get_client() -> Mistral:
|
||||
api_key = os.environ.get("MISTRAL_API_KEY")
|
||||
if not api_key:
|
||||
raise RuntimeError("MISTRAL_API_KEY is not set.")
|
||||
|
||||
api_base = os.environ.get("MISTRAL_API_BASE") # e.g. https://codestral.mistral.ai/v1
|
||||
# SDK supports server_url in recent versions; we pass it if available.
|
||||
try:
|
||||
if api_base:
|
||||
return Mistral(api_key=api_key, server_url=api_base)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return Mistral(api_key=api_key)
|
||||
|
||||
|
||||
def chat_json(
|
||||
*,
|
||||
model: str,
|
||||
system: str,
|
||||
user: str,
|
||||
temperature: float = 0.0,
|
||||
max_tokens: int = 4096,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Request JSON mode output. Returns parsed JSON dict.
|
||||
|
||||
The SDK returns `choices[0].message.content` as a *stringified JSON object* in JSON mode.
|
||||
"""
|
||||
import json
|
||||
|
||||
client = get_client()
|
||||
resp = client.chat.complete(
|
||||
model=model,
|
||||
messages=[
|
||||
{"role": "system", "content": system},
|
||||
{"role": "user", "content": user},
|
||||
],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
)
|
||||
|
||||
content = resp.choices[0].message.content
|
||||
if isinstance(content, dict):
|
||||
return content
|
||||
if not isinstance(content, str):
|
||||
raise RuntimeError(f"Unexpected content type: {type(content)}")
|
||||
|
||||
try:
|
||||
return json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
raise RuntimeError(f"Model did not return valid JSON: {e}\nRaw:\n{content}") from e
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate a structured JSON "SafePatch" using Mistral (JSON mode),
|
||||
without touching repository files.
|
||||
|
||||
Example:
|
||||
python tools/mistral/mistral_generate_patch.py \
|
||||
--model codestral-latest \
|
||||
--scope ai:spec \
|
||||
--request "Draft RFC2119 spec for Feature X" \
|
||||
--out /tmp/patch.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
from tools.mistral.mistral_client import chat_json
|
||||
from tools.mistral.scope_allowlists import explain_scope
|
||||
|
||||
SCHEMA_PATH = Path(__file__).parent / "schemas" / "safe_patch.schema.json"
|
||||
|
||||
|
||||
SYSTEM_TEMPLATE = """You are a SAFE PATCH GENERATOR for a software repository.
|
||||
|
||||
Rules:
|
||||
- Output MUST be a single JSON object matching the provided schema.
|
||||
- NEVER include markdown, code fences, prose outside JSON.
|
||||
- Only propose file edits within the allowed scope; if a request would require out-of-scope edits,
|
||||
provide an in-scope alternative and add a note.
|
||||
- Do NOT include changes to CI workflows or security boundaries.
|
||||
- Do NOT include secrets.
|
||||
- Use minimal diffs: create/update only what is necessary.
|
||||
|
||||
Allowed scope:
|
||||
{scope_expl}
|
||||
|
||||
Schema:
|
||||
{schema}
|
||||
"""
|
||||
|
||||
USER_TEMPLATE = """Request:
|
||||
{request}
|
||||
|
||||
Context notes (optional):
|
||||
{context}
|
||||
"""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--model", default="codestral-latest")
|
||||
ap.add_argument("--scope", required=True, help="ai:spec|ai:plan|ai:tasks|ai:impl|ai:qa|ai:docs")
|
||||
ap.add_argument("--request", required=True)
|
||||
ap.add_argument("--context", default="")
|
||||
ap.add_argument("--out", required=True)
|
||||
args = ap.parse_args()
|
||||
|
||||
schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
|
||||
system = SYSTEM_TEMPLATE.format(scope_expl=explain_scope(args.scope), schema=json.dumps(schema, indent=2))
|
||||
user = USER_TEMPLATE.format(request=args.request, context=args.context)
|
||||
|
||||
patch = chat_json(model=args.model, system=system, user=user, temperature=0.0, max_tokens=4096)
|
||||
|
||||
# Validate schema
|
||||
v = Draft202012Validator(schema)
|
||||
errors = sorted(v.iter_errors(patch), key=lambda e: e.path)
|
||||
if errors:
|
||||
msg = "\n".join([f"- {list(e.path)}: {e.message}" for e in errors])
|
||||
raise SystemExit(f"Patch JSON does not match schema:\n{msg}\n\nRaw patch:\n{json.dumps(patch, indent=2)}")
|
||||
|
||||
out = Path(args.out)
|
||||
out.write_text(json.dumps(patch, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
print(f"Wrote patch: {out}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1 @@
|
||||
You are an embedded systems architect. Provide block diagrams (text), interfaces, budgets (power/latency/memory), risks+mitigations, and verification plan.
|
||||
@@ -0,0 +1 @@
|
||||
You are documentation. Update docs and runbooks only. Keep instructions concise and actionable.
|
||||
@@ -0,0 +1 @@
|
||||
You are a firmware agent. Implement minimal changes, add tests, and keep diffs small. Never modify CI workflows. Prefer deterministic behavior.
|
||||
@@ -0,0 +1 @@
|
||||
You are a PM agent. Produce RFC2119-friendly requirements and acceptance criteria. Keep scope tight. Prefer small PRs and explicit assumptions.
|
||||
@@ -0,0 +1 @@
|
||||
You are QA. Generate edge cases, tests, and validation steps. Focus on reproducibility and evidence packs.
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "SafePatch",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"summary",
|
||||
"edits"
|
||||
],
|
||||
"properties": {
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"edits": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"path",
|
||||
"action"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"create",
|
||||
"update",
|
||||
"delete"
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"const": "delete"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"not": {
|
||||
"required": [
|
||||
"content"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"enum": [
|
||||
"create",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"content"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Suggested verification commands ONLY. Never executed automatically."
|
||||
},
|
||||
"notes": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Local scope allowlists for applying patches safely.
|
||||
|
||||
This is NOT a replacement for CI scope guards; it is a local "pre-flight" safety net.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
from typing import List
|
||||
|
||||
|
||||
DENY_GLOBS = [
|
||||
".git/**",
|
||||
".github/workflows/**",
|
||||
".github/actions/**",
|
||||
"**/.env",
|
||||
"**/*.key",
|
||||
"**/*secret*",
|
||||
"**/*token*",
|
||||
]
|
||||
|
||||
ALLOWED_BY_SCOPE = {
|
||||
"ai:spec": [
|
||||
"specs/**",
|
||||
"docs/**",
|
||||
"README.md",
|
||||
".github/copilot-instructions.md",
|
||||
".github/copilot/**",
|
||||
".github/prompts/**",
|
||||
],
|
||||
"ai:plan": [
|
||||
"specs/**",
|
||||
"docs/**",
|
||||
"README.md",
|
||||
],
|
||||
"ai:tasks": [
|
||||
"specs/**",
|
||||
"docs/**",
|
||||
],
|
||||
"ai:docs": [
|
||||
"docs/**",
|
||||
"README.md",
|
||||
".github/copilot-instructions.md",
|
||||
".github/copilot/**",
|
||||
".github/prompts/**",
|
||||
],
|
||||
"ai:impl": [
|
||||
"firmware/**",
|
||||
"docs/**",
|
||||
"specs/**",
|
||||
"tools/**",
|
||||
],
|
||||
"ai:qa": [
|
||||
"firmware/**",
|
||||
"tools/**",
|
||||
"docs/**",
|
||||
"specs/**",
|
||||
],
|
||||
}
|
||||
|
||||
def _matches_any(path: str, globs: List[str]) -> bool:
|
||||
return any(fnmatch.fnmatch(path, g) for g in globs)
|
||||
|
||||
def is_path_allowed(scope: str, path: str) -> bool:
|
||||
path = path.replace("\\", "/").lstrip("/")
|
||||
if _matches_any(path, DENY_GLOBS):
|
||||
return False
|
||||
allow = ALLOWED_BY_SCOPE.get(scope)
|
||||
if not allow:
|
||||
return False
|
||||
return _matches_any(path, allow)
|
||||
|
||||
def explain_scope(scope: str) -> str:
|
||||
allow = ALLOWED_BY_SCOPE.get(scope, [])
|
||||
return f"{scope} allows: " + ", ".join(allow)
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Search the local .mistral_index with cosine similarity.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from tools.mistral.mistral_client import get_client
|
||||
|
||||
|
||||
def cosine_topk(matrix: np.ndarray, q: np.ndarray, k: int) -> Tuple[List[int], np.ndarray]:
|
||||
m = matrix / (np.linalg.norm(matrix, axis=1, keepdims=True) + 1e-9)
|
||||
qn = q / (np.linalg.norm(q) + 1e-9)
|
||||
scores = m @ qn
|
||||
return list(np.argsort(-scores)[:k]), scores
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--query", required=True)
|
||||
ap.add_argument("--index", default=".mistral_index")
|
||||
ap.add_argument("--model", default="mistral-embed")
|
||||
ap.add_argument("--topk", type=int, default=5)
|
||||
args = ap.parse_args()
|
||||
|
||||
idx_dir = Path(args.index)
|
||||
meta_path = idx_dir / "meta.jsonl"
|
||||
vec_path = idx_dir / "vectors.npy"
|
||||
if not meta_path.exists() or not vec_path.exists():
|
||||
raise SystemExit("Index not found. Run: python tools/mistral/index_repo.py")
|
||||
|
||||
metas = [json.loads(line) for line in meta_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||
vecs = np.load(vec_path)
|
||||
|
||||
client = get_client()
|
||||
resp = client.embeddings.create(model=args.model, inputs=[args.query])
|
||||
q = np.array(resp.data[0].embedding, dtype=np.float32)
|
||||
|
||||
ids, scores = cosine_topk(vecs, q, args.topk)
|
||||
print("Top matches:")
|
||||
for i in ids:
|
||||
m = metas[i]
|
||||
print(f"- {m['path']} (chunk {m['chunk']}) score={float(scores[i]):.4f}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||