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.
- package/LICENSE +674 -0
- package/README.md +260 -0
- package/SKILL.md +89 -0
- package/dist/extension.d.ts +25 -0
- package/dist/extension.js +287 -0
- package/examples/rust/README.md +22 -0
- package/examples/rust/profile.yaml +51 -0
- package/package.json +71 -0
- package/references/dispatch-model-semantics.md +110 -0
- package/references/gate-runner-usage.md +19 -0
- package/references/migration-from-rust-agent-workflow.md +107 -0
- package/references/profile-schema.md +66 -0
- package/references/registry-resolution.md +14 -0
- package/references/scaffolder-usage.md +27 -0
- package/references/sync-settings-usage.md +67 -0
- package/registry/aliases.yaml +38 -0
- package/registry/built_in.yaml +42 -0
- package/requirements.txt +2 -0
- package/role-packs/coding/coding-architect.md +33 -0
- package/role-packs/coding/coding-auditor.md +31 -0
- package/role-packs/coding/coding-canary.md +31 -0
- package/role-packs/coding/coding-docs.md +32 -0
- package/role-packs/coding/coding-implementer.md +33 -0
- package/role-packs/coding/coding-mapper.md +31 -0
- package/role-packs/coding/coding-orchestrator.md +28 -0
- package/role-packs/coding/coding-planner.md +31 -0
- package/role-packs/coding/coding-profiler.md +31 -0
- package/role-packs/coding/coding-reviewer.md +32 -0
- package/role-packs/coding/coding-tester.md +32 -0
- package/scripts/gate_runner.py +155 -0
- package/scripts/install.sh +120 -0
- package/scripts/profile_loader.py +633 -0
- package/scripts/scaffolder.py +289 -0
- package/scripts/sync_settings.py +362 -0
- package/templates/blank.yaml +13 -0
- package/templates/go.yaml +44 -0
- package/templates/python.yaml +46 -0
- package/templates/rust.yaml +49 -0
- 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
|