Add whitemagic-foundation OpenSpec design; isolate reference libs

Design-only foundation for WhiteMagic, a .NET 8 x64 library unifying the
four studied process-manipulation libs. Adds proposal, design (7 decisions),
7 capability specs, and TDD task breakdown; all validate strict.

Move Blackmagic, Blackmagic-old, GreyMagic, MemorySharp, fasm into
reference/ (gitignored) — studied, not built here; each has its own
upstream repo and nested .git. Rewrite plan doc paths to reference/.

Corrects two factual defects found in review:
- current BlackMagic has no D3D EndScene hook; MainThreadPump is net-new
  built on DetourManager, not a port
- no BlackMagic.slnx exists; task 1.3 creates a fresh solution

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kbe
2026-07-21 16:50:03 +02:00
co-authored by Claude Opus 4.8
commit 4405af15fd
44 changed files with 4495 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
---
name: "OPSX: Apply"
description: Implement tasks from an OpenSpec change (Experimental)
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, artifacts, experimental]
---
Implement tasks from an OpenSpec change.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue`
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! You can archive this change with `/opsx:archive`.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
+161
View File
@@ -0,0 +1,161 @@
---
name: "OPSX: Archive"
description: Archive a completed change in the experimental workflow
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, archive, experimental]
---
Archive a completed change in the experimental workflow.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx:archive` (e.g., `/opsx:archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Prompt user for confirmation to continue
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Prompt user for confirmation to continue
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Spec sync status (synced / sync skipped / no delta specs)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs
All artifacts complete. All tasks complete.
```
**Output On Success (No Delta Specs)**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** No delta specs
All artifacts complete. All tasks complete.
```
**Output On Success With Warnings**
```
## Archive Complete (with warnings)
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** Sync skipped (user chose to skip)
**Warnings:**
- Archived with 2 incomplete artifacts
- Archived with 3 incomplete tasks
- Delta spec sync was skipped (user chose to skip)
Review the archive if this was not intentional.
```
**Output On Error (Archive Exists)**
```
## Archive Failed
**Change:** <change-name>
**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
Target archive directory already exists.
**Options:**
1. Rename the existing archive
2. Delete the existing archive if it's a duplicate
3. Wait until a different date to archive
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+175
View File
@@ -0,0 +1,175 @@
---
name: "OPSX: Explore"
description: "Enter explore mode - think through ideas, investigate problems, clarify requirements"
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, explore, experimental, thinking]
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The argument after `/opsx:explore` is whatever the user wants to think about. Could be:
- A vague idea: "real-time collaboration"
- A specific problem: "the auth system is getting unwieldy"
- A change name: "add-dark-mode" (to explore in context of that change)
- A comparison: "postgres vs sqlite for this"
- Nothing (just enter explore mode)
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
If the user mentioned a specific change name, read its artifacts for context.
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+110
View File
@@ -0,0 +1,110 @@
---
name: "OPSX: Propose"
description: Propose a new change - create it and generate all artifacts in one step
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, artifacts, experimental]
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx:apply
---
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The argument after `/opsx:propose` is the change name (kebab-case), OR a description of what the user wants to build.
**Steps**
1. **If no input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` to start implementing."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
+144
View File
@@ -0,0 +1,144 @@
---
name: "OPSX: Sync"
description: Sync delta specs from a change to main specs
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, specs, experimental]
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx:sync` (e.g., `/opsx:sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result
+82
View File
@@ -0,0 +1,82 @@
---
name: "OPSX: Update"
description: Update a change - revise existing planning artifacts and keep them coherent (Experimental)
allowed-tools: Bash(openspec:*)
category: Workflow
tags: [workflow, artifacts, experimental]
---
Revise a change's existing planning artifacts and keep them coherent. Never edit code.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx:update` (e.g., `/opsx:update add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to update.
Present the top 3-4 most recently modified changes as options, showing:
- Change name
- Schema (from `schema` field if present, otherwise "spec-driven")
- Status (e.g., "0/5 tasks", "complete", "no tasks")
- How recently it was modified (from `lastModified` field)
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to update.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Get the change's artifacts**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand current state. The response includes:
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
- `isComplete`: Boolean indicating if all artifacts are complete
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
The artifact ids and paths come from the active schema - do NOT assume them, and do NOT branch on hardcoded artifact names. Custom schemas must work unchanged.
The files to edit are `artifactPaths.<id>.existingOutputPaths` - the concrete files that exist on disk, already glob-expanded for glob artifacts (e.g. `specs/**/*.md`). Do NOT write to `resolvedOutputPath`: for a glob artifact it is still the glob pattern, not a real file.
3. **Understand the request**
- If the user asked for a specific revision ("the design now uses X"), that is the starting edit.
- If they only said "update" / "make this coherent", treat it as a coherence review: read the existing artifacts and check them against each other for contradictions, gaps, and duplication.
4. **Read and reconcile**
- Read the artifact(s) the request touches and the change's other existing artifacts.
- Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised.
- Note everything that is now inconsistent, missing, or contradictory.
- Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `/opsx:continue` to create them.
- If the change is already coherent, say so and make no edits.
5. **Confirm and apply, one artifact at a time**
- Show each proposed revision and why. Write only after the user confirms.
- If the user rejects a revision, do not write it - leave that artifact unchanged.
- When a substantial rewrite is needed, get that artifact's rules and template first:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
6. **Point to the next step (guidance only - NEVER act on it)**
- Artifacts still missing -> suggest `/opsx:continue` to create them.
- Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `/opsx:apply` to carry the delta into code.
- Everything done and implemented -> suggest `/opsx:archive`.
**Output**
After each invocation, show:
- Which artifacts were revised (and which proposed revisions were rejected)
- Anything deferred to `/opsx:continue` (not-yet-created artifacts or files)
- Where the change stands and the recommended next command
**Guardrails**
- Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `/opsx:apply`.
- Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names.
- Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`.
- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `/opsx:continue`'s job.
- Confirm every edit with the user before writing.
- If the request changes the change's *intent* rather than refining it, recommend starting fresh with `/opsx:new` (the "Update vs. Start Fresh" heuristic).
@@ -0,0 +1,160 @@
---
name: openspec-apply-change
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Implement tasks from an OpenSpec change.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! Ready to archive this change.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
@@ -0,0 +1,118 @@
---
name: openspec-archive-change
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Archive a completed change in the experimental workflow.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Whether specs were synced (if applicable)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
All artifacts complete. All tasks complete.
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use openspec-sync-specs approach (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+290
View File
@@ -0,0 +1,290 @@
---
name: openspec-explore
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Handling Different Entry Points
**User brings a vague idea:**
```
User: I'm thinking about adding real-time collaboration
You: Real-time collab is a big space. Let me think about this...
COLLABORATION SPECTRUM
════════════════════════════════════════════
Awareness Coordination Sync
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Presence│ │Cursors │ │ CRDT │
│ "3 │ │ Multi │ │Conflict│
│online" │ │ select │ │ free │
└────────┘ └────────┘ └────────┘
│ │ │
trivial moderate complex
Where's your head at?
```
**User brings a specific problem:**
```
User: The auth system is a mess
You: [reads codebase]
┌─────────────────────────────────────────────┐
│ CURRENT AUTH FLOW │
└─────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Google │ │ GitHub │ │ Email │
│ OAuth │ │ OAuth │ │ Magic │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
┌───────────┐
│ Session │
└─────┬─────┘
┌───────────┐
│ Perms │
└───────────┘
I see three tangles. Which one's burning?
```
**User is stuck mid-implementation:**
```
User: /opsx:explore add-auth-system
The OAuth integration is more complex than expected
You: [reads change artifacts]
You're on task 4: "Implement OAuth flow"
Let me trace what's involved...
[draws diagram, explores options, suggests paths]
Want to update the design to reflect this?
Or add a spike task to investigate?
```
**User wants to compare options:**
```
User: Should we use Postgres or SQLite?
You: Generic answer is boring. What's the context?
User: A CLI tool that tracks local dev environments
You: That changes everything.
┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘
Key constraints:
• No daemon running
• Must work offline
• Single user
SQLite Postgres
Deployment embedded ✓ needs server ✗
Offline yes ✓ no ✗
Single file yes ✓ no ✗
SQLite. Not even close.
Unless... is there a sync component?
```
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Create a change proposal
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+114
View File
@@ -0,0 +1,114 @@
---
name: openspec-propose
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx:apply
---
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
+148
View File
@@ -0,0 +1,148 @@
---
name: openspec-sync-specs
description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result
@@ -0,0 +1,86 @@
---
name: openspec-update-change
description: Update an OpenSpec change by revising its existing planning artifacts and keeping them coherent with one another. Use when the user wants to revise a change's plan, fold new decisions into it, or reconcile its artifacts after an edit. Never edits code.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Revise a change's existing planning artifacts and keep them coherent. Never edit code.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to update.
Present the top 3-4 most recently modified changes as options, showing:
- Change name
- Schema (from `schema` field if present, otherwise "spec-driven")
- Status (e.g., "0/5 tasks", "complete", "no tasks")
- How recently it was modified (from `lastModified` field)
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to update.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Get the change's artifacts**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand current state. The response includes:
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
- `isComplete`: Boolean indicating if all artifacts are complete
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
The artifact ids and paths come from the active schema - do NOT assume them, and do NOT branch on hardcoded artifact names. Custom schemas must work unchanged.
The files to edit are `artifactPaths.<id>.existingOutputPaths` - the concrete files that exist on disk, already glob-expanded for glob artifacts (e.g. `specs/**/*.md`). Do NOT write to `resolvedOutputPath`: for a glob artifact it is still the glob pattern, not a real file.
3. **Understand the request**
- If the user asked for a specific revision ("the design now uses X"), that is the starting edit.
- If they only said "update" / "make this coherent", treat it as a coherence review: read the existing artifacts and check them against each other for contradictions, gaps, and duplication.
4. **Read and reconcile**
- Read the artifact(s) the request touches and the change's other existing artifacts.
- Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised.
- Note everything that is now inconsistent, missing, or contradictory.
- Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `/opsx:continue` to create them.
- If the change is already coherent, say so and make no edits.
5. **Confirm and apply, one artifact at a time**
- Show each proposed revision and why. Write only after the user confirms.
- If the user rejects a revision, do not write it - leave that artifact unchanged.
- When a substantial rewrite is needed, get that artifact's rules and template first:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
6. **Point to the next step (guidance only - NEVER act on it)**
- Artifacts still missing -> suggest `/opsx:continue` to create them.
- Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `/opsx:apply` to carry the delta into code.
- Everything done and implemented -> suggest `/opsx:archive`.
**Output**
After each invocation, show:
- Which artifacts were revised (and which proposed revisions were rejected)
- Anything deferred to `/opsx:continue` (not-yet-created artifacts or files)
- Where the change stands and the recommended next command
**Guardrails**
- Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `/opsx:apply`.
- Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names.
- Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`.
- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `/opsx:continue`'s job.
- Confirm every edit with the user before writing.
- If the request changes the change's *intent* rather than refining it, recommend starting fresh with `/opsx:new` (the "Update vs. Start Fresh" heuristic).
+11
View File
@@ -0,0 +1,11 @@
# Reference libraries — studied, not built here (each has its own upstream repo)
reference/
# .NET build output
[Bb]in/
[Oo]bj/
.vs/
*.user
# Scratch
*.tmp
+153
View File
@@ -0,0 +1,153 @@
---
description: Implement tasks from an OpenSpec change (Experimental)
---
Implement tasks from an OpenSpec change.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name (e.g., `/opsx-apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Provided arguments**: $@
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx-continue`
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! You can archive this change with `/opsx-archive`.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
+158
View File
@@ -0,0 +1,158 @@
---
description: Archive a completed change in the experimental workflow
---
Archive a completed change in the experimental workflow.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx-archive` (e.g., `/opsx-archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Provided arguments**: $@
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Prompt user for confirmation to continue
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Prompt user for confirmation to continue
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Spec sync status (synced / sync skipped / no delta specs)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs
All artifacts complete. All tasks complete.
```
**Output On Success (No Delta Specs)**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** No delta specs
All artifacts complete. All tasks complete.
```
**Output On Success With Warnings**
```
## Archive Complete (with warnings)
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** Sync skipped (user chose to skip)
**Warnings:**
- Archived with 2 incomplete artifacts
- Archived with 3 incomplete tasks
- Delta spec sync was skipped (user chose to skip)
Review the archive if this was not intentional.
```
**Output On Error (Archive Exists)**
```
## Archive Failed
**Change:** <change-name>
**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
Target archive directory already exists.
**Options:**
1. Rename the existing archive
2. Delete the existing archive if it's a duplicate
3. Wait until a different date to archive
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+172
View File
@@ -0,0 +1,172 @@
---
description: "Enter explore mode - think through ideas, investigate problems, clarify requirements"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be:
**Provided arguments**: $@
- A vague idea: "real-time collaboration"
- A specific problem: "the auth system is getting unwieldy"
- A change name: "add-dark-mode" (to explore in context of that change)
- A comparison: "postgres vs sqlite for this"
- Nothing (just enter explore mode)
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
If the user mentioned a specific change name, read its artifacts for context.
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+107
View File
@@ -0,0 +1,107 @@
---
description: Propose a new change - create it and generate all artifacts in one step
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx-apply
---
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The argument after `/opsx-propose` is the change name (kebab-case), OR a description of what the user wants to build.
**Provided arguments**: $@
**Steps**
1. **If no input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx-apply` to start implementing."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
+141
View File
@@ -0,0 +1,141 @@
---
description: Sync delta specs from a change to main specs
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx-sync` (e.g., `/opsx-sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Provided arguments**: $@
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result
+79
View File
@@ -0,0 +1,79 @@
---
description: Update a change - revise existing planning artifacts and keep them coherent (Experimental)
---
Revise a change's existing planning artifacts and keep them coherent. Never edit code.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name after `/opsx-update` (e.g., `/opsx-update add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Provided arguments**: $@
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to update.
Present the top 3-4 most recently modified changes as options, showing:
- Change name
- Schema (from `schema` field if present, otherwise "spec-driven")
- Status (e.g., "0/5 tasks", "complete", "no tasks")
- How recently it was modified (from `lastModified` field)
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to update.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Get the change's artifacts**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand current state. The response includes:
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
- `isComplete`: Boolean indicating if all artifacts are complete
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
The artifact ids and paths come from the active schema - do NOT assume them, and do NOT branch on hardcoded artifact names. Custom schemas must work unchanged.
The files to edit are `artifactPaths.<id>.existingOutputPaths` - the concrete files that exist on disk, already glob-expanded for glob artifacts (e.g. `specs/**/*.md`). Do NOT write to `resolvedOutputPath`: for a glob artifact it is still the glob pattern, not a real file.
3. **Understand the request**
- If the user asked for a specific revision ("the design now uses X"), that is the starting edit.
- If they only said "update" / "make this coherent", treat it as a coherence review: read the existing artifacts and check them against each other for contradictions, gaps, and duplication.
4. **Read and reconcile**
- Read the artifact(s) the request touches and the change's other existing artifacts.
- Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised.
- Note everything that is now inconsistent, missing, or contradictory.
- Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `/opsx-continue` to create them.
- If the change is already coherent, say so and make no edits.
5. **Confirm and apply, one artifact at a time**
- Show each proposed revision and why. Write only after the user confirms.
- If the user rejects a revision, do not write it - leave that artifact unchanged.
- When a substantial rewrite is needed, get that artifact's rules and template first:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
6. **Point to the next step (guidance only - NEVER act on it)**
- Artifacts still missing -> suggest `/opsx-continue` to create them.
- Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `/opsx-apply` to carry the delta into code.
- Everything done and implemented -> suggest `/opsx-archive`.
**Output**
After each invocation, show:
- Which artifacts were revised (and which proposed revisions were rejected)
- Anything deferred to `/opsx-continue` (not-yet-created artifacts or files)
- Where the change stands and the recommended next command
**Guardrails**
- Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `/opsx-apply`.
- Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names.
- Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`.
- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `/opsx-continue`'s job.
- Confirm every edit with the user before writing.
- If the request changes the change's *intent* rather than refining it, recommend starting fresh with `/opsx-new` (the "Update vs. Start Fresh" heuristic).
+160
View File
@@ -0,0 +1,160 @@
---
name: openspec-apply-change
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Implement tasks from an OpenSpec change.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! Ready to archive this change.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
@@ -0,0 +1,118 @@
---
name: openspec-archive-change
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Archive a completed change in the experimental workflow.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Whether specs were synced (if applicable)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
All artifacts complete. All tasks complete.
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use openspec-sync-specs approach (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+290
View File
@@ -0,0 +1,290 @@
---
name: openspec-explore
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Handling Different Entry Points
**User brings a vague idea:**
```
User: I'm thinking about adding real-time collaboration
You: Real-time collab is a big space. Let me think about this...
COLLABORATION SPECTRUM
════════════════════════════════════════════
Awareness Coordination Sync
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Presence│ │Cursors │ │ CRDT │
│ "3 │ │ Multi │ │Conflict│
│online" │ │ select │ │ free │
└────────┘ └────────┘ └────────┘
│ │ │
trivial moderate complex
Where's your head at?
```
**User brings a specific problem:**
```
User: The auth system is a mess
You: [reads codebase]
┌─────────────────────────────────────────────┐
│ CURRENT AUTH FLOW │
└─────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Google │ │ GitHub │ │ Email │
│ OAuth │ │ OAuth │ │ Magic │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
┌───────────┐
│ Session │
└─────┬─────┘
┌───────────┐
│ Perms │
└───────────┘
I see three tangles. Which one's burning?
```
**User is stuck mid-implementation:**
```
User: /opsx-explore add-auth-system
The OAuth integration is more complex than expected
You: [reads change artifacts]
You're on task 4: "Implement OAuth flow"
Let me trace what's involved...
[draws diagram, explores options, suggests paths]
Want to update the design to reflect this?
Or add a spike task to investigate?
```
**User wants to compare options:**
```
User: Should we use Postgres or SQLite?
You: Generic answer is boring. What's the context?
User: A CLI tool that tracks local dev environments
You: That changes everything.
┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘
Key constraints:
• No daemon running
• Must work offline
• Single user
SQLite Postgres
Deployment embedded ✓ needs server ✗
Offline yes ✓ no ✗
Single file yes ✓ no ✗
SQLite. Not even close.
Unless... is there a sync component?
```
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Create a change proposal
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+114
View File
@@ -0,0 +1,114 @@
---
name: openspec-propose
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx-apply
---
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx-apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
+148
View File
@@ -0,0 +1,148 @@
---
name: openspec-sync-specs
description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result
@@ -0,0 +1,86 @@
---
name: openspec-update-change
description: Update an OpenSpec change by revising its existing planning artifacts and keeping them coherent with one another. Use when the user wants to revise a change's plan, fold new decisions into it, or reconcile its artifacts after an edit. Never edits code.
allowed-tools: Bash(openspec:*)
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.6.0"
---
Revise a change's existing planning artifacts and keep them coherent. Never edit code.
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to update.
Present the top 3-4 most recently modified changes as options, showing:
- Change name
- Schema (from `schema` field if present, otherwise "spec-driven")
- Status (e.g., "0/5 tasks", "complete", "no tasks")
- How recently it was modified (from `lastModified` field)
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to update.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Get the change's artifacts**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand current state. The response includes:
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
- `isComplete`: Boolean indicating if all artifacts are complete
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
The artifact ids and paths come from the active schema - do NOT assume them, and do NOT branch on hardcoded artifact names. Custom schemas must work unchanged.
The files to edit are `artifactPaths.<id>.existingOutputPaths` - the concrete files that exist on disk, already glob-expanded for glob artifacts (e.g. `specs/**/*.md`). Do NOT write to `resolvedOutputPath`: for a glob artifact it is still the glob pattern, not a real file.
3. **Understand the request**
- If the user asked for a specific revision ("the design now uses X"), that is the starting edit.
- If they only said "update" / "make this coherent", treat it as a coherence review: read the existing artifacts and check them against each other for contradictions, gaps, and duplication.
4. **Read and reconcile**
- Read the artifact(s) the request touches and the change's other existing artifacts.
- Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised.
- Note everything that is now inconsistent, missing, or contradictory.
- Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `/opsx-continue` to create them.
- If the change is already coherent, say so and make no edits.
5. **Confirm and apply, one artifact at a time**
- Show each proposed revision and why. Write only after the user confirms.
- If the user rejects a revision, do not write it - leave that artifact unchanged.
- When a substantial rewrite is needed, get that artifact's rules and template first:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
6. **Point to the next step (guidance only - NEVER act on it)**
- Artifacts still missing -> suggest `/opsx-continue` to create them.
- Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `/opsx-apply` to carry the delta into code.
- Everything done and implemented -> suggest `/opsx-archive`.
**Output**
After each invocation, show:
- Which artifacts were revised (and which proposed revisions were rejected)
- Anything deferred to `/opsx-continue` (not-yet-created artifacts or files)
- Where the change stands and the recommended next command
**Guardrails**
- Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `/opsx-apply`.
- Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names.
- Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`.
- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `/opsx-continue`'s job.
- Confirm every edit with the user before writing.
- If the request changes the change's *intent* rather than refining it, recommend starting fresh with `/opsx-new` (the "Update vs. Start Fresh" heuristic).
+93
View File
@@ -0,0 +1,93 @@
# Memory-Manipulation Library Comparison — BlackMagic-old, MemorySharp, GreyMagic, BlackMagic
**Date**: 2026-07-21
**Purpose**: Compare four C# process-manipulation libraries present in this repo and derive the design of a modern successor ("WhiteMagic"). The OpenSpec change `whitemagic-foundation` formalizes the design; this document is the supporting study.
## The four subjects
| Library | Location | Era / Platform | Role in this study |
|---|---|---|---|
| **BlackMagic-old** | `reference/Blackmagic-old/` | .NET FW 4.0, x86 | The FASM "before" — initial commit, FASM as public API |
| **MemorySharp** | `reference/MemorySharp/` (≡ `lib/MemorySharp/`, byte-identical) | .NET FW, x86 | Contemporaneous peer that kept FASM behind a polished API |
| **GreyMagic** | `reference/GreyMagic/` | .NET FW, ~2016, x86 | Introduces the in-process execution model + detour/patch/marshal-cache |
| **BlackMagic** (current) | `reference/Blackmagic/` | **.NET 8, x86 + x64** | The FASM "after" — modernized, FASM removed |
> `reference/MemorySharp/` and `lib/MemorySharp/` are identical copies (`diff -rq` clean).
## Where FASM lives (the through-line)
FASM (the Flat Assembler, via the `ManagedFasm` C++/CLI wrapper in `reference/fasm/`) exists in these libraries for exactly one job: **turning assembly text into machine code at runtime**, so a small call-stub / injection-stub can be synthesized when the target address, arguments, and calling convention are only known at call time.
- **BlackMagic-old** — FASM is a *public, load-bearing* dependency: `public ManagedFasm Asm { get; set; }` sits directly on the facade (`BMMain.cs:80`), and `SInject.cs` builds its DLL-redirect injection stub as mnemonic text at runtime.
- **MemorySharp** — same dependency, *wrapped*: the assembler is internal (`Fasm32Assembler`, created unconditionally by `AssemblyFactory`), driven by calling-convention formatters behind `Execute<T>`.
- **GreyMagic** — FASM only on the *external* path (`ExternalProcessReader.Asm`); the in-process path needs no assembler because it calls functions as delegates directly.
- **BlackMagic (current)** — FASM **removed entirely**. Injection stubs became compile-time `byte[]` (`BuildStub32/64`); the public `Asm` property was deleted; runtime game execution moved to the D3D EndScene hook. See `FASM-MIGRATION.md`.
**Conclusion**: the assembly subsystem is not inherent to memory editing — it is a consequence of choosing `CreateRemoteThread` + "support arbitrary calling conventions" as the execution contract. Change the execution primitive (as current BM did) and the need for a runtime assembler evaporates. A managed assembler ([Iced](https://github.com/icedland/iced)) or hand-emitted stubs cover the residual need with no native dependency and full x64 support.
## Feature matrix
| Axis | BM-old | MemorySharp | GreyMagic | BM (current) |
|---|---|---|---|---|
| Platform | FW 4.0, x86 | FW, x86 | FW, x86 | **.NET 8, x86 + x64** |
| Handles | raw `IntPtr` | `SafeMemoryHandle` | `SafeMemoryHandle` | `SafeMemoryHandle`, nullable |
| Addressing | `uint` | `IntPtr` | `IntPtr` | `IntPtr` (64-bit-safe) |
| Process model | external | external | **external + in-process** | external (+ D3D hook) |
| Typed read/write | `ReadInt` etc. | `Read<T>` + marshal | `Read<T>` + **MarshalCache** | `Read<T> where unmanaged` |
| Pattern scanning | ✅ | ❌ ("coming soon") | ❌ (has PE parser) | ✅ + cache |
| FASM / assembler | **public `Asm`** | internal, behind `Execute<T>` | `Asm` (external only) | **none** |
| Remote fn call | raw `Asm` stubs | **`Execute<T>(conv, args…)`** + async | **in-proc delegates** | D3D-hook shellcode |
| Function hooking | — | — | **Detour mgr** (reversible, `CallOriginal`) | D3D EndScene only |
| Byte patching | — | ❌ ("coming soon") | **Patch mgr** (named, reversible) | ad hoc |
| DLL injection | CreateThread + hijack | LoadLibrary via CreateThread | (via `Asm`) | CreateThread + hijack, x86/x64 |
| Named allocation | — | `RemoteAllocation` | **`AllocatedMemory`** (by name) | `AllocateMemory` |
| PE parsing | — | — | **`PeHeaderParser`** | — |
| PEB / TEB | — | ✅ `ManagedPeb`/`ManagedTeb` | — | — |
| Window mutation | — | ✅ (move/resize/title/flash) | — | — |
| Input simulation | — | ✅ (keyboard/mouse, no focus) | — | — |
| High-level ergonomics | simple facade | `sharp[addr]`, `module["fn"]`, Enum reads | `CreateFunction<T>`, vtable helpers | facade |
| Helpers | — | ApplicationFinder, HandleManipulator, Randomizer, Serialization, Singleton | MarshalCache, Utilities | — |
## What each does best (the "take from each" summary)
- **BlackMagic-old** → the clean minimal `Open / Read / Write / FindPattern` facade; it is also the historical proof that FASM was once load-bearing and can be retired.
- **MemorySharp** → high-level ergonomics: calling-convention `Execute<T>` + parameter marshalling + async, `RemotePointer` indexer, PEB/TEB, window + keyboard/mouse simulation, helper utilities.
- **GreyMagic** → the engine: dual in/out-of-process `MemoryBase`, `MarshalCache<T>` fast typed IO, reversible `DetourManager` + `PatchManager`, `CreateFunction<T>`/vtable helpers, named `AllocatedMemory`, `PeHeaderParser`.
- **BlackMagic (current)** → the modern platform: .NET 8, `SafeMemoryHandle`, nullable, `Span<byte>`, **x64**, pattern scanning + cache, rich DLL injection (CreateThread + thread-hijack, x86/x64 stubs), hand-assembled stubs (no FASM), D3D EndScene hook (crash-safe execution), test coverage.
## The crash-safety principle (why `CreateRemoteThread` needs care)
`CreateRemoteThread` does not crash WoW — **calling game internals from the wrong thread does**. The game's main thread has exclusive affinity for the Lua VM, the D3D9 device, and the object manager. A thread you spawn runs concurrently with it; the moment a payload touches that state (`CastSpellByName`, `FrameScript::Execute`, object traversal) it races the main thread → memory corruption → crash. This matches the "3 crashes in one session" recorded in `FASM-MIGRATION.md`.
The rule the successor must encode — **split execution by payload safety**:
1. **`CreateRemoteThread` is safe** for *self-contained, thread-agnostic* payloads: `LoadLibrary` (DLL injection), pure WinAPI, code touching only memory you own.
2. **Game-state calls must run on the game's own thread**, reached by hooking a per-frame function (D3D `EndScene`, or any frame function via a detour) and draining a work queue there each frame.
3. **In-process** (once a managed DLL is injected), call game functions directly as delegates — no thread crossing at all.
## WhiteMagic — synthesis
A modern successor unifying the four. Full design in `openspec/changes/whitemagic-foundation/`.
```
WhiteMagic (facade — BM-old ergonomics)
├─ Core: SafeHandle, native P/Invoke, x64 [BM current]
├─ MemoryBase (abstract Read/Write + MarshalCache) [GreyMagic]
│ ├─ ExternalReader (RPM/WPM)
│ └─ InProcessReader (direct deref, injected)
├─ Discovery: PatternScanner(+cache), PeHeaderParser [BM current + GreyMagic]
├─ Allocation: AllocatedMemory (named chunks) [GreyMagic]
├─ Assembler: IAssembler → { HandStubs | Iced } [BM current; Iced replaces FASM]
├─ Execution (three tiers):
│ ├─ RemoteThreadExecutor (CreateRemoteThread — safe payloads) [MemorySharp Execute<T>, no FASM]
│ ├─ MainThreadPump (frame-hook work queue) [BM D3D hook + GreyMagic detour] ← crash-safe
│ └─ InProcessInvoker (CreateFunction<T> delegates) [GreyMagic]
├─ Hooking: DetourManager + PatchManager [GreyMagic]
├─ Injection: CreateThread + ThreadHijack (x86/x64) [BM current]
├─ HighLevel: RemotePointer, Module["fn"], PEB/TEB,
│ input sim, window, async, helpers [MemorySharp]
└─ Safety: disassemble-before-splice (Iced), auto-restore
all patches/detours on Dispose [new]
```
**Net result**: BM's modern, FASM-free, x64 core + GreyMagic's dual-mode / detour / patch / marshal-cache engine + MemorySharp's high-level ergonomics — with a three-tier execution model whose *default* for game calls is the crash-safe main-thread pump, while `CreateRemoteThread` stays available for the payloads it is genuinely safe for.
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-21
@@ -0,0 +1,74 @@
## Context
BlackMagic is a pure C# process manipulation library. It replaced FASM (native x86 assembler DLL) with hand-assembled `byte[]` stubs. Two capabilities were lost:
- `InjectAndExecuteEx`: non-blocking remote thread that returns a handle without waiting.
- Text-based assembly: build shellcode from `pushad`, `mov eax, 0x1234`, etc. instead of raw bytes.
Existing code:
- `BMThread.cs` has blocking `Execute(addr, param)` → waits 10s, returns exit code.
- `BMThread.cs` has `CreateRemoteThread(addr, param)` → returns `SafeMemoryHandle?`.
- `SInject.cs` has `InjectCode(addr, bytes)` and `InjectCode(bytes)` → allocate + write.
- `BlackMagicTest/` uses xUnit, `net8.0-windows`, pure-logic tests (no process needed).
## Goals / Non-Goals
**Goals:**
- Add `InjectAndExecuteEx()`: inject code + create remote thread, return handle, do NOT wait.
- Add `AsmBuilder`: pure C# x86 text assembler. Convert `"pushad\nmov eax, 1\npopad"``byte[]`.
- Add `SetPassLimit(int)` on `AsmBuilder` for label resolution iteration control.
- Add convenience overloads: `InjectAndExecute(string asm)`, `InjectAndExecuteEx(string asm)`.
- TDD: write tests first for every public API surface.
**Non-Goals:**
- Full x86 instruction set (only shellcode-common subset: mov, push, pop, call, jmp, ret, nop, pushad/popad, test, je/jne, inc, add, sub, xor, and, or, cmp, lea, nop, hlt).
- x64 text assembly (keep x86 only; x64 stubs remain hand-assembled `byte[]`).
- Reimplementing FASM's directive system (`org`, `use32`, macros).
- Native DLL dependency.
## Decisions
### D1: AsmBuilder lives in `BlackMagic/Asm/AsmBuilder.cs`
New directory `Asm/` keeps assembler code isolated. Static class, no instance state needed — each `Assemble()` call is self-contained.
**Alternatives considered:**
- Instance class with `AddLine()` builder pattern → rejected: adds statefulness for no benefit. Each shellcode is a fresh call.
- Put in `Injection/` → rejected: assembler is generic, not injection-specific.
### D2: Two-pass assembler (labels + bytes)
Pass 1: scan instructions, record label positions, emit bytes (reserving 4 bytes for near jumps). Pass 2: resolve label offsets, patch jump targets.
This handles forward references (`jmp @skip` before `@skip:` label is defined) without multiple iterations. `SetPassLimit()` caps the loop for safety but 2 passes is sufficient for all shellcode patterns.
**Alternatives considered:**
- Single-pass → rejected: can't resolve forward jumps.
- FASM-style multi-pass → overkill: shellcode doesn't need complex expression evaluation.
### D3: Instruction encoding via switch + helper methods
Each instruction maps to hand-coded byte emission. Helper methods: `EmitU8()`, `EmitU32()`, `EmitModRM()`, `EmitSIB()`. Same pattern as `SInject.cs`'s existing `BuildStub()` methods.
**Alternatives considered:**
- Lookup table / dictionary → rejected: instruction encoding is irregular (ModRM, SIB, displacement, immediate). Switch statements are clearer.
### D4: InjectAndExecuteEx returns `SafeMemoryHandle`
Matches existing `CreateRemoteThread()` return type. Caller is responsible for disposing the handle. No auto-wait, no auto-close.
### D5: TDD approach
Write xUnit tests in `BlackMagicTest/AsmBuilderTests.cs` first:
- Each instruction: known input text → expected `byte[]` output.
- Label resolution: forward jump, backward jump, multiple labels.
- Error cases: unknown instruction, missing operand, invalid register.
- `SetPassLimit`: verify iteration cap is respected.
Then implement `AsmBuilder` to make tests pass.
## Risks / Trade-offs
- **Instruction subset**: users may need an instruction not in the initial set. Mitigation: document supported instructions, add new ones incrementally.
- **Label complexity**: relative jumps are limited to ±127 bytes (near) or ±2GB (far). Shellcode rarely exceeds this, but document the limit.
- **No runtime validation of shellcode**: the assembler produces bytes; it doesn't verify the result is safe to execute. This matches FASM's behavior — the assembler doesn't validate semantics.
@@ -0,0 +1,23 @@
## Why
BlackMagic replaced FASM for shellcode generation but lost two useful capabilities:
1. **Non-blocking remote execution** (`InjectAndExecuteEx`): FASM's managed wrapper returned a thread handle without waiting. BlackMagic only has blocking `Execute()`. For DLL injection, a non-blocking variant avoids hanging when the target is slow to load.
2. **Text-based assembly**: FASM allowed building shellcode from assembly text (`AddLine("pushad")`). BlackMagic requires hand-assembled `byte[]`. For prototyping, debugging, and one-off shellcode, text assembly is faster to write and easier to review. A managed assembler eliminates the native FASM DLL dependency while keeping the ergonomic benefit.
## What Changes
- Add `InjectAndExecuteEx()` to `BlackMagic` and `BMThread`: inject code then create a remote thread without waiting, returning the thread handle.
- Add `AsmBuilder` class: pure C# x86 text assembler that converts instruction text to `byte[]` machine code. Supports common shellcode instructions (mov, push, pop, call, jmp, ret, nop, pushad/popad, test, je, jne, inc, add, sub, xor, etc.).
- Add `InjectAndExecute(string asm)` and `InjectAndExecuteEx(string asm)` overloads that accept assembly text, assemble via `AsmBuilder`, then inject+execute.
- Add `SetPassLimit()` to `AsmBuilder` for label resolution iteration control.
## Capabilities
### New Capabilities
- `non-blocking-execute`: Non-blocking remote thread creation that returns a handle without waiting for exit.
- `text-assembler`: Pure C# x86 text assembler converting assembly source to byte arrays without native dependencies.
### Modified Capabilities
<!-- None — additive features only. -->
@@ -0,0 +1,41 @@
## ADDED Requirements
### Requirement: InjectAndExecuteEx creates remote thread without waiting
`BlackMagic.InjectAndExecuteEx(IntPtr startAddress, IntPtr parameter)` injects code at `startAddress` into the opened process, creates a remote thread with `parameter`, and returns the thread handle immediately without waiting for the thread to exit.
#### Scenario: successful non-blocking execution
- **WHEN** a process is open and `InjectAndExecuteEx(addr, param)` is called with a valid code address
- **THEN** a remote thread is created in the target process and a valid `SafeMemoryHandle` is returned
#### Scenario: no process open
- **WHEN** no process is open and `InjectAndExecuteEx(addr, param)` is called
- **THEN** `null` is returned
### Requirement: InjectAndExecuteEx single-parameter overload
`BlackMagic.InjectAndExecuteEx(IntPtr startAddress)` calls `InjectAndExecuteEx(startAddress, IntPtr.Zero)`.
#### Scenario: parameter-less non-blocking execution
- **WHEN** `InjectAndExecuteEx(addr)` is called with a valid address
- **THEN** the thread is created with parameter `IntPtr.Zero`
### Requirement: InjectAndExecuteEx from assembly text
`BlackMagic.InjectAndExecuteEx(string asm)` assembles the text via `AsmBuilder`, allocates remote memory, writes the bytes, calls `InjectAndExecuteEx` on the allocated address, and returns the thread handle.
#### Scenario: execute assembly text non-blocking
- **WHEN** `InjectAndExecuteEx("nop")` is called with a process open
- **THEN** the text is assembled to bytes, written to remote memory, a thread is started, and the handle is returned
#### Scenario: assembly failure
- **WHEN** `InjectAndExecuteEx("invalidinstruction")` is called
- **THEN** `ArgumentException` is thrown with the assembly error
### Requirement: InjectAndExecute from assembly text (blocking convenience)
`BlackMagic.InjectAndExecute(string asm)` assembles the text, allocates remote memory, writes the bytes, calls `Execute` (blocking, 10s timeout), and returns the exit code.
#### Scenario: execute assembly text blocking
- **WHEN** `InjectAndExecute("mov eax, 42\nret")` is called with a process open
- **THEN** the text is assembled, injected, executed, and the thread exit code is returned
@@ -0,0 +1,84 @@
## ADDED Requirements
### Requirement: AsmBuilder assembles x86 instruction text to byte array
`AsmBuilder.Assemble(string source)` parses x86 assembly text and returns the corresponding `byte[]` machine code.
#### Scenario: single instruction
- **WHEN** `AsmBuilder.Assemble("nop")` is called
- **THEN** the result is `[0x90]`
#### Scenario: multiple instructions
- **WHEN** `AsmBuilder.Assemble("pushad\npopad")` is called
- **THEN** the result is `[0x60, 0x61]`
#### Scenario: instruction with immediate operand
- **WHEN** `AsmBuilder.Assemble("mov eax, 1")` is called
- **THEN** the result is `[0xB8, 0x01, 0x00, 0x00, 0x00]`
### Requirement: AsmBuilder supports register operands
Supported registers: `eax`, `ecx`, `edx`, `ebx`, `esp`, `ebp`, `esi`, `edi` (and 8-bit: `al`, `cl`, `dl`, `bl`, `ah`, `ch`, `dh`, `bh`).
#### Scenario: register-to-register move
- **WHEN** `AsmBuilder.Assemble("mov eax, ecx")` is called
- **THEN** the result is `[0x89, 0xC8]` (mov eax, ecx encoding)
#### Scenario: register encoding
- **WHEN** registers are used in instructions
- **THEN** each register maps to its correct 3-bit encoding (eax=0, ecx=1, edx=2, ebx=3, esp=4, ebp=5, esi=6, edi=7)
### Requirement: AsmBuilder supports labels and jumps
Labels are defined with `@name:` and referenced with `jmp @name` or `je @name`. Forward and backward references are resolved in a second pass.
#### Scenario: forward jump
- **WHEN** `AsmBuilder.Assemble("jmp @skip\nnop\n@skip:\nret")` is called
- **THEN** the jump skips exactly over the `nop` (2 bytes) and lands on `ret`
#### Scenario: backward jump
- **WHEN** `AsmBuilder.Assemble("@loop:\nnop\njmp @loop")` is called
- **THEN** the jump targets the earlier label correctly
#### Scenario: multiple labels
- **WHEN** multiple labels are used in one source
- **THEN** each label resolves to its correct byte offset
### Requirement: AsmBuilder SetPassLimit controls iteration
`AsmBuilder.SetPassLimit(int limit)` sets the maximum number of assembly passes for label resolution. Default is 10. If the limit is exceeded before all labels resolve, `InvalidOperationException` is thrown.
#### Scenario: default pass limit
- **WHEN** no `SetPassLimit` is called
- **THEN** the assembler uses 10 passes maximum
#### Scenario: custom pass limit
- **WHEN** `SetPassLimit(20)` is called
- **THEN** the assembler uses 20 passes maximum
#### Scenario: pass limit exceeded
- **WHEN** forward references cannot resolve within the pass limit
- **THEN** `InvalidOperationException` is thrown with label resolution details
### Requirement: AsmBuilder reports clear errors
Unknown instructions, missing operands, and invalid register names produce `ArgumentException` with the line number and offending text.
#### Scenario: unknown instruction
- **WHEN** `AsmBuilder.Assemble("xyzw")` is called
- **THEN** `ArgumentException` is thrown mentioning line 1 and "xyzw"
#### Scenario: missing operand
- **WHEN** `AsmBuilder.Assemble("mov")` is called (no operands)
- **THEN** `ArgumentException` is thrown mentioning missing operand
### Requirement: AsmBuilder supported instruction set
The following x86 instructions are supported:
- **Data movement**: `mov`, `push`, `pop`, `pushad`, `popad`, `lea`
- **Arithmetic**: `add`, `sub`, `inc`, `dec`, `xor`, `and`, `or`, `cmp`, `test`
- **Control flow**: `jmp`, `je`, `jne`, `call`, `ret`, `nop`, `hlt`
#### Scenario: all instructions produce valid bytes
- **WHEN** each supported instruction is assembled individually
- **THEN** it produces the correct x86 machine code encoding
@@ -0,0 +1,44 @@
## 1. AsmBuilder — Core (TDD: tests first)
- [ ] 1.1 Create `BlackMagicTest/AsmBuilderTests.cs` with tests for single-instruction assembly: `nop``[0x90]`, `pushad``[0x60]`, `popad``[0x61]`, `ret``[0xC3]`, `hlt``[0xF4]`
- [ ] 1.2 Create `BlackMagic/Asm/AsmBuilder.cs` with `Assemble(string source)` entry point and instruction table. Implement `nop`, `pushad`, `popad`, `ret`, `hlt` to make step 1.1 tests pass
- [ ] 1.3 Add tests for `mov reg, imm32` (eax, ecx, edx, ebx, esp, ebp, esi, edi) — each register maps to `0xB8 + reg` encoding with 4-byte little-endian immediate
- [ ] 1.4 Implement `mov reg, imm32` in AsmBuilder to make step 1.3 tests pass
- [ ] 1.5 Add tests for `mov reg, reg` (register-to-register via ModRM byte 0xC0 + (src<<3 | dst))
- [ ] 1.6 Implement `mov reg, reg` to make step 1.5 tests pass
- [ ] 1.7 Add tests for `push reg` (`0x50 + reg`), `pop reg` (`0x58 + reg`)
- [ ] 1.8 Implement `push reg`, `pop reg` to make step 1.7 tests pass
- [ ] 1.9 Add tests for `add reg, imm8` (`0x83 /0` with sign-extended byte), `sub reg, imm8` (`0x83 /5`), `xor reg, reg` (`0x31 /r`), `and reg, imm8`, `or reg, imm8`, `cmp reg, imm8` (`0x83 /7`)
- [ ] 1.10 Implement `add`, `sub`, `xor`, `and`, `or`, `cmp` to make step 1.9 tests pass
- [ ] 1.11 Add tests for `inc reg` (`0x40 + reg`), `dec reg` (`0x48 + reg`), `test reg, reg` (`0x85 /r`)
- [ ] 1.12 Implement `inc`, `dec`, `test` to make step 1.11 tests pass
- [ ] 1.13 Add tests for `jmp @label`, `je @label`, `jne @label` with forward and backward references
- [ ] 1.14 Implement two-pass label resolution in AsmBuilder (pass 1: record label offsets, pass 2: patch jump targets). Make step 1.13 tests pass
- [ ] 1.15 Add tests for `call @label` (near call, E8 rel32) and `nop` multi-instruction sequences
- [ ] 1.16 Implement `call` to make step 1.15 tests pass
- [ ] 1.17 Add tests for error cases: unknown instruction → `ArgumentException` with line number, missing operand → `ArgumentException`, invalid register → `ArgumentException`
- [ ] 1.18 Implement error reporting to make step 1.17 tests pass
## 2. AsmBuilder — Pass Limit
- [ ] 2.1 Add tests for `SetPassLimit()`: default is 10, custom value is respected, exceeded limit throws `InvalidOperationException`
- [ ] 2.2 Implement `SetPassLimit(int limit)` and pass-limit enforcement in AsmBuilder to make step 2.1 tests pass
## 3. InjectAndExecuteEx — Non-blocking Execution
- [ ] 3.1 Add tests for `BlackMagic.InjectAndExecuteEx(IntPtr, IntPtr)`: returns `null` when no process open, returns valid handle when process open (mock or integration)
- [ ] 3.2 Implement `InjectAndExecuteEx(IntPtr startAddress, IntPtr parameter)` in `BMThread.cs` to make step 3.1 tests pass
- [ ] 3.3 Add test for single-parameter overload: `InjectAndExecuteEx(IntPtr)` passes `IntPtr.Zero`
- [ ] 3.4 Implement `InjectAndExecuteEx(IntPtr startAddress)` overload in `BMThread.cs`
## 4. Convenience Overloads (text → inject → execute)
- [ ] 4.1 Add tests for `InjectAndExecute(string asm)`: assembles text, allocates remote memory, writes bytes, executes, returns exit code. Test assembly failure → `ArgumentException`
- [ ] 4.2 Implement `InjectAndExecute(string asm)` in `BMInject.cs` to make step 4.1 tests pass
- [ ] 4.3 Add tests for `InjectAndExecuteEx(string asm)`: assembles text, allocates remote memory, writes bytes, returns thread handle. Test assembly failure → `ArgumentException`
- [ ] 4.4 Implement `InjectAndExecuteEx(string asm)` in `BMInject.cs` to make step 4.3 tests pass
## 5. Build Verification
- [ ] 5.1 Run full test suite: `"C:\Program Files\dotnet\dotnet.exe" test BlackMagicTest/BlackMagicTest.csproj` — all tests pass
- [ ] 5.2 Run full solution build: `"C:\Program Files\dotnet\dotnet.exe" build BlackMagic.slnx` — zero errors
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-21
@@ -0,0 +1,126 @@
## Context
This repo contains four C# process-manipulation libraries studied in `docs/memory-library-comparison.md`:
- **BlackMagic-old** (`reference/Blackmagic-old/`) — .NET FW 4.0, x86, FASM as a public `Asm` property.
- **MemorySharp** (`reference/MemorySharp/`) — .NET FW, x86, FASM behind `Execute<T>`; rich high-level API (PEB/TEB, window, input, calling conventions) but unmaintained since 2016.
- **GreyMagic** (`reference/GreyMagic/`) — .NET FW, ~2016, x86; dual in/out-of-process `MemoryBase`, `MarshalCache`, `DetourManager`, `PatchManager`, `CreateFunction<T>`, `PeHeaderParser`.
- **BlackMagic** (`reference/Blackmagic/`, current) — .NET 8, x86 + x64, FASM removed, pattern scanning + cache, DLL injection (CreateThread + hijack), hand-assembled x86 stubs (`SInject.cs` `EmitU8`/`EmitU32` byte emitter), `CreateRemoteThread`-based `Execute` (`BMThread.cs`). No frame hook exists.
The consuming use case is a WoW 3.3.5a bot. The dominant failure mode observed (`FASM-MIGRATION.md`) is game crashes when protected/game-state functions are invoked on a thread created by `CreateRemoteThread`, because WoW's main thread has exclusive affinity for the Lua VM, D3D9 device, and object manager.
WhiteMagic is a **new, additive** .NET 8 library that unifies the four. It reuses their ideas, not their assemblies. No project already depends on WhiteMagic, so there is no backward-compatibility constraint.
## Goals / Non-Goals
**Goals:**
- Single modern (.NET 8, nullable, `Span<byte>`, `SafeHandle`) library that is bitness-agnostic (x86 + x64).
- A **three-tier execution model** whose default path for game-state calls is crash-safe (runs on the target's own thread), while `CreateRemoteThread` remains available for thread-agnostic payloads.
- Dual memory access: out-of-process (RPM/WPM) and in-process (direct deref) behind one abstract `MemoryBase`, with `MarshalCache<T>` for allocation-free typed IO.
- Reversible function hooking (`DetourManager`) and byte patching (`PatchManager`) with auto-restore on dispose.
- Replace FASM with an `IAssembler` seam: hand-emitted convention stubs by default, optional Iced backend for arbitrary assembly. Zero native dependency in the default configuration.
- Port DLL injection (CreateThread + thread-hijack, x86/x64) and pattern scanning + cache from current BlackMagic.
- Provide MemorySharp-grade ergonomics: `RemotePointer` indexer, `Module["fn"].Execute(...)`, PEB/TEB, window mutation, input simulation, async wrappers, helpers.
- Test-first for all pure logic (assembler encodings, marshal cache, pattern matching, stub building, pump queue semantics).
**Non-Goals:**
- Modifying or replacing BlackMagic/MemorySharp/GreyMagic — WhiteMagic is additive.
- Shipping a full x86/x64 assembler ourselves — arbitrary assembly is delegated to Iced; only the fixed convention-stub shapes are hand-emitted.
- Managed-DLL injection bootstrapper (the CLR host that loads `InProcessReader` into the target). WhiteMagic exposes the in-process API surface; wiring an actual managed loader is a follow-up change.
- WoW-specific offsets, Lua unlock, or bot logic — those live in the consumer, not the library.
- Anti-cheat evasion.
## Decisions
### D1: Layered architecture with an abstract `MemoryBase` (from GreyMagic)
`MemoryBase` defines abstract `ReadBytes`/`WriteBytes`/`Read<T>`/`Write<T>`, relative/absolute addressing, and hosts the `PatchManager`. Two concrete readers:
- `ExternalReader : MemoryBase``ReadProcessMemory`/`WriteProcessMemory` over a `SafeMemoryHandle`. Owns allocation, injection, and the remote-thread + main-thread executors.
- `InProcessReader : MemoryBase``unsafe` direct pointer deref; owns the `DetourManager` and `InProcessInvoker`.
**Why**: GreyMagic proved this abstraction lets the same higher-level code (pattern scan, patch, high-level API) run in either mode. External is the primary path for a bot host; in-process is the fast/crash-free path once injected.
**Alternatives considered**: single external-only class (current BlackMagic) — rejected: forecloses the in-process delegate path, which is the cleanest crash-free execution. MemorySharp's factory-per-concern model (`Assembly`, `Threads`, `Windows` factories) — adopted selectively for the high-level surface, but the read/write core stays on `MemoryBase` for GreyMagic-style polymorphism.
### D2: Three-tier execution, crash-safe by default (the headline)
Execution is split by **payload safety**, not by convenience:
| Tier | Type | Use when | Mechanism |
|---|---|---|---|
| Remote thread | `RemoteThreadExecutor` | payload is thread-agnostic (LoadLibrary, pure WinAPI, self-contained shellcode) | `CreateRemoteThread` + convention stub, wait, exit code |
| **Main-thread pump** | `MainThreadPump` | **payload touches game state** (default) | queue delegate → drained on target thread via per-frame hook |
| In-process | `InProcessInvoker` | injected in-process | `Marshal.GetDelegateForFunctionPointer`, direct call |
`MainThreadPump` installs a detour on a caller-supplied per-frame function address (an `EndScene` resolver ships as a convenience helper) via `DetourManager`. Each frame the hook drains a thread-safe queue and runs pending work items synchronously in the game's context, returning results/exceptions to the requesting thread through a completion handle. **This is net-new — no frame hook exists in current BlackMagic to port.** It is built on GreyMagic-style detours (D5) as its one underlying primitive; the pump is the first consumer of `DetourManager`.
**Why**: `CreateRemoteThread` does not itself crash the game — calling single-thread-affinity game internals from a foreign thread does. Making the pump the default for game calls encodes that rule so callers cannot trip the crash by accident, while power users retain raw `CreateRemoteThread` for the payloads it is safe for.
**Alternatives considered**: (a) always `CreateRemoteThread` (MemorySharp/old-BM) — rejected: the documented crash source. (b) always in-process (GreyMagic) — rejected: requires a managed loader in the target and is not always available; external must work standalone. (c) thread-hijack for every call — rejected: high risk, one-shot, poor for repeated calls; kept only for injection.
### D3: `IAssembler` seam replacing FASM
```
IAssembler { byte[] Assemble(string asm, ulong origin = 0); }
```
Two backends:
- `StubAssembler` (default) — hand-emits the fixed calling-convention trampolines (cdecl/stdcall/thiscall/fastcall: push args, call, cleanup, ret) and the injection stubs, using an `EmitU8`/`EmitU32`/`EmitU64` byte emitter (the pattern already in `SInject.cs`). No parsing, deterministic, x64-capable, zero dependency.
- `IcedAssembler` (optional) — wraps Iced for arbitrary user-supplied mnemonics when a caller genuinely needs runtime text assembly.
**Why**: The comparison established that the only thing FASM did was runtime text→bytecode, needed solely for stubs and (rarely) arbitrary asm. Stubs are a small fixed set best hand-emitted; arbitrary asm is better served by a modern, managed, x64, MIT-licensed assembler (Iced) than by a native C++/CLI FASM DLL. Retiring FASM removes the x86-only mixed-mode constraint.
**Alternatives considered**: keep FASM/Fasm.NET — rejected: native dependency, x86-only, unmaintained. Hand-emit everything including arbitrary asm — rejected: reimplementing an assembler is out of scope; Iced already exists.
### D4: `MarshalCache<T>` for typed IO (from GreyMagic)
A `static class MarshalCache<T>` computes and caches `Marshal.SizeOf`, `TypeCode`, `TypeRequiresMarshal`, and `IsIntPtr` once per type. `Read<T>`/`Write<T>` branch on the cached flags: blittable types round-trip through `Span`/`MemoryMarshal`; marshal-required types use `Marshal.PtrToStructure`.
**Why**: GreyMagic's signature perf win — avoids per-call reflection. Current BlackMagic's `where unmanaged` constraint is faster still for blittable types but cannot express marshalled structs; the cache gives both.
### D5: Reversible hooking and patching with auto-restore (from GreyMagic)
`DetourManager`/`Detour` (inline `E9` jmp over a prologue, `CallOriginal`, `Apply`/`Remove`) and `PatchManager`/`Patch` (named byte patch, `Apply`/`Remove`/`IsApplied`). Both register into a manager that restores all live modifications on `MemoryBase.Dispose`. Detours are in-process only (they require executing the hook delegate in the target); patches work in both modes.
**Safety addition**: before splicing a detour, `StubAssembler`/`IcedAssembler` disassembles the target prologue to confirm the overwrite lands on instruction boundaries, avoiding the mid-instruction-splice crash class. When only `StubAssembler` is present, a minimal length-disassembler covers the common prologue shapes; full validation requires the Iced backend.
**Why**: MemorySharp promised Hook/Patch "coming soon" and never shipped them; GreyMagic shipped both and they are the cleanest available. Auto-restore prevents leaving the target corrupted after a crash of the host.
### D6: High-level surface as opt-in factories (from MemorySharp)
`RemotePointer` (indexer `sharp[addr]`), `RemoteModule`/`RemoteFunction` (`sharp["user32"]["MessageBoxA"].Execute(...)`), `ManagedPeb`/`ManagedTeb`, `WindowFactory` (move/resize/title/flash/activate), `Keyboard`/`Mouse` (PostMessage + SendInput), and async wrappers over the executors. These sit above `MemoryBase` and the execution tiers; none is required for core memory work.
**Why**: This is the ergonomic layer that made MemorySharp pleasant. It is pure P/Invoke over the core — no assembler, no FASM.
### D7: New projects, additive, test-first
`WhiteMagic/` (library, `net8.0-windows`, `AllowUnsafeBlocks`) and `WhiteMagicTest/` (xUnit). No solution file exists in the repo root today; task 1.3 creates a fresh `WhiteMagic.sln` (or builds the csproj directly). BlackMagic lives in a nested subdir with its own git and is not part of this build. Pure-logic components (assembler encodings, marshal cache, pattern matcher, stub builders, pump queue) get tests before implementation, mirroring the existing `inject-and-assemble` change's TDD discipline. Live-process behavior (RPM/WPM, injection, detours) is validated in integration tests gated on an available target.
## Risks / Trade-offs
- **Scope is large** → Deliver in vertical slices (see Migration Plan). The minimum crash-safe slice is `MemoryBase` + `ExternalReader` + `DetourManager` + `MainThreadPump`; everything else layers on without reworking it.
- **In-process tier needs a managed loader not in scope** → Ship `InProcessReader`'s API and delegate-call path now; mark the actual CLR-host injection as a follow-up. External + pump deliver crash-safety without it.
- **Detour prologue splicing can crash if misaligned** → Validate instruction boundaries before writing (D5); require Iced backend for full validation; keep `Remove`/auto-restore so a bad detour is recoverable.
- **Iced adds a NuGet dependency** → Isolated behind `IAssembler`; default `StubAssembler` keeps the library dependency-free. Only callers needing arbitrary asm opt in.
- **`MainThreadPump` adds per-frame overhead and a hook on a hot function** → Keep the drained-per-frame work bounded; the hook is a thin queue check when idle. Document that a wedged work item stalls the frame.
- **x64 detours need larger/absolute jumps (14-byte `push/ret` or RIP-relative)** → `StubAssembler` emits the correct form per bitness; covered by encoding tests.
- **Blittable vs marshalled ambiguity** in `MarshalCache` → Explicit flags and tests per type category; document that `[StructLayout]` is required for non-blittable remote structs.
## Migration Plan
WhiteMagic is additive; there is nothing to migrate off. Delivery is phased so each slice is independently useful and testable:
1. **Core**`MemoryBase`, `ExternalReader`, `SafeMemoryHandle`, `MarshalCache`, typed/string/bytes IO. (Replaces nothing; standalone.)
2. **Crash-safe execution slice**`StubAssembler`, `DetourManager`, `MainThreadPump`, `RemoteThreadExecutor`. Proves game-state calls without crashes end-to-end. This is the headline deliverable.
3. **Injection & discovery** — DLL injection (CreateThread + hijack, x86/x64), pattern scanning + cache, `PeHeaderParser`, named `AllocatedMemory`.
4. **In-process tier**`InProcessReader`, `InProcessInvoker`, `CreateFunction<T>`, vtable helpers (API surface; managed loader deferred).
5. **High-level ergonomics**`RemotePointer`, `RemoteModule`/`RemoteFunction`, PEB/TEB, window, input, async, `PatchManager` polish, helpers.
6. **Optional Iced backend**`IcedAssembler`, arbitrary-asm inject, full prologue validation.
**Rollback**: WhiteMagic is a separate assembly; removing its project reference reverts consumers with no effect on existing libraries.
## Open Questions
- **Frame-hook target**: ~~default to D3D9 `EndScene`, or accept a caller-supplied per-frame function address?~~ **Resolved**: `MainThreadPump` takes a caller-supplied frame-function address (WoW-agnostic, library stays offset-free per Non-Goals); an `EndScene` resolver ships as a convenience helper only. Slice 2 depends on this — settled before slice 2 starts.
- **Managed in-process loader**: which host mechanism (custom CLR host vs. a native shim that calls `CorBindToRuntimeEx`/`ICLRRuntimeHost`)? Deferred to a follow-up change but affects the `InProcessReader` seam shape.
- **Iced as default vs optional**: keep hand-stubs default (zero dep) — confirmed — but should the library ship a `WhiteMagic.Iced` companion package rather than an optional reference? Package boundary TBD.
- **Async model**: `Task`-based wrappers (MemorySharp) vs. exposing the pump's completion handles directly. Likely both: pump returns a handle, async wrappers adapt it to `Task<T>`.
@@ -0,0 +1,39 @@
## Why
Four process-manipulation libraries in this repo each solve part of the problem but none is complete: **BlackMagic-old** and **MemorySharp** depend on the native FASM assembler; **MemorySharp** has rich high-level ergonomics but is 32-bit-only and unmaintained; **GreyMagic** has the best engine (in-process reads, detours, patches, marshal cache) but is 32-bit and external-FASM-bound; **current BlackMagic** is the only modern, x64, FASM-free base but lacks remote function-calling, hooking, and high-level ergonomics. The recurring failure mode is game crashes from calling game internals on a foreign thread created by `CreateRemoteThread`. WhiteMagic unifies the best of all four into one modern (.NET 8, x64) library whose default path for game-state calls is crash-safe.
## What Changes
- Introduce a new library, **WhiteMagic**, as a fresh .NET 8 project (`WhiteMagic/`) with an xUnit test project (`WhiteMagicTest/`). Additive — existing BlackMagic is untouched.
- **Dual memory-access model**: an abstract `MemoryBase` with an `ExternalReader` (ReadProcessMemory/WriteProcessMemory) and an `InProcessReader` (direct pointer deref for injected scenarios), fronted by a `MarshalCache<T>` for allocation-free typed reads/writes.
- **Three-tier remote execution** — the headline capability:
- `RemoteThreadExecutor`: `CreateRemoteThread`-based `Execute<T>(addr, convention, args…)`, documented as safe for **thread-agnostic payloads only**.
- `MainThreadPump`: a crash-safe work queue drained on the target's own thread via a detour on a caller-supplied per-frame function (with an `EndScene` resolver helper) — the default for game-state calls. Net-new; no frame hook exists in current BlackMagic to port.
- `InProcessInvoker`: direct native-delegate calls (`CreateFunction<T>`) when injected in-process.
- **Function hooking**: reversible `DetourManager` (inline jmp, `CallOriginal`) and `PatchManager` (named byte patches) with auto-restore on dispose.
- **Managed assembler seam**: an `IAssembler` abstraction with two backends — hand-emitted calling-convention stubs (default) and an optional [Iced](https://github.com/icedland/iced) backend for arbitrary x86/x64 assembly. **No FASM, no native DLL.**
- **DLL injection**: `CreateRemoteThread` + thread-hijack strategies, x86 and x64 stubs (ported from current BlackMagic).
- **Discovery & allocation**: pattern scanning with cache, PE-header parsing, and named-chunk `AllocatedMemory`.
- **High-level ergonomics**: `RemotePointer` indexer, `Module["fn"].Execute(...)`, `ManagedPeb`/`ManagedTeb`, window mutation, keyboard/mouse simulation, async execution wrappers, and helper utilities.
## Capabilities
### New Capabilities
- `memory-access`: Dual external/in-process readers over an abstract `MemoryBase`, with `MarshalCache`-backed typed, array, and string read/write and relative/absolute addressing.
- `memory-discovery`: Pattern/signature scanning (with cache), PE-header parsing, and named-chunk remote allocation.
- `managed-assembler`: `IAssembler` seam producing machine code with no native dependency — hand-emitted convention stubs plus an optional Iced backend for arbitrary assembly.
- `remote-execution`: Three-tier execution (remote-thread, crash-safe main-thread pump, in-process delegate) with calling-convention-aware `Execute<T>` and parameter marshalling.
- `function-hooking`: Reversible inline detours (`CallOriginal`) and named byte patches with lifecycle management and auto-restore.
- `dll-injection`: DLL injection via `CreateRemoteThread` and thread-hijack redirection for x86 and x64 targets.
- `high-level-api`: Ergonomic surface — `RemotePointer` indexer, module/function access, PEB/TEB, window and input simulation, async wrappers, and helpers.
### Modified Capabilities
<!-- None — WhiteMagic is a new library; no existing WhiteMagic specs exist in openspec/specs/. -->
## Impact
- **New code**: `WhiteMagic/` library, `WhiteMagicTest/` xUnit project, both added to the solution.
- **New dependency (optional)**: `Iced` NuGet package, isolated behind `IAssembler`; the default hand-stub backend has zero third-party dependencies.
- **No native dependency**: FASM (`reference/fasm/`, `ManagedFasm`) is not referenced. It remains historical reference only, consistent with `FASM-MIGRATION.md`.
- **No changes** to BlackMagic, MemorySharp, GreyMagic, or their tests — WhiteMagic reuses their ideas, not their assemblies.
- **Platform**: builds x86 and x64; game targeting stays x86 to match `Wow.exe`, but the library is bitness-agnostic.
@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: DLL injection via remote thread
WhiteMagic SHALL inject a DLL into an open target process by creating a remote thread on `LoadLibrary`, returning the base address of the injected module on success and reporting failure without throwing for expected failure conditions.
#### Scenario: successful injection
- **WHEN** a valid DLL path is injected into an open process of matching bitness
- **THEN** the returned base address MUST be non-zero and the module MUST be loaded in the target
#### Scenario: bitness mismatch rejected
- **WHEN** the target process bitness differs from the caller
- **THEN** injection MUST fail with a clear error rather than corrupt the target
#### Scenario: missing file
- **WHEN** the DLL path does not exist
- **THEN** injection MUST report an argument error
### Requirement: DLL injection via thread hijack
WhiteMagic SHALL inject a DLL by hijacking an existing thread — saving its context, redirecting execution through a `LoadLibrary` stub, and restoring the original context — returning the injected module base address.
#### Scenario: hijack loads the module
- **WHEN** a valid DLL is injected by hijacking a running thread
- **THEN** the module MUST be loaded and the hijacked thread's original context MUST be restored
#### Scenario: exit code reports load result
- **WHEN** the redirect stub completes
- **THEN** the stub MUST record `LoadLibrary`'s result so the caller can detect load success or failure
### Requirement: x86 and x64 stubs
Injection stubs SHALL be emitted correctly for both x86 and x64 targets, including proper x64 addressing.
#### Scenario: x86 stub
- **WHEN** injecting into a 32-bit target
- **THEN** a 32-bit redirect stub MUST be emitted
#### Scenario: x64 stub
- **WHEN** injecting into a 64-bit target
- **THEN** a 64-bit redirect stub with correct absolute/RIP-relative addressing MUST be emitted
### Requirement: Raw code injection
WhiteMagic SHALL inject raw machine-code bytes into an open process, either at a caller-supplied address or into freshly allocated remote memory whose address is returned.
#### Scenario: inject at address
- **WHEN** raw bytes are injected at a given address
- **THEN** memory at that address MUST equal the injected bytes
#### Scenario: inject into fresh allocation
- **WHEN** raw bytes are injected without an address
- **THEN** remote memory MUST be allocated, the bytes written, and the allocation address returned
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Reversible inline detours
WhiteMagic SHALL provide a `DetourManager` that creates named inline detours redirecting a target function to a managed hook, supporting `Apply`, `Remove`, and calling the original function. Detours operate in-process.
#### Scenario: apply redirects the target
- **WHEN** a detour from a target function to a hook delegate is applied
- **THEN** calling the target MUST invoke the hook delegate
#### Scenario: call original
- **WHEN** the hook invokes `CallOriginal(args)`
- **THEN** the original target behavior MUST execute with those arguments and its result returned
#### Scenario: remove restores original bytes
- **WHEN** an applied detour is removed
- **THEN** the target's original prologue bytes MUST be restored and calling the target MUST no longer invoke the hook
#### Scenario: named lookup
- **WHEN** a detour is created with a name
- **THEN** it MUST be retrievable from the manager by that name
### Requirement: Instruction-boundary validation before splicing
Before overwriting a target prologue, the detour SHALL verify the overwrite covers whole instructions so that no instruction is split.
#### Scenario: aligned splice permitted
- **WHEN** the bytes required for the jump cover a whole number of prologue instructions
- **THEN** the detour MUST apply
#### Scenario: misaligned splice rejected
- **WHEN** the required overwrite would end in the middle of an instruction and boundary information is available
- **THEN** the detour MUST refuse to apply rather than corrupt the target
### Requirement: Named reversible byte patches
WhiteMagic SHALL provide a `PatchManager` that creates named byte patches with `Apply`, `Remove`, and `IsApplied`, usable in both external and in-process modes.
#### Scenario: apply writes patch bytes
- **WHEN** a patch is applied at an address
- **THEN** memory at that address MUST equal the patch bytes
#### Scenario: remove restores original
- **WHEN** an applied patch is removed
- **THEN** memory at that address MUST equal the original bytes captured at creation
#### Scenario: is-applied reflects state
- **WHEN** `IsApplied` is queried
- **THEN** it MUST return true only when the current bytes equal the patch bytes
### Requirement: Auto-restore on dispose
All live detours and patches SHALL be reverted when their owning `MemoryBase` is disposed.
#### Scenario: dispose reverts modifications
- **WHEN** a `MemoryBase` with active detours and patches is disposed
- **THEN** every modified region MUST be restored to its pre-modification bytes
@@ -0,0 +1,69 @@
## ADDED Requirements
### Requirement: Remote pointer indexer
WhiteMagic SHALL expose a `RemotePointer` obtained by indexing the memory facade with an address, offering read/write/execute operations relative to that base address.
#### Scenario: read via indexer
- **WHEN** `sharp[addr].Read<int>(offset)` is called
- **THEN** it MUST read an int at `addr + offset`
#### Scenario: write via indexer
- **WHEN** `sharp[addr].WriteString("text")` is called
- **THEN** the string MUST be written starting at `addr`
### Requirement: Module and function access
WhiteMagic SHALL expose modules and their exported functions by name, allowing a resolved function to be executed with a calling convention and arguments.
#### Scenario: resolve function by name
- **WHEN** `sharp["user32"]["MessageBoxA"]` is resolved
- **THEN** it MUST return a function bound to the export address of `MessageBoxA` in `user32`
#### Scenario: execute resolved function
- **WHEN** a resolved function is executed with a calling convention and arguments
- **THEN** it MUST invoke the target through the chosen execution strategy with those arguments
### Requirement: PEB and TEB access
WhiteMagic SHALL expose managed reads of the target's Process Environment Block and a thread's Thread Environment Block.
#### Scenario: read PEB field
- **WHEN** a PEB field (e.g. being-debugged flag) is read
- **THEN** it MUST reflect the target's actual PEB value
#### Scenario: read TEB field
- **WHEN** a TEB field is read for a given thread
- **THEN** it MUST reflect that thread's actual TEB value
### Requirement: Window mutation
WhiteMagic SHALL enumerate and mutate target windows — position, size, title, activation, and flashing.
#### Scenario: move and resize
- **WHEN** a window's X, Y, width, and height are set
- **THEN** the window MUST move and resize to those values
#### Scenario: query by class name
- **WHEN** windows are queried by class name
- **THEN** matching windows MUST be returned
### Requirement: Keyboard and mouse simulation
WhiteMagic SHALL simulate keyboard and mouse input to a target window, including input delivered without activating the window where the mechanism allows.
#### Scenario: write text to a window
- **WHEN** text is written to a target window's keyboard interface
- **THEN** the window MUST receive the corresponding key input
#### Scenario: mouse click
- **WHEN** a click at a coordinate is issued to a window's mouse interface
- **THEN** the window MUST receive the corresponding mouse input
### Requirement: Asynchronous execution wrappers
WhiteMagic SHALL provide `Task`-based asynchronous wrappers over its execution strategies.
#### Scenario: async execute returns a task
- **WHEN** an async execute is invoked
- **THEN** it MUST return a `Task<T>` that completes with the execution result
@@ -0,0 +1,49 @@
## ADDED Requirements
### Requirement: IAssembler abstraction with no native dependency
WhiteMagic SHALL define an `IAssembler` seam that produces machine code, with a default backend that has no native or third-party dependency. FASM MUST NOT be referenced by the default configuration.
#### Scenario: default backend is dependency-free
- **WHEN** WhiteMagic is built in its default configuration
- **THEN** no reference to FASM or `ManagedFasm` MUST be present in the output
#### Scenario: backend is replaceable
- **WHEN** an alternate `IAssembler` implementation is supplied
- **THEN** execution and injection MUST use it without other code changes
### Requirement: Hand-emitted calling-convention stubs
The default `StubAssembler` SHALL emit call trampolines for the cdecl, stdcall, thiscall, and fastcall conventions — pushing/placing arguments, calling the target, cleaning the stack per convention, and returning — for both x86 and x64 targets.
#### Scenario: cdecl stub encoding
- **WHEN** a cdecl call stub for a function with N 4-byte arguments is emitted (x86)
- **THEN** the bytes MUST push the arguments in reverse order, `call` the target, `add esp, N*4`, and `ret`
#### Scenario: stdcall omits caller cleanup
- **WHEN** a stdcall stub is emitted
- **THEN** it MUST NOT emit a caller-side stack cleanup (the callee cleans)
#### Scenario: x64 uses register argument order
- **WHEN** an x64 call stub is emitted
- **THEN** the first integer arguments MUST be placed in the platform argument registers before the call
### Requirement: Byte emitter primitives
`StubAssembler` SHALL provide little-endian emit primitives (`EmitU8`, `EmitU32`, `EmitU64`) used to hand-assemble stubs deterministically.
#### Scenario: little-endian 32-bit emit
- **WHEN** `EmitU32(0x11223344)` is called
- **THEN** the appended bytes MUST be `[0x44, 0x33, 0x22, 0x11]`
### Requirement: Optional Iced backend for arbitrary assembly
WhiteMagic SHALL provide an optional `IcedAssembler` backend that assembles arbitrary x86/x64 mnemonic text to machine code for callers who require runtime text assembly.
#### Scenario: arbitrary mnemonics assembled
- **WHEN** the Iced backend assembles `"push 0\nadd esp, 4\nret"` at a given origin
- **THEN** it MUST return the corresponding machine code bytes
#### Scenario: origin-relative encoding
- **WHEN** assembly containing a relative jump is assembled at a specified origin address
- **THEN** the encoded relative offsets MUST be correct for that origin
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Abstract memory base with two readers
WhiteMagic SHALL expose an abstract `MemoryBase` type defining `ReadBytes`, `WriteBytes`, generic `Read<T>`/`Write<T>`, array read/write, and string read/write, with two concrete implementations: `ExternalReader` (out-of-process via ReadProcessMemory/WriteProcessMemory) and `InProcessReader` (in-process via direct pointer dereference).
#### Scenario: external read round-trip
- **WHEN** an `ExternalReader` opens a target process and writes a value with `Write<int>(addr, 0x1234)` then reads it back with `Read<int>(addr)`
- **THEN** the returned value MUST equal `0x1234`
#### Scenario: in-process read of own memory
- **WHEN** an `InProcessReader` reads a known address in its own process
- **THEN** the value MUST match a direct managed read of the same address
#### Scenario: shared API surface
- **WHEN** code is written against the `MemoryBase` abstract type
- **THEN** it MUST operate unchanged against both `ExternalReader` and `InProcessReader`
### Requirement: Typed read/write via marshal cache
`MemoryBase` SHALL support generic `Read<T>`/`Write<T>` for blittable and marshalled struct types, using a per-type `MarshalCache<T>` that caches size, type code, and marshalling requirements to avoid per-call reflection.
#### Scenario: blittable struct round-trip
- **WHEN** a blittable `[StructLayout(LayoutKind.Sequential)]` struct is written and read back
- **THEN** all fields MUST be preserved exactly
#### Scenario: marshal cache computed once
- **WHEN** `Read<T>` is invoked repeatedly for the same type `T`
- **THEN** `Marshal.SizeOf` and type inspection for `T` MUST be computed at most once and reused
#### Scenario: array read
- **WHEN** `Read<T>(addr, count)` is called
- **THEN** it MUST return an array of exactly `count` elements read contiguously from `addr`
### Requirement: String read and write with encoding
`MemoryBase` SHALL read and write strings with a caller-specified `Encoding` and a maximum length, terminating reads at a null terminator or the maximum length.
#### Scenario: ASCII write then read
- **WHEN** `WriteString(addr, "hello", Encoding.ASCII)` is called then `ReadString(addr, Encoding.ASCII)`
- **THEN** the result MUST equal `"hello"`
#### Scenario: read stops at null terminator
- **WHEN** a null-terminated string shorter than `maxLength` is read
- **THEN** the returned string MUST exclude the terminator and everything after it
### Requirement: Relative and absolute addressing
`MemoryBase` SHALL convert between addresses relative to the module image base and absolute addresses via `GetAbsolute` and `GetRelative`, and accept an `isRelative` flag on read/write operations.
#### Scenario: relative resolves against image base
- **WHEN** `GetAbsolute(relative)` is called with the process image base known
- **THEN** the result MUST equal `imageBase + relative`
#### Scenario: read with isRelative
- **WHEN** `Read<int>(offset, isRelative: true)` is called
- **THEN** the read MUST occur at `GetAbsolute(offset)`
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Pattern scanning with mask
WhiteMagic SHALL scan process memory for a byte signature with a wildcard mask, returning the address of the first match or `IntPtr.Zero` when no match is found. Scans SHALL be available over an explicit range, a single module, and all modules.
#### Scenario: pattern found
- **WHEN** a known byte sequence is scanned for with a matching mask over a range containing it
- **THEN** the returned address MUST point at the first occurrence
#### Scenario: wildcard mask
- **WHEN** the mask marks positions as wildcards (e.g. `"xx?x"`)
- **THEN** those byte positions MUST be ignored during matching
#### Scenario: pattern not found
- **WHEN** a pattern absent from the range is scanned for
- **THEN** the result MUST be `IntPtr.Zero`
### Requirement: Pattern scan cache
The scanner SHALL cache resolved pattern results keyed by pattern and mask, returning the cached address on repeat lookups, and SHALL expose an operation to clear the cache.
#### Scenario: repeat lookup served from cache
- **WHEN** the same pattern and mask are scanned twice without clearing the cache
- **THEN** the second lookup MUST return the same address without rescanning memory
#### Scenario: cache cleared
- **WHEN** the cache is cleared
- **THEN** the next lookup MUST rescan memory
### Requirement: PE header parsing
WhiteMagic SHALL parse the PE headers of a module to expose its sections and entry point without executing the module.
#### Scenario: sections enumerated
- **WHEN** a valid PE module is parsed
- **THEN** its section names, virtual addresses, and sizes MUST be enumerable
#### Scenario: entry point located
- **WHEN** a valid PE module is parsed
- **THEN** the parsed entry-point RVA MUST match the module's header
### Requirement: Named remote allocation
WhiteMagic SHALL allocate a chunk of remote memory subdivided into named regions, allowing typed read/write and address lookup by name, and freeing the whole chunk on dispose.
#### Scenario: write and read by name
- **WHEN** a named region is allocated and `Write<int>("count", 5)` then `Read<int>("count")` is called
- **THEN** the result MUST equal `5`
#### Scenario: address by name
- **WHEN** a region named `"buffer"` is allocated
- **THEN** requesting its address MUST return `chunkBase + regionOffset`
#### Scenario: freed on dispose
- **WHEN** the allocation is disposed
- **THEN** the underlying remote memory MUST be released
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Three-tier execution model
WhiteMagic SHALL provide three execution strategies selected by payload safety: `RemoteThreadExecutor` (via `CreateRemoteThread`), `MainThreadPump` (work marshalled onto the target's own thread), and `InProcessInvoker` (direct native-delegate calls when injected in-process).
#### Scenario: strategies are distinct and selectable
- **WHEN** a caller chooses an execution strategy
- **THEN** each of remote-thread, main-thread-pump, and in-process MUST be individually invokable
#### Scenario: main-thread pump is the documented default for game state
- **WHEN** documentation or API guidance describes calling functions that touch single-thread-affinity process state
- **THEN** it MUST direct callers to the main-thread pump, not `CreateRemoteThread`
### Requirement: Remote-thread execution for thread-agnostic payloads
`RemoteThreadExecutor` SHALL create a remote thread at a target address using a calling-convention-aware stub, wait for completion, and return the typed exit value. Its documentation MUST state that it is safe only for thread-agnostic payloads.
#### Scenario: execute with parameters and convention
- **WHEN** `Execute<int>(addr, CallingConvention.Cdecl, arg1, arg2)` is called on a safe self-contained function
- **THEN** the target MUST be called with the arguments laid out per cdecl and the typed return value returned
#### Scenario: parameters marshalled and freed
- **WHEN** a `string` or struct parameter is passed to `Execute`
- **THEN** it MUST be allocated in the remote process, passed by pointer, and freed after the call completes
#### Scenario: no process open
- **WHEN** `Execute` is called with no process open
- **THEN** it MUST fail deterministically rather than crash
### Requirement: Crash-safe main-thread pump
`MainThreadPump` SHALL install a hook on a per-frame function in the target and, each time that function runs, drain a thread-safe queue of work items, executing each on the target's own thread and returning its result or exception to the requesting caller.
#### Scenario: work runs on the hooked thread
- **WHEN** a work item is queued and the hooked per-frame function next executes
- **THEN** the work item MUST run in the context of the thread that calls the per-frame function
#### Scenario: result returned to caller
- **WHEN** a caller queues a function returning a value and awaits its completion
- **THEN** the caller MUST receive the returned value
#### Scenario: exception propagated, pump survives
- **WHEN** a queued work item throws
- **THEN** the exception MUST be surfaced to the requesting caller AND subsequent queued items MUST still be processed
#### Scenario: uninstall restores the frame function
- **WHEN** the pump is disposed
- **THEN** the hooked per-frame function MUST be restored to its original bytes
### Requirement: In-process delegate invocation
`InProcessInvoker` SHALL convert a function address to a typed managed delegate and call it directly, without creating a thread or crossing a thread boundary.
#### Scenario: call as delegate
- **WHEN** `CreateFunction<TDelegate>(addr)` is called in-process and the returned delegate is invoked
- **THEN** the native function at `addr` MUST be called directly on the current thread with the delegate's marshalled arguments
@@ -0,0 +1,86 @@
## 1. Project Setup
- [ ] 1.1 Create `WhiteMagic/WhiteMagic.csproj` targeting `net8.0-windows`, `AllowUnsafeBlocks=true`, nullable enabled
- [ ] 1.2 Create `WhiteMagicTest/WhiteMagicTest.csproj` (xUnit, `net8.0-windows`) referencing `WhiteMagic`
- [ ] 1.3 No solution file exists in the repo root. Create one (`dotnet new sln -n WhiteMagic`) and add both projects, OR skip the solution and build csproj directly (decide before 1.5). Do NOT reference `BlackMagic.slnx` — it does not exist.
- [ ] 1.4 Add `WhiteMagic/Native/` P/Invoke surface (`LibraryImport`): OpenProcess, Read/WriteProcessMemory, VirtualAllocEx/FreeEx/ProtectEx, CreateRemoteThread, Wow64Get/SetThreadContext, Get/SetThreadContext, LoadLibrary, GetProcAddress; add `SafeMemoryHandle`
- [ ] 1.5 Verify empty projects build: `dotnet build WhiteMagic.sln` (or `dotnet build WhiteMagic/WhiteMagic.csproj` if no solution) — zero errors
## 2. Core Memory Access (spec: memory-access)
- [ ] 2.1 Add tests for `MarshalCache<T>`: blittable size, marshal-required flag, IsIntPtr, computed-once behavior
- [ ] 2.2 Implement `WhiteMagic/MarshalCache.cs` to pass 2.1
- [ ] 2.3 Add tests for `MemoryBase` abstract contract + `ExternalReader` round-trip (`Read<T>`/`Write<T>`, arrays) using the current process as target
- [ ] 2.4 Implement `WhiteMagic/MemoryBase.cs` (abstract) and `WhiteMagic/ExternalReader.cs` to pass 2.3
- [ ] 2.5 Add tests for string read/write with encoding, null-terminator stop, and max length
- [ ] 2.6 Implement `ReadString`/`WriteString` on `MemoryBase` to pass 2.5
- [ ] 2.7 Add tests for relative/absolute addressing (`GetAbsolute`/`GetRelative`, `isRelative` flag)
- [ ] 2.8 Implement addressing helpers to pass 2.7
- [ ] 2.9 Add tests + `unsafe` implementation for `InProcessReader` (direct deref against own process); verify shared `MemoryBase` API works for both readers
## 3. Managed Assembler (spec: managed-assembler)
- [ ] 3.1 Add tests for `EmitU8`/`EmitU32`/`EmitU64` little-endian primitives
- [ ] 3.2 Implement `WhiteMagic/Assembly/StubAssembler.cs` emitters + `IAssembler` interface to pass 3.1
- [ ] 3.3 Add tests for x86 cdecl stub encoding (reverse push, call, `add esp, N*4`, ret) with known byte expectations
- [ ] 3.4 Implement x86 cdecl stub to pass 3.3
- [ ] 3.5 Add tests for stdcall (no caller cleanup), thiscall (ecx = this), fastcall (ecx/edx) x86 stubs
- [ ] 3.6 Implement x86 stdcall/thiscall/fastcall stubs to pass 3.5
- [ ] 3.7 Add tests for x64 stub argument-register placement and call
- [ ] 3.8 Implement x64 stub to pass 3.7
- [ ] 3.9 Confirm no FASM/`ManagedFasm` reference exists in `WhiteMagic` output (assert via a test that scans loaded references)
## 4. Crash-Safe Execution Slice (spec: remote-execution, function-hooking)
- [ ] 4.1 Add tests for `PatchManager`/`Patch`: apply writes bytes, remove restores original, `IsApplied` reflects state
- [ ] 4.2 Implement `WhiteMagic/Hooking/PatchManager.cs` + `Patch.cs` to pass 4.1
- [ ] 4.3 Add tests for `DetourManager`/`Detour` in-process: apply redirects, `CallOriginal`, remove restores, named lookup
- [ ] 4.4 Implement `WhiteMagic/Hooking/DetourManager.cs` + `Detour.cs` (inline jmp, x86/x64 form) to pass 4.3
- [ ] 4.5 Add tests for instruction-boundary validation (aligned splice permitted, misaligned rejected when boundary info available)
- [ ] 4.6 Implement minimal prologue length-decoder in `Detour.Apply` to pass 4.5. Default `StubAssembler` covers ONLY the common x86/x64 prologue shapes — enumerate the covered opcodes in code + XML doc (e.g. `push reg` 0x50-0x57, `mov edi,edi` 8B FF, `push ebp`/`mov ebp,esp` 55 8B EC, `sub esp,imm` 83 EC / 81 EC, REX-prefixed forms). On any opcode outside the set, refuse the splice (do not guess). Full arbitrary-prologue validation is gated on the optional Iced backend (task 8.3) — document that slices 2-5 ship partial boundary safety.
- [ ] 4.7 Add tests for auto-restore: disposing a `MemoryBase` reverts all active patches and detours
- [ ] 4.8 Wire manager registration + `MemoryBase.Dispose` restore to pass 4.7
- [ ] 4.9 Add tests for `MainThreadPump` queue semantics: item runs on hooked thread, result returned, throwing item surfaces exception and pump survives, dispose uninstalls hook (use a self-hosted frame-loop harness in-process)
- [ ] 4.10 Implement `WhiteMagic/Execution/MainThreadPump.cs` (frame-function detour + thread-safe work queue + completion handles) to pass 4.9
- [ ] 4.11 Add tests for `RemoteThreadExecutor.Execute<T>` (convention stub + wait + typed exit; no-process failure is deterministic)
- [ ] 4.12 Implement `WhiteMagic/Execution/RemoteThreadExecutor.cs` and parameter marshalling (string/struct → remote alloc → free) to pass 4.11
## 5. Injection & Discovery (spec: dll-injection, memory-discovery)
- [ ] 5.1 Add tests for pattern scanning: found (range/module/all-modules), wildcard mask, not-found returns Zero
- [ ] 5.2 Implement `WhiteMagic/Discovery/PatternScanner.cs` to pass 5.1
- [ ] 5.3 Add tests + implement scan result cache (repeat served from cache, clear rescans)
- [ ] 5.4 Add tests + implement `WhiteMagic/Discovery/PeHeaderParser.cs` (sections, entry point)
- [ ] 5.5 Add tests + implement `WhiteMagic/Memory/AllocatedMemory.cs` (named regions, typed read/write by name, address by name, free on dispose)
- [ ] 5.6 Add tests + implement raw code injection (`InjectCode` at address and into fresh allocation)
- [ ] 5.7 Add tests + implement DLL injection via remote thread (LoadLibrary), including bitness-mismatch and missing-file failures
- [ ] 5.8 Add tests + implement DLL injection via thread-hijack (save/redirect/restore context) with x86 and x64 stubs
## 6. In-Process Tier (spec: remote-execution)
- [ ] 6.1 Add tests for `InProcessInvoker.CreateFunction<TDelegate>` calling a known in-process function directly
- [ ] 6.2 Implement `WhiteMagic/Execution/InProcessInvoker.cs` (`Marshal.GetDelegateForFunctionPointer`) + vtable-entry helper to pass 6.1
- [ ] 6.3 Document that the CLR-host managed loader (injecting `InProcessReader` into a foreign process) is a separate follow-up change
## 7. High-Level Ergonomics (spec: high-level-api)
- [ ] 7.1 Add tests + implement `RemotePointer` indexer (`sharp[addr].Read/Write/Execute` relative to base)
- [ ] 7.2 Add tests + implement `RemoteModule`/`RemoteFunction` (`sharp["mod"]["fn"]`) resolving export addresses and executing via a chosen strategy
- [ ] 7.3 Add tests + implement `ManagedPeb`/`ManagedTeb` field reads
- [ ] 7.4 Add tests + implement `WindowFactory`/`RemoteWindow` (enumerate, move/resize/title/activate/flash, query by class)
- [ ] 7.5 Add tests + implement keyboard/mouse simulation (PostMessage + SendInput) to a target window
- [ ] 7.6 Add tests + implement `Task`-based async execution wrappers over the executors and pump
- [ ] 7.7 Add minimal facade (`WhiteMagic` entry type) exposing `Open`, readers, executors, managers, and the indexer
## 8. Optional Iced Backend (spec: managed-assembler)
- [ ] 8.1 Add `Iced` package reference behind an `IcedAssembler : IAssembler` in a way that keeps the default `StubAssembler` dependency-free
- [ ] 8.2 Add tests + implement `IcedAssembler.Assemble(text, origin)` for arbitrary mnemonics and origin-relative encoding
- [ ] 8.3 Add tests + wire full prologue instruction-boundary validation (D5) using the Iced disassembler when present
## 9. Verification
- [ ] 9.1 Run full test suite: `dotnet test WhiteMagicTest/WhiteMagicTest.csproj` — all pass
- [ ] 9.2 Run full build (`dotnet build WhiteMagic.sln` or the WhiteMagic csproj, per 1.3) — zero errors, zero new warnings in `WhiteMagic`
- [ ] 9.3 Confirm existing BlackMagic/its tests are unchanged and still green
- [ ] 9.4 Update `docs/memory-library-comparison.md` "WhiteMagic — synthesis" section with any deviations discovered during implementation
+1
View File
@@ -0,0 +1 @@
schema: spec-driven