Building Integrations
How to build your own betterspec integration for any AI tool or workflow.
Building Integrations
betterspec is designed to be integrated with any AI tool or workflow system. There are two approaches depending on your needs.
Approach 1: Read Specs as Files
The simplest integration requires no dependencies. betterspec specs are plain markdown files with a predictable directory structure:
betterspec/
changes/<change-id>/
proposal.md
specs/
requirements.md
scenarios.md
design.md
tasks.md
.betterspec-meta.json
knowledge/
capabilities/To integrate, read the files you need:
import { readFile } from "fs/promises";
import { join } from "path";
async function getChangeSpecs(cwd: string, changeId: string) {
const base = join(cwd, "betterspec", "changes", changeId);
const [proposal, requirements, scenarios, design, tasks] = await Promise.all([
readFile(join(base, "proposal.md"), "utf-8"),
readFile(join(base, "specs", "requirements.md"), "utf-8"),
readFile(join(base, "specs", "scenarios.md"), "utf-8"),
readFile(join(base, "design.md"), "utf-8"),
readFile(join(base, "tasks.md"), "utf-8"),
]);
return { proposal, requirements, scenarios, design, tasks };
}To check the status of a change, read .betterspec-meta.json:
async function getChangeStatus(cwd: string, changeId: string) {
const metaPath = join(cwd, "betterspec", "changes", changeId, ".betterspec-meta.json");
const meta = JSON.parse(await readFile(metaPath, "utf-8"));
return meta.status; // "proposed" | "in-progress" | "completed" | "archived"
}To parse task progress, count the checkboxes in tasks.md:
function parseTaskProgress(tasksContent: string) {
const completed = (tasksContent.match(/- \[x\]/gi) || []).length;
const remaining = (tasksContent.match(/- \[ \]/g) || []).length;
const total = completed + remaining;
return { total, completed, remaining, percent: total > 0 ? (completed / total) * 100 : 0 };
}This approach works well for:
- System prompt injection (concatenate spec files into context)
- Status dashboards (parse metadata and task checkboxes)
- File watchers that react to spec changes
- CI/CD checks that verify specs exist before merging
Approach 2: Use @betterspec/core
For deeper integrations, use the @betterspec/core package. It handles config resolution, change listing, spec parsing, and scaffolding.
bun add @betterspec/coreimport {
readConfig,
configExists,
listChanges,
readChange,
createChange,
summarizeTasks,
} from "@betterspec/core";This gives you:
- Config resolution — handles
local,local+global, andglobalmodes - Change listing — filters by status, reads metadata
- Spec reading — returns parsed change objects with all spec content
- Task summarization — checkbox counting with structured output
- Scaffolding — create changes, directories, skill files, and knowledge bases
See the Programmatic API reference for full documentation.
Key Patterns
Inject Specs into System Prompts
The most common integration pattern. Read the active change's specs and prepend them to the system prompt:
import { listChanges, readChange } from "@betterspec/core";
async function buildSystemPrompt(cwd: string, basePrompt: string) {
const changes = await listChanges(cwd);
const active = changes.find((c) => c.status === "in-progress");
if (!active) return basePrompt;
const change = await readChange(cwd, active.id);
return `${basePrompt}
## Active Change: ${active.id}
### Proposal
${change.proposal}
### Requirements
${change.requirements}
### Design
${change.design}
### Tasks
${change.tasks}`;
}Track Progress
Show task completion status in your tool's UI:
import { listChanges, summarizeTasks } from "@betterspec/core";
async function getProgressDashboard(cwd: string) {
const changes = await listChanges(cwd);
return Promise.all(
changes
.filter((c) => c.status === "in-progress")
.map(async (c) => {
const tasks = await summarizeTasks(cwd, c.id);
return {
id: c.id,
progress: `${tasks.completed}/${tasks.total} (${Math.round(tasks.percent)}%)`,
};
})
);
}Guard Against Unspecced Work
Warn when edits happen outside the scope of any active change:
import { listChanges, readChange } from "@betterspec/core";
async function isFileInScope(cwd: string, filePath: string) {
const changes = await listChanges(cwd);
const active = changes.filter((c) => c.status === "in-progress");
for (const change of active) {
const detail = await readChange(cwd, change.id);
// Check if the file is mentioned in design or tasks
if (detail.design.includes(filePath) || detail.tasks.includes(filePath)) {
return true;
}
}
return false;
}Examples
For a complete integration example, see the OpenCode plugin source. It demonstrates all of these patterns — system prompt injection, progress tracking, scope guarding, and multi-agent role separation.