feat: add extract-feature and export-spec portability skills

Two new OpenSpec skills for porting features to sandboxed codebases:
- /opsx:extract-feature generates minimal, printable code recipes
- /opsx:export-spec generates compact specs for AI-assisted reimplementation
Both support cumulative dependency analysis across archived changes.

Includes first export of migrate-to-semantic-kernel in all three formats:
code recipe (~120 lines), portable spec (~40 lines), OpenSpec variant (~25 lines).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
local
2026-04-05 00:59:06 +01:00
parent 471e9ce935
commit d3300c7db9
7 changed files with 1021 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
---
name: "OPSX: Export Spec"
description: Export a feature as a compact, portable spec for AI-assisted reimplementation on a sandboxed machine
category: Workflow
tags: [workflow, portability, experimental]
---
Export a feature as a portable spec for AI-assisted reimplementation.
Instead of retyping code, you retype a compact spec. The AI on the sandbox generates the code.
---
**Input**: The argument after `/opsx:export-spec` is a change name (active or archived), or a description of the feature. If omitted, prompt for selection.
**Steps**
1. **Identify the source feature**
Same selection logic as `/opsx:extract-feature`:
- Check active changes and archive for the change name
- If not found, prompt with **AskUserQuestion tool**
- Read all artifacts: `proposal.md`, `design.md`, `tasks.md`, specs
2. **Analyze dependency chain (cumulative mode)**
Features often build on each other. Before generating the spec, determine
what the target feature depends on.
a. Read all archived change proposals in `openspec/changes/archive/` (sorted by date).
b. Build a dependency graph: what does the target require? What's superseded?
c. Ask the user:
> "This feature depends on earlier changes. How should I scope the spec?"
> 1. **Cumulative** — include all foundation (recommended for fresh codebase)
> 2. **Delta only** — just this change (target already has foundation)
> 3. **Custom** — pick which dependencies to include
d. In cumulative mode: merge into one coherent spec, skip superseded components.
e. In delta mode: add an "Assumes" section listing what must already exist.
3. **Read the actual implementation**
Read all source files created or modified by this feature (and dependencies if cumulative).
The spec must reflect what was actually built, not just what was planned.
4. **Determine the target context**
Use **AskUserQuestion tool** to ask:
> "Tell me about the target codebase:
> 1. Project name / root namespace
> 2. Existing stack (ASP.NET Core? Blazor? MudBlazor?)
> 3. Does it already have any of these? (controllers, DI setup, chat endpoint)
> 4. Does the target have OpenSpec? GitHub Copilot? Claude Code?"
This shapes what the spec assumes vs what it must specify.
5. **Generate the portable spec**
Create a single markdown document that is:
- **Compact**: Target ~30-50 lines for a medium feature
- **Precise**: Unambiguous enough for an AI to implement correctly
- **Self-contained**: No references to external files or repos
- **Stack-aware**: Uses the right terminology for the target stack
Structure:
```markdown
# Feature: <Name>
## Target: <project name> (<stack>)
## Packages
<list with versions>
## Architecture
<2-3 sentence overview>
## Components
### <Component>: <path hint>
- <What it does>
- <Key behavior>
- <Interface/contract>
## Contracts
<API shapes, model definitions — things that MUST be exact>
## Wiring
<DI registration, middleware order, config keys — dependency order>
## Behavior
<Non-obvious requirements>
```
**Compression strategies:**
- Use bullet points, not prose
- Specify contracts precisely (field names, types, API shapes)
- Let the AI infer standard patterns
- Only specify non-obvious behavior
- Omit anything the AI would do by default
6. **Estimate typing effort**
Count characters in the spec. Compare to the code recipe equivalent.
Show the compression ratio.
7. **Optionally generate an OpenSpec-compatible version**
If the target has OpenSpec, also generate:
- A `proposal.md` (minimal — 5-10 lines)
- A `tasks.md` (implementation steps)
Save as: `openspec/exports/<change-name>-openspec.md`
8. **Write the output**
Save to: `openspec/exports/<change-name>-spec.md`
Display the full content for review.
**Guardrails**
- Prioritize precision over brevity — ambiguity wastes more time than length
- Always include exact field names, types, and API shapes
- Include non-obvious gotchas (like /v1 base URL requirements)
- Mental test: could an AI implement this correctly without seeing the original code?
- If too complex for ~50 lines, split into multiple specs by component
- Always show the compression ratio
- Must be readable when printed in monospace — no wide tables or long lines
- In cumulative mode, the spec must read as one coherent feature, not a list of changes
- Skip superseded components — always describe the latest version
- In delta mode, add an "Assumes" section so the target AI knows what must exist
- In the output header, note which changes were included and which were skipped
ARGUMENTS: based on the above

