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.
Files changed (102) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/LICENSE +21 -0
  3. package/README.md +171 -0
  4. package/_shared/auto-design.md +129 -0
  5. package/_shared/layout-convention.md +198 -0
  6. package/_shared/quality-profile.md +134 -0
  7. package/_shared/recommendation-hardening.md +166 -0
  8. package/_shared/scope-expansion-control.md +176 -0
  9. package/_shared/spec-first-ordering.md +79 -0
  10. package/_shared/zero-ambiguity-gate.md +311 -0
  11. package/agent-templates/codebase-scout.md +17 -0
  12. package/agent-templates/concurrency-auditor.md +5 -0
  13. package/agent-templates/design-challenger.md +26 -0
  14. package/agent-templates/financial-integrity-auditor.md +5 -0
  15. package/agent-templates/perf-auditor.md +23 -0
  16. package/agent-templates/phase-reviewer.md +54 -0
  17. package/agent-templates/plan-task-executor-opus.md +46 -0
  18. package/agent-templates/plan-task-executor.md +43 -0
  19. package/agent-templates/preflight-auditor.md +45 -0
  20. package/agent-templates/security-auditor.md +42 -0
  21. package/agent-templates/semantics-reviewer.md +5 -0
  22. package/agent-templates/spec-test-author.md +29 -0
  23. package/agents/concurrency-auditor.md +15 -0
  24. package/agents/correctness-reviewer.md +66 -0
  25. package/agents/demanding-executor.md +58 -0
  26. package/agents/design-challenger.md +38 -0
  27. package/agents/executor.md +55 -0
  28. package/agents/explorer.md +29 -0
  29. package/agents/financial-integrity-auditor.md +15 -0
  30. package/agents/performance-auditor.md +35 -0
  31. package/agents/preflight-auditor.md +57 -0
  32. package/agents/security-auditor.md +54 -0
  33. package/agents/semantics-reviewer.md +15 -0
  34. package/agents/spec-test-author.md +41 -0
  35. package/bin/codeops-worktree +244 -0
  36. package/bin/index.mjs +106 -0
  37. package/bin/install-agents.mjs +453 -0
  38. package/bin/install-skills.mjs +466 -0
  39. package/bin/lib/opencode-install.mjs +185 -0
  40. package/install.sh +55 -0
  41. package/package.json +73 -0
  42. package/plugin/index.ts +181 -0
  43. package/references/domains/compiler-and-language.md +28 -0
  44. package/references/domains/data-and-migration.md +22 -0
  45. package/references/domains/distributed-and-concurrent.md +26 -0
  46. package/references/domains/financial-system.md +28 -0
  47. package/references/domains/selection.md +19 -0
  48. package/references/domains/web-application.md +23 -0
  49. package/schemas/codeops-config.schema.json +56 -0
  50. package/scripts/check-version.mjs +163 -0
  51. package/scripts/codeops-migrate.sh +355 -0
  52. package/scripts/codeops-roadmap-compact.sh +232 -0
  53. package/scripts/codeops-roadmap-sync.sh +275 -0
  54. package/scripts/codeops_outcomes.py +155 -0
  55. package/scripts/codeops_plan.py +239 -0
  56. package/scripts/codeops_plan_migrate.py +318 -0
  57. package/scripts/codeops_worktree_snapshot.py +99 -0
  58. package/scripts/install_agents.py +288 -0
  59. package/scripts/release.mjs +533 -0
  60. package/skills/analyze-project/SKILL.md +28 -0
  61. package/skills/clean-comments/SKILL.md +22 -0
  62. package/skills/exec-plan/SKILL.md +267 -0
  63. package/skills/exec-plan/commit-modes.md +113 -0
  64. package/skills/exec-plan/execution-protocol.md +471 -0
  65. package/skills/git-commit/SKILL.md +35 -0
  66. package/skills/github-issues/SKILL.md +38 -0
  67. package/skills/grill-me/SKILL.md +342 -0
  68. package/skills/make-plan/SKILL.md +282 -0
  69. package/skills/make-plan/quality-checklist.md +96 -0
  70. package/skills/make-plan/templates.md +535 -0
  71. package/skills/make-plan/zero-ambiguity-gate.md +19 -0
  72. package/skills/make-requirements/SKILL.md +268 -0
  73. package/skills/make-requirements/discovery-phases.md +255 -0
  74. package/skills/make-requirements/review-and-add.md +73 -0
  75. package/skills/make-requirements/templates.md +296 -0
  76. package/skills/make-requirements/zero-ambiguity-gate.md +18 -0
  77. package/skills/outcome-review/SKILL.md +34 -0
  78. package/skills/preflight/SKILL.md +310 -0
  79. package/skills/preflight/dimensions.md +181 -0
  80. package/skills/preflight/report-format.md +300 -0
  81. package/skills/retro-requirements/SKILL.md +218 -0
  82. package/skills/retro-requirements/confidence-classification.md +45 -0
  83. package/skills/retro-requirements/phases.md +609 -0
  84. package/skills/retro-requirements/triage-gate.md +135 -0
  85. package/skills/roadmap/SKILL.md +381 -0
  86. package/skills/roadmap/stage-hooks.md +80 -0
  87. package/skills/roadmap/template.md +200 -0
  88. package/skills/setup-codeops/SKILL.md +94 -0
  89. package/skills/setup-codeops/migration.md +106 -0
  90. package/skills/setup-codeops/scaffold.md +99 -0
  91. package/skills/setup-routing/SKILL.md +102 -0
  92. package/skills/setup-routing/routing.md +44 -0
  93. package/skills/techdocs/SKILL.md +199 -0
  94. package/skills/techdocs/authoring-and-update.md +178 -0
  95. package/skills/techdocs/templates.md +655 -0
  96. package/skills/techdocs/vitepress-setup.md +143 -0
  97. package/skills/upgrade-plan/SKILL.md +75 -0
  98. package/skills/upgrade-plan/content-quality-gate.md +35 -0
  99. package/skills/upgrade-plan/upgrade-checklists.md +107 -0
  100. package/standards/coding-standards-full.md +124 -0
  101. package/standards/coding-standards.md +64 -0
  102. 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())