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.
This commit is contained in:
Clément SAILLANT
2026-02-19 04:40:13 +01:00
parent 87655f7ac1
commit ccbeb68937
38 changed files with 847 additions and 3 deletions
+75
View File
@@ -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/
+9 -3
View File
@@ -1,6 +1,6 @@
# Kill_LIFE 🚀 — AI-Native Embedded Project Template
![Kill_LIFE Banner](https://raw.githubusercontent.com/electron-rare/Kill_LIFE/main/docs/assets/banner_kill_life.png)
![Kill_LIFE Banner](docs/assets/banner_kill_life_generated.png)
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 [Dont Panic](https://hitchhikers.fandom.com/wiki/Don%27t_Panic)), même en cas dapocalypse. 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, cest 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** : ![Dont Panic](docs/assets/dont_panic_generated.png) [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** : _« Jai vu des evidence packs briller dans lobscurité près des gates S1… »_
- **Badge 42** : ![Badge 42](docs/assets/badge_42_generated.png) — clique pour une blague mathématique.
- **Badge 42** : ![Badge 42](docs/assets/badge_42_generated.gif) — 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** : ![OpenClaw](docs/assets/openclaw_interactif_generated.png) — change de couleur selon le statut CI/CD.
- **OpenClaw interactif** :
- ![OpenClaw CI/CD Success](docs/assets/openclaw_cicd_success.png)
- ![OpenClaw CI/CD Running](docs/assets/openclaw_cicd_running.png)
- ![OpenClaw CI/CD Error](docs/assets/openclaw_cicd_error.png)
- ![OpenClaw CI/CD Cancel](docs/assets/openclaw_cicd_cancel.png)
- ![OpenClaw CI/CD Inactive](docs/assets/openclaw_cicd_inactive.png)
- **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é ? »_
+58
View File
@@ -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)
+25
View File
@@ -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.
+27
View File
@@ -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`.
+31
View File
@@ -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
+17
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

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)
+4
View File
@@ -0,0 +1,4 @@
mistralai>=1.0.0
jsonschema>=4.21.0
pydantic>=2.6.0
numpy>=1.26.0
+15
View File
@@ -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
View File
+96
View File
@@ -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())
+86
View File
@@ -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())
+68
View File
@@ -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
+82
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
You are documentation. Update docs and runbooks only. Keep instructions concise and actionable.
+1
View File
@@ -0,0 +1 @@
You are a firmware agent. Implement minimal changes, add tests, and keep diffs small. Never modify CI workflows. Prefer deterministic behavior.
+1
View File
@@ -0,0 +1 @@
You are a PM agent. Produce RFC2119-friendly requirements and acceptance criteria. Keep scope tight. Prefer small PRs and explicit assumptions.
+1
View File
@@ -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"
}
}
}
+75
View File
@@ -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)
+54
View File
@@ -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())