opencode-codeops 1.4.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/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/_shared/auto-design.md +129 -0
- package/_shared/layout-convention.md +198 -0
- package/_shared/quality-profile.md +134 -0
- package/_shared/recommendation-hardening.md +166 -0
- package/_shared/scope-expansion-control.md +176 -0
- package/_shared/spec-first-ordering.md +79 -0
- package/_shared/zero-ambiguity-gate.md +311 -0
- package/agent-templates/codebase-scout.md +17 -0
- package/agent-templates/concurrency-auditor.md +5 -0
- package/agent-templates/design-challenger.md +26 -0
- package/agent-templates/financial-integrity-auditor.md +5 -0
- package/agent-templates/perf-auditor.md +23 -0
- package/agent-templates/phase-reviewer.md +54 -0
- package/agent-templates/plan-task-executor-opus.md +46 -0
- package/agent-templates/plan-task-executor.md +43 -0
- package/agent-templates/preflight-auditor.md +45 -0
- package/agent-templates/security-auditor.md +42 -0
- package/agent-templates/semantics-reviewer.md +5 -0
- package/agent-templates/spec-test-author.md +29 -0
- package/agents/concurrency-auditor.md +15 -0
- package/agents/correctness-reviewer.md +66 -0
- package/agents/demanding-executor.md +58 -0
- package/agents/design-challenger.md +38 -0
- package/agents/executor.md +55 -0
- package/agents/explorer.md +29 -0
- package/agents/financial-integrity-auditor.md +15 -0
- package/agents/performance-auditor.md +35 -0
- package/agents/preflight-auditor.md +57 -0
- package/agents/security-auditor.md +54 -0
- package/agents/semantics-reviewer.md +15 -0
- package/agents/spec-test-author.md +41 -0
- package/bin/codeops-worktree +244 -0
- package/bin/index.mjs +106 -0
- package/bin/install-agents.mjs +453 -0
- package/bin/install-skills.mjs +466 -0
- package/bin/lib/opencode-install.mjs +185 -0
- package/install.sh +55 -0
- package/package.json +73 -0
- package/plugin/index.ts +181 -0
- package/references/domains/compiler-and-language.md +28 -0
- package/references/domains/data-and-migration.md +22 -0
- package/references/domains/distributed-and-concurrent.md +26 -0
- package/references/domains/financial-system.md +28 -0
- package/references/domains/selection.md +19 -0
- package/references/domains/web-application.md +23 -0
- package/schemas/codeops-config.schema.json +56 -0
- package/scripts/check-version.mjs +163 -0
- package/scripts/codeops-migrate.sh +355 -0
- package/scripts/codeops-roadmap-compact.sh +232 -0
- package/scripts/codeops-roadmap-sync.sh +275 -0
- package/scripts/codeops_outcomes.py +155 -0
- package/scripts/codeops_plan.py +239 -0
- package/scripts/codeops_plan_migrate.py +318 -0
- package/scripts/codeops_worktree_snapshot.py +99 -0
- package/scripts/install_agents.py +288 -0
- package/scripts/release.mjs +533 -0
- package/skills/analyze-project/SKILL.md +28 -0
- package/skills/clean-comments/SKILL.md +22 -0
- package/skills/exec-plan/SKILL.md +267 -0
- package/skills/exec-plan/commit-modes.md +113 -0
- package/skills/exec-plan/execution-protocol.md +471 -0
- package/skills/git-commit/SKILL.md +35 -0
- package/skills/github-issues/SKILL.md +38 -0
- package/skills/grill-me/SKILL.md +342 -0
- package/skills/make-plan/SKILL.md +282 -0
- package/skills/make-plan/quality-checklist.md +96 -0
- package/skills/make-plan/templates.md +535 -0
- package/skills/make-plan/zero-ambiguity-gate.md +19 -0
- package/skills/make-requirements/SKILL.md +268 -0
- package/skills/make-requirements/discovery-phases.md +255 -0
- package/skills/make-requirements/review-and-add.md +73 -0
- package/skills/make-requirements/templates.md +296 -0
- package/skills/make-requirements/zero-ambiguity-gate.md +18 -0
- package/skills/outcome-review/SKILL.md +34 -0
- package/skills/preflight/SKILL.md +310 -0
- package/skills/preflight/dimensions.md +181 -0
- package/skills/preflight/report-format.md +300 -0
- package/skills/retro-requirements/SKILL.md +218 -0
- package/skills/retro-requirements/confidence-classification.md +45 -0
- package/skills/retro-requirements/phases.md +609 -0
- package/skills/retro-requirements/triage-gate.md +135 -0
- package/skills/roadmap/SKILL.md +381 -0
- package/skills/roadmap/stage-hooks.md +80 -0
- package/skills/roadmap/template.md +200 -0
- package/skills/setup-codeops/SKILL.md +94 -0
- package/skills/setup-codeops/migration.md +106 -0
- package/skills/setup-codeops/scaffold.md +99 -0
- package/skills/setup-routing/SKILL.md +102 -0
- package/skills/setup-routing/routing.md +44 -0
- package/skills/techdocs/SKILL.md +199 -0
- package/skills/techdocs/authoring-and-update.md +178 -0
- package/skills/techdocs/templates.md +655 -0
- package/skills/techdocs/vitepress-setup.md +143 -0
- package/skills/upgrade-plan/SKILL.md +75 -0
- package/skills/upgrade-plan/content-quality-gate.md +35 -0
- package/skills/upgrade-plan/upgrade-checklists.md +107 -0
- package/standards/coding-standards-full.md +124 -0
- package/standards/coding-standards.md +64 -0
- package/standards/output-style.md +17 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
install_agents.py — CodeOps for OpenCode
|
|
4
|
+
Generates OpenCode agent Markdown files from codeops/codeops.json routing config.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 install_agents.py --project <path> --roles ROLE[,ROLE...]
|
|
8
|
+
python3 install_agents.py --project <path> --check
|
|
9
|
+
python3 install_agents.py --project <path> --dry-run --roles ROLE[,ROLE...]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Role → template-filename mapping (authoritative — drives dispatch resolution)
|
|
22
|
+
# OpenCode resolves agents by filename (sans .md); dispatch packets use role names.
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
ROLE_TO_TEMPLATE: dict[str, str] = {
|
|
25
|
+
"explorer": "codebase-scout",
|
|
26
|
+
"design-challenger": "design-challenger",
|
|
27
|
+
"performance-auditor": "perf-auditor",
|
|
28
|
+
"correctness-reviewer": "phase-reviewer",
|
|
29
|
+
"executor": "plan-task-executor",
|
|
30
|
+
"demanding-executor": "plan-task-executor-opus",
|
|
31
|
+
"preflight-auditor": "preflight-auditor",
|
|
32
|
+
"security-auditor": "security-auditor",
|
|
33
|
+
"spec-test-author": "spec-test-author",
|
|
34
|
+
"financial-integrity-auditor": "financial-integrity-auditor",
|
|
35
|
+
"concurrency-auditor": "concurrency-auditor",
|
|
36
|
+
"semantics-reviewer": "semantics-reviewer",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ALL_ROLES = list(ROLE_TO_TEMPLATE.keys())
|
|
40
|
+
|
|
41
|
+
# Default permissions per template (read-only audit vs full-access executor)
|
|
42
|
+
TEMPLATE_PERMISSIONS: dict[str, dict[str, str]] = {
|
|
43
|
+
"codebase-scout": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "deny"},
|
|
44
|
+
"design-challenger": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "deny"},
|
|
45
|
+
"perf-auditor": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "allow"},
|
|
46
|
+
"phase-reviewer": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "allow"},
|
|
47
|
+
"plan-task-executor": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "allow", "bash": "allow"},
|
|
48
|
+
"plan-task-executor-opus": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "allow", "bash": "allow"},
|
|
49
|
+
"preflight-auditor": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "allow"},
|
|
50
|
+
"security-auditor": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "deny", "bash": "allow"},
|
|
51
|
+
"spec-test-author": {"read": "allow", "grep": "allow", "glob": "allow", "edit": "allow", "bash": "allow"},
|
|
52
|
+
"financial-integrity-auditor": {"read": "allow", "edit": "deny", "bash": "deny"},
|
|
53
|
+
"concurrency-auditor": {"read": "allow", "edit": "deny", "bash": "deny"},
|
|
54
|
+
"semantics-reviewer": {"read": "allow", "edit": "deny", "bash": "deny"},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# effort -> temperature mapping
|
|
58
|
+
EFFORT_TO_TEMPERATURE: dict[str, float] = {
|
|
59
|
+
"high": 0.1,
|
|
60
|
+
"medium": 0.2,
|
|
61
|
+
"low": 0.3,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# sandbox -> permission overrides
|
|
65
|
+
SANDBOX_PERMISSIONS: dict[str, dict[str, str]] = {
|
|
66
|
+
"read-only": {"edit": "deny", "bash": "deny"},
|
|
67
|
+
"workspace-write": {"edit": "allow", "bash": "allow"},
|
|
68
|
+
"danger-full-access": {"edit": "allow", "bash": "allow"},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
CODEOPS_MARKER = "# Generated by CodeOps install_agents.py"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def find_plugin_root() -> Optional[Path]:
|
|
75
|
+
"""Locate the opencode-codeops package root via CODEOPS_PLUGIN_ROOT or this script's location."""
|
|
76
|
+
env_root = os.environ.get("CODEOPS_PLUGIN_ROOT")
|
|
77
|
+
if env_root and (Path(env_root) / "agent-templates").is_dir():
|
|
78
|
+
return Path(env_root)
|
|
79
|
+
# Fallback: this script lives in <package root>/scripts/.
|
|
80
|
+
script_dir = Path(__file__).parent
|
|
81
|
+
candidate = script_dir.parent
|
|
82
|
+
if (candidate / "agent-templates").is_dir():
|
|
83
|
+
return candidate
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def strip_frontmatter(content: str) -> str:
|
|
88
|
+
"""Remove YAML frontmatter block (--- ... ---) from the top of a Markdown file."""
|
|
89
|
+
if content.startswith("---"):
|
|
90
|
+
end = content.find("\n---", 3)
|
|
91
|
+
if end != -1:
|
|
92
|
+
return content[end + 4:].lstrip("\n")
|
|
93
|
+
return content
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def load_template_body(plugin_root: Path, template_name: str) -> str:
|
|
97
|
+
"""Load and return the body of an agent template (frontmatter stripped)."""
|
|
98
|
+
path = plugin_root / "agent-templates" / f"{template_name}.md"
|
|
99
|
+
if not path.exists():
|
|
100
|
+
raise FileNotFoundError(f"Agent template not found: {path}")
|
|
101
|
+
return strip_frontmatter(path.read_text(encoding="utf-8"))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def build_agent_frontmatter(
|
|
105
|
+
role: str,
|
|
106
|
+
template_name: str,
|
|
107
|
+
description: str,
|
|
108
|
+
temperature: float,
|
|
109
|
+
model: Optional[str],
|
|
110
|
+
permissions: dict[str, str],
|
|
111
|
+
sandbox: Optional[str],
|
|
112
|
+
) -> str:
|
|
113
|
+
"""Construct the OpenCode agent YAML frontmatter block."""
|
|
114
|
+
lines = [
|
|
115
|
+
CODEOPS_MARKER,
|
|
116
|
+
f"# Role: {role} | Template: {template_name}",
|
|
117
|
+
"# Do not edit this file manually — regenerate with: install_agents.py",
|
|
118
|
+
"---",
|
|
119
|
+
f"description: {description}",
|
|
120
|
+
"mode: subagent",
|
|
121
|
+
]
|
|
122
|
+
if model:
|
|
123
|
+
lines.append(f"model: {model}")
|
|
124
|
+
lines.append(f"temperature: {temperature}")
|
|
125
|
+
lines.append("hidden: true")
|
|
126
|
+
lines.append("permission:")
|
|
127
|
+
for key, value in permissions.items():
|
|
128
|
+
lines.append(f" {key}: {value}")
|
|
129
|
+
if sandbox == "danger-full-access":
|
|
130
|
+
lines.append("# WARNING: this role has danger-full-access sandbox — review carefully")
|
|
131
|
+
lines.append("---")
|
|
132
|
+
return "\n".join(lines) + "\n"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def load_template_description(plugin_root: Path, template_name: str) -> str:
|
|
136
|
+
"""Extract description from an agent template's original frontmatter."""
|
|
137
|
+
path = plugin_root / "agent-templates" / f"{template_name}.md"
|
|
138
|
+
if not path.exists():
|
|
139
|
+
return f"CodeOps {template_name} agent"
|
|
140
|
+
content = path.read_text(encoding="utf-8")
|
|
141
|
+
m = re.search(r"^description:\s*(.+)$", content, re.MULTILINE)
|
|
142
|
+
return m.group(1).strip() if m else f"CodeOps {template_name} agent"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def generate_agent_file(
|
|
146
|
+
plugin_root: Path,
|
|
147
|
+
role: str,
|
|
148
|
+
role_config: dict,
|
|
149
|
+
) -> str:
|
|
150
|
+
"""Generate the full content of an OpenCode agent .md file for a given role."""
|
|
151
|
+
template_name = ROLE_TO_TEMPLATE[role]
|
|
152
|
+
|
|
153
|
+
# Description from template
|
|
154
|
+
description = load_template_description(plugin_root, template_name)
|
|
155
|
+
|
|
156
|
+
# Temperature from effort
|
|
157
|
+
effort = role_config.get("effort", "high")
|
|
158
|
+
temperature = EFFORT_TO_TEMPERATURE.get(effort, 0.1)
|
|
159
|
+
|
|
160
|
+
# Model: only set if explicitly configured
|
|
161
|
+
model: Optional[str] = role_config.get("model") or None
|
|
162
|
+
|
|
163
|
+
# Permissions: start from template defaults, then apply sandbox overrides
|
|
164
|
+
permissions = dict(TEMPLATE_PERMISSIONS.get(template_name, {"read": "allow", "edit": "deny", "bash": "deny"}))
|
|
165
|
+
sandbox = role_config.get("sandbox")
|
|
166
|
+
if sandbox and sandbox in SANDBOX_PERMISSIONS:
|
|
167
|
+
permissions.update(SANDBOX_PERMISSIONS[sandbox])
|
|
168
|
+
|
|
169
|
+
frontmatter = build_agent_frontmatter(
|
|
170
|
+
role=role,
|
|
171
|
+
template_name=template_name,
|
|
172
|
+
description=description,
|
|
173
|
+
temperature=temperature,
|
|
174
|
+
model=model,
|
|
175
|
+
permissions=permissions,
|
|
176
|
+
sandbox=sandbox,
|
|
177
|
+
)
|
|
178
|
+
body = load_template_body(plugin_root, template_name)
|
|
179
|
+
return frontmatter + "\n" + body
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_codeops_generated(path: Path) -> bool:
|
|
183
|
+
"""Return True if the file was generated by this script (has the CodeOps marker)."""
|
|
184
|
+
if not path.exists():
|
|
185
|
+
return False
|
|
186
|
+
first_line = path.read_text(encoding="utf-8").split("\n", 1)[0]
|
|
187
|
+
return first_line.strip() == CODEOPS_MARKER
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main() -> int:
|
|
191
|
+
parser = argparse.ArgumentParser(
|
|
192
|
+
description="Generate OpenCode agent files from CodeOps routing config."
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument("--project", required=True, help="Path to the project root")
|
|
195
|
+
parser.add_argument("--roles", help="Comma-separated list of roles to install (default: all)")
|
|
196
|
+
parser.add_argument("--check", action="store_true", help="Check for missing or stale agents without writing")
|
|
197
|
+
parser.add_argument("--dry-run", action="store_true", help="Show what would be written without writing")
|
|
198
|
+
args = parser.parse_args()
|
|
199
|
+
|
|
200
|
+
project = Path(args.project).resolve()
|
|
201
|
+
config_path = project / "codeops" / "codeops.json"
|
|
202
|
+
|
|
203
|
+
# Load routing config if present
|
|
204
|
+
routing_roles: dict[str, dict] = {}
|
|
205
|
+
if config_path.exists():
|
|
206
|
+
try:
|
|
207
|
+
cfg = json.loads(config_path.read_text(encoding="utf-8"))
|
|
208
|
+
routing_roles = cfg.get("routing", {}).get("roles", {})
|
|
209
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
210
|
+
print(f"Warning: could not parse {config_path}: {e}", file=sys.stderr)
|
|
211
|
+
|
|
212
|
+
# Determine which roles to process
|
|
213
|
+
if args.roles:
|
|
214
|
+
requested = [r.strip() for r in args.roles.split(",")]
|
|
215
|
+
unknown = [r for r in requested if r not in ROLE_TO_TEMPLATE]
|
|
216
|
+
if unknown:
|
|
217
|
+
print(f"Error: unknown roles: {', '.join(unknown)}", file=sys.stderr)
|
|
218
|
+
print(f"Available roles: {', '.join(ALL_ROLES)}", file=sys.stderr)
|
|
219
|
+
return 1
|
|
220
|
+
roles = requested
|
|
221
|
+
else:
|
|
222
|
+
roles = ALL_ROLES
|
|
223
|
+
|
|
224
|
+
# Find plugin root
|
|
225
|
+
plugin_root = find_plugin_root()
|
|
226
|
+
if plugin_root is None:
|
|
227
|
+
print(
|
|
228
|
+
"Error: could not locate opencode-codeops plugin root. "
|
|
229
|
+
"Set CODEOPS_PLUGIN_ROOT or run from within the plugin directory.",
|
|
230
|
+
file=sys.stderr,
|
|
231
|
+
)
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
agents_dir = project / ".opencode" / "agents"
|
|
235
|
+
|
|
236
|
+
if args.check:
|
|
237
|
+
# Check mode: report missing or stale files
|
|
238
|
+
issues = []
|
|
239
|
+
for role in roles:
|
|
240
|
+
out_path = agents_dir / f"{role}.md"
|
|
241
|
+
if not out_path.exists():
|
|
242
|
+
issues.append(f"MISSING: {out_path}")
|
|
243
|
+
elif not is_codeops_generated(out_path):
|
|
244
|
+
issues.append(f"HAND-AUTHORED (skip): {out_path}")
|
|
245
|
+
if issues:
|
|
246
|
+
print("Agent check found issues:")
|
|
247
|
+
for i in issues:
|
|
248
|
+
print(f" {i}")
|
|
249
|
+
return 1
|
|
250
|
+
else:
|
|
251
|
+
print(f"OK: all {len(roles)} CodeOps agent files present.")
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
# Generate and write (or dry-run)
|
|
255
|
+
if not args.dry_run:
|
|
256
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
|
|
258
|
+
written = 0
|
|
259
|
+
skipped = 0
|
|
260
|
+
for role in roles:
|
|
261
|
+
out_path = agents_dir / f"{role}.md"
|
|
262
|
+
|
|
263
|
+
# Preserve hand-authored files
|
|
264
|
+
if out_path.exists() and not is_codeops_generated(out_path):
|
|
265
|
+
print(f" SKIP (hand-authored): {out_path.name}")
|
|
266
|
+
skipped += 1
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
role_config = routing_roles.get(role, {})
|
|
270
|
+
content = generate_agent_file(plugin_root, role, role_config)
|
|
271
|
+
|
|
272
|
+
if args.dry_run:
|
|
273
|
+
print(f" [dry-run] would write: {out_path}")
|
|
274
|
+
print(f" model={role_config.get('model', '(inherit)')}, "
|
|
275
|
+
f"effort={role_config.get('effort', 'high')}, "
|
|
276
|
+
f"sandbox={role_config.get('sandbox', 'default')}")
|
|
277
|
+
else:
|
|
278
|
+
out_path.write_text(content, encoding="utf-8")
|
|
279
|
+
print(f" wrote: {out_path.name}")
|
|
280
|
+
written += 1
|
|
281
|
+
|
|
282
|
+
if not args.dry_run:
|
|
283
|
+
print(f"\nDone: {written} written, {skipped} skipped (hand-authored).")
|
|
284
|
+
return 0
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
sys.exit(main())
|