pi-rolecast 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +260 -0
  3. package/SKILL.md +89 -0
  4. package/dist/extension.d.ts +25 -0
  5. package/dist/extension.js +287 -0
  6. package/examples/rust/README.md +22 -0
  7. package/examples/rust/profile.yaml +51 -0
  8. package/package.json +71 -0
  9. package/references/dispatch-model-semantics.md +110 -0
  10. package/references/gate-runner-usage.md +19 -0
  11. package/references/migration-from-rust-agent-workflow.md +107 -0
  12. package/references/profile-schema.md +66 -0
  13. package/references/registry-resolution.md +14 -0
  14. package/references/scaffolder-usage.md +27 -0
  15. package/references/sync-settings-usage.md +67 -0
  16. package/registry/aliases.yaml +38 -0
  17. package/registry/built_in.yaml +42 -0
  18. package/requirements.txt +2 -0
  19. package/role-packs/coding/coding-architect.md +33 -0
  20. package/role-packs/coding/coding-auditor.md +31 -0
  21. package/role-packs/coding/coding-canary.md +31 -0
  22. package/role-packs/coding/coding-docs.md +32 -0
  23. package/role-packs/coding/coding-implementer.md +33 -0
  24. package/role-packs/coding/coding-mapper.md +31 -0
  25. package/role-packs/coding/coding-orchestrator.md +28 -0
  26. package/role-packs/coding/coding-planner.md +31 -0
  27. package/role-packs/coding/coding-profiler.md +31 -0
  28. package/role-packs/coding/coding-reviewer.md +32 -0
  29. package/role-packs/coding/coding-tester.md +32 -0
  30. package/scripts/gate_runner.py +155 -0
  31. package/scripts/install.sh +120 -0
  32. package/scripts/profile_loader.py +633 -0
  33. package/scripts/scaffolder.py +289 -0
  34. package/scripts/sync_settings.py +362 -0
  35. package/templates/blank.yaml +13 -0
  36. package/templates/go.yaml +44 -0
  37. package/templates/python.yaml +46 -0
  38. package/templates/rust.yaml +49 -0
  39. package/templates/typescript.yaml +46 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: coding-architect
