docs(cpm): translate all CPM project files to English
CPM is an international project; only the blogpost stays German. Translated: cpm.py docstring/messages/help texts, README.md, cpm-setup skill, all team-profile.yaml files (incl. runtime_agent comments), example apm.yml wrappers and the model-mapping template. Also fixes DEFAULT_MAPPING_PATH pointing to ~/.config/apm-team instead of ~/.config/cpm.
This commit is contained in:
parent
913be9d528
commit
0df9cec833
9 changed files with 341 additions and 347 deletions
|
|
@ -1,168 +1,168 @@
|
|||
# Corentic Package Manager (CPM)
|
||||
|
||||
Ein **CPM-Package** ist ein Agent-Package bestehend aus einem [APM](https://microsoft.github.io/apm/)-Manifest plus Team-Konfiguration (`team-profile.yaml`). Der Corentic Package Manager übersetzt dieses Package in die native Konfiguration eines Ziel-Harnesses.
|
||||
A **CPM package** is an agent package consisting of an [APM](https://microsoft.github.io/apm/) manifest plus a team configuration (`team-profile.yaml`). The Corentic Package Manager translates this package into the native configuration of a target harness.
|
||||
|
||||
Übersetzungsziele:
|
||||
Translation targets:
|
||||
|
||||
| Ziel-Harness | Status |
|
||||
| Target harness | Status |
|
||||
|---|---|
|
||||
| [oh-my-opencode-slim](https://ohmyopencodeslim.com/) | ✅ implementiert |
|
||||
| oh-my-pi | geplant |
|
||||
| Codex / Claude / Copilot | Vision |
|
||||
| [oh-my-opencode-slim](https://ohmyopencodeslim.com/) | ✅ implemented |
|
||||
| oh-my-pi | planned |
|
||||
| Codex / Claude / Copilot | vision |
|
||||
|
||||
Deterministisch – der CPM rät nicht. Er nimmt keine Verfügbarkeit an und wählt keine Modelle automatisch aus.
|
||||
Deterministic – the CPM does not guess. It assumes no availability and never picks models automatically.
|
||||
|
||||
```
|
||||
CPM-Package Nutzer Runtime
|
||||
CPM package User Runtime
|
||||
apm.yml + + model-mapping.yaml → cpm render → .opencode/
|
||||
team-profile.yaml (Modellklasse → oh-my-opencode-slim.json
|
||||
(Rollen, Modell- konkrete Modell-ID) (oder künftig: natives
|
||||
klassen, MCPs/Skills) Config eines anderen Harness)
|
||||
team-profile.yaml (model class → oh-my-opencode-slim.json
|
||||
(roles, model concrete model ID) (or later: native config
|
||||
classes, MCPs/skills) of another harness)
|
||||
```
|
||||
|
||||
## Voraussetzungen
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+ mit PyYAML (`pip install pyyaml`)
|
||||
- OpenCode mit installiertem Plugin [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim)
|
||||
- Ein CPM-Package mit `team-profile.yaml`
|
||||
- Python 3.10+ with PyYAML (`pip install pyyaml`)
|
||||
- OpenCode with the [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) plugin installed
|
||||
- A CPM package with a `team-profile.yaml`
|
||||
|
||||
## Schnellstart
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
# 1. Modell-Mapping anlegen – interaktiv, mit Auswahl aus deinen opencode-Modellen
|
||||
# 1. Create the model mapping – interactively, choosing from your opencode models
|
||||
python3 cpm.py init --fill
|
||||
|
||||
# 2. Geführt: Team-Profil lesen, Mapping prüfen/auffüllen, rendern
|
||||
cd <projekt>
|
||||
python3 /pfad/zu/cpm.py setup --package /pfad/zum/package
|
||||
# 2. Guided: read team profile, check/fill mapping, render
|
||||
cd <project>
|
||||
python3 /path/to/cpm.py setup --package /path/to/package
|
||||
|
||||
# 3. Aktivieren
|
||||
opencode # Projekt-Config wird geladen
|
||||
# 3. Activate
|
||||
opencode # project config gets loaded
|
||||
/preset acme-job-applications
|
||||
# Reload OpenCode → Team aktiv (by Design: kein Hot-Swap)
|
||||
# Reload OpenCode -> team is active (by design: no hot-swap)
|
||||
```
|
||||
|
||||
Alternativ manuell:
|
||||
Alternatively manually:
|
||||
|
||||
```bash
|
||||
# 1. Modell-Mapping einmalig anlegen
|
||||
# 1. Create the model mapping once
|
||||
mkdir -p ~/.config/cpm
|
||||
cp examples/model-mapping.yaml ~/.config/cpm/model-mapping.yaml
|
||||
$EDITOR ~/.config/cpm/model-mapping.yaml # an eigene Modelle anpassen
|
||||
$EDITOR ~/.config/cpm/model-mapping.yaml # adapt to your own models
|
||||
|
||||
# 2. Validieren (Dry-Run)
|
||||
# 2. Validate (dry run)
|
||||
python3 cpm.py check --package examples/package
|
||||
|
||||
# 3. Rendern
|
||||
cd <projekt>
|
||||
python3 /pfad/zu/cpm.py render --package /pfad/zum/package
|
||||
# 3. Render
|
||||
cd <project>
|
||||
python3 /path/to/cpm.py render --package /path/to/package
|
||||
```
|
||||
|
||||
## Die drei Dateien
|
||||
## The three files
|
||||
|
||||
| Datei | Gehört | Zweck | Manuell editieren? |
|
||||
| File | Owned by | Purpose | Edit manually? |
|
||||
|---|---|---|---|
|
||||
| `team-profile.yaml` | Package-Autor | Rollen, Modellklassen, MCP-/Skill-Allowlists pro Rolle | Ja (Autor) |
|
||||
| `~/.config/cpm/model-mapping.yaml` | Nutzer | Modellklasse → konkrete Modell-ID (+ optional variant/temperature) | Ja (Nutzer) |
|
||||
| `.opencode/oh-my-opencode-slim.json` | generiert | Preset mit konkreten Modell-IDs | **Nein** – immer neu rendern |
|
||||
| `team-profile.yaml` | Package author | Roles, model classes, MCP/skill allowlists per role | Yes (author) |
|
||||
| `~/.config/cpm/model-mapping.yaml` | User | Model class → concrete model ID (+ optional variant/temperature) | Yes (user) |
|
||||
| `.opencode/oh-my-opencode-slim.json` | generated | Preset with concrete model IDs | **No** – always re-render |
|
||||
|
||||
Dazu eine Provenance-Datei `.opencode/oh-my-opencode-slim.cpm-provenance.json`, die dokumentiert, welche Rolle welches Modell erhalten hat.
|
||||
Plus a provenance file `.opencode/oh-my-opencode-slim.cpm-provenance.json` documenting which role received which model.
|
||||
|
||||
## Kommandoreferenz
|
||||
## Command reference
|
||||
|
||||
```bash
|
||||
python3 cpm.py init [--mapping FILE] [--fill] # Mapping anlegen/füllen
|
||||
python3 cpm.py setup --package DIR [--mapping F] ... # geführter Ablauf
|
||||
python3 cpm.py init [--mapping FILE] [--fill] # create/fill mapping
|
||||
python3 cpm.py setup --package DIR [--mapping F] ... # guided flow
|
||||
python3 cpm.py render --package DIR [--mapping F] [--output F] [--dry-run]
|
||||
python3 cpm.py check --package DIR [--mapping F] # Dry-Run-Validierung
|
||||
python3 cpm.py check --package DIR [--mapping F] # dry-run validation
|
||||
```
|
||||
|
||||
### `init` – Mapping anlegen
|
||||
### `init` – create the mapping
|
||||
|
||||
Erstellt `~/.config/cpm/model-mapping.yaml` (falls nicht vorhanden). Mit `--fill` werden alle `PLACEHOLDER`-Klassen interaktiv abgefragt: der CPM listet alle Modelle aus deiner `opencode.json(c)` auf, du wählst per Nummer oder gibst eine Modell-ID manuell ein.
|
||||
Creates `~/.config/cpm/model-mapping.yaml` (if not present). With `--fill`, all `PLACEHOLDER` classes are asked interactively: the CPM lists every model from your `opencode.json(c)`, you pick by number or enter a model ID manually.
|
||||
|
||||
### `setup` – Geführter Ablauf
|
||||
### `setup` – guided flow
|
||||
|
||||
Der Ein-Kommando-Weg: zeigt das Team-Profil verständlich an, legt fehlendes Mapping an, fragt fehlende Modellklassen interaktiv ab (gleiche Auswahl wie `init --fill`), rendert und gibt die Aktivierungsschritte aus.
|
||||
The one-command path: shows the team profile in plain language, creates a missing mapping, interactively asks for missing model classes (same selection as `init --fill`), renders and prints the activation steps.
|
||||
|
||||
### `render` / `check`
|
||||
|
||||
| Option | Default | Bedeutung |
|
||||
| Option | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `--package` | `.` | CPM-Package-Verzeichnis mit `team-profile.yaml` |
|
||||
| `--mapping` | `~/.config/cpm/model-mapping.yaml` | Lokale Mapping-Tabelle |
|
||||
| `--output` | `<package>/.opencode/oh-my-opencode-slim.json` | Zieldatei |
|
||||
| `--team-file` | `team-profile.yaml` (alternativ `team.yaml`) | Alternativer Profilname |
|
||||
| `--package` | `.` | CPM package directory containing `team-profile.yaml` |
|
||||
| `--mapping` | `~/.config/cpm/model-mapping.yaml` | Local mapping table |
|
||||
| `--output` | `<package>/.opencode/oh-my-opencode-slim.json` | Target file |
|
||||
| `--team-file` | `team-profile.yaml` (alternatively `team.yaml`) | Alternative profile filename |
|
||||
|
||||
**Merge-Verhalten:** Existierende Presets in der Zieldatei bleiben unberührt; nur das Preset der Team-ID wird ersetzt. Mehrere Packages koexistieren damit im selben Projekt. Aktiviert wird explizit per `/preset <name>`.
|
||||
**Merge behavior:** Existing presets in the target file remain untouched; only the preset for this team ID is replaced. Multiple packages coexist in the same project. Activation is explicit via `/preset <name>`.
|
||||
|
||||
## Team-Profil-Schema (`corentic.team-profile/v1`)
|
||||
## Team profile schema (`corentic.team-profile/v1`)
|
||||
|
||||
```yaml
|
||||
schema: corentic.team-profile/v1
|
||||
id: acme.job-applications # wird zum Preset-Namen (namespaced)
|
||||
id: acme.job-applications # becomes the preset name (namespaced)
|
||||
description: ...
|
||||
|
||||
roles:
|
||||
- id: researcher # fachliche Rolle
|
||||
purpose: ... # wird zu Prompt/Description für Custom Agents
|
||||
runtime_agent: librarian # optional: Harness-Builtin oder 'custom'
|
||||
# (Default: role-id als Custom Agent)
|
||||
model_class: fast-research # Pflicht, muss im Mapping existieren
|
||||
- id: researcher # domain role
|
||||
purpose: ... # becomes prompt/description for custom agents
|
||||
runtime_agent: librarian # optional: harness builtin or 'custom'
|
||||
# (default: role id as custom agent)
|
||||
model_class: fast-research # required, must exist in the mapping
|
||||
capabilities:
|
||||
mcps: [websearch, openviking] # MCP-Allowlist ([] = keine)
|
||||
skills: [job-application] # Skill-Allowlist ([] = keine)
|
||||
mcps: [websearch, openviking] # MCP allowlist ([] = none)
|
||||
skills: [job-application] # skill allowlist ([] = none)
|
||||
```
|
||||
|
||||
Das Feld `runtime_agent` ist bewusst harness-neutral benannt: Der Adapter entscheidet, wie die Rolle im Ziel-Harness repräsentiert wird. Erkannte Builtins des oh-my-opencode-slim-Adapters: `orchestrator`, `oracle`, `librarian`, `explorer`, `fixer`, `designer`, `council`, `observer`. Alles andere (oder `runtime_agent: custom`) erzeugt einen Custom Agent inklusive `prompt` und `orchestratorPrompt` aus dem `purpose`-Feld.
|
||||
The `runtime_agent` field is deliberately harness-neutral: the adapter decides how the role is represented in the target harness. Builtins recognized by the oh-my-opencode-slim adapter: `orchestrator`, `oracle`, `librarian`, `explorer`, `fixer`, `designer`, `council`, `observer`. Everything else (or `runtime_agent: custom`) creates a custom agent including `prompt` and `orchestratorPrompt` derived from the `purpose` field.
|
||||
|
||||
### Mapping-Formate
|
||||
### Mapping formats
|
||||
|
||||
```yaml
|
||||
model_classes:
|
||||
fast-research: ollama/qwen3.5:9b # einfach
|
||||
high-reasoning: # erweitert
|
||||
fast-research: ollama/qwen3.5:9b # simple
|
||||
high-reasoning: # extended
|
||||
model: ollama/qwen3.6:35b-a3b-q4_K_M
|
||||
variant: thinking
|
||||
temperature: 0.3
|
||||
```
|
||||
|
||||
Alle Zusatzfelder werden 1:1 in den Agent-Eintrag des Presets übernommen.
|
||||
All extra fields are passed through 1:1 into the agent entry of the preset.
|
||||
|
||||
## Was der CPM prüft
|
||||
## What the CPM checks
|
||||
|
||||
1. Team-Profil vorhanden, Schema bekannt, `id` und `roles` vorhanden
|
||||
2. Jede `model_class` hat einen Mapping-Eintrag (sonst Fehler mit Lösungshinweis)
|
||||
3. MCPs gegen `~/.config/opencode/opencode.json(c)` abgleichen (**Warnung**, kein Abbruch)
|
||||
4. Rollen ohne MCPs/Skills → Hinweis
|
||||
1. Team profile present, schema known, `id` and `roles` present
|
||||
2. Every `model_class` has a mapping entry (otherwise error with fix hint)
|
||||
3. MCPs matched against `~/.config/opencode/opencode.json(c)` (**warning**, not fatal)
|
||||
4. Roles without MCPs/skills -> note
|
||||
|
||||
## Beispiele
|
||||
## Examples
|
||||
|
||||
| Beispiel | Basis | Team |
|
||||
| Example | Based on | Team |
|
||||
|---|---|---|
|
||||
| `examples/package/` | Eigenes `job-application`-Package (OpenViking + ShareLaTeX) | `acme.job-applications` – Researcher, Writer, Checker, Notifier |
|
||||
| `examples/microsoft-design-review/` | Offizielles [microsoft/apm-sample-package](https://github.com/microsoft/apm-sample-package) | `acme.design-review` – Reviewer (Oracle), Style-Checker (Fixer), Accessibility-Auditor |
|
||||
| `examples/microsoft-issue-autopilot/` | Offizielles [`apm-issue-autopilot`](https://github.com/microsoft/apm/tree/main/packages/apm-issue-autopilot) aus dem microsoft/apm-Repo | `acme.issue-autopilot` – Triager, Shepherd, PR-Writer, Reviewer (+ GitHub-MCP) |
|
||||
| `examples/package/` | Own `job-application` package (OpenViking + ShareLaTeX) | `acme.job-applications` – Researcher, Writer, Checker, Notifier |
|
||||
| `examples/microsoft-design-review/` | Official [microsoft/apm-sample-package](https://github.com/microsoft/apm-sample-package) | `acme.design-review` – Reviewer (Oracle), Style-Checker (Fixer), Accessibility-Auditor |
|
||||
| `examples/microsoft-issue-autopilot/` | Official [`apm-issue-autopilot`](https://github.com/microsoft/apm/tree/main/packages/apm-issue-autopilot) from the microsoft/apm repo | `acme.issue-autopilot` – Triager, Shepherd, PR-Writer, Reviewer (+ GitHub MCP) |
|
||||
|
||||
Die beiden Microsoft-Beispiele sind dünne CPM-Wrapper: Das eigene `apm.yml` zieht das offizielle Package als versionierte APM-Dependency (`microsoft/apm-sample-package#v1.0.0` bzw. Monorepo-Subpath `microsoft/apm/packages/apm-issue-autopilot`) und liefert nur die Team-Empfehlung dazu.
|
||||
The two Microsoft examples are thin CPM wrappers: their own `apm.yml` pulls the official package as a versioned APM dependency (`microsoft/apm-sample-package#v1.0.0` or monorepo subpath `microsoft/apm/packages/apm-issue-autopilot`) and only adds the team recommendation.
|
||||
|
||||
```bash
|
||||
cd corentic-package-manager/examples
|
||||
|
||||
# Trockenlauf: was würde generiert?
|
||||
# Dry run: what would be generated?
|
||||
python3 ../cpm.py check --package package --mapping model-mapping.yaml
|
||||
python3 ../cpm.py check --package microsoft-design-review --mapping model-mapping.yaml
|
||||
python3 ../cpm.py check --package microsoft-issue-autopilot --mapping model-mapping.yaml
|
||||
|
||||
# Rendern in das Beispiel-Projekt
|
||||
# Render into the example project
|
||||
python3 ../cpm.py render --package package \
|
||||
--mapping model-mapping.yaml \
|
||||
--output package/.opencode/oh-my-opencode-slim.json
|
||||
```
|
||||
|
||||
Erwartetes Ergebnis: Preset `acme-job-applications` mit fünf Agenten – `orchestrator`, `librarian` (Alias `researcher`), Custom Agent `writer`, `oracle` (Alias `checker`), Custom Agent `notifier`. Der Researcher erhält keinen LaTeX-Zugang, der Writer keine Job-Suchtools, der Notifier gar keine externen Zugriffe.
|
||||
Expected result: preset `acme-job-applications` with five agents – `orchestrator`, `librarian` (alias `researcher`), custom agent `writer`, `oracle` (alias `checker`), custom agent `notifier`. The researcher gets no LaTeX access, the writer sees no job-search tools, the notifier gets no external access at all.
|
||||
|
||||
Danach:
|
||||
Then:
|
||||
|
||||
```bash
|
||||
cd package && opencode
|
||||
|
|
@ -171,16 +171,16 @@ cd package && opencode
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Ursache/Lösung |
|
||||
| Problem | Cause/Fix |
|
||||
|---|---|
|
||||
| `Modellklasse X ist nicht gemappt` | Mapping ergänzen, erneut rendern |
|
||||
| `Warnung: MCP ... nicht konfiguriert` | MCP in `opencode.jsonc` einrichten oder aus dem Profil entfernen |
|
||||
| `/preset` zeigt nichts Neues | OpenCode nach dem Render neu laden; Preset greift nicht mid-session |
|
||||
| Preset weg nach manuellem Editieren | Datei ist generiert – Änderungen gehören ins Mapping oder Team-Profil |
|
||||
| YAML-Fehler | Zeilenangabe aus der Fehlermeldung folgen |
|
||||
| `Model class X is not mapped` | Add to the mapping, re-render |
|
||||
| `Warning: MCP ... not configured` | Set up the MCP in `opencode.jsonc` or remove it from the profile |
|
||||
| `/preset` does not show anything new | Reload OpenCode after rendering; presets do not apply mid-session |
|
||||
| Preset lost after manual editing | The file is generated – changes belong in the mapping or team profile |
|
||||
| YAML errors | Follow the line number in the error message |
|
||||
|
||||
## Grenzen (bewusst)
|
||||
## Deliberate limits
|
||||
|
||||
- Der CPM startet keine Agenten und orchestriert nichts zur Laufzeit – das macht der Ziel-Harness.
|
||||
- MCP-/Skill-Allowlists sind Capability Scoping, keine Sandbox. Irreversible Aktionen brauchen serverseitige Autorisierung + Human Approval.
|
||||
- Die Modellverfügbarkeit wird gegen die opencode.jsonc-Namen geprüft, nicht per API-Healthcheck.
|
||||
- The CPM does not start agents and orchestrates nothing at runtime – that is the job of the target harness.
|
||||
- MCP/skill allowlists are capability scoping, not a sandbox. Irreversible actions need server-side authorization + human approval.
|
||||
- Model availability is checked against opencode.json(c) names, not via API health checks.
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
"""cpm – Corentic Package Manager.
|
||||
|
||||
Ein CPM-Package ist ein Agent-Package bestehend aus einem APM-Manifest plus
|
||||
Team-Konfiguration (team-profile.yaml). Der CPM übersetzt dieses Package in
|
||||
die native Konfiguration eines Ziel-Harnesses. Erstes Übersetzungsziel:
|
||||
oh-my-opencode-slim. Weitere Ziele (oh-my-pi, codex, claude, copilot) sind
|
||||
geplant.
|
||||
A CPM package is an agent package consisting of an APM manifest plus a team
|
||||
configuration (team-profile.yaml). The CPM translates this package into the
|
||||
native configuration of a target harness. First translation target:
|
||||
oh-my-opencode-slim. Further targets (oh-my-pi, codex, claude, copilot) are
|
||||
planned.
|
||||
|
||||
Liest ein team-profile.yaml, löst Modellklassen gegen eine lokale
|
||||
Mapping-Tabelle auf und generiert ein namespacetes Preset für den
|
||||
Ziel-Harness.
|
||||
Reads a team-profile.yaml, resolves model classes against a local mapping
|
||||
table and generates a namespaced preset for the target harness.
|
||||
|
||||
Kommandos:
|
||||
cpm init Nutzer-Mapping anlegen (optional interaktiv füllen)
|
||||
cpm setup Geführt: Profil lesen, Mapping auffüllen, rendern
|
||||
cpm render Team-Profil in Harness-Preset übersetzen und schreiben
|
||||
cpm check Validieren ohne zu schreiben (Dry-Run)
|
||||
Commands:
|
||||
cpm init Create user mapping file (optionally fill interactively)
|
||||
cpm setup Guided: read profile, fill mapping, render
|
||||
cpm render Translate team profile into a harness preset and write it
|
||||
cpm check Validate without writing (dry-run)
|
||||
|
||||
Beispiele:
|
||||
Examples:
|
||||
python3 cpm.py setup --package examples/package
|
||||
python3 cpm.py render --package examples/package --mapping ~/.config/cpm/model-mapping.yaml
|
||||
python3 cpm.py check --package examples/package
|
||||
|
|
@ -34,8 +33,8 @@ from pathlib import Path
|
|||
|
||||
import yaml
|
||||
|
||||
# Eingebaute Agenten des ersten Übersetzungsziels oh-my-opencode-slim.
|
||||
# Alles andere wird als Custom Agent behandelt.
|
||||
# Built-in agents of the first translation target oh-my-opencode-slim.
|
||||
# Everything else is treated as a custom agent.
|
||||
BUILTIN_AGENTS = {
|
||||
"orchestrator",
|
||||
"oracle",
|
||||
|
|
@ -47,31 +46,31 @@ BUILTIN_AGENTS = {
|
|||
"observer",
|
||||
}
|
||||
|
||||
DEFAULT_MAPPING_PATH = Path.home() / ".config" / "apm-team" / "model-mapping.yaml"
|
||||
DEFAULT_MAPPING_PATH = Path.home() / ".config" / "cpm" / "model-mapping.yaml"
|
||||
DEFAULT_TEAM_FILE = "team-profile.yaml"
|
||||
DEFAULT_OUTPUT = Path(".opencode") / "oh-my-opencode-slim.json"
|
||||
PROVENANCE_SUFFIX = ".cpm-provenance.json"
|
||||
|
||||
SUPPORTED_SCHEMA = ("corentic.team-profile/v1", "acme.team-profile/v1")
|
||||
SUPPORTED_SCHEMA = ("corentic.team-profile/v1",)
|
||||
|
||||
|
||||
class OmosError(Exception):
|
||||
"""Fehler mit nutzerlesbarer Ursache."""
|
||||
class CpmError(Exception):
|
||||
"""Error with a user-readable cause."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Laden
|
||||
# Loading
|
||||
|
||||
|
||||
def load_yaml(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
raise OmosError(f"Datei nicht gefunden: {path}")
|
||||
raise CpmError(f"File not found: {path}")
|
||||
try:
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
except yaml.YAMLError as exc:
|
||||
raise OmosError(f"YAML-Fehler in {path}: {exc}") from exc
|
||||
raise CpmError(f"YAML error in {path}: {exc}") from exc
|
||||
if not isinstance(data, dict):
|
||||
raise OmosError(f"{path} enthält kein YAML-Mapping")
|
||||
raise CpmError(f"{path} does not contain a YAML mapping")
|
||||
return data
|
||||
|
||||
|
||||
|
|
@ -87,25 +86,25 @@ def load_team_profile(package_dir: Path, team_file: str | None) -> tuple[dict, P
|
|||
schema = profile.get("schema")
|
||||
if schema and schema not in SUPPORTED_SCHEMA:
|
||||
print(
|
||||
f"Warnung: Unbekanntes Schema '{schema}'. "
|
||||
f"Unterstützt: {', '.join(SUPPORTED_SCHEMA)}. Fahre fort."
|
||||
f"Warning: Unknown schema '{schema}'. "
|
||||
f"Supported: {', '.join(SUPPORTED_SCHEMA)}. Continuing."
|
||||
)
|
||||
if not profile.get("id"):
|
||||
raise OmosError(f"{candidate}: Feld 'id' fehlt")
|
||||
raise CpmError(f"{candidate}: missing field 'id'")
|
||||
roles = profile.get("roles")
|
||||
if not isinstance(roles, list) or not roles:
|
||||
raise OmosError(f"{candidate}: Keine Rollen definiert ('roles')")
|
||||
raise CpmError(f"{candidate}: no roles defined ('roles')")
|
||||
return profile, candidate
|
||||
searched = ", ".join(str(c) for c in candidates)
|
||||
raise OmosError(f"Kein Team-Profil gefunden. Gesucht: {searched}")
|
||||
raise CpmError(f"No team profile found. Searched: {searched}")
|
||||
|
||||
|
||||
def load_mapping(mapping_path: Path | None) -> dict:
|
||||
path = mapping_path or DEFAULT_MAPPING_PATH
|
||||
if not path.exists():
|
||||
raise OmosError(
|
||||
f"Modell-Mapping nicht gefunden: {path}\n"
|
||||
"Lege die Datei an, z. B.:\n"
|
||||
raise CpmError(
|
||||
f"Model mapping not found: {path}\n"
|
||||
"Create the file, e.g.:\n"
|
||||
" model_classes:\n"
|
||||
" fast-research: ollama/qwen3.5:9b\n"
|
||||
" high-reasoning:\n"
|
||||
|
|
@ -115,22 +114,22 @@ def load_mapping(mapping_path: Path | None) -> dict:
|
|||
data = load_yaml(path)
|
||||
classes = data.get("model_classes")
|
||||
if not isinstance(classes, dict) or not classes:
|
||||
raise OmosError(f"{path}: Sektion 'model_classes' fehlt oder ist leer")
|
||||
raise CpmError(f"{path}: section 'model_classes' missing or empty")
|
||||
return classes
|
||||
|
||||
|
||||
def resolve_model(model_class_entry: object, model_class: str, role_id: str) -> dict:
|
||||
"""Löst einen Mapping-Eintrag (string oder dict) zu Agent-Feldern auf."""
|
||||
"""Resolve a mapping entry (string or dict) into agent fields."""
|
||||
if isinstance(model_class_entry, str):
|
||||
return {"model": model_class_entry}
|
||||
if isinstance(model_class_entry, dict):
|
||||
entry = {k: v for k, v in model_class_entry.items()}
|
||||
if "model" not in entry:
|
||||
raise OmosError(
|
||||
f"Rolle '{role_id}': Mapping für '{model_class}' hat kein 'model'-Feld"
|
||||
raise CpmError(
|
||||
f"Role '{role_id}': mapping for '{model_class}' has no 'model' field"
|
||||
)
|
||||
return entry
|
||||
raise OmosError(f"Ungültiger Mapping-Eintrag für '{model_class}': {model_class_entry!r}")
|
||||
raise CpmError(f"Invalid mapping entry for '{model_class}': {model_class_entry!r}")
|
||||
|
||||
|
||||
def sanitize_preset_name(team_id: str) -> str:
|
||||
|
|
@ -138,11 +137,11 @@ def sanitize_preset_name(team_id: str) -> str:
|
|||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Übersetzung
|
||||
# Translation
|
||||
|
||||
|
||||
def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]:
|
||||
"""Erzeugt (preset, custom_agents, warnings)."""
|
||||
"""Returns (preset, custom_agents, warnings)."""
|
||||
preset: dict = {}
|
||||
custom_agents: dict = {}
|
||||
warnings: list[str] = []
|
||||
|
|
@ -150,15 +149,15 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|||
for role in profile["roles"]:
|
||||
role_id = role.get("id")
|
||||
if not role_id:
|
||||
raise OmosError("Rolle ohne 'id' gefunden")
|
||||
raise CpmError("Found a role without 'id'")
|
||||
|
||||
model_class = role.get("model_class")
|
||||
if not model_class:
|
||||
raise OmosError(f"Rolle '{role_id}': 'model_class' fehlt")
|
||||
raise CpmError(f"Role '{role_id}': 'model_class' is missing")
|
||||
if model_class not in mapping:
|
||||
raise OmosError(
|
||||
f"Rolle '{role_id}': Modellklasse '{model_class}' ist nicht gemappt.\n"
|
||||
f"Ergänze in deinem Mapping:\n"
|
||||
raise CpmError(
|
||||
f"Role '{role_id}': model class '{model_class}' is not mapped.\n"
|
||||
f"Add to your mapping:\n"
|
||||
f" {model_class}: <provider/model>"
|
||||
)
|
||||
|
||||
|
|
@ -171,7 +170,7 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|||
|
||||
runtime_agent = role.get("runtime_agent", role_id)
|
||||
if runtime_agent == "custom":
|
||||
# Explizit als Custom Agent markiert → rolleneigener Name.
|
||||
# Explicitly marked as custom agent -> use the role's own name.
|
||||
agent_key = role_id
|
||||
else:
|
||||
agent_key = runtime_agent
|
||||
|
|
@ -190,17 +189,17 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|||
prompt = (
|
||||
purpose
|
||||
if purpose
|
||||
else f"Custom Agent '{role_id}' aus Team-Profil "
|
||||
f"'{profile.get('id', 'unbekannt')}'."
|
||||
else f"Custom agent '{role_id}' from team profile "
|
||||
f"'{profile.get('id', 'unknown')}'."
|
||||
)
|
||||
custom_agents[agent_key] = {
|
||||
"model": agent_fields["model"],
|
||||
"description": purpose or f"Custom subagent '{role_id}'",
|
||||
"prompt": prompt,
|
||||
# Dem Orchestrator sagen, wann er delegieren soll.
|
||||
# Tell the orchestrator when to delegate to this agent.
|
||||
"orchestratorPrompt": (
|
||||
f"@{agent_key}\n- Rolle: {purpose}\n"
|
||||
"- Delegiere Aufgaben dieser Rolle an diesen Agenten."
|
||||
f"@{agent_key}\n- Role: {purpose}\n"
|
||||
"- Delegate tasks of this role to this agent."
|
||||
if purpose
|
||||
else f"@{agent_key}"
|
||||
),
|
||||
|
|
@ -210,15 +209,15 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|||
|
||||
if not mcps and not skills:
|
||||
warnings.append(
|
||||
f"Rolle '{role_id}' ({agent_key}): keine MCPs/Skills zugewiesen "
|
||||
"(rein koordinierend?)"
|
||||
f"Role '{role_id}' ({agent_key}): no MCPs/skills assigned "
|
||||
"(purely coordinating?)"
|
||||
)
|
||||
|
||||
orchestrator_present = any(k == "orchestrator" for k in preset)
|
||||
if not orchestrator_present:
|
||||
warnings.append(
|
||||
"Kein 'orchestrator' im Team. Ohne Orchestrator-Preset-Eintrag bleibt "
|
||||
"dessen Modell unverändert."
|
||||
"No 'orchestrator' in the team. Without an orchestrator preset "
|
||||
"entry its model stays unchanged."
|
||||
)
|
||||
|
||||
return preset, custom_agents, warnings
|
||||
|
|
@ -231,8 +230,8 @@ def merge_into_config(config: dict, preset_name: str, preset: dict,
|
|||
existing = presets.get(preset_name)
|
||||
if existing:
|
||||
print(
|
||||
f"Hinweis: Preset '{preset_name}' existierte bereits und wird ersetzt "
|
||||
"(andere Presets bleiben unberührt)."
|
||||
f"Note: preset '{preset_name}' already existed and will be replaced "
|
||||
"(other presets remain untouched)."
|
||||
)
|
||||
presets[preset_name] = preset
|
||||
|
||||
|
|
@ -242,7 +241,7 @@ def merge_into_config(config: dict, preset_name: str, preset: dict,
|
|||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MCP-Verfügbarkeit prüfen (best effort)
|
||||
# MCP availability check (best effort)
|
||||
|
||||
|
||||
def strip_jsonc(text: str) -> str:
|
||||
|
|
@ -251,12 +250,14 @@ def strip_jsonc(text: str) -> str:
|
|||
return text
|
||||
|
||||
|
||||
def configured_mcps() -> set[str]:
|
||||
candidates = [
|
||||
OPENCODE_CONFIGS = [
|
||||
Path.home() / ".config" / "opencode" / "opencode.jsonc",
|
||||
Path.home() / ".config" / "opencode" / "opencode.json",
|
||||
]
|
||||
for path in candidates:
|
||||
]
|
||||
|
||||
|
||||
def configured_mcps() -> set[str]:
|
||||
for path in OPENCODE_CONFIGS:
|
||||
if path.exists():
|
||||
try:
|
||||
data = json.loads(strip_jsonc(path.read_text(encoding="utf-8")))
|
||||
|
|
@ -267,13 +268,9 @@ def configured_mcps() -> set[str]:
|
|||
|
||||
|
||||
def available_models() -> list[str]:
|
||||
"""Listet alle provider/model-Kombinationen aus der opencode.json(c)."""
|
||||
candidates = [
|
||||
Path.home() / ".config" / "opencode" / "opencode.jsonc",
|
||||
Path.home() / ".config" / "opencode" / "opencode.json",
|
||||
]
|
||||
"""List all provider/model combinations from opencode.json(c)."""
|
||||
models: list[str] = []
|
||||
for path in candidates:
|
||||
for path in OPENCODE_CONFIGS:
|
||||
if not path.exists():
|
||||
continue
|
||||
try:
|
||||
|
|
@ -288,12 +285,12 @@ def available_models() -> list[str]:
|
|||
|
||||
|
||||
INIT_TEMPLATE = """\
|
||||
# Lokale Mapping-Tabelle des NUTZERS.
|
||||
# Ordnet abstrakte Modellklassen konkrete Modell-IDs zu.
|
||||
# Formate:
|
||||
# klasse: provider/modell
|
||||
# klasse:
|
||||
# model: provider/modell
|
||||
# Local mapping table of the USER.
|
||||
# Maps abstract model classes to concrete model IDs.
|
||||
# Formats:
|
||||
# class: provider/model
|
||||
# class:
|
||||
# model: provider/model
|
||||
# variant: thinking # optional
|
||||
# temperature: 0.7 # optional
|
||||
|
||||
|
|
@ -310,7 +307,7 @@ def check_mcps(preset: dict, warnings: list[str]) -> None:
|
|||
available = configured_mcps()
|
||||
if not available:
|
||||
warnings.append(
|
||||
"Konnte opencode.json(c) nicht lesen – MCP-Prüfung übersprungen."
|
||||
"Could not read opencode.json(c) - MCP check skipped."
|
||||
)
|
||||
return
|
||||
needed: set[str] = set()
|
||||
|
|
@ -319,13 +316,13 @@ def check_mcps(preset: dict, warnings: list[str]) -> None:
|
|||
missing = sorted(m for m in needed if m not in available)
|
||||
if missing:
|
||||
warnings.append(
|
||||
"MCPs im Team-Profil, aber nicht in opencode.json konfiguriert: "
|
||||
+ ", ".join(missing)
|
||||
"MCPs referenced in the team profile but not configured in "
|
||||
"opencode.json: " + ", ".join(missing)
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kommandos
|
||||
# Commands
|
||||
|
||||
|
||||
def package_output_path(package_dir: Path) -> Path:
|
||||
|
|
@ -344,7 +341,7 @@ def cmd_render(args: argparse.Namespace) -> int:
|
|||
try:
|
||||
config = json.loads(output.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
raise OmosError(f"{output} ist kein gültiges JSON: {exc}")
|
||||
raise CpmError(f"{output} is not valid JSON: {exc}")
|
||||
else:
|
||||
config = {}
|
||||
|
||||
|
|
@ -363,7 +360,7 @@ def cmd_render(args: argparse.Namespace) -> int:
|
|||
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(merged, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"Geschrieben: {output}")
|
||||
print(f"Written: {output}")
|
||||
|
||||
provenance = {
|
||||
"profile": profile["id"],
|
||||
|
|
@ -390,79 +387,79 @@ def cmd_render(args: argparse.Namespace) -> int:
|
|||
|
||||
_report(warnings)
|
||||
print(
|
||||
"\nNächste Schritte:\n"
|
||||
" 1. opencode starten (Projekt-Config wird geladen)\n"
|
||||
"\nNext steps:\n"
|
||||
" 1. start opencode (project config gets loaded)\n"
|
||||
f" 2. /preset {sanitize_preset_name(str(profile['id']))}\n"
|
||||
" 3. OpenCode neu laden → Team aktiv"
|
||||
" 3. reload OpenCode -> team is active"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_check(args: argparse.Namespace) -> int:
|
||||
args.dry_run = True
|
||||
# Dry-Run: vorhandene Projekt-Config einbeziehen, aber nie schreiben.
|
||||
# Dry run: include an existing project config, but never write.
|
||||
existing = package_output_path(Path(args.package).resolve())
|
||||
args.output = str(existing) if existing.exists() else None
|
||||
try:
|
||||
cmd_render(args)
|
||||
except OmosError as exc:
|
||||
print(f"Fehler: {exc}", file=sys.stderr)
|
||||
except CpmError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _report(warnings: list[str], dry_run: bool = False) -> None:
|
||||
if dry_run:
|
||||
print("\n--- Dry-Run: Es wurde nichts geschrieben ---")
|
||||
print("\n--- Dry run: nothing was written ---")
|
||||
if warnings:
|
||||
print("\nWarnungen:")
|
||||
print("\nWarnings:")
|
||||
for warning in warnings:
|
||||
print(f" ⚠ {warning}")
|
||||
else:
|
||||
print("\nKeine Warnungen.")
|
||||
print("\nNo warnings.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Interaktive Assistenten
|
||||
# Interactive assistants
|
||||
|
||||
|
||||
def _choose_model(model_class: str, models: list[str]) -> str:
|
||||
"""Interaktive Auswahl eines Modells für eine Modellklasse."""
|
||||
print(f"\nFür '{model_class}' ist kein Modell zugeordnet.")
|
||||
"""Interactively pick a model for a model class."""
|
||||
print(f"\nNo model assigned for '{model_class}'.")
|
||||
if models:
|
||||
print("Verfügbare Modelle (aus opencode.json(c)):")
|
||||
print("Available models (from opencode.json(c)):")
|
||||
for i, model in enumerate(models, 1):
|
||||
print(f" {i}. {model}")
|
||||
print(" 0. Modell-ID manuell eingeben")
|
||||
print(" 0. Enter model ID manually")
|
||||
while True:
|
||||
choice = input("Auswahl: ").strip()
|
||||
choice = input("Selection: ").strip()
|
||||
if choice == "0":
|
||||
return input("Modell-ID (provider/model): ").strip()
|
||||
return input("Model ID (provider/model): ").strip()
|
||||
if choice.isdigit() and 1 <= int(choice) <= len(models):
|
||||
return models[int(choice) - 1]
|
||||
print("Ungültige Auswahl.")
|
||||
print("Invalid selection.")
|
||||
else:
|
||||
print("Keine Modelle in opencode.json(c) gefunden.")
|
||||
return input("Modell-ID (provider/model): ").strip()
|
||||
print("No models found in opencode.json(c).")
|
||||
return input("Model ID (provider/model): ").strip()
|
||||
|
||||
|
||||
def cmd_init(args: argparse.Namespace) -> int:
|
||||
mapping_path = Path(args.mapping) if args.mapping else DEFAULT_MAPPING_PATH
|
||||
if mapping_path.exists():
|
||||
print(f"Mapping existiert bereits: {mapping_path}")
|
||||
print(f"Mapping already exists: {mapping_path}")
|
||||
else:
|
||||
mapping_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
mapping_path.write_text(INIT_TEMPLATE, encoding="utf-8")
|
||||
print(f"Angelegt: {mapping_path}")
|
||||
print(f"Created: {mapping_path}")
|
||||
|
||||
if args.fill:
|
||||
missing = [k for k, v in load_mapping(mapping_path).items()
|
||||
if v == "PLACEHOLDER"]
|
||||
models = available_models()
|
||||
if missing and not models:
|
||||
raise OmosError(
|
||||
"Keine Modelle in ~/.config/opencode/opencode.json(c) gefunden. "
|
||||
"Trage die Modell-IDs manuell ein oder richte opencode ein."
|
||||
raise CpmError(
|
||||
"No models found in ~/.config/opencode/opencode.json(c). "
|
||||
"Enter the model IDs manually or set up opencode first."
|
||||
)
|
||||
data = load_yaml(mapping_path)
|
||||
for model_class in missing:
|
||||
|
|
@ -474,33 +471,33 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|||
remaining = sum(1 for v in data["model_classes"].values()
|
||||
if v == "PLACEHOLDER")
|
||||
if remaining:
|
||||
print(f"\n{remaining} Eintrag/Einträge bleiben PLACEHOLDER – "
|
||||
"bitte manuell ergänzen.")
|
||||
print(f"\n{remaining} entr(y/ies) left as PLACEHOLDER - "
|
||||
"please fill in manually.")
|
||||
|
||||
print("\nNächster Schritt:")
|
||||
print("\nNext step:")
|
||||
print(f" cpm.py check --package <package-dir> --mapping {mapping_path}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_setup(args: argparse.Namespace) -> int:
|
||||
"""Geführter Ablauf: Profil lesen → Mapping auffüllen → rendern."""
|
||||
"""Guided flow: read profile -> fill mapping -> render."""
|
||||
package_dir = Path(args.package).resolve()
|
||||
profile, profile_path = load_team_profile(package_dir, args.team_file)
|
||||
profile, _profile_path = load_team_profile(package_dir, args.team_file)
|
||||
mapping_path = Path(args.mapping) if args.mapping else DEFAULT_MAPPING_PATH
|
||||
|
||||
print(f"Team-Profil: {profile['id']}")
|
||||
print(f"Team profile: {profile['id']}")
|
||||
if profile.get("description"):
|
||||
print(f" {str(profile['description']).strip()}")
|
||||
print("Rollen:")
|
||||
print("Roles:")
|
||||
for role in profile["roles"]:
|
||||
mcps = (role.get("capabilities", {}) or {}).get("mcps", [])
|
||||
skills = (role.get("capabilities", {}) or {}).get("skills", [])
|
||||
print(f" - {role['id']} → {role.get('model_class')} "
|
||||
f"(MCPs: {', '.join(mcps) or 'keine'}; "
|
||||
f"Skills: {', '.join(skills) or 'keine'})")
|
||||
f"(MCPs: {', '.join(mcps) or 'none'}; "
|
||||
f"Skills: {', '.join(skills) or 'none'})")
|
||||
|
||||
if not mapping_path.exists():
|
||||
print(f"\nMapping fehlt – lege an: {mapping_path}")
|
||||
print(f"\nMapping missing - creating: {mapping_path}")
|
||||
args_init = argparse.Namespace(mapping=str(mapping_path), fill=True)
|
||||
cmd_init(args_init)
|
||||
|
||||
|
|
@ -518,9 +515,9 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|||
yaml.safe_dump(classes, sort_keys=False, allow_unicode=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f"\nMapping aktualisiert: {mapping_path}")
|
||||
print(f"\nMapping updated: {mapping_path}")
|
||||
|
||||
print("\nRendern …")
|
||||
print("\nRendering ...")
|
||||
args_render = argparse.Namespace(
|
||||
package=str(package_dir),
|
||||
team_file=args.team_file,
|
||||
|
|
@ -535,9 +532,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser = argparse.ArgumentParser(
|
||||
prog="cpm",
|
||||
description=(
|
||||
"Adapter zwischen APM-Team-Profilen und oh-my-opencode-slim. "
|
||||
"Übersetzt deterministisch: Team-Profil + lokales Modell-Mapping "
|
||||
"-> Harness-Preset."
|
||||
"Corentic Package Manager. Translates CPM packages (APM manifest + "
|
||||
"team-profile.yaml) deterministically into harness-native presets: "
|
||||
"team profile + local model mapping -> harness preset."
|
||||
),
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
|
@ -546,70 +543,70 @@ def main(argv: list[str] | None = None) -> int:
|
|||
p.add_argument(
|
||||
"--package",
|
||||
default=".",
|
||||
help="Pfad zum APM-Package (mit team-profile.yaml)",
|
||||
help="Path to the CPM package directory (containing team-profile.yaml)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--team-file",
|
||||
help="Alternativer Dateiname des Team-Profils (Default: team-profile.yaml)",
|
||||
help="Alternative filename of the team profile (default: team-profile.yaml)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--mapping",
|
||||
help=f"Pfad zur Modell-Mapping-Datei (Default: {DEFAULT_MAPPING_PATH})",
|
||||
help=f"Path to the model mapping file (default: {DEFAULT_MAPPING_PATH})",
|
||||
)
|
||||
|
||||
p_render = sub.add_parser("render", help="Preset generieren und schreiben")
|
||||
p_render = sub.add_parser("render", help="Generate and write the preset")
|
||||
common(p_render)
|
||||
p_render.add_argument(
|
||||
"--output",
|
||||
help=f"Zieldatei (Default: {DEFAULT_OUTPUT} relativ zum Package)",
|
||||
help=f"Target file (default: {DEFAULT_OUTPUT} relative to the package)",
|
||||
)
|
||||
p_render.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Nur anzeigen, nichts schreiben",
|
||||
help="Print only, write nothing",
|
||||
)
|
||||
p_render.set_defaults(func=cmd_render)
|
||||
|
||||
p_check = sub.add_parser("check", help="Validieren ohne zu schreiben")
|
||||
p_check = sub.add_parser("check", help="Validate without writing")
|
||||
common(p_check)
|
||||
p_check.set_defaults(func=cmd_check)
|
||||
|
||||
p_init = sub.add_parser(
|
||||
"init",
|
||||
help="Modell-Mapping-Datei anlegen und optional Platzhalter füllen",
|
||||
help="Create the model mapping file, optionally filling placeholders",
|
||||
)
|
||||
p_init.add_argument(
|
||||
"--mapping",
|
||||
help=f"Zielpfad (Default: {DEFAULT_MAPPING_PATH})",
|
||||
help=f"Target path (default: {DEFAULT_MAPPING_PATH})",
|
||||
)
|
||||
p_init.add_argument(
|
||||
"--fill",
|
||||
action="store_true",
|
||||
help="Fehlende Modellklassen interaktiv aus opencode.json(c)-Modellen wählen",
|
||||
help="Interactively choose missing model classes from opencode.json(c) models",
|
||||
)
|
||||
p_init.set_defaults(func=cmd_init)
|
||||
|
||||
p_setup = sub.add_parser(
|
||||
"setup",
|
||||
help="Geführt: Profil lesen, Mapping auffüllen, rendern",
|
||||
help="Guided: read profile, fill mapping, render",
|
||||
)
|
||||
common(p_setup)
|
||||
p_setup.add_argument(
|
||||
"--output",
|
||||
help=f"Zieldatei (Default: {DEFAULT_OUTPUT} relativ zum Package)",
|
||||
help=f"Target file (default: {DEFAULT_OUTPUT} relative to the package)",
|
||||
)
|
||||
p_setup.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Nur anzeigen, nichts schreiben",
|
||||
help="Print only, write nothing",
|
||||
)
|
||||
p_setup.set_defaults(func=cmd_setup)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
return args.func(args)
|
||||
except OmosError as exc:
|
||||
print(f"Fehler: {exc}", file=sys.stderr)
|
||||
except CpmError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# CPM-Wrapper um das offizielle Microsoft Sample-Package.
|
||||
# Installiert dessen Primitives (design-standards, design-review,
|
||||
# accessibility-audit, style-checker, design-reviewer) und liefert
|
||||
# die Team-Empfehlung dafür.
|
||||
# Thin CPM wrapper around the official Microsoft sample package.
|
||||
# Installs its primitives (design-standards, design-review,
|
||||
# accessibility-audit, style-checker, design-reviewer) and adds the
|
||||
# team recommendation.
|
||||
#
|
||||
# Original: https://github.com/microsoft/apm-sample-package
|
||||
|
||||
name: acme/design-review-cpm
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Design-Review-Team auf Basis des offiziellen microsoft/apm-sample-package:
|
||||
Reviewer, Style-Checker und Accessibility-Auditor unter einem Orchestrator.
|
||||
Design review team based on the official microsoft/apm-sample-package:
|
||||
reviewer, style checker and accessibility auditor under one orchestrator.
|
||||
author: Tobias Endres
|
||||
license: MIT
|
||||
type: hybrid
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
# team-profile.yaml – Design-Review-Team
|
||||
# Rollen basierend auf den Primitives aus microsoft/apm-sample-package.
|
||||
# team-profile.yaml – Design Review Team
|
||||
# Roles based on the primitives from microsoft/apm-sample-package.
|
||||
|
||||
schema: corentic.team-profile/v1
|
||||
|
||||
id: acme.design-review
|
||||
description: >
|
||||
Team für strukturierte Design-Reviews: Style-Guideline-Prüfung,
|
||||
Design-Review nach Vorlage und Accessibility-Audit unter einem
|
||||
koordinierenden Reviewer.
|
||||
Team for structured design reviews: style-guideline checks,
|
||||
design review per template and an accessibility audit under a
|
||||
coordinating reviewer.
|
||||
|
||||
roles:
|
||||
- id: orchestrator
|
||||
purpose: >-
|
||||
Nimmt Review-Anfragen an, verteilt Style-Check, Design-Review
|
||||
und Accessibility-Audit und konsolidiert die Findings zu einem Report.
|
||||
Takes review requests, distributes style check, design review and
|
||||
accessibility audit and consolidates the findings into one report.
|
||||
model_class: strong-generalist
|
||||
capabilities:
|
||||
mcps: []
|
||||
|
|
@ -21,10 +21,11 @@ roles:
|
|||
|
||||
- id: reviewer
|
||||
purpose: >-
|
||||
Führt das Design-Review gemäß der design-review.prompt.md-Vorlage durch.
|
||||
Bewertet UI-Konsistenz gegen die design-standards.instructions.md.
|
||||
Meldet Findings mit Schweregrad, keine eigenmächtigen Codeänderungen.
|
||||
runtime_agent: oracle # tiefes Prüfen = Oracle-Profil
|
||||
Performs the design review following the design-review.prompt.md
|
||||
template. Evaluates UI consistency against the
|
||||
design-standards.instructions.md. Reports findings with severity,
|
||||
no unauthorized code changes.
|
||||
runtime_agent: oracle # deep review = oracle profile
|
||||
model_class: high-reasoning
|
||||
capabilities:
|
||||
mcps: []
|
||||
|
|
@ -32,9 +33,10 @@ roles:
|
|||
|
||||
- id: style-checker
|
||||
purpose: >-
|
||||
Prüft Code mechanisch gegen die Style-Guidelines aus dem
|
||||
style-checker-Skill. Liefert Verstoßliste mit Datei/Zeile. Keine Fixes.
|
||||
runtime_agent: fixer # schnelle, gezielte Prüfungen
|
||||
Mechanically checks code against the style guidelines from the
|
||||
style-checker skill. Returns a violation list with file/line.
|
||||
No fixes.
|
||||
runtime_agent: fixer # fast, targeted checks
|
||||
model_class: fast-research
|
||||
capabilities:
|
||||
mcps: []
|
||||
|
|
@ -42,10 +44,10 @@ roles:
|
|||
|
||||
- id: accessibility-auditor
|
||||
purpose: >-
|
||||
Auditet die Oberfläche gemäß accessibility-audit.prompt.md.
|
||||
Kategorisiert Verstöße (kritisch/warnung/hinweis) und zitiert die
|
||||
betroffenen Stellen.
|
||||
runtime_agent: librarian # Recherche-/Prüfprofil
|
||||
Audits the surface according to accessibility-audit.prompt.md.
|
||||
Categorizes violations (critical/warning/note) and quotes the
|
||||
affected locations.
|
||||
runtime_agent: librarian # research/review profile
|
||||
model_class: fast-research
|
||||
capabilities:
|
||||
mcps: []
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
# CPM-Wrapper um apm-issue-autopilot aus dem offiziellen microsoft/apm-Repo.
|
||||
# Thin CPM wrapper around apm-issue-autopilot from the official microsoft/apm repo.
|
||||
#
|
||||
# Das Original-Paket orchestriert bereits Skills (apm-triage-panel,
|
||||
# shepherd-driver, pr-description-skill) und treibt Issues bis zum
|
||||
# mergebaren PR. Das Team-Profil hier bildet genau diese Phasen auf
|
||||
# Agentenrollen ab.
|
||||
# The upstream package already orchestrates skills (apm-triage-panel,
|
||||
# shepherd-driver, pr-description-skill) and drives issues to a mergeable PR.
|
||||
# The team profile below maps exactly those phases onto agent roles.
|
||||
#
|
||||
# Original: https://github.com/microsoft/apm/tree/main/packages/apm-issue-autopilot
|
||||
# APM unterstützt Monorepo-Subpaths, daher ist die Dependency direkt
|
||||
# auf das Unterverzeichnis gerichtet.
|
||||
# Upstream: https://github.com/microsoft/apm/tree/main/packages/apm-issue-autopilot
|
||||
# APM supports monorepo subpaths, so the dependency points directly at the
|
||||
# subdirectory.
|
||||
|
||||
name: acme/issue-autopilot-cpm
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Issue-Triage-Team auf Basis von microsoft/apm packages/apm-issue-autopilot:
|
||||
Triage als Gate, Shepherd für den Drive-to-Merge, PR-Beschreibungen und
|
||||
eine prüfende Gegenstimme. Human-in-the-loop: Escalation an den Maintainer
|
||||
bei Zweifel.
|
||||
Issue triage team based on microsoft/apm packages/apm-issue-autopilot:
|
||||
triage as gate, shepherd for drive-to-merge, PR descriptions and a
|
||||
reviewing counter-check. Human-in-the-loop: escalate to the maintainer
|
||||
on doubt.
|
||||
author: Tobias Endres
|
||||
license: MIT
|
||||
type: hybrid
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
# team-profile.yaml – Issue-Autopilot-Team
|
||||
# Rollen basierend auf den Phasen des offiziellen apm-issue-autopilot-Pakets:
|
||||
# Triage → Entscheidung → Drive-to-Merge → PR-Beschreibung.
|
||||
# team-profile.yaml – Issue Autopilot Team
|
||||
# Roles based on the phases of the official apm-issue-autopilot package:
|
||||
# Triage -> Decision -> Drive-to-Merge -> PR description.
|
||||
|
||||
schema: corentic.team-profile/v1
|
||||
|
||||
id: acme.issue-autopilot
|
||||
description: >
|
||||
Treibt GitHub-Issues vom Intake bis zum mergebaren PR: Triage-Panel
|
||||
als zentrales Gate, Shepherd für jeden akzeptierten Issue,
|
||||
PR-Autor mit verankerter Beschreibung. Escaliert standardmäßig beim
|
||||
Maintainer im Zweifelsfall.
|
||||
Drives GitHub issues from intake to mergeable PR: triage panel as the
|
||||
central gate, a shepherd per accepted issue, a PR author with anchored
|
||||
descriptions. Escalates to the maintainer by default on doubt.
|
||||
|
||||
roles:
|
||||
- id: orchestrator
|
||||
purpose: >-
|
||||
Nimmt die Issue-Liste entgegen, startet pro Issue die Triage und fasst
|
||||
zu EIN konsolidiertem Review zusammen. Eskaliert Zweifelfälle explizit
|
||||
an den Maintainer statt selbst zu entscheiden.
|
||||
Takes the issue list, starts triage per issue and consolidates into ONE
|
||||
combined review. Escalates doubtful cases explicitly to the maintainer
|
||||
instead of deciding itself.
|
||||
model_class: strong-generalist
|
||||
capabilities:
|
||||
mcps: [github]
|
||||
|
|
@ -24,10 +23,10 @@ roles:
|
|||
|
||||
- id: triager
|
||||
purpose: >-
|
||||
Führt die Triage gemäß apm-triage-panel-Rubrik durch. Bewertet
|
||||
Reproduzierbarkeit, Scope, Priorität und Duplikate. Empfehlung mit
|
||||
Begründung, keine direkten Fixes.
|
||||
runtime_agent: oracle # Bewertungs-Gate = Reasoning-Profil
|
||||
Performs triage according to the apm-triage-panel rubric. Evaluates
|
||||
reproducibility, scope, priority and duplicates. Recommendation with
|
||||
rationale, no direct fixes.
|
||||
runtime_agent: oracle # decision gate = reasoning profile
|
||||
model_class: high-reasoning
|
||||
capabilities:
|
||||
mcps: [github]
|
||||
|
|
@ -35,10 +34,10 @@ roles:
|
|||
|
||||
- id: shepherd
|
||||
purpose: >-
|
||||
Treibt akzeptierte Issues über shepherd-driver bis zum mergebaren PR:
|
||||
Branch, Fix, Mutation-Break- und Lint-Gates, CI-Watch, Konfliktauflösung.
|
||||
Hält bei rotem CI inne.
|
||||
runtime_agent: fixer # Umsetzung + Korrekturen
|
||||
Drives accepted issues via shepherd-driver to a mergeable PR:
|
||||
branch, fix, mutation-break and lint gates, CI watch, conflict
|
||||
resolution. Halts on red CI.
|
||||
runtime_agent: fixer # implementation + corrections
|
||||
model_class: strong-writing
|
||||
capabilities:
|
||||
mcps: [github]
|
||||
|
|
@ -46,9 +45,9 @@ roles:
|
|||
|
||||
- id: pr-writer
|
||||
purpose: >-
|
||||
Verfasst die PR-Beschreibung mit pr-description-skill. Verankert sie
|
||||
am Issue mit Acceptance-Kriterien und Testnachweis. Merged nie selbst.
|
||||
runtime_agent: custom # eigener Custom Agent 'pr-writer'
|
||||
Authors the PR description using pr-description-skill. Anchors it to
|
||||
the issue with acceptance criteria and test evidence. Never merges.
|
||||
runtime_agent: custom # dedicated custom agent 'pr-writer'
|
||||
model_class: strong-writing
|
||||
capabilities:
|
||||
mcps: [github]
|
||||
|
|
@ -56,8 +55,9 @@ roles:
|
|||
|
||||
- id: reviewer
|
||||
purpose: >-
|
||||
Gegenprobe vor dem Push: prüft Fix gegen Issue-Akzeptanzkriterien
|
||||
und PRINCIPLES.md-Gate. Findings blockieren den Push, kein Self-Merge.
|
||||
Counter-check before push: verifies the fix against issue acceptance
|
||||
criteria and the PRINCIPLES.md gate. Findings block the push,
|
||||
no self-merge.
|
||||
runtime_agent: oracle
|
||||
model_class: high-reasoning
|
||||
capabilities:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# ~/.config/cpm/model-mapping.yaml
|
||||
#
|
||||
# Lokale Mapping-Tabelle des NUTZERS.
|
||||
# Ordnet abstrakte Modellklassen aus CPM-Packages (Team-Profile) konkrete, lokal
|
||||
# verfügbare Modell-IDs zu. Einmal anlegen, gilt für alle Packages.
|
||||
# Local mapping table of the USER.
|
||||
# Maps abstract model classes from CPM packages (team profiles) to concrete,
|
||||
# locally available model IDs. Create once, applies to all packages.
|
||||
#
|
||||
# Einfache Form: klasse: provider/modell-id
|
||||
# Erweiterte Form: klasse: { model: ..., variant: ..., temperature: ... }
|
||||
# Simple form: class: provider/model-id
|
||||
# Extended form: class: { model: ..., variant: ..., temperature: ... }
|
||||
|
||||
model_classes:
|
||||
strong-generalist: ollama/qwen3.6:35b-a3b-q4_K_M
|
||||
|
|
|
|||
|
|
@ -1,61 +1,57 @@
|
|||
# team-profile.yaml
|
||||
# Deklaration des empfohlenen Agenten-Teams für das Package job-application.
|
||||
#
|
||||
# Diese Datei gehört dem Package-AUTOR. Sie beschreibt:
|
||||
# - welche Rollen für den fachlichen Workflow sinnvoll sind,
|
||||
# - welche Modellklasse jede Rolle benötigt (abstrakt, keine konkreten IDs),
|
||||
# - welche Teilmenge der MCPs und Skills jede Rolle verwenden darf.
|
||||
#
|
||||
# Der Nutzer löst die Modellklassen lokal auf (siehe model-mapping.yaml).
|
||||
# Der CPM (Corentic Package Manager) übersetzt dieses Profil in die native
|
||||
# Konfiguration eines Ziel-Harnesses – aktuell: oh-my-opencode-slim.
|
||||
# team-profile.yaml - Job Application Team
|
||||
# Roles for the job-application package (OpenViking + ShareLaTeX).
|
||||
|
||||
schema: corentic.team-profile/v1
|
||||
|
||||
id: acme.job-applications
|
||||
description: >
|
||||
Human-in-the-loop-Team zur Recherche, Vorbereitung und Prüfung
|
||||
individueller Bewerbungsunterlagen.
|
||||
Human-in-the-loop team for researching, preparing and reviewing
|
||||
individual job application documents.
|
||||
|
||||
roles:
|
||||
- id: orchestrator
|
||||
purpose: Zerlegt Aufgaben, delegiert an Spezialisten, integriert Ergebnisse
|
||||
purpose: >-
|
||||
Decomposes tasks, delegates to specialists and integrates results.
|
||||
model_class: strong-generalist
|
||||
capabilities:
|
||||
mcps: []
|
||||
skills: [job-application]
|
||||
|
||||
- id: researcher
|
||||
purpose: Sucht und bewertet Stellenausschreibungen gegen das Kandidatenprofil
|
||||
runtime_agent: librarian # nutzt oh-my-opencode-slim-Builtin 'librarian' mit Alias
|
||||
purpose: >-
|
||||
Searches and evaluates job openings against the candidate profile.
|
||||
runtime_agent: librarian # uses the oh-my-opencode-slim builtin with an alias
|
||||
model_class: fast-research
|
||||
capabilities:
|
||||
mcps: [websearch, webfetch, openviking]
|
||||
skills: [job-application]
|
||||
|
||||
- id: writer
|
||||
purpose: Erstellt auf Fakten basierende Anschreiben und Lebensläufe in ShareLaTeX.
|
||||
Nutze nur freigegebene Profilfakten. Erfinde keine Erfahrung.
|
||||
Reiche niemals selbstständig eine Bewerbung ein.
|
||||
runtime_agent: custom # eigener Custom Agent 'writer'
|
||||
purpose: >-
|
||||
Creates fact-based cover letters and CVs in ShareLaTeX.
|
||||
Only use approved profile facts. Never invent experience.
|
||||
Never submit an application autonomously.
|
||||
runtime_agent: custom # dedicated custom agent 'writer'
|
||||
model_class: strong-writing
|
||||
capabilities:
|
||||
mcps: [openviking, sharelatex]
|
||||
skills: []
|
||||
|
||||
- id: checker
|
||||
purpose: Prüft Fakten, Ton und Vollständigkeit gegen das Kandidatenprofil.
|
||||
Jede Behauptung braucht eine belegbare Quelle.
|
||||
runtime_agent: oracle # nutzt oh-my-opencode-slim-Builtin 'oracle' mit Alias
|
||||
purpose: >-
|
||||
Reviews facts, tone and completeness against the candidate profile.
|
||||
Every claim requires a verifiable source.
|
||||
runtime_agent: oracle # uses the oh-my-opencode-slim builtin with an alias
|
||||
model_class: high-reasoning
|
||||
capabilities:
|
||||
mcps: [openviking]
|
||||
skills: []
|
||||
|
||||
- id: notifier
|
||||
purpose: Informiert den Nutzer über fertige Entwürfe.
|
||||
Versende niemals etwas ohne explizite Nutzerfreigabe.
|
||||
runtime_agent: custom # eigener Custom Agent 'notifier'
|
||||
purpose: >-
|
||||
Informs the user about finished drafts.
|
||||
Never send anything without explicit user approval.
|
||||
runtime_agent: custom # dedicated custom agent 'notifier'
|
||||
model_class: cheap-reliable
|
||||
capabilities:
|
||||
mcps: []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: cpm-setup
|
||||
description: Activate when the user wants to set up, render, or troubleshoot a CPM package (APM package + team-profile). Use for cpm init, cpm setup, cpm render, team-profile, model-mapping, oh-my-opencode-slim preset, fehlende Modellklassen.
|
||||
description: Activate when the user wants to set up, render, or troubleshoot a CPM package (APM package + team-profile). Use for cpm init, cpm setup, cpm render, team-profile, model-mapping, oh-my-opencode-slim preset, missing model classes.
|
||||
allowed-tools:
|
||||
- "Bash(python3 corentic-package-manager/cpm.py *)"
|
||||
- "Read"
|
||||
|
|
@ -11,65 +11,65 @@ allowed-tools:
|
|||
|
||||
# CPM Setup Skill
|
||||
|
||||
Führt den Nutzer durch: CPM-Package (APM-Manifest + Team-Profil) → lokales Modell-Mapping → gerendertes Harness-Preset (aktuell: oh-my-opencode-slim).
|
||||
Guides the user through: CPM package (APM manifest + team profile) → local model mapping → rendered harness preset (currently: oh-my-opencode-slim).
|
||||
|
||||
## Der Ablauf (Resolve–Validate–Render)
|
||||
## The flow (Resolve–Validate–Render)
|
||||
|
||||
### 1. Team-Profil finden
|
||||
### 1. Find the team profile
|
||||
|
||||
Prüfe, ob eine `team-profile.yaml` existiert (Package-Root oder `corentic-package-manager/examples/package/team-profile.yaml`). Lies sie und fasse dem Nutzer zusammen:
|
||||
Check whether a `team-profile.yaml` exists (package root or `corentic-package-manager/examples/package/team-profile.yaml`). Read it and summarize for the user:
|
||||
|
||||
```
|
||||
Profil: acme.job-applications
|
||||
Rollen: orchestrator → strong-generalist, researcher → fast-research,
|
||||
Profile: acme.job-applications
|
||||
Roles: orchestrator → strong-generalist, researcher → fast-research,
|
||||
writer → strong-writing, checker → high-reasoning, notifier → cheap-reliable
|
||||
MCPs benötigt: openviking, sharelatex (+ websearch/webfetch als OpenCode-Native)
|
||||
MCPs required: openviking, sharelatex (+ websearch/webfetch as OpenCode-native)
|
||||
```
|
||||
|
||||
### 2. Mapping prüfen
|
||||
### 2. Check the mapping
|
||||
|
||||
Prüfe `~/.config/cpm/model-mapping.yaml`. Für **jede** im Profil verwendete Modellklasse muss ein Eintrag existieren.
|
||||
Check `~/.config/cpm/model-mapping.yaml`. For **every** model class used in the profile an entry must exist.
|
||||
|
||||
Wenn eine Modellklasse fehlt, biete dem Nutzer konkrete Optionen an (verfügbare Modelle aus `~/.config/opencode/opencode.jsonc` auflisten):
|
||||
If a model class is missing, offer the user concrete options (list available models from `~/.config/opencode/opencode.jsonc`):
|
||||
|
||||
```
|
||||
Für 'high-reasoning' ist kein Modell zugeordnet.
|
||||
1. Vorhandenes Modell wählen (z. B. ollama/qwen3.6:35b-a3b-q4_K_M)
|
||||
2. Neues Provider/Modell in opencode.jsonc konfigurieren
|
||||
3. Abbrechen
|
||||
No model assigned for 'high-reasoning'.
|
||||
1. Pick an existing model (e.g. ollama/qwen3.6:35b-a3b-q4_K_M)
|
||||
2. Configure a new provider/model in opencode.jsonc
|
||||
3. Abort
|
||||
```
|
||||
|
||||
Nach Auswahl schreibe das Mapping und markiere optional `# selected_by: user`.
|
||||
After selection write the mapping and optionally mark `# selected_by: user`.
|
||||
|
||||
Alternativ die interaktiven Kommandos nutzen: `cpm init --fill` und `cpm setup`.
|
||||
Alternatively use the interactive commands: `cpm init --fill` and `cpm setup`.
|
||||
|
||||
### 3. MCPs prüfen
|
||||
### 3. Check MCPs
|
||||
|
||||
Vergleiche die im Profil genannten MCPs mit der Sektion `mcp` in `~/.config/opencode/opencode.jsonc`. Fehlende dem Nutzer melden – nicht stillschweigend ignorieren. Native OpenCode-Tools (`websearch`, `webfetch`) sind keine MCPs und brauchen keinen Eintrag.
|
||||
Compare the MCPs named in the profile against the `mcp` section in `~/.config/opencode/opencode.jsonc`. Report missing ones to the user – never silently ignore. OpenCode-native tools (`websearch`, `webfetch`) are not MCPs and need no entry.
|
||||
|
||||
### 4. Rendern
|
||||
### 4. Render
|
||||
|
||||
```bash
|
||||
python3 corentic-package-manager/cpm.py render --package <package-dir>
|
||||
```
|
||||
|
||||
Zeige die Ausgabe inklusive Warnungen. Die Datei `.opencode/oh-my-opencode-slim.json` ist generiert – nicht manuell editieren.
|
||||
Show the output including warnings. The file `.opencode/oh-my-opencode-slim.json` is generated – never edit it manually.
|
||||
|
||||
### 5. Aktivieren
|
||||
### 5. Activate
|
||||
|
||||
Nutzer anleiten: `opencode` starten → `/preset <team-id>` → Reload. Preset-Wechsel greifen erst nach einem Reload (by Design).
|
||||
Guide the user: start `opencode` → `/preset <team-id>` → reload. Preset switches only take effect after a reload (by design).
|
||||
|
||||
## Fehlerbehandlung
|
||||
## Error handling
|
||||
|
||||
| Fehler | Lösung |
|
||||
| Error | Fix |
|
||||
|---|---|
|
||||
| „Modellklasse X ist nicht gemappt" | Schritt 2: Mapping ergänzen |
|
||||
| „MCP ... nicht in opencode.json konfiguriert" | Schritt 3: MCP einrichten oder Rolle anpassen |
|
||||
| „Kein Team-Profil gefunden" | Pfad prüfen, `--team-file` verwenden |
|
||||
| Ungültiges YAML | Zeile aus Fehlermeldung zeigen |
|
||||
| "Model class X is not mapped" | Step 2: add to mapping |
|
||||
| "MCP ... not configured in opencode.json" | Step 3: set up the MCP or adjust the role |
|
||||
| "No team profile found" | Check path, use `--team-file` |
|
||||
| Invalid YAML | Follow the line number in the error message |
|
||||
|
||||
## Regeln
|
||||
## Rules
|
||||
|
||||
- Niemals selbst Modelle erraten oder zuordnen – immer den Nutzer wählen lassen.
|
||||
- Niemals die generierte `.opencode/oh-my-opencode-slim.json` direkt editieren; Quelle sind Team-Profil + Mapping.
|
||||
- Sicherheitsgrenzen des Packages (MCP/Skill-Allowlists) nur verschärfen, nie aufweichen.
|
||||
- Never guess or assign models yourself – always let the user choose.
|
||||
- Never edit the generated `.opencode/oh-my-opencode-slim.json` directly; sources are the team profile + mapping.
|
||||
- Only ever tighten package security boundaries (MCP/skill allowlists), never widen them.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue