diff --git a/corentic-package-manager/README.md b/corentic-package-manager/README.md index b562e02..9dd4729 100644 --- a/corentic-package-manager/README.md +++ b/corentic-package-manager/README.md @@ -32,12 +32,13 @@ team-profile.yaml (model class → oh-my-opencode-slim.json # 1. Create the model mapping – interactively, choosing from your opencode models python3 cpm.py init --fill -# 2. Guided: read team profile, check/fill mapping, render +# 2. Guided: read team profile, check/fill mapping, render, install primitives cd python3 /path/to/cpm.py setup --package /path/to/package # 3. Activate -opencode # project config gets loaded +cd /path/to/package # the config lives in /.opencode/ +opencode /preset acme-job-applications # Reload OpenCode -> team is active (by design: no hot-swap) ``` @@ -53,9 +54,10 @@ $EDITOR ~/.config/cpm/model-mapping.yaml # adapt to your own models # 2. Validate (dry run) python3 cpm.py check --package examples/package -# 3. Render -cd -python3 /path/to/cpm.py render --package /path/to/package +# 3. Render + install package primitives +cd /path/to/package +python3 /path/to/cpm.py render --package . +apm install # deploys skills/instructions from APM dependencies ``` ## The three files @@ -83,7 +85,7 @@ Creates `~/.config/cpm/model-mapping.yaml` (if not present). With `--fill`, all ### `setup` – guided flow -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. +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 then handles package primitives: if the package declares APM dependencies, `setup` offers to run `apm install` for you (or prints the manual command if the `apm` CLI is not installed). ### `render` / `check` @@ -169,13 +171,28 @@ cd package && opencode /preset acme-job-applications ``` +## Using the team + +Rendering + activation only configures the agents. To actually work with the team: + +1. **Install primitives.** If the package declares APM dependencies, run `apm install` in the package directory (`cpm setup` offers this automatically). This deploys the skills/instructions that role allowlists reference into `.opencode/skills/` etc. +2. **Reload OpenCode.** Agents (preset) and skills are loaded at startup. +3. **Sanity check.** In OpenCode: `/agents` lists the team members; "ping all agents" verifies they respond. +4. **Work via the orchestrator.** Just state your task in normal language – e.g. *"Review the components in src/ against our design standards"*. The orchestrator decomposes it and delegates according to the generated routing prompts: style violations → `@style-checker`, design judgment → `@reviewer`, audits → the auditor agent. It then consolidates the findings. + +Two things to know: + +- The preset switch (`/preset`) and skill loading both require an OpenCode restart/reload – by design. +- Activating a preset writes its name to your **global** user config (`~/.config/opencode/oh-my-opencode-slim.json`), so it persists across sessions and projects until you switch again. + ## Troubleshooting | Problem | Cause/Fix | |---|---| | `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` does not show anything new | Start opencode in the directory containing `.opencode/` and reload; presets do not apply mid-session | +| Agent references a skill that does not exist | Run `apm install` in the package directory – the preset only configures agents, primitives come from APM | | 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 | diff --git a/corentic-package-manager/cpm.py b/corentic-package-manager/cpm.py index 11f2e40..a8e6238 100644 --- a/corentic-package-manager/cpm.py +++ b/corentic-package-manager/cpm.py @@ -28,6 +28,8 @@ import argparse import copy import json import re +import shutil +import subprocess import sys from pathlib import Path @@ -329,6 +331,63 @@ def package_output_path(package_dir: Path) -> Path: return package_dir / DEFAULT_OUTPUT +def has_apm_dependencies(package_dir: Path) -> bool: + """True if the package declares an apm.yml with non-empty APM dependencies.""" + manifest = package_dir / "apm.yml" + if not manifest.exists(): + return False + try: + data = load_yaml(manifest) + except CpmError: + return False + deps = (data.get("dependencies") or {}).get("apm") or [] + return bool(deps) + + +def apm_cli_available() -> bool: + return shutil.which("apm") is not None + + +def ensure_apm_install(package_dir: Path, dry_run: bool, interactive: bool) -> None: + """Make sure the user knows about (and ideally ran) 'apm install'. + + The rendered preset only configures agents; the skills/MCP primitives + referenced by the team profile are deployed by APM. + """ + if not has_apm_dependencies(package_dir): + return + + print( + "\nAPM dependencies detected: this package references skills/instructions " + "from other packages that must be installed before the team can use them." + ) + if dry_run: + print(f" Dry run: run 'apm install' in {package_dir} yourself.") + return + + if apm_cli_available() and interactive and sys.stdin.isatty(): + answer = input("Run 'apm install' now? [y/N] ").strip().lower() + if answer in ("y", "yes"): + print() + result = subprocess.run(["apm", "install"], cwd=package_dir) + if result.returncode == 0: + print("\napm install finished successfully.") + else: + print( + f"\n'apm install' exited with code {result.returncode}. " + f"Fix the issue above and re-run it in {package_dir}.", + file=sys.stderr, + ) + return + else: + if not apm_cli_available(): + print( + " The 'apm' CLI was not found on PATH.\n" + " Install it via: pip install apm-cli (or: brew install microsoft/apm/apm)" + ) + print(f" Run manually:\n cd {package_dir}\n apm install") + + def cmd_render(args: argparse.Namespace) -> int: package_dir = Path(args.package).resolve() profile, profile_path = load_team_profile(package_dir, args.team_file) @@ -386,12 +445,26 @@ def cmd_render(args: argparse.Namespace) -> int: print(f"Provenance: {provenance_path}") _report(warnings) - print( - "\nNext steps:\n" - " 1. start opencode (project config gets loaded)\n" - f" 2. /preset {sanitize_preset_name(str(profile['id']))}\n" - " 3. reload OpenCode -> team is active" - ) + steps = [ + "\nNext steps:", + f" 1. start opencode from {output.parent.parent.resolve()} " + "(the config lives in its .opencode/ directory)", + ] + if has_apm_dependencies(package_dir): + steps.append( + " 2. run 'apm install' there first - the team references skills " + "from APM dependencies that are not deployed yet" + ) + steps.append( + f" 3. /preset {sanitize_preset_name(str(profile['id']))}" + ) + steps.append(" 4. reload OpenCode -> team is active") + else: + steps.append( + f" 2. /preset {sanitize_preset_name(str(profile['id']))}" + ) + steps.append(" 3. reload OpenCode -> team is active") + print("\n".join(steps)) return 0 @@ -423,21 +496,27 @@ def _report(warnings: list[str], dry_run: bool = False) -> None: # Interactive assistants +_MODELS_LISTED = False + + def _choose_model(model_class: str, models: list[str]) -> str: """Interactively pick a model for a model class.""" + global _MODELS_LISTED print(f"\nNo model assigned for '{model_class}'.") if models: - print("Available models (from opencode.json(c)):") - for i, model in enumerate(models, 1): - print(f" {i}. {model}") - print(" 0. Enter model ID manually") + if not _MODELS_LISTED: + print("Available models (from opencode.json(c)):") + for i, model in enumerate(models, 1): + print(f" {i}. {model}") + print(" 0. Enter model ID manually") + _MODELS_LISTED = True while True: - choice = input("Selection: ").strip() + choice = input(f"Selection for '{model_class}': ").strip() if choice == "0": return input("Model ID (provider/model): ").strip() if choice.isdigit() and 1 <= int(choice) <= len(models): return models[int(choice) - 1] - print("Invalid selection.") + print(f"Invalid selection. Enter 1-{len(models)} or 0.") else: print("No models found in opencode.json(c).") return input("Model ID (provider/model): ").strip() @@ -474,8 +553,13 @@ def cmd_init(args: argparse.Namespace) -> int: print(f"\n{remaining} entr(y/ies) left as PLACEHOLDER - " "please fill in manually.") - print("\nNext step:") - print(f" cpm.py check --package --mapping {mapping_path}") + print("\nNext steps:") + if mapping_path == DEFAULT_MAPPING_PATH: + print(" Mapping is in place. Validate or render a package, e.g.:") + print(" cpm.py check --package ./my-cpm-package") + print(" cpm.py setup --package ./my-cpm-package") + else: + print(f" cpm.py check --package --mapping {mapping_path}") return 0 @@ -525,7 +609,16 @@ def cmd_setup(args: argparse.Namespace) -> int: output=args.output, dry_run=args.dry_run, ) - return cmd_render(args_render) + result = cmd_render(args_render) + + # Deploy the package primitives (skills/instructions/MCP configs) that + # the team references. The preset alone only configures the agents. + ensure_apm_install( + package_dir, + dry_run=args.dry_run, + interactive=not args.dry_run, + ) + return result def main(argv: list[str] | None = None) -> int: diff --git a/corentic-package-manager/examples/microsoft-design-review/.gitignore b/corentic-package-manager/examples/microsoft-design-review/.gitignore new file mode 100644 index 0000000..bf3dd49 --- /dev/null +++ b/corentic-package-manager/examples/microsoft-design-review/.gitignore @@ -0,0 +1,2 @@ +# Generated artifacts +.opencode/ diff --git a/corentic-package-manager/examples/microsoft-issue-autopilot/.gitignore b/corentic-package-manager/examples/microsoft-issue-autopilot/.gitignore new file mode 100644 index 0000000..bf3dd49 --- /dev/null +++ b/corentic-package-manager/examples/microsoft-issue-autopilot/.gitignore @@ -0,0 +1,2 @@ +# Generated artifacts +.opencode/ diff --git a/corentic-package-manager/skills/cpm-setup/SKILL.md b/corentic-package-manager/skills/cpm-setup/SKILL.md index 79ad7da..6a6e81d 100644 --- a/corentic-package-manager/skills/cpm-setup/SKILL.md +++ b/corentic-package-manager/skills/cpm-setup/SKILL.md @@ -55,9 +55,24 @@ python3 corentic-package-manager/cpm.py render --package Show the output including warnings. The file `.opencode/oh-my-opencode-slim.json` is generated – never edit it manually. -### 5. Activate +### 5. Install package primitives -Guide the user: start `opencode` → `/preset ` → reload. Preset switches only take effect after a reload (by design). +If the package declares an `apm.yml` with APM dependencies, the skills/instructions the roles reference are NOT deployed by cpm – APM does that: + +```bash +cd +apm install # requires the apm CLI (pip install apm-cli) +``` + +`cpm setup` offers this step automatically; when guiding manually, always run it before activation if dependencies exist. + +### 6. Activate + +Guide the user: `cd ` (the generated config lives in its `.opencode/` directory!) → start `opencode` → `/preset ` → reload. Preset switches and skill loading both take effect only after a reload (by design). Activating writes the preset name to the global user config, so it persists across projects until switched again. + +## Using the team + +After activation the user works via the orchestrator in normal language ("Review X against our standards"). The orchestrator delegates per the generated routing prompts and consolidates results. Verify first with `/agents` and "ping all agents". ## Error handling