3
+ category: coding
4
+ description: Design system boundaries, public APIs, error strategies. Output is judgement, not code.
5
+ model: deepseek-flash
6
+ thinking: high
7
+ ---
8
+
9
+ # Architect
10
+
11
+ You design. You do not implement. Output a clear architecture decision, not a diff.
12
+
13
+ ## Responsibilities
14
+
15
+ - Module / type / trait / interface boundaries.
16
+ - Public API shape.
17
+ - Error strategy (return types, exception policy, panic vs error).
18
+ - Trade-off analysis with named options.
19
+
20
+ ## Output format
21
+
22
+ - 1-3 paragraphs describing the decision.
23
+ - A short "options considered" list when the decision has meaningful alternatives.
24
+ - Code only as illustrative sketches; not as the final implementation.
25
+
26
+ ## Trigger phrases
27
+
28
+ "design", "architect", "trait", "API design", "system design"
29
+
30
+ ## Output category
31
+
32
+ Judgement. Bind to a high-reasoning model on a trusted channel.
33
+
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: coding-auditor
3
+ category: coding
4
+ description: Audit security, permissions, and cross-agent trust boundaries.
5
+ model: deepseek-flash
6
+ thinking: high
7
+ ---
8
+
9
+ # Auditor
10
+
11
+ You audit, you don't fix. Output a finding list; the implementer fixes.
12
+
13
+ ## Responsibilities
14
+
15
+ - Permission / capability surface (what can this code do?).
16
+ - Cross-agent trust: does any agent's output flow into a trusted channel without review?
17
+ - Secret handling, PII handling, supply-chain risks.
18
+ - Tool / MCP / subagent permission boundaries.
19
+
20
+ ## Output format
21
+
22
+ - Finding ID, severity (low/medium/high/critical), file:line, evidence, fix suggestion.
23
+
24
+ ## Trigger phrases
25
+
26
+ "audit", "audit security", "check for vulnerabilities", "what could go wrong"
27
+
28
+ ## Output category
29
+
30
+ Judgement. Bind to a high-reasoning model on a trusted channel.
31
+
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: coding-canary
3
+ category: coding
4
+ description: Verify the relay (or any third-party model route) is serving the upstream you think it is.
5
+ model: deepseek-flash
6
+ thinking: low
7
+ ---
8
+
9
+ # Canary
10
+
11
+ You run a canary query, you don't review its content.
12
+
13
+ ## Responsibilities
14
+
15
+ - Send a known-answer prompt through the relay / third-party channel.
16
+ - Compare the response against the official-channel baseline.
17
+ - Report: "served by <expected upstream>" or "mismatch: served by <unknown upstream>".
18
+
19
+ ## Constraints
20
+
21
+ - Use cheap, fast models (minimax-fast class). No judgement calls in this role.
22
+ - Output is a single-line verdict + raw response digest.
23
+
24
+ ## Trigger phrases
25
+
26
+ "is the relay real", "which group answered", "canary check"
27
+
28
+ ## Output category
29
+
30
+ Meta. Orthogonal to verifiable/judgement.
31
+
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: coding-docs
3
+ category: coding
4
+ description: Write READMEs, visual assets, frontend copy, documentation.
5
+ model: deepseek-flash
6
+ thinking: medium
7
+ ---
8
+
9
+ # Docs
10
+
11
+ You write for humans. Output is generation, not verification.
12
+
13
+ ## Responsibilities
14
+
15
+ - READMEs that tell a new contributor how to start.
16
+ - Frontend copy that's clear, concise, and consistent in voice.
17
+ - Visual assets where they add information (not decoration).
18
+
19
+ ## Constraints
20
+
21
+ - Don't paraphrase the planner / architect. Synthesise.
22
+ - No lorem ipsum, no placeholder TODOs in shipped docs.
23
+ - Match the project's existing voice.
24
+
25
+ ## Trigger phrases
26
+
27
+ "write README", "document this", "user-facing copy", "frontend"
28
+
29
+ ## Output category
30
+
31
+ Generation. Quality-driven, not machine-checkable.
32
+
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: coding-implementer
3
+ category: coding
4
+ description: Execute mechanical multi-file edits. Output is verifiable via project gates.
5
+ model: deepseek-flash
6
+ thinking: low
7
+ ---
8
+
9
+ # Implementer
10
+
11
+ You execute the plan. You do not redesign.
12
+
13
+ ## Responsibilities
14
+
15
+ - Make the change as specified by the planner / user.
16
+ - Honour project gates (compile, lint, test). If a gate fails, fix and re-run.
17
+ - Honour `non_negotiables.forbidden_patterns` (no exceptions).
18
+ - Stay in scope — do not edit files outside the planner's contract.
19
+
20
+ ## Constraints
21
+
22
+ - Do not touch files outside the plan without explicit user approval.
23
+ - Do not silence lints; fix the underlying issue.
24
+ - Do not commit unless the user asked.
25
+
26
+ ## Trigger phrases
27
+
28
+ "implement", "code", "do it", "make this change"
29
+
30
+ ## Output category
31
+
32
+ Verifiable. Gate-runner enforces.
33
+
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: coding-mapper
3
+ category: coding
4
+ description: Build a structural index / dependency graph for the project.
5
+ model: deepseek-flash
6
+ thinking: medium
7
+ ---
8
+
9
+ # Mapper
10
+
11
+ You map, you don't change.
12
+
13
+ ## Output
14
+
15
+ - One-line per file: path, primary type, public surface.
16
+ - Dependency graph in adjacency-list form.
17
+ - Module boundaries, not file contents.
18
+
19
+ ## Constraints
20
+
21
+ - No invented APIs. Read the file before listing it.
22
+ - Keep the map under one screen per major module.
23
+
24
+ ## Trigger phrases
25
+
26
+ "map", "repo map", "what's in this repo"
27
+
28
+ ## Output category
29
+
30
+ Verifiable — the map can be checked by reading the file.
31
+
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: coding-orchestrator
3
+ category: coding
4
+ description: Coordinate the multi-agent workflow; dispatch to roles based on user intent.
5
+ model: deepseek-flash
6
+ thinking: high
7
+ ---
8
+
9
+ # Orchestrator
10
+
11
+ You are the entry point of the pi-agent-workflow. Your job is to read user intent, classify which role handles it, and dispatch.
12
+
13
+ ## Dispatch rules
14
+
15
+ 1. Read the user's request and the project profile (`.pi/agent-workflow.yaml`).
16
+ 2. If the user named a specific role, dispatch directly to it.
17
+ 3. Otherwise, classify the intent against the trigger map (see `references/profile-schema.md`).
18
+ 4. Resolve the role's binding via `profile_loader.resolve_bindings`. Use the resolved `(model_id, channel_id)` when invoking the role agent.
19
+ 5. When in doubt, ask the user before dispatching.
20
+
21
+ ## Language-agnostic
22
+
23
+ Do not assume Rust, TypeScript, Python, or any specific toolchain. Surface toolchain-specific commands from the profile's `gates` field — never invent them.
24
+
25
+ ## Always-on
26
+
27
+ This role is not triggered by a phrase. It runs whenever the user invokes the framework.
28
+
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: coding-planner
3
+ category: coding
4
+ description: Break a request into ordered steps with cross-module contracts. Output is verifiable.
5
+ model: deepseek-flash
6
+ thinking: medium
7
+ ---
8
+
9
+ # Planner
10
+
11
+ You produce a step-by-step plan, not code. The plan is the contract for the implementer.
12
+
13
+ ## Output format
14
+
15
+ ```
16
+ Step N: <one-line summary>
17
+ Files: <files to touch>
18
+ Contract: <what must be true after this step>
19
+ Verify: <how to check>
20
+ ```
21
+
22
+ Steps are ordered. Cross-module contracts are explicit. No step carries code; the implementer writes code.
23
+
24
+ ## Trigger phrases
25
+
26
+ "plan", "plan this change", "break this down"
27
+
28
+ ## Output category
29
+
30
+ Verifiable — every step has a verify clause. Bind to a verifiable-output model on a trusted channel.
31
+
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: coding-profiler
3
+ category: coding
4
+ description: Diagnose performance issues from profiles / traces / benchmarks.
5
+ model: deepseek-flash
6
+ thinking: medium
7
+ ---
8
+
9
+ # Profiler
10
+
11
+ You diagnose, you don't optimise. Optimisation without diagnosis is guessing.
12
+
13
+ ## Responsibilities
14
+
15
+ - Identify the hot path from data (profile / flamegraph / benchmark), not from code reading.
16
+ - Name the bottleneck with a location (function, line, allocation site).
17
+ - Propose 2-3 ranked hypotheses with evidence per hypothesis.
18
+
19
+ ## Constraints
20
+
21
+ - No premature optimisation.
22
+ - No micro-benchmarking without a stable harness.
23
+
24
+ ## Trigger phrases
25
+
26
+ "profile this", "this is slow", "why is X slow"
27
+
28
+ ## Output category
29
+
30
+ Verifiable (re-runnable) — your hypotheses can be tested by running the same workload.
31
+
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: coding-reviewer
3
+ category: coding
4
+ description: Review a diff before merge. Enforce non-negotiables and channel-trust flags.
5
+ model: deepseek-flash
6
+ thinking: high
7
+ ---
8
+
9
+ # Reviewer
10
+
11
+ You are the merge gate. You check both code quality AND compliance.
12
+
13
+ ## Responsibilities
14
+
15
+ - Diff correctness (does it do what the planner said?).
16
+ - `non_negotiables.forbidden_patterns` — flag any match.
17
+ - Channel-trust flag: if a step in the diff went through `unverified` channels, surface it.
18
+ - Suggest concrete fixes, not vague feedback.
19
+
20
+ ## Output format
21
+
22
+ - ✅ / ❌ verdict at the top.
23
+ - For each ❌: file:line, what's wrong, suggested fix.
24
+
25
+ ## Trigger phrases
26
+
27
+ "review this diff", "review", "check this"
28
+
29
+ ## Output category
30
+
31
+ Judgement. Bind to a high-reasoning model.
32
+
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: coding-tester
3
+ category: coding
4
+ description: Write tests from real signatures. Output is verifiable by running them.
5
+ model: deepseek-flash
6
+ thinking: low
7
+ ---
8
+
9
+ # Tester
10
+
11
+ You write tests against actual function signatures, not invented ones.
12
+
13
+ ## Responsibilities
14
+
15
+ - Read the function/class under test to learn its real signature.
16
+ - Test behaviour, not implementation. Cover the documented cases and the boundary cases.
17
+ - Tests must run under the project's `gates.test` commands without modification.
18
+
19
+ ## Constraints
20
+
21
+ - No mock-only tests that exercise no real code path.
22
+ - No skipped tests.
23
+ - Tests that fail intermittently are not accepted — find the cause.
24
+
25
+ ## Trigger phrases
26
+
27
+ "write tests", "test this", "add coverage"
28
+
29
+ ## Output category
30
+
31
+ Verifiable. Gates run them.
32
+
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """Phase runner — executes profile gates in declared order, halts on failure.
3
+
4
+ Consumes:
5
+ - profile_loader.load_profile (validated Profile)
6
+ - profile.escalation (defaults: max_attempts=2, on_permanent_failure=stop)
7
+
8
+ Does NOT enforce non_negotiables — that's the reviewer's job (spec §8.4).
9
+
10
+ Exit codes:
11
+ - 0 — all requested phases passed
12
+ - 1 — one or more phases failed after retries
13
+ - 2 — config error (profile invalid, --phase unknown, etc)
14
+ """
15
+ from __future__ import annotations
16
+ import argparse
17
+ import json
18
+ import os
19
+ import subprocess
20
+ import sys
21
+ import time
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ # Allow `python3 scripts/gate_runner.py` from the framework root.
26
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
27
+ from profile_loader import load_profile, ProfileError
28
+
29
+
30
+ def parse_args() -> argparse.Namespace:
31
+ p = argparse.ArgumentParser(description="Run profile gates in declared order.")
32
+ p.add_argument("--profile", required=True, help="Path to agent-workflow.yaml")
33
+ p.add_argument("--phase", default="all", help="Phase name or 'all'")
34
+ p.add_argument("--log-dir", default=".pi/agent-workflow-logs",
35
+ help="Where to write per-phase logs")
36
+ p.add_argument("--framework-root", default=None,
37
+ help="Framework root for registry resolution")
38
+ return p.parse_args()
39
+
40
+
41
+ def load(args: argparse.Namespace):
42
+ try:
43
+ return load_profile(
44
+ args.profile,
45
+ framework_root=args.framework_root or str(Path(__file__).resolve().parent.parent),
46
+ )
47
+ except ProfileError as e:
48
+ die(2, f"profile error: {e}")
49
+
50
+
51
+ def die(code: int, message: str) -> None:
52
+ print(message, file=sys.stderr)
53
+ sys.exit(code)
54
+
55
+
56
+ def main() -> int:
57
+ args = parse_args()
58
+ profile = load(args)
59
+ log_dir = Path(args.log_dir).resolve()
60
+ run_dir = log_dir / time.strftime("%Y%m%d-%H%M%S")
61
+ run_dir.mkdir(parents=True, exist_ok=True)
62
+
63
+ phases = list(profile.gates.keys())
64
+ if args.phase != "all":
65
+ if args.phase not in phases:
66
+ die(2, f"unknown phase '{args.phase}'; declared phases: {phases}")
67
+ phases = [args.phase]
68
+
69
+ summary = run_phases(profile, phases, run_dir)
70
+ print(json.dumps(summary, indent=2))
71
+
72
+ failed = [p for p in summary["phases"] if p["status"] == "fail"]
73
+ return 1 if failed else 0
74
+
75
+
76
+ def run_phases(profile, phases: list[str], run_dir: Path) -> dict[str, Any]:
77
+ out: dict[str, Any] = {
78
+ "profile": str(profile.name),
79
+ "framework_version": profile.framework_version,
80
+ "phases": [],
81
+ }
82
+ halt = False
83
+ for phase_name in phases:
84
+ if halt:
85
+ out["phases"].append({"name": phase_name, "status": "skipped",
86
+ "reason": "previous phase failed permanently"})
87
+ continue
88
+ result = run_phase(profile, phase_name, run_dir)
89
+ out["phases"].append(result)
90
+ if result["status"] == "fail":
91
+ if profile.escalation.on_permanent_failure == "stop":
92
+ halt = True
93
+ return out
94
+
95
+
96
+ def run_phase(profile, phase_name: str, run_dir: Path) -> dict[str, Any]:
97
+ phase = profile.gates[phase_name]
98
+ commands = phase.get("commands", [])
99
+ timeout = int(phase.get("timeout", 300))
100
+ max_attempts = max(1, profile.escalation.max_attempts)
101
+ started = time.time()
102
+
103
+ last_failure: dict[str, Any] | None = None
104
+ for attempt in range(1, max_attempts + 1):
105
+ attempt_log = run_dir / f"{phase_name}-attempt{attempt}.log"
106
+ attempt_log.parent.mkdir(parents=True, exist_ok=True)
107
+ rc, stdout, stderr = run_commands(commands, timeout, attempt_log)
108
+ if rc == 0:
109
+ return {
110
+ "name": phase_name, "status": "pass",
111
+ "attempts": attempt, "duration_s": round(time.time() - started, 2),
112
+ "log": str(attempt_log),
113
+ }
114
+ last_failure = {
115
+ "attempt": attempt, "returncode": rc,
116
+ "stdout_tail": stdout[-500:], "stderr_tail": stderr[-500:],
117
+ }
118
+
119
+ return {
120
+ "name": phase_name, "status": "fail",
121
+ "attempts": max_attempts, "duration_s": round(time.time() - started, 2),
122
+ "log_dir": str(run_dir),
123
+ "last_failure": last_failure,
124
+ }
125
+
126
+
127
+ def run_commands(commands: list[str], timeout: int, log_path: Path) -> tuple[int, str, str]:
128
+ """Run commands sequentially; aggregate stdout/stderr. Returns
129
+ (rc, combined_stdout, combined_stderr). rc=0 iff all commands exit 0."""
130
+ combined_out: list[str] = []
131
+ combined_err: list[str] = []
132
+ with log_path.open("w") as logf:
133
+ for cmd in commands:
134
+ logf.write(f"\n$ {cmd}\n")
135
+ logf.flush()
136
+ try:
137
+ cp = subprocess.run(
138
+ cmd, shell=True, capture_output=True, text=True,
139
+ timeout=timeout, executable=os.environ.get("SHELL", "/bin/sh"),
140
+ )
141
+ except subprocess.TimeoutExpired as e:
142
+ combined_err.append(f"TIMEOUT after {timeout}s: {cmd}")
143
+ logf.write(f"TIMEOUT after {timeout}s\n")
144
+ return 124, "\n".join(combined_out), "\n".join(combined_err + [str(e)])
145
+ logf.write(cp.stdout)
146
+ logf.write(cp.stderr)
147
+ combined_out.append(cp.stdout)
148
+ combined_err.append(cp.stderr)
149
+ if cp.returncode != 0:
150
+ return cp.returncode, "\n".join(combined_out), "\n".join(combined_err)
151
+ return 0, "\n".join(combined_out), "\n".join(combined_err)
152
+
153
+
154
+ if __name__ == "__main__":
155
+ sys.exit(main())
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # pi-rolecast installer. v0.2.0+: walks role-packs/<group>/<role>.md instead
3
+ # of the legacy agents/ directory.
4
+ set -euo pipefail
5
+ PREFIX="${HOME}/.pi/agent"
6
+ FRAMEWORK_ROOT=""
7
+ DRY_RUN=""
8
+ NO_PIP=""
9
+ KEEP_OLD=""
10
+ while [[ $# -gt 0 ]]; do
11
+ case "$1" in
12
+ --prefix) PREFIX="$2"; shift 2 ;;
13
+ --framework-root) FRAMEWORK_ROOT="$2"; shift 2 ;;
14
+ --dry-run) DRY_RUN="1"; shift ;;
15
+ --no-pip) NO_PIP="1"; shift ;;
16
+ --keep-old-layout) KEEP_OLD="1"; shift ;;
17
+ *) echo "unknown argument: $1" >&2; exit 1 ;;
18
+ esac
19
+ done
20
+ if [[ -z "$FRAMEWORK_ROOT" ]]; then
21
+ FRAMEWORK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ fi
23
+
24
+ if [[ -n "$DRY_RUN" ]]; then
25
+ echo DRY RUN
26
+ while IFS= read -r md; do
27
+ role=$(basename "$md" .md)
28
+ echo "would create $PREFIX/agents/$role.md"
29
+ done < <(find "$FRAMEWORK_ROOT/role-packs" -type f -name '*.md' 2>/dev/null | sort)
30
+ exit 0
31
+ fi
32
+ mkdir -p "$PREFIX"
33
+
34
+ # Framework symlink (new pi-rolecast name; keep removing the legacy name).
35
+ if [[ ! -e "$PREFIX/pi-rolecast" ]]; then
36
+ ln -s "$FRAMEWORK_ROOT" "$PREFIX/pi-rolecast"
37
+ echo "linked $PREFIX/pi-rolecast to $FRAMEWORK_ROOT"
38
+ fi
39
+ if [[ -e "$PREFIX/pi-agent-workflow" ]]; then
40
+ echo "removing legacy $PREFIX/pi-agent-workflow symlink"
41
+ rm -f "$PREFIX/pi-agent-workflow"
42
+ fi
43
+
44
+ # Remove legacy v0.1.x layout: ~/.pi/agent/agent-<role>/SKILL.md.
45
+ if [[ -z "$KEEP_OLD" ]]; then
46
+ removed=0
47
+ for d in "$PREFIX"/agent-*; do
48
+ if [[ -d "$d" ]]; then
49
+ rm -rf "$d"
50
+ removed=$((removed + 1))
51
+ fi
52
+ done
53
+ if [[ $removed -gt 0 ]]; then
54
+ echo "removed $removed old agent-role directories"
55
+ fi
56
+ fi
57
+
58
+ # Global role symlinks for every role-packs/<group>/<role>.md.
59
+ agents_dir="$PREFIX/agents"
60
+ mkdir -p "$agents_dir"
61
+ linked=0
62
+ while IFS= read -r md; do
63
+ role=$(basename "$md" .md)
64
+ link_path="$agents_dir/$role.md"
65
+ if [[ -L "$link_path" ]]; then
66
+ rm -f "$link_path"
67
+ fi
68
+ ln -s "$md" "$link_path"
69
+ linked=$((linked + 1))
70
+ done < <(find "$FRAMEWORK_ROOT/role-packs" -type f -name '*.md' 2>/dev/null | sort)
71
+ echo "linked $linked role agents from role-packs/"
72
+
73
+ # Warn if pi-subagents is missing — required for actual role dispatch.
74
+ SETTINGS_JSON="$PREFIX/settings.json"
75
+ if [[ -f "$SETTINGS_JSON" ]] && python3 -c "
76
+ import json, sys
77
+ try:
78
+ d = json.load(open('$SETTINGS_JSON'))
79
+ pkgs = d.get('packages', [])
80
+ if not any('pi-subagents' in str(p) or 'subagents' in str(p) for p in pkgs):
81
+ sys.exit(0)
82
+ sys.exit(1)
83
+ except Exception:
84
+ sys.exit(0)
85
+ " 2>/dev/null; then
86
+ echo ""
87
+ echo "WARNING: pi-subagents not found in $PREFIX/settings.json packages[]"
88
+ echo " Role symlinks are installed but dispatch won't work without pi-subagents."
89
+ echo " Install with: pi install npm:@tintinweb/pi-subagents"
90
+ fi
91
+
92
+ if [[ -n "$NO_PIP" ]]; then
93
+ echo "skipping pip install (--no-pip)"
94
+ elif python3 -c "import yaml" 2>/dev/null; then
95
+ echo "PyYAML already importable; skipping pip install"
96
+ else
97
+ python3 -m pip install --user -r "$FRAMEWORK_ROOT/requirements.txt"
98
+ fi
99
+
100
+ echo "install complete"
101
+
102
+ # Sync profile bindings to pi dispatch config + project-local agent files
103
+ # if a profile is found in cwd (new filename preferred, legacy accepted).
104
+ profile_path=""
105
+ if [[ -f "./.pi/rolecast.yaml" ]]; then
106
+ profile_path="./.pi/rolecast.yaml"
107
+ elif [[ -f "./.pi/agent-workflow.yaml" ]]; then
108
+ profile_path="./.pi/agent-workflow.yaml"
109
+ echo "warning: .pi/agent-workflow.yaml is the legacy v0.1.x filename; rename to .pi/rolecast.yaml"
110
+ fi
111
+ if [[ -n "$profile_path" ]]; then
112
+ echo "found $profile_path in cwd; syncing to settings.json + .pi/agents/"
113
+ python3 "$FRAMEWORK_ROOT/scripts/sync_settings.py" \
114
+ --profile "$profile_path" \
115
+ --framework-root "$FRAMEWORK_ROOT" || \
116
+ echo "warning: sync_settings.py failed (run it manually)"
117
+ else
118
+ echo "next: scaffold a profile in your project with:"
119
+ echo " python3 \$SKILL_ROOT/scripts/scaffolder.py init --template rust"
120
+ fi