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:
Tobias J. Endres 2026-08-23 17:56:09 +02:00
parent 913be9d528
commit 0df9cec833
9 changed files with 341 additions and 347 deletions

View file

@ -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.

View file

@ -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
OPENCODE_CONFIGS = [
Path.home() / ".config" / "opencode" / "opencode.jsonc",
Path.home() / ".config" / "opencode" / "opencode.json",
]
def configured_mcps() -> set[str]:
candidates = [
Path.home() / ".config" / "opencode" / "opencode.jsonc",
Path.home() / ".config" / "opencode" / "opencode.json",
]
for path in candidates:
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"],
@ -386,83 +383,83 @@ def cmd_render(args: argparse.Namespace) -> int:
provenance_path.write_text(
json.dumps(provenance, indent=2, ensure_ascii=False), encoding="utf-8"
)
print(f"Provenance: {provenance_path}")
print(f"Provenance: {provenance_path}")
_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"\nr '{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

View file

@ -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

View file

@ -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: []

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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: []

View file

@ -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 (ResolveValidateRender)
## The flow (ResolveValidateRender)
### 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.