@kodrunhq/opencode-autopilot 0.1.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/assets/agents/placeholder-agent.md +13 -0
  4. package/assets/commands/configure.md +17 -0
  5. package/assets/commands/new-agent.md +16 -0
  6. package/assets/commands/new-command.md +15 -0
  7. package/assets/commands/new-skill.md +15 -0
  8. package/assets/commands/review-pr.md +49 -0
  9. package/assets/skills/.gitkeep +0 -0
  10. package/assets/skills/coding-standards/SKILL.md +327 -0
  11. package/package.json +52 -0
  12. package/src/agents/autopilot.ts +42 -0
  13. package/src/agents/documenter.ts +44 -0
  14. package/src/agents/index.ts +49 -0
  15. package/src/agents/metaprompter.ts +50 -0
  16. package/src/agents/pipeline/index.ts +25 -0
  17. package/src/agents/pipeline/oc-architect.ts +49 -0
  18. package/src/agents/pipeline/oc-challenger.ts +44 -0
  19. package/src/agents/pipeline/oc-critic.ts +42 -0
  20. package/src/agents/pipeline/oc-explorer.ts +46 -0
  21. package/src/agents/pipeline/oc-implementer.ts +56 -0
  22. package/src/agents/pipeline/oc-planner.ts +45 -0
  23. package/src/agents/pipeline/oc-researcher.ts +46 -0
  24. package/src/agents/pipeline/oc-retrospector.ts +42 -0
  25. package/src/agents/pipeline/oc-reviewer.ts +44 -0
  26. package/src/agents/pipeline/oc-shipper.ts +42 -0
  27. package/src/agents/pr-reviewer.ts +74 -0
  28. package/src/agents/researcher.ts +43 -0
  29. package/src/config.ts +168 -0
  30. package/src/index.ts +152 -0
  31. package/src/installer.ts +130 -0
  32. package/src/orchestrator/arena.ts +41 -0
  33. package/src/orchestrator/artifacts.ts +28 -0
  34. package/src/orchestrator/confidence.ts +59 -0
  35. package/src/orchestrator/fallback/chat-message-handler.ts +49 -0
  36. package/src/orchestrator/fallback/error-classifier.ts +148 -0
  37. package/src/orchestrator/fallback/event-handler.ts +235 -0
  38. package/src/orchestrator/fallback/fallback-config.ts +16 -0
  39. package/src/orchestrator/fallback/fallback-manager.ts +323 -0
  40. package/src/orchestrator/fallback/fallback-state.ts +120 -0
  41. package/src/orchestrator/fallback/index.ts +11 -0
  42. package/src/orchestrator/fallback/message-replay.ts +40 -0
  43. package/src/orchestrator/fallback/resolve-chain.ts +34 -0
  44. package/src/orchestrator/fallback/tool-execute-handler.ts +44 -0
  45. package/src/orchestrator/fallback/types.ts +46 -0
  46. package/src/orchestrator/handlers/architect.ts +114 -0
  47. package/src/orchestrator/handlers/build.ts +363 -0
  48. package/src/orchestrator/handlers/challenge.ts +41 -0
  49. package/src/orchestrator/handlers/explore.ts +9 -0
  50. package/src/orchestrator/handlers/index.ts +21 -0
  51. package/src/orchestrator/handlers/plan.ts +35 -0
  52. package/src/orchestrator/handlers/recon.ts +40 -0
  53. package/src/orchestrator/handlers/retrospective.ts +123 -0
  54. package/src/orchestrator/handlers/ship.ts +38 -0
  55. package/src/orchestrator/handlers/types.ts +31 -0
  56. package/src/orchestrator/lesson-injection.ts +80 -0
  57. package/src/orchestrator/lesson-memory.ts +110 -0
  58. package/src/orchestrator/lesson-schemas.ts +24 -0
  59. package/src/orchestrator/lesson-types.ts +6 -0
  60. package/src/orchestrator/phase.ts +76 -0
  61. package/src/orchestrator/plan.ts +43 -0
  62. package/src/orchestrator/schemas.ts +86 -0
  63. package/src/orchestrator/skill-injection.ts +52 -0
  64. package/src/orchestrator/state.ts +80 -0
  65. package/src/orchestrator/types.ts +20 -0
  66. package/src/review/agent-catalog.ts +439 -0
  67. package/src/review/agents/auth-flow-verifier.ts +47 -0
  68. package/src/review/agents/code-quality-auditor.ts +51 -0
  69. package/src/review/agents/concurrency-checker.ts +47 -0
  70. package/src/review/agents/contract-verifier.ts +45 -0
  71. package/src/review/agents/database-auditor.ts +47 -0
  72. package/src/review/agents/dead-code-scanner.ts +47 -0
  73. package/src/review/agents/go-idioms-auditor.ts +46 -0
  74. package/src/review/agents/index.ts +82 -0
  75. package/src/review/agents/logic-auditor.ts +47 -0
  76. package/src/review/agents/product-thinker.ts +49 -0
  77. package/src/review/agents/python-django-auditor.ts +46 -0
  78. package/src/review/agents/react-patterns-auditor.ts +46 -0
  79. package/src/review/agents/red-team.ts +49 -0
  80. package/src/review/agents/rust-safety-auditor.ts +46 -0
  81. package/src/review/agents/scope-intent-verifier.ts +45 -0
  82. package/src/review/agents/security-auditor.ts +47 -0
  83. package/src/review/agents/silent-failure-hunter.ts +45 -0
  84. package/src/review/agents/spec-checker.ts +45 -0
  85. package/src/review/agents/state-mgmt-auditor.ts +46 -0
  86. package/src/review/agents/test-interrogator.ts +43 -0
  87. package/src/review/agents/type-soundness.ts +46 -0
  88. package/src/review/agents/wiring-inspector.ts +46 -0
  89. package/src/review/cross-verification.ts +71 -0
  90. package/src/review/finding-builder.ts +74 -0
  91. package/src/review/fix-cycle.ts +146 -0
  92. package/src/review/memory.ts +114 -0
  93. package/src/review/pipeline.ts +258 -0
  94. package/src/review/report.ts +141 -0
  95. package/src/review/sanitize.ts +8 -0
  96. package/src/review/schemas.ts +75 -0
  97. package/src/review/selection.ts +98 -0
  98. package/src/review/severity.ts +71 -0
  99. package/src/review/stack-gate.ts +127 -0
  100. package/src/review/types.ts +43 -0
  101. package/src/templates/agent-template.ts +47 -0
  102. package/src/templates/command-template.ts +29 -0
  103. package/src/templates/skill-template.ts +42 -0
  104. package/src/tools/confidence.ts +93 -0
  105. package/src/tools/create-agent.ts +81 -0
  106. package/src/tools/create-command.ts +74 -0
  107. package/src/tools/create-skill.ts +74 -0
  108. package/src/tools/forensics.ts +88 -0
  109. package/src/tools/orchestrate.ts +310 -0
  110. package/src/tools/phase.ts +92 -0
  111. package/src/tools/placeholder.ts +11 -0
  112. package/src/tools/plan.ts +56 -0
  113. package/src/tools/review.ts +295 -0
  114. package/src/tools/state.ts +112 -0
  115. package/src/utils/fs-helpers.ts +39 -0
  116. package/src/utils/gitignore.ts +27 -0
  117. package/src/utils/paths.ts +17 -0
  118. package/src/utils/validators.ts +57 -0
@@ -0,0 +1,71 @@
1
+ import type { Severity } from "./types";
2
+
3
+ interface SeverityDefinition {
4
+ readonly criteria: readonly string[];
5
+ readonly action: string;
6
+ }
7
+
8
+ export const SEVERITY_DEFINITIONS: Readonly<Record<Severity, SeverityDefinition>> = Object.freeze({
9
+ CRITICAL: Object.freeze({
10
+ criteria: Object.freeze([
11
+ "Runtime error on a common code path (null dereference, missing import, type mismatch)",
12
+ "Data loss or corruption (wrong delete, missing transaction, race condition on write)",
13
+ "Security vulnerability (injection, auth bypass, exposed secrets)",
14
+ "API contract broken (frontend and backend disagree on shape/method/URL)",
15
+ "Feature fundamentally does not work (CRUD missing a letter, broken wiring)",
16
+ ]),
17
+ action: "Must fix before push. Blocks PR.",
18
+ }),
19
+ HIGH: Object.freeze({
20
+ criteria: Object.freeze([
21
+ "Edge case not handled (null input, empty list, boundary value)",
22
+ "Test exists but does not actually verify behavior (no assertions, mocked too broadly)",
23
+ "Partial implementation (feature half-built, TODO left in production code)",
24
+ "Error handling missing or swallows errors silently",
25
+ "Performance issue on a hot path",
26
+ ]),
27
+ action: "Should fix before push. Does not block but strongly recommended.",
28
+ }),
29
+ MEDIUM: Object.freeze({
30
+ criteria: Object.freeze([
31
+ "Edge case potentially unhandled",
32
+ "Incomplete error context",
33
+ "Minor performance concern",
34
+ "Missing input validation on non-critical path",
35
+ "Inconsistent error handling pattern",
36
+ ]),
37
+ action: "Fix when convenient. Noted in report.",
38
+ }),
39
+ LOW: Object.freeze({
40
+ criteria: Object.freeze([
41
+ "Naming inconsistency with codebase conventions",
42
+ "Unused import or dead code",
43
+ "Console.log or debug statement left in",
44
+ "Comment is outdated or misleading",
45
+ "Code style does not match surrounding code",
46
+ ]),
47
+ action: "Fix if convenient. Will not block or flag.",
48
+ }),
49
+ });
50
+
51
+ const SEVERITY_RANK: Readonly<Record<Severity, number>> = Object.freeze({
52
+ CRITICAL: 0,
53
+ HIGH: 1,
54
+ MEDIUM: 2,
55
+ LOW: 3,
56
+ });
57
+
58
+ /**
59
+ * Compare two severities for sorting. Returns negative if a is higher severity,
60
+ * positive if b is higher, zero if equal.
61
+ */
62
+ export function compareSeverity(a: Severity, b: Severity): number {
63
+ return SEVERITY_RANK[a] - SEVERITY_RANK[b];
64
+ }
65
+
66
+ /**
67
+ * Returns true if the severity blocks a PR (only CRITICAL blocks).
68
+ */
69
+ export function isBlockingSeverity(severity: Severity): boolean {
70
+ return severity === "CRITICAL";
71
+ }
@@ -0,0 +1,127 @@
1
+ import { basename } from "node:path";
2
+ import type { AgentDefinition } from "./types";
3
+
4
+ /**
5
+ * Maps gated agent names to their required stack tags.
6
+ * If the agent is not in this map, it is ungated (always allowed).
7
+ * If listed, at least ONE of the required tags must be present in the project.
8
+ */
9
+ export const STACK_GATE_RULES: Readonly<Record<string, readonly string[]>> = Object.freeze({
10
+ "react-patterns-auditor": Object.freeze(["react", "nextjs"]),
11
+ "go-idioms-auditor": Object.freeze(["go"]),
12
+ "python-django-auditor": Object.freeze(["django", "fastapi"]),
13
+ "rust-safety-auditor": Object.freeze(["rust"]),
14
+ "state-mgmt-auditor": Object.freeze(["react", "vue", "svelte", "angular"]),
15
+ "type-soundness": Object.freeze(["typescript", "kotlin", "rust", "go"]),
16
+ });
17
+
18
+ /**
19
+ * Filters agents based on detected project stack tags.
20
+ * Agents not in STACK_GATE_RULES are always kept (ungated).
21
+ * Gated agents are kept only if at least one required tag matches.
22
+ */
23
+ export function applyStackGate(
24
+ agents: readonly AgentDefinition[],
25
+ tags: readonly string[],
26
+ ): readonly AgentDefinition[] {
27
+ const tagSet = new Set(tags);
28
+
29
+ return agents.filter((agent) => {
30
+ const requiredTags = STACK_GATE_RULES[agent.name];
31
+
32
+ // Ungated agent: always allow
33
+ if (requiredTags === undefined) {
34
+ return true;
35
+ }
36
+
37
+ // Gated agent: allow if any required tag matches
38
+ return requiredTags.some((tag) => tagSet.has(tag));
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Extension-to-tag mappings for stack detection from file paths.
44
+ */
45
+ const EXTENSION_TAGS: Readonly<Record<string, readonly string[]>> = Object.freeze({
46
+ ".ts": Object.freeze(["typescript"]),
47
+ ".tsx": Object.freeze(["typescript", "react"]),
48
+ ".js": Object.freeze(["javascript"]),
49
+ ".jsx": Object.freeze(["javascript", "react"]),
50
+ ".go": Object.freeze(["go"]),
51
+ ".py": Object.freeze(["python"]),
52
+ ".rs": Object.freeze(["rust"]),
53
+ ".vue": Object.freeze(["vue", "javascript"]),
54
+ ".svelte": Object.freeze(["svelte", "javascript"]),
55
+ ".kt": Object.freeze(["kotlin"]),
56
+ ".kts": Object.freeze(["kotlin"]),
57
+ });
58
+
59
+ /**
60
+ * Django-specific file patterns that indicate the Django framework.
61
+ */
62
+ const DJANGO_INDICATORS = Object.freeze([
63
+ "manage.py",
64
+ "wsgi.py",
65
+ "asgi.py",
66
+ "settings.py",
67
+ "urls.py",
68
+ "admin.py",
69
+ ]);
70
+
71
+ /**
72
+ * Framework-specific filename patterns for detection beyond extensions.
73
+ * Maps filename (or pattern) to the tags it indicates.
74
+ */
75
+ const FRAMEWORK_INDICATORS: Readonly<Record<string, readonly string[]>> = Object.freeze({
76
+ "next.config.js": Object.freeze(["nextjs", "react", "javascript"]),
77
+ "next.config.ts": Object.freeze(["nextjs", "react", "typescript"]),
78
+ "next.config.mjs": Object.freeze(["nextjs", "react", "javascript"]),
79
+ "angular.json": Object.freeze(["angular", "typescript"]),
80
+ "nuxt.config.ts": Object.freeze(["vue", "typescript"]),
81
+ "nuxt.config.js": Object.freeze(["vue", "javascript"]),
82
+ "svelte.config.js": Object.freeze(["svelte", "javascript"]),
83
+ });
84
+
85
+ /**
86
+ * Detect stack tags from a list of file paths by examining extensions
87
+ * and framework-specific filenames.
88
+ */
89
+ export function detectStackTags(filePaths: readonly string[]): readonly string[] {
90
+ const tags = new Set<string>();
91
+
92
+ for (const filePath of filePaths) {
93
+ const fileName = basename(filePath);
94
+
95
+ // Check extension-based tags
96
+ for (const [ext, extTags] of Object.entries(EXTENSION_TAGS)) {
97
+ if (filePath.endsWith(ext)) {
98
+ for (const tag of extTags) {
99
+ tags.add(tag);
100
+ }
101
+ break;
102
+ }
103
+ }
104
+
105
+ // Check Django indicators
106
+ if (DJANGO_INDICATORS.includes(fileName)) {
107
+ tags.add("python");
108
+ tags.add("django");
109
+ }
110
+
111
+ // Check framework indicators (Next.js, Angular, Nuxt, Svelte)
112
+ const frameworkTags = FRAMEWORK_INDICATORS[fileName];
113
+ if (frameworkTags) {
114
+ for (const tag of frameworkTags) {
115
+ tags.add(tag);
116
+ }
117
+ }
118
+
119
+ // FastAPI indicator — any .py file with "fastapi" in path
120
+ if (filePath.endsWith(".py") && filePath.includes("fastapi")) {
121
+ tags.add("python");
122
+ tags.add("fastapi");
123
+ }
124
+ }
125
+
126
+ return [...tags];
127
+ }
@@ -0,0 +1,43 @@
1
+ import type { z } from "zod";
2
+ import type {
3
+ agentResultSchema,
4
+ falsePositiveSchema,
5
+ reviewConfigSchema,
6
+ reviewFindingSchema,
7
+ reviewMemorySchema,
8
+ reviewReportSchema,
9
+ reviewStateSchema,
10
+ severitySchema,
11
+ verdictSchema,
12
+ } from "./schemas";
13
+
14
+ export type Severity = z.infer<typeof severitySchema>;
15
+ export type Verdict = z.infer<typeof verdictSchema>;
16
+ export type ReviewFinding = z.infer<typeof reviewFindingSchema>;
17
+ export type AgentResult = z.infer<typeof agentResultSchema>;
18
+ export type ReviewReport = z.infer<typeof reviewReportSchema>;
19
+ export type ReviewConfig = z.infer<typeof reviewConfigSchema>;
20
+ export type FalsePositive = z.infer<typeof falsePositiveSchema>;
21
+ export type ReviewMemory = z.infer<typeof reviewMemorySchema>;
22
+ export type ReviewState = z.infer<typeof reviewStateSchema>;
23
+
24
+ export type AgentCategory = "core" | "parallel" | "sequenced";
25
+
26
+ export interface AgentDefinition {
27
+ readonly name: string;
28
+ readonly category: AgentCategory;
29
+ readonly domain: string;
30
+ readonly catches: readonly string[];
31
+ readonly triggerSignals: readonly string[];
32
+ readonly stackAffinity: readonly string[];
33
+ readonly hardGatesSummary: string;
34
+ }
35
+
36
+ /** Shared interface for review agents used in the pipeline. */
37
+ export interface ReviewAgent {
38
+ readonly name: string;
39
+ readonly description: string;
40
+ readonly relevantStacks: readonly string[];
41
+ readonly severityFocus: readonly Severity[];
42
+ readonly prompt: string;
43
+ }
@@ -0,0 +1,47 @@
1
+ import { stringify } from "yaml";
2
+
3
+ export interface AgentTemplateInput {
4
+ readonly name: string;
5
+ readonly description: string;
6
+ readonly mode: "primary" | "subagent" | "all";
7
+ readonly model?: string;
8
+ readonly temperature?: number;
9
+ readonly permission?: Record<string, string>;
10
+ }
11
+
12
+ const DEFAULT_PERMISSION: Readonly<Record<string, string>> = Object.freeze({
13
+ read: "allow",
14
+ edit: "deny",
15
+ bash: "deny",
16
+ webfetch: "deny",
17
+ task: "deny",
18
+ });
19
+
20
+ export function generateAgentMarkdown(input: AgentTemplateInput): string {
21
+ const frontmatter: Record<string, unknown> = {
22
+ description: input.description,
23
+ mode: input.mode,
24
+ ...(input.model !== undefined && { model: input.model }),
25
+ ...(input.temperature !== undefined && { temperature: input.temperature }),
26
+ permission: input.permission ?? DEFAULT_PERMISSION,
27
+ };
28
+
29
+ return `---
30
+ ${stringify(frontmatter).trim()}
31
+ ---
32
+ You are ${input.name}, an AI agent.
33
+
34
+ <!-- TODO: Replace this placeholder prompt with specific instructions for your agent. -->
35
+ <!-- Consider: What is this agent's specialty? What tools should it use? What should it avoid? -->
36
+
37
+ ## Role
38
+
39
+ Describe your agent's primary role and expertise here.
40
+
41
+ ## Instructions
42
+
43
+ 1. Add specific behavioral instructions
44
+ 2. Define constraints and guardrails
45
+ 3. Specify output format preferences
46
+ `;
47
+ }
@@ -0,0 +1,29 @@
1
+ import { stringify } from "yaml";
2
+
3
+ export interface CommandTemplateInput {
4
+ readonly name: string;
5
+ readonly description: string;
6
+ readonly agent?: string;
7
+ readonly model?: string;
8
+ }
9
+
10
+ export function generateCommandMarkdown(input: CommandTemplateInput): string {
11
+ const frontmatter: Record<string, unknown> = {
12
+ description: input.description,
13
+ ...(input.agent !== undefined && { agent: input.agent }),
14
+ ...(input.model !== undefined && { model: input.model }),
15
+ };
16
+
17
+ return `---
18
+ ${stringify(frontmatter).trim()}
19
+ ---
20
+ The user wants to run the "${input.name}" command.
21
+
22
+ $ARGUMENTS
23
+
24
+ <!-- TODO: Replace this placeholder with specific instructions for what the command should do. -->
25
+ <!-- Use $ARGUMENTS to reference user-provided arguments. -->
26
+ <!-- Use $1, $2, $3 for individual positional arguments. -->
27
+ <!-- Use @filename to include file content. -->
28
+ `;
29
+ }
@@ -0,0 +1,42 @@
1
+ import { stringify } from "yaml";
2
+
3
+ export interface SkillTemplateInput {
4
+ readonly name: string;
5
+ readonly description: string;
6
+ readonly license?: string;
7
+ readonly compatibility?: string;
8
+ }
9
+
10
+ export function generateSkillMarkdown(input: SkillTemplateInput): string {
11
+ const frontmatter: Record<string, unknown> = {
12
+ name: input.name,
13
+ description: input.description,
14
+ ...(input.license !== undefined && { license: input.license }),
15
+ ...(input.compatibility !== undefined && { compatibility: input.compatibility }),
16
+ };
17
+
18
+ return `---
19
+ ${stringify(frontmatter).trim()}
20
+ ---
21
+
22
+ ## What I do
23
+
24
+ Describe what this skill provides to the AI agent. Be specific about the domain knowledge, patterns, or capabilities it adds.
25
+
26
+ ## Rules
27
+
28
+ 1. Add rules the AI should follow when this skill is active
29
+ 2. Define constraints and boundaries
30
+ 3. Specify when this skill should and should not be applied
31
+
32
+ ## Examples
33
+
34
+ ### Example 1
35
+
36
+ Provide concrete examples of how this skill should be used in practice.
37
+
38
+ ### Example 2
39
+
40
+ Add more examples to cover different scenarios and edge cases.
41
+ `;
42
+ }
@@ -0,0 +1,93 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { appendConfidence, filterByPhase, summarizeConfidence } from "../orchestrator/confidence";
3
+ import { phaseSchema } from "../orchestrator/schemas";
4
+ import { loadState, saveState } from "../orchestrator/state";
5
+ import { getProjectArtifactDir } from "../utils/paths";
6
+
7
+ interface ConfidenceArgs {
8
+ readonly subcommand: string;
9
+ readonly phase?: string;
10
+ readonly agent?: string;
11
+ readonly area?: string;
12
+ readonly level?: string;
13
+ readonly rationale?: string;
14
+ }
15
+
16
+ export async function confidenceCore(args: ConfidenceArgs, artifactDir: string): Promise<string> {
17
+ try {
18
+ switch (args.subcommand) {
19
+ case "append": {
20
+ if (!args.phase || !args.agent || !args.area || !args.level || !args.rationale) {
21
+ return JSON.stringify({
22
+ error: "phase, agent, area, level, and rationale are required",
23
+ });
24
+ }
25
+ const state = await loadState(artifactDir);
26
+ if (state === null) {
27
+ return JSON.stringify({ error: "no_state" });
28
+ }
29
+ const updated = appendConfidence(state, {
30
+ phase: args.phase,
31
+ agent: args.agent,
32
+ area: args.area,
33
+ level: args.level as "HIGH" | "MEDIUM" | "LOW",
34
+ rationale: args.rationale,
35
+ });
36
+ await saveState(updated, artifactDir);
37
+ return JSON.stringify({
38
+ ok: true,
39
+ entries: updated.confidence.length,
40
+ });
41
+ }
42
+
43
+ case "summary": {
44
+ const state = await loadState(artifactDir);
45
+ if (state === null) {
46
+ return JSON.stringify({ error: "no_state" });
47
+ }
48
+ const summary = summarizeConfidence(state.confidence);
49
+ return JSON.stringify(summary);
50
+ }
51
+
52
+ case "filter": {
53
+ if (!args.phase) {
54
+ return JSON.stringify({ error: "phase required for filter" });
55
+ }
56
+ const state = await loadState(artifactDir);
57
+ if (state === null) {
58
+ return JSON.stringify({ error: "no_state" });
59
+ }
60
+ const validPhase = phaseSchema.parse(args.phase);
61
+ const filtered = filterByPhase(state.confidence, validPhase);
62
+ return JSON.stringify({ entries: filtered });
63
+ }
64
+
65
+ default:
66
+ return JSON.stringify({
67
+ error: `unknown subcommand: ${args.subcommand}`,
68
+ });
69
+ }
70
+ } catch (error: unknown) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ return JSON.stringify({ error: message });
73
+ }
74
+ }
75
+
76
+ export const ocConfidence = tool({
77
+ description:
78
+ "Manage orchestrator confidence ledger. Subcommands: append (add entry), summary (aggregate counts), filter (entries by phase).",
79
+ args: {
80
+ subcommand: tool.schema.enum(["append", "summary", "filter"]).describe("Operation to perform"),
81
+ phase: tool.schema.string().optional().describe("Phase name for append/filter"),
82
+ agent: tool.schema.string().optional().describe("Agent name for append"),
83
+ area: tool.schema.string().optional().describe("Area of confidence for append"),
84
+ level: tool.schema
85
+ .enum(["HIGH", "MEDIUM", "LOW"])
86
+ .optional()
87
+ .describe("Confidence level for append"),
88
+ rationale: tool.schema.string().optional().describe("Rationale text for append"),
89
+ },
90
+ async execute(args) {
91
+ return confidenceCore(args, getProjectArtifactDir(process.cwd()));
92
+ },
93
+ });
@@ -0,0 +1,81 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { tool } from "@opencode-ai/plugin";
4
+ import { generateAgentMarkdown } from "../templates/agent-template";
5
+ import { ensureDir } from "../utils/fs-helpers";
6
+ import { getGlobalConfigDir } from "../utils/paths";
7
+ import { validateAssetName } from "../utils/validators";
8
+
9
+ interface CreateAgentArgs {
10
+ readonly name: string;
11
+ readonly description: string;
12
+ readonly mode: "primary" | "subagent" | "all";
13
+ readonly model?: string;
14
+ readonly temperature?: number;
15
+ }
16
+
17
+ export async function createAgentCore(args: CreateAgentArgs, baseDir: string): Promise<string> {
18
+ const validation = validateAssetName(args.name);
19
+ if (!validation.valid) {
20
+ return `Error: ${validation.error}`;
21
+ }
22
+
23
+ const agentsDir = join(baseDir, "agents");
24
+ const filePath = join(agentsDir, `${args.name}.md`);
25
+
26
+ const markdown = generateAgentMarkdown({
27
+ name: args.name,
28
+ description: args.description,
29
+ mode: args.mode,
30
+ model: args.model,
31
+ temperature: args.temperature,
32
+ });
33
+
34
+ try {
35
+ await ensureDir(agentsDir);
36
+ await writeFile(filePath, markdown, { encoding: "utf-8", flag: "wx" });
37
+ } catch (error) {
38
+ if (
39
+ error instanceof Error &&
40
+ "code" in error &&
41
+ (error as NodeJS.ErrnoException).code === "EEXIST"
42
+ ) {
43
+ return `Error: Agent '${args.name}' already exists at ${filePath}. Choose a different name or delete the existing file first.`;
44
+ }
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ return `Error: Failed to write agent: ${message}`;
47
+ }
48
+
49
+ return `Agent '${args.name}' created at ${filePath}. Restart OpenCode to use it.`;
50
+ }
51
+
52
+ export const ocCreateAgent = tool({
53
+ description:
54
+ "Create a new OpenCode agent. Writes a properly-formatted agent markdown file to ~/.config/opencode/agents/.",
55
+ args: {
56
+ name: tool.schema
57
+ .string()
58
+ .min(1)
59
+ .max(64)
60
+ .describe("Agent name (e.g., 'code-reviewer'). Lowercase alphanumeric with hyphens."),
61
+ description: tool.schema.string().min(1).max(500).describe("What the agent does"),
62
+ mode: tool.schema
63
+ .enum(["primary", "subagent", "all"])
64
+ .default("subagent")
65
+ .describe("Agent mode: primary (main agent), subagent (invoked via @mention), or all (both)"),
66
+ model: tool.schema
67
+ .string()
68
+ .max(128)
69
+ .optional()
70
+ .describe("Model identifier (omit for model-agnostic)"),
71
+ temperature: tool.schema
72
+ .number()
73
+ .min(0)
74
+ .max(1)
75
+ .optional()
76
+ .describe("Temperature 0.0-1.0 (omit for default)"),
77
+ },
78
+ async execute(args) {
79
+ return createAgentCore(args, getGlobalConfigDir());
80
+ },
81
+ });
@@ -0,0 +1,74 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { tool } from "@opencode-ai/plugin";
4
+ import { generateCommandMarkdown } from "../templates/command-template";
5
+ import { ensureDir } from "../utils/fs-helpers";
6
+ import { getGlobalConfigDir } from "../utils/paths";
7
+ import { validateCommandName } from "../utils/validators";
8
+
9
+ interface CreateCommandArgs {
10
+ readonly name: string;
11
+ readonly description: string;
12
+ readonly agent?: string;
13
+ readonly model?: string;
14
+ }
15
+
16
+ export async function createCommandCore(args: CreateCommandArgs, baseDir: string): Promise<string> {
17
+ const validation = validateCommandName(args.name);
18
+ if (!validation.valid) {
19
+ return `Error: ${validation.error}`;
20
+ }
21
+
22
+ const commandsDir = join(baseDir, "commands");
23
+ const filePath = join(commandsDir, `${args.name}.md`);
24
+
25
+ const markdown = generateCommandMarkdown({
26
+ name: args.name,
27
+ description: args.description,
28
+ agent: args.agent,
29
+ model: args.model,
30
+ });
31
+
32
+ try {
33
+ await ensureDir(commandsDir);
34
+ await writeFile(filePath, markdown, { encoding: "utf-8", flag: "wx" });
35
+ } catch (error) {
36
+ if (
37
+ error instanceof Error &&
38
+ "code" in error &&
39
+ (error as NodeJS.ErrnoException).code === "EEXIST"
40
+ ) {
41
+ return `Error: Command '${args.name}' already exists at ${filePath}. Choose a different name or delete the existing file first.`;
42
+ }
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ return `Error: Failed to write command: ${message}`;
45
+ }
46
+
47
+ return `Command '${args.name}' created at ${filePath}. Restart OpenCode to use it.`;
48
+ }
49
+
50
+ export const ocCreateCommand = tool({
51
+ description:
52
+ "Create a new OpenCode command. Writes a command markdown file to ~/.config/opencode/commands/.",
53
+ args: {
54
+ name: tool.schema
55
+ .string()
56
+ .min(1)
57
+ .max(64)
58
+ .describe("Command name (e.g., 'review'). Lowercase alphanumeric with hyphens."),
59
+ description: tool.schema
60
+ .string()
61
+ .min(1)
62
+ .max(500)
63
+ .describe("What the command does when invoked"),
64
+ agent: tool.schema
65
+ .string()
66
+ .max(64)
67
+ .optional()
68
+ .describe("Agent to use when running this command (e.g., 'code-reviewer')"),
69
+ model: tool.schema.string().max(128).optional().describe("Model override for this command"),
70
+ },
71
+ async execute(args) {
72
+ return createCommandCore(args, getGlobalConfigDir());
73
+ },
74
+ });
@@ -0,0 +1,74 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { tool } from "@opencode-ai/plugin";
4
+ import { generateSkillMarkdown } from "../templates/skill-template";
5
+ import { ensureDir } from "../utils/fs-helpers";
6
+ import { getGlobalConfigDir } from "../utils/paths";
7
+ import { validateAssetName } from "../utils/validators";
8
+
9
+ interface CreateSkillArgs {
10
+ readonly name: string;
11
+ readonly description: string;
12
+ readonly license?: string;
13
+ readonly compatibility?: string;
14
+ }
15
+
16
+ export async function createSkillCore(args: CreateSkillArgs, baseDir: string): Promise<string> {
17
+ const validation = validateAssetName(args.name);
18
+ if (!validation.valid) {
19
+ return `Error: ${validation.error}`;
20
+ }
21
+
22
+ const skillDir = join(baseDir, "skills", args.name);
23
+ const filePath = join(skillDir, "SKILL.md");
24
+
25
+ const markdown = generateSkillMarkdown({
26
+ name: args.name,
27
+ description: args.description,
28
+ license: args.license,
29
+ compatibility: args.compatibility,
30
+ });
31
+
32
+ try {
33
+ await ensureDir(skillDir);
34
+ await writeFile(filePath, markdown, { encoding: "utf-8", flag: "wx" });
35
+ } catch (error) {
36
+ if (
37
+ error instanceof Error &&
38
+ "code" in error &&
39
+ (error as NodeJS.ErrnoException).code === "EEXIST"
40
+ ) {
41
+ return `Error: Skill '${args.name}' already exists at ${filePath}. Choose a different name or delete the existing skill directory first.`;
42
+ }
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ return `Error: Failed to write skill: ${message}`;
45
+ }
46
+
47
+ return `Skill '${args.name}' created at ${filePath}. Restart OpenCode to use it.`;
48
+ }
49
+
50
+ export const ocCreateSkill = tool({
51
+ description:
52
+ "Create a new OpenCode skill. Writes a SKILL.md file to ~/.config/opencode/skills/<name>/.",
53
+ args: {
54
+ name: tool.schema
55
+ .string()
56
+ .min(1)
57
+ .max(64)
58
+ .describe("Skill name (1-64 chars, lowercase with hyphens, e.g., 'typescript-patterns')"),
59
+ description: tool.schema
60
+ .string()
61
+ .min(1)
62
+ .max(1024)
63
+ .describe("What the skill provides to the AI agent"),
64
+ license: tool.schema.string().max(64).optional().describe("License (e.g., 'MIT')"),
65
+ compatibility: tool.schema
66
+ .string()
67
+ .max(64)
68
+ .optional()
69
+ .describe("Compatibility (e.g., 'opencode')"),
70
+ },
71
+ async execute(args) {
72
+ return createSkillCore(args, getGlobalConfigDir());
73
+ },
74
+ });