@snarkipus/opencode-beads 0.9.4 → 1.0.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 (47) hide show
  1. package/README.md +85 -22
  2. package/package.json +14 -13
  3. package/src/agents.ts +82 -0
  4. package/src/context.ts +148 -0
  5. package/src/guard.ts +138 -0
  6. package/src/guidance.ts +53 -0
  7. package/src/host.ts +140 -0
  8. package/src/instructions.ts +49 -0
  9. package/src/plugin.ts +9 -9
  10. package/src/prime.ts +23 -12
  11. package/src/opencode-runtime.ts +0 -156
  12. package/src/plugin-core.ts +0 -347
  13. package/src/plugin-hooks.ts +0 -56
  14. package/src/vendor-adaptations.ts +0 -146
  15. package/src/vendor-provenance.ts +0 -252
  16. package/src/vendor.ts +0 -277
  17. package/vendor/agents/task-agent.md +0 -61
  18. package/vendor/commands/audit.md +0 -28
  19. package/vendor/commands/blocked.md +0 -15
  20. package/vendor/commands/close.md +0 -18
  21. package/vendor/commands/comments.md +0 -30
  22. package/vendor/commands/compact.md +0 -31
  23. package/vendor/commands/create.md +0 -19
  24. package/vendor/commands/decision.md +0 -111
  25. package/vendor/commands/delete.md +0 -32
  26. package/vendor/commands/dep.md +0 -101
  27. package/vendor/commands/epic.md +0 -27
  28. package/vendor/commands/export.md +0 -25
  29. package/vendor/commands/import.md +0 -18
  30. package/vendor/commands/init.md +0 -18
  31. package/vendor/commands/label.md +0 -33
  32. package/vendor/commands/list.md +0 -64
  33. package/vendor/commands/prime.md +0 -10
  34. package/vendor/commands/quickstart.md +0 -13
  35. package/vendor/commands/ready.md +0 -15
  36. package/vendor/commands/rename-prefix.md +0 -24
  37. package/vendor/commands/reopen.md +0 -22
  38. package/vendor/commands/restore.md +0 -27
  39. package/vendor/commands/search.md +0 -103
  40. package/vendor/commands/show.md +0 -17
  41. package/vendor/commands/stats.md +0 -17
  42. package/vendor/commands/sync.md +0 -17
  43. package/vendor/commands/template.md +0 -337
  44. package/vendor/commands/update.md +0 -24
  45. package/vendor/commands/version.md +0 -26
  46. package/vendor/commands/workflow.md +0 -58
  47. package/vendor/manifest.json +0 -168