View File

@@ -0,0 +1,113 @@
---
name: "OPSX: Extract Feature"
description: Extract a feature into a minimal, printable code recipe for manual reimplementation
category: Workflow
tags: [workflow, portability, experimental]
---
Extract a feature into a minimal, printable code recipe for manual reimplementation.
Generates a markdown document with:
- Package dependencies
- Ordered code blocks (no comments, no boilerplate)
- Clear markers for generic vs domain-specific code
Print it, take it to the sandbox, type it in.
---
**Input**: The argument after `/opsx:extract-feature` is a change name (active or archived), a git commit range, or a list of files. If omitted, prompt for selection.
**Steps**
1. **Identify the source feature**
If a change name is provided:
- Check active changes: `openspec list --json`
- Check archive: look in `openspec/changes/archive/` for directories ending with the name
- If found, read its artifacts: `proposal.md`, `design.md`, `tasks.md`
If no name provided:
- Run `openspec list --json` and list archived changes
- Use **AskUserQuestion tool** to let the user select
If a file list or commit range is provided instead:
- Read those files directly
- Identify the feature from the code
2. **Analyze dependency chain (cumulative mode)**
Features often build on each other. Before generating the recipe, determine
what the target feature depends on.
a. Read all archived change proposals in `openspec/changes/archive/` (sorted by date).
b. Build a dependency graph: what does the target require? What's superseded?
c. Ask the user:
> "This feature depends on earlier changes. How should I scope the recipe?"
> 1. **Cumulative** — include all foundation code (recommended for fresh codebase)
> 2. **Delta only** — just this change's code (target already has foundation)
> 3. **Custom** — pick which dependencies to include
d. In cumulative mode: merge the chain, skip superseded code, use final file versions.
e. In delta mode: include only the selected change's code.
3. **Analyze the feature scope**
From the change artifacts and/or code (across full dependency chain if cumulative), determine:
- Which files were created or modified
- What NuGet/npm packages were added
- What the dependency order is (e.g., models before controllers)
- What is generic infrastructure vs domain-specific logic
Read all relevant source files. Always read the final current version of each file.
4. **Generate the code recipe**
Create a markdown document with these sections, in this order:
**Header**: Feature name, source change, date
**Prerequisites**: Package references with exact versions
**Steps**: Ordered by dependency. Each step contains:
- Step number and action (e.g., "New file", "Add to existing file", "Modify")
- Target path (relative, adaptable)
- Namespace placeholder: `__YOUR_NAMESPACE__` where the target project namespace goes
- **Code block**: The actual code, stripped of:
- All comments (// and /* */ and /// XML docs)
- Redundant blank lines
- Verbose variable names that can be shortened
- Any code not directly related to the feature
- If the step modifies an existing file: show only the code to add, with a brief marker for insertion point (e.g., "Add after AddControllers()")
**Domain-Specific Sections**: Clearly marked with `// ADAPT:` prefix explaining what to change for the target domain
5. **Optimize for retyping**
Review the generated document and:
- Merge small files if they can be combined
- Remove any using statements that the IDE will auto-add
- Shorten any unnecessarily verbose code
- Ensure no step exceeds ~40 lines (split if needed)
- Add line counts per step so the user can estimate effort
- Total the overall line count at the top
6. **Write the output**
Save to: `openspec/exports/<change-name>-recipe.md`
Also display the full content so the user can review it immediately.
**Guardrails**
- Never include comments in code blocks — the goal is minimum keystrokes
- Always read the actual current source files, not just the change artifacts
- Preserve compilation order: models -> services -> controllers -> DI registration
- Mark domain-specific code clearly so the user knows what to adapt vs copy verbatim
- Keep each step self-contained — the user may take breaks between steps
- If a feature spans more than ~200 lines of stripped code, warn the user and suggest using `/opsx:export-spec` instead
- Output must be valid markdown that renders well when printed
- When in cumulative mode, skip superseded code — always use the latest version of each file
- In the output header, show which changes were included and which were skipped
- If a dependency chain is long (4+ changes), suggest `/opsx:export-spec` as more efficient
ARGUMENTS: based on the above

View File

@@ -0,0 +1,217 @@
---
name: openspec-export-spec
description: Export a feature as a compact, portable spec that an AI agent (Copilot, Claude) on a sandboxed machine can implement from. Optimized for minimal hand-typing.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Export a feature as a portable spec for AI-assisted reimplementation on a sandboxed machine.
Instead of retyping code, you retype a compact spec. The AI on the sandbox (Copilot, Claude, OpenSpec) generates the code from the spec.
---
**Input**: A change name (active or archived), or a description of the feature. If omitted, prompt for selection.
**Steps**
1. **Identify the source feature**
Same as `openspec-extract-feature` step 1:
- Check active changes and archive for the change name
- If not found, prompt with **AskUserQuestion tool**
- Read all artifacts: `proposal.md`, `design.md`, `tasks.md`, specs
2. **Analyze dependency chain (cumulative mode)**
Features often build on each other. Before generating the spec, determine
what the target feature depends on.
a. **Read all archived change proposals** in `openspec/changes/archive/` (sorted by date).
For each, read `proposal.md` to understand what it adds and what it depends on.
b. **Build a dependency graph**:
- Which changes does the target feature require?
- Which earlier changes are superseded? (e.g., if Change 6 replaces Change 3's
implementation, include Change 6's version, not Change 3's)
- Which changes are unrelated and can be skipped?
c. **Ask the user** using **AskUserQuestion tool**:
> "This feature depends on earlier changes. How should I scope the spec?"
>
> Options:
> 1. **Cumulative** — include all foundation this feature needs (recommended if target is a fresh codebase)
> 2. **Delta only** — just what this specific change adds (use if target already has the foundation)
> 3. **Custom** — let me pick which dependencies to include
Show the dependency chain so the user can decide.
d. **In cumulative mode**: The spec covers the entire stack from foundation to feature.
- Merge packages, components, wiring from all included changes
- Skip superseded components (use the latest version)
- The spec should read as a single coherent feature, not a list of changes
e. **In delta mode**: The spec covers only the selected change.
- Add a "Assumes" section listing what must already exist in the target
3. **Read the actual implementation**
Read all source files that were created or modified by this feature
(and its dependencies if cumulative).
The spec must reflect what was actually built, not just what was planned.
4. **Determine the target context**
Use **AskUserQuestion tool** to ask:
> "Tell me about the target codebase:
> 1. Project name / root namespace
> 2. Existing stack (ASP.NET Core? Blazor? MudBlazor?)
> 3. Does it already have any of these? (controllers, DI setup, chat endpoint)
> 4. Does the target have OpenSpec? GitHub Copilot? Claude Code?"
This shapes what the spec assumes vs what it must specify.
5. **Generate the portable spec**
Create a single markdown document that is:
- **Compact**: Target ~30-50 lines for a medium feature
- **Precise**: Unambiguous enough for an AI to implement correctly
- **Self-contained**: No references to external files or repos
- **Stack-aware**: Uses the right terminology for the target stack
Structure:
```markdown
# Feature: <Name>
## Target: <project name> (<stack>)
## Packages
<list with versions>
## Architecture
<2-3 sentence overview of how the pieces fit together>
## Components
### <Component 1>: <path hint>
- <What it does — 1 line>
- <Key behavior — 1 line>
- <Interface/contract — 1 line>
### <Component 2>: <path hint>
...
## Contracts
<API shapes, model definitions, SSE formats — the things that MUST be exact>
## Wiring
<DI registration, middleware order, configuration keys — in dependency order>
## Behavior
<Key behavioral requirements that aren't obvious from the structure>
```
**Compression strategies:**
- Use bullet points, not prose
- Specify contracts precisely (field names, types, API shapes)
- Let the AI infer standard patterns (error handling, null checks, etc.)
- Only specify non-obvious behavior (the surprising parts)
- Omit anything the AI would do by default for the given stack
6. **Estimate typing effort**
Count characters in the spec. Compare to the code recipe equivalent.
Show the compression ratio:
```
Code recipe: ~120 lines to type
This spec: ~35 lines to type
Compression: 3.4x
```
7. **Optionally generate an OpenSpec-compatible version**
If the target has OpenSpec, also generate a version structured as:
- A `proposal.md` (minimal — 5-10 lines)
- A `tasks.md` (implementation steps the target AI follows)
These can be even more compact because OpenSpec provides the scaffolding.
Save this variant alongside the main spec.
8. **Write the output**
Save to: `openspec/exports/<change-name>-spec.md`
If OpenSpec variant: `openspec/exports/<change-name>-openspec.md`
Display the full content for review.
**Output Format**
```markdown
# Feature: Semantic Kernel Chat with Tool Calling
## Target: ApplicationX (ASP.NET Core + Blazor WASM + MudBlazor)
## Packages
- Microsoft.SemanticKernel 1.74.0
- Microsoft.SemanticKernel.Connectors.OpenAI 1.74.0
## Architecture
POST /api/chat endpoint accepts messages, runs them through SK's chat completion
with auto tool calling enabled, streams response as SSE. An ExtractionPlugin
validates structured data extracted by the LLM.
## Components
### ChatController: Controllers/ChatController.cs
- POST endpoint, injects Kernel via DI
- Converts ChatMessage[] to SK ChatHistory
- Streams via GetStreamingChatMessageContentsAsync
- Outputs SSE: `data: {"text":"..."}\n\n` then `data: [DONE]\n\n`
### ExtractionPlugin: Plugins/ExtractionPlugin.cs
- [KernelFunction("validate_extracted_fields")]
- Accepts JSON string, deserializes to ExtractedFields
- Returns {"isValid": bool, "errors": string[]}
### ExtractedFields: Models/ExtractedFields.cs
- Required: Client(string), Project(string), Hours(decimal), Rate(decimal), Currency(string), Date(string)
- Optional: Description(string), PoNumber(string)
### ValidationResult: Models/ValidationResult.cs
- IsValid(bool), Errors(List<string>)
### ChatRequest/ChatMessage: Shared/Models/
- ChatRequest: Messages(List<ChatMessage>)
- ChatMessage: Role(string), Content(string)
## Wiring (Program.cs, add after AddControllers)
- AddOpenAIChatCompletion(model, endpoint with /v1 suffix, apiKey)
- AddKernel()
- AddSingleton<ExtractionPlugin>()
- UseCors for Blazor client origin
## Behavior
- FunctionChoiceBehavior.Auto() enables autonomous tool calling
- SK's built-in limit prevents runaway tool call loops
- Plugin imported per-request via _kernel.ImportPluginFromObject
- Base URL must include /v1 — OpenAI SDK appends chat/completions directly
```
**Lines to type: ~35 | Code equivalent: ~150 lines | Compression: 4.3x**
**Guardrails**
- Prioritize precision over brevity — an ambiguous spec wastes more time than a slightly longer one
- Always include exact field names, types, and API shapes — these are the hardest to guess
- Include non-obvious gotchas (like the /v1 base URL requirement)
- Test the spec mentally: could an AI implement this correctly without seeing the original code?
- If the feature is too complex for a single spec page (~50+ lines), split into multiple specs by component
- Always show the compression ratio so the user can decide between spec and code recipe
- The spec must be readable when printed in monospace — no wide tables or long lines
- In cumulative mode, the spec must read as one coherent feature — not a list of sequential changes
- Skip superseded components — always describe the latest version of each piece
- In delta mode, add an "Assumes" section so the target AI knows what must already exist
- In the output header, note which changes were included and which were skipped

View File

@@ -0,0 +1,170 @@
---
name: openspec-extract-feature
description: Extract a feature from a change (archived or active) into a minimal, printable code recipe optimized for manual retyping into a sandboxed codebase.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Extract a feature into a minimal, printable code recipe for manual reimplementation.
Generates a markdown document with:
- Package dependencies
- Ordered code blocks (no comments, no boilerplate)
- Clear markers for generic vs domain-specific code
Print it, take it to the sandbox, type it in.
---
**Input**: A change name (active or archived), a git commit range, or a list of files. If omitted, prompt for selection.
**Steps**
1. **Identify the source feature**
If a change name is provided:
- Check active changes: `openspec list --json`
- Check archive: look in `openspec/changes/archive/` for directories ending with the name
- If found, read its artifacts: `proposal.md`, `design.md`, `tasks.md`
If no name provided:
- Run `openspec list --json` and list archived changes
- Use **AskUserQuestion tool** to let the user select
If a file list or commit range is provided instead:
- Read those files directly
- Identify the feature from the code
2. **Analyze dependency chain (cumulative mode)**
Features are often not independent — they build on each other. Before generating
the recipe, determine what the target feature depends on.
a. **Read all archived change proposals** in `openspec/changes/archive/` (sorted by date).
For each, read `proposal.md` to understand what it adds and what it depends on.
b. **Build a dependency graph**:
- Which changes does the target feature require?
- Which earlier changes are superseded? (e.g., if Change 6 replaces Change 3's
implementation, include Change 6's version, not Change 3's)
- Which changes are unrelated and can be skipped?
c. **Ask the user** using **AskUserQuestion tool**:
> "This feature depends on earlier changes. How should I scope the recipe?"
>
> Options:
> 1. **Cumulative** — include all foundation code this feature needs (recommended if target is a fresh codebase)
> 2. **Delta only** — just the code this specific change adds (use if target already has the foundation)
> 3. **Custom** — let me pick which dependencies to include
Show the dependency chain so the user can decide, e.g.:
```
migrate-to-semantic-kernel depends on:
← basic-chat-interface (UI + shared models)
← wire-responses-api (superseded — SK replaces the proxy)
← multi-turn-conversations (chat history)
```
d. **In cumulative mode**: Merge the dependency chain into a single coherent recipe.
- Walk changes in dependency order
- Skip superseded code (use the latest version of each file)
- Collapse packages from all changes into one prerequisites section
- If a file was created in Change 2 and modified in Change 5, include only the final version
e. **In delta mode**: Include only the code from the selected change.
3. **Analyze the feature scope**
From the change artifacts and/or code, determine:
- Which files were created or modified (across the full dependency chain if cumulative)
- What NuGet/npm packages were added
- What the dependency order is (e.g., models before controllers)
- What is generic infrastructure vs domain-specific logic
Read all relevant source files in the current codebase to get the actual current code.
**Always read the final current version of each file**, not intermediate versions.
3. **Generate the code recipe**
Create a markdown document with these sections, in this order:
**Header**: Feature name, source change, date
**Prerequisites**: Package references with exact versions
**Steps**: Ordered by dependency. Each step contains:
- Step number and action (e.g., "New file", "Add to existing file", "Modify")
- Target path (relative, adaptable)
- Namespace placeholder: `__YOUR_NAMESPACE__` where the target project namespace goes
- **Code block**: The actual code, stripped of:
- All comments (// and /* */ and /// XML docs)
- Redundant blank lines
- Verbose variable names that can be shortened
- Any code not directly related to the feature
- If the step modifies an existing file: show only the code to add, with a brief marker for insertion point (e.g., "Add after AddControllers()")
**Domain-Specific Sections**: Clearly marked with `// ADAPT:` prefix explaining what to change for the target domain
4. **Optimize for retyping**
Review the generated document and:
- Merge small files if they can be combined
- Remove any using statements that the IDE will auto-add
- Shorten any unnecessarily verbose code
- Ensure no step exceeds ~40 lines (split if needed)
- Add line counts per step so the user can estimate effort
- Total the overall line count at the top
5. **Write the output**
Save to: `openspec/exports/<change-name>-recipe.md`
Also display the full content so the user can review it immediately.
**Output Format**
```markdown
# Feature Recipe: <Feature Name>
**Source**: <change-name> | **Lines to type**: ~<N>
## Prerequisites
- `Microsoft.SemanticKernel` 1.74.0
- `Microsoft.SemanticKernel.Connectors.OpenAI` 1.74.0
## Step 1: <Action> — <path> (~N lines)
```csharp
<stripped code>
```
## Step 2: <Action> — <path> (~N lines)
Insert after `<marker>`:
```csharp
<stripped code>
```
## Domain-Specific (adapt these)
### ExtractionPlugin.cs
```csharp
// ADAPT: Replace field names and validation logic for your domain
<code>
```
```
**Guardrails**
- Never include comments in code blocks — the goal is minimum keystrokes
- Always read the actual current source files, not just the change artifacts
- Preserve compilation order: models → services → controllers → DI registration
- Mark domain-specific code clearly so the user knows what to adapt vs copy verbatim
- Keep each step self-contained — the user may take breaks between steps
- If a feature spans more than ~200 lines of stripped code, warn the user and suggest using `/opsx:export-spec` instead
- Output must be valid markdown that renders well when printed
- When in cumulative mode, skip superseded code — always use the latest version of each file
- In the output header, show which changes were included and which were skipped (with reason)
- If a dependency chain is long (4+ changes), suggest `/opsx:export-spec` as a more efficient alternative