init: @trentuna/pi-openspec — 13-tool OpenSpec extension for pi
- extensions/openspec.ts — 13 tools wrapping openspec CLI - skills/openspec/SKILL.md — full workflow guidance for agents - package.json — pi manifest with pi-package keyword, @trentuna scope - README.md — comprehensive: install, all 13 tools, quick-start, workflow - LICENSE — MIT - .gitignore Adapted from a-team/extensions/openspec.ts (Hannibal, session 135). Standalone package so any trentuna member can: pi install git:http://localhost:3001/trentuna/pi-openspec.git
This commit is contained in:
commit
9e1f99a28f
6 changed files with 808 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
.DS_Store
|
||||
*.log
|
||||
dist/
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Trentuna
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
166
README.md
Normal file
166
README.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# @trentuna/pi-openspec
|
||||
|
||||
**OpenSpec workflow tools for pi agent sessions.** Wraps the [OpenSpec CLI](https://github.com/Fission-AI/OpenSpec) as 13 native pi tools, making spec-driven development a first-class operation in any agent session.
|
||||
|
||||
This is the first protocol-level pi package built for the pi ecosystem — OpenSpec as infrastructure, not a team-internal tool.
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
OpenSpec brings discipline to AI-assisted software development: write the spec before writing the code, track changes explicitly, archive what's done. This package makes those operations available directly in pi sessions — no manual CLI, no skill-file hunting.
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
### External (npm — preferred for external users)
|
||||
|
||||
```bash
|
||||
pi install npm:@trentuna/pi-openspec
|
||||
```
|
||||
|
||||
*Requires npm auth — see [npm publish status](#npm-publish-status).*
|
||||
|
||||
### Internal (Trentuna VM — git install)
|
||||
|
||||
```bash
|
||||
pi install git:http://localhost:3001/trentuna/pi-openspec.git
|
||||
```
|
||||
|
||||
### Trentuna members (bootstrap)
|
||||
|
||||
Included automatically when you run `commons/bin/bootstrap.sh`. No manual install needed.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **openspec CLI** — `npm install -g openspec` (v1.2.0+)
|
||||
- **pi** — any recent version
|
||||
- **Node.js** 18+
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
All 13 tools are available immediately after install.
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `openspec_init` | Initialize OpenSpec in a project (creates `openspec/` dir, config, skill files) |
|
||||
| `openspec_update` | Regenerate skill/instruction files after config changes or schema switch |
|
||||
| `openspec_list` | List active changes (work in progress) or stable spec domains |
|
||||
| `openspec_show` | Show the full content of a change or spec |
|
||||
| `openspec_new_change` | Create a new change directory with scaffolded artifacts |
|
||||
| `openspec_status` | Artifact dependency graph — what's done, what's ready, what's blocked |
|
||||
| `openspec_instructions` | Enriched instructions for creating a specific artifact (or implementing tasks) |
|
||||
| `openspec_validate` | Validate change or spec for structural correctness |
|
||||
| `openspec_archive` | Archive a completed change (merges delta specs → main, preserves history) |
|
||||
| `openspec_spec_list` | List all spec domains (the stable source of truth) |
|
||||
| `openspec_spec_show` | Display a specific spec by ID |
|
||||
| `openspec_schema_list` | List available workflow schemas |
|
||||
| `openspec_schema_fork` | Fork a schema for customization |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
**5 steps to ship a specified, implemented, archived change:**
|
||||
|
||||
```bash
|
||||
# 1. Initialize in your project (once)
|
||||
openspec_init(path=".", tools="pi")
|
||||
|
||||
# 2. Create a change
|
||||
openspec_new_change(name="add-user-auth", description="Add JWT authentication")
|
||||
|
||||
# 3. Check what to write next
|
||||
openspec_status(changeName="add-user-auth")
|
||||
|
||||
# 4. Get instructions for each artifact, then write it
|
||||
openspec_instructions(changeName="add-user-auth", artifact="proposal")
|
||||
# → write proposal.md, then specs, design, tasks
|
||||
|
||||
# 5. Implement and archive
|
||||
openspec_instructions(changeName="add-user-auth", artifact="apply")
|
||||
# → implement tasks, then:
|
||||
openspec_validate(name="add-user-auth")
|
||||
openspec_archive(changeName="add-user-auth")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Skill
|
||||
|
||||
Load the workflow skill for step-by-step guidance on all operations:
|
||||
|
||||
```
|
||||
Load the openspec skill
|
||||
```
|
||||
|
||||
The skill covers the full workflow: init → new_change → status → instructions → implement → validate → archive. It includes common patterns, gotchas, and tool reference.
|
||||
|
||||
Skill location: `skills/openspec/SKILL.md`
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
OpenSpec organizes work into:
|
||||
|
||||
- **Specs** (`openspec/specs/`) — the source of truth. Describe how the system currently works.
|
||||
- **Changes** (`openspec/changes/`) — proposed modifications. Each change is a folder containing a proposal, delta specs, design, and task list.
|
||||
|
||||
When a change is complete, `openspec_archive` merges its delta specs into the main specs and moves the folder to `changes/archive/YYYY-MM-DD-<name>/`. History preserved, source of truth updated.
|
||||
|
||||
The default `spec-driven` schema builds artifacts in dependency order:
|
||||
```
|
||||
proposal → specs → design → tasks → implement → archive
|
||||
```
|
||||
|
||||
`openspec_status` shows the dependency graph at any point. `openspec_instructions` gives you enriched context before writing each artifact.
|
||||
|
||||
---
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
pi-openspec/
|
||||
package.json # pi manifest + npm metadata
|
||||
extensions/
|
||||
openspec.ts # 13-tool extension (wraps openspec CLI)
|
||||
skills/
|
||||
openspec/
|
||||
SKILL.md # workflow guidance for agent sessions
|
||||
README.md
|
||||
LICENSE # MIT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## npm Publish Status
|
||||
|
||||
**Not yet published.** The package installs cleanly via git from Forgejo (Trentuna internal). npm publish requires:
|
||||
|
||||
1. `npm login` on the publishing machine
|
||||
2. `@trentuna` npm org created at npmjs.com
|
||||
3. `npm publish --access public` from this repo
|
||||
|
||||
This is a Ludo action. Track at [trentuna/a-team#107](http://localhost:3001/trentuna/a-team/issues/107).
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
This package is part of the Trentuna tooling stack. After it's stable and battle-tested, it will be submitted to the pi-mono package gallery as the first protocol-level pi package in the ecosystem.
|
||||
|
||||
Issues and PRs welcome at `trentuna/pi-openspec`.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT — Trentuna, 2026.
|
||||
|
||||
OpenSpec CLI by [Fission AI](https://github.com/Fission-AI/OpenSpec). pi by [badlogic](https://github.com/badlogicgames/pi-mono).
|
||||
438
extensions/openspec.ts
Normal file
438
extensions/openspec.ts
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
/**
|
||||
* OpenSpec Pi Extension
|
||||
*
|
||||
* Native OpenSpec integration for pi — spec-driven development workflow,
|
||||
* change management, artifact creation, spec browsing, and project setup.
|
||||
*
|
||||
* Wraps the openspec CLI (https://github.com/Fission-AI/OpenSpec) as pi tools,
|
||||
* making spec-driven workflow a first-class operation in any agent session.
|
||||
*
|
||||
* Package: @trentuna/pi-openspec
|
||||
* Source: https://github.com/trentuna/pi-openspec (canonical)
|
||||
* Origin: https://github.com/Fission-AI/OpenSpec (openspec CLI upstream)
|
||||
* Docs: ~/.napkin/docs/fission-ai_openspec/ (octopus-adopted)
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* WORKFLOW OVERVIEW
|
||||
*
|
||||
* OpenSpec organizes work into specs (source of truth) and changes (proposed
|
||||
* modifications). The standard workflow:
|
||||
*
|
||||
* 1. openspec_new_change → create a change folder with scaffolded artifacts
|
||||
* 2. openspec_status → see which artifacts need to be created
|
||||
* 3. openspec_instructions → get enriched instructions for each artifact
|
||||
* [Write artifact files using Write/Edit tools]
|
||||
* 4. openspec_instructions apply → get task implementation instructions
|
||||
* [Implement the tasks]
|
||||
* 5. openspec_validate → check implementation matches artifacts
|
||||
* 6. openspec_archive → finalize: merge delta specs + move to archive
|
||||
*
|
||||
* For new projects:
|
||||
* openspec_init → initialize OpenSpec (creates openspec/ directory, skills)
|
||||
* openspec_update → regenerate skill files after config changes
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* CORE CONCEPTS
|
||||
*
|
||||
* Specs — describe how the system CURRENTLY behaves (openspec/specs/)
|
||||
* Contain requirements and scenarios (Given/When/Then).
|
||||
* Source of truth for the project.
|
||||
*
|
||||
* Changes — proposed modifications, each a self-contained folder
|
||||
* (openspec/changes/<name>/) containing:
|
||||
* proposal.md — why and what (intent, scope, approach)
|
||||
* design.md — how (technical decisions, architecture)
|
||||
* tasks.md — implementation checklist with [ ] checkboxes
|
||||
* specs/ — delta specs (ADDED/MODIFIED/REMOVED requirements)
|
||||
*
|
||||
* Delta specs — describe what's CHANGING, not the full spec. On archive,
|
||||
* deltas merge into main specs. Enables parallel work on same spec.
|
||||
*
|
||||
* Schemas — define artifact types and dependencies. Default: spec-driven
|
||||
* (proposal → specs → design → tasks → implement → archive).
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Install:
|
||||
* ln -sf $(pwd)/extensions/openspec.ts ~/.pi/agent/extensions/openspec.ts
|
||||
* # or: run install/pi/install.sh
|
||||
*
|
||||
* Requirements:
|
||||
* openspec CLI on PATH: npm install -g @fission-ai/openspec
|
||||
* Source: ~/upstream/openspec
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
// ── CLI helper ─────────────────────────────────────────────────────────────────
|
||||
|
||||
async function openspecCli(
|
||||
args: string[],
|
||||
exec: (command: string, args: string[]) => Promise<{ stdout: string; stderr: string; code: number | null; killed: boolean }>,
|
||||
cwd?: string
|
||||
): Promise<{ output: string; exitCode: number; cancelled: boolean }> {
|
||||
const result = await exec("openspec", args);
|
||||
return {
|
||||
output: result.stdout || result.stderr || "",
|
||||
exitCode: result.code ?? 1,
|
||||
cancelled: result.killed ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
function ok(text: string) {
|
||||
return { content: [{ type: "text" as const, text: text }], details: { success: true } };
|
||||
}
|
||||
|
||||
function err(text: string) {
|
||||
return { content: [{ type: "text" as const, text: text }], details: { success: false } };
|
||||
}
|
||||
|
||||
// ── Extension ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const exec = pi.exec.bind(pi);
|
||||
|
||||
// ═══════════════════════ PROJECT SETUP ════════════════════════════════════
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_init",
|
||||
label: "Initialize OpenSpec",
|
||||
description:
|
||||
"Initialize OpenSpec in the current project. Creates openspec/ directory, " +
|
||||
"config.yaml, and skill files for AI tools. " +
|
||||
"Run once per project before using any other openspec tools. " +
|
||||
"Use tool='pi' to generate pi-compatible skill files (recommended for this environment). " +
|
||||
"Use tool='claude' for Claude Code compatibility.",
|
||||
parameters: Type.Object({
|
||||
path: Type.Optional(
|
||||
Type.String({ description: "Project path to initialize (default: current directory)" })
|
||||
),
|
||||
tools: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"AI tools to configure skill files for. " +
|
||||
"Options: 'pi' (recommended), 'claude', 'all', 'none', " +
|
||||
"or comma-separated list (pi,claude,cursor). Default: pi.",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["init"];
|
||||
if (params.path) args.push(params.path);
|
||||
if (params.tools) args.push("--tools", params.tools);
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error initializing: ${result.output}`);
|
||||
return ok(result.output || "OpenSpec initialized. Run openspec_list to see active changes.");
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_update",
|
||||
label: "Update OpenSpec Skill Files",
|
||||
description:
|
||||
"Regenerate OpenSpec skill/instruction files for AI tools. " +
|
||||
"Run after changing config.yaml, switching schemas, or adding custom workflow commands. " +
|
||||
"This keeps the AI-tool integrations in sync with your OpenSpec configuration.",
|
||||
parameters: Type.Object({
|
||||
path: Type.Optional(
|
||||
Type.String({ description: "Project path to update (default: current directory)" })
|
||||
),
|
||||
force: Type.Optional(
|
||||
Type.Boolean({ description: "Force update even when tools appear up to date" })
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["update"];
|
||||
if (params.path) args.push(params.path);
|
||||
if (params.force) args.push("--force");
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error updating: ${result.output}`);
|
||||
return ok(result.output || "OpenSpec skills updated.");
|
||||
},
|
||||
});
|
||||
|
||||
// ═══════════════════════ DISCOVERY ════════════════════════════════════════
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_list",
|
||||
label: "List Changes or Specs",
|
||||
description:
|
||||
"List active changes or specs in the current project. " +
|
||||
"Default: lists active changes (work in progress). " +
|
||||
"Use listType='specs' to list the stable spec domains instead. " +
|
||||
"Run at the start of a session to orient on what's in flight. " +
|
||||
"JSON output includes change names, schemas, and artifact completion state.",
|
||||
parameters: Type.Object({
|
||||
listType: Type.Optional(
|
||||
Type.Union([Type.Literal("changes"), Type.Literal("specs")], {
|
||||
description: "'changes' (default) — work in progress | 'specs' — spec domains",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["list", "--json"];
|
||||
if (params.listType === "specs") args.push("--specs");
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error listing: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_show",
|
||||
label: "Show Change or Spec",
|
||||
description:
|
||||
"Show the full content of a change or spec. " +
|
||||
"For changes: displays proposal, design summary, tasks, and delta spec overview. " +
|
||||
"For specs: displays requirements and scenarios. " +
|
||||
"Use this to read existing work before continuing or before creating new artifacts.",
|
||||
parameters: Type.Object({
|
||||
name: Type.String({ description: "Change name (kebab-case) or spec ID" }),
|
||||
itemType: Type.Optional(
|
||||
Type.Union([Type.Literal("change"), Type.Literal("spec")], {
|
||||
description: "Item type. Auto-detected from name if omitted.",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["show", params.name, "--json", "--no-interactive"];
|
||||
if (params.itemType) args.push("--type", params.itemType);
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
// ═══════════════════════ CHANGE LIFECYCLE ═════════════════════════════════
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_new_change",
|
||||
label: "Create New Change",
|
||||
description:
|
||||
"Create a new OpenSpec change directory with scaffolded artifacts. " +
|
||||
"A change packages everything needed for one piece of work: " +
|
||||
"proposal (why + what), specs (delta requirements), design (how), tasks (checklist). " +
|
||||
"\n\nAfter creating: call openspec_status to see the artifact dependency graph, " +
|
||||
"then openspec_instructions for each artifact before writing it. " +
|
||||
"\n\nNaming: use kebab-case ('add-user-auth', 'fix-pagination', 'refactor-api'). " +
|
||||
"Avoid generic names like 'update' or 'wip'.",
|
||||
parameters: Type.Object({
|
||||
name: Type.String({ description: "Change name in kebab-case" }),
|
||||
description: Type.Optional(
|
||||
Type.String({ description: "Short description added to the change README" })
|
||||
),
|
||||
schema: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Workflow schema. Default: 'spec-driven' (proposal → specs → design → tasks). " +
|
||||
"Other schemas available if configured in openspec/schemas/.",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["new", "change", params.name];
|
||||
if (params.description) args.push("--description", params.description);
|
||||
if (params.schema) args.push("--schema", params.schema);
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error creating change '${params.name}': ${result.output}`);
|
||||
return ok(
|
||||
result.output ||
|
||||
`Change '${params.name}' created at openspec/changes/${params.name}/. ` +
|
||||
`Run openspec_status to see artifact build order.`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_status",
|
||||
label: "Change Status",
|
||||
description:
|
||||
"Get artifact completion status for a change. " +
|
||||
"Shows the dependency graph: which artifacts exist (done), " +
|
||||
"which are ready to create (dependencies satisfied), and which are blocked. " +
|
||||
"The 'applyRequires' field lists which artifacts must be complete before implementation. " +
|
||||
"\n\nCall this after openspec_new_change to see the artifact build order. " +
|
||||
"Call again after writing each artifact to see what becomes available next.",
|
||||
parameters: Type.Object({
|
||||
changeName: Type.String({ description: "Change name (kebab-case)" }),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await openspecCli(
|
||||
["status", "--change", params.changeName, "--json"],
|
||||
exec
|
||||
);
|
||||
if (result.exitCode !== 0) return err(`Error getting status: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_instructions",
|
||||
label: "Get Artifact Instructions",
|
||||
description:
|
||||
"Get enriched instructions for creating a specific artifact or for implementing tasks. " +
|
||||
"\n\nReturns: context (project background), rules (constraints), template (structure), " +
|
||||
"outputPath (where to write the file), and dependencies (files to read first). " +
|
||||
"\n\nIMPORTANT: 'context' and 'rules' guide what YOU write — do NOT copy them into the artifact file. " +
|
||||
"Use 'template' as the structure for the output file. " +
|
||||
"\n\nArtifact IDs (spec-driven schema): proposal, specs, design, tasks. " +
|
||||
"Use artifact='apply' to get implementation instructions (task list + approach).",
|
||||
parameters: Type.Object({
|
||||
changeName: Type.String({ description: "Change name (kebab-case)" }),
|
||||
artifact: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Artifact ID: 'proposal', 'specs', 'design', 'tasks', or 'apply' (for implementation). " +
|
||||
"If omitted, returns instructions for the next pending artifact.",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["instructions", "--change", params.changeName, "--json"];
|
||||
if (params.artifact) args.push(params.artifact);
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error getting instructions: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_validate",
|
||||
label: "Validate Change or Spec",
|
||||
description:
|
||||
"Validate a change or spec for structural correctness. " +
|
||||
"Checks that artifacts follow the expected format and that requirements are well-formed. " +
|
||||
"\n\nFor changes: checks all artifacts are present and valid. " +
|
||||
"For specs: checks requirement and scenario structure. " +
|
||||
"\n\nRun before archiving to catch missing or malformed artifacts. " +
|
||||
"Reports issues as CRITICAL, WARNING, or INFO.",
|
||||
parameters: Type.Object({
|
||||
name: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Change or spec name to validate. If omitted, validates all active changes.",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["validate"];
|
||||
if (params.name) args.push(params.name);
|
||||
const result = await openspecCli(args, exec);
|
||||
// Non-zero may mean validation issues (report them, don't error)
|
||||
return ok(result.output || (result.exitCode === 0 ? "Validation passed." : "Validation issues found."));
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_archive",
|
||||
label: "Archive Completed Change",
|
||||
description:
|
||||
"Archive a completed change after implementation. " +
|
||||
"\n\nWhat happens: " +
|
||||
"(1) Delta specs merge into main openspec/specs/ — the source of truth updates. " +
|
||||
"(2) Change folder moves to openspec/changes/archive/YYYY-MM-DD-<name>/ for history. " +
|
||||
"(3) All artifacts preserved for audit trail. " +
|
||||
"\n\nCall openspec_validate first to catch issues. " +
|
||||
"The archive will warn on incomplete tasks but won't block. " +
|
||||
"Delta spec sync is offered if not already done.",
|
||||
parameters: Type.Object({
|
||||
changeName: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Change name to archive. Provide explicitly for non-interactive use. " +
|
||||
"If omitted, openspec selects interactively (may not work in agent context).",
|
||||
})
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const args = ["archive", "--no-interactive"];
|
||||
if (params.changeName) args.push(params.changeName);
|
||||
const result = await openspecCli(args, exec);
|
||||
if (result.exitCode !== 0) return err(`Error archiving: ${result.output}`);
|
||||
return ok(result.output || "Change archived. Delta specs merged into main specs.");
|
||||
},
|
||||
});
|
||||
|
||||
// ═══════════════════════ SPEC BROWSING ════════════════════════════════════
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_spec_list",
|
||||
label: "List Specs",
|
||||
description:
|
||||
"List all spec domains in the current project. " +
|
||||
"Specs (openspec/specs/) are the stable source of truth — they describe " +
|
||||
"how the system CURRENTLY behaves. " +
|
||||
"Browse them before creating a change to understand what's already specified " +
|
||||
"and which domain your change touches.",
|
||||
parameters: Type.Object({}),
|
||||
async execute(_toolCallId, _params) {
|
||||
const result = await openspecCli(["spec", "list", "--json"], exec);
|
||||
if (result.exitCode !== 0) return err(`Error listing specs: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_spec_show",
|
||||
label: "View Spec",
|
||||
description:
|
||||
"Display a specific spec by ID. " +
|
||||
"Specs contain requirements (what the system must do) and scenarios (concrete examples). " +
|
||||
"Read specs before implementing to understand what behavior is already defined " +
|
||||
"and what your change's delta specs must satisfy or extend.",
|
||||
parameters: Type.Object({
|
||||
specId: Type.String({
|
||||
description: "Spec ID (e.g. 'auth', 'payments', 'ui') — from openspec_spec_list",
|
||||
}),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await openspecCli(["spec", "show", params.specId, "--no-interactive"], exec);
|
||||
if (result.exitCode !== 0) return err(`Error showing spec: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
// ═══════════════════════ SCHEMA MANAGEMENT ════════════════════════════════
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_schema_list",
|
||||
label: "List Available Schemas",
|
||||
description:
|
||||
"List available workflow schemas. " +
|
||||
"Schemas define artifact types and their dependencies. " +
|
||||
"Default: 'spec-driven' (proposal → specs → design → tasks). " +
|
||||
"Custom schemas can be created for specialized workflows " +
|
||||
"(e.g. research-first, rapid, security-review).",
|
||||
parameters: Type.Object({}),
|
||||
async execute(_toolCallId, _params) {
|
||||
// 'openspec schema which' lists the schema resolution path
|
||||
// Try listing via schema which with no name
|
||||
const result = await openspecCli(["schema", "which"], exec);
|
||||
if (result.exitCode !== 0) return err(`Error: ${result.output}`);
|
||||
return ok(result.output);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "openspec_schema_fork",
|
||||
label: "Fork a Schema",
|
||||
description:
|
||||
"Copy an existing schema to this project for customization. " +
|
||||
"Forked schemas live at openspec/schemas/<name>/ and can be modified " +
|
||||
"to define custom artifact types, dependencies, and instruction templates. " +
|
||||
"\n\nExample: fork 'spec-driven' as 'rapid' to create a minimal workflow " +
|
||||
"with just proposal + tasks (skipping design for small changes).",
|
||||
parameters: Type.Object({
|
||||
source: Type.String({ description: "Source schema name to fork (e.g. 'spec-driven')" }),
|
||||
name: Type.String({ description: "New schema name (kebab-case)" }),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await openspecCli(["schema", "fork", params.source, params.name], exec);
|
||||
if (result.exitCode !== 0) return err(`Error forking schema: ${result.output}`);
|
||||
return ok(result.output || `Schema '${params.source}' forked as '${params.name}'. Edit openspec/schemas/${params.name}/schema.yaml to customize.`);
|
||||
},
|
||||
});
|
||||
}
|
||||
20
package.json
Normal file
20
package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@trentuna/pi-openspec",
|
||||
"version": "0.1.0",
|
||||
"description": "OpenSpec pi extension — spec-driven development workflow for pi agent sessions",
|
||||
"keywords": ["pi-package", "openspec", "spec-driven", "ai-agents", "trentuna"],
|
||||
"author": "Trentuna <ludo@trentuna.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://localhost:3001/trentuna/pi-openspec.git"
|
||||
},
|
||||
"homepage": "https://trentuna.com",
|
||||
"pi": {
|
||||
"extensions": ["./extensions"],
|
||||
"skills": ["./skills"]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
159
skills/openspec/SKILL.md
Normal file
159
skills/openspec/SKILL.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
name: openspec
|
||||
description: Spec-driven development workflow using OpenSpec. Use when initializing OpenSpec in a project, creating change proposals, writing specs, generating designs and task lists, implementing changes, or archiving completed work. Provides step-by-step workflow guidance for all 13 openspec tools.
|
||||
---
|
||||
|
||||
# OpenSpec Workflow Skill
|
||||
|
||||
OpenSpec organizes software work into **specs** (source of truth) and **changes** (proposed modifications). The workflow guides you from intent to implementation to archive.
|
||||
|
||||
## When to load this skill
|
||||
|
||||
Load when you are:
|
||||
- Setting up OpenSpec in a new or existing project
|
||||
- Creating a change proposal for a new feature or fix
|
||||
- Writing specs, designs, or task lists for an active change
|
||||
- Implementing tasks from a change
|
||||
- Validating or archiving completed work
|
||||
|
||||
## Core Concepts
|
||||
|
||||
**Specs** (`openspec/specs/`) — the source of truth. Describe how the system currently behaves. Organized by domain (e.g., `specs/auth/`, `specs/payments/`).
|
||||
|
||||
**Changes** (`openspec/changes/`) — proposed modifications. Each change is a folder with proposal, delta specs, design, and task list. Changes merge into specs on archive.
|
||||
|
||||
**Artifacts** — the documents inside a change. The default `spec-driven` schema builds them in dependency order:
|
||||
```
|
||||
proposal → specs (delta) → design → tasks → implement → archive
|
||||
```
|
||||
|
||||
**Key insight:** `openspec_instructions` is your north star. Call it before writing each artifact — it returns enriched context, rules, template structure, and output path. Do NOT skip this step.
|
||||
|
||||
## The Standard Workflow
|
||||
|
||||
### 0. Initialize (once per project)
|
||||
|
||||
```
|
||||
openspec_init(path=".", tools="pi")
|
||||
```
|
||||
|
||||
Creates `openspec/` directory, `config.yaml`, and pi-compatible skill files. Run once. Safe to skip if `openspec/` already exists.
|
||||
|
||||
### 1. Orient
|
||||
|
||||
```
|
||||
openspec_list() # see active changes in progress
|
||||
openspec_spec_list() # see stable spec domains
|
||||
openspec_show(name="change-name") # read an existing change in full
|
||||
```
|
||||
|
||||
Run at session start to understand what's in flight before creating anything new.
|
||||
|
||||
### 2. Create a Change
|
||||
|
||||
```
|
||||
openspec_new_change(name="add-user-auth", description="Add JWT auth to API")
|
||||
```
|
||||
|
||||
Creates `openspec/changes/add-user-auth/` with scaffolded artifact files. Name in kebab-case. Be specific — avoid generic names like `update` or `wip`.
|
||||
|
||||
Then immediately:
|
||||
```
|
||||
openspec_status(changeName="add-user-auth")
|
||||
```
|
||||
|
||||
This shows the dependency graph: which artifacts exist, which are ready to write, which are blocked.
|
||||
|
||||
### 3. Write Each Artifact (in dependency order)
|
||||
|
||||
For each artifact the status shows as "ready":
|
||||
|
||||
```
|
||||
openspec_instructions(changeName="add-user-auth", artifact="proposal")
|
||||
```
|
||||
|
||||
Read the returned `context`, `rules`, and `template` carefully. The `outputPath` tells you exactly where to write. The `dependencies` list tells you what to read first.
|
||||
|
||||
**IMPORTANT:** `context` and `rules` guide what YOU write — do NOT copy them into the artifact file. Use `template` as the structure.
|
||||
|
||||
Write the artifact using the Write tool. Then call `openspec_status` again to see what unlocks next.
|
||||
|
||||
Repeat for: `specs`, `design`, `tasks`.
|
||||
|
||||
### 4. Implement Tasks
|
||||
|
||||
```
|
||||
openspec_instructions(changeName="add-user-auth", artifact="apply")
|
||||
```
|
||||
|
||||
Returns the task list and implementation approach. Work through tasks, checking them off in `tasks.md` as you go. Commit per completed task (not per file edit).
|
||||
|
||||
### 5. Validate
|
||||
|
||||
```
|
||||
openspec_validate(name="add-user-auth")
|
||||
```
|
||||
|
||||
Checks structural correctness of all artifacts. Fix any CRITICAL issues before archiving. WARNINGS are advisory.
|
||||
|
||||
### 6. Archive
|
||||
|
||||
```
|
||||
openspec_archive(changeName="add-user-auth")
|
||||
```
|
||||
|
||||
Merges delta specs into `openspec/specs/`. Moves the change folder to `openspec/changes/archive/YYYY-MM-DD-add-user-auth/`. The change is done.
|
||||
|
||||
## Schema Operations
|
||||
|
||||
```
|
||||
openspec_schema_list() # see available workflow schemas
|
||||
openspec_schema_fork(source="spec-driven", name="rapid") # fork for customization
|
||||
```
|
||||
|
||||
Use `rapid` or other custom schemas when the full `spec-driven` workflow is more than the task needs (e.g., a small bugfix that doesn't warrant a full spec + design).
|
||||
|
||||
## Reading Existing Specs
|
||||
|
||||
```
|
||||
openspec_spec_list() # list spec domains
|
||||
openspec_spec_show(specId="auth") # read a spec in full
|
||||
```
|
||||
|
||||
Always read relevant specs before creating a change — understand what's already specified and which domain your change touches.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Starting fresh on an existing codebase:**
|
||||
1. `openspec_init` → `openspec_spec_list` (probably empty) → `openspec_new_change` for your first feature → write artifacts → implement
|
||||
|
||||
**Resuming work in progress:**
|
||||
1. `openspec_list` → `openspec_show` (read the change) → `openspec_status` (see what's ready) → `openspec_instructions` for next artifact → continue
|
||||
|
||||
**Quick change (small fix):**
|
||||
1. `openspec_new_change(schema="rapid")` if you have a rapid schema, else use `spec-driven` and skip non-critical artifacts → `openspec_instructions(artifact="apply")` → implement → `openspec_archive`
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Don't skip `openspec_instructions`** before writing an artifact. The context and rules it returns are essential — they encode project-specific constraints that the template alone doesn't carry.
|
||||
- **Commit per task, not per file.** The session commit protocol applies — each logical unit of work is one commit.
|
||||
- **`openspec_update` after config changes.** If you modify `openspec/config.yaml` or switch schemas, run `openspec_update` to regenerate skill files.
|
||||
- **Delta specs are not full specs.** A delta spec shows what's ADDED/MODIFIED/REMOVED relative to existing specs. Don't rewrite the entire domain — only the changes.
|
||||
|
||||
## Tool Reference
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `openspec_init` | Initialize OpenSpec in a project |
|
||||
| `openspec_update` | Regenerate skill/instruction files after config changes |
|
||||
| `openspec_list` | List active changes (WIP) or stable specs |
|
||||
| `openspec_show` | Show a change or spec in full |
|
||||
| `openspec_new_change` | Create a new change directory with scaffolded artifacts |
|
||||
| `openspec_status` | Artifact dependency graph and completion state |
|
||||
| `openspec_instructions` | Enriched instructions for creating a specific artifact |
|
||||
| `openspec_validate` | Validate change/spec structural correctness |
|
||||
| `openspec_archive` | Archive completed change (merges delta specs into main) |
|
||||
| `openspec_spec_list` | List spec domains (stable source of truth) |
|
||||
| `openspec_spec_show` | View a specific spec by ID |
|
||||
| `openspec_schema_list` | List available workflow schemas |
|
||||
| `openspec_schema_fork` | Fork a schema for customization |
|
||||
Loading…
Add table
Add a link
Reference in a new issue