@@ -1,252 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import * as fs from "node:fs/promises";
3
- import * as path from "node:path";
4
-
5
- export const VENDOR_MANIFEST = "manifest.json";
6
- export const VENDOR_SOURCES = Object.freeze([
7
- {
8
- source: "plugins/beads/agents/task-agent.md",
9
- target: "agents/task-agent.md",
10
- },
11
- {
12
- source: "plugins/beads/skills/beads/commands",
13
- target: "commands",
14
- },
15
- ]);
16
-
17
- interface Semver {
18
- major: bigint;
19
- minor: bigint;
20
- patch: bigint;
21
- }
22
-
23
- export interface VendorFileRecord {
24
- path: string;
25
- bytes: number;
26
- sha256: string;
27
- }
28
-
29
- export interface VendorManifest {
30
- schemaVersion: 1;
31
- repository: string;
32
- tag: string;
33
- commit: string;
34
- sources: typeof VENDOR_SOURCES;
35
- files: VendorFileRecord[];
36
- }
37
-
38
- export interface ExpectedVendorProvenance {
39
- repository: string;
40
- tag: string;
41
- commit: string;
42
- }
43
-
44
- export interface VendorRelease {
45
- tag: string;
46
- commit: string;
47
- }
48
-
49
- function parseStableTag(tag: string): Semver | undefined {
50
- const match = tag.match(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/);
51
- if (!match) return undefined;
52
-
53
- const [, major, minor, patch] = match;
54
- if (major === undefined || minor === undefined || patch === undefined) return undefined;
55
- return { major: BigInt(major), minor: BigInt(minor), patch: BigInt(patch) };
56
- }
57
-
58
- function compareSemver(left: Semver, right: Semver): number {
59
- for (const key of ["major", "minor", "patch"] as const) {
60
- if (left[key] < right[key]) return -1;
61
- if (left[key] > right[key]) return 1;
62
- }
63
- return 0;
64
- }
65
-
66
- /** Select the newest stable semver release from git ls-remote output. */
67
- export function selectStableRelease(lsRemoteOutput: string): VendorRelease {
68
- const releases = new Map<
69
- string,
70
- { tagCommit?: string; peeledCommit?: string; version: Semver }
71
- >();
72
-
73
- for (const line of lsRemoteOutput.split(/\r?\n/)) {
74
- if (!line.trim()) continue;
75
- const match = line.match(/^([0-9a-fA-F]{40})\s+refs\/tags\/([^\s^]+)(\^\{\})?$/);
76
- const commit = match?.[1]?.toLowerCase();
77
- const tag = match?.[2];
78
- const version = tag ? parseStableTag(tag) : undefined;
79
- if (!commit || !tag || !version) continue;
80
-
81
- const release = releases.get(tag) ?? { version };
82
- const existingCommit = match[3] ? release.peeledCommit : release.tagCommit;
83
- if (releases.has(tag) && existingCommit && existingCommit !== commit) {
84
- throw new Error(`Conflicting commits for vendor tag: ${tag}`);
85
- }
86
- if (match[3]) release.peeledCommit = commit;
87
- else release.tagCommit = commit;
88
- releases.set(tag, release);
89
- }
90
-
91
- const candidates = [...releases].flatMap(([tag, release]) => {
92
- const commit = release.peeledCommit ?? release.tagCommit;
93
- return commit ? [{ tag, commit, version: release.version }] : [];
94
- });
95
- candidates.sort(
96
- (left, right) =>
97
- compareSemver(right.version, left.version) ||
98
- (left.tag < right.tag ? -1 : left.tag > right.tag ? 1 : 0)
99
- );
100
- const selected = candidates[0];
101
- if (!selected) throw new Error("No stable semver Beads tags found");
102
- return { tag: selected.tag, commit: selected.commit };
103
- }
104
-
105
- /** Select the newest stable semver tag from git ls-remote output. */
106
- export function selectStableTag(lsRemoteOutput: string): string {
107
- return selectStableRelease(lsRemoteOutput).tag;
108
- }
109
-
110
- async function listVendorFiles(vendorDir: string): Promise<string[]> {
111
- const files: string[] = [];
112
-
113
- async function visit(relativeDir: string): Promise<void> {
114
- const entries = await fs.readdir(path.join(vendorDir, relativeDir), { withFileTypes: true });
115
- for (const entry of entries) {
116
- const relativePath = path.posix.join(relativeDir, entry.name);
117
- if (entry.isDirectory()) await visit(relativePath);
118
- else if (entry.isFile()) files.push(relativePath);
119
- else throw new Error(`Unsupported vendor entry: ${relativePath}`);
120
- }
121
- }
122
-
123
- await visit("agents");
124
- await visit("commands");
125
- return files.sort();
126
- }
127
-
128
- async function fileRecord(vendorDir: string, relativePath: string): Promise<VendorFileRecord> {
129
- const content = await fs.readFile(path.join(vendorDir, relativePath));
130
- return {
131
- path: relativePath,
132
- bytes: content.byteLength,
133
- sha256: createHash("sha256").update(content).digest("hex"),
134
- };
135
- }
136
-
137
- /** Build deterministic provenance for a validated vendor candidate. */
138
- export async function createVendorManifest(
139
- vendorDir: string,
140
- repository: string,
141
- tag: string,
142
- commit: string
143
- ): Promise<VendorManifest> {
144
- if (!repository) throw new Error("Vendor repository is required");
145
- if (!parseStableTag(tag)) throw new Error(`Vendor tag is not stable semver: ${tag}`);
146
- if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error(`Invalid vendor commit SHA: ${commit}`);
147
-
148
- const files = await listVendorFiles(vendorDir);
149
- if (files.length === 0) throw new Error("Vendor inventory is empty");
150
-
151
- return {
152
- schemaVersion: 1,
153
- repository,
154
- tag,
155
- commit,
156
- sources: VENDOR_SOURCES,
157
- files: await Promise.all(files.map((relativePath) => fileRecord(vendorDir, relativePath))),
158
- };
159
- }
160
-
161
- export function serializeVendorManifest(manifest: VendorManifest): string {
162
- return `${JSON.stringify(manifest, null, 2)}\n`;
163
- }
164
-
165
- function isRecord(value: unknown): value is Record<string, unknown> {
166
- return typeof value === "object" && value !== null && !Array.isArray(value);
167
- }
168
-
169
- function hasExactKeys(value: Record<string, unknown>, expectedKeys: string[]): boolean {
170
- const actualKeys = Object.keys(value);
171
- return (
172
- actualKeys.length === expectedKeys.length &&
173
- expectedKeys.every((key) => Object.hasOwn(value, key))
174
- );
175
- }
176
-
177
- function parseManifest(content: string): VendorManifest {
178
- const parsed: unknown = JSON.parse(content);
179
- if (!isRecord(parsed)) {
180
- throw new Error("Vendor manifest must be an object");
181
- }
182
- const values = parsed;
183
- if (!hasExactKeys(values, ["schemaVersion", "repository", "tag", "commit", "sources", "files"])) {
184
- throw new Error("Vendor manifest has unexpected or missing fields");
185
- }
186
- if (values.schemaVersion !== 1) throw new Error("Unsupported vendor manifest schema");
187
- if (typeof values.repository !== "string" || !values.repository) throw new Error("Missing vendor repository");
188
- if (typeof values.tag !== "string" || !parseStableTag(values.tag)) throw new Error("Invalid vendor tag");
189
- if (typeof values.commit !== "string" || !/^[0-9a-f]{40}$/.test(values.commit)) throw new Error("Invalid vendor commit");
190
- if (JSON.stringify(values.sources) !== JSON.stringify(VENDOR_SOURCES)) throw new Error("Vendor source paths differ from the expected inventory");
191
- if (!Array.isArray(values.files) || values.files.length === 0) {
192
- throw new Error("Missing vendor file inventory");
193
- }
194
-
195
- const files = values.files.map((value): VendorFileRecord => {
196
- if (!isRecord(value)) {
197
- throw new Error("Invalid vendor file record");
198
- }
199
- const record = value;
200
- if (!hasExactKeys(record, ["path", "bytes", "sha256"])) {
201
- throw new Error("Vendor file record has unexpected or missing fields");
202
- }
203
- if (typeof record.path !== "string" || !record.path) throw new Error("Invalid vendor file path");
204
- if (typeof record.bytes !== "number" || !Number.isSafeInteger(record.bytes) || record.bytes < 0) throw new Error("Invalid vendor file size");
205
- if (typeof record.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(record.sha256)) throw new Error("Invalid vendor file checksum");
206
- return { path: record.path, bytes: record.bytes, sha256: record.sha256 };
207
- });
208
-
209
- return {
210
- schemaVersion: 1,
211
- repository: values.repository,
212
- tag: values.tag,
213
- commit: values.commit,
214
- sources: VENDOR_SOURCES,
215
- files,
216
- };
217
- }
218
-
219
- /** Validate manifest inventory and checksums against files on disk. */
220
- export async function validateVendorManifest(
221
- vendorDir: string,
222
- expected?: ExpectedVendorProvenance
223
- ): Promise<VendorManifest> {
224
- const manifest = parseManifest(
225
- await fs.readFile(path.join(vendorDir, VENDOR_MANIFEST), "utf-8")
226
- );
227
- if (
228
- expected &&
229
- (manifest.repository !== expected.repository ||
230
- manifest.tag !== expected.tag ||
231
- manifest.commit !== expected.commit)
232
- ) {
233
- throw new Error("Vendor release provenance differs from the resolved upstream release");
234
- }
235
- const actualPaths = await listVendorFiles(vendorDir);
236
- const manifestPaths = manifest.files.map((file) => file.path);
237
- if (JSON.stringify(manifestPaths) !== JSON.stringify([...manifestPaths].sort())) {
238
- throw new Error("Vendor manifest inventory is not sorted");
239
- }
240
- if (JSON.stringify(manifestPaths) !== JSON.stringify(actualPaths)) {
241
- throw new Error("Vendor file inventory differs from the manifest");
242
- }
243
-
244
- for (const expected of manifest.files) {
245
- const actual = await fileRecord(vendorDir, expected.path);
246
- if (actual.bytes !== expected.bytes || actual.sha256 !== expected.sha256) {
247
- throw new Error(`Vendor checksum mismatch: ${expected.path}`);
248
- }
249
- }
250
-
251
- return manifest;
252
- }
package/src/vendor.ts DELETED
@@ -1,277 +0,0 @@
1
- /**
2
- * Vendor file loaders for beads plugin.
3
- *
4
- * The vendor directory contains beads command definitions and agent prompts
5
- * synced from the upstream beads repository via scripts/sync-beads.sh.
6
- */
7
-
8
- import * as fs from "node:fs/promises";
9
- import * as path from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import type { AgentConfig, Config } from "@opencode-ai/sdk";
12
- import { adaptVendorPrompt } from "./vendor-adaptations";
13
-
14
- function getVendorDir(): string {
15
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
- return path.join(__dirname, "..", "vendor");
17
- }
18
-
19
- interface ParsedMarkdown {
20
- frontmatter: Record<string, string>;
21
- body: string;
22
- }
23
-
24
- const DOCUMENT_ONLY_COMMANDS = new Set(["commands/prime.md", "commands/template.md"]);
25
- const COMMAND_FRONTMATTER_FIELDS = new Set([
26
- "agent",
27
- "argument-hint",
28
- "description",
29
- "model",
30
- "subtask",
31
- ]);
32
- const AGENT_FRONTMATTER_FIELDS = new Set([
33
- "color",
34
- "description",
35
- "disable",
36
- "maxSteps",
37
- "mode",
38
- "model",
39
- "temperature",
40
- "top_p",
41
- ]);
42
-
43
- function vendorError(relativePath: string, reason: string): Error {
44
- return new Error(`Invalid vendor artifact ${relativePath}: ${reason}`);
45
- }
46
-
47
- function parseMarkdownWithFrontmatter(content: string, relativePath: string): ParsedMarkdown {
48
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
49
- const match = content.match(frontmatterRegex);
50
-
51
- if (!match) throw vendorError(relativePath, "missing or malformed frontmatter delimiters");
52
-
53
- const frontmatterStr = match[1];
54
- const body = match[2];
55
-
56
- if (frontmatterStr === undefined || body === undefined) {
57
- throw vendorError(relativePath, "missing frontmatter or body");
58
- }
59
-
60
- const frontmatter: Record<string, string> = {};
61
-
62
- for (const line of frontmatterStr.split("\n")) {
63
- const trimmed = line.trim();
64
- if (!trimmed || trimmed.startsWith("#")) continue;
65
-
66
- const colonIndex = trimmed.indexOf(":");
67
- if (colonIndex <= 0) throw vendorError(relativePath, `malformed frontmatter line: ${trimmed}`);
68
-
69
- const key = trimmed.slice(0, colonIndex).trim();
70
- let value = trimmed.slice(colonIndex + 1).trim();
71
- if (Object.hasOwn(frontmatter, key)) {
72
- throw vendorError(relativePath, `duplicate frontmatter field: ${key}`);
73
- }
74
-
75
- // Handle quoted strings
76
- if (
77
- (value.startsWith('"') && value.endsWith('"')) ||
78
- (value.startsWith("'") && value.endsWith("'"))
79
- ) {
80
- value = value.slice(1, -1);
81
- } else if (value.startsWith('"') || value.startsWith("'")) {
82
- throw vendorError(relativePath, `unterminated quoted value for ${key}`);
83
- }
84
-
85
- // Handle empty array syntax like []
86
- if (value === "[]") {
87
- value = "";
88
- }
89
-
90
- frontmatter[key] = value;
91
- }
92
-
93
- const trimmedBody = body.trim();
94
- if (!trimmedBody) throw vendorError(relativePath, "body must not be empty");
95
- return { frontmatter, body: trimmedBody };
96
- }
97
-
98
- async function readVendorFile(vendorDirectory: string, relativePath: string): Promise<string> {
99
- try {
100
- const fullPath = path.join(vendorDirectory, relativePath);
101
- return await fs.readFile(fullPath, "utf-8");
102
- } catch (error) {
103
- const reason = error instanceof Error ? error.message : String(error);
104
- throw vendorError(relativePath, `cannot read file: ${reason}`);
105
- }
106
- }
107
-
108
- async function manifestPaths(vendorDirectory: string): Promise<string[]> {
109
- const content = await readVendorFile(vendorDirectory, "manifest.json");
110
- let manifest: unknown;
111
- try {
112
- manifest = JSON.parse(content);
113
- } catch (error) {
114
- const reason = error instanceof Error ? error.message : String(error);
115
- throw vendorError("manifest.json", `invalid JSON: ${reason}`);
116
- }
117
-
118
- if (
119
- typeof manifest !== "object" ||
120
- manifest === null ||
121
- !("files" in manifest) ||
122
- !Array.isArray(manifest.files)
123
- ) {
124
- throw vendorError("manifest.json", "files must be an array");
125
- }
126
- const paths = manifest.files.map((file, index) => {
127
- if (
128
- typeof file !== "object" ||
129
- file === null ||
130
- !("path" in file) ||
131
- typeof file.path !== "string"
132
- ) {
133
- throw vendorError("manifest.json", `files[${index}].path must be a string`);
134
- }
135
- return file.path;
136
- });
137
- if (new Set(paths).size !== paths.length) {
138
- throw vendorError("manifest.json", "file paths must be unique");
139
- }
140
- return paths.sort();
141
- }
142
-
143
- function requiredString(
144
- frontmatter: Record<string, string>,
145
- field: string,
146
- relativePath: string
147
- ): string {
148
- const value = frontmatter[field];
149
- if (!value?.trim()) throw vendorError(relativePath, `${field} must be a non-empty string`);
150
- return value;
151
- }
152
-
153
- function parseBoolean(value: string, field: string, relativePath: string): boolean {
154
- if (value === "true") return true;
155
- if (value === "false") return false;
156
- throw vendorError(relativePath, `${field} must be true or false`);
157
- }
158
-
159
- function parseNumber(value: string, field: string, relativePath: string): number {
160
- const parsed = Number(value);
161
- if (!Number.isFinite(parsed)) throw vendorError(relativePath, `${field} must be a number`);
162
- return parsed;
163
- }
164
-
165
- function rejectUnsupportedFields(
166
- frontmatter: Record<string, string>,
167
- supported: ReadonlySet<string>,
168
- relativePath: string
169
- ): void {
170
- const unsupported = Object.keys(frontmatter).find((field) => !supported.has(field));
171
- if (unsupported) throw vendorError(relativePath, `unsupported frontmatter field: ${unsupported}`);
172
- }
173
-
174
- const BEADS_HOST_GUIDANCE = `## CLI Safety
175
-
176
- There is no native \`bd\` or Beads MCP tool in OpenCode. Run \`bd\` through \`bash\`.
177
- Treat the injected \`bd prime\` output as the canonical workflow reference. Use \`bd --help\` to discover commands, \`bd <command> --help\` for current syntax, and \`--json\` when structured output improves reliability. Distinguish command failures from output parsing failures.
178
-
179
- Run relevant validation before closing an issue. Do not commit, push, or run Dolt synchronization automatically; do so only when current instructions or repository policy explicitly require it.`;
180
-
181
- const BEADS_DELEGATION_GUIDANCE = `## Delegation
182
-
183
- Delegate multi-command Beads work and issue-graph analysis to the \`beads-task-agent\`; perform only single atomic operations directly.`;
184
-
185
- const BEADS_SUBAGENT_CONTEXT = `## Beads Task Agent
186
-
187
- Full \`bd prime\` workflow context is injected at start and after compaction. Use \`bd\` via \`bash\`; rerun prime if context is missing or stale.
188
-
189
- For status or graph analysis, never claim, update, close, or modify a bead. Return the analysis and stop.
190
-
191
- For completion, process exactly one bead. Use a caller-supplied ID; otherwise choose one highest-priority ready bead. First run \`bd show <id> --json\`; confirm it is open, actionable, and appropriate; then claim with \`bd update <id> --claim --json\`. If an assigned bead is closed, blocked, unsuitable, missing, or otherwise not actionable, report and return without mutation or substitution.
192
-
193
- Implement and validate it. Create discoveries with \`discovered-from:<current-id>\` and a parent only when structurally obvious. Never claim or implement work created this invocation; it may be eligible later. Close or block the current bead, report discoveries, and return without seeking more work. Return concise results, not raw JSON.`;
194
-
195
- /** Build the single shared guidance layer for one injected audience. */
196
- export function beadsGuidance(
197
- audience: "primary" | "task-agent"
198
- ): string {
199
- const sections = [BEADS_HOST_GUIDANCE];
200
- if (audience === "primary") sections.push(BEADS_DELEGATION_GUIDANCE);
201
- return `<beads-guidance>\n${sections.join("\n\n")}\n</beads-guidance>`;
202
- }
203
-
204
- export async function loadAgent(vendorDirectory = getVendorDir()): Promise<Config["agent"]> {
205
- const relativePath = "agents/task-agent.md";
206
- const paths = await manifestPaths(vendorDirectory);
207
- if (!paths.includes(relativePath)) {
208
- throw vendorError("manifest.json", `missing required file record: ${relativePath}`);
209
- }
210
- const content = await readVendorFile(vendorDirectory, relativePath);
211
- const parsed = parseMarkdownWithFrontmatter(content, relativePath);
212
- const { frontmatter } = parsed;
213
- rejectUnsupportedFields(frontmatter, AGENT_FRONTMATTER_FIELDS, relativePath);
214
- if (frontmatter.mode !== undefined && frontmatter.mode !== "subagent") {
215
- throw vendorError(relativePath, "mode must be subagent when provided");
216
- }
217
-
218
- const agent: AgentConfig = {
219
- description: requiredString(frontmatter, "description", relativePath),
220
- prompt: BEADS_SUBAGENT_CONTEXT,
221
- mode: "subagent",
222
- };
223
- // Keep upstream adaptation validation as a publish-time compatibility gate.
224
- adaptVendorPrompt(relativePath, parsed.body);
225
- if (frontmatter.model) agent.model = frontmatter.model;
226
- if (frontmatter.temperature) {
227
- agent.temperature = parseNumber(frontmatter.temperature, "temperature", relativePath);
228
- }
229
- if (frontmatter.top_p) agent.top_p = parseNumber(frontmatter.top_p, "top_p", relativePath);
230
- if (frontmatter.disable) {
231
- agent.disable = parseBoolean(frontmatter.disable, "disable", relativePath);
232
- }
233
- if (frontmatter.color) agent.color = frontmatter.color;
234
- if (frontmatter.maxSteps) {
235
- agent.maxSteps = parseNumber(frontmatter.maxSteps, "maxSteps", relativePath);
236
- }
237
-
238
- return { "beads-task-agent": agent };
239
- }
240
-
241
- export async function loadCommands(vendorDirectory = getVendorDir()): Promise<Config["command"]> {
242
- const paths = await manifestPaths(vendorDirectory);
243
- const commandPaths = paths.filter((file) => file.startsWith("commands/") && file.endsWith(".md"));
244
- if (commandPaths.length === 0) {
245
- throw vendorError("manifest.json", "no command Markdown files are recorded");
246
- }
247
- const commands: Config["command"] = {};
248
-
249
- for (const relativePath of commandPaths) {
250
- const content = await readVendorFile(vendorDirectory, relativePath);
251
- if (DOCUMENT_ONLY_COMMANDS.has(relativePath)) continue;
252
- const parsed = parseMarkdownWithFrontmatter(content, relativePath);
253
- const { frontmatter } = parsed;
254
- rejectUnsupportedFields(frontmatter, COMMAND_FRONTMATTER_FIELDS, relativePath);
255
- const file = path.basename(relativePath);
256
- const name = `beads:${file.replace(".md", "")}`;
257
-
258
- const argHint = frontmatter["argument-hint"];
259
- const baseDescription = requiredString(frontmatter, "description", relativePath);
260
- const description = argHint
261
- ? `${baseDescription} (${argHint})`
262
- : baseDescription;
263
-
264
- const command: NonNullable<Config["command"]>[string] = {
265
- description,
266
- template: adaptVendorPrompt(relativePath, parsed.body),
267
- };
268
- if (frontmatter.agent) command.agent = frontmatter.agent;
269
- if (frontmatter.model) command.model = frontmatter.model;
270
- if (frontmatter.subtask) {
271
- command.subtask = parseBoolean(frontmatter.subtask, "subtask", relativePath);
272
- }
273
- commands[name] = command;
274
- }
275
-
276
- return commands;
277
- }
@@ -1,61 +0,0 @@
1
- ---
2
- description: Autonomous agent that finds and completes ready tasks
3
- ---
4
-
5
- You are a task-completion agent for beads. Your goal is to find ready work and complete it autonomously.
6
-
7
- # Agent Workflow
8
-
9
- 1. **Find Ready Work**
10
- - Use the `ready` MCP tool to get unblocked tasks
11
- - Prefer higher priority tasks (P0 > P1 > P2 > P3 > P4)
12
- - If no ready tasks, report completion
13
-
14
- 2. **Claim the Task**
15
- - Use the `show` tool to get full task details
16
- - Use the `claim` tool for atomic start-work semantics
17
- - Report what you're working on
18
-
19
- 3. **Execute the Task**
20
- - Read the task description carefully
21
- - Use available tools to complete the work
22
- - Follow best practices from project documentation
23
- - Run tests if applicable
24
-
25
- 4. **Track Discoveries**
26
- - If you find bugs, TODOs, or related work:
27
- - Use `create` tool to file new issues
28
- - Use `dep` tool with `discovered-from` to link them
29
- - This maintains context for future work
30
-
31
- 5. **Complete the Task**
32
- - Verify the work is done correctly
33
- - Use `close` tool with a clear completion message
34
- - Report what was accomplished
35
-
36
- 6. **Continue**
37
- - Check for newly unblocked work with `ready`
38
- - Repeat the cycle
39
-
40
- # Important Guidelines
41
-
42
- - Always claim before working (MCP: `claim`; CLI: `--claim`) and close when done
43
- - Link discovered work with `discovered-from` dependencies
44
- - Don't close issues unless work is actually complete
45
- - If blocked, use `update` to set status to `blocked` and explain why
46
- - Communicate clearly about progress and blockers
47
-
48
- # Available Tools
49
-
50
- Via beads MCP server:
51
- - `ready` - Find unblocked tasks
52
- - `show` - Get task details
53
- - `claim` - Atomically claim task for work
54
- - `update` - Update task status/fields
55
- - `create` - Create new issues
56
- - `dep` - Manage dependencies
57
- - `close` - Complete tasks
58
- - `blocked` - Check blocked issues
59
- - `stats` - View project stats
60
-
61
- You are autonomous but should communicate your progress clearly. Start by finding ready work!
@@ -1,28 +0,0 @@
1
- ---
2
- description: Log and label agent interactions (append-only JSONL)
3
- argument-hint: record|label
4
- ---
5
-
6
- Append-only audit logging for agent interactions (prompts, responses, tool calls) in `.beads/interactions.jsonl`.
7
-
8
- Each line is one event. Labeling is done by appending a new `"label"` event referencing a previous entry.
9
-
10
- ## Usage
11
-
12
- - **Record an interaction**:
13
- - `bd audit record --kind llm_call --model "claude-3-5-haiku" --prompt "..." --response "..."`
14
- - `bd audit record --kind tool_call --tool-name "go test" --exit-code 1 --error "..." --issue-id bd-42`
15
-
16
- - **Pipe JSON via stdin**:
17
- - `cat event.json | bd audit record`
18
-
19
- - **Label an entry**:
20
- - `bd audit label int-a1b2 --label good --reason "Worked perfectly"`
21
- - `bd audit label int-a1b2 --label bad --reason "Hallucinated a file path"`
22
-
23
- ## Notes
24
-
25
- - Audit entries are **append-only** (no in-place edits).
26
- - `bd dolt push` includes `.beads/interactions.jsonl` in the commit allowlist.
27
-
28
-
@@ -1,15 +0,0 @@
1
- ---
2
- description: Show blocked issues
3
- argument-hint: []
4
- ---
5
-
6
- Show all issues that are blocked by dependencies.
7
-
8
- Use `bd blocked` to see which issues have blockers preventing them from being worked on. This is the inverse of `bd ready` - it shows what's NOT ready.
9
-
10
- Blocked issues have one or more dependencies with type "blocks" that are still open. Once all blocking dependencies are closed, the issue becomes ready and will appear in `bd ready`.
11
-
12
- Useful for:
13
- - Understanding why work is stuck
14
- - Identifying critical path items
15
- - Planning dependency resolution
@@ -1,18 +0,0 @@
1
- ---
2
- description: Close a completed issue
3
- argument-hint: [issue-id] [reason]
4
- ---
5
-
6
- Close a beads issue that's been completed.
7
-
8
- If arguments are provided:
9
- - $1: Issue ID
10
- - $2+: Completion reason (optional)
11
-
12
- If the issue ID is missing, ask for it. Optionally ask for a reason describing what was done.
13
-
14
- Use the beads MCP `close` tool to close the issue. Show confirmation with the issue details.
15
-
16
- After closing, suggest checking for:
17
- - Dependent issues that might now be unblocked (use `ready` tool)
18
- - New work discovered during this task (use `create` tool with `discovered-from` link)
@@ -1,30 +0,0 @@
1
- ---
2
- description: View or manage comments on an issue
3
- argument-hint: [issue-id]
4
- ---
5
-
6
- View or add comments to a beads issue.
7
-
8
- Comments are separate from issue properties (title, description, etc.) because they serve a different purpose: they're a **discussion thread** rather than **singular editable fields**. Use `bd comments` for threaded conversations and `bd edit` for core issue metadata.
9
-
10
- ## View Comments
11
-
12
- To view all comments on an issue:
13
- - $1: Issue ID (e.g., bd-123)
14
-
15
- Use the beads CLI `bd comments <issue-id>` to list all comments. Show them to the user with timestamps and authors.
16
-
17
- ## Add Comment
18
-
19
- To add a comment:
20
- - $1: "add"
21
- - $2: Issue ID
22
- - $3: Comment text (or use -f flag for file input)
23
-
24
- Use `bd comments add <issue-id> "comment text"` to add a comment. Confirm the comment was added successfully.
25
-
26
- Comments are useful for:
27
- - Progress updates during work
28
- - Design notes or technical decisions
29
- - Links to related resources
30
- - Questions or blockers