Implement OpenSpec artifacts and SDK contracts

This commit is contained in:
Anthony Ettinger 2026-06-06 16:44:58 +00:00
parent 1aa67367b8
commit 17942eef57
16 changed files with 486 additions and 21 deletions

View file

@ -6,6 +6,7 @@ import { renderPluginStatus, renderTui } from "@logicsrc/tui";
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
import { boards, tasks } from "./fixtures.js";
import { print, type OutputFormat } from "./format.js";
import { exportOpenSpecSummary, importOpenSpec, writeOpenSpecChange } from "./openspec.js";
import { defaultPluginRegistry } from "./registry.js";
const program = new Command();
@ -134,6 +135,8 @@ program
.option("--yolo", "Start the master agent with autonomous execution enabled.")
.option("--repo <repo>", "Target repository, for example profullstack/logicsrc")
.option("--agents <agents>", "Comma-separated slave agent roles", "reproduce,patch,review")
.option("--change <id>", "OpenSpec-compatible change id", "agentswarm-yolo")
.option("--out <dir>", "OpenSpec-compatible output directory")
.action((options) => {
if (!options.yolo) {
console.log("AgentSwarm is coming soon. Run `logicsrc agentswarm --yolo` to open the master agent flow.");
@ -145,21 +148,69 @@ program
.map((agent) => agent.trim())
.filter(Boolean);
print(
{
type: "logicsrc.agentswarm.session",
status: "opening",
mode: "yolo",
master_agent: "agentswarm-master",
slave_agents: slaveAgents,
repo: options.repo ?? null,
openspec_compatible: program.opts().openspec || process.env.LOGICSRC_OPENSPEC_COMPAT === "1",
openspec_only: program.opts().openspecOnly || process.env.LOGICSRC_OPENSPEC_ONLY === "1"
},
"json"
);
const openspecCompatible = program.opts().openspec || process.env.LOGICSRC_OPENSPEC_COMPAT === "1";
const openspecArtifacts = openspecCompatible
? writeOpenSpecChange({
id: options.change,
title: "AgentSwarm YOLO Session",
summary: "Open a LogicSRC AgentSwarm master agent session and coordinate scoped slave agents.",
capability: "agentswarm",
repo: options.repo,
agents: slaveAgents,
outDir: options.out
})
: null;
print({
type: "logicsrc.agentswarm.session",
status: "opening",
mode: "yolo",
master_agent: "agentswarm-master",
slave_agents: slaveAgents,
repo: options.repo ?? null,
openspec_compatible: openspecCompatible,
openspec_only: program.opts().openspecOnly || process.env.LOGICSRC_OPENSPEC_ONLY === "1",
openspec_artifacts: openspecArtifacts
}, "json");
});
const openspec = program.command("openspec").description("Import, export, and generate OpenSpec.dev-compatible repo-local planning artifacts.");
openspec
.command("import")
.argument("[root]", "OpenSpec root directory", "openspec")
.option("--format <format>", "table, json, or markdown", "json")
.description("Read OpenSpec.dev-style specs and changes from a repo.")
.action((root, options) => print(importOpenSpec(root), options.format as OutputFormat));
openspec
.command("export")
.argument("[root]", "OpenSpec root directory", "openspec")
.option("--out <file>", "Write a markdown summary", "logicsrc-openspec-summary.md")
.description("Export OpenSpec.dev-style repo artifacts into a LogicSRC-readable summary.")
.action((root, options) => {
const outFile = exportOpenSpecSummary(importOpenSpec(root), options.out);
console.log(`Wrote ${outFile}`);
});
openspec
.command("change")
.option("--id <id>", "Change id", "logic-src-change")
.option("--title <title>", "Change title", "LogicSRC OpenSpec Change")
.option("--summary <summary>", "Change summary", "Describe a LogicSRC-compatible change.")
.option("--capability <capability>", "Capability/spec id", "logicsrc")
.option("--repo <repo>", "Target repository")
.option("--out <dir>", "Output directory")
.description("Create an OpenSpec.dev-style proposal/design/tasks/spec delta.")
.action((options) => print(writeOpenSpecChange({
id: options.id,
title: options.title,
summary: options.summary,
capability: options.capability,
repo: options.repo,
outDir: options.out
}), "json"));
program.command("plugins").option("--format <format>", "table, json, or markdown", "table").description("Show plugin status.").action((options) => {
const snapshot = defaultPluginRegistry().snapshot();
print(snapshot.plugins, options.format as OutputFormat);

View file

@ -0,0 +1,176 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path";
export interface OpenSpecChangeInput {
id: string;
title: string;
summary: string;
capability: string;
repo?: string;
agents?: string[];
outDir?: string;
tasks?: string[];
}
export interface OpenSpecImportSummary {
root: string;
specs: Array<{ id: string; path: string; title: string }>;
changes: Array<{ id: string; path: string; files: string[] }>;
}
export function writeOpenSpecChange(input: OpenSpecChangeInput) {
const root = input.outDir ?? join("openspec", "changes", input.id);
const specRoot = join(root, "specs", input.capability);
mkdirSync(specRoot, { recursive: true });
const tasks = input.tasks?.length ? input.tasks : [
"Review existing LogicSRC schemas and CLI conventions.",
"Open the AgentSwarm master agent session.",
"Assign slave agents to reproduction, patching, review, and evidence.",
"Export audit artifacts back into LogicSRC run/event documents."
];
const proposal = `# ${input.title}
## Summary
${input.summary}
## Repository
${input.repo ?? "not specified"}
## Compatibility
This change uses OpenSpec.dev-style repo-local planning artifacts while preserving LogicSRC task, agent, run, event, plugin, SDK, MCP, CLI, TUI, PWA, and API contract names.
`;
const design = `# ${input.title} Design
## Architecture
- A master agent owns the session goal, policy, and final decision packet.
- Slave agents execute scoped work such as reproduction, patching, review, documentation, and release evidence.
- LogicSRC records agent runs, model/tool use, artifacts, audit events, and schema versions.
## OpenSpec Compatibility
OpenSpec-compatible files stay under \`openspec/changes/${input.id}\` and can be reviewed through normal git workflows.
`;
const taskList = tasks.map((task, index) => `- [ ] ${index + 1}. ${task}`).join("\n");
const spec = `# ${input.capability} Specification
## Purpose
${input.summary}
## Requirements
### Requirement: Master agent session
The system SHALL create a master agent session that can coordinate scoped slave agents.
#### Scenario: YOLO AgentSwarm session
- GIVEN a repository target${input.repo ? ` of \`${input.repo}\`` : ""}
- WHEN \`logicsrc --openspec agentswarm --yolo\` runs
- THEN create OpenSpec-compatible proposal, design, task, and spec-delta artifacts
- AND keep LogicSRC audit and run contracts available for export
### Requirement: Slave agent coordination
The system SHALL record slave agent roles for reproducible review.
#### Scenario: Declared slave agents
- GIVEN slave agents ${input.agents?.length ? input.agents.map((agent) => `\`${agent}\``).join(", ") : "are declared"}
- WHEN the session opens
- THEN include those roles in the LogicSRC session output
- AND include them in the repo-local OpenSpec artifacts
`;
const files = [
{ path: join(root, "proposal.md"), content: proposal },
{ path: join(root, "design.md"), content: design },
{ path: join(root, "tasks.md"), content: `# ${input.title} Tasks\n\n${taskList}\n` },
{ path: join(specRoot, "spec.md"), content: spec }
];
for (const file of files) {
writeFileSync(file.path, file.content);
}
return {
id: input.id,
root,
files: files.map((file) => file.path)
};
}
export function importOpenSpec(root = "openspec"): OpenSpecImportSummary {
const specsRoot = join(root, "specs");
const changesRoot = join(root, "changes");
const specs = existsSync(specsRoot)
? listDirectories(specsRoot).map((directory) => {
const specPath = join(specsRoot, directory, "spec.md");
return {
id: directory,
path: specPath,
title: readMarkdownTitle(specPath) ?? directory
};
})
: [];
const changes = existsSync(changesRoot)
? listDirectories(changesRoot).map((directory) => {
const changePath = join(changesRoot, directory);
return {
id: directory,
path: changePath,
files: listFiles(changePath).map((file) => file.slice(changePath.length + 1))
};
})
: [];
return { root, specs, changes };
}
export function exportOpenSpecSummary(summary: OpenSpecImportSummary, outFile: string) {
const markdown = `# OpenSpec Import Summary
Root: \`${summary.root}\`
## Specs
${summary.specs.length ? summary.specs.map((spec) => `- \`${spec.id}\`${spec.title} (${spec.path})`).join("\n") : "- none"}
## Changes
${summary.changes.length ? summary.changes.map((change) => `- \`${change.id}\`${change.files.length} file(s) (${change.path})`).join("\n") : "- none"}
`;
writeFileSync(outFile, markdown);
return outFile;
}
function listDirectories(root: string) {
return readdirSync(root).filter((entry) => statSync(join(root, entry)).isDirectory()).sort();
}
function listFiles(root: string): string[] {
const entries = readdirSync(root);
return entries.flatMap((entry) => {
const path = join(root, entry);
return statSync(path).isDirectory() ? listFiles(path) : [path];
}).sort();
}
function readMarkdownTitle(path: string) {
if (!existsSync(path)) {
return null;
}
const line = readFileSync(path, "utf8").split(/\r?\n/).find((entry) => entry.startsWith("# "));
return line ? line.replace(/^#\s+/, "").trim() : basename(path);
}

15
packages/sdk/package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "@logicsrc/sdk",
"version": "0.1.0",
"description": "LogicSRC SDK contract types and client interface.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run src"
},
"devDependencies": {
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { createAgentSwarmSession, createTask } from "./index.js";
describe("LogicSRC SDK contract helpers", () => {
it("creates task documents with LogicSRC defaults", () => {
expect(createTask({
title: "Document SDK",
description: "Add SDK contract helpers.",
board: "/docs",
creator_did: "did:example:alice"
})).toMatchObject({
type: "logicsrc.task",
version: "0.1",
status: "draft"
});
});
it("creates AgentSwarm session documents", () => {
expect(createAgentSwarmSession({
repo: "profullstack/logicsrc",
agents: ["reproduce", "patch"],
yolo: true,
openspec: true
})).toMatchObject({
type: "logicsrc.agentswarm.session",
mode: "yolo",
openspec_compatible: true,
slave_agents: ["reproduce", "patch"]
});
});
});

90
packages/sdk/src/index.ts Normal file
View file

@ -0,0 +1,90 @@
export type LogicSrcId = string;
export interface LogicSrcTask {
type: "logicsrc.task";
version: string;
title: string;
description: string;
board: string;
creator_did: string;
status: "draft" | "open" | "funded" | "claimed" | "submitted" | "approved" | "rejected" | "disputed" | "closed";
budget?: {
amount: number;
currency: string;
};
assignee_did?: string;
}
export interface LogicSrcAgent {
type: "logicsrc.agent";
id: LogicSrcId;
name: string;
capabilities: string[];
logicsrc_compatibility_version?: string;
}
export interface LogicSrcRun {
type: "logicsrc.agent_run";
id: LogicSrcId;
agent_id: LogicSrcId;
status: "queued" | "running" | "succeeded" | "failed" | "canceled";
task_id?: LogicSrcId;
}
export interface LogicSrcEvent {
type: "logicsrc.event";
event: string;
resource_id: LogicSrcId;
created_at?: string;
}
export interface AgentSwarmSession {
type: "logicsrc.agentswarm.session";
status: "opening" | "active" | "paused" | "completed" | "failed";
mode: "yolo" | "review";
master_agent: string;
slave_agents: string[];
repo: string | null;
openspec_compatible: boolean;
openspec_only: boolean;
}
export interface LogicSrcClient {
createTask(input: Omit<LogicSrcTask, "type" | "version" | "status"> & Partial<Pick<LogicSrcTask, "version" | "status">>): Promise<LogicSrcTask>;
validateTask(input: unknown): Promise<{ ok: boolean; errors: string[] }>;
startAgentSwarm(input: {
repo?: string;
agents?: string[];
yolo?: boolean;
openspec?: boolean;
openspecOnly?: boolean;
}): Promise<AgentSwarmSession>;
}
export function createTask(input: Omit<LogicSrcTask, "type" | "version" | "status"> & Partial<Pick<LogicSrcTask, "version" | "status">>): LogicSrcTask {
return {
type: "logicsrc.task",
version: input.version ?? "0.1",
status: input.status ?? "draft",
...input
};
}
export function createAgentSwarmSession(input: {
repo?: string;
agents?: string[];
yolo?: boolean;
openspec?: boolean;
openspecOnly?: boolean;
}): AgentSwarmSession {
return {
type: "logicsrc.agentswarm.session",
status: "opening",
mode: input.yolo ? "yolo" : "review",
master_agent: "agentswarm-master",
slave_agents: input.agents ?? ["reproduce", "patch", "review"],
repo: input.repo ?? null,
openspec_compatible: input.openspec ?? false,
openspec_only: input.openspecOnly ?? false
};
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}