opencodekit 0.15.10 → 0.15.11
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/dist/index.js +1 -1
- package/dist/template/.opencode/agent/build.md +390 -0
- package/dist/template/.opencode/command/implement.md +136 -10
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-agent-roles-build-orchestrates-general-e.md +14 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-simplified-swarm-helper-tool-to-fix-type.md +20 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-use-beads-as-swarm-board-source-of-truth.md +14 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-learning-user-wants-real-swarm-coordination-guida.md +15 -0
- package/dist/template/.opencode/memory/research/opencode-mcp-bug-report.md +126 -0
- package/dist/template/.opencode/opencode.json +151 -46
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plans/swarm-protocol.md +123 -0
- package/dist/template/.opencode/plugin/README.md +10 -0
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +297 -0
- package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +405 -0
- package/dist/template/.opencode/tool/swarm-delegate.ts +175 -0
- package/dist/template/.opencode/tool/swarm-helper.ts +164 -0
- package/package.json +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tool } from "@opencode-ai/plugin";
|
|
4
|
+
|
|
5
|
+
function isSafePathSegment(value: string): boolean {
|
|
6
|
+
if (!value) return false;
|
|
7
|
+
if (value.includes("..")) return false;
|
|
8
|
+
if (value.includes("/")) return false;
|
|
9
|
+
if (value.includes("\\")) return false;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function sanitizeFilename(value: string): string {
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (!trimmed) return "delegation.md";
|
|
16
|
+
if (!isSafePathSegment(trimmed)) return "delegation.md";
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function splitList(input?: string): string[] {
|
|
21
|
+
if (!input) return [];
|
|
22
|
+
const parts = input
|
|
23
|
+
.split(/\r?\n|,/g)
|
|
24
|
+
.map((s) => s.trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
return [...new Set(parts)];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatBullets(items: string[]): string {
|
|
30
|
+
if (items.length === 0) return "- (none)";
|
|
31
|
+
return items.map((i) => `- ${i}`).join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(filePath);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default tool({
|
|
44
|
+
description: `Generate a Swarm delegation packet (Beads-as-board).
|
|
45
|
+
|
|
46
|
+
Purpose:
|
|
47
|
+
- Creates a consistent delegation envelope for a specific Beads task
|
|
48
|
+
- Optionally appends it to .beads/artifacts/<id>/delegation.md
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
swarm-delegate({
|
|
52
|
+
bead_id: "opencodekit-template-xyz",
|
|
53
|
+
title: "Add feature X",
|
|
54
|
+
expected_outcome: "Feature X works and tests pass",
|
|
55
|
+
required_tools: "read, grep, lsp, bash",
|
|
56
|
+
must_do: "LSP before edits, run project verification commands",
|
|
57
|
+
must_not_do: "no new deps, don't edit dist/",
|
|
58
|
+
acceptance_checks: "typecheck: <command>, lint: <command>, test: <command>",
|
|
59
|
+
context: "See .beads/artifacts/<id>/spec.md",
|
|
60
|
+
write: true
|
|
61
|
+
})`,
|
|
62
|
+
args: {
|
|
63
|
+
bead_id: tool.schema
|
|
64
|
+
.string()
|
|
65
|
+
.describe("Beads issue id (e.g., opencodekit-template-abc)"),
|
|
66
|
+
title: tool.schema
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Optional title (defaults to Beads id only)"),
|
|
70
|
+
expected_outcome: tool.schema
|
|
71
|
+
.string()
|
|
72
|
+
.describe("Measurable end state for this task"),
|
|
73
|
+
required_tools: tool.schema
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Comma/newline-separated list of tools"),
|
|
77
|
+
must_do: tool.schema
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Comma/newline-separated MUST DO list"),
|
|
81
|
+
must_not_do: tool.schema
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Comma/newline-separated MUST NOT DO list"),
|
|
85
|
+
acceptance_checks: tool.schema
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe(
|
|
89
|
+
"Comma/newline-separated checks (prefer 'typecheck: <command>', 'lint: <command>', 'test: <command>')",
|
|
90
|
+
),
|
|
91
|
+
context: tool.schema
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Extra context pointers (files/links/notes)"),
|
|
95
|
+
write: tool.schema
|
|
96
|
+
.boolean()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("When true, append packet to task artifact file"),
|
|
99
|
+
output_file: tool.schema
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe(
|
|
103
|
+
"Artifact filename under .beads/artifacts/<id>/ (default: delegation.md)",
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
execute: async (args: {
|
|
107
|
+
bead_id: string;
|
|
108
|
+
title?: string;
|
|
109
|
+
expected_outcome: string;
|
|
110
|
+
required_tools?: string;
|
|
111
|
+
must_do?: string;
|
|
112
|
+
must_not_do?: string;
|
|
113
|
+
acceptance_checks?: string;
|
|
114
|
+
context?: string;
|
|
115
|
+
write?: boolean;
|
|
116
|
+
output_file?: string;
|
|
117
|
+
}) => {
|
|
118
|
+
const beadId = args.bead_id.trim();
|
|
119
|
+
if (!beadId) return "Error: bead_id is required.";
|
|
120
|
+
if (!isSafePathSegment(beadId)) {
|
|
121
|
+
return "Error: bead_id must be a single path segment (no slashes or '..').";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const title = args.title?.trim();
|
|
125
|
+
const taskLine = title ? `${beadId} - ${title}` : beadId;
|
|
126
|
+
|
|
127
|
+
const requiredTools = splitList(args.required_tools);
|
|
128
|
+
const mustDo = splitList(args.must_do);
|
|
129
|
+
const mustNotDo = splitList(args.must_not_do);
|
|
130
|
+
const acceptanceChecks = splitList(args.acceptance_checks);
|
|
131
|
+
const context = args.context?.trim();
|
|
132
|
+
|
|
133
|
+
const now = new Date();
|
|
134
|
+
const stamp = now.toISOString();
|
|
135
|
+
|
|
136
|
+
const packet = [
|
|
137
|
+
"# Delegation Packet",
|
|
138
|
+
"",
|
|
139
|
+
`- TASK: ${taskLine}`,
|
|
140
|
+
`- EXPECTED OUTCOME: ${args.expected_outcome.trim()}`,
|
|
141
|
+
"- REQUIRED TOOLS:",
|
|
142
|
+
formatBullets(requiredTools),
|
|
143
|
+
"- MUST DO:",
|
|
144
|
+
formatBullets(mustDo),
|
|
145
|
+
"- MUST NOT DO:",
|
|
146
|
+
formatBullets(mustNotDo),
|
|
147
|
+
"- ACCEPTANCE CHECKS:",
|
|
148
|
+
formatBullets(acceptanceChecks),
|
|
149
|
+
"- CONTEXT:",
|
|
150
|
+
context ? context : "(none)",
|
|
151
|
+
].join("\n");
|
|
152
|
+
|
|
153
|
+
if (!args.write) {
|
|
154
|
+
return `${packet}\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const artifactDir = path.join(process.cwd(), ".beads", "artifacts", beadId);
|
|
158
|
+
const outName = sanitizeFilename(args.output_file || "delegation.md");
|
|
159
|
+
const outPath = path.join(artifactDir, outName);
|
|
160
|
+
|
|
161
|
+
const specPath = path.join(artifactDir, "spec.md");
|
|
162
|
+
const hasSpec = await fileExists(specPath);
|
|
163
|
+
|
|
164
|
+
await fs.mkdir(artifactDir, { recursive: true });
|
|
165
|
+
|
|
166
|
+
const header = `\n\n---\nGenerated: ${stamp}\n---\n\n`;
|
|
167
|
+
await fs.appendFile(outPath, `${header}${packet}\n`, "utf-8");
|
|
168
|
+
|
|
169
|
+
const specNote = hasSpec
|
|
170
|
+
? ""
|
|
171
|
+
: `\nWarning: spec.md not found at ${specPath}`;
|
|
172
|
+
|
|
173
|
+
return `✓ Delegation packet appended to ${outPath}${specNote}\n\n${packet}\n`;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tool } from "@opencode-ai/plugin";
|
|
4
|
+
|
|
5
|
+
export const SWARM_MAIL_FILE = ".beads/swarm-mail.jsonl";
|
|
6
|
+
|
|
7
|
+
type BaseArgs = {
|
|
8
|
+
team_name: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type GetTeamStatusArgs = BaseArgs & {
|
|
12
|
+
limit?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type SendTeamMessageArgs = BaseArgs & {
|
|
16
|
+
from_worker?: string;
|
|
17
|
+
to_worker?: string;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default tool({
|
|
22
|
+
description: `Swarm coordination helper.
|
|
23
|
+
|
|
24
|
+
Operations:
|
|
25
|
+
- getTeamStatus: Read swarm mailbox for worker reports and progress
|
|
26
|
+
- sendTeamMessage: Write to swarm mailbox for coordination
|
|
27
|
+
|
|
28
|
+
Pattern:
|
|
29
|
+
- Build agent orchestrates: parses plan → spawns team → monitors → synthesizes
|
|
30
|
+
- General agent executes: reads delegation packet → makes changes → reports back
|
|
31
|
+
- Coordination via mailbox: .beads/swarm-mail.jsonl (append-only log)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
getTeamStatus({ team_name: "plan-implementation" })
|
|
35
|
+
→ Returns: worker_status for each worker (pending/working/done/error)
|
|
36
|
+
|
|
37
|
+
sendTeamMessage({
|
|
38
|
+
team_name: "plan-implementation",
|
|
39
|
+
from_worker: "worker-1",
|
|
40
|
+
to_worker: "worker-2",
|
|
41
|
+
message: "Need help with file locking",
|
|
42
|
+
})
|
|
43
|
+
→ Appends JSONL line: { timestamp, from, to, message }
|
|
44
|
+
|
|
45
|
+
`,
|
|
46
|
+
args: {
|
|
47
|
+
team_name: tool.schema
|
|
48
|
+
.string()
|
|
49
|
+
.describe("Team identifier (e.g., plan-implementation, auth-feature)"),
|
|
50
|
+
operation: tool.schema
|
|
51
|
+
.enum(["getTeamStatus", "sendTeamMessage"])
|
|
52
|
+
.describe("Which swarm coordination operation to perform"),
|
|
53
|
+
from_worker: tool.schema
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Sending worker name (sendTeamMessage)"),
|
|
57
|
+
to_worker: tool.schema
|
|
58
|
+
.string()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Target worker name (sendTeamMessage)"),
|
|
61
|
+
message: tool.schema
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Message content (sendTeamMessage)"),
|
|
65
|
+
limit: tool.schema
|
|
66
|
+
.number()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Limit mailbox entries (getTeamStatus)"),
|
|
69
|
+
},
|
|
70
|
+
execute: async (args) => {
|
|
71
|
+
const operation = args.operation as string;
|
|
72
|
+
|
|
73
|
+
if (operation === "getTeamStatus") {
|
|
74
|
+
const result = await getTeamStatus(args as any);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (operation === "sendTeamMessage") {
|
|
79
|
+
const result = await sendTeamMessage(args as any);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `Error: Unknown operation: ${operation}. Valid: getTeamStatus, sendTeamMessage`;
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
async function getTeamStatus(args: GetTeamStatusArgs) {
|
|
88
|
+
const { team_name, limit } = args;
|
|
89
|
+
|
|
90
|
+
if (!team_name) {
|
|
91
|
+
return "Error: team_name is required.";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const mailPath = path.join(process.cwd(), SWARM_MAIL_FILE);
|
|
95
|
+
let lines: string[];
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const content = await fs.readFile(mailPath, "utf-8");
|
|
99
|
+
lines = content.trim().split("\n").filter(Boolean);
|
|
100
|
+
} catch {
|
|
101
|
+
return `Error: Failed to read ${SWARM_MAIL_FILE}.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const team_messages = lines
|
|
105
|
+
.map((line) => {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(line) as {
|
|
108
|
+
timestamp: string;
|
|
109
|
+
team_name: string;
|
|
110
|
+
from_worker?: string;
|
|
111
|
+
to_worker?: string;
|
|
112
|
+
message?: string;
|
|
113
|
+
status?: string;
|
|
114
|
+
};
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
|
|
121
|
+
const relevant = team_messages.filter((m) => m.team_name === team_name);
|
|
122
|
+
|
|
123
|
+
if (limit && limit > 0) {
|
|
124
|
+
return JSON.stringify(relevant.slice(-limit), null, 2);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return JSON.stringify(relevant, null, 2);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function sendTeamMessage(args: SendTeamMessageArgs) {
|
|
131
|
+
const { team_name, from_worker, to_worker, message } = args;
|
|
132
|
+
|
|
133
|
+
if (!team_name || !message) {
|
|
134
|
+
return "Error: team_name and message are required.";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const mailPath = path.join(process.cwd(), SWARM_MAIL_FILE);
|
|
138
|
+
const entry = {
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
team_name,
|
|
141
|
+
from_worker: from_worker || "system",
|
|
142
|
+
to_worker: to_worker || "all",
|
|
143
|
+
message,
|
|
144
|
+
status: "unread",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await fs.appendFile(mailPath, `${JSON.stringify(entry)}\n`, "utf-8");
|
|
149
|
+
return JSON.stringify(
|
|
150
|
+
{
|
|
151
|
+
success: true,
|
|
152
|
+
team_name,
|
|
153
|
+
from_worker,
|
|
154
|
+
to_worker,
|
|
155
|
+
message,
|
|
156
|
+
mail_file: SWARM_MAIL_FILE,
|
|
157
|
+
},
|
|
158
|
+
null,
|
|
159
|
+
2,
|
|
160
|
+
);
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
return `Error: Failed to write to ${SWARM_MAIL_FILE}: ${error}`;
|
|
163
|
+
}
|
|
164
|
+
}
|
package/package.json
CHANGED