Compare commits
21 Commits
99927c2263
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d2404e3cf | |||
| 3b27d2cfd4 | |||
| 6b4fcf2647 | |||
| 0bd9ec8a36 | |||
| e35d16a92e | |||
| d296f97a71 | |||
| 1362972630 | |||
| cd40a58f33 | |||
| ae3b123eeb | |||
| 2d327b5af8 | |||
| f4cb809f9d | |||
| 495a9dea3d | |||
| 6544c5fc4c | |||
| 3d26ea1b8d | |||
| ebf4bbecd8 | |||
| 04a22ccbf6 | |||
| 9f04dac50b | |||
| 9f686270c2 | |||
| a21f5063c8 | |||
| c693e23888 | |||
| 856700fbe0 |
@@ -59,7 +59,7 @@ Archive a completed change in the experimental workflow.
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
If user chooses sync, execute `/opsx:sync` logic. Proceed to archive regardless of choice.
|
||||
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**
|
||||
|
||||
@@ -153,5 +153,5 @@ Target archive directory already exists.
|
||||
- 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 /opsx:sync approach (agent-driven)
|
||||
- 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
|
||||
|
||||
@@ -225,7 +225,7 @@ Failed K changes:
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use `/opsx:new` to create a new change.
|
||||
No active changes found. Create a new change to get started.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
|
||||
@@ -7,7 +7,7 @@ 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 (e.g., start a change with `/opsx:new` or `/opsx:ff`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
**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.
|
||||
|
||||
@@ -100,8 +100,7 @@ If the user mentioned a specific change name, read its artifacts for context.
|
||||
|
||||
Think freely. When insights crystallize, you might offer:
|
||||
|
||||
- "This feels solid enough to start a change. Want me to create one?"
|
||||
→ Can transition to `/opsx:new` or `/opsx:ff`
|
||||
- "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
|
||||
@@ -153,7 +152,7 @@ If the user mentions a change or you detect one is relevant:
|
||||
|
||||
There's no required ending. Discovery might:
|
||||
|
||||
- **Flow into action**: "Ready to start? `/opsx:new` or `/opsx:ff`"
|
||||
- **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"
|
||||
|
||||
@@ -84,7 +84,10 @@ After completing all artifacts, summarize:
|
||||
- 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 the `template` as a starting point, filling in based on context
|
||||
- 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`)
|
||||
|
||||
@@ -11,16 +11,19 @@ Guide the user through their first complete OpenSpec workflow cycle. This is a t
|
||||
|
||||
## Preflight
|
||||
|
||||
Before starting, check if OpenSpec is initialized:
|
||||
Before starting, check if the OpenSpec CLI is installed:
|
||||
|
||||
```bash
|
||||
openspec status --json 2>&1 || echo "NOT_INITIALIZED"
|
||||
# Unix/macOS
|
||||
openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
|
||||
# Windows (PowerShell)
|
||||
# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
|
||||
```
|
||||
|
||||
**If not initialized:**
|
||||
> OpenSpec isn't set up in this project yet. Run `openspec init` first, then come back to `/opsx:onboard`.
|
||||
**If CLI not installed:**
|
||||
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.
|
||||
|
||||
Stop here if not initialized.
|
||||
Stop here if not installed.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +66,10 @@ Scan the codebase for small improvement opportunities. Look for:
|
||||
|
||||
Also check recent git activity:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
git log --oneline -10 2>/dev/null || echo "No git history"
|
||||
# Windows (PowerShell)
|
||||
# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
|
||||
```
|
||||
|
||||
### Present Suggestions
|
||||
@@ -258,7 +264,10 @@ For a small task like this, we might only need one spec file.
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
@@ -453,21 +462,29 @@ This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
## Command Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:propose` | Create a change and generate all artifacts |
|
||||
| `/opsx:explore` | Think through problems before/during work |
|
||||
| `/opsx:new` | Start a new change, step through artifacts |
|
||||
| `/opsx:ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx:continue` | Continue working on an existing change |
|
||||
| `/opsx:apply` | Implement tasks from a change |
|
||||
| `/opsx:verify` | Verify implementation matches artifacts |
|
||||
| `/opsx:archive` | Archive a completed change |
|
||||
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:new` | Start a new change, step through artifacts one at a time |
|
||||
| `/opsx:continue` | Continue working on an existing change |
|
||||
| `/opsx:ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx:verify` | Verify implementation matches artifacts |
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Try `/opsx:new` or `/opsx:ff` on something you actually want to build. You've got the rhythm now!
|
||||
Try `/opsx:propose` on something you actually want to build. You've got the rhythm now!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -497,17 +514,25 @@ If the user says they just want to see the commands or skip the tutorial:
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:propose <name>` | Create a change and generate all artifacts |
|
||||
| `/opsx:explore` | Think through problems (no code changes) |
|
||||
| `/opsx:new <name>` | Start a new change, step by step |
|
||||
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx:continue <name>` | Continue an existing change |
|
||||
| `/opsx:apply <name>` | Implement tasks |
|
||||
| `/opsx:verify <name>` | Verify implementation |
|
||||
| `/opsx:archive <name>` | Archive when done |
|
||||
|
||||
Try `/opsx:new` to start your first change, or `/opsx:ff` if you want to move fast.
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:new <name>` | Start a new change, step by step |
|
||||
| `/opsx:continue <name>` | Continue an existing change |
|
||||
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx:verify <name>` | Verify implementation |
|
||||
|
||||
Try `/opsx:propose` to start your first change.
|
||||
```
|
||||
|
||||
Exit gracefully.
|
||||
|
||||
106
.claude/commands/opsx/propose.md
Normal file
106
.claude/commands/opsx/propose.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: "OPSX: Propose"
|
||||
description: Propose a new change - create it and generate all artifacts in one step
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
**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 at `openspec/changes/<name>/` 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
|
||||
|
||||
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
|
||||
- `outputPath`: Where 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
|
||||
- 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
|
||||
@@ -2,7 +2,18 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(uv:*)",
|
||||
"WebSearch"
|
||||
"Bash(openspec:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(*)",
|
||||
"Bash(pip index:*)",
|
||||
"Bash(pip show:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(git:*)",
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__context7__query-docs",
|
||||
"mcp__exa__get_code_context_exa",
|
||||
"mcp__exa__web_search_exa"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Implement tasks from an OpenSpec change.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Archive a completed change in the experimental workflow.
|
||||
@@ -63,7 +63,7 @@ Archive a completed change in the experimental workflow.
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
If user chooses sync, execute /opsx:sync logic (use the openspec-sync-specs skill). Proceed to archive regardless of choice.
|
||||
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**
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Archive multiple completed changes in a single operation.
|
||||
@@ -229,7 +229,7 @@ Failed K changes:
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use `/opsx:new` to create a new change.
|
||||
No active changes found. Create a new change to get started.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Continue working on a change by creating the next artifact.
|
||||
|
||||
@@ -6,12 +6,12 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.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 (e.g., start a change with `/opsx:new` or `/opsx:ff`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
**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.
|
||||
|
||||
@@ -95,8 +95,7 @@ This tells you:
|
||||
|
||||
Think freely. When insights crystallize, you might offer:
|
||||
|
||||
- "This feels solid enough to start a change. Want me to create one?"
|
||||
→ Can transition to `/opsx:new` or `/opsx:ff`
|
||||
- "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
|
||||
@@ -252,7 +251,7 @@ You: That changes everything.
|
||||
|
||||
There's no required ending. Discovery might:
|
||||
|
||||
- **Flow into action**: "Ready to start? /opsx:new or /opsx:ff"
|
||||
- **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"
|
||||
@@ -269,8 +268,7 @@ When it feels like things are crystallizing, you might summarize:
|
||||
**Open questions**: [if any remain]
|
||||
|
||||
**Next steps** (if ready):
|
||||
- Create a change: /opsx:new <name>
|
||||
- Fast-forward to tasks: /opsx:ff <name>
|
||||
- Create a change proposal
|
||||
- Keep exploring: just keep talking
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Fast-forward through artifact creation - generate everything needed to start implementation in one go.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Start a new change using the experimental artifact-driven approach.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
|
||||
@@ -15,16 +15,19 @@ Guide the user through their first complete OpenSpec workflow cycle. This is a t
|
||||
|
||||
## Preflight
|
||||
|
||||
Before starting, check if OpenSpec is initialized:
|
||||
Before starting, check if the OpenSpec CLI is installed:
|
||||
|
||||
```bash
|
||||
openspec status --json 2>&1 || echo "NOT_INITIALIZED"
|
||||
# Unix/macOS
|
||||
openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
|
||||
# Windows (PowerShell)
|
||||
# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
|
||||
```
|
||||
|
||||
**If not initialized:**
|
||||
> OpenSpec isn't set up in this project yet. Run `openspec init` first, then come back to `/opsx:onboard`.
|
||||
**If CLI not installed:**
|
||||
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.
|
||||
|
||||
Stop here if not initialized.
|
||||
Stop here if not installed.
|
||||
|
||||
---
|
||||
|
||||
@@ -67,7 +70,10 @@ Scan the codebase for small improvement opportunities. Look for:
|
||||
|
||||
Also check recent git activity:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
git log --oneline -10 2>/dev/null || echo "No git history"
|
||||
# Windows (PowerShell)
|
||||
# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
|
||||
```
|
||||
|
||||
### Present Suggestions
|
||||
@@ -262,7 +268,10 @@ For a small task like this, we might only need one spec file.
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
@@ -457,21 +466,29 @@ This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
## Command Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:propose` | Create a change and generate all artifacts |
|
||||
| `/opsx:explore` | Think through problems before/during work |
|
||||
| `/opsx:new` | Start a new change, step through artifacts |
|
||||
| `/opsx:ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx:continue` | Continue working on an existing change |
|
||||
| `/opsx:apply` | Implement tasks from a change |
|
||||
| `/opsx:verify` | Verify implementation matches artifacts |
|
||||
| `/opsx:archive` | Archive a completed change |
|
||||
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:new` | Start a new change, step through artifacts one at a time |
|
||||
| `/opsx:continue` | Continue working on an existing change |
|
||||
| `/opsx:ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx:verify` | Verify implementation matches artifacts |
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Try `/opsx:new` or `/opsx:ff` on something you actually want to build. You've got the rhythm now!
|
||||
Try `/opsx:propose` on something you actually want to build. You've got the rhythm now!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -501,17 +518,25 @@ If the user says they just want to see the commands or skip the tutorial:
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:propose <name>` | Create a change and generate all artifacts |
|
||||
| `/opsx:explore` | Think through problems (no code changes) |
|
||||
| `/opsx:new <name>` | Start a new change, step by step |
|
||||
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx:continue <name>` | Continue an existing change |
|
||||
| `/opsx:apply <name>` | Implement tasks |
|
||||
| `/opsx:verify <name>` | Verify implementation |
|
||||
| `/opsx:archive <name>` | Archive when done |
|
||||
|
||||
Try `/opsx:new` to start your first change, or `/opsx:ff` if you want to move fast.
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:new <name>` | Start a new change, step by step |
|
||||
| `/opsx:continue <name>` | Continue an existing change |
|
||||
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx:verify <name>` | Verify implementation |
|
||||
|
||||
Try `/opsx:propose` to start your first change.
|
||||
```
|
||||
|
||||
Exit gracefully.
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
---
|
||||
name: openspec-ff-change
|
||||
description: Fast-forward through OpenSpec artifact creation. Use when the user wants to quickly create all artifacts needed for implementation without stepping through each one individually.
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Fast-forward through artifact creation - generate everything needed to start implementation in one go.
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
|
||||
|
||||
@@ -28,7 +37,7 @@ Fast-forward through artifact creation - generate everything needed to start imp
|
||||
```bash
|
||||
openspec new change "<name>"
|
||||
```
|
||||
This creates a scaffolded change at `openspec/changes/<name>/`.
|
||||
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
|
||||
|
||||
3. **Get the artifact build order**
|
||||
```bash
|
||||
@@ -59,7 +68,7 @@ Fast-forward through artifact creation - generate everything needed to start imp
|
||||
- Read any completed dependency files for context
|
||||
- Create the artifact file using `template` as the structure
|
||||
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||
- Show brief progress: "✓ Created <artifact-id>"
|
||||
- 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`
|
||||
@@ -97,5 +106,5 @@ After completing all artifacts, summarize:
|
||||
- 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, suggest continuing that change instead
|
||||
- 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
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Sync delta specs from a change to main specs.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Implement tasks from an OpenSpec change.
|
||||
|
||||
**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")
|
||||
- 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:
|
||||
- Context 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 the files listed in `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
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Archive a completed change in the experimental workflow.
|
||||
|
||||
**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
|
||||
- `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**
|
||||
|
||||
Check for delta specs at `openspec/changes/<name>/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, execute /opsx:sync logic (use the openspec-sync-specs skill). Proceed to archive regardless of choice.
|
||||
|
||||
5. **Perform the archive**
|
||||
|
||||
Create the archive directory if it doesn't exist:
|
||||
```bash
|
||||
mkdir -p openspec/changes/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 the change directory to archive
|
||||
|
||||
```bash
|
||||
mv openspec/changes/<name> openspec/changes/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:** openspec/changes/archive/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
|
||||
@@ -1,246 +0,0 @@
|
||||
---
|
||||
name: openspec-bulk-archive-change
|
||||
description: Archive multiple completed changes at once. Use when archiving several parallel changes.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Archive multiple completed changes in a single operation.
|
||||
|
||||
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
|
||||
|
||||
**Input**: None required (prompts for selection)
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Get active changes**
|
||||
|
||||
Run `openspec list --json` to get all active changes.
|
||||
|
||||
If no active changes exist, inform user and stop.
|
||||
|
||||
2. **Prompt for change selection**
|
||||
|
||||
Use **AskUserQuestion tool** with multi-select to let user choose changes:
|
||||
- Show each change with its schema
|
||||
- Include an option for "All changes"
|
||||
- Allow any number of selections (1+ works, 2+ is the typical use case)
|
||||
|
||||
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
|
||||
|
||||
3. **Batch validation - gather status for all selected changes**
|
||||
|
||||
For each selected change, collect:
|
||||
|
||||
a. **Artifact status** - Run `openspec status --change "<name>" --json`
|
||||
- Parse `schemaName` and `artifacts` list
|
||||
- Note which artifacts are `done` vs other states
|
||||
|
||||
b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
|
||||
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- If no tasks file exists, note as "No tasks"
|
||||
|
||||
c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
|
||||
- List which capability specs exist
|
||||
- For each, extract requirement names (lines matching `### Requirement: <name>`)
|
||||
|
||||
4. **Detect spec conflicts**
|
||||
|
||||
Build a map of `capability -> [changes that touch it]`:
|
||||
|
||||
```
|
||||
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
|
||||
api -> [change-c] <- OK (only 1 change)
|
||||
```
|
||||
|
||||
A conflict exists when 2+ selected changes have delta specs for the same capability.
|
||||
|
||||
5. **Resolve conflicts agentically**
|
||||
|
||||
**For each conflict**, investigate the codebase:
|
||||
|
||||
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
|
||||
|
||||
b. **Search the codebase** for implementation evidence:
|
||||
- Look for code implementing requirements from each delta spec
|
||||
- Check for related files, functions, or tests
|
||||
|
||||
c. **Determine resolution**:
|
||||
- If only one change is actually implemented -> sync that one's specs
|
||||
- If both implemented -> apply in chronological order (older first, newer overwrites)
|
||||
- If neither implemented -> skip spec sync, warn user
|
||||
|
||||
d. **Record resolution** for each conflict:
|
||||
- Which change's specs to apply
|
||||
- In what order (if both)
|
||||
- Rationale (what was found in codebase)
|
||||
|
||||
6. **Show consolidated status table**
|
||||
|
||||
Display a table summarizing all changes:
|
||||
|
||||
```
|
||||
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|
||||
|---------------------|-----------|-------|---------|-----------|--------|
|
||||
| schema-management | Done | 5/5 | 2 delta | None | Ready |
|
||||
| project-config | Done | 3/3 | 1 delta | None | Ready |
|
||||
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
|
||||
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
|
||||
```
|
||||
|
||||
For conflicts, show the resolution:
|
||||
```
|
||||
* Conflict resolution:
|
||||
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
|
||||
```
|
||||
|
||||
For incomplete changes, show warnings:
|
||||
```
|
||||
Warnings:
|
||||
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
|
||||
```
|
||||
|
||||
7. **Confirm batch operation**
|
||||
|
||||
Use **AskUserQuestion tool** with a single confirmation:
|
||||
|
||||
- "Archive N changes?" with options based on status
|
||||
- Options might include:
|
||||
- "Archive all N changes"
|
||||
- "Archive only N ready changes (skip incomplete)"
|
||||
- "Cancel"
|
||||
|
||||
If there are incomplete changes, make clear they'll be archived with warnings.
|
||||
|
||||
8. **Execute archive for each confirmed change**
|
||||
|
||||
Process changes in the determined order (respecting conflict resolution):
|
||||
|
||||
a. **Sync specs** if delta specs exist:
|
||||
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
|
||||
- For conflicts, apply in resolved order
|
||||
- Track if sync was done
|
||||
|
||||
b. **Perform the archive**:
|
||||
```bash
|
||||
mkdir -p openspec/changes/archive
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
```
|
||||
|
||||
c. **Track outcome** for each change:
|
||||
- Success: archived successfully
|
||||
- Failed: error during archive (record error)
|
||||
- Skipped: user chose not to archive (if applicable)
|
||||
|
||||
9. **Display summary**
|
||||
|
||||
Show final results:
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived 3 changes:
|
||||
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
|
||||
- project-config -> archive/2026-01-19-project-config/
|
||||
- add-oauth -> archive/2026-01-19-add-oauth/
|
||||
|
||||
Skipped 1 change:
|
||||
- add-verify-skill (user chose not to archive incomplete)
|
||||
|
||||
Spec sync summary:
|
||||
- 4 delta specs synced to main specs
|
||||
- 1 conflict resolved (auth: applied both in chronological order)
|
||||
```
|
||||
|
||||
If any failures:
|
||||
```
|
||||
Failed 1 change:
|
||||
- some-change: Archive directory already exists
|
||||
```
|
||||
|
||||
**Conflict Resolution Examples**
|
||||
|
||||
Example 1: Only one implemented
|
||||
```
|
||||
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
|
||||
|
||||
Checking add-oauth:
|
||||
- Delta adds "OAuth Provider Integration" requirement
|
||||
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
|
||||
|
||||
Checking add-jwt:
|
||||
- Delta adds "JWT Token Handling" requirement
|
||||
- Searching codebase... no JWT implementation found
|
||||
|
||||
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
|
||||
```
|
||||
|
||||
Example 2: Both implemented
|
||||
```
|
||||
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
|
||||
|
||||
Checking add-rest-api (created 2026-01-10):
|
||||
- Delta adds "REST Endpoints" requirement
|
||||
- Searching codebase... found src/api/rest.ts
|
||||
|
||||
Checking add-graphql (created 2026-01-15):
|
||||
- Delta adds "GraphQL Schema" requirement
|
||||
- Searching codebase... found src/api/graphql.ts
|
||||
|
||||
Resolution: Both implemented. Will apply add-rest-api specs first,
|
||||
then add-graphql specs (chronological order, newer takes precedence).
|
||||
```
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
|
||||
|
||||
Spec sync summary:
|
||||
- N delta specs synced to main specs
|
||||
- No conflicts (or: M conflicts resolved)
|
||||
```
|
||||
|
||||
**Output On Partial Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete (partial)
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
|
||||
Skipped M changes:
|
||||
- <change-2> (user chose not to archive incomplete)
|
||||
|
||||
Failed K changes:
|
||||
- <change-3>: Archive directory already exists
|
||||
```
|
||||
|
||||
**Output When No Changes**
|
||||
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use `/opsx:new` to create a new change.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
- Allow any number of changes (1+ is fine, 2+ is the typical use case)
|
||||
- Always prompt for selection, never auto-select
|
||||
- Detect spec conflicts early and resolve by checking codebase
|
||||
- When both changes are implemented, apply specs in chronological order
|
||||
- Skip spec sync only when implementation is missing (warn user)
|
||||
- Show clear per-change status before confirming
|
||||
- Use single confirmation for entire batch
|
||||
- Track and report all outcomes (success/skip/fail)
|
||||
- Preserve .openspec.yaml when moving to archive
|
||||
- Archive directory target uses current date: YYYY-MM-DD-<name>
|
||||
- If archive target exists, fail that change but continue with others
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
name: openspec-continue-change
|
||||
description: Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Continue working on a change by creating the next artifact.
|
||||
|
||||
**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 work on.
|
||||
|
||||
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 continue.
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check current status**
|
||||
```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
|
||||
|
||||
3. **Act based on status**:
|
||||
|
||||
---
|
||||
|
||||
**If all artifacts are complete (`isComplete: true`)**:
|
||||
- Congratulate the user
|
||||
- Show final status including the schema used
|
||||
- Suggest: "All artifacts created! You can now implement this change or archive it."
|
||||
- STOP
|
||||
|
||||
---
|
||||
|
||||
**If artifacts are ready to create** (status shows artifacts with `status: "ready"`):
|
||||
- Pick the FIRST artifact with `status: "ready"` from the status output
|
||||
- Get its instructions:
|
||||
```bash
|
||||
openspec instructions <artifact-id> --change "<name>" --json
|
||||
```
|
||||
- Parse the JSON. The key fields are:
|
||||
- `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
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- **Create the artifact file**:
|
||||
- Read any completed dependency files for context
|
||||
- Use `template` as the structure - fill in its sections
|
||||
- Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file
|
||||
- Write to the output path specified in instructions
|
||||
- Show what was created and what's now unlocked
|
||||
- STOP after creating ONE artifact
|
||||
|
||||
---
|
||||
|
||||
**If no artifacts are ready (all blocked)**:
|
||||
- This shouldn't happen with a valid schema
|
||||
- Show status and suggest checking for issues
|
||||
|
||||
4. **After creating an artifact, show progress**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After each invocation, show:
|
||||
- Which artifact was created
|
||||
- Schema workflow being used
|
||||
- Current progress (N/M complete)
|
||||
- What artifacts are now unlocked
|
||||
- Prompt: "Want to continue? Just ask me to continue or tell me what to do next."
|
||||
|
||||
**Artifact Creation Guidelines**
|
||||
|
||||
The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create.
|
||||
|
||||
Common artifact patterns:
|
||||
|
||||
**spec-driven schema** (proposal → specs → design → tasks):
|
||||
- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact.
|
||||
- The Capabilities section is critical - each capability listed will need a spec file.
|
||||
- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name).
|
||||
- **design.md**: Document technical decisions, architecture, and implementation approach.
|
||||
- **tasks.md**: Break down implementation into checkboxed tasks.
|
||||
|
||||
For other schemas, follow the `instruction` field from the CLI output.
|
||||
|
||||
**Guardrails**
|
||||
- Create ONE artifact per invocation
|
||||
- Always read dependency artifacts before creating a new one
|
||||
- Never skip artifacts or create out of order
|
||||
- If context is unclear, ask the user before creating
|
||||
- Verify the artifact file exists after writing before marking progress
|
||||
- Use the schema's artifact sequence, don't assume specific artifact names
|
||||
- **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
|
||||
@@ -1,290 +0,0 @@
|
||||
---
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
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 (e.g., start a change with `/opsx:new` or `/opsx:ff`). 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.
|
||||
|
||||
---
|
||||
|
||||
## 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 one?"
|
||||
→ Can transition to `/opsx:new` or `/opsx:ff`
|
||||
- Or keep exploring - no pressure to formalize
|
||||
|
||||
### When a change exists
|
||||
|
||||
If the user mentions a change or you detect one is relevant:
|
||||
|
||||
1. **Read existing artifacts for context**
|
||||
- `openspec/changes/<name>/proposal.md`
|
||||
- `openspec/changes/<name>/design.md`
|
||||
- `openspec/changes/<name>/tasks.md`
|
||||
- etc.
|
||||
|
||||
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 action**: "Ready to start? /opsx:new or /opsx:ff"
|
||||
- **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: /opsx:new <name>
|
||||
- Fast-forward to tasks: /opsx:ff <name>
|
||||
- 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
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
name: openspec-new-change
|
||||
description: Start a new OpenSpec change using the experimental artifact workflow. Use when the user wants to create a new feature, fix, or modification with a structured step-by-step approach.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Start a new change using the experimental artifact-driven approach.
|
||||
|
||||
**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. **Determine the workflow schema**
|
||||
|
||||
Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow.
|
||||
|
||||
**Use a different schema only if the user mentions:**
|
||||
- A specific schema name → use `--schema <name>`
|
||||
- "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose
|
||||
|
||||
**Otherwise**: Omit `--schema` to use the default.
|
||||
|
||||
3. **Create the change directory**
|
||||
```bash
|
||||
openspec new change "<name>"
|
||||
```
|
||||
Add `--schema <name>` only if the user requested a specific workflow.
|
||||
This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema.
|
||||
|
||||
4. **Show the artifact status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
This shows which artifacts need to be created and which are ready (dependencies satisfied).
|
||||
|
||||
5. **Get instructions for the first artifact**
|
||||
The first artifact depends on the schema (e.g., `proposal` for spec-driven).
|
||||
Check the status output to find the first artifact with status "ready".
|
||||
```bash
|
||||
openspec instructions <first-artifact-id> --change "<name>"
|
||||
```
|
||||
This outputs the template and context for creating the first artifact.
|
||||
|
||||
6. **STOP and wait for user direction**
|
||||
|
||||
**Output**
|
||||
|
||||
After completing the steps, summarize:
|
||||
- Change name and location
|
||||
- Schema/workflow being used and its artifact sequence
|
||||
- Current status (0/N artifacts complete)
|
||||
- The template for the first artifact
|
||||
- Prompt: "Ready to create the first artifact? Just describe what this change is about and I'll draft it, or ask me to continue."
|
||||
|
||||
**Guardrails**
|
||||
- Do NOT create any artifacts yet - just show the instructions
|
||||
- Do NOT advance beyond showing the first artifact template
|
||||
- If the name is invalid (not kebab-case), ask for a valid name
|
||||
- If a change with that name already exists, suggest continuing that change instead
|
||||
- Pass --schema if using a non-default workflow
|
||||
@@ -1,529 +0,0 @@
|
||||
---
|
||||
name: openspec-onboard
|
||||
description: Guided onboarding for OpenSpec - walk through a complete workflow cycle with narration and real codebase work.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
|
||||
|
||||
---
|
||||
|
||||
## Preflight
|
||||
|
||||
Before starting, check if OpenSpec is initialized:
|
||||
|
||||
```bash
|
||||
openspec status --json 2>&1 || echo "NOT_INITIALIZED"
|
||||
```
|
||||
|
||||
**If not initialized:**
|
||||
> OpenSpec isn't set up in this project yet. Run `openspec init` first, then come back to `/opsx:onboard`.
|
||||
|
||||
Stop here if not initialized.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Welcome
|
||||
|
||||
Display:
|
||||
|
||||
```
|
||||
## Welcome to OpenSpec!
|
||||
|
||||
I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it.
|
||||
|
||||
**What we'll do:**
|
||||
1. Pick a small, real task in your codebase
|
||||
2. Explore the problem briefly
|
||||
3. Create a change (the container for our work)
|
||||
4. Build the artifacts: proposal → specs → design → tasks
|
||||
5. Implement the tasks
|
||||
6. Archive the completed change
|
||||
|
||||
**Time:** ~15-20 minutes
|
||||
|
||||
Let's start by finding something to work on.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Task Selection
|
||||
|
||||
### Codebase Analysis
|
||||
|
||||
Scan the codebase for small improvement opportunities. Look for:
|
||||
|
||||
1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files
|
||||
2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch
|
||||
3. **Functions without tests** - Cross-reference `src/` with test directories
|
||||
4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`)
|
||||
5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code
|
||||
6. **Missing validation** - User input handlers without validation
|
||||
|
||||
Also check recent git activity:
|
||||
```bash
|
||||
git log --oneline -10 2>/dev/null || echo "No git history"
|
||||
```
|
||||
|
||||
### Present Suggestions
|
||||
|
||||
From your analysis, present 3-4 specific suggestions:
|
||||
|
||||
```
|
||||
## Task Suggestions
|
||||
|
||||
Based on scanning your codebase, here are some good starter tasks:
|
||||
|
||||
**1. [Most promising task]**
|
||||
Location: `src/path/to/file.ts:42`
|
||||
Scope: ~1-2 files, ~20-30 lines
|
||||
Why it's good: [brief reason]
|
||||
|
||||
**2. [Second task]**
|
||||
Location: `src/another/file.ts`
|
||||
Scope: ~1 file, ~15 lines
|
||||
Why it's good: [brief reason]
|
||||
|
||||
**3. [Third task]**
|
||||
Location: [location]
|
||||
Scope: [estimate]
|
||||
Why it's good: [brief reason]
|
||||
|
||||
**4. Something else?**
|
||||
Tell me what you'd like to work on.
|
||||
|
||||
Which task interests you? (Pick a number or describe your own)
|
||||
```
|
||||
|
||||
**If nothing found:** Fall back to asking what the user wants to build:
|
||||
> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix?
|
||||
|
||||
### Scope Guardrail
|
||||
|
||||
If the user picks or describes something too large (major feature, multi-day work):
|
||||
|
||||
```
|
||||
That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through.
|
||||
|
||||
For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details.
|
||||
|
||||
**Options:**
|
||||
1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]?
|
||||
2. **Pick something else** - One of the other suggestions, or a different small task?
|
||||
3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer.
|
||||
|
||||
What would you prefer?
|
||||
```
|
||||
|
||||
Let the user override if they insist—this is a soft guardrail.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Explore Demo
|
||||
|
||||
Once a task is selected, briefly demonstrate explore mode:
|
||||
|
||||
```
|
||||
Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction.
|
||||
```
|
||||
|
||||
Spend 1-2 minutes investigating the relevant code:
|
||||
- Read the file(s) involved
|
||||
- Draw a quick ASCII diagram if it helps
|
||||
- Note any considerations
|
||||
|
||||
```
|
||||
## Quick Exploration
|
||||
|
||||
[Your brief analysis—what you found, any considerations]
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [Optional: ASCII diagram if helpful] │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
Explore mode (`/opsx:explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem.
|
||||
|
||||
Now let's create a change to hold our work.
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user acknowledgment before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Create the Change
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Creating a Change
|
||||
|
||||
A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
|
||||
|
||||
Let me create one for our task.
|
||||
```
|
||||
|
||||
**DO:** Create the change with a derived kebab-case name:
|
||||
```bash
|
||||
openspec new change "<derived-name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Created: `openspec/changes/<name>/`
|
||||
|
||||
The folder structure:
|
||||
```
|
||||
openspec/changes/<name>/
|
||||
├── proposal.md ← Why we're doing this (empty, we'll fill it)
|
||||
├── design.md ← How we'll build it (empty)
|
||||
├── specs/ ← Detailed requirements (empty)
|
||||
└── tasks.md ← Implementation checklist (empty)
|
||||
```
|
||||
|
||||
Now let's fill in the first artifact—the proposal.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Proposal
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## The Proposal
|
||||
|
||||
The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
|
||||
|
||||
I'll draft one based on our task.
|
||||
```
|
||||
|
||||
**DO:** Draft the proposal content (don't save yet):
|
||||
|
||||
```
|
||||
Here's a draft proposal:
|
||||
|
||||
---
|
||||
|
||||
## Why
|
||||
|
||||
[1-2 sentences explaining the problem/opportunity]
|
||||
|
||||
## What Changes
|
||||
|
||||
[Bullet points of what will be different]
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `<capability-name>`: [brief description]
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- If modifying existing behavior -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/path/to/file.ts`: [what changes]
|
||||
- [other files if applicable]
|
||||
|
||||
---
|
||||
|
||||
Does this capture the intent? I can adjust before we save it.
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user approval/feedback.
|
||||
|
||||
After approval, save the proposal:
|
||||
```bash
|
||||
openspec instructions proposal --change "<name>" --json
|
||||
```
|
||||
Then write the content to `openspec/changes/<name>/proposal.md`.
|
||||
|
||||
```
|
||||
Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
|
||||
|
||||
Next up: specs.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Specs
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Specs
|
||||
|
||||
Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
|
||||
|
||||
For a small task like this, we might only need one spec file.
|
||||
```
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
|
||||
```
|
||||
Here's the spec:
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: <Name>
|
||||
|
||||
<Description of what the system should do>
|
||||
|
||||
#### Scenario: <Scenario name>
|
||||
|
||||
- **WHEN** <trigger condition>
|
||||
- **THEN** <expected outcome>
|
||||
- **AND** <additional outcome if needed>
|
||||
|
||||
---
|
||||
|
||||
This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Design
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Design
|
||||
|
||||
The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
|
||||
|
||||
For small changes, this might be brief. That's fine—not every change needs deep design discussion.
|
||||
```
|
||||
|
||||
**DO:** Draft design.md:
|
||||
|
||||
```
|
||||
Here's the design:
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
[Brief context about the current state]
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- [What we're trying to achieve]
|
||||
|
||||
**Non-Goals:**
|
||||
- [What's explicitly out of scope]
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: [Key decision]
|
||||
|
||||
[Explanation of approach and rationale]
|
||||
|
||||
---
|
||||
|
||||
For a small task, this captures the key decisions without over-engineering.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Tasks
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Tasks
|
||||
|
||||
Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
|
||||
|
||||
These should be small, clear, and in logical order.
|
||||
```
|
||||
|
||||
**DO:** Generate tasks based on specs and design:
|
||||
|
||||
```
|
||||
Here are the implementation tasks:
|
||||
|
||||
---
|
||||
|
||||
## 1. [Category or file]
|
||||
|
||||
- [ ] 1.1 [Specific task]
|
||||
- [ ] 1.2 [Specific task]
|
||||
|
||||
## 2. Verify
|
||||
|
||||
- [ ] 2.1 [Verification step]
|
||||
|
||||
---
|
||||
|
||||
Each checkbox becomes a unit of work in the apply phase. Ready to implement?
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user to confirm they're ready to implement.
|
||||
|
||||
Save to `openspec/changes/<name>/tasks.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Apply (Implementation)
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Implementation
|
||||
|
||||
Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
|
||||
```
|
||||
|
||||
**DO:** For each task:
|
||||
|
||||
1. Announce: "Working on task N: [description]"
|
||||
2. Implement the change in the codebase
|
||||
3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
|
||||
4. Mark complete in tasks.md: `- [ ]` → `- [x]`
|
||||
5. Brief status: "✓ Task N complete"
|
||||
|
||||
Keep narration light—don't over-explain every line of code.
|
||||
|
||||
After all tasks:
|
||||
|
||||
```
|
||||
## Implementation Complete
|
||||
|
||||
All tasks done:
|
||||
- [x] Task 1
|
||||
- [x] Task 2
|
||||
- [x] ...
|
||||
|
||||
The change is implemented! One more step—let's archive it.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Archive
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Archiving
|
||||
|
||||
When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
|
||||
|
||||
Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
|
||||
```
|
||||
|
||||
**DO:**
|
||||
```bash
|
||||
openspec archive "<name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
|
||||
|
||||
The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 11: Recap & Next Steps
|
||||
|
||||
```
|
||||
## Congratulations!
|
||||
|
||||
You just completed a full OpenSpec cycle:
|
||||
|
||||
1. **Explore** - Thought through the problem
|
||||
2. **New** - Created a change container
|
||||
3. **Proposal** - Captured WHY
|
||||
4. **Specs** - Defined WHAT in detail
|
||||
5. **Design** - Decided HOW
|
||||
6. **Tasks** - Broke it into steps
|
||||
7. **Apply** - Implemented the work
|
||||
8. **Archive** - Preserved the record
|
||||
|
||||
This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
---
|
||||
|
||||
## Command Reference
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:explore` | Think through problems before/during work |
|
||||
| `/opsx:new` | Start a new change, step through artifacts |
|
||||
| `/opsx:ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx:continue` | Continue working on an existing change |
|
||||
| `/opsx:apply` | Implement tasks from a change |
|
||||
| `/opsx:verify` | Verify implementation matches artifacts |
|
||||
| `/opsx:archive` | Archive a completed change |
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Try `/opsx:new` or `/opsx:ff` on something you actually want to build. You've got the rhythm now!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Graceful Exit Handling
|
||||
|
||||
### User wants to stop mid-way
|
||||
|
||||
If the user says they need to stop, want to pause, or seem disengaged:
|
||||
|
||||
```
|
||||
No problem! Your change is saved at `openspec/changes/<name>/`.
|
||||
|
||||
To pick up where we left off later:
|
||||
- `/opsx:continue <name>` - Resume artifact creation
|
||||
- `/opsx:apply <name>` - Jump to implementation (if tasks exist)
|
||||
|
||||
The work won't be lost. Come back whenever you're ready.
|
||||
```
|
||||
|
||||
Exit gracefully without pressure.
|
||||
|
||||
### User just wants command reference
|
||||
|
||||
If the user says they just want to see the commands or skip the tutorial:
|
||||
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:explore` | Think through problems (no code changes) |
|
||||
| `/opsx:new <name>` | Start a new change, step by step |
|
||||
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx:continue <name>` | Continue an existing change |
|
||||
| `/opsx:apply <name>` | Implement tasks |
|
||||
| `/opsx:verify <name>` | Verify implementation |
|
||||
| `/opsx:archive <name>` | Archive when done |
|
||||
|
||||
Try `/opsx:new` to start your first change, or `/opsx:ff` if you want to move fast.
|
||||
```
|
||||
|
||||
Exit gracefully.
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive)
|
||||
- **Keep narration light** during implementation—teach without lecturing
|
||||
- **Don't skip phases** even if the change is small—the goal is teaching the workflow
|
||||
- **Pause for acknowledgment** at marked points, but don't over-pause
|
||||
- **Handle exits gracefully**—never pressure the user to continue
|
||||
- **Use real codebase tasks**—don't simulate or use fake examples
|
||||
- **Adjust scope gently**—guide toward smaller tasks but respect user choice
|
||||
@@ -1,138 +0,0 @@
|
||||
---
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
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).
|
||||
|
||||
**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. **Find delta specs**
|
||||
|
||||
Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`.
|
||||
|
||||
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.
|
||||
|
||||
3. **For each delta spec, apply changes to main specs**
|
||||
|
||||
For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`:
|
||||
|
||||
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
|
||||
|
||||
4. **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
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
name: openspec-verify-change
|
||||
description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
---
|
||||
|
||||
Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
**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 implementation tasks (tasks artifact exists).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
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")
|
||||
- Which artifacts exist for this change
|
||||
|
||||
3. **Get the change directory and load artifacts**
|
||||
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
```
|
||||
|
||||
This returns the change directory and context files. Read all available artifacts from `contextFiles`.
|
||||
|
||||
4. **Initialize verification report structure**
|
||||
|
||||
Create a report structure with three dimensions:
|
||||
- **Completeness**: Track tasks and spec coverage
|
||||
- **Correctness**: Track requirement implementation and scenario coverage
|
||||
- **Coherence**: Track design adherence and pattern consistency
|
||||
|
||||
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
||||
|
||||
5. **Verify Completeness**
|
||||
|
||||
**Task Completion**:
|
||||
- If tasks.md exists in contextFiles, read it
|
||||
- Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- Count complete vs total tasks
|
||||
- If incomplete tasks exist:
|
||||
- Add CRITICAL issue for each incomplete task
|
||||
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
||||
|
||||
**Spec Coverage**:
|
||||
- If delta specs exist in `openspec/changes/<name>/specs/`:
|
||||
- Extract all requirements (marked with "### Requirement:")
|
||||
- For each requirement:
|
||||
- Search codebase for keywords related to the requirement
|
||||
- Assess if implementation likely exists
|
||||
- If requirements appear unimplemented:
|
||||
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
||||
- Recommendation: "Implement requirement X: <description>"
|
||||
|
||||
6. **Verify Correctness**
|
||||
|
||||
**Requirement Implementation Mapping**:
|
||||
- For each requirement from delta specs:
|
||||
- Search codebase for implementation evidence
|
||||
- If found, note file paths and line ranges
|
||||
- Assess if implementation matches requirement intent
|
||||
- If divergence detected:
|
||||
- Add WARNING: "Implementation may diverge from spec: <details>"
|
||||
- Recommendation: "Review <file>:<lines> against requirement X"
|
||||
|
||||
**Scenario Coverage**:
|
||||
- For each scenario in delta specs (marked with "#### Scenario:"):
|
||||
- Check if conditions are handled in code
|
||||
- Check if tests exist covering the scenario
|
||||
- If scenario appears uncovered:
|
||||
- Add WARNING: "Scenario not covered: <scenario name>"
|
||||
- Recommendation: "Add test or implementation for scenario: <description>"
|
||||
|
||||
7. **Verify Coherence**
|
||||
|
||||
**Design Adherence**:
|
||||
- If design.md exists in contextFiles:
|
||||
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
||||
- Verify implementation follows those decisions
|
||||
- If contradiction detected:
|
||||
- Add WARNING: "Design decision not followed: <decision>"
|
||||
- Recommendation: "Update implementation or revise design.md to match reality"
|
||||
- If no design.md: Skip design adherence check, note "No design.md to verify against"
|
||||
|
||||
**Code Pattern Consistency**:
|
||||
- Review new code for consistency with project patterns
|
||||
- Check file naming, directory structure, coding style
|
||||
- If significant deviations found:
|
||||
- Add SUGGESTION: "Code pattern deviation: <details>"
|
||||
- Recommendation: "Consider following project pattern: <example>"
|
||||
|
||||
8. **Generate Verification Report**
|
||||
|
||||
**Summary Scorecard**:
|
||||
```
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|------------------|
|
||||
| Completeness | X/Y tasks, N reqs|
|
||||
| Correctness | M/N reqs covered |
|
||||
| Coherence | Followed/Issues |
|
||||
```
|
||||
|
||||
**Issues by Priority**:
|
||||
|
||||
1. **CRITICAL** (Must fix before archive):
|
||||
- Incomplete tasks
|
||||
- Missing requirement implementations
|
||||
- Each with specific, actionable recommendation
|
||||
|
||||
2. **WARNING** (Should fix):
|
||||
- Spec/design divergences
|
||||
- Missing scenario coverage
|
||||
- Each with specific recommendation
|
||||
|
||||
3. **SUGGESTION** (Nice to fix):
|
||||
- Pattern inconsistencies
|
||||
- Minor improvements
|
||||
- Each with specific recommendation
|
||||
|
||||
**Final Assessment**:
|
||||
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
||||
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
||||
- If all clear: "All checks passed. Ready for archive."
|
||||
|
||||
**Verification Heuristics**
|
||||
|
||||
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
|
||||
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
|
||||
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
|
||||
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
||||
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
||||
|
||||
**Graceful Degradation**
|
||||
|
||||
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
||||
- If tasks + specs exist: verify completeness and correctness, skip design
|
||||
- If full artifacts: verify all three dimensions
|
||||
- Always note which checks were skipped and why
|
||||
|
||||
**Output Format**
|
||||
|
||||
Use clear markdown with:
|
||||
- Table for summary scorecard
|
||||
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
|
||||
- Code references in format: `file.ts:123`
|
||||
- Specific, actionable recommendations
|
||||
- No vague suggestions like "consider reviewing"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -41,3 +41,5 @@ $RECYCLE.BIN/
|
||||
*.swo
|
||||
|
||||
skills/**/test
|
||||
__pycache__
|
||||
temp
|
||||
@@ -56,7 +56,7 @@ Archive a completed change in the experimental workflow.
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
If user chooses sync, execute `/opsx-sync` logic. Proceed to archive regardless of choice.
|
||||
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**
|
||||
|
||||
@@ -150,5 +150,5 @@ Target archive directory already exists.
|
||||
- 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 /opsx-sync approach (agent-driven)
|
||||
- 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
|
||||
|
||||
@@ -222,7 +222,7 @@ Failed K changes:
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use `/opsx-new` to create a new change.
|
||||
No active changes found. Create a new change to get started.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Enter explore mode - think through ideas, investigate problems, cla
|
||||
|
||||
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 (e.g., start a change with `/opsx-new` or `/opsx-ff`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
**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.
|
||||
|
||||
@@ -97,8 +97,7 @@ If the user mentioned a specific change name, read its artifacts for context.
|
||||
|
||||
Think freely. When insights crystallize, you might offer:
|
||||
|
||||
- "This feels solid enough to start a change. Want me to create one?"
|
||||
→ Can transition to `/opsx-new` or `/opsx-ff`
|
||||
- "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
|
||||
@@ -150,7 +149,7 @@ If the user mentions a change or you detect one is relevant:
|
||||
|
||||
There's no required ending. Discovery might:
|
||||
|
||||
- **Flow into action**: "Ready to start? `/opsx-new` or `/opsx-ff`"
|
||||
- **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"
|
||||
|
||||
@@ -81,7 +81,10 @@ After completing all artifacts, summarize:
|
||||
- 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 the `template` as a starting point, filling in based on context
|
||||
- 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`)
|
||||
|
||||
@@ -8,16 +8,19 @@ Guide the user through their first complete OpenSpec workflow cycle. This is a t
|
||||
|
||||
## Preflight
|
||||
|
||||
Before starting, check if OpenSpec is initialized:
|
||||
Before starting, check if the OpenSpec CLI is installed:
|
||||
|
||||
```bash
|
||||
openspec status --json 2>&1 || echo "NOT_INITIALIZED"
|
||||
# Unix/macOS
|
||||
openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
|
||||
# Windows (PowerShell)
|
||||
# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
|
||||
```
|
||||
|
||||
**If not initialized:**
|
||||
> OpenSpec isn't set up in this project yet. Run `openspec init` first, then come back to `/opsx-onboard`.
|
||||
**If CLI not installed:**
|
||||
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx-onboard`.
|
||||
|
||||
Stop here if not initialized.
|
||||
Stop here if not installed.
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +63,10 @@ Scan the codebase for small improvement opportunities. Look for:
|
||||
|
||||
Also check recent git activity:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
git log --oneline -10 2>/dev/null || echo "No git history"
|
||||
# Windows (PowerShell)
|
||||
# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
|
||||
```
|
||||
|
||||
### Present Suggestions
|
||||
@@ -255,7 +261,10 @@ For a small task like this, we might only need one spec file.
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
@@ -450,21 +459,29 @@ This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
## Command Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-propose` | Create a change and generate all artifacts |
|
||||
| `/opsx-explore` | Think through problems before/during work |
|
||||
| `/opsx-new` | Start a new change, step through artifacts |
|
||||
| `/opsx-ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx-continue` | Continue working on an existing change |
|
||||
| `/opsx-apply` | Implement tasks from a change |
|
||||
| `/opsx-verify` | Verify implementation matches artifacts |
|
||||
| `/opsx-archive` | Archive a completed change |
|
||||
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-new` | Start a new change, step through artifacts one at a time |
|
||||
| `/opsx-continue` | Continue working on an existing change |
|
||||
| `/opsx-ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx-verify` | Verify implementation matches artifacts |
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Try `/opsx-new` or `/opsx-ff` on something you actually want to build. You've got the rhythm now!
|
||||
Try `/opsx-propose` on something you actually want to build. You've got the rhythm now!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -494,17 +511,25 @@ If the user says they just want to see the commands or skip the tutorial:
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-propose <name>` | Create a change and generate all artifacts |
|
||||
| `/opsx-explore` | Think through problems (no code changes) |
|
||||
| `/opsx-new <name>` | Start a new change, step by step |
|
||||
| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx-continue <name>` | Continue an existing change |
|
||||
| `/opsx-apply <name>` | Implement tasks |
|
||||
| `/opsx-verify <name>` | Verify implementation |
|
||||
| `/opsx-archive <name>` | Archive when done |
|
||||
|
||||
Try `/opsx-new` to start your first change, or `/opsx-ff` if you want to move fast.
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-new <name>` | Start a new change, step by step |
|
||||
| `/opsx-continue <name>` | Continue an existing change |
|
||||
| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx-verify <name>` | Verify implementation |
|
||||
|
||||
Try `/opsx-propose` to start your first change.
|
||||
```
|
||||
|
||||
Exit gracefully.
|
||||
|
||||
103
.opencode/command/opsx-propose.md
Normal file
103
.opencode/command/opsx-propose.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
**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 at `openspec/changes/<name>/` 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
|
||||
|
||||
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
|
||||
- `outputPath`: Where 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
|
||||
- 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
|
||||
1
.opencode/skills/lyxy-reader-office
Symbolic link
1
.opencode/skills/lyxy-reader-office
Symbolic link
@@ -0,0 +1 @@
|
||||
../../skills/lyxy-reader-office
|
||||
1
.opencode/skills/lyxy-runner-js
Symbolic link
1
.opencode/skills/lyxy-runner-js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../skills/lyxy-runner-js
|
||||
1
.opencode/skills/lyxy-runner-python
Symbolic link
1
.opencode/skills/lyxy-runner-python
Symbolic link
@@ -0,0 +1 @@
|
||||
../../skills/lyxy-runner-python
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Implement tasks from an OpenSpec change.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Archive a completed change in the experimental workflow.
|
||||
@@ -63,7 +63,7 @@ Archive a completed change in the experimental workflow.
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
If user chooses sync, execute /opsx-sync logic (use the openspec-sync-specs skill). Proceed to archive regardless of choice.
|
||||
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**
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Archive multiple completed changes in a single operation.
|
||||
@@ -229,7 +229,7 @@ Failed K changes:
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use `/opsx-new` to create a new change.
|
||||
No active changes found. Create a new change to get started.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Continue working on a change by creating the next artifact.
|
||||
|
||||
@@ -6,12 +6,12 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.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 (e.g., start a change with `/opsx-new` or `/opsx-ff`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
**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.
|
||||
|
||||
@@ -95,8 +95,7 @@ This tells you:
|
||||
|
||||
Think freely. When insights crystallize, you might offer:
|
||||
|
||||
- "This feels solid enough to start a change. Want me to create one?"
|
||||
→ Can transition to `/opsx-new` or `/opsx-ff`
|
||||
- "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
|
||||
@@ -252,7 +251,7 @@ You: That changes everything.
|
||||
|
||||
There's no required ending. Discovery might:
|
||||
|
||||
- **Flow into action**: "Ready to start? /opsx-new or /opsx-ff"
|
||||
- **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"
|
||||
@@ -269,8 +268,7 @@ When it feels like things are crystallizing, you might summarize:
|
||||
**Open questions**: [if any remain]
|
||||
|
||||
**Next steps** (if ready):
|
||||
- Create a change: /opsx-new <name>
|
||||
- Fast-forward to tasks: /opsx-ff <name>
|
||||
- Create a change proposal
|
||||
- Keep exploring: just keep talking
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Fast-forward through artifact creation - generate everything needed to start implementation in one go.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Start a new change using the experimental artifact-driven approach.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
|
||||
@@ -15,16 +15,19 @@ Guide the user through their first complete OpenSpec workflow cycle. This is a t
|
||||
|
||||
## Preflight
|
||||
|
||||
Before starting, check if OpenSpec is initialized:
|
||||
Before starting, check if the OpenSpec CLI is installed:
|
||||
|
||||
```bash
|
||||
openspec status --json 2>&1 || echo "NOT_INITIALIZED"
|
||||
# Unix/macOS
|
||||
openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
|
||||
# Windows (PowerShell)
|
||||
# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
|
||||
```
|
||||
|
||||
**If not initialized:**
|
||||
> OpenSpec isn't set up in this project yet. Run `openspec init` first, then come back to `/opsx-onboard`.
|
||||
**If CLI not installed:**
|
||||
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx-onboard`.
|
||||
|
||||
Stop here if not initialized.
|
||||
Stop here if not installed.
|
||||
|
||||
---
|
||||
|
||||
@@ -67,7 +70,10 @@ Scan the codebase for small improvement opportunities. Look for:
|
||||
|
||||
Also check recent git activity:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
git log --oneline -10 2>/dev/null || echo "No git history"
|
||||
# Windows (PowerShell)
|
||||
# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
|
||||
```
|
||||
|
||||
### Present Suggestions
|
||||
@@ -262,7 +268,10 @@ For a small task like this, we might only need one spec file.
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
@@ -457,21 +466,29 @@ This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
## Command Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-propose` | Create a change and generate all artifacts |
|
||||
| `/opsx-explore` | Think through problems before/during work |
|
||||
| `/opsx-new` | Start a new change, step through artifacts |
|
||||
| `/opsx-ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx-continue` | Continue working on an existing change |
|
||||
| `/opsx-apply` | Implement tasks from a change |
|
||||
| `/opsx-verify` | Verify implementation matches artifacts |
|
||||
| `/opsx-archive` | Archive a completed change |
|
||||
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-new` | Start a new change, step through artifacts one at a time |
|
||||
| `/opsx-continue` | Continue working on an existing change |
|
||||
| `/opsx-ff` | Fast-forward: create all artifacts at once |
|
||||
| `/opsx-verify` | Verify implementation matches artifacts |
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Try `/opsx-new` or `/opsx-ff` on something you actually want to build. You've got the rhythm now!
|
||||
Try `/opsx-propose` on something you actually want to build. You've got the rhythm now!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -501,17 +518,25 @@ If the user says they just want to see the commands or skip the tutorial:
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**Core workflow:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-propose <name>` | Create a change and generate all artifacts |
|
||||
| `/opsx-explore` | Think through problems (no code changes) |
|
||||
| `/opsx-new <name>` | Start a new change, step by step |
|
||||
| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx-continue <name>` | Continue an existing change |
|
||||
| `/opsx-apply <name>` | Implement tasks |
|
||||
| `/opsx-verify <name>` | Verify implementation |
|
||||
| `/opsx-archive <name>` | Archive when done |
|
||||
|
||||
Try `/opsx-new` to start your first change, or `/opsx-ff` if you want to move fast.
|
||||
**Additional commands:**
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx-new <name>` | Start a new change, step by step |
|
||||
| `/opsx-continue <name>` | Continue an existing change |
|
||||
| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
|
||||
| `/opsx-verify <name>` | Verify implementation |
|
||||
|
||||
Try `/opsx-propose` to start your first change.
|
||||
```
|
||||
|
||||
Exit gracefully.
|
||||
|
||||
110
.opencode/skills/openspec-propose/SKILL.md
Normal file
110
.opencode/skills/openspec-propose/SKILL.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
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.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.2.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
|
||||
|
||||
---
|
||||
|
||||
**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 at `openspec/changes/<name>/` 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
|
||||
|
||||
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
|
||||
- `outputPath`: Where 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
|
||||
- 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
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Sync delta specs from a change to main specs.
|
||||
|
||||
@@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.1.1"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
903
document/the-complete-guide-to-building-skill.md
Normal file
903
document/the-complete-guide-to-building-skill.md
Normal file
@@ -0,0 +1,903 @@
|
||||
# Claude 技能构建完全指南
|
||||
|
||||
## 引言
|
||||
|
||||
[技能(Skill)](https://claude.com/blog/skills)是一组指令——以简单文件夹的形式打包——用于教会 Claude 如何处理特定任务或工作流程。技能是自定义 Claude 以满足特定需求的最强大方式之一。它让你无需在每次对话中反复解释你的偏好、流程和领域专业知识,而是只需教会 Claude 一次,便可持续受益。
|
||||
|
||||
当你有可重复的工作流程时,技能尤其强大:根据规格生成前端设计、使用一致的方法论进行研究、创建符合团队风格指南的文档,或者编排多步骤流程。它们与 Claude 的内置功能(如代码执行和文档创建)配合良好。对于正在构建 MCP 集成的开发者来说,技能增加了另一个强大的层次,帮助将原始工具访问转化为可靠、优化的工作流程。
|
||||
|
||||
本指南涵盖了构建有效技能所需的一切——从规划、结构到测试和分发。无论你是为自己、团队还是社区构建技能,你都能在全文中找到实用的模式和真实案例。
|
||||
|
||||
### 你将学到:
|
||||
|
||||
- 技能结构的技术要求和最佳实践
|
||||
- 独立技能和 MCP 增强工作流程的模式
|
||||
- 我们在不同用例中观察到的有效模式
|
||||
- 如何测试、迭代和分发你的技能
|
||||
|
||||
### 适用人群:
|
||||
|
||||
- 希望 Claude 一致地遵循特定工作流程的开发者
|
||||
- 希望 Claude 遵循特定工作流程的高级用户
|
||||
- 寻求在整个组织中标准化 Claude 使用方式的团队
|
||||
|
||||
### 本指南的两条路径
|
||||
|
||||
构建独立技能?重点关注"基础知识"、"规划与设计"以及第 1-2 类。增强 MCP 集成?"技能 + MCP"部分和第 3 类适合你。两条路径共享相同的技术要求,但你可以选择与你的用例相关的内容。
|
||||
|
||||
**本指南的收获**:阅读完本指南后,你将能够在一次工作时段内构建一个功能完整的技能。使用 skill-creator 构建和测试你的第一个可用技能大约需要 15-30 分钟。
|
||||
|
||||
让我们开始吧。
|
||||
|
||||
# 第 1 章:基础知识
|
||||
|
||||
## 什么是技能?
|
||||
|
||||
技能是一个包含以下内容的文件夹:
|
||||
|
||||
- **SKILL.md(必需)**:带有 YAML 前置元数据的 Markdown 格式指令
|
||||
- **scripts/(可选)**:可执行代码(Python、Bash 等)
|
||||
- **references/(可选)**:按需加载的文档
|
||||
- **assets/(可选)**:输出中使用的模板、字体、图标
|
||||
|
||||
## 核心设计原则
|
||||
|
||||
### 渐进式披露
|
||||
|
||||
技能采用三级系统:
|
||||
|
||||
- **第一级(YAML 前置元数据)**:始终加载到 Claude 的系统提示中。仅提供足够的信息让 Claude 知道何时应该使用每个技能,而无需将所有内容加载到上下文中。
|
||||
- **第二级(SKILL.md 正文)**:当 Claude 认为该技能与当前任务相关时加载。包含完整的指令和指南。
|
||||
- **第三级(关联文件)**:技能目录中捆绑的附加文件,Claude 可以根据需要选择导航和发现。
|
||||
|
||||
这种渐进式披露在保持专业能力的同时最大限度地减少了 token 使用。
|
||||
|
||||
### 可组合性
|
||||
|
||||
Claude 可以同时加载多个技能。你的技能应该能够与其他技能良好协作,而不是假设它是唯一可用的能力。
|
||||
|
||||
### 可移植性
|
||||
|
||||
技能在 Claude.ai、Claude Code 和 API 中的工作方式完全相同。只需创建一次技能,它就可以在所有平台上无需修改地运行,前提是环境支持技能所需的任何依赖项。
|
||||
|
||||
## 面向 MCP 构建者:技能 + 连接器
|
||||
|
||||
_💡 构建不含 MCP 的独立技能?跳至"规划与设计"——你随时可以回来查阅此部分。_
|
||||
|
||||
如果你已经有一个[可用的 MCP 服务器](https://support.claude.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop),那么你已经完成了最困难的部分。技能是其上的知识层——捕获你已经了解的工作流程和最佳实践,以便 Claude 能够一致地应用它们。
|
||||
|
||||
### 厨房比喻
|
||||
|
||||
**MCP 提供专业厨房**:访问工具、食材和设备。
|
||||
|
||||
**技能提供食谱**:关于如何创造有价值产出的分步指令。
|
||||
|
||||
两者结合,使用户能够完成复杂任务,而无需自己弄清楚每一步。
|
||||
|
||||
它们如何协同工作:
|
||||
|
||||
| MCP(连接性) | 技能(知识) |
|
||||
| ---------------------------------------------------------- | ---------------------------- |
|
||||
| (Notion、Asana、Linear 等)有效地将 Claude 连接到你的服务 | 教会 Claude 如何使用你的服务 |
|
||||
| 提供实时数据访问和工具调用 | 捕获工作流程和最佳实践 |
|
||||
| Claude 能做什么 | Claude 应该怎么做 |
|
||||
|
||||
### 这对你的 MCP 用户为何重要
|
||||
|
||||
#### 没有技能时:
|
||||
|
||||
- 用户连接了你的 MCP 但不知道下一步该做什么
|
||||
- 支持工单询问"如何用你的集成做 X"
|
||||
- 每次对话都从零开始
|
||||
- 结果不一致,因为用户每次的提示方式不同
|
||||
- 当真正的问题是工作流程指导时,用户却责怪你的连接器
|
||||
|
||||
#### 有技能时:
|
||||
|
||||
- 预构建的工作流程在需要时自动激活
|
||||
- 一致、可靠的工具使用
|
||||
- 最佳实践嵌入到每次交互中
|
||||
- 降低你的集成的学习曲线
|
||||
|
||||
# 第 2 章:规划与设计
|
||||
|
||||
## 从用例开始
|
||||
|
||||
在编写任何代码之前,确定 2-3 个你的技能应该支持的具体用例。
|
||||
|
||||
### 良好的用例定义:
|
||||
|
||||
```
|
||||
用例:项目冲刺计划
|
||||
触发器:用户说"帮我规划这个冲刺"或"创建冲刺任务"
|
||||
步骤:
|
||||
1. 从 Linear 获取当前项目状态(通过 MCP)
|
||||
2. 分析团队速度和产能
|
||||
3. 建议任务优先级
|
||||
4. 在 Linear 中创建带有适当标签和估算的任务
|
||||
结果:冲刺完全规划完成,任务已创建
|
||||
```
|
||||
|
||||
### 问问自己:
|
||||
|
||||
- 用户想要完成什么?
|
||||
- 这需要哪些多步骤工作流程?
|
||||
- 需要哪些工具(内置的还是 MCP 的)?
|
||||
- 应该嵌入哪些领域知识或最佳实践?
|
||||
|
||||
## 常见技能用例类别
|
||||
|
||||
在 Anthropic,我们观察到三种常见用例:
|
||||
|
||||
### 类别 1:文档与资产创建
|
||||
|
||||
用途:创建一致、高质量的输出,包括文档、演示文稿、应用程序、设计、代码等。
|
||||
|
||||
_真实案例_:[frontend-design 技能](https://github.com/anthropics/skills/tree/main/skills/frontend-design)(另见 [docx、pptx、xlsx 和 ppt 技能](https://github.com/anthropics/skills/tree/main/skills))
|
||||
|
||||
"创建独特的、生产级的前端界面,具有高设计质量。在构建 Web 组件、页面、产物、海报或应用程序时使用。"
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 嵌入式风格指南和品牌标准
|
||||
- 用于一致输出的模板结构
|
||||
- 最终确定前的质量检查清单
|
||||
- 无需外部工具——使用 Claude 的内置功能
|
||||
|
||||
### 类别 2:工作流程自动化
|
||||
|
||||
用途:受益于一致方法论的多步骤流程,包括跨多个 MCP 服务器的协调。
|
||||
|
||||
_真实案例_:[skill-creator 技能](https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md)
|
||||
|
||||
"用于创建新技能的交互式指南。引导用户完成用例定义、前置元数据生成、指令编写和验证。"
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 带有验证关卡的分步工作流程
|
||||
- 常见结构的模板
|
||||
- 内置的审查和改进建议
|
||||
- 迭代优化循环
|
||||
|
||||
### 类别 3:MCP 增强
|
||||
|
||||
用途:增强 MCP 服务器提供的工具访问的工作流程指导。
|
||||
|
||||
_真实案例_:[sentry-code-review 技能(来自 Sentry)](https://github.com/getsentry/sentry-for-claude/tree/main/skills)
|
||||
|
||||
"使用 Sentry 通过其 MCP 服务器提供的错误监控数据,自动分析和修复 GitHub Pull Request 中检测到的 bug。"
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 按顺序协调多个 MCP 调用
|
||||
- 嵌入领域专业知识
|
||||
- 提供用户原本需要指定的上下文
|
||||
- 常见 MCP 问题的错误处理
|
||||
|
||||
## 定义成功标准
|
||||
|
||||
### 你如何知道你的技能正在工作?
|
||||
|
||||
这些是理想目标——粗略的基准而非精确的阈值。追求严谨,但接受会有一定程度的主观评估成分。我们正在积极开发更强大的衡量指导和工具。
|
||||
|
||||
### 定量指标:
|
||||
|
||||
- 技能在 90% 的相关查询上触发
|
||||
- 如何衡量:运行 10-20 个应该触发你技能的测试查询。跟踪它自动加载的次数与需要显式调用的次数。
|
||||
- 在 X 次工具调用内完成工作流程
|
||||
- 如何衡量:比较启用和未启用技能时执行相同任务的情况。计算工具调用次数和消耗的总 token 数。
|
||||
- 每个工作流程 0 次失败的 API 调用
|
||||
- 如何衡量:在测试运行期间监控 MCP 服务器日志。跟踪重试率和错误代码。
|
||||
|
||||
### 定性指标:
|
||||
|
||||
- 用户不需要提示 Claude 下一步
|
||||
- 如何评估:在测试期间,记录你需要重定向或澄清的频率。征求 beta 用户的反馈。
|
||||
- 工作流程完成时无需用户纠正
|
||||
- 如何评估:运行相同的请求 3-5 次。比较输出的结构一致性和质量。
|
||||
- 跨会话的一致结果
|
||||
- 如何评估:新用户能否在第一次尝试时以最少的指导完成任务?
|
||||
|
||||
## 技术要求
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
your-skill-name/
|
||||
├── SKILL.md # 必需 - 主技能文件
|
||||
├── scripts/ # 可选 - 可执行代码
|
||||
│ ├── process_data.py # 示例
|
||||
│ └── validate.sh # 示例
|
||||
├── references/ # 可选 - 文档
|
||||
│ ├── api-guide.md # 示例
|
||||
│ └── examples/ # 示例
|
||||
└── assets/ # 可选 - 模板等
|
||||
└── report-template.md # 示例
|
||||
```
|
||||
|
||||
### 关键规则
|
||||
|
||||
#### SKILL.md 命名:
|
||||
|
||||
- 必须完全是 SKILL.md(区分大小写)
|
||||
- 不接受任何变体(skill.md、Skill.md 等)
|
||||
|
||||
#### 技能文件夹命名:
|
||||
|
||||
- 使用 kebab-case:notion-project-setup ✅
|
||||
- 不要有空格:Notion Project Setup ❌
|
||||
- 不要有下划线:notion_project_setup ❌
|
||||
- 不要有大写字母:NotionProjectSetup ❌
|
||||
|
||||
#### 不要包含 README.md:
|
||||
|
||||
- 不要在技能文件夹内包含 README.md
|
||||
- 所有文档都放在 SKILL.md 或 references/ 中
|
||||
- 注意:通过 GitHub 分发时,你仍然需要一个仓库级别的 README 供人类用户阅读——参见"分发与共享"。
|
||||
|
||||
### YAML 前置元数据:最重要的部分
|
||||
|
||||
YAML 前置元数据是 Claude 决定是否加载你的技能的依据。务必做好这一点。
|
||||
|
||||
#### 最小必需格式
|
||||
|
||||
```
|
||||
name: your-skill-name
|
||||
description: 它做什么。当用户要求 [特定短语] 时使用。
|
||||
```
|
||||
|
||||
这就是你开始所需的全部内容。
|
||||
|
||||
### 字段要求
|
||||
|
||||
**name**(必需):
|
||||
|
||||
- 仅限 kebab-case
|
||||
- 不能有空格或大写字母
|
||||
- 应与文件夹名称匹配
|
||||
|
||||
**description**(必需):
|
||||
|
||||
- **必须同时包含**:
|
||||
- 技能做什么
|
||||
- 何时使用它(触发条件)
|
||||
- 少于 1024 个字符
|
||||
- 不能有 XML 标签(< 或 >)
|
||||
- 包含用户可能会说的特定任务
|
||||
- 如果相关,提及文件类型
|
||||
|
||||
**license**(可选):
|
||||
|
||||
- 如果将技能开源则使用
|
||||
- 常见:MIT、Apache-2.0
|
||||
|
||||
**compatibility**(可选)
|
||||
|
||||
- 1-500 个字符
|
||||
- 指示环境要求:例如目标产品、所需系统包、网络访问需求等
|
||||
|
||||
**metadata**(可选):
|
||||
|
||||
- 任何自定义键值对
|
||||
- 建议:author、version、mcp-server
|
||||
- 示例:
|
||||
|
||||
```YAML
|
||||
metadata:
|
||||
author: ProjectHub
|
||||
version: 1.0.0
|
||||
mcp-server: projecthub
|
||||
```
|
||||
|
||||
#### 安全限制
|
||||
|
||||
**前置元数据中禁止的内容**:
|
||||
|
||||
- XML 尖括号(< >)
|
||||
|
||||
- 名称中包含"claude"或"anthropic"的技能(保留字)
|
||||
|
||||
**原因**:前置元数据出现在 Claude 的系统提示中。恶意内容可能注入指令。
|
||||
|
||||
### 编写有效的技能
|
||||
|
||||
#### description 字段
|
||||
|
||||
根据 Anthropic 的[工程博客](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills):"此元数据……提供足够的信息让 Claude 知道何时应该使用每个技能,而无需将所有内容加载到上下文中。"这是渐进式披露的第一级。
|
||||
|
||||
**结构**:
|
||||
|
||||
```
|
||||
[它做什么] + [何时使用] + [关键能力]
|
||||
```
|
||||
|
||||
**好的 description 示例**:
|
||||
|
||||
```
|
||||
# 好 - 具体且可操作
|
||||
description: 分析 Figma 设计文件并生成开发者交接文档。当用户上传 .fig 文件、要求"设计规格"、"组件文档"或"设计到代码交接"时使用。
|
||||
|
||||
# 好 - 包含触发短语
|
||||
description: 管理 Linear 项目工作流程,包括冲刺计划、任务创建和状态跟踪。当用户提到"冲刺"、"Linear 任务"、"项目计划"或要求"创建工单"时使用。
|
||||
|
||||
# 好 - 清晰的价值主张
|
||||
description: PayFlow 的端到端客户入职工作流程。处理账户创建、支付设置和订阅管理。当用户说"引导新客户"、"设置订阅"或"创建 PayFlow 账户"时使用。
|
||||
```
|
||||
|
||||
**差的 description 示例**:
|
||||
|
||||
```
|
||||
# 太模糊
|
||||
description: 帮助处理项目。
|
||||
|
||||
# 缺少触发器
|
||||
description: 创建复杂的多页文档系统。
|
||||
|
||||
# 太技术化,没有用户触发器
|
||||
description: 实现具有层级关系的 Project 实体模型。
|
||||
```
|
||||
|
||||
#### 编写主要指令
|
||||
|
||||
在前置元数据之后,用 Markdown 编写实际的指令。
|
||||
|
||||
**推荐结构**:
|
||||
|
||||
根据你的技能调整此模板。用你的具体内容替换括号中的部分。
|
||||
|
||||
````
|
||||
---
|
||||
name: your-skill
|
||||
description: [...]
|
||||
---
|
||||
|
||||
# 你的技能名称
|
||||
|
||||
## 指令
|
||||
|
||||
### 步骤 1:[第一个主要步骤]
|
||||
清晰解释会发生什么。
|
||||
示例:```bash python scripts/fetch_data.py --project-id PROJECT_ID```
|
||||
预期输出:[描述成功的样子]
|
||||
````
|
||||
|
||||
(根据需要添加更多步骤)
|
||||
|
||||
## 示例
|
||||
|
||||
### 示例 1:[常见场景]
|
||||
|
||||
用户说:"设置一个新的营销活动"
|
||||
|
||||
操作:
|
||||
|
||||
1. 通过 MCP 获取现有活动
|
||||
2. 使用提供的参数创建新活动
|
||||
|
||||
结果:活动已创建,附带确认链接
|
||||
|
||||
(根据需要添加更多示例)
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 错误:[常见错误消息]
|
||||
|
||||
**原因**:[为什么会发生]
|
||||
|
||||
**解决方案**:[如何修复]
|
||||
|
||||
(根据需要添加更多错误案例)
|
||||
|
||||
### 指令最佳实践
|
||||
|
||||
#### 具体且可操作
|
||||
|
||||
✅ **好**:
|
||||
|
||||
```
|
||||
运行 python scripts/validate.py --input {filename} 检查数据格式。
|
||||
如果验证失败,常见问题包括:
|
||||
- 缺少必需字段(将它们添加到 CSV)
|
||||
- 无效的日期格式(使用 YYYY-MM-DD)
|
||||
```
|
||||
|
||||
❌ **差**:
|
||||
|
||||
```
|
||||
在继续之前验证数据。
|
||||
```
|
||||
|
||||
#### 包含错误处理
|
||||
|
||||
```
|
||||
## 常见问题
|
||||
|
||||
### MCP 连接失败
|
||||
如果你看到"Connection refused":
|
||||
1. 验证 MCP 服务器正在运行:检查设置 > 扩展
|
||||
2. 确认 API 密钥有效
|
||||
3. 尝试重新连接:设置 > 扩展 > [你的服务] > 重新连接
|
||||
```
|
||||
|
||||
#### 清晰地引用捆绑资源
|
||||
|
||||
```
|
||||
在编写查询之前,查阅 `references/api-patterns.md` 了解:
|
||||
- 速率限制指南
|
||||
- 分页模式
|
||||
- 错误代码和处理
|
||||
```
|
||||
|
||||
#### 使用渐进式披露
|
||||
|
||||
保持 SKILL.md 专注于核心指令。将详细文档移至 `references/` 并链接到它。(参见[核心设计原则](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)了解三级系统的工作原理。)
|
||||
|
||||
# 第 3 章:测试与迭代
|
||||
|
||||
技能可以根据你的需求在不同的严格程度下进行测试:
|
||||
|
||||
- **在 Claude.ai 中手动测试** - 直接运行查询并观察行为。快速迭代,无需设置。
|
||||
- **在 Claude Code 中脚本化测试** - 自动化测试用例以在更改间进行可重复的验证。
|
||||
- **通过技能 API 进行程序化测试** - 构建评估套件,针对定义的测试集系统地运行。
|
||||
|
||||
选择与你的质量要求和技能可见度相匹配的方法。一个由小团队内部使用的技能与一个部署给数千企业用户的技能有不同的测试需求。
|
||||
|
||||
> **专业提示**:先在单个任务上迭代,然后再扩展
|
||||
|
||||
我们发现,最有效的技能创建者会在单个具有挑战性的任务上迭代,直到 Claude 成功,然后将成功的方法提取到技能中。这利用了 Claude 的上下文学习能力,比广泛测试提供更快的信号。一旦你有了一个可用的基础,再扩展到多个测试用例以确保覆盖率。
|
||||
|
||||
## 推荐的测试方法
|
||||
|
||||
根据早期经验,有效的技能测试通常涵盖三个领域:
|
||||
|
||||
### 1: 触发测试
|
||||
|
||||
**目标**:确保你的技能在正确的时间加载。
|
||||
|
||||
**测试用例**:
|
||||
|
||||
- ✅ 在明显任务上触发
|
||||
- ✅ 在改述的请求上触发
|
||||
- ❌ 不在无关主题上触发
|
||||
|
||||
**示例测试套件**:
|
||||
|
||||
```
|
||||
应该触发:
|
||||
- "帮我设置一个新的 ProjectHub 工作区"
|
||||
- "我需要在 ProjectHub 中创建一个项目"
|
||||
- "为 Q4 计划初始化一个 ProjectHub 项目"
|
||||
不应该触发:
|
||||
- "旧金山的天气怎么样?"
|
||||
- "帮我写 Python 代码"
|
||||
- "创建一个电子表格"(除非 ProjectHub 技能处理表格)
|
||||
```
|
||||
|
||||
### 2: 功能测试
|
||||
|
||||
**目标**:验证技能产生正确的输出。
|
||||
|
||||
**测试用例**:
|
||||
|
||||
- 生成有效输出
|
||||
- API 调用成功
|
||||
- 错误处理正常工作
|
||||
- 覆盖边缘情况
|
||||
|
||||
**示例**:
|
||||
|
||||
```
|
||||
测试:创建包含 5 个任务的项目
|
||||
给定:项目名称"Q4 计划",5 个任务描述
|
||||
当:技能执行工作流程
|
||||
那么:
|
||||
- 项目在 ProjectHub 中创建
|
||||
- 5 个任务以正确的属性创建
|
||||
- 所有任务链接到项目
|
||||
- 无 API 错误
|
||||
```
|
||||
|
||||
### 3: 性能比较
|
||||
|
||||
**目标**:证明技能相比基线改善了结果。
|
||||
|
||||
使用"定义成功标准"中的指标。以下是一个比较可能的样子。
|
||||
|
||||
**基线比较**:
|
||||
|
||||
```
|
||||
没有技能时:
|
||||
- 用户每次都提供指令
|
||||
- 15 次来回消息
|
||||
- 3 次失败的 API 调用需要重试
|
||||
- 消耗 12,000 个 token
|
||||
|
||||
有技能时:
|
||||
- 自动工作流程执行
|
||||
- 仅 2 个澄清问题
|
||||
- 0 次失败的 API 调用
|
||||
- 消耗 6,000 个 token
|
||||
```
|
||||
|
||||
## 使用 skill-creator 技能
|
||||
|
||||
skill-creator 技能——可在 Claude.ai 通过插件目录获取,或下载用于 Claude Code——可以帮助你构建和迭代技能。如果你有 MCP 服务器并了解你的前 2-3 个工作流程,你可以在一次工作时段内构建和测试一个功能完整的技能——通常在 15-30 分钟内。
|
||||
|
||||
### 创建技能:
|
||||
|
||||
- 从自然语言描述生成技能
|
||||
- 生成带有前置元数据的正确格式 SKILL.md
|
||||
- 建议触发短语和结构
|
||||
|
||||
### 审查技能:
|
||||
|
||||
- 标记常见问题(模糊的描述、缺失的触发器、结构问题)
|
||||
- 识别潜在的过度/不足触发风险
|
||||
- 根据技能的声明目的建议测试用例
|
||||
|
||||
### 迭代改进:
|
||||
|
||||
- 使用你的技能并遇到边缘情况或失败后,将这些示例带回 skill-creator
|
||||
- 示例:"使用此聊天中识别的问题和解决方案来改进技能处理 [特定边缘情况] 的方式"
|
||||
|
||||
**使用方法**:
|
||||
|
||||
```
|
||||
"使用 skill-creator 技能帮我为 [你的用例] 构建一个技能"
|
||||
```
|
||||
|
||||
_注意:skill-creator 帮助你设计和完善技能,但不执行自动化测试套件或产生定量评估结果。_
|
||||
|
||||
## 基于反馈的迭代
|
||||
|
||||
技能是活的文档。计划根据以下情况进行迭代:
|
||||
|
||||
### 触发不足的信号:
|
||||
|
||||
- 技能在应该加载时没有加载
|
||||
|
||||
- 用户手动启用它
|
||||
|
||||
- 关于何时使用它的支持问题
|
||||
|
||||
> **解决方案**:在 description 中添加更多细节和细微差别——这可能包括关键字,特别是技术术语
|
||||
|
||||
### 过度触发的信号:
|
||||
|
||||
- 技能为无关查询加载
|
||||
- 用户禁用它
|
||||
- 对目的感到困惑
|
||||
|
||||
> **解决方案**:添加负面触发器,更加具体
|
||||
|
||||
### 执行问题:
|
||||
|
||||
- 结果不一致
|
||||
- API 调用失败
|
||||
- 需要用户纠正
|
||||
|
||||
> **解决方案**:改进指令,添加错误处理
|
||||
|
||||
# 第 4 章:模式与故障排除
|
||||
|
||||
这些模式来自早期采用者和内部团队创建的技能。它们代表了我们观察到的有效的常见方法,而非规定性模板。
|
||||
|
||||
## 选择你的方法:问题优先 vs. 工具优先
|
||||
|
||||
可以把它想象成 Home Depot(家得宝)。你可能带着问题走进去——"我需要修理厨房柜子"——然后员工指引你找到合适的工具。或者你可能挑选了一把新电钻,然后询问如何将它用于你的特定工作。
|
||||
|
||||
技能的工作方式相同:
|
||||
|
||||
- **问题优先**:"我需要设置一个项目工作区" → 你的技能按正确的顺序编排正确的 MCP 调用。用户描述结果;技能处理工具。
|
||||
|
||||
- **工具优先**:"我连接了 Notion MCP" → 你的技能教会 Claude 最佳工作流程和最佳实践。用户有访问权限;技能提供专业知识。
|
||||
|
||||
大多数技能倾向于一个方向。知道哪种框架适合你的用例有助于你从下面选择正确的模式。
|
||||
|
||||
## 模式 1:顺序工作流程编排
|
||||
|
||||
**适用场景**:你的用户需要按特定顺序执行的多步骤流程。
|
||||
|
||||
### 示例结构:
|
||||
|
||||
```
|
||||
## 工作流程:引导新客户
|
||||
|
||||
### 步骤 1:创建账户
|
||||
调用 MCP 工具:`create_customer`
|
||||
参数:name、email、company
|
||||
|
||||
### 步骤 2:设置支付
|
||||
调用 MCP 工具:`setup_payment_method`
|
||||
等待:支付方式验证
|
||||
|
||||
### 步骤 3:创建订阅
|
||||
调用 MCP 工具:`create_subscription`
|
||||
参数:plan_id、customer_id(来自步骤 1)
|
||||
|
||||
### 步骤 4:发送欢迎邮件
|
||||
调用 MCP 工具:`send_email`
|
||||
模板:welcome_email_template
|
||||
```
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 明确的步骤顺序
|
||||
- 步骤之间的依赖关系
|
||||
- 每个阶段的验证
|
||||
- 失败时的回滚指令
|
||||
|
||||
## 模式 2:多 MCP 协调
|
||||
|
||||
**适用场景**:工作流程跨越多个服务。
|
||||
|
||||
### 示例:设计到开发交接
|
||||
|
||||
```
|
||||
### 阶段 1:设计导出(Figma MCP)
|
||||
1. 从 Figma 导出设计资产
|
||||
2. 生成设计规格
|
||||
3. 创建资产清单
|
||||
|
||||
### 阶段 2:资产存储(Drive MCP)
|
||||
1. 在 Drive 中创建项目文件夹
|
||||
2. 上传所有资产
|
||||
3. 生成可共享链接
|
||||
|
||||
### 阶段 3:任务创建(Linear MCP)
|
||||
1. 创建开发任务
|
||||
2. 将资产链接附加到任务
|
||||
3. 分配给工程团队
|
||||
|
||||
### 阶段 4:通知(Slack MCP)
|
||||
1. 在 #engineering 发布交接摘要
|
||||
2. 包含资产链接和任务引用
|
||||
```
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 清晰的阶段分离
|
||||
- MCP 之间的数据传递
|
||||
- 进入下一阶段前的验证
|
||||
- 集中式错误处理
|
||||
|
||||
## 模式 3:迭代优化
|
||||
|
||||
**适用场景**:输出质量通过迭代得到改善。
|
||||
|
||||
### 示例:报告生成
|
||||
|
||||
```
|
||||
## 迭代报告创建
|
||||
### 初稿
|
||||
1. 通过 MCP 获取数据
|
||||
2. 生成第一版报告草稿
|
||||
3. 保存到临时文件
|
||||
|
||||
### 质量检查
|
||||
1. 运行验证脚本:`scripts/check_report.py`
|
||||
2. 识别问题:
|
||||
- 缺失的章节
|
||||
- 不一致的格式
|
||||
- 数据验证错误
|
||||
|
||||
### 优化循环
|
||||
1. 解决每个识别的问题
|
||||
2. 重新生成受影响的章节
|
||||
3. 重新验证
|
||||
4. 重复直到达到质量阈值
|
||||
|
||||
### 最终化
|
||||
1. 应用最终格式
|
||||
2. 生成摘要
|
||||
3. 保存最终版本
|
||||
```
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 明确的质量标准
|
||||
- 迭代改进
|
||||
- 验证脚本
|
||||
- 知道何时停止迭代
|
||||
|
||||
## 模式 4:上下文感知工具选择
|
||||
|
||||
**适用场景**:相同的结果,但根据上下文使用不同的工具。
|
||||
|
||||
### 示例:文件存储
|
||||
|
||||
```
|
||||
## 智能文件存储
|
||||
### 决策树
|
||||
1. 检查文件类型和大小
|
||||
2. 确定最佳存储位置:
|
||||
- 大文件(>10MB):使用云存储 MCP
|
||||
- 协作文档:使用 Notion/Docs MCP
|
||||
- 代码文件:使用 GitHub MCP
|
||||
- 临时文件:使用本地存储
|
||||
|
||||
### 执行存储
|
||||
基于决策:
|
||||
- 调用适当的 MCP 工具
|
||||
- 应用特定于服务的元数据
|
||||
- 生成访问链接
|
||||
|
||||
### 向用户提供上下文
|
||||
解释为什么选择该存储
|
||||
```
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 清晰的决策标准
|
||||
- 备用选项
|
||||
- 关于选择的透明度
|
||||
|
||||
## 模式 5:领域特定智能
|
||||
|
||||
**适用场景**:你的技能添加了超越工具访问的专业知识。
|
||||
|
||||
### 示例:金融合规
|
||||
|
||||
```
|
||||
## 带合规的支付处理
|
||||
### 处理前(合规检查)
|
||||
1. 通过 MCP 获取交易详情
|
||||
2. 应用合规规则:
|
||||
- 检查制裁名单
|
||||
- 验证司法管辖区许可
|
||||
- 评估风险级别
|
||||
3. 记录合规决策
|
||||
|
||||
### 处理
|
||||
如果合规通过:
|
||||
- 调用支付处理 MCP 工具
|
||||
- 应用适当的欺诈检查
|
||||
- 处理交易
|
||||
否则:
|
||||
- 标记以供审查
|
||||
- 创建合规案例
|
||||
|
||||
### 审计追踪
|
||||
- 记录所有合规检查
|
||||
- 记录处理决策
|
||||
- 生成审计报告
|
||||
```
|
||||
|
||||
**关键技术**:
|
||||
|
||||
- 领域专业知识嵌入逻辑
|
||||
- 行动前的合规性
|
||||
- 全面的文档记录
|
||||
- 清晰的治理
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 技能不触发
|
||||
|
||||
**症状**:技能从不自动加载
|
||||
|
||||
**修复**:
|
||||
|
||||
修改你的 description 字段。参见"description 字段"中的好/差示例。
|
||||
|
||||
**快速检查清单**:
|
||||
|
||||
- 是否太通用?("帮助处理项目"不会工作)
|
||||
- 是否包含用户实际会说的触发短语?
|
||||
- 如果适用,是否提到相关文件类型?
|
||||
|
||||
**调试方法**:
|
||||
|
||||
询问 Claude:"你什么时候会使用 [技能名称] 技能?"Claude 会引用 description。根据缺失的内容进行调整。
|
||||
|
||||
### 技能触发太频繁
|
||||
|
||||
**症状**:技能为无关查询加载
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **添加负面触发器**
|
||||
|
||||
```
|
||||
description: CSV 文件的高级数据分析。用于统计建模、回归、聚类。不要用于简单的数据探索(改用 data-viz 技能)。
|
||||
```
|
||||
|
||||
2. **更加具体**
|
||||
|
||||
```
|
||||
# 太宽泛
|
||||
description: 处理文档
|
||||
|
||||
# 更具体
|
||||
description: 处理用于合同审查的 PDF 法律文档
|
||||
```
|
||||
|
||||
3. **明确范围**
|
||||
|
||||
```
|
||||
description: 用于电子商务的 PayFlow 支付处理。专门用于在线支付工作流程,而非一般财务查询。
|
||||
```
|
||||
|
||||
### MCP 连接问题
|
||||
|
||||
**症状**:技能加载但 MCP 调用失败
|
||||
|
||||
**检查清单**:
|
||||
|
||||
1. **验证 MCP 服务器已连接**
|
||||
- Claude.ai:设置 > 扩展 > [你的服务] 应显示"已连接"状态
|
||||
2. **检查身份验证**
|
||||
- API 密钥有效且未过期
|
||||
- 已授予适当的权限/范围
|
||||
- OAuth 令牌已刷新
|
||||
3. **独立测试 MCP**
|
||||
- 让 Claude 直接调用 MCP(不使用技能)
|
||||
- "使用 [服务] MCP 获取我的项目"
|
||||
- 如果这失败了,问题在 MCP 而非技能
|
||||
4. **验证工具名称**
|
||||
- 技能引用正确的 MCP 工具名称
|
||||
- 检查 MCP 服务器文档
|
||||
- 工具名称区分大小写
|
||||
|
||||
### 指令未被遵循
|
||||
|
||||
**症状**:技能加载但 Claude 不遵循指令
|
||||
|
||||
**常见原因**:
|
||||
|
||||
1. **指令太冗长**
|
||||
- 保持指令简洁
|
||||
- 使用项目符号和编号列表
|
||||
- 将详细参考移至单独的文件
|
||||
|
||||
2. **指令被埋没**
|
||||
- 将关键指令放在顶部
|
||||
- 使用 ## 重要 或 ## 关键 标题
|
||||
- 如果需要,重复关键点
|
||||
|
||||
3. **语言模糊**
|
||||
|
||||
```
|
||||
# 差
|
||||
确保正确验证内容
|
||||
|
||||
# 好
|
||||
关键:在调用 create_project 之前,验证:
|
||||
- 项目名称非空
|
||||
- 至少分配一名团队成员
|
||||
- 开始日期不在过去
|
||||
```
|
||||
|
||||
**高级技术**:对于关键验证,考虑捆绑一个以编程方式执行检查的脚本,而不是依赖语言指令。代码是确定性的;语言解释不是。参见 Office 技能中此模式的示例。
|
||||
|
||||
4. **模型"懒惰"** 添加明确的鼓励:
|
||||
|
||||
```
|
||||
## 性能说明
|
||||
- 花时间彻底完成这项工作
|
||||
- 质量比速度更重要
|
||||
- 不要跳过验证步骤
|
||||
```
|
||||
|
||||
注意:将此添加到用户提示中比添加到 SKILL.md 中更有效
|
||||
|
||||
### 大上下文问题
|
||||
|
||||
**症状**:技能似乎很慢或响应质量下降
|
||||
|
||||
**原因**:
|
||||
|
||||
- 技能内容太大
|
||||
- 同时启用了太多技能
|
||||
- 所有内容都被加载而不是渐进式披露
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **优化 SKILL.md 大小**
|
||||
- 将详细文档移至 references/
|
||||
- 链接到引用而不是内联
|
||||
- SKILL.md 保持在 5,000 字以下
|
||||
2. **减少启用的技能**
|
||||
- 评估是否同时启用了超过 20-50 个技能
|
||||
- 建议选择性启用
|
||||
- 考虑为相关功能使用技能"包"
|
||||
@@ -17,9 +17,7 @@ my-skill/
|
||||
技能使用**渐进式披露**来高效管理上下文:
|
||||
|
||||
1. **发现**:在启动时,代理仅加载每个可用技能的名称和描述,足以了解何时可能相关。
|
||||
|
||||
2. **激活**:当任务匹配技能的描述时,代理将完整的 `SKILL.md` 指令读入上下文。
|
||||
|
||||
3. **执行**:代理遵循指令,根据需要可选择性地加载引用文件或执行打包的代码。
|
||||
|
||||
这种方法使代理保持快速,同时能够按需访问更多上下文。
|
||||
@@ -55,11 +53,8 @@ Use this skill when the user needs to work with PDF files...
|
||||
- `description`:何时使用此技能
|
||||
|
||||
Markdown 正文包含实际指令,对结构或内容没有特定限制。
|
||||
|
||||
这种简单格式具有一些关键优势:
|
||||
|
||||
- **自文档化**:技能作者或用户可以阅读 `SKILL.md` 并了解其功能,使技能易于审核和改进。
|
||||
|
||||
- **可扩展**:技能的复杂度可以范围从仅文本指令到可执行代码、资产和模板。
|
||||
|
||||
- **可移植**:技能只是文件,因此易于编辑、版本控制和共享。
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-17
|
||||
@@ -0,0 +1,76 @@
|
||||
## Context
|
||||
|
||||
当前项目中存在 `lyxy-reader-docx` skill,仅支持 DOCX 格式的文档解析。`skills/lyxy-reader-office/scripts/` 目录下已完成全部解析脚本的开发(parser.py、common.py、docx_parser.py、pptx_parser.py、xlsx_parser.py、pdf_parser.py、README.md),支持 DOCX、PPTX、XLSX、PDF 四种格式。现在需要创建 `SKILL.md` 作为 skill 的入口文件,并清理被替代的旧 skill。
|
||||
|
||||
已有资源:
|
||||
- `skills/lyxy-reader-docx/SKILL.md`:参考模板,已验证的 skill 结构
|
||||
- `skills/lyxy-runner-python/SKILL.md`:Python 执行 skill,与本 skill 协作
|
||||
- `skills/lyxy-reader-office/scripts/README.md`:完整的脚本使用文档
|
||||
- `document/specification.md`:skill 格式规范
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 创建 `skills/lyxy-reader-office/SKILL.md`,遵循 skill 格式规范
|
||||
- SKILL.md 的 description 能覆盖 .docx、.xlsx、.pptx、.pdf 四种文件扩展名关键词,确保大模型在发现阶段能正确匹配
|
||||
- 引导大模型优先使用该 skill 读取这四种格式的文件
|
||||
- 引导大模型阅读 `scripts/README.md` 获取详细的脚本使用说明
|
||||
- 引导大模型在 `lyxy-runner-python` skill 可用时必须使用该 skill 来运行 Python 脚本
|
||||
- 删除 `skills/lyxy-reader-docx` 目录
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不修改已有的解析脚本代码
|
||||
- 不修改 `lyxy-runner-python` skill
|
||||
- 不新增任何解析功能
|
||||
- 不创建单独的文档说明文件(如旧 skill 的 `docx_parser.md`),详细用法通过引导阅读 README.md 解决
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1:SKILL.md 结构参考 lyxy-reader-docx 但大幅精简
|
||||
|
||||
**选择**:SKILL.md 专注于发现(description)、激活引导(何时使用、触发条件)和执行入口(指向 README.md),不在 SKILL.md 中重复 README.md 的内容。
|
||||
|
||||
**理由**:
|
||||
- 规范建议 SKILL.md 保持在 500 行以下
|
||||
- README.md 已包含完整的命令行用法、参数说明、安装指南、错误处理等
|
||||
- 重复内容会导致维护负担,且增加不必要的 token 消耗
|
||||
- 渐进式披露:SKILL.md 负责激活判断,README.md 负责执行细节
|
||||
|
||||
**替代方案**:将 README.md 全部内容嵌入 SKILL.md — 拒绝,因为会超过 500 行限制且违反渐进式披露原则。
|
||||
|
||||
### 决策 2:强制引导使用 lyxy-runner-python
|
||||
|
||||
**选择**:在 SKILL.md 中明确要求大模型在 `lyxy-runner-python` skill 可用时**必须**使用该 skill 来运行 parser.py,不提供直接 Python 执行的选项。仅在 `lyxy-runner-python` 不可用时降级到直接 Python 执行。
|
||||
|
||||
**理由**:
|
||||
- lyxy-runner-python 使用 uv 管理依赖,能自动安装解析器所需的第三方库
|
||||
- 环境隔离,不污染系统 Python
|
||||
- 与 lyxy-reader-docx 保持一致的执行策略
|
||||
|
||||
**替代方案**:将 lyxy-runner-python 作为可选推荐 — 拒绝,因为用户明确要求"必须使用"。
|
||||
|
||||
### 决策 3:通过引导阅读 README.md 提供详细用法
|
||||
|
||||
**选择**:SKILL.md 中仅给出基本的执行命令格式和常见示例,详细的参数说明、依赖安装、解析器对比等内容通过引导大模型阅读 `scripts/README.md` 获取。
|
||||
|
||||
**理由**:
|
||||
- 符合渐进式披露原则:SKILL.md 是指令层,README.md 是资源层
|
||||
- README.md 已有完整且结构化的文档,无需重复
|
||||
- 减少 SKILL.md 的体积,提高激活阶段的效率
|
||||
|
||||
### 决策 4:删除 lyxy-reader-docx 而非保留兼容
|
||||
|
||||
**选择**:直接删除整个 `skills/lyxy-reader-docx/` 目录。
|
||||
|
||||
**理由**:
|
||||
- lyxy-reader-office 完全覆盖了 lyxy-reader-docx 的功能
|
||||
- 保留旧 skill 会导致两个 skill 争抢 .docx 文件的处理权,造成歧义
|
||||
- 用户明确要求删除
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[旧 skill 引用残留]** → 检查项目中是否有其他地方引用了 `lyxy-reader-docx`(如 .claude/settings 中的 skill 配置),删除时需同步清理
|
||||
- **[description 覆盖度]** → description 需要包含 docx、xlsx、pptx、pdf 等关键词,确保大模型在发现阶段能匹配到四种文件类型。但 description 有 1024 字符限制,需要精简表达
|
||||
- **[SKILL.md 体积控制]** → 需在"提供足够的执行引导"和"保持 500 行以内"之间平衡。通过引导阅读 README.md 解决详细信息的需求
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
当前项目中仅有 `lyxy-reader-docx` skill 用于解析 DOCX 文档,无法覆盖 XLSX、PPTX、PDF 等常见办公文档格式。用户在日常工作中经常需要大模型读取多种格式的文档,缺少统一的多格式文档解析能力会导致大模型无法有效处理这些文件。需要创建一个统一的办公文档解析 skill,覆盖四种主流格式,并替换功能已被覆盖的旧 skill。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `lyxy-reader-office` skill,支持解析 DOCX、XLSX、PPTX、PDF 四种格式
|
||||
- 该 skill 使用 `scripts/parser.py` 作为统一入口,自动识别文件类型并分派到对应的格式解析器
|
||||
- 引导大模型在遇到这四种文件时优先激活并使用该 skill
|
||||
- 引导大模型通过阅读 `scripts/README.md` 了解脚本的详细使用方式
|
||||
- 引导大模型在环境中存在 `lyxy-runner-python` skill 时,必须使用该 skill 来运行 Python 脚本
|
||||
- **BREAKING**:删除 `skills/lyxy-reader-docx` 目录,因为其功能已完全被 `lyxy-reader-office` 覆盖
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `office-document-parsing`: 统一的办公文档解析能力,覆盖 DOCX、XLSX、PPTX、PDF 四种格式,支持全文提取、标题提取、章节提取、正则搜索、字数统计、行数统计等功能,PDF 额外支持 OCR 高精度模式
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `docx-text-extraction`: 该能力将被 `office-document-parsing` 完全替代,原 spec 不再适用
|
||||
|
||||
## Impact
|
||||
|
||||
- **新增文件**:`skills/lyxy-reader-office/SKILL.md`(skill 主文件)
|
||||
- **脚本文件**:`skills/lyxy-reader-office/scripts/` 下的所有解析脚本已就绪(parser.py、common.py、docx_parser.py、pptx_parser.py、xlsx_parser.py、pdf_parser.py、README.md)
|
||||
- **删除目录**:`skills/lyxy-reader-docx/`(整个目录,包含 SKILL.md、docx_parser.md、scripts/docx_parser.py)
|
||||
- **依赖关系**:运行时依赖 Python 3.6+,推荐通过 `lyxy-runner-python` skill 使用 `uv` 自动管理依赖
|
||||
- **Spec 变更**:`docx-text-extraction` spec 将被废弃,新增 `office-document-parsing` spec
|
||||
@@ -0,0 +1,29 @@
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Delegate execution to lyxy-runner-python skill
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「必须通过 lyxy-runner-python 执行脚本」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 替代 lyxy-reader-docx skill
|
||||
|
||||
### Requirement: Extract full text from DOCX file
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「支持多格式文档解析」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 的 parser.py 解析 DOCX 文件
|
||||
|
||||
### Requirement: Extract document metadata
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「统一的查询功能」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 的 `-c` 和 `-l` 参数
|
||||
|
||||
### Requirement: Extract document titles
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「统一的查询功能」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 的 `-t` 参数
|
||||
|
||||
### Requirement: Extract chapter content by title name
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「统一的查询功能」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 的 `-tc` 参数
|
||||
|
||||
### Requirement: Search document with regex
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「统一的查询功能」需求完全替代
|
||||
**Migration**: 使用 lyxy-reader-office skill 的 `-s` 参数
|
||||
|
||||
### Requirement: Multi-strategy parsing fallback
|
||||
**Reason**: 该能力已被 `office-document-parsing` spec 中的「多策略解析降级」需求完全替代,且新版本支持更多格式和更多解析策略
|
||||
**Migration**: 使用 lyxy-reader-office skill,支持 DOCX/PPTX/XLSX/PDF 四种格式的多策略降级
|
||||
@@ -0,0 +1,115 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 优先使用 lyxy-reader-office 解析办公文档
|
||||
大模型在遇到 .docx、.xlsx、.pptx、.pdf 文件时,SHALL 优先激活并使用 lyxy-reader-office skill 来读取文件内容。
|
||||
|
||||
#### Scenario: 用户请求读取支持的文件格式
|
||||
- **WHEN** 用户请求读取或解析 .docx、.xlsx、.pptx 或 .pdf 文件
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-office skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
#### Scenario: 文件扩展名自动识别
|
||||
- **WHEN** 用户提供的文件路径以 .docx、.xlsx、.pptx 或 .pdf 结尾
|
||||
- **THEN** 大模型 SHALL 自动识别为 lyxy-reader-office skill 的处理范围
|
||||
- **AND** 无需用户显式指定使用哪个 skill
|
||||
|
||||
### Requirement: 必须通过 lyxy-runner-python 执行脚本
|
||||
当环境中存在 lyxy-runner-python skill 时,大模型 SHALL 必须使用该 skill 来运行 parser.py 脚本。
|
||||
|
||||
#### Scenario: lyxy-runner-python 可用
|
||||
- **WHEN** 大模型环境中存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 通过 lyxy-runner-python skill 执行 parser.py
|
||||
- **AND** 利用 lyxy-runner-python 的自动依赖管理功能(uv)安装所需的 Python 包
|
||||
|
||||
#### Scenario: lyxy-runner-python 不可用
|
||||
- **WHEN** 大模型环境中不存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 降级到直接使用 Python 执行 parser.py
|
||||
- **AND** 提示用户当前使用直接执行模式
|
||||
- **AND** 禁止自动执行 pip install 安装依赖
|
||||
|
||||
### Requirement: 引导阅读 README 获取详细用法
|
||||
大模型在需要了解 parser.py 的详细使用方式时,SHALL 阅读 `scripts/README.md` 文件。
|
||||
|
||||
#### Scenario: 首次使用 skill 执行解析
|
||||
- **WHEN** 大模型首次使用 lyxy-reader-office skill 或不确定具体参数用法
|
||||
- **THEN** 大模型 SHALL 阅读 `scripts/README.md` 获取命令行参数、依赖安装和使用示例等详细信息
|
||||
|
||||
#### Scenario: 遇到特殊参数需求
|
||||
- **WHEN** 用户请求使用特殊功能(如 PDF OCR、章节提取、正则搜索等)
|
||||
- **THEN** 大模型 SHALL 参考 `scripts/README.md` 中的对应参数说明
|
||||
|
||||
### Requirement: 支持多格式文档解析
|
||||
系统 SHALL 支持 DOCX、PPTX、XLSX、PDF 四种格式的文档解析,将文件转换为 Markdown 格式输出。
|
||||
|
||||
#### Scenario: 解析 DOCX 文件
|
||||
- **WHEN** 用户请求解析 .docx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 保留标题、列表、表格、粗体、斜体等格式
|
||||
- **AND** 移除图片
|
||||
|
||||
#### Scenario: 解析 PPTX 文件
|
||||
- **WHEN** 用户请求解析 .pptx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 每张幻灯片以 `## Slide N` 为标题
|
||||
- **AND** 幻灯片之间以 `---` 分隔
|
||||
|
||||
#### Scenario: 解析 XLSX 文件
|
||||
- **WHEN** 用户请求解析 .xlsx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 以 `## SheetName` 区分工作表
|
||||
- **AND** 数据以 Markdown 表格呈现
|
||||
|
||||
#### Scenario: 解析 PDF 文件
|
||||
- **WHEN** 用户请求解析 .pdf 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 默认使用普通文本提取模式
|
||||
|
||||
#### Scenario: PDF OCR 高精度模式
|
||||
- **WHEN** 用户请求对 PDF 文件启用 OCR 或高精度解析
|
||||
- **THEN** 系统使用 `--high-res` 参数启用 OCR 版面分析
|
||||
- **AND** 通过 Docling OCR 或 unstructured hi_res 策略处理
|
||||
|
||||
### Requirement: 统一的查询功能
|
||||
系统 SHALL 对所有支持的文件格式提供统一的查询接口,包括全文提取、元数据查询、标题提取、章节提取和正则搜索。
|
||||
|
||||
#### Scenario: 获取文档字数
|
||||
- **WHEN** 用户请求获取文档的字数
|
||||
- **THEN** 系统使用 `-c` 参数返回文档的总字符数
|
||||
|
||||
#### Scenario: 获取文档行数
|
||||
- **WHEN** 用户请求获取文档的行数
|
||||
- **THEN** 系统使用 `-l` 参数返回文档的总行数
|
||||
|
||||
#### Scenario: 提取所有标题
|
||||
- **WHEN** 用户请求提取文档的标题结构
|
||||
- **THEN** 系统使用 `-t` 参数返回所有 1-6 级标题
|
||||
|
||||
#### Scenario: 提取指定章节内容
|
||||
- **WHEN** 用户请求提取特定标题名称的章节内容
|
||||
- **THEN** 系统使用 `-tc` 参数返回该章节的完整内容
|
||||
- **AND** 包含完整的上级标题链和所有下级内容
|
||||
|
||||
#### Scenario: 正则表达式搜索
|
||||
- **WHEN** 用户请求在文档中搜索关键词或模式
|
||||
- **THEN** 系统使用 `-s` 参数返回所有匹配结果及上下文
|
||||
- **AND** 默认包含前后各 2 行非空行上下文
|
||||
- **AND** 支持 `-n` 参数自定义上下文行数
|
||||
|
||||
### Requirement: 多策略解析降级
|
||||
系统 SHALL 对每种文件格式按优先级尝试多种解析策略,确保最大的兼容性。
|
||||
|
||||
#### Scenario: 解析器按优先级降级
|
||||
- **WHEN** 优先级最高的解析器不可用或解析失败
|
||||
- **THEN** 系统自动尝试下一优先级的解析器
|
||||
- **AND** 记录每个解析器的失败原因
|
||||
|
||||
#### Scenario: 所有解析器失败
|
||||
- **WHEN** 某种格式的所有解析策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种解析策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
#### Scenario: DOCX/PPTX/XLSX 无依赖运行
|
||||
- **WHEN** 未安装任何第三方解析库
|
||||
- **THEN** DOCX、PPTX、XLSX 文件 SHALL 仍可通过内置 XML 原生解析工作
|
||||
- **AND** PDF 至少需要 pypdf 才能解析
|
||||
@@ -0,0 +1,22 @@
|
||||
## 1. 创建 SKILL.md
|
||||
|
||||
- [x] 1.1 创建 `skills/lyxy-reader-office/SKILL.md` 文件,包含符合规范的 YAML frontmatter(name、description、compatibility)
|
||||
- [x] 1.2 编写 SKILL.md 正文:Purpose 部分,说明 skill 用途及与 lyxy-runner-python 的协作关系(必须使用 lyxy-runner-python,不可用时降级到直接 Python 执行)
|
||||
- [x] 1.3 编写 SKILL.md 正文:When to Use 部分,列出典型场景、触发词和支持的文件扩展名(.docx、.xlsx、.pptx、.pdf)
|
||||
- [x] 1.4 编写 SKILL.md 正文:Capabilities 部分,概述四种格式的解析能力和统一查询功能(全文提取、元数据、标题、章节、搜索)
|
||||
- [x] 1.5 编写 SKILL.md 正文:Execution 部分,给出基本的执行命令格式,并引导大模型阅读 `scripts/README.md` 获取详细参数说明和依赖安装指南
|
||||
- [x] 1.6 编写 SKILL.md 正文:Examples 部分,给出各格式的基本使用示例
|
||||
- [x] 1.7 编写 SKILL.md 正文:Notes 部分,说明限制和注意事项
|
||||
- [x] 1.8 验证 SKILL.md 总行数不超过 500 行,符合渐进式披露原则
|
||||
|
||||
## 2. 清理旧 Skill
|
||||
|
||||
- [x] 2.1 删除 `skills/lyxy-reader-docx/` 整个目录(包含 SKILL.md、docx_parser.md、scripts/docx_parser.py)
|
||||
- [x] 2.2 检查项目中是否有其他位置引用了 `lyxy-reader-docx`(如 .claude/settings 配置),如有则清理引用
|
||||
|
||||
## 3. 验证
|
||||
|
||||
- [x] 3.1 确认 `skills/lyxy-reader-office/SKILL.md` 存在且 frontmatter 格式正确
|
||||
- [x] 3.2 确认 `skills/lyxy-reader-office/scripts/` 下所有脚本文件完整(parser.py、common.py、docx_parser.py、pptx_parser.py、xlsx_parser.py、pdf_parser.py、README.md)
|
||||
- [x] 3.3 确认 `skills/lyxy-reader-docx/` 目录已被删除
|
||||
- [x] 3.4 使用各格式的测试文件(temp/test.docx、temp/test.xlsx、temp/test.pptx、temp/test.pdf)验证 parser.py 可正常运行
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-18
|
||||
176
openspec/changes/archive/2026-02-19-add-lyxy-kb/design.md
Normal file
176
openspec/changes/archive/2026-02-19-add-lyxy-kb/design.md
Normal file
@@ -0,0 +1,176 @@
|
||||
## Context
|
||||
|
||||
当前项目已有 `lyxy-reader-office` skill(解析 docx/pdf/pptx/xlsx 为 markdown)和 `lyxy-runner-python` skill(uv 执行 Python 脚本)。需要在此基础上新增知识库管理能力,让用户能将项目文档组织为可被大模型高效检索和问答的知识库。
|
||||
|
||||
核心约束:
|
||||
- Skill 和 Command 分离:skill 定义底层规范和能力,command 定义用户交互流程
|
||||
- 纯文件驱动:不依赖数据库或向量存储,所有数据以文件形式存在
|
||||
- 渐进式查询:优先读摘要索引,按需加载详细内容,节省 token
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 提供完整的文档入库流程:放入文件 → 解析 → 归档 → 生成摘要
|
||||
- 支持增量更新和全量重建两种 project.md 维护模式
|
||||
- 基于知识库进行多轮问答,回答时标注文件来源
|
||||
- 复用已有 lyxy-reader-office 解析 office 文档
|
||||
- 支持任意纯文本文件直接入库
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不做向量化或语义搜索
|
||||
- 不做跨项目知识关联
|
||||
- 不做文档版本对比或 diff
|
||||
- 不做权限控制或多用户协作
|
||||
- 不做 Web UI 或可视化界面
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 目录结构:CWD 即知识库根目录
|
||||
|
||||
每个知识项目是 CWD 下的一个子目录,内含固定结构:
|
||||
|
||||
```
|
||||
CWD/
|
||||
└── <project-name>/
|
||||
├── project.md # 高度摘要 + 文件索引
|
||||
├── manifest.json # 增量追踪
|
||||
├── parsed/ # 解析后的 markdown
|
||||
├── sources/ # 待处理区
|
||||
└── archive/ # 原始文件备份
|
||||
```
|
||||
|
||||
**为什么不加 `knowledge/` 外层目录**:CWD 本身就是用户选定的知识库工作目录,额外嵌套层没有意义。用户可以在任意位置 `mkdir my-kb && cd my-kb` 然后使用 commands。
|
||||
|
||||
### 2. 文档生命周期:sources → parsed + archive
|
||||
|
||||
文件流转:
|
||||
1. 用户将文档放入 `sources/`
|
||||
2. ingest 解析文件,生成 `parsed/<文件名>.md`
|
||||
3. 原始文件移入 `archive/<文件名_YYYYMMDDHHmm>.<ext>`(每个版本都带时间戳)
|
||||
4. 同名文件覆盖 parsed 中的旧版本,archive 中保留所有历史版本
|
||||
|
||||
**为什么 archive 全部带时间戳**:统一规则,无需判断"是否已有同名文件"。每次入库都是一个带时间戳的快照。
|
||||
|
||||
**同名不同扩展名冲突处理**:如 `技术方案.pdf` 和 `技术方案.docx` 同时存在于 sources/,因为 parsed 产物都会命名为 `技术方案.md`,产生冲突。此时拒绝处理并提示用户重命名。
|
||||
|
||||
### 3. 增量追踪:manifest.json
|
||||
|
||||
```json
|
||||
{
|
||||
"project": "my-project",
|
||||
"created_at": "2026-02-18T16:00",
|
||||
"last_ingest": "2026-02-18T17:25",
|
||||
"files": [
|
||||
{
|
||||
"name": "需求文档",
|
||||
"ext": ".docx",
|
||||
"parsed": "parsed/需求文档.md",
|
||||
"versions": [
|
||||
{
|
||||
"archived": "archive/需求文档_202602181600.docx",
|
||||
"hash": "sha256:abc123...",
|
||||
"ingested_at": "2026-02-18T16:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
manifest 用于:
|
||||
- 检测 sources/ 中哪些文件是新文件
|
||||
- 检测同名不同扩展名冲突(对比 files 中已有的 name 和 ext)
|
||||
- 记录版本历史,关联 parsed 和 archive 文件
|
||||
|
||||
### 4. project.md 格式与更新策略
|
||||
|
||||
project.md 结构:
|
||||
|
||||
```markdown
|
||||
# <项目名称>
|
||||
|
||||
## 概述
|
||||
(高度总结的项目信息)
|
||||
|
||||
## 关键信息
|
||||
(从所有文档中提炼的核心要点)
|
||||
|
||||
## 文件索引
|
||||
|
||||
| 文件名 | 解析文件 | 最新归档 | 摘要 |
|
||||
|--------|----------|----------|------|
|
||||
| 需求文档 | parsed/需求文档.md | archive/需求文档_202602181600.docx | 简要摘要... |
|
||||
|
||||
## 更新记录
|
||||
- 2026-02-18 16:00: 解析 需求文档.docx
|
||||
```
|
||||
|
||||
**默认增量追加**(`lyxy-kb-ingest`):
|
||||
- 新文件:在文件索引表追加新行,在更新记录追加条目
|
||||
- 概述和关键信息部分**不**自动更新
|
||||
|
||||
**全量重写**(`lyxy-kb-rebuild`):
|
||||
- 读取所有 parsed/*.md,重新生成整个 project.md
|
||||
- 概述、关键信息、文件索引全部重新生成
|
||||
|
||||
### 5. 解析后 markdown 的元信息标记
|
||||
|
||||
每个 parsed 文件头部包含元信息注释:
|
||||
|
||||
```markdown
|
||||
<!-- source: 技术方案.pdf -->
|
||||
<!-- archived: archive/技术方案_202602181725.pdf -->
|
||||
<!-- parsed_at: 2026-02-18 17:25 -->
|
||||
|
||||
# 技术方案
|
||||
(文档正文内容...)
|
||||
```
|
||||
|
||||
用于问答时标注来源。
|
||||
|
||||
### 6. 文件类型解析策略
|
||||
|
||||
| 文件类型 | 解析方式 |
|
||||
|----------|----------|
|
||||
| .docx, .pdf, .pptx, .xlsx | lyxy-reader-office(通过 lyxy-runner-python 执行) |
|
||||
| .md, .txt, .csv, .json, .xml, .yaml/.yml, .log, .html 等 | 直接读取文件内容 |
|
||||
|
||||
判断逻辑:先检查是否为 office 文档扩展名,是则调用 lyxy-reader-office;否则视为纯文本直接读取。
|
||||
|
||||
### 7. 渐进式查询策略(ask 模式)
|
||||
|
||||
```
|
||||
① 读取 project.md → 获取整体概述和文件索引
|
||||
② 根据用户问题判断需要哪些 parsed 文件
|
||||
③ 读取相关 parsed 文件(可能只需部分章节)
|
||||
④ 回答问题,标注来源格式:「根据《文件名》(parsed/文件名.md),...」
|
||||
⑤ 保持会话上下文,用户可继续追问
|
||||
```
|
||||
|
||||
### 8. Skill 与 Command 的职责划分
|
||||
|
||||
**Skill(`skills/lyxy-kb/SKILL.md`)**:
|
||||
- 定义知识库目录结构规范
|
||||
- 定义 project.md 格式规范
|
||||
- 定义 manifest.json 结构
|
||||
- 定义解析规则和文件类型映射
|
||||
- 定义渐进式查询策略
|
||||
- 定义来源引用格式
|
||||
|
||||
**Commands(`commands/lyxy-kb/`)**:
|
||||
- `init.md`:创建项目目录和初始文件的交互流程
|
||||
- `ingest.md`:触发解析、展示进度、增量更新 project.md 的交互流程
|
||||
- `rebuild.md`:全量重写 project.md 的交互流程
|
||||
- `ask.md`:进入会话问答模式的交互流程
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[project.md 增量追加导致概述过时]** → 用户可通过 `lyxy-kb-rebuild` 全量重写来更新概述。增量模式优先保证 token 效率,trade-off 是概述不会自动融入新文件的信息。
|
||||
|
||||
**[大量文件时 rebuild 的 token 消耗]** → 如果 parsed 文件总量很大,全量重写时需要读取所有文件。目前无特殊缓解措施,依赖大模型上下文窗口限制自然约束项目规模。
|
||||
|
||||
**[parsed 文件名冲突]** → 同名不同扩展名直接拒绝,要求用户重命名。简单但可能偶尔不便。
|
||||
|
||||
**[纯文本文件的摘要质量]** → CSV、JSON 等结构化数据直接读取后,摘要可能不如 office 文档自然。大模型需要自行判断如何提取关键信息。
|
||||
31
openspec/changes/archive/2026-02-19-add-lyxy-kb/proposal.md
Normal file
31
openspec/changes/archive/2026-02-19-add-lyxy-kb/proposal.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## Why
|
||||
|
||||
在使用大模型辅助工作时,经常需要基于一组项目相关文档(需求文档、技术方案、数据表等)进行问答和分析。目前缺乏一种轻量的、基于文件的方式来组织这些文档,让大模型能够高效地读取、总结和检索。需要一套 skill + command 来实现个人知识库的初始化、文档解析入库、渐进式问答。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `lyxy-kb` skill,定义知识库的目录结构规范、文档生命周期、解析规则、渐进式查询策略等底层能力
|
||||
- 新增 `lyxy-kb-init` command,引导用户创建知识项目目录结构
|
||||
- 新增 `lyxy-kb-ingest` command,解析 sources/ 中的新文件,增量更新 project.md
|
||||
- 新增 `lyxy-kb-rebuild` command,全量重新生成 project.md
|
||||
- 新增 `lyxy-kb-ask` command,进入会话式问答模式,基于项目知识渐进式查询回答问题并标注来源
|
||||
- 复用已有的 `lyxy-reader-office` skill 解析 office 文档,纯文本类文件直接读取
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `kb-project-management`:知识项目的初始化与目录结构管理,包括 project.md、parsed/、sources/、archive/ 的创建和维护
|
||||
- `kb-document-ingestion`:文档解析入库流程,包括文件类型识别、调用解析器、生成 parsed markdown、归档原始文件(带时间戳)、增量追踪(manifest.json)、同名冲突检测
|
||||
- `kb-knowledge-query`:基于知识库的渐进式问答能力,包括读取 project.md 摘要索引、按需加载 parsed 文件、回答时标注文件来源
|
||||
- `kb-project-summary`:project.md 的生成与维护策略,包括增量追加(默认)和全量重写两种模式
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
- **新增文件**:`skills/lyxy-kb/SKILL.md`、`commands/lyxy-kb/` 下 4 个 command 文件(init.md、ingest.md、rebuild.md、ask.md)
|
||||
- **依赖**:运行时依赖 `lyxy-reader-office` skill 解析 office 文档,依赖 `lyxy-runner-python` skill 执行 Python 脚本
|
||||
- **用户侧影响**:用户在任意项目目录下即可使用 command 创建和管理知识库项目,CWD 即为知识库根目录
|
||||
@@ -0,0 +1,71 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 扫描并识别待处理文件
|
||||
系统 SHALL 扫描项目 `sources/` 目录下的所有文件,并根据扩展名判断解析方式:
|
||||
- Office 文档(.docx、.pdf、.pptx、.xlsx):调用 lyxy-reader-office skill 解析
|
||||
- 其他文件(.md、.txt、.csv、.json、.xml、.yaml、.yml、.log、.html 等):直接读取内容
|
||||
|
||||
#### Scenario: sources 中有 office 文档
|
||||
- **WHEN** sources/ 中存在 `报告.docx`
|
||||
- **THEN** 系统 SHALL 使用 lyxy-reader-office skill(通过 lyxy-runner-python 执行)将其解析为 markdown
|
||||
|
||||
#### Scenario: sources 中有纯文本文件
|
||||
- **WHEN** sources/ 中存在 `config.json`
|
||||
- **THEN** 系统 SHALL 直接读取文件内容作为 parsed 产物
|
||||
|
||||
#### Scenario: sources 目录为空
|
||||
- **WHEN** sources/ 中没有任何文件
|
||||
- **THEN** 系统 SHALL 提示用户 sources/ 中无待处理文件
|
||||
|
||||
### Requirement: 同名不同扩展名冲突检测
|
||||
系统 SHALL 在解析前检测 sources/ 中是否存在同名但不同扩展名的文件(如 `技术方案.pdf` 和 `技术方案.docx`),因为 parsed 产物都会命名为相同的 `.md` 文件。同时也需检测 sources/ 中的文件名是否与 manifest.json 中已有记录的不同扩展名文件冲突。
|
||||
|
||||
#### Scenario: sources 中存在同名不同扩展名文件
|
||||
- **WHEN** sources/ 中同时存在 `技术方案.pdf` 和 `技术方案.docx`
|
||||
- **THEN** 系统 SHALL 拒绝处理这两个文件,并提示用户重命名其中一个以消除冲突
|
||||
|
||||
#### Scenario: sources 中文件与已入库文件同名但不同扩展名
|
||||
- **WHEN** manifest.json 中已有 `技术方案`(ext: `.pdf`)的记录,且 sources/ 中出现 `技术方案.docx`
|
||||
- **THEN** 系统 SHALL 拒绝处理该文件,并提示用户重命名
|
||||
|
||||
### Requirement: 生成 parsed markdown 文件
|
||||
系统 SHALL 将解析后的内容写入 `parsed/<文件名>.md`,文件头部 MUST 包含元信息注释:
|
||||
```
|
||||
<!-- source: <原始文件名含扩展名> -->
|
||||
<!-- archived: archive/<文件名_时间戳>.<扩展名> -->
|
||||
<!-- parsed_at: <解析时间 YYYY-MM-DD HH:mm> -->
|
||||
```
|
||||
若 parsed/ 中已存在同名文件(同一文档的更新版本),SHALL 覆盖旧文件。
|
||||
|
||||
#### Scenario: 首次解析文件
|
||||
- **WHEN** 解析 `需求文档.docx`,parsed/ 中不存在 `需求文档.md`
|
||||
- **THEN** 系统创建 `parsed/需求文档.md`,头部包含 source、archived、parsed_at 元信息,正文为解析后的 markdown 内容
|
||||
|
||||
#### Scenario: 更新已有文件
|
||||
- **WHEN** 解析 `技术方案.pdf`,parsed/ 中已存在 `技术方案.md`(上一版本)
|
||||
- **THEN** 系统覆盖 `parsed/技术方案.md`,元信息更新为最新版本的 archive 路径和时间
|
||||
|
||||
### Requirement: 归档原始文件
|
||||
系统 SHALL 将已解析的原始文件从 `sources/` 移动到 `archive/`,文件名格式为 `<文件名_YYYYMMDDHHmm>.<扩展名>`。每个进入 archive 的文件都 MUST 带有时间戳后缀,即使该文件只有一个版本。
|
||||
|
||||
#### Scenario: 归档文件
|
||||
- **WHEN** `需求文档.docx` 解析完成,当前时间为 2026-02-18 16:00
|
||||
- **THEN** 原始文件移动为 `archive/需求文档_202602181600.docx`
|
||||
|
||||
#### Scenario: 同名文件多次入库
|
||||
- **WHEN** `技术方案.pdf` 第二次入库,archive 中已有 `技术方案_202602181600.pdf`
|
||||
- **THEN** 新版本归档为 `技术方案_202602181725.pdf`(以当前时间为时间戳),两个版本并存于 archive
|
||||
|
||||
### Requirement: 更新 manifest.json
|
||||
系统 SHALL 在每个文件处理完成后更新 manifest.json:
|
||||
- 新文件:在 files 数组中追加新条目,包含 name、ext、parsed 路径,versions 数组包含首个版本的 archived 路径、hash 和 ingested_at
|
||||
- 已有文件更新:在对应条目的 versions 数组中追加新版本记录
|
||||
- last_ingest 时间戳 SHALL 更新为当前 ingest 的时间
|
||||
|
||||
#### Scenario: 新文件入库更新 manifest
|
||||
- **WHEN** `需求文档.docx` 首次解析完成
|
||||
- **THEN** manifest.json 的 files 数组中追加 `{"name": "需求文档", "ext": ".docx", "parsed": "parsed/需求文档.md", "versions": [{"archived": "archive/需求文档_202602181600.docx", "hash": "sha256:...", "ingested_at": "2026-02-18T16:00"}]}`
|
||||
|
||||
#### Scenario: 已有文件更新 manifest
|
||||
- **WHEN** `技术方案.pdf` 第二次入库
|
||||
- **THEN** manifest.json 中该文件条目的 versions 数组追加新版本记录,不删除旧版本记录
|
||||
@@ -0,0 +1,38 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 渐进式查询策略
|
||||
系统 SHALL 采用渐进式查询策略回答用户问题,以节省 token 消耗:
|
||||
1. 首先读取 project.md 获取整体概述和文件索引
|
||||
2. 根据用户问题和文件索引判断需要查阅哪些 parsed 文件
|
||||
3. 按需读取相关 parsed 文件的全部或部分内容
|
||||
4. 基于获取的信息回答问题
|
||||
|
||||
#### Scenario: 问题可通过摘要回答
|
||||
- **WHEN** 用户提问"这个项目主要做什么?",且 project.md 的概述中已包含足够信息
|
||||
- **THEN** 系统仅基于 project.md 内容回答,不加载 parsed 文件
|
||||
|
||||
#### Scenario: 问题需要查阅具体文件
|
||||
- **WHEN** 用户提问"系统的权限控制是怎么设计的?",且 project.md 文件索引中 `需求文档` 的摘要提到了权限相关内容
|
||||
- **THEN** 系统读取 `parsed/需求文档.md` 获取详细信息后回答
|
||||
|
||||
### Requirement: 来源标注
|
||||
系统在回答中引用具体信息时 SHALL 标注文件来源,格式为:「根据《文件名》(parsed/文件名.md),...」。来源标注 MUST 指向 parsed 目录下的具体文件。
|
||||
|
||||
#### Scenario: 回答中包含来源标注
|
||||
- **WHEN** 系统从 `parsed/技术方案.md` 中获取信息来回答问题
|
||||
- **THEN** 回答中 SHALL 包含类似「根据《技术方案》(parsed/技术方案.md),系统采用微服务架构...」的来源标注
|
||||
|
||||
#### Scenario: 回答综合多个文件
|
||||
- **WHEN** 回答需要综合 `parsed/需求文档.md` 和 `parsed/技术方案.md` 的信息
|
||||
- **THEN** 回答中 SHALL 分别标注各信息点的来源文件
|
||||
|
||||
### Requirement: 会话问答模式
|
||||
系统 SHALL 在 ask 模式下保持会话上下文,用户可以连续提问而无需每次重新加载知识库。会话的退出由用户自然决定(开启新话题或新会话),系统不主动终止会话。
|
||||
|
||||
#### Scenario: 多轮追问
|
||||
- **WHEN** 用户先问"系统用了什么技术栈?",接着追问"数据库选型的理由是什么?"
|
||||
- **THEN** 系统在第二次回答时保持之前的上下文,可复用已加载的 parsed 文件内容
|
||||
|
||||
#### Scenario: 知识库中无相关信息
|
||||
- **WHEN** 用户提出的问题在 project.md 和所有 parsed 文件中均无相关信息
|
||||
- **THEN** 系统 SHALL 明确告知用户当前知识库中未找到相关信息,而非编造答案
|
||||
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 初始化知识项目目录结构
|
||||
系统 SHALL 在 CWD 下创建以指定名称命名的子目录,并在其中生成以下固定结构:
|
||||
- `project.md`:初始内容包含项目名称标题、空的概述/关键信息段落、空的文件索引表和空的更新记录
|
||||
- `manifest.json`:初始内容包含项目名称、创建时间、空的 files 数组
|
||||
- `parsed/` 目录
|
||||
- `sources/` 目录
|
||||
- `archive/` 目录
|
||||
|
||||
#### Scenario: 成功初始化新项目
|
||||
- **WHEN** 用户执行 `/lyxy-kb-init my-project`,且 CWD 下不存在 `my-project` 目录
|
||||
- **THEN** 系统创建 `my-project/` 目录及完整子结构(project.md、manifest.json、parsed/、sources/、archive/),并提示用户将文档放入 sources/ 目录
|
||||
|
||||
#### Scenario: 目标目录已存在
|
||||
- **WHEN** 用户执行 `/lyxy-kb-init my-project`,且 CWD 下已存在 `my-project` 目录
|
||||
- **THEN** 系统 SHALL 提示用户该目录已存在,不覆盖任何现有内容
|
||||
|
||||
### Requirement: 项目目录结构规范
|
||||
知识项目 SHALL 遵循固定的目录结构:`project.md`、`manifest.json`、`parsed/`、`sources/`、`archive/`。所有 command 和 skill 操作 SHALL 基于此结构进行,不在结构外创建额外文件或目录。
|
||||
|
||||
#### Scenario: 验证项目结构完整性
|
||||
- **WHEN** 任何 command(ingest/rebuild/ask)在指定项目目录上执行
|
||||
- **THEN** 系统 SHALL 先检查目录结构是否完整(包含 project.md、manifest.json、parsed/、sources/、archive/),若不完整则提示用户先执行 init
|
||||
@@ -0,0 +1,53 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: project.md 格式规范
|
||||
project.md SHALL 遵循以下固定结构:
|
||||
|
||||
```markdown
|
||||
# <项目名称>
|
||||
|
||||
## 概述
|
||||
(高度总结的项目信息)
|
||||
|
||||
## 关键信息
|
||||
(从所有文档中提炼的核心要点)
|
||||
|
||||
## 文件索引
|
||||
|
||||
| 文件名 | 解析文件 | 最新归档 | 摘要 |
|
||||
|--------|----------|----------|------|
|
||||
|
||||
## 更新记录
|
||||
```
|
||||
|
||||
初始化时概述和关键信息为空,文件索引表为空表头,更新记录为空。
|
||||
|
||||
#### Scenario: 初始化后的 project.md
|
||||
- **WHEN** 执行 `/lyxy-kb-init my-project`
|
||||
- **THEN** 生成的 project.md 包含 `# my-project` 标题、空的概述/关键信息段落、空的文件索引表(仅表头)和空的更新记录
|
||||
|
||||
### Requirement: 增量追加模式
|
||||
执行 ingest 时,系统 SHALL 以增量方式更新 project.md:
|
||||
- 在文件索引表中追加新解析文件的行(文件名、parsed 路径、最新 archive 路径、该文件的简要摘要)
|
||||
- 在更新记录中追加本次 ingest 的条目(时间和处理的文件列表)
|
||||
- 已有文件更新时:覆盖文件索引表中对应行的最新归档路径和摘要
|
||||
- 概述和关键信息部分 SHALL NOT 在增量模式下自动更新
|
||||
|
||||
#### Scenario: 首次 ingest 追加索引
|
||||
- **WHEN** 首次 ingest 解析了 `需求文档.docx`
|
||||
- **THEN** project.md 文件索引表中追加一行,更新记录中追加 `- 2026-02-18 16:00: 解析 需求文档.docx`
|
||||
|
||||
#### Scenario: 已有文件更新时追加索引
|
||||
- **WHEN** `技术方案.pdf` 第二次入库
|
||||
- **THEN** project.md 文件索引表中该文件的最新归档路径和摘要被更新,更新记录追加新条目
|
||||
|
||||
### Requirement: 全量重写模式
|
||||
执行 rebuild 时,系统 SHALL 读取所有 `parsed/*.md` 文件,重新生成整个 project.md:
|
||||
- 概述:基于所有 parsed 文件内容重新生成高度总结
|
||||
- 关键信息:重新提炼核心要点
|
||||
- 文件索引:基于 manifest.json 和 parsed 文件重新生成完整索引表
|
||||
- 更新记录:保留历史记录,追加本次 rebuild 条目
|
||||
|
||||
#### Scenario: 全量重写
|
||||
- **WHEN** 用户执行 `/lyxy-kb-rebuild my-project`,项目中有 3 个 parsed 文件
|
||||
- **THEN** 系统读取所有 3 个 parsed 文件,重新生成 project.md 的概述、关键信息和文件索引,更新记录追加 rebuild 条目
|
||||
26
openspec/changes/archive/2026-02-19-add-lyxy-kb/tasks.md
Normal file
26
openspec/changes/archive/2026-02-19-add-lyxy-kb/tasks.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## 1. Skill 基础
|
||||
|
||||
- [x] 1.1 创建 `skills/lyxy-kb/` 目录
|
||||
- [x] 1.2 编写 `skills/lyxy-kb/SKILL.md`,定义知识库底层规范,包括:目录结构规范(project.md、manifest.json、parsed/、sources/、archive/)、project.md 格式规范(标题/概述/关键信息/文件索引表/更新记录)、manifest.json 结构定义、文件类型解析策略(office 文档用 lyxy-reader-office,其他纯文本直接读取)、parsed 文件元信息标记格式、渐进式查询策略、来源引用格式、同名不同扩展名冲突检测规则
|
||||
|
||||
## 2. Command: lyxy-kb-init
|
||||
|
||||
- [x] 2.1 创建 `commands/lyxy-kb/` 目录
|
||||
- [x] 2.2 编写 `commands/lyxy-kb/init.md`,实现初始化交互流程:接收项目名称参数,检查目标目录是否已存在(已存在则提示不覆盖),在 CWD 下创建项目子目录及完整结构(project.md、manifest.json、parsed/、sources/、archive/),project.md 按规范格式生成初始内容(空概述/关键信息/文件索引/更新记录),manifest.json 初始化为含项目名和创建时间的空结构
|
||||
|
||||
## 3. Command: lyxy-kb-ingest
|
||||
|
||||
- [x] 3.1 编写 `commands/lyxy-kb/ingest.md`,实现增量解析入库交互流程,包括以下步骤:
|
||||
- 接收项目名称参数,验证项目目录结构完整性
|
||||
- 读取 manifest.json,扫描 sources/ 下所有文件
|
||||
- 执行同名不同扩展名冲突检测(sources/ 内部互相检测 + 与 manifest 已有记录检测)
|
||||
- 对每个无冲突的文件:根据扩展名判断解析方式(office → lyxy-reader-office,其他 → 直接读取),生成 parsed markdown(含头部元信息注释),移动原始文件到 archive(带时间戳后缀),更新 manifest.json
|
||||
- 增量更新 project.md:在文件索引表追加/更新行,在更新记录追加条目,不修改概述和关键信息
|
||||
|
||||
## 4. Command: lyxy-kb-rebuild
|
||||
|
||||
- [x] 4.1 编写 `commands/lyxy-kb/rebuild.md`,实现全量重写交互流程:接收项目名称参数,验证项目目录结构完整性,读取所有 parsed/*.md 文件,基于全部内容重新生成 project.md(概述、关键信息、文件索引表全部重写),保留历史更新记录并追加本次 rebuild 条目
|
||||
|
||||
## 5. Command: lyxy-kb-ask
|
||||
|
||||
- [x] 5.1 编写 `commands/lyxy-kb/ask.md`,实现会话问答交互流程:接收项目名称参数,验证项目目录结构完整性,指导大模型执行渐进式查询策略(先读 project.md 摘要索引 → 按需加载 parsed 文件 → 回答并标注来源),进入持续会话模式(用户可连续追问),知识库无相关信息时明确告知
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-25
|
||||
322
openspec/changes/archive/2026-02-25-build-skillmgr-cli/design.md
Normal file
322
openspec/changes/archive/2026-02-25-build-skillmgr-cli/design.md
Normal file
@@ -0,0 +1,322 @@
|
||||
## Context
|
||||
|
||||
当前手动管理 AI 编程平台的 skills/commands 面临以下挑战:
|
||||
|
||||
- **平台差异**:Claude Code 使用目录结构,OpenCode 使用扁平化文件命名(如 `lyxy-kb-init.md`)
|
||||
- **手动操作**:需要手动理解每个平台的规范、复制文件、重命名
|
||||
- **追踪困难**:无法知道哪些 skills/commands 已安装、来自哪个源、何时安装
|
||||
- **更新风险**:手动更新容易遗漏文件或破坏已有配置
|
||||
|
||||
目标用户是作者本人,工具需要简单直接,优先实现核心功能而非过度设计。
|
||||
|
||||
**约束条件**:
|
||||
- 单用户场景,不考虑多用户协作
|
||||
- 仅支持最新版本,不处理多版本共存
|
||||
- 不解析依赖关系,用户手动管理依赖
|
||||
- 平台支持范围:Claude Code、OpenCode(未来可扩展)
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 自动化从 git 源仓库到目标平台的完整安装流程
|
||||
- 支持全局和项目级两种安装作用域
|
||||
- 内置 Claude Code 和 OpenCode 的平台适配规则
|
||||
- 记录所有安装操作,支持查询、更新、卸载、清理
|
||||
- 事务性安装机制,避免部分失败导致的不一致状态
|
||||
- 用户友好的交互体验(确认覆盖、清晰的错误提示)
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- ❌ 多版本管理(语义化版本、版本锁定、版本冲突解决)
|
||||
- ❌ 依赖解析和自动安装依赖
|
||||
- ❌ 插件化的平台适配器系统
|
||||
- ❌ 复杂的仓库注册中心或包索引服务
|
||||
- ❌ 跨平台迁移或批量同步
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 技术栈选择:Go + Cobra
|
||||
|
||||
**决策**:使用 Go 语言开发,CLI 框架选择 Cobra。
|
||||
|
||||
**理由**:
|
||||
- **Go**:编译为单一可执行文件,无运行时依赖,跨平台分发简单
|
||||
- **Cobra**:业界标准 CLI 框架(kubectl、docker 都用它),支持子命令、自动帮助生成、参数验证
|
||||
- **替代方案**:标准库 `flag` 包功能过于简单,不适合多子命令场景
|
||||
|
||||
**影响**:开发者需要熟悉 Go 和 Cobra 的基本用法。
|
||||
|
||||
---
|
||||
|
||||
### 2. 配置文件格式:JSON
|
||||
|
||||
**决策**:使用 JSON 格式存储配置(repository.json、install.json)。
|
||||
|
||||
**理由**:
|
||||
- Go 标准库原生支持,无需第三方依赖
|
||||
- 结构清晰,易于程序读写和人工检查
|
||||
- **替代方案**:YAML(需要第三方库)、TOML(生态较小)
|
||||
|
||||
**配置结构**:
|
||||
```
|
||||
~/.skillmgr/
|
||||
├── repository.json # 源仓库列表
|
||||
├── install.json # 安装记录
|
||||
└── cache/ # git 仓库缓存
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 事务性安装:Tmp Staging
|
||||
|
||||
**决策**:采用三阶段事务性安装(Stage → Commit → Rollback)。
|
||||
|
||||
**理由**:
|
||||
- 避免部分文件复制失败导致目标目录处于不一致状态
|
||||
- 先在系统临时目录(`os.TempDir()`)组装完整的目标文件树
|
||||
- 验证成功后一次性移动到最终位置
|
||||
- 失败时自动清理临时目录,不污染目标
|
||||
|
||||
**流程**:
|
||||
```
|
||||
1. 创建 staging 目录(/tmp/skillmgr-xxxxx/)
|
||||
2. 复制所有文件到 staging(应用平台适配规则)
|
||||
3. 验证 staging 目录完整性
|
||||
4. 移动 staging 到目标位置(原子操作)
|
||||
5. 失败则删除 staging,不影响目标
|
||||
```
|
||||
|
||||
**替代方案**:直接复制到目标(风险高)、使用 Git worktree(过于复杂)。
|
||||
|
||||
---
|
||||
|
||||
### 4. 平台适配器:内置而非插件化
|
||||
|
||||
**决策**:将 Claude Code 和 OpenCode 的适配规则硬编码在程序内,不支持用户自定义适配器。
|
||||
|
||||
**理由**:
|
||||
- 目标平台数量少且稳定(2 个),插件系统收益低
|
||||
- 硬编码保证规则的正确性和一致性
|
||||
- 简化实现和维护成本
|
||||
- **替代方案**:配置文件定义适配规则(增加复杂度)、插件系统(过度设计)
|
||||
|
||||
**适配器接口**:
|
||||
```go
|
||||
type PlatformAdapter interface {
|
||||
GetSkillInstallPath(scope, name) (string, error)
|
||||
GetCommandInstallPath(scope, group) (string, error)
|
||||
AdaptSkill(sourcePath, destPath) (map[string]string, error)
|
||||
AdaptCommand(sourcePath, destPath, group) (map[string]string, error)
|
||||
}
|
||||
```
|
||||
|
||||
**差异处理**:
|
||||
- **Skills**:两个平台都保持目录结构,直接复制
|
||||
- **Commands**:
|
||||
- Claude Code 保持目录结构(`commands/lyxy-kb/init.md`)
|
||||
- OpenCode 扁平化文件名(`command/lyxy-kb-init.md`)
|
||||
|
||||
---
|
||||
|
||||
### 5. 安装策略:复制而非符号链接
|
||||
|
||||
**决策**:全局和项目级安装都使用文件复制,不使用符号链接。
|
||||
|
||||
**理由**:
|
||||
- 避免符号链接在跨平台和跨文件系统时的兼容性问题(尤其是 Windows)
|
||||
- 项目可以独立于全局安装,避免意外修改影响其他项目
|
||||
- 磁盘空间在现代系统中不是瓶颈
|
||||
- **替代方案**:全局符号链接(复杂度高,跨平台问题)
|
||||
|
||||
**影响**:
|
||||
- 更新全局安装不会自动影响项目级安装(需显式更新)
|
||||
- 多个项目可以独立更新各自的 skills/commands
|
||||
|
||||
---
|
||||
|
||||
### 6. 命令文件组织:命令组概念
|
||||
|
||||
**决策**:将 commands 按目录组织,整个目录作为"命令组"一起安装。
|
||||
|
||||
**理由**:
|
||||
- 源仓库中 commands 按功能分组(如 `commands/lyxy-kb/` 包含 init/ask/ingest/rebuild)
|
||||
- 命令组内的命令通常有关联,应一起安装
|
||||
- 简化用户操作,避免逐个命令安装
|
||||
|
||||
**命令组到命令的映射**:
|
||||
- Claude Code:`/lyxy-kb-init` → `commands/lyxy-kb/init.md`
|
||||
- OpenCode:`/lyxy-kb:init` → `command/lyxy-kb-init.md`
|
||||
|
||||
---
|
||||
|
||||
### 7. 安装记录清理:Clean 命令
|
||||
|
||||
**决策**:提供 `clean` 命令扫描并清理孤立记录(install.json 中存在但目标路径已删除)。
|
||||
|
||||
**理由**:
|
||||
- 用户可能手动删除已安装的目录
|
||||
- 避免 install.json 与实际文件系统状态不一致
|
||||
- 不自动清理(避免误删),由用户显式触发
|
||||
|
||||
**实现**:
|
||||
```bash
|
||||
skillmgr clean
|
||||
# 扫描 install.json 中所有记录
|
||||
# 检查 install_path 是否存在
|
||||
# 列出孤立记录并确认删除
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 目录冲突处理:用户决策
|
||||
|
||||
**决策**:安装前检查目标目录是否存在,存在时由用户决定是否覆盖。
|
||||
|
||||
**场景**:
|
||||
1. **install.json 有记录 + 目录存在**:已安装,询问是否覆盖
|
||||
2. **install.json 无记录 + 目录存在**:未被 skillmgr 管理的目录,询问是否覆盖
|
||||
3. **install.json 有记录 + 目录不存在**:孤立记录,清理记录后继续安装
|
||||
|
||||
**用户交互**:
|
||||
```
|
||||
Skill 'lyxy-kb' is already installed. Overwrite? [y/N]:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. 项目结构:独立 Go 项目
|
||||
|
||||
**决策**:在 `manager/` 目录下创建独立的 Go 项目,与现有 skills 仓库分离。
|
||||
|
||||
**目录结构**:
|
||||
```
|
||||
manager/
|
||||
├── cmd/skillmgr/ # CLI 命令实现
|
||||
├── internal/ # 内部包(不对外暴露)
|
||||
│ ├── config/ # 配置文件读写
|
||||
│ ├── repo/ # Git 仓库管理
|
||||
│ ├── adapter/ # 平台适配器
|
||||
│ ├── installer/ # 安装逻辑
|
||||
│ └── prompt/ # 用户交互
|
||||
├── pkg/ # 可对外暴露的包
|
||||
│ └── fileutil/ # 文件工具
|
||||
├── go.mod
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 不污染现有 skills 仓库结构
|
||||
- 工具本身可以独立开发、测试、发布
|
||||
- 清晰的模块边界
|
||||
|
||||
---
|
||||
|
||||
### 10. 测试隔离:环境变量注入
|
||||
|
||||
**决策**:通过环境变量覆盖配置和目标路径,实现零污染测试。
|
||||
|
||||
**理由**:
|
||||
- 测试不应影响用户的实际配置(`~/.skillmgr/`)和安装目录(`~/.claude/`)
|
||||
- 环境变量注入是轻量级且侵入性最小的方案
|
||||
- 支持并行测试(每个测试独立目录)
|
||||
|
||||
**实现**:
|
||||
```go
|
||||
// 配置路径注入
|
||||
func GetConfigRoot() (string, error) {
|
||||
if testRoot := os.Getenv("SKILLMGR_TEST_ROOT"); testRoot != "" {
|
||||
return testRoot, nil
|
||||
}
|
||||
// 生产模式...
|
||||
}
|
||||
|
||||
// 目标路径注入
|
||||
func getBasePath(scope Scope) (string, error) {
|
||||
if testBase := os.Getenv("SKILLMGR_TEST_BASE"); testBase != "" {
|
||||
return testBase, nil
|
||||
}
|
||||
// 生产模式...
|
||||
}
|
||||
```
|
||||
|
||||
**测试使用**:
|
||||
```go
|
||||
func TestInstall(t *testing.T) {
|
||||
testRoot := t.TempDir()
|
||||
testBase := t.TempDir()
|
||||
os.Setenv("SKILLMGR_TEST_ROOT", testRoot)
|
||||
os.Setenv("SKILLMGR_TEST_BASE", testBase)
|
||||
defer os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
defer os.Unsetenv("SKILLMGR_TEST_BASE")
|
||||
// 测试代码...
|
||||
}
|
||||
```
|
||||
|
||||
**替代方案**:
|
||||
- **依赖注入**(将路径作为参数传递):侵入性强,需要重构所有函数签名
|
||||
- **Mock 文件系统**(如 afero):复杂度高,且无法测试真实文件系统行为
|
||||
- **专用测试模式标志**:需要额外的全局状态管理
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 1. 无版本管理
|
||||
|
||||
**风险**:用户无法回退到旧版本的 skill/command,更新可能引入破坏性变更。
|
||||
|
||||
**缓解**:
|
||||
- 文档中建议用户在重要项目中使用 git 管理项目配置目录(如 `.claude/`)
|
||||
- 工具记录 `updated_at` 时间,方便追溯
|
||||
|
||||
---
|
||||
|
||||
### 2. 无依赖解析
|
||||
|
||||
**风险**:安装 command 时,依赖的 skill 可能未安装(如 `lyxy-kb` 命令依赖 `lyxy-reader-office` skill)。
|
||||
|
||||
**缓解**:
|
||||
- 在 skill 的 SKILL.md 中明确记录依赖关系(如 `compatibility` 字段)
|
||||
- 错误提示中建议用户检查依赖
|
||||
- 未来可选增强:扫描 SKILL.md 提示缺失依赖
|
||||
|
||||
---
|
||||
|
||||
### 3. Git 依赖
|
||||
|
||||
**风险**:工具依赖系统中已安装 Git 客户端,无 Git 则无法拉取仓库。
|
||||
|
||||
**缓解**:
|
||||
- 在 README 中明确前置条件
|
||||
- 首次运行时检测 Git 是否可用,提示安装
|
||||
- 错误消息中包含 Git 安装指引
|
||||
|
||||
---
|
||||
|
||||
### 4. 跨文件系统移动失败
|
||||
|
||||
**风险**:`os.Rename()` 在跨文件系统时会失败(如 tmp 在 tmpfs,目标在 ext4)。
|
||||
|
||||
**缓解**:
|
||||
- 捕获 Rename 错误,fallback 到递归复制 + 删除 staging
|
||||
- 在事务实现中明确处理两种路径
|
||||
|
||||
---
|
||||
|
||||
### 5. 平台适配规则变化
|
||||
|
||||
**风险**:Claude Code 或 OpenCode 未来修改目录结构规范,导致工具失效。
|
||||
|
||||
**缓解**:
|
||||
- 将适配规则集中在 `internal/adapter/` 包中,便于修改
|
||||
- 提供版本号,用户可锁定工具版本以保证稳定性
|
||||
- 文档中建议关注平台更新公告
|
||||
|
||||
---
|
||||
|
||||
### 6. 手动修改配置文件
|
||||
|
||||
**风险**:用户手动编辑 repository.json 或 install.json 可能破坏格式,导致解析失败。
|
||||
|
||||
**缓解**:
|
||||
- JSON 解析错误时提示备份并重建配置文件
|
||||
- 提供 `doctor` 命令(未来增强)诊断和修复配置
|
||||
@@ -0,0 +1,37 @@
|
||||
## Why
|
||||
|
||||
当前手动管理和分发 AI 编程平台的 skills/commands 存在诸多问题:需要手动理解不同平台(Claude Code、OpenCode)的目录结构差异、手动复制文件、手动处理命名转换(如 OpenCode 的扁平化命名),且难以追踪已安装的内容和版本。随着 skills 数量增长和多平台支持需求,这种手动流程变得不可维护。需要一个自动化的管理工具来简化从源仓库到目标平台的完整流程。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **新增**:创建独立的 Go CLI 工具(skillmgr),提供命令行界面管理 skills/commands 的完整生命周期
|
||||
- **新增**:支持从 git 仓库拉取和缓存 skills/commands 源代码
|
||||
- **新增**:内置 Claude Code 和 OpenCode 两个平台的适配规则
|
||||
- **新增**:支持全局安装(~/.claude/、~/.opencode/)和项目级安装(./.claude/、./.opencode/)
|
||||
- **新增**:安装记录追踪系统,支持更新、卸载、清理孤立记录
|
||||
- **新增**:事务性安装机制,通过 tmp staging 避免部分失败导致的不一致状态
|
||||
- **新增**:用户交互确认(目录覆盖、冲突解决)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `repository-management`:管理源仓库配置(添加、移除、同步 git 仓库)
|
||||
- `skill-installation`:安装 skills 到目标平台(支持全局/项目作用域)
|
||||
- `command-installation`:安装 commands 到目标平台(处理命令组和平台特定命名)
|
||||
- `platform-adaptation`:适配不同 AI 编程平台的目录结构和命名规则
|
||||
- `install-tracking`:跟踪和管理安装记录(查询、更新、清理)
|
||||
- `transactional-install`:事务性文件安装(staging → commit → rollback)
|
||||
- `test-infrastructure`:测试环境隔离和自动化(零污染测试、fixture 管理、CI 集成)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
(无现有能力需要修改)
|
||||
|
||||
## Impact
|
||||
|
||||
- **新增项目**:在 `manager/` 目录下创建独立的 Go 项目(不影响现有 skills 仓库结构)
|
||||
- **用户配置**:在用户目录创建 `~/.skillmgr/` 配置目录(repository.json、install.json、cache/)
|
||||
- **目标平台**:修改目标平台的 `.claude/` 和 `.opencode/` 目录(根据用户操作)
|
||||
- **依赖**:需要 Git 客户端(用于 clone/pull 操作)
|
||||
- **兼容性**:工具设计为独立运行,不破坏现有手动管理的 skills/commands
|
||||
@@ -0,0 +1,95 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以安装命令组到全局目录
|
||||
|
||||
工具必须支持将整个命令组(commands 目录下的子目录)安装到平台配置目录。
|
||||
|
||||
#### Scenario: 全局安装命令组到 Claude Code
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install command <group> --platform claude --global`
|
||||
- **THEN** 系统将 `commands/<group>/` 下所有 .md 文件复制到 `~/.claude/commands/<group>/`
|
||||
|
||||
#### Scenario: 全局安装命令组到 OpenCode
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install command <group> --platform opencode --global`
|
||||
- **THEN** 系统将 `commands/<group>/` 下所有 .md 文件重命名为 `<group>-<action>.md` 并复制到 `~/.opencode/command/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以安装命令组到项目目录
|
||||
|
||||
工具必须支持将命令组安装到当前项目的平台配置目录。
|
||||
|
||||
#### Scenario: 项目级安装命令组到 Claude Code
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install command <group> --platform claude`
|
||||
- **THEN** 系统将命令组复制到 `./.claude/commands/<group>/`
|
||||
|
||||
#### Scenario: 项目级安装命令组到 OpenCode
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install command <group> --platform opencode`
|
||||
- **THEN** 系统将命令组扁平化复制到 `./.opencode/command/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在所有源仓库中查找命令组
|
||||
|
||||
工具必须在所有已配置源仓库的 `commands/` 目录中搜索指定命令组。
|
||||
|
||||
#### Scenario: 找到命令组
|
||||
|
||||
- **WHEN** 源仓库包含 `commands/<group>/` 目录且内有 .md 文件
|
||||
- **THEN** 系统使用该命令组进行安装
|
||||
|
||||
#### Scenario: 命令组不存在
|
||||
|
||||
- **WHEN** 所有源仓库都不包含指定命令组
|
||||
- **THEN** 系统报错"command '<group>' not found in any repository"
|
||||
|
||||
#### Scenario: 命令组目录为空
|
||||
|
||||
- **WHEN** 找到命令组目录但其中没有 .md 文件
|
||||
- **THEN** 系统报错"command group '<group>' contains no command files"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: OpenCode 平台必须扁平化命令文件名
|
||||
|
||||
工具必须在安装到 OpenCode 平台时,将命令文件重命名为 `<group>-<action>.md` 格式。
|
||||
|
||||
#### Scenario: 转换命令文件名
|
||||
|
||||
- **WHEN** 安装 `commands/lyxy-kb/init.md` 到 OpenCode
|
||||
- **THEN** 文件被重命名为 `lyxy-kb-init.md`
|
||||
|
||||
#### Scenario: 保留 .md 扩展名
|
||||
|
||||
- **WHEN** 转换文件名时
|
||||
- **THEN** 系统保留 `.md` 扩展名
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理命令组目录冲突
|
||||
|
||||
工具必须在安装前检查目标目录或文件是否已存在。
|
||||
|
||||
#### Scenario: Claude Code 命令组目录冲突
|
||||
|
||||
- **WHEN** `~/.claude/commands/<group>/` 目录已存在
|
||||
- **THEN** 系统询问用户是否覆盖
|
||||
|
||||
#### Scenario: OpenCode 命令文件冲突
|
||||
|
||||
- **WHEN** 目标 `~/.opencode/command/` 中已存在同名的 `<group>-*.md` 文件
|
||||
- **THEN** 系统询问用户是否覆盖所有冲突文件
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须记录命令组安装
|
||||
|
||||
工具必须在成功安装后将记录写入 install.json。
|
||||
|
||||
#### Scenario: 记录命令组安装信息
|
||||
|
||||
- **WHEN** 命令组安装成功
|
||||
- **THEN** 系统在 install.json 中添加 type 为 "command"、包含命令组名称和安装路径的记录
|
||||
@@ -0,0 +1,124 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须持久化安装记录
|
||||
|
||||
工具必须在每次成功安装后,将安装信息写入 `~/.skillmgr/install.json`。
|
||||
|
||||
#### Scenario: 创建新记录
|
||||
|
||||
- **WHEN** 首次安装某个 skill/command
|
||||
- **THEN** 系统在 install.json 的 installations 数组中添加新记录
|
||||
|
||||
#### Scenario: 记录包含必要字段
|
||||
|
||||
- **WHEN** 创建安装记录
|
||||
- **THEN** 记录必须包含 type、name、source_repo、platform、scope、install_path、installed_at、updated_at
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以查询已安装项
|
||||
|
||||
工具必须提供命令列出所有已安装的 skills 和 commands。
|
||||
|
||||
#### Scenario: 列出所有已安装项
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list` 命令
|
||||
- **THEN** 系统显示所有 install.json 中的记录
|
||||
|
||||
#### Scenario: 按类型过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --type skill` 或 `--type command`
|
||||
- **THEN** 系统仅显示对应类型的安装记录
|
||||
|
||||
#### Scenario: 按平台过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --platform claude` 或 `--platform opencode`
|
||||
- **THEN** 系统仅显示对应平台的安装记录
|
||||
|
||||
#### Scenario: 按作用域过滤
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list --global` 或省略该参数
|
||||
- **THEN** 系统仅显示对应作用域的安装记录
|
||||
|
||||
#### Scenario: 无已安装项
|
||||
|
||||
- **WHEN** install.json 为空或不存在
|
||||
- **THEN** 系统提示"无已安装的 skills/commands"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以卸载已安装项
|
||||
|
||||
工具必须提供卸载功能,删除文件并移除安装记录。
|
||||
|
||||
#### Scenario: 卸载 skill
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr uninstall skill <name> --platform <platform> --global`
|
||||
- **THEN** 系统从 install.json 查找记录,删除对应目录,移除记录
|
||||
|
||||
#### Scenario: 卸载 command
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr uninstall command <group> --platform <platform> --global`
|
||||
- **THEN** 系统删除对应的命令文件或目录,移除记录
|
||||
|
||||
#### Scenario: 卸载不存在的项
|
||||
|
||||
- **WHEN** install.json 中无对应记录
|
||||
- **THEN** 系统提示"未找到安装记录",不执行删除
|
||||
|
||||
#### Scenario: 安装路径已被手动删除
|
||||
|
||||
- **WHEN** install.json 有记录但文件已不存在
|
||||
- **THEN** 系统仅移除记录,不报错
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以更新已安装项
|
||||
|
||||
工具必须提供更新功能,重新从源仓库安装最新版本。
|
||||
|
||||
#### Scenario: 更新单个 skill
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update skill <name> --platform <platform> --global`
|
||||
- **THEN** 系统从源重新安装到原路径,更新 updated_at 字段
|
||||
|
||||
#### Scenario: 更新单个 command
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update command <group> --platform <platform> --global`
|
||||
- **THEN** 系统从源重新安装到原路径,更新 updated_at 字段
|
||||
|
||||
#### Scenario: 更新所有已安装项
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr update --all`
|
||||
- **THEN** 系统遍历 install.json 中所有记录,逐个更新
|
||||
|
||||
#### Scenario: 源仓库找不到原始项
|
||||
|
||||
- **WHEN** 更新时源仓库中不再存在该 skill/command
|
||||
- **THEN** 系统报错,不修改已安装文件和记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以清理孤立记录
|
||||
|
||||
工具必须提供命令扫描并清理 install.json 中文件路径已不存在的记录。
|
||||
|
||||
#### Scenario: 扫描孤立记录
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr clean` 命令
|
||||
- **THEN** 系统遍历 install.json 中所有记录,检查 install_path 是否存在
|
||||
|
||||
#### Scenario: 清理孤立记录
|
||||
|
||||
- **WHEN** 发现安装路径不存在的记录
|
||||
- **THEN** 系统列出这些记录并从 install.json 中删除
|
||||
|
||||
#### Scenario: 无孤立记录
|
||||
|
||||
- **WHEN** 所有记录的安装路径都存在
|
||||
- **THEN** 系统提示"无孤立记录"
|
||||
|
||||
#### Scenario: 显示清理结果
|
||||
|
||||
- **WHEN** 清理完成
|
||||
- **THEN** 系统显示清理的记录数量和详情(type、name、platform、scope、路径)
|
||||
@@ -0,0 +1,104 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须支持 Claude Code 平台
|
||||
|
||||
工具必须内置 Claude Code 平台的目录结构和命名规则。
|
||||
|
||||
#### Scenario: Skill 安装路径
|
||||
|
||||
- **WHEN** 安装 skill 到 Claude Code
|
||||
- **THEN** 目标路径为 `<base>/.claude/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Command 安装路径
|
||||
|
||||
- **WHEN** 安装 command 到 Claude Code
|
||||
- **THEN** 目标路径为 `<base>/.claude/commands/<command-group>/`
|
||||
|
||||
#### Scenario: 保持源目录结构
|
||||
|
||||
- **WHEN** 复制文件到 Claude Code
|
||||
- **THEN** 系统保持源仓库的目录结构不变
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须支持 OpenCode 平台
|
||||
|
||||
工具必须内置 OpenCode 平台的目录结构和命名规则。
|
||||
|
||||
#### Scenario: Skill 全局安装路径
|
||||
|
||||
- **WHEN** 全局安装 skill 到 OpenCode
|
||||
- **THEN** 目标路径为 `~/.config/opencode/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Skill 项目级安装路径
|
||||
|
||||
- **WHEN** 项目级安装 skill 到 OpenCode
|
||||
- **THEN** 目标路径为 `./.opencode/skills/<skill-name>/`
|
||||
|
||||
#### Scenario: Command 全局安装路径
|
||||
|
||||
- **WHEN** 全局安装 command 到 OpenCode
|
||||
- **THEN** 目标路径为 `~/.config/opencode/commands/`
|
||||
|
||||
#### Scenario: Command 项目级安装路径
|
||||
|
||||
- **WHEN** 项目级安装 command 到 OpenCode
|
||||
- **THEN** 目标路径为 `./.opencode/commands/`
|
||||
|
||||
#### Scenario: Skill 保持结构
|
||||
|
||||
- **WHEN** 复制 skill 到 OpenCode
|
||||
- **THEN** 系统保持源目录结构
|
||||
|
||||
#### Scenario: Command 扁平化
|
||||
|
||||
- **WHEN** 复制 command 到 OpenCode
|
||||
- **THEN** 系统将文件重命名为 `<group>-<action>.md` 并放置在 commands/ 目录下
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须根据作用域确定基础路径
|
||||
|
||||
工具必须根据全局/项目作用域计算正确的基础路径。
|
||||
|
||||
#### Scenario: 全局作用域
|
||||
|
||||
- **WHEN** 用户指定 `--global` 参数
|
||||
- **THEN** 基础路径为用户主目录(`~` 或 `$HOME`)
|
||||
|
||||
#### Scenario: 项目作用域
|
||||
|
||||
- **WHEN** 用户未指定 `--global` 参数
|
||||
- **THEN** 基础路径为当前工作目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须生成文件映射表
|
||||
|
||||
适配器必须生成源文件到目标文件的完整映射表,供事务性安装使用。
|
||||
|
||||
#### Scenario: Skill 文件映射
|
||||
|
||||
- **WHEN** 适配 skill 文件
|
||||
- **THEN** 系统返回源路径到目标路径的 map(包括所有子目录和文件)
|
||||
|
||||
#### Scenario: Command 文件映射(Claude)
|
||||
|
||||
- **WHEN** 适配 command 到 Claude Code
|
||||
- **THEN** 系统返回 `commands/<group>/<action>.md` → `<base>/.claude/commands/<group>/<action>.md` 的映射
|
||||
|
||||
#### Scenario: Command 文件映射(OpenCode)
|
||||
|
||||
- **WHEN** 适配 command 到 OpenCode
|
||||
- **THEN** 系统返回 `commands/<group>/<action>.md` → `<base>/commands/<group>-<action>.md` 的映射(全局时 base 为 `~/.config/opencode`,项目级时为 `./.opencode`)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理不支持的平台
|
||||
|
||||
工具必须拒绝处理未实现的平台。
|
||||
|
||||
#### Scenario: 不支持的平台参数
|
||||
|
||||
- **WHEN** 用户指定未实现的平台(如 `--platform cursor`)
|
||||
- **THEN** 系统报错"unsupported platform: cursor"
|
||||
@@ -0,0 +1,78 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以添加源仓库
|
||||
|
||||
工具必须允许用户添加 git 仓库作为 skills/commands 的源,并将配置持久化到 `~/.skillmgr/repository.json`。
|
||||
|
||||
#### Scenario: 成功添加新仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr add <git-url>` 命令
|
||||
- **THEN** 系统克隆仓库到 `~/.skillmgr/cache/` 并将配置写入 repository.json
|
||||
|
||||
#### Scenario: 添加已存在的仓库
|
||||
|
||||
- **WHEN** 用户添加已存在的仓库(同名)
|
||||
- **THEN** 系统提示"仓库名称已存在,请先使用 `skillmgr remove <name>` 移除",拒绝添加
|
||||
|
||||
#### Scenario: 指定仓库别名
|
||||
|
||||
- **WHEN** 用户使用 `--name` 参数指定仓库别名
|
||||
- **THEN** 系统使用指定的别名作为仓库名称
|
||||
|
||||
#### Scenario: 指定分支
|
||||
|
||||
- **WHEN** 用户使用 `--branch` 参数指定分支
|
||||
- **THEN** 系统克隆指定分支而非默认分支
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以移除源仓库
|
||||
|
||||
工具必须允许用户从配置中移除已添加的源仓库。
|
||||
|
||||
#### Scenario: 成功移除仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr remove <name>` 命令
|
||||
- **THEN** 系统从 repository.json 中删除对应配置
|
||||
|
||||
#### Scenario: 移除不存在的仓库
|
||||
|
||||
- **WHEN** 用户尝试移除不存在的仓库名称
|
||||
- **THEN** 系统提示仓库不存在,不报错
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以列出已配置的源仓库
|
||||
|
||||
工具必须提供命令列出所有已添加的源仓库及其信息。
|
||||
|
||||
#### Scenario: 列出所有仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr list-repos` 命令
|
||||
- **THEN** 系统显示所有仓库的名称、URL、分支和添加时间
|
||||
|
||||
#### Scenario: 无已配置仓库
|
||||
|
||||
- **WHEN** 用户执行列表命令但 repository.json 为空
|
||||
- **THEN** 系统提示"无已配置的源仓库"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以同步源仓库
|
||||
|
||||
工具必须提供命令从远程拉取最新代码,更新本地缓存。
|
||||
|
||||
#### Scenario: 同步单个仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr sync <name>` 命令
|
||||
- **THEN** 系统对指定仓库执行 `git pull`
|
||||
|
||||
#### Scenario: 同步所有仓库
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr sync` 不带参数
|
||||
- **THEN** 系统对所有已配置仓库执行 `git pull`
|
||||
|
||||
#### Scenario: Git 操作失败
|
||||
|
||||
- **WHEN** git pull 失败(网络错误、冲突等)
|
||||
- **THEN** 系统显示 git 错误信息并继续处理其他仓库
|
||||
@@ -0,0 +1,95 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 用户可以安装 skill 到全局目录
|
||||
|
||||
工具必须支持将 skill 安装到用户主目录下的平台配置目录(如 `~/.claude/skills/`)。
|
||||
|
||||
#### Scenario: 全局安装到 Claude Code
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform claude --global`
|
||||
- **THEN** 系统将 skill 复制到 `~/.claude/skills/<name>/`
|
||||
|
||||
#### Scenario: 全局安装到 OpenCode
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform opencode --global`
|
||||
- **THEN** 系统将 skill 复制到 `~/.config/opencode/skills/<name>/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以安装 skill 到项目目录
|
||||
|
||||
工具必须支持将 skill 安装到当前项目目录下的平台配置目录。
|
||||
|
||||
#### Scenario: 项目级安装到 Claude Code
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install skill <name> --platform claude`
|
||||
- **THEN** 系统将 skill 复制到 `./claude/skills/<name>/`
|
||||
|
||||
#### Scenario: 项目级安装到 OpenCode
|
||||
|
||||
- **WHEN** 用户在项目目录执行 `skillmgr install skill <name> --platform opencode`
|
||||
- **THEN** 系统将 skill 复制到 `./.opencode/skills/<name>/`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在所有源仓库中查找 skill
|
||||
|
||||
工具必须在所有已配置的源仓库缓存中搜索指定的 skill。
|
||||
|
||||
#### Scenario: 在第一个仓库找到
|
||||
|
||||
- **WHEN** 第一个仓库包含目标 skill
|
||||
- **THEN** 系统使用该仓库的 skill 进行安装
|
||||
|
||||
#### Scenario: 在后续仓库找到
|
||||
|
||||
- **WHEN** 前面的仓库不包含目标 skill,但后续仓库包含
|
||||
- **THEN** 系统使用找到的第一个匹配仓库
|
||||
|
||||
#### Scenario: 所有仓库都不包含
|
||||
|
||||
- **WHEN** 所有源仓库都不包含目标 skill
|
||||
- **THEN** 系统报错"skill '<name>' not found in any repository"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户可以临时指定源仓库
|
||||
|
||||
工具必须支持通过 `--from` 参数临时指定源仓库 URL,不保存到配置文件。
|
||||
|
||||
#### Scenario: 使用临时仓库安装
|
||||
|
||||
- **WHEN** 用户执行 `skillmgr install skill <name> --platform claude --global --from <git-url>`
|
||||
- **THEN** 系统从指定 URL 拉取仓库并安装,不修改 repository.json
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须处理目录已存在的情况
|
||||
|
||||
工具必须在安装前检查目标目录是否已存在,并根据情况处理。
|
||||
|
||||
#### Scenario: install.json 有记录且目录存在
|
||||
|
||||
- **WHEN** 目标 skill 已通过 skillmgr 安装
|
||||
- **THEN** 系统询问用户是否覆盖,默认为否
|
||||
|
||||
#### Scenario: install.json 无记录但目录存在
|
||||
|
||||
- **WHEN** 目标目录存在但不在 install.json 中
|
||||
- **THEN** 系统询问用户是否覆盖该目录,默认为否
|
||||
|
||||
#### Scenario: 用户拒绝覆盖
|
||||
|
||||
- **WHEN** 用户选择不覆盖
|
||||
- **THEN** 系统取消安装,不修改任何文件
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须记录安装操作
|
||||
|
||||
工具必须在成功安装后将记录写入 `~/.skillmgr/install.json`。
|
||||
|
||||
#### Scenario: 记录包含完整信息
|
||||
|
||||
- **WHEN** 安装成功完成
|
||||
- **THEN** 系统在 install.json 中添加包含 type、name、platform、scope、install_path、installed_at、updated_at 的记录
|
||||
@@ -0,0 +1,153 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 测试必须不污染用户环境
|
||||
|
||||
工具的所有测试必须通过环境变量隔离配置和安装目录,不影响用户的实际数据和系统配置。
|
||||
|
||||
#### Scenario: 配置目录隔离
|
||||
|
||||
- **WHEN** 测试运行时设置 `SKILLMGR_TEST_ROOT` 环境变量
|
||||
- **THEN** 系统使用该环境变量指定的目录作为配置根目录,而非 `~/.skillmgr/`
|
||||
|
||||
#### Scenario: 安装目标目录隔离
|
||||
|
||||
- **WHEN** 测试运行时设置 `SKILLMGR_TEST_BASE` 环境变量
|
||||
- **THEN** 系统使用该环境变量指定的目录作为全局/项目基础路径,而非用户主目录或当前工作目录
|
||||
|
||||
#### Scenario: 生产模式不受影响
|
||||
|
||||
- **WHEN** 环境变量未设置(生产模式)
|
||||
- **THEN** 系统使用默认路径(`~/.skillmgr/`、`~/.claude/` 等)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须自动清理临时资源
|
||||
|
||||
所有测试创建的临时目录、文件和 git 仓库必须在测试结束后自动清理,不留垃圾文件。
|
||||
|
||||
#### Scenario: 使用 Go 测试框架自动清理
|
||||
|
||||
- **WHEN** 测试使用 `t.TempDir()` 创建临时目录
|
||||
- **THEN** Go 测试框架在测试结束时自动删除该目录及其所有内容
|
||||
|
||||
#### Scenario: 测试失败时也清理
|
||||
|
||||
- **WHEN** 测试失败或 panic
|
||||
- **THEN** 临时资源仍然被自动清理
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须支持并行执行
|
||||
|
||||
测试设计必须允许多个测试并行运行,互不干扰,充分利用多核性能。
|
||||
|
||||
#### Scenario: 独立测试环境
|
||||
|
||||
- **WHEN** 使用 `go test -parallel N` 并行运行多个测试
|
||||
- **THEN** 每个测试使用独立的临时目录,不产生竞态条件
|
||||
|
||||
#### Scenario: 配置隔离
|
||||
|
||||
- **WHEN** 多个测试同时设置环境变量
|
||||
- **THEN** 每个测试的环境变量设置独立生效(通过 t.Setenv 或 defer os.Unsetenv)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 用户交互必须可 mock
|
||||
|
||||
所有涉及用户输入的功能必须支持测试时注入 mock 输入,不依赖真实的标准输入。
|
||||
|
||||
#### Scenario: Mock 用户确认输入
|
||||
|
||||
- **WHEN** 测试需要模拟用户输入 "y" 或 "n"
|
||||
- **THEN** `prompt.ConfirmWithReader` 函数接受 `io.Reader` 参数,测试时传入 `strings.NewReader("y\n")`
|
||||
|
||||
#### Scenario: 生产模式使用真实输入
|
||||
|
||||
- **WHEN** 生产代码调用 `prompt.Confirm`
|
||||
- **THEN** 内部调用 `ConfirmWithReader(message, os.Stdin)` 读取真实用户输入
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试必须使用真实文件系统
|
||||
|
||||
测试应使用真实的文件系统操作和 git 命令,而非 mock,以确保行为与生产一致。
|
||||
|
||||
#### Scenario: 真实文件复制测试
|
||||
|
||||
- **WHEN** 测试文件复制功能
|
||||
- **THEN** 在临时目录中创建真实文件,执行复制,验证结果
|
||||
|
||||
#### Scenario: 真实 git 操作测试
|
||||
|
||||
- **WHEN** 测试 git clone/pull 功能
|
||||
- **THEN** 在临时目录中初始化真实的 git 仓库,执行 git 命令
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试数据必须可复用
|
||||
|
||||
测试应提供标准的 fixture 数据和辅助函数,避免每个测试重复构建测试环境。
|
||||
|
||||
#### Scenario: Fixture 仓库
|
||||
|
||||
- **WHEN** 测试需要一个标准的 skills/commands 仓库
|
||||
- **THEN** 从 `testdata/fixtures/test-repo/` 复制 fixture 并初始化为 git 仓库
|
||||
|
||||
#### Scenario: 测试辅助函数
|
||||
|
||||
- **WHEN** 测试需要设置隔离环境
|
||||
- **THEN** 调用 `setupTestEnv(t)` 函数自动设置环境变量和临时目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试覆盖必须全面
|
||||
|
||||
测试必须覆盖所有核心模块、关键路径和边界条件。
|
||||
|
||||
#### Scenario: 单元测试覆盖
|
||||
|
||||
- **WHEN** 实现任何核心函数(config、adapter、repo、installer)
|
||||
- **THEN** 必须编写对应的单元测试,覆盖正常和异常情况
|
||||
|
||||
#### Scenario: 集成测试覆盖
|
||||
|
||||
- **WHEN** 实现跨模块功能(完整安装流程)
|
||||
- **THEN** 必须编写集成测试,验证端到端行为
|
||||
|
||||
#### Scenario: 平台兼容性测试
|
||||
|
||||
- **WHEN** 支持多个平台(Claude Code、OpenCode)
|
||||
- **THEN** 每个平台都必须有独立的兼容性测试
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 测试脚本必须简化运行
|
||||
|
||||
必须提供自动化脚本,简化测试环境设置和测试执行。
|
||||
|
||||
#### Scenario: 自动化测试脚本
|
||||
|
||||
- **WHEN** 开发者运行 `./scripts/test.sh`
|
||||
- **THEN** 脚本自动设置测试环境变量、运行所有测试、清理临时资源
|
||||
|
||||
#### Scenario: 沙盒测试环境
|
||||
|
||||
- **WHEN** 开发者运行 `./scripts/sandbox.sh`
|
||||
- **THEN** 脚本创建隔离的沙盒环境,允许手动测试而不影响系统
|
||||
|
||||
---
|
||||
|
||||
### Requirement: CI/CD 集成必须无缝
|
||||
|
||||
测试必须能在 CI/CD 环境中稳定运行,不依赖本地环境配置。
|
||||
|
||||
#### Scenario: GitHub Actions 集成
|
||||
|
||||
- **WHEN** 在 CI 中运行测试
|
||||
- **THEN** 通过环境变量设置测试路径,所有测试通过且自动清理
|
||||
|
||||
#### Scenario: 测试失败报告
|
||||
|
||||
- **WHEN** 测试失败
|
||||
- **THEN** CI 系统捕获失败信息、日志和覆盖率报告
|
||||
@@ -0,0 +1,126 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统必须使用临时目录进行 staging
|
||||
|
||||
工具必须在系统临时目录中创建 staging 区域,组装完整的目标文件树后再移动到最终位置。
|
||||
|
||||
#### Scenario: 创建 staging 目录
|
||||
|
||||
- **WHEN** 开始安装事务
|
||||
- **THEN** 系统在 `/tmp/` 或 `os.TempDir()` 创建唯一的临时目录(如 `skillmgr-xxxxx/`)
|
||||
|
||||
#### Scenario: Staging 目录结构与目标一致
|
||||
|
||||
- **WHEN** 在 staging 中组装文件
|
||||
- **THEN** staging 目录结构必须与最终目标目录结构完全一致
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须先完整复制到 staging
|
||||
|
||||
工具必须将所有源文件完整复制到 staging 目录,应用平台适配规则。
|
||||
|
||||
#### Scenario: 复制所有文件到 staging
|
||||
|
||||
- **WHEN** 执行 Stage 阶段
|
||||
- **THEN** 系统根据平台适配器返回的文件映射,将所有文件复制到 staging
|
||||
|
||||
#### Scenario: 复制失败回滚
|
||||
|
||||
- **WHEN** 复制过程中任何文件失败
|
||||
- **THEN** 系统删除 staging 目录,报错终止,不影响目标目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须验证 staging 完整性
|
||||
|
||||
工具必须在提交前验证 staging 目录中的文件完整性。
|
||||
|
||||
#### Scenario: 验证文件数量
|
||||
|
||||
- **WHEN** 所有文件复制到 staging
|
||||
- **THEN** 系统验证 staging 中文件数量与预期映射表一致
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须原子性提交 staging
|
||||
|
||||
工具必须在 staging 验证通过后,将整个 staging 目录移动到目标位置。
|
||||
|
||||
#### Scenario: 同文件系统移动
|
||||
|
||||
- **WHEN** staging 和目标在同一文件系统
|
||||
- **THEN** 系统使用 `os.Rename()` 原子性移动
|
||||
|
||||
#### Scenario: 跨文件系统复制
|
||||
|
||||
- **WHEN** `os.Rename()` 失败(跨文件系统)
|
||||
- **THEN** 系统 fallback 到递归复制 + 删除 staging
|
||||
|
||||
#### Scenario: 覆盖已存在目录
|
||||
|
||||
- **WHEN** 目标目录已存在(用户已确认覆盖)
|
||||
- **THEN** 系统先删除目标目录,再移动 staging
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须在失败时自动回滚
|
||||
|
||||
工具必须在任何阶段失败时,自动清理 staging 目录,不留垃圾文件。
|
||||
|
||||
#### Scenario: Stage 阶段失败
|
||||
|
||||
- **WHEN** 文件复制到 staging 失败
|
||||
- **THEN** 系统删除 staging 目录,不修改目标
|
||||
|
||||
#### Scenario: Commit 阶段失败
|
||||
|
||||
- **WHEN** 移动 staging 到目标失败
|
||||
- **THEN** 系统删除 staging 目录,目标目录保持原状(或已删除的状态)
|
||||
|
||||
#### Scenario: defer 确保清理
|
||||
|
||||
- **WHEN** 事务对象被销毁
|
||||
- **THEN** 系统使用 defer 确保 staging 目录被清理
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须提供事务接口
|
||||
|
||||
工具必须提供清晰的事务接口,包含 Stage、Commit、Rollback 方法。
|
||||
|
||||
#### Scenario: 创建事务对象
|
||||
|
||||
- **WHEN** 开始安装流程
|
||||
- **THEN** 系统创建 Transaction 对象,包含 stagingDir、targetDir、fileMap 字段
|
||||
|
||||
#### Scenario: 调用 Stage 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Stage()`
|
||||
- **THEN** 系统执行文件复制到 staging
|
||||
|
||||
#### Scenario: 调用 Commit 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Commit()`
|
||||
- **THEN** 系统将 staging 移动到目标
|
||||
|
||||
#### Scenario: 调用 Rollback 方法
|
||||
|
||||
- **WHEN** 调用 `transaction.Rollback()`
|
||||
- **THEN** 系统删除 staging 目录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统必须确保目标目录的父目录存在
|
||||
|
||||
工具必须在提交前确保目标目录的父目录已创建。
|
||||
|
||||
#### Scenario: 创建父目录
|
||||
|
||||
- **WHEN** 提交 staging 到目标
|
||||
- **THEN** 系统先创建目标目录的父目录(如 `~/.claude/skills/`)
|
||||
|
||||
#### Scenario: 父目录创建失败
|
||||
|
||||
- **WHEN** 父目录创建失败(权限不足等)
|
||||
- **THEN** 系统报错,回滚 staging
|
||||
191
openspec/changes/archive/2026-02-25-build-skillmgr-cli/tasks.md
Normal file
191
openspec/changes/archive/2026-02-25-build-skillmgr-cli/tasks.md
Normal file
@@ -0,0 +1,191 @@
|
||||
## 1. 项目初始化
|
||||
|
||||
- [x] 1.1 在 `manager/` 目录创建 Go 项目,初始化 go.mod(module: skillmgr)
|
||||
- [x] 1.2 创建项目目录结构(cmd/、internal/、pkg/、testdata/)
|
||||
- [x] 1.3 添加 Cobra 依赖(github.com/spf13/cobra)
|
||||
- [x] 1.4 创建 .gitignore 文件
|
||||
- [x] 1.5 创建测试脚本目录(scripts/)
|
||||
|
||||
## 2. 核心类型定义
|
||||
|
||||
- [x] 2.1 创建 `internal/types/types.go`,定义 Platform、ItemType、Scope 枚举类型
|
||||
- [x] 2.2 定义 Repository 结构体(name、url、branch、added_at)
|
||||
- [x] 2.3 定义 RepositoryConfig 结构体(repositories 数组)
|
||||
- [x] 2.4 定义 InstallRecord 结构体(type、name、source_repo、platform、scope、install_path、installed_at、updated_at)
|
||||
- [x] 2.5 定义 InstallConfig 结构体(installations 数组)
|
||||
- [x] 2.6 定义 SkillMetadata 和 CommandGroup 结构体
|
||||
|
||||
## 3. 配置管理模块
|
||||
|
||||
- [x] 3.1 创建 `internal/config/paths.go`,实现配置目录路径函数(GetConfigRoot、GetRepositoryConfigPath、GetInstallConfigPath、GetCachePath)
|
||||
- [x] 3.2 在 GetConfigRoot 中添加环境变量 SKILLMGR_TEST_ROOT 支持(测试隔离)
|
||||
- [x] 3.3 实现 EnsureConfigDirs 函数,确保配置目录存在
|
||||
- [x] 3.4 创建 `internal/config/repository.go`,实现 LoadRepositoryConfig 和 SaveRepositoryConfig
|
||||
- [x] 3.5 实现 AddRepository 函数(检查同名仓库,存在则拒绝并提示先移除)
|
||||
- [x] 3.6 实现 RemoveRepository 和 FindRepository 函数
|
||||
- [x] 3.7 创建 `internal/config/install.go`,实现 LoadInstallConfig 和 SaveInstallConfig
|
||||
- [x] 3.8 实现 AddInstallRecord、RemoveInstallRecord、FindInstallRecord、UpdateInstallRecord 函数
|
||||
- [x] 3.9 实现 CleanOrphanRecords 函数(扫描并清理安装路径不存在的记录)
|
||||
|
||||
## 4. 文件工具模块
|
||||
|
||||
- [x] 4.1 创建 `pkg/fileutil/fileutil.go`
|
||||
- [x] 4.2 实现 CopyFile 函数(复制单个文件并保留权限)
|
||||
- [x] 4.3 实现 CopyDir 函数(递归复制目录)
|
||||
|
||||
## 5. Git 仓库管理模块
|
||||
|
||||
- [x] 5.1 创建 `internal/repo/git.go`
|
||||
- [x] 5.2 实现 URLToPathName 函数(将 git URL 转换为缓存目录名)
|
||||
- [x] 5.3 实现 CloneOrPull 函数(检查仓库是否存在,不存在则 clone,存在则 pull)
|
||||
- [x] 5.4 实现 cloneRepo 函数(执行 git clone --depth 1)
|
||||
- [x] 5.5 实现 pullRepo 函数(执行 git pull)
|
||||
- [x] 5.6 创建 `internal/repo/scanner.go`
|
||||
- [x] 5.7 实现 ScanSkills 函数(扫描 skills/ 目录,返回 skill 列表)
|
||||
- [x] 5.8 实现 ScanCommands 函数(扫描 commands/ 目录,返回命令组列表)
|
||||
- [x] 5.9 实现 FindSkill 函数(在所有仓库缓存中查找指定 skill)
|
||||
- [x] 5.10 实现 FindCommand 函数(在所有仓库缓存中查找指定命令组)
|
||||
|
||||
## 6. 平台适配器模块
|
||||
|
||||
- [x] 6.1 创建 `internal/adapter/adapter.go`,定义 PlatformAdapter 接口
|
||||
- [x] 6.2 实现 GetAdapter 函数(根据 Platform 返回对应适配器,不支持则报错)
|
||||
- [x] 6.3 实现 getBasePath 辅助函数(根据 Scope 返回基础路径,支持 SKILLMGR_TEST_BASE 环境变量)
|
||||
- [x] 6.4 创建 `internal/adapter/claude.go`,实现 ClaudeAdapter 结构体
|
||||
- [x] 6.5 实现 Claude 的 GetSkillInstallPath 和 GetCommandInstallPath 方法
|
||||
- [x] 6.6 实现 Claude 的 AdaptSkill 方法(遍历源目录,生成文件映射)
|
||||
- [x] 6.7 实现 Claude 的 AdaptCommand 方法(保持目录结构)
|
||||
- [x] 6.8 创建 `internal/adapter/opencode.go`,实现 OpenCodeAdapter 结构体
|
||||
- [x] 6.9 实现 OpenCode 的 GetSkillInstallPath(注意 command 是单数)和 GetCommandInstallPath 方法
|
||||
- [x] 6.10 实现 OpenCode 的 AdaptSkill 方法(与 Claude 相同)
|
||||
- [x] 6.11 实现 OpenCode 的 AdaptCommand 方法(扁平化文件名:<group>-<action>.md)
|
||||
|
||||
## 7. 事务性安装模块
|
||||
|
||||
- [x] 7.1 创建 `internal/installer/transaction.go`
|
||||
- [x] 7.2 定义 Transaction 结构体(stagingDir、targetDir、fileMap)
|
||||
- [x] 7.3 实现 NewTransaction 函数(在系统临时目录创建 staging)
|
||||
- [x] 7.4 实现 Stage 方法(复制所有文件到 staging,创建必要的子目录)
|
||||
- [x] 7.5 实现 Commit 方法(确保父目录存在,删除已存在的目标,移动 staging 到目标)
|
||||
- [x] 7.6 处理 Commit 中的跨文件系统情况(Rename 失败则 fallback 到 CopyDir)
|
||||
- [x] 7.7 实现 Rollback 方法(删除 staging 目录)
|
||||
- [x] 7.8 在 NewTransaction 中使用 defer 确保清理
|
||||
|
||||
## 8. 用户交互模块
|
||||
|
||||
- [x] 8.1 创建 `internal/prompt/prompt.go`
|
||||
- [x] 8.2 实现 ConfirmWithReader 函数(接受 io.Reader,支持测试 mock)
|
||||
- [x] 8.3 实现 Confirm 函数(调用 ConfirmWithReader,使用 os.Stdin)
|
||||
|
||||
## 9. 安装器模块
|
||||
|
||||
- [x] 9.1 创建 `internal/installer/installer.go`
|
||||
- [x] 9.2 实现 checkExistingInstallation 函数(检查 install.json 记录和目录存在性,询问用户是否覆盖)
|
||||
- [x] 9.3 实现 InstallSkill 函数(查找 skill、获取适配器、确定路径、检查冲突、适配、事务安装、记录)
|
||||
- [x] 9.4 实现 InstallCommand 函数(查找 command、获取适配器、确定路径、检查冲突、适配、事务安装、记录)
|
||||
- [x] 9.5 创建 `internal/installer/uninstaller.go`
|
||||
- [x] 9.6 实现 UninstallSkill 函数(查找记录、删除目录、移除记录)
|
||||
- [x] 9.7 实现 UninstallCommand 函数(查找记录、删除目录或文件、移除记录)
|
||||
|
||||
## 10. CLI 根命令
|
||||
|
||||
- [x] 10.1 创建 `cmd/skillmgr/root.go`
|
||||
- [x] 10.2 定义 rootCmd,设置 Use、Short、Long
|
||||
- [x] 10.3 实现 Execute 函数
|
||||
- [x] 10.4 在 init 中调用 config.EnsureConfigDirs 初始化配置目录
|
||||
- [x] 10.5 创建 `cmd/skillmgr/main.go`,调用 Execute
|
||||
|
||||
## 11. 仓库管理命令
|
||||
|
||||
- [x] 11.1 创建 `cmd/skillmgr/add.go`,实现 addCmd
|
||||
- [x] 11.2 添加 --name 和 --branch 参数
|
||||
- [x] 11.3 实现 RunE:解析参数、调用 repo.CloneOrPull、调用 config.AddRepository、显示成功信息
|
||||
- [x] 11.4 创建 `cmd/skillmgr/remove.go`,实现 removeCmd
|
||||
- [x] 11.5 实现 RunE:调用 config.RemoveRepository
|
||||
- [x] 11.6 创建 `cmd/skillmgr/list_repos.go`,实现 listReposCmd
|
||||
- [x] 11.7 实现 RunE:调用 config.LoadRepositoryConfig、格式化输出
|
||||
- [x] 11.8 创建 `cmd/skillmgr/sync.go`,实现 syncCmd
|
||||
- [x] 11.9 实现 RunE:支持指定仓库名或同步所有,调用 repo.CloneOrPull
|
||||
|
||||
## 12. 安装命令
|
||||
|
||||
- [x] 12.1 创建 `cmd/skillmgr/install.go`,实现 installCmd
|
||||
- [x] 12.2 添加 --platform(必需)、--global、--from 参数
|
||||
- [x] 12.3 实现 Args 验证(必须有 2 个参数:type 和 name)
|
||||
- [x] 12.4 实现 RunE:解析 type(skill/command)、调用对应安装函数
|
||||
- [x] 12.5 处理 --from 参数(TODO:临时仓库,暂时跳过实现)
|
||||
|
||||
## 13. 追踪管理命令
|
||||
|
||||
- [x] 13.1 创建 `cmd/skillmgr/list.go`,实现 listCmd
|
||||
- [x] 13.2 添加 --type、--platform、--global 参数
|
||||
- [x] 13.3 实现 RunE:加载 install.json、根据参数过滤、格式化输出
|
||||
- [x] 13.4 创建 `cmd/skillmgr/uninstall.go`,实现 uninstallCmd
|
||||
- [x] 13.5 添加 --platform(必需)、--global 参数
|
||||
- [x] 13.6 实现 Args 验证和 RunE:调用对应卸载函数
|
||||
- [x] 13.7 创建 `cmd/skillmgr/update.go`,实现 updateCmd
|
||||
- [x] 13.8 添加 --platform、--global、--all 参数
|
||||
- [x] 13.9 实现 RunE:支持更新单个或全部已安装项
|
||||
- [x] 13.10 创建 `cmd/skillmgr/clean.go`,实现 cleanCmd
|
||||
- [x] 13.11 实现 RunE:调用 config.CleanOrphanRecords、显示清理结果
|
||||
|
||||
## 14. 搜索命令(可选)
|
||||
|
||||
- [x] 14.1 创建 `cmd/skillmgr/search.go`,实现 searchCmd
|
||||
- [x] 14.2 实现 RunE:遍历所有仓库缓存、扫描 skills 和 commands、匹配关键词、输出结果
|
||||
|
||||
## 15. 错误处理和用户体验优化
|
||||
|
||||
- [x] 15.1 确保所有 Git 操作失败时显示清晰错误信息
|
||||
- [x] 15.2 安装/卸载成功时显示 ✓ 符号和路径信息
|
||||
- [x] 15.3 配置文件解析错误时提示用户检查 JSON 格式
|
||||
- [x] 15.4 未找到 skill/command 时列出可用项
|
||||
|
||||
## 16. 测试基础设施
|
||||
|
||||
- [x] 16.1 创建 `testdata/fixtures/` 目录
|
||||
- [x] 16.2 创建测试用 fixture 仓库(test-repo,包含 2 个 skills 和 1 个 command 组)
|
||||
- [x] 16.3 编写测试辅助函数 setupTestRepo(初始化临时 git 仓库)
|
||||
- [x] 16.4 编写测试辅助函数 copyFixtureRepo(复制 fixture 并初始化 git)
|
||||
- [x] 16.5 创建 `scripts/test.sh`(设置测试环境变量并运行测试)
|
||||
- [x] 16.6 创建 `scripts/sandbox.sh`(手动测试沙盒环境)
|
||||
|
||||
## 17. 单元测试
|
||||
|
||||
- [x] 17.1 创建 `internal/config/paths_test.go`,测试环境变量覆盖
|
||||
- [x] 17.2 创建 `internal/config/repository_test.go`,测试仓库配置增删改查
|
||||
- [x] 17.3 测试 AddRepository 拒绝同名仓库场景
|
||||
- [x] 17.4 创建 `internal/config/install_test.go`,测试安装记录管理
|
||||
- [x] 17.5 测试 CleanOrphanRecords 功能
|
||||
- [x] 17.6 创建 `internal/adapter/claude_test.go`,测试路径计算和文件映射
|
||||
- [x] 17.7 创建 `internal/adapter/opencode_test.go`,测试扁平化命名转换
|
||||
- [x] 17.8 创建 `internal/repo/git_test.go`,测试 URL 转换
|
||||
- [x] 17.9 创建 `internal/installer/transaction_test.go`,测试 Stage/Commit/Rollback
|
||||
- [x] 17.10 创建 `internal/prompt/prompt_test.go`,测试用户输入 mock
|
||||
- [x] 17.11 创建 `pkg/fileutil/fileutil_test.go`,测试文件复制
|
||||
|
||||
## 18. 集成测试
|
||||
|
||||
- [x] 18.1 创建 `internal/installer/installer_test.go`
|
||||
- [x] 18.2 测试完整安装流程(add repo → install skill → 验证文件和记录)
|
||||
- [x] 18.3 测试冲突覆盖场景(已安装 → 再次安装 → 用户确认)
|
||||
- [x] 18.4 测试事务回滚(Stage 失败 → 验证 staging 清理)
|
||||
- [x] 18.5 测试卸载流程(install → uninstall → 验证删除)
|
||||
- [x] 18.6 测试更新流程(install → update → 验证更新)
|
||||
- [x] 18.7 测试清理孤立记录(install → 手动删除 → clean)
|
||||
- [x] 18.8 测试 Claude Code 平台安装(skill 和 command)
|
||||
- [x] 18.9 测试 OpenCode 平台安装(skill 和 command 扁平化)
|
||||
|
||||
## 19. 构建和手动验证
|
||||
|
||||
- [x] 19.1 编写 Makefile 或构建脚本,支持 `go build -o skillmgr`
|
||||
- [x] 19.2 在沙盒环境手动测试基本流程
|
||||
- [x] 19.3 验证全局和项目级安装
|
||||
- [x] 19.4 验证两个平台的适配正确性
|
||||
|
||||
## 20. 文档
|
||||
|
||||
- [x] 20.1 编写 README.md,包含安装说明、使用示例、命令参考
|
||||
- [x] 20.2 记录配置文件格式和路径
|
||||
- [x] 20.3 添加常见问题和故障排除指南
|
||||
- [x] 20.4 添加测试说明(如何运行测试、测试环境变量)
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-25
|
||||
@@ -0,0 +1,76 @@
|
||||
## Context
|
||||
|
||||
当前项目包含 4 个 skills(lyxy-runner-python、lyxy-runner-js、lyxy-reader-office、lyxy-kb),每个 SKILL.md 文件内容在 200-300 行之间。根据 `document/the-complete-guide-to-building-skill.md` 中的渐进式披露原则,SKILL.md 应保持精简(建议 5000 字以内),详细文档应放在 `references/` 目录中。
|
||||
|
||||
**当前状态**:
|
||||
- SKILL.md 包含完整的工作流程、示例、错误处理、最佳实践等所有内容
|
||||
- 无 references/ 目录结构
|
||||
- 每次 Claude 加载 skill 时需要处理全部内容
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 遵循三层渐进式披露结构:YAML 前置元数据 → SKILL.md 核心指令 → references/ 详细文档
|
||||
- 减少 skill 加载时的 token 消耗
|
||||
- 保持 lyxy-runner-python 和 lyxy-runner-js 的 description 宽泛性
|
||||
- 为 4 个现有 skills 创建 references/ 目录并迁移内容
|
||||
|
||||
**Non-Goals:**
|
||||
- 不修改 skill 的功能逻辑
|
||||
- 不修改 scripts/ 目录中的可执行代码
|
||||
- 不新增 skill 或删除现有 skill
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1:三层结构内容划分
|
||||
|
||||
| 层级 | 位置 | 内容 | 加载时机 |
|
||||
|-----|------|------|---------|
|
||||
| 第一层 | YAML 前置元数据 | name、description、compatibility | 始终加载到系统提示 |
|
||||
| 第二层 | SKILL.md 正文 | 核心工作流、关键命令、快速参考 | Claude 判断相关时加载 |
|
||||
| 第三层 | references/*.md | 详细示例、错误处理、最佳实践、API 参考 | 按需导航和发现 |
|
||||
|
||||
**理由**:遵循官方指南的渐进式披露原则,最小化 token 消耗的同时保持完整能力。
|
||||
|
||||
### 决策 2:SKILL.md 保留内容
|
||||
|
||||
每个 SKILL.md 正文保留以下核心部分:
|
||||
1. **Purpose**:简要说明用途和关键依赖
|
||||
2. **When to Use**:适用/不适用场景(简化版)
|
||||
3. **Quick Reference**:核心命令表格或流程图
|
||||
4. **Workflow**:简化的执行步骤
|
||||
5. **References 链接**:指向 references/ 中详细文档的链接
|
||||
|
||||
**理由**:保留足够信息让 Claude 完成任务,详细内容按需加载。
|
||||
|
||||
### 决策 3:references/ 目录结构
|
||||
|
||||
```
|
||||
references/
|
||||
├── examples.md # 详细示例
|
||||
├── error-handling.md # 错误处理和故障排除
|
||||
├── best-practices.md # 最佳实践和注意事项
|
||||
└── api-reference.md # API/命令参数详细说明(可选)
|
||||
```
|
||||
|
||||
**理由**:按主题分类,便于 Claude 按需定位和加载。
|
||||
|
||||
### 决策 4:Description 格式
|
||||
|
||||
采用统一格式:`[做什么] + [何时使用] + [关键能力]`
|
||||
|
||||
- lyxy-runner-python:保持 "Any task that requires Python processing should use this skill."(宽泛)
|
||||
- lyxy-runner-js:保持 "Any task that requires Javascript/Typescript processing should use this skill."(宽泛)
|
||||
- lyxy-reader-office:保持当前描述,已包含具体文件类型和操作
|
||||
- lyxy-kb:可优化添加用户触发短语
|
||||
|
||||
**理由**:runner skills 需要宽泛以匹配所有相关任务,reader/kb skills 可以更具体以精准触发。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 缓解措施 |
|
||||
|-----|---------|
|
||||
| references/ 文件未被 Claude 发现 | 在 SKILL.md 中明确列出 references/ 文件并说明用途 |
|
||||
| 拆分后信息不完整 | 核心工作流程保留在 SKILL.md,确保基本任务可完成 |
|
||||
| 迁移过程中遗漏内容 | 逐个 skill 处理,对比前后内容确保完整性 |
|
||||
| 文件数量增加 | references/ 文件按需加载,不影响初始 token 消耗 |
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
当前 skills 的 SKILL.md 文件内容较长(200-300 行),未遵循渐进式披露(Progressive Disclosure)原则。根据 skill 编写指南,应采用三层结构:YAML 前置元数据(触发判断)→ SKILL.md 正文(核心指令)→ references/(详细文档)。这样可以减少 token 消耗,提高 Claude 的响应效率。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **结构优化**:为每个 skill 创建 `references/` 目录,将详细文档、示例、错误处理等内容从 SKILL.md 正文移至 references/
|
||||
- **SKILL.md 精简**:保留核心工作流程和关键指令,将详细说明改为链接到 references/ 中的文档
|
||||
- **Description 优化**:确保 description 同时包含「做什么」和「何时使用」两部分,便于 Claude 判断是否加载
|
||||
- **lyxy-runner-python/js 保持宽泛**:这两个 runner skill 的 description 需要保持宽泛,以便匹配所有 Python/JS 处理任务
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `skill-progressive-disclosure`: 定义 skill 渐进式披露的三层结构规范,包括 YAML 前置元数据、SKILL.md 正文、references/ 目录的职责划分和内容组织方式
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
(无现有 spec 需要修改)
|
||||
|
||||
## Impact
|
||||
|
||||
- **文件结构变更**:每个 skill 目录新增 `references/` 子目录
|
||||
- **SKILL.md 重构**:4 个 skills 的 SKILL.md 文件需要精简,详细内容迁移到 references/
|
||||
- `skills/lyxy-runner-python/SKILL.md`
|
||||
- `skills/lyxy-runner-js/SKILL.md`
|
||||
- `skills/lyxy-reader-office/SKILL.md`
|
||||
- `skills/lyxy-kb/SKILL.md`
|
||||
- **向后兼容**:功能不变,仅优化文档结构,**无破坏性变更**
|
||||
@@ -0,0 +1,77 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 三层渐进式披露结构
|
||||
|
||||
每个 skill 目录 SHALL 采用三层渐进式披露结构,按加载时机分为:第一层(YAML 前置元数据)、第二层(SKILL.md 正文)、第三层(references/ 目录)。
|
||||
|
||||
#### Scenario: skill 目录包含完整三层结构
|
||||
- **WHEN** 检查任意 skill 目录结构
|
||||
- **THEN** 目录 SHALL 包含 SKILL.md 文件和 references/ 子目录
|
||||
|
||||
#### Scenario: 第一层始终加载
|
||||
- **WHEN** Claude 启动会话时
|
||||
- **THEN** 所有 skill 的 YAML 前置元数据 SHALL 被加载到系统提示中
|
||||
|
||||
### Requirement: YAML 前置元数据内容规范
|
||||
|
||||
YAML 前置元数据 SHALL 仅包含触发判断所需的最小信息,使 Claude 能够判断何时使用该 skill。
|
||||
|
||||
#### Scenario: 必需字段
|
||||
- **WHEN** 编写 SKILL.md 的 YAML 前置元数据
|
||||
- **THEN** SHALL 包含 name 和 description 字段
|
||||
|
||||
#### Scenario: description 格式
|
||||
- **WHEN** 编写 description 字段
|
||||
- **THEN** SHALL 同时包含「做什么」和「何时使用」两部分信息
|
||||
|
||||
#### Scenario: description 长度限制
|
||||
- **WHEN** 检查 description 字段
|
||||
- **THEN** 内容 SHALL 少于 1024 个字符
|
||||
|
||||
#### Scenario: runner skill 的 description 宽泛性
|
||||
- **WHEN** skill 用途为通用脚本执行(如 Python/JavaScript runner)
|
||||
- **THEN** description SHALL 保持宽泛,以匹配所有相关处理任务
|
||||
|
||||
### Requirement: SKILL.md 正文内容规范
|
||||
|
||||
SKILL.md 正文 SHALL 包含核心工作流程和关键指令,保持精简以减少 token 消耗。
|
||||
|
||||
#### Scenario: SKILL.md 必需章节
|
||||
- **WHEN** 编写 SKILL.md 正文
|
||||
- **THEN** SHALL 包含以下章节:Purpose、When to Use、Quick Reference、Workflow、References 链接
|
||||
|
||||
#### Scenario: SKILL.md 长度建议
|
||||
- **WHEN** 检查 SKILL.md 总字数
|
||||
- **THEN** 建议控制在 5000 字以内(含 YAML 前置元数据)
|
||||
|
||||
#### Scenario: 详细内容迁移
|
||||
- **WHEN** SKILL.md 包含详细示例、完整错误处理、最佳实践等冗长内容
|
||||
- **THEN** 这些内容 SHALL 迁移到 references/ 目录,SKILL.md 中保留链接引用
|
||||
|
||||
### Requirement: references 目录结构规范
|
||||
|
||||
每个 skill SHALL 包含 references/ 子目录,用于存放按需加载的详细文档。
|
||||
|
||||
#### Scenario: references 目录存在性
|
||||
- **WHEN** 检查 skill 目录结构
|
||||
- **THEN** SHALL 存在 references/ 子目录
|
||||
|
||||
#### Scenario: references 文件分类
|
||||
- **WHEN** 组织 references/ 目录内容
|
||||
- **THEN** SHALL 按主题分类为独立的 markdown 文件,如 examples.md、error-handling.md、best-practices.md
|
||||
|
||||
#### Scenario: SKILL.md 引用 references
|
||||
- **WHEN** SKILL.md 需要引用详细文档
|
||||
- **THEN** SHALL 在 SKILL.md 中明确列出 references/ 中的文件并说明用途
|
||||
|
||||
### Requirement: 内容迁移完整性
|
||||
|
||||
从 SKILL.md 迁移内容到 references/ 时,SHALL 确保所有原有信息被完整保留。
|
||||
|
||||
#### Scenario: 迁移前后对比
|
||||
- **WHEN** 完成单个 skill 的内容迁移
|
||||
- **THEN** references/ 中的文件 SHALL 包含原 SKILL.md 中被移除的所有详细内容
|
||||
|
||||
#### Scenario: 功能不变性
|
||||
- **WHEN** 完成结构优化后
|
||||
- **THEN** skill 的功能逻辑 SHALL 保持不变,仅文档结构发生变化
|
||||
@@ -0,0 +1,45 @@
|
||||
## 1. lyxy-runner-python 结构优化
|
||||
|
||||
- [x] 1.1 创建 `skills/lyxy-runner-python/references/` 目录
|
||||
- [x] 1.2 提取示例代码到 `references/examples.md`
|
||||
- [x] 1.3 提取错误处理内容到 `references/error-handling.md`
|
||||
- [x] 1.4 提取最佳实践和注意事项到 `references/best-practices.md`
|
||||
- [x] 1.5 精简 SKILL.md,保留 Purpose、When to Use、Quick Reference、Workflow 章节
|
||||
- [x] 1.6 在 SKILL.md 末尾添加 References 链接章节
|
||||
- [x] 1.7 验证 description 保持宽泛("Any task that requires Python processing...")
|
||||
|
||||
## 2. lyxy-runner-js 结构优化
|
||||
|
||||
- [x] 2.1 创建 `skills/lyxy-runner-js/references/` 目录
|
||||
- [x] 2.2 提取示例代码到 `references/examples.md`
|
||||
- [x] 2.3 提取错误处理内容到 `references/error-handling.md`
|
||||
- [x] 2.4 提取最佳实践和输出处理到 `references/best-practices.md`
|
||||
- [x] 2.5 精简 SKILL.md,保留 Purpose、When to Use、Quick Reference、Workflow 章节
|
||||
- [x] 2.6 在 SKILL.md 末尾添加 References 链接章节
|
||||
- [x] 2.7 验证 description 保持宽泛("Any task that requires Javascript/Typescript processing...")
|
||||
|
||||
## 3. lyxy-reader-office 结构优化
|
||||
|
||||
- [x] 3.1 创建 `skills/lyxy-reader-office/references/` 目录
|
||||
- [x] 3.2 提取详细示例到 `references/examples.md`
|
||||
- [x] 3.3 提取解析器说明和依赖安装到 `references/parsers.md`
|
||||
- [x] 3.4 提取错误处理和限制说明到 `references/error-handling.md`
|
||||
- [x] 3.5 精简 SKILL.md,保留 Purpose、When to Use、Quick Reference、Workflow 章节
|
||||
- [x] 3.6 在 SKILL.md 末尾添加 References 链接章节
|
||||
|
||||
## 4. lyxy-kb 结构优化
|
||||
|
||||
- [x] 4.1 创建 `skills/lyxy-kb/references/` 目录
|
||||
- [x] 4.2 提取目录结构和文件格式详细说明到 `references/structure.md`
|
||||
- [x] 4.3 提取文档生命周期和处理策略到 `references/workflow.md`
|
||||
- [x] 4.4 提取渐进式查询策略到 `references/query-strategy.md`
|
||||
- [x] 4.5 精简 SKILL.md,保留 Purpose、When to Use、Quick Reference、Workflow 章节
|
||||
- [x] 4.6 在 SKILL.md 末尾添加 References 链接章节
|
||||
- [x] 4.7 优化 description 添加用户触发短语
|
||||
|
||||
## 5. 验证与收尾
|
||||
|
||||
- [x] 5.1 对比每个 skill 的原始 SKILL.md 和重构后内容,确保信息完整
|
||||
- [x] 5.2 验证每个 skill 的 references/ 目录包含必要文件
|
||||
- [x] 5.3 验证每个 SKILL.md 的 References 章节正确链接到 references/ 文件
|
||||
- [x] 5.4 验证所有 description 符合格式要求(<1024 字符)
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-07
|
||||
@@ -0,0 +1,124 @@
|
||||
## Context
|
||||
|
||||
当前项目已有 `lyxy-reader-office` skill 用于解析办公文档,其架构设计成熟,包含统一命令行入口、多解析器回退机制、多功能查询等特性。现在需要创建 `lyxy-reader-html` skill,功能上类似但针对 HTML 内容,同时需支持 URL 下载能力。
|
||||
|
||||
**约束条件**:
|
||||
- 与 `lyxy-reader-office` 保持相同的用户体验(参数、输出格式)
|
||||
- 代码完全独立,不复用 `lyxy-reader-office` 的代码
|
||||
- 参考 `temp/downloader/download.py` 和 `temp/parser/parse.py` 的现有实现
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 创建完整的 `lyxy-reader-html` skill 目录结构
|
||||
- 实现统一的命令行入口 `parser.py`,支持 URL 和 HTML 文件输入
|
||||
- 实现下载器模块,按 pyppeteer → selenium → httpx → urllib 优先级回退
|
||||
- 实现解析器模块,按 trafilatura → domscribe → MarkItDown → html2text 优先级回退
|
||||
- 实现 HTML 预处理清理和 Markdown 后处理
|
||||
- 实现与 `lyxy-reader-office` 一致的查询功能(全文、字数、行数、标题、章节、搜索)
|
||||
|
||||
**Non-Goals:**
|
||||
- 不支持可配置的 HTML 清理选项
|
||||
- 不支持 JavaScript 渲染开关(默认启用完整下载链)
|
||||
- 不支持正文/全文切换(默认使用解析器的正文提取)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 目录结构参考 lyxy-reader-office
|
||||
**决策**:采用与 `lyxy-reader-office` 相同的目录结构
|
||||
```
|
||||
lyxy-reader-html/
|
||||
├── SKILL.md
|
||||
├── scripts/
|
||||
│ ├── parser.py # 统一入口
|
||||
│ ├── common.py # 公共函数
|
||||
│ ├── downloader.py # URL 下载
|
||||
│ ├── html_parser.py # HTML 解析
|
||||
│ └── README.md
|
||||
└── references/
|
||||
├── examples.md
|
||||
├── parsers.md
|
||||
└── error-handling.md
|
||||
```
|
||||
|
||||
**理由**:保持项目一致性,降低用户学习成本
|
||||
|
||||
---
|
||||
|
||||
### 2. 下载器优先级:pyppeteer → selenium → httpx → urllib
|
||||
**决策**:直接采用 `temp/downloader/download.py` 的优先级顺序
|
||||
- pyppeteer(支持 JS 渲染)
|
||||
- selenium(支持 JS 渲染,作为 pyppeteer 的备选)
|
||||
- httpx(轻量级 HTTP 客户端)
|
||||
- urllib(标准库,兜底)
|
||||
|
||||
**备选方案考虑**:
|
||||
- httpx → urllib → pyppeteer → selenium(速度优先)
|
||||
- 选择保持原顺序,因为 JS 渲染能力对许多现代网页很重要
|
||||
|
||||
---
|
||||
|
||||
### 3. 解析器优先级:trafilatura → domscribe → MarkItDown → html2text
|
||||
**决策**:采用精简后的 4 个解析器,顺序参考 `temp/parser/parse.py`
|
||||
1. trafilatura - 专门用于网页正文提取,质量高
|
||||
2. domscribe - 专注内容提取
|
||||
3. MarkItDown - 微软官方,格式规范
|
||||
4. html2text - 经典库,作为兜底
|
||||
|
||||
**备选方案考虑**:
|
||||
- 保留原 6 个解析器(增加 markdownify 和 html-to-markdown)
|
||||
- 选择精简为 4 个,减少维护复杂度
|
||||
|
||||
---
|
||||
|
||||
### 4. HTML 预处理清理默认开启且不可配置
|
||||
**决策**:解析前固定执行 HTML 清理,移除 script/style/link/svg 标签和 URL 属性
|
||||
- 使用 `temp/clean_html.py` 的清理逻辑
|
||||
- 不提供 `--no-clean` 参数
|
||||
|
||||
**理由**:简化设计,减少用户选择负担
|
||||
|
||||
---
|
||||
|
||||
### 5. Markdown 处理函数独立实现
|
||||
**决策**:在 `common.py` 中独立实现以下函数,不复用 `lyxy-reader-office`:
|
||||
- `remove_markdown_images()` - 移除图片标记
|
||||
- `normalize_markdown_whitespace()` - 规范化空行
|
||||
- `extract_titles()` - 提取标题
|
||||
- `extract_title_content()` - 提取章节内容
|
||||
- `search_markdown()` - 正则搜索
|
||||
|
||||
**理由**:保持 skill 之间完全隔离
|
||||
|
||||
---
|
||||
|
||||
### 6. 命令行参数与 lyxy-reader-office 保持一致
|
||||
**决策**:支持以下参数,与 `lyxy-reader-office` 完全一致:
|
||||
- (无参数):全文输出
|
||||
- `-c` / `--count`:字数统计
|
||||
- `-l` / `--lines`:行数统计
|
||||
- `-t` / `--titles`:提取标题
|
||||
- `-tc <name>` / `--title-content <name>`:提取章节
|
||||
- `-s <pattern>` / `--search <pattern>`:正则搜索
|
||||
- `-n <num>` / `--context <num>`:搜索上下文行数
|
||||
|
||||
**不添加** HTML 专用参数
|
||||
|
||||
**理由**:统一用户体验
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| pyppeteer/selenium 依赖重,安装困难 | 中 | 提供 httpx/urllib 作为轻量备选 |
|
||||
| trafilatura 可能提取不到正文 | 低 | 后续解析器会继续尝试 |
|
||||
| 不同解析器输出质量差异大 | 中 | 用户可通过安装不同依赖来间接选择解析器 |
|
||||
| URL 下载超时或被反爬 | 中 | 多下载器回退增加成功率 |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
不适用 - 这是新 skill 创建,无迁移需求。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无 - 所有决策已明确。
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
当前已有 `lyxy-reader-office` skill 用于解析办公文档,但缺少对 HTML 网页内容的解析能力。用户需要从 URL 或本地 HTML 文件中提取内容并转换为 Markdown 格式,同时支持标题提取、内容搜索等查询功能。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 创建新 skill `lyxy-reader-html`,目录结构参考 `lyxy-reader-office`
|
||||
- 实现命令行工具 `scripts/parser.py`,接受 URL 或 HTML 文件作为输入
|
||||
- URL 模式下按优先级尝试下载器:pyppeteer → selenium → httpx → urllib
|
||||
- HTML 解析按优先级尝试:trafilatura → domscribe → MarkItDown → html2text
|
||||
- 支持 HTML 预处理清理(移除 script/style/link 等标签和 URL 属性)
|
||||
- 实现查询功能:全文输出、字数统计、行数统计、标题提取、章节提取、正则搜索
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `html-document-parsing`: HTML 文档和 URL 内容解析能力,将 HTML 转换为 Markdown 并支持多种查询模式
|
||||
|
||||
### Modified Capabilities
|
||||
(无)
|
||||
|
||||
## Impact
|
||||
|
||||
- 新增目录 `skills/lyxy-reader-html/`
|
||||
- 新增 Python 脚本依赖:trafilatura、domscribe、markitdown、html2text、httpx、pyppeteer、selenium、beautifulsoup4
|
||||
- 与现有 skill 完全隔离,不影响其他功能
|
||||
@@ -0,0 +1,137 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 优先使用 lyxy-reader-html 解析 HTML 内容
|
||||
大模型在遇到 URL、.html 或 .htm 文件时,SHALL 优先激活并使用 lyxy-reader-html skill 来读取内容。
|
||||
|
||||
#### Scenario: 用户请求读取 URL
|
||||
- **WHEN** 用户提供以 http:// 或 https:// 开头的 URL
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-html skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
#### Scenario: 用户请求读取 HTML 文件
|
||||
- **WHEN** 用户提供的文件路径以 .html 或 .htm 结尾
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-html skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
### Requirement: 必须通过 lyxy-runner-python 执行脚本
|
||||
当环境中存在 lyxy-runner-python skill 时,大模型 SHALL 必须使用该 skill 来运行 parser.py 脚本。
|
||||
|
||||
#### Scenario: lyxy-runner-python 可用
|
||||
- **WHEN** 大模型环境中存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 通过 lyxy-runner-python skill 执行 parser.py
|
||||
- **AND** 利用 lyxy-runner-python 的自动依赖管理功能(uv)安装所需的 Python 包
|
||||
|
||||
#### Scenario: lyxy-runner-python 不可用
|
||||
- **WHEN** 大模型环境中不存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 降级到直接使用 Python 执行 parser.py
|
||||
- **AND** 提示用户当前使用直接执行模式
|
||||
- **AND** 禁止自动执行 pip install 安装依赖
|
||||
|
||||
### Requirement: 引导阅读 README 获取详细用法
|
||||
大模型在需要了解 parser.py 的详细使用方式时,SHALL 阅读 `scripts/README.md` 文件。
|
||||
|
||||
#### Scenario: 首次使用 skill 执行解析
|
||||
- **WHEN** 大模型首次使用 lyxy-reader-html skill 或不确定具体参数用法
|
||||
- **THEN** 大模型 SHALL 阅读 `scripts/README.md` 获取命令行参数、依赖安装和使用示例等详细信息
|
||||
|
||||
#### Scenario: 遇到特殊参数需求
|
||||
- **WHEN** 用户请求使用特殊功能(如章节提取、正则搜索等)
|
||||
- **THEN** 大模型 SHALL 参考 `scripts/README.md` 中的对应参数说明
|
||||
|
||||
### Requirement: 支持 URL 和 HTML 文件两种输入源
|
||||
系统 SHALL 支持从 URL 下载 HTML 内容或直接读取本地 HTML 文件两种输入方式。
|
||||
|
||||
#### Scenario: 输入为 URL
|
||||
- **WHEN** 输入以 http:// 或 https:// 开头
|
||||
- **THEN** 系统 SHALL 识别为 URL 模式
|
||||
- **AND** 尝试下载 URL 对应的 HTML 内容
|
||||
|
||||
#### Scenario: 输入为本地 HTML 文件
|
||||
- **WHEN** 输入是存在的本地文件且扩展名为 .html 或 .htm
|
||||
- **THEN** 系统 SHALL 识别为 HTML 文件模式
|
||||
- **AND** 直接读取文件内容进行解析
|
||||
|
||||
#### Scenario: 输入无法识别
|
||||
- **WHEN** 输入既不是 URL 也不是有效的 HTML 文件
|
||||
- **THEN** 系统 SHALL 输出错误信息
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: URL 下载器按优先级降级
|
||||
系统 SHALL 按 pyppeteer → selenium → httpx → urllib 的优先级尝试下载 URL 内容。
|
||||
|
||||
#### Scenario: 下载器按优先级降级
|
||||
- **WHEN** 优先级最高的下载器不可用或下载失败
|
||||
- **THEN** 系统自动尝试下一优先级的下载器
|
||||
- **AND** 记录每个下载器的失败原因
|
||||
|
||||
#### Scenario: 所有下载器失败
|
||||
- **WHEN** 所有下载策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种下载策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: HTML 解析器按优先级降级
|
||||
系统 SHALL 按 trafilatura → domscribe → MarkItDown → html2text 的优先级尝试解析 HTML 内容。
|
||||
|
||||
#### Scenario: 解析器按优先级降级
|
||||
- **WHEN** 优先级最高的解析器不可用或解析失败
|
||||
- **THEN** 系统自动尝试下一优先级的解析器
|
||||
- **AND** 记录每个解析器的失败原因
|
||||
|
||||
#### Scenario: 所有解析器失败
|
||||
- **WHEN** 所有解析策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种解析策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: HTML 内容预处理清理
|
||||
系统 SHALL 在解析前对 HTML 内容进行预处理清理,移除噪声元素。
|
||||
|
||||
#### Scenario: 清理 script 和 style 标签
|
||||
- **WHEN** HTML 内容包含 <script>、<style>、<link>、<svg> 标签
|
||||
- **THEN** 系统 SHALL 移除这些标签及其内容
|
||||
|
||||
#### Scenario: 清理 URL 属性
|
||||
- **WHEN** HTML 标签包含 href、src、srcset、action、data-*src 等 URL 属性
|
||||
- **THEN** 系统 SHALL 移除这些属性
|
||||
|
||||
#### Scenario: 清理 style 属性
|
||||
- **WHEN** HTML 标签包含 style 属性
|
||||
- **THEN** 系统 SHALL 移除 style 属性
|
||||
|
||||
### Requirement: 支持统一的查询功能
|
||||
系统 SHALL 提供统一的查询接口,包括全文提取、元数据查询、标题提取、章节提取和正则搜索。
|
||||
|
||||
#### Scenario: 获取文档字数
|
||||
- **WHEN** 用户请求获取文档的字数
|
||||
- **THEN** 系统使用 `-c` 参数返回文档的总字符数
|
||||
|
||||
#### Scenario: 获取文档行数
|
||||
- **WHEN** 用户请求获取文档的行数
|
||||
- **THEN** 系统使用 `-l` 参数返回文档的总行数
|
||||
|
||||
#### Scenario: 提取所有标题
|
||||
- **WHEN** 用户请求提取文档的标题结构
|
||||
- **THEN** 系统使用 `-t` 参数返回所有 1-6 级标题
|
||||
|
||||
#### Scenario: 提取指定章节内容
|
||||
- **WHEN** 用户请求提取特定标题名称的章节内容
|
||||
- **THEN** 系统使用 `-tc` 参数返回该章节的完整内容
|
||||
- **AND** 包含完整的上级标题链和所有下级内容
|
||||
|
||||
#### Scenario: 正则表达式搜索
|
||||
- **WHEN** 用户请求在文档中搜索关键词或模式
|
||||
- **THEN** 系统使用 `-s` 参数返回所有匹配结果及上下文
|
||||
- **AND** 默认包含前后各 2 行非空行上下文
|
||||
- **AND** 支持 `-n` 参数自定义上下文行数
|
||||
|
||||
### Requirement: Markdown 后处理
|
||||
系统 SHALL 对解析后的 Markdown 内容进行后处理,提升可读性。
|
||||
|
||||
#### Scenario: 移除图片标记
|
||||
- **WHEN** 解析结果包含 Markdown 图片语法 ``
|
||||
- **THEN** 系统 SHALL 移除这些图片标记
|
||||
|
||||
#### Scenario: 规范化空行
|
||||
- **WHEN** 解析结果包含连续 3 个或更多空行
|
||||
- **THEN** 系统 SHALL 将其合并为单个空行
|
||||
@@ -0,0 +1,58 @@
|
||||
## 1. 初始化 Skill 目录结构
|
||||
|
||||
- [x] 1.1 创建 `skills/lyxy-reader-html/` 目录
|
||||
- [x] 1.2 创建 `skills/lyxy-reader-html/scripts/` 子目录
|
||||
- [x] 1.3 创建 `skills/lyxy-reader-html/references/` 子目录
|
||||
|
||||
## 2. 创建 SKILL.md 主文档
|
||||
|
||||
- [x] 2.1 编写 YAML 前置元数据(name、description、compatibility)
|
||||
- [x] 2.2 编写 Purpose 章节
|
||||
- [x] 2.3 编写 When to Use 章节(含触发词)
|
||||
- [x] 2.4 编写 Quick Reference 章节(参数表)
|
||||
- [x] 2.5 编写 Workflow 章节
|
||||
- [x] 2.6 编写 References 章节
|
||||
|
||||
## 3. 实现 common.py 公共模块
|
||||
|
||||
- [x] 3.1 实现 HTML 清理函数 `clean_html_content()`
|
||||
- [x] 3.2 实现 Markdown 图片移除函数 `remove_markdown_images()`
|
||||
- [x] 3.3 实现 Markdown 空行规范化函数 `normalize_markdown_whitespace()`
|
||||
- [x] 3.4 实现标题级别检测函数 `get_heading_level()`
|
||||
- [x] 3.5 实现标题提取函数 `extract_titles()`
|
||||
- [x] 3.6 实现章节内容提取函数 `extract_title_content()`
|
||||
- [x] 3.7 实现正则搜索函数 `search_markdown()`
|
||||
|
||||
## 4. 实现 downloader.py URL 下载模块
|
||||
|
||||
- [x] 4.1 实现 `download_with_pyppeteer()` 函数
|
||||
- [x] 4.2 实现 `download_with_selenium()` 函数
|
||||
- [x] 4.3 实现 `download_with_httpx()` 函数
|
||||
- [x] 4.4 实现 `download_with_urllib()` 函数
|
||||
- [x] 4.5 实现统一的 `download_html()` 入口函数,按优先级尝试各下载器
|
||||
|
||||
## 5. 实现 html_parser.py HTML 解析模块
|
||||
|
||||
- [x] 5.1 实现 `parse_with_trafilatura()` 函数
|
||||
- [x] 5.2 实现 `parse_with_domscribe()` 函数
|
||||
- [x] 5.3 实现 `parse_with_markitdown()` 函数
|
||||
- [x] 5.4 实现 `parse_with_html2text()` 函数
|
||||
- [x] 5.5 实现统一的 `parse_html()` 入口函数,按优先级尝试各解析器
|
||||
|
||||
## 6. 实现 parser.py 命令行入口
|
||||
|
||||
- [x] 6.1 实现命令行参数解析(argparse)
|
||||
- [x] 6.2 实现输入源判断(URL / HTML 文件)
|
||||
- [x] 6.3 实现 URL 下载流程(如需要)
|
||||
- [x] 6.4 实现 HTML 清理流程
|
||||
- [x] 6.5 实现 HTML 解析流程
|
||||
- [x] 6.6 实现 Markdown 后处理(移除图片、规范化空行)
|
||||
- [x] 6.7 实现各查询模式(全文、字数、行数、标题、章节、搜索)
|
||||
- [x] 6.8 实现错误处理和退出码
|
||||
|
||||
## 7. 创建参考文档
|
||||
|
||||
- [x] 7.1 创建 `scripts/README.md` 详细使用文档
|
||||
- [x] 7.2 创建 `references/examples.md` 使用示例
|
||||
- [x] 7.3 创建 `references/parsers.md` 解析器说明
|
||||
- [x] 7.4 创建 `references/error-handling.md` 错误处理指南
|
||||
@@ -1,8 +1,21 @@
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
忽略项目目录下的「.opencode」和「opencode」两个目录,与开发的skill无关;
|
||||
忽略项目目录下的「.opencode」、「opencode」、「.claude」、「.codex」这几个目录,与开发的skill无关;
|
||||
这个项目是专门用于开发用于大模型工具的 skill;
|
||||
所有开发的 skill 都放在「skills」目录下,每个子目录都代表一个 skill,目录名为 skill 的名称;
|
||||
skill 名称统一加上「lyxy-」前缀;
|
||||
开发过程中的文档使用中文,面向中文开发者进行交流;
|
||||
「document」下是 skill 开发的相关规范文档;
|
||||
|
||||
Skill 编写规范:
|
||||
渐进式披露三层结构:
|
||||
第一层(YAML前置元数据)始终加载用于触发判断;第二层(SKILL.md正文)相关时加载含核心指令;第三层(references/)按需加载存放详细文档。
|
||||
YAML前置元数据:
|
||||
name用kebab-case与文件夹名一致;description少于1024字符必须包含「做什么」和「何时使用」;禁止XML标签;可选compatibility和metadata字段。
|
||||
description编写:[功能描述]+[触发条件/用户短语]+[关键能力]。具体场景包含触发短语和文件类型,通用工具保持宽泛。
|
||||
SKILL.md正文章节:Purpose简要用途、When to Use适用场景、Quick Reference核心命令、Workflow执行步骤、References链接。长度建议少于5000字,详细内容移至references/。
|
||||
references/目录:examples.md详细示例、error-handling.md错误处理、best-practices.md最佳实践。
|
||||
常见模式:顺序工作流编排带验证关卡、迭代优化循环、上下文感知选择工具、领域智能嵌入专业知识。
|
||||
故障排除:不触发检查description触发短语;过度触发添加负面触发器更具体化;指令不遵循保持简洁关键指令放顶部用项目符号。
|
||||
|
||||
更多skill开发技巧和规范参考「document」下的文档;
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Delegate execution to lyxy-runner-python skill
|
||||
当大模型执行 lyxy-reader-docx skill 时,应优先使用 lyxy-runner-python skill 来运行 docx_parser.py 脚本。如果 lyxy-runner-python skill 不可用,则直接使用 Python 执行。
|
||||
|
||||
#### Scenario: lyxy-runner-python available
|
||||
- **WHEN** 大模型环境中存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型通过 lyxy-runner-python skill 执行 docx_parser.py
|
||||
- **AND** 利用 lyxy-runner-python 的自动依赖管理功能安装所需的 Python 包(markitdown 或 python-docx)
|
||||
|
||||
#### Scenario: lyxy-runner-python unavailable
|
||||
- **WHEN** 大模型环境中不存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型直接使用 Python 执行 docx_parser.py
|
||||
- **AND** 提示用户正在使用直接执行模式(未使用 lyxy-runner-python)
|
||||
- **AND** 依赖 docx_parser.py 内部的多策略解析降级机制
|
||||
|
||||
### Requirement: Extract full text from DOCX file
|
||||
系统 SHALL 能够将 DOCX 文件完整解析为 Markdown 格式的文本内容,移除所有图片但保留文本格式(如标题、列表、粗体、斜体、表格)。
|
||||
|
||||
#### Scenario: Successful full text extraction
|
||||
- **WHEN** 用户请求解析一个有效的 .docx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 所有文本内容被保留
|
||||
- **AND** 所有图片被移除
|
||||
- **AND** 表格、列表、粗体、斜体等格式被转换为 Markdown 语法
|
||||
- **AND** 连续的空行被合并为一个空行
|
||||
|
||||
#### Scenario: Invalid DOCX file
|
||||
- **WHEN** 提供的文件不是有效的 DOCX 格式或已损坏
|
||||
- **THEN** 系统返回错误信息
|
||||
- **AND** 错误信息明确说明文件格式问题
|
||||
|
||||
#### Scenario: Empty DOCX file
|
||||
- **WHEN** 提供的 DOCX 文件为空或无文本内容
|
||||
- **THEN** 系统返回空字符串或相应的提示信息
|
||||
|
||||
### Requirement: Extract document metadata
|
||||
系统 SHALL 能够提供 DOCX 文档的元数据信息,包括字数和行数。
|
||||
|
||||
#### Scenario: Get word count
|
||||
- **WHEN** 用户请求获取 DOCX 文档的字数
|
||||
- **THEN** 系统返回文档的总字数(数字)
|
||||
- **AND** 字数统计基于解析后的 Markdown 内容
|
||||
|
||||
#### Scenario: Get line count
|
||||
- **WHEN** 用户请求获取 DOCX 文档的行数
|
||||
- **THEN** 系统返回文档的总行数(数字)
|
||||
- **AND** 行数统计基于解析后的 Markdown 内容
|
||||
|
||||
### Requirement: Extract document titles
|
||||
系统 SHALL 能够提取 DOCX 文档中的所有标题(1-6级标题),并按原始层级关系返回。
|
||||
|
||||
#### Scenario: Extract all titles
|
||||
- **WHEN** 用户请求提取 DOCX 文档的所有标题
|
||||
- **THEN** 系统返回所有 1-6 级标题
|
||||
- **AND** 每个标题以 Markdown 标题格式返回(如 `# 主标题`、`## 二级标题`)
|
||||
- **AND** 标题按文档中的原始顺序返回
|
||||
- **AND** 保留原始标题层级关系
|
||||
|
||||
#### Scenario: Document with no titles
|
||||
- **WHEN** DOCX 文档中不包含任何标题
|
||||
- **THEN** 系统返回空列表或无标题提示
|
||||
|
||||
### Requirement: Extract chapter content by title name
|
||||
系统 SHALL 能够根据标题名称提取指定章节的内容,包括完整的上级标题链和所有下级内容。
|
||||
|
||||
#### Scenario: Extract single chapter content
|
||||
- **WHEN** 用户请求提取特定标题名称的章节内容
|
||||
- **THEN** 系统返回该章节的完整内容
|
||||
- **AND** 包含完整的上级标题链(如:主标题 -> 章标题 -> 小节)
|
||||
- **AND** 包含该标题下的所有下级内容
|
||||
- **AND** 返回内容使用 Markdown 格式
|
||||
|
||||
#### Scenario: Extract multiple chapters with same name
|
||||
- **WHEN** 文档中存在多个同名标题且用户请求提取该标题内容
|
||||
- **THEN** 系统返回所有同名标题的章节内容
|
||||
- **AND** 每个章节都包含其完整的上级标题链
|
||||
- **AND** 各章节内容之间有明确的分隔
|
||||
|
||||
#### Scenario: Title not found
|
||||
- **WHEN** 用户请求提取的标题名称在文档中不存在
|
||||
- **THEN** 系统返回未找到标题的错误信息
|
||||
|
||||
### Requirement: Search document with regex
|
||||
系统 SHALL 能够使用正则表达式在 DOCX 文档中搜索关键词,并返回所有匹配结果及其上下文。
|
||||
|
||||
#### Scenario: Basic keyword search
|
||||
- **WHEN** 用户使用关键词搜索 DOCX 文档
|
||||
- **THEN** 系统返回所有匹配的文本片段
|
||||
- **AND** 每个结果包含默认的上下文(前后各 2 行)
|
||||
- **AND** 各结果之间使用 `---` 分隔
|
||||
- **AND** 上下文行数不包含空行
|
||||
|
||||
#### Scenario: Custom context lines
|
||||
- **WHEN** 用户指定上下文行数进行搜索
|
||||
- **THEN** 系统按指定行数返回上下文
|
||||
- **AND** 支持 0 行上下文(仅返回匹配行)
|
||||
- **AND** 上下文行数不包含空行
|
||||
|
||||
#### Scenario: Regex pattern search
|
||||
- **WHEN** 用户使用正则表达式模式搜索
|
||||
- **THEN** 系统支持 Python 标准正则表达式语法
|
||||
- **AND** 正确处理特殊字符的转义
|
||||
- **AND** 返回所有匹配模式的文本片段
|
||||
|
||||
#### Scenario: No matches found
|
||||
- **WHEN** 搜索关键词或模式在文档中无匹配
|
||||
- **THEN** 系统返回未找到匹配的提示信息
|
||||
|
||||
#### Scenario: Invalid regex pattern
|
||||
- **WHEN** 用户提供的正则表达式格式无效
|
||||
- **THEN** 系统返回正则表达式无效的错误信息
|
||||
|
||||
### Requirement: Multi-strategy parsing fallback
|
||||
系统 SHALL 按优先级尝试多种解析策略,确保最大的兼容性。
|
||||
|
||||
#### Scenario: Prefer MarkItDown parser
|
||||
- **WHEN** 系统检测到 `markitdown` 库已安装
|
||||
- **THEN** 系统优先使用 MarkItDown 解析器解析 DOCX 文件
|
||||
|
||||
#### Scenario: Fallback to python-docx
|
||||
- **WHEN** MarkItDown 解析失败或未安装,且 `python-docx` 库已安装
|
||||
- **THEN** 系统使用 python-docx 作为备选解析器
|
||||
|
||||
#### Scenario: Fallback to XML parsing
|
||||
- **WHEN** MarkItDown 和 python-docx 均未安装或解析失败
|
||||
- **THEN** 系统使用 XML 原生解析作为最后备选方案
|
||||
|
||||
#### Scenario: All parsers failed
|
||||
- **WHEN** 所有解析策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种解析策略的失败原因
|
||||
137
openspec/specs/html-document-parsing/spec.md
Normal file
137
openspec/specs/html-document-parsing/spec.md
Normal file
@@ -0,0 +1,137 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: 优先使用 lyxy-reader-html 解析 HTML 内容
|
||||
大模型在遇到 URL、.html 或 .htm 文件时,SHALL 优先激活并使用 lyxy-reader-html skill 来读取内容。
|
||||
|
||||
#### Scenario: 用户请求读取 URL
|
||||
- **WHEN** 用户提供以 http:// 或 https:// 开头的 URL
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-html skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
#### Scenario: 用户请求读取 HTML 文件
|
||||
- **WHEN** 用户提供的文件路径以 .html 或 .htm 结尾
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-html skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
### Requirement: 必须通过 lyxy-runner-python 执行脚本
|
||||
当环境中存在 lyxy-runner-python skill 时,大模型 SHALL 必须使用该 skill 来运行 parser.py 脚本。
|
||||
|
||||
#### Scenario: lyxy-runner-python 可用
|
||||
- **WHEN** 大模型环境中存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 通过 lyxy-runner-python skill 执行 parser.py
|
||||
- **AND** 利用 lyxy-runner-python 的自动依赖管理功能(uv)安装所需的 Python 包
|
||||
|
||||
#### Scenario: lyxy-runner-python 不可用
|
||||
- **WHEN** 大模型环境中不存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 降级到直接使用 Python 执行 parser.py
|
||||
- **AND** 提示用户当前使用直接执行模式
|
||||
- **AND** 禁止自动执行 pip install 安装依赖
|
||||
|
||||
### Requirement: 引导阅读 README 获取详细用法
|
||||
大模型在需要了解 parser.py 的详细使用方式时,SHALL 阅读 `scripts/README.md` 文件。
|
||||
|
||||
#### Scenario: 首次使用 skill 执行解析
|
||||
- **WHEN** 大模型首次使用 lyxy-reader-html skill 或不确定具体参数用法
|
||||
- **THEN** 大模型 SHALL 阅读 `scripts/README.md` 获取命令行参数、依赖安装和使用示例等详细信息
|
||||
|
||||
#### Scenario: 遇到特殊参数需求
|
||||
- **WHEN** 用户请求使用特殊功能(如章节提取、正则搜索等)
|
||||
- **THEN** 大模型 SHALL 参考 `scripts/README.md` 中的对应参数说明
|
||||
|
||||
### Requirement: 支持 URL 和 HTML 文件两种输入源
|
||||
系统 SHALL 支持从 URL 下载 HTML 内容或直接读取本地 HTML 文件两种输入方式。
|
||||
|
||||
#### Scenario: 输入为 URL
|
||||
- **WHEN** 输入以 http:// 或 https:// 开头
|
||||
- **THEN** 系统 SHALL 识别为 URL 模式
|
||||
- **AND** 尝试下载 URL 对应的 HTML 内容
|
||||
|
||||
#### Scenario: 输入为本地 HTML 文件
|
||||
- **WHEN** 输入是存在的本地文件且扩展名为 .html 或 .htm
|
||||
- **THEN** 系统 SHALL 识别为 HTML 文件模式
|
||||
- **AND** 直接读取文件内容进行解析
|
||||
|
||||
#### Scenario: 输入无法识别
|
||||
- **WHEN** 输入既不是 URL 也不是有效的 HTML 文件
|
||||
- **THEN** 系统 SHALL 输出错误信息
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: URL 下载器按优先级降级
|
||||
系统 SHALL 按 pyppeteer → selenium → httpx → urllib 的优先级尝试下载 URL 内容。
|
||||
|
||||
#### Scenario: 下载器按优先级降级
|
||||
- **WHEN** 优先级最高的下载器不可用或下载失败
|
||||
- **THEN** 系统自动尝试下一优先级的下载器
|
||||
- **AND** 记录每个下载器的失败原因
|
||||
|
||||
#### Scenario: 所有下载器失败
|
||||
- **WHEN** 所有下载策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种下载策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: HTML 解析器按优先级降级
|
||||
系统 SHALL 按 trafilatura → domscribe → MarkItDown → html2text 的优先级尝试解析 HTML 内容。
|
||||
|
||||
#### Scenario: 解析器按优先级降级
|
||||
- **WHEN** 优先级最高的解析器不可用或解析失败
|
||||
- **THEN** 系统自动尝试下一优先级的解析器
|
||||
- **AND** 记录每个解析器的失败原因
|
||||
|
||||
#### Scenario: 所有解析器失败
|
||||
- **WHEN** 所有解析策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种解析策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
### Requirement: HTML 内容预处理清理
|
||||
系统 SHALL 在解析前对 HTML 内容进行预处理清理,移除噪声元素。
|
||||
|
||||
#### Scenario: 清理 script 和 style 标签
|
||||
- **WHEN** HTML 内容包含 <script>、<style>、<link>、<svg> 标签
|
||||
- **THEN** 系统 SHALL 移除这些标签及其内容
|
||||
|
||||
#### Scenario: 清理 URL 属性
|
||||
- **WHEN** HTML 标签包含 href、src、srcset、action、data-*src 等 URL 属性
|
||||
- **THEN** 系统 SHALL 移除这些属性
|
||||
|
||||
#### Scenario: 清理 style 属性
|
||||
- **WHEN** HTML 标签包含 style 属性
|
||||
- **THEN** 系统 SHALL 移除 style 属性
|
||||
|
||||
### Requirement: 支持统一的查询功能
|
||||
系统 SHALL 提供统一的查询接口,包括全文提取、元数据查询、标题提取、章节提取和正则搜索。
|
||||
|
||||
#### Scenario: 获取文档字数
|
||||
- **WHEN** 用户请求获取文档的字数
|
||||
- **THEN** 系统使用 `-c` 参数返回文档的总字符数
|
||||
|
||||
#### Scenario: 获取文档行数
|
||||
- **WHEN** 用户请求获取文档的行数
|
||||
- **THEN** 系统使用 `-l` 参数返回文档的总行数
|
||||
|
||||
#### Scenario: 提取所有标题
|
||||
- **WHEN** 用户请求提取文档的标题结构
|
||||
- **THEN** 系统使用 `-t` 参数返回所有 1-6 级标题
|
||||
|
||||
#### Scenario: 提取指定章节内容
|
||||
- **WHEN** 用户请求提取特定标题名称的章节内容
|
||||
- **THEN** 系统使用 `-tc` 参数返回该章节的完整内容
|
||||
- **AND** 包含完整的上级标题链和所有下级内容
|
||||
|
||||
#### Scenario: 正则表达式搜索
|
||||
- **WHEN** 用户请求在文档中搜索关键词或模式
|
||||
- **THEN** 系统使用 `-s` 参数返回所有匹配结果及上下文
|
||||
- **AND** 默认包含前后各 2 行非空行上下文
|
||||
- **AND** 支持 `-n` 参数自定义上下文行数
|
||||
|
||||
### Requirement: Markdown 后处理
|
||||
系统 SHALL 对解析后的 Markdown 内容进行后处理,提升可读性。
|
||||
|
||||
#### Scenario: 移除图片标记
|
||||
- **WHEN** 解析结果包含 Markdown 图片语法 ``
|
||||
- **THEN** 系统 SHALL 移除这些图片标记
|
||||
|
||||
#### Scenario: 规范化空行
|
||||
- **WHEN** 解析结果包含连续 3 个或更多空行
|
||||
- **THEN** 系统 SHALL 将其合并为单个空行
|
||||
71
openspec/specs/kb-document-ingestion/spec.md
Normal file
71
openspec/specs/kb-document-ingestion/spec.md
Normal file
@@ -0,0 +1,71 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 扫描并识别待处理文件
|
||||
系统 SHALL 扫描项目 `sources/` 目录下的所有文件,并根据扩展名判断解析方式:
|
||||
- Office 文档(.docx、.pdf、.pptx、.xlsx):调用 lyxy-reader-office skill 解析
|
||||
- 其他文件(.md、.txt、.csv、.json、.xml、.yaml、.yml、.log、.html 等):直接读取内容
|
||||
|
||||
#### Scenario: sources 中有 office 文档
|
||||
- **WHEN** sources/ 中存在 `报告.docx`
|
||||
- **THEN** 系统 SHALL 使用 lyxy-reader-office skill(通过 lyxy-runner-python 执行)将其解析为 markdown
|
||||
|
||||
#### Scenario: sources 中有纯文本文件
|
||||
- **WHEN** sources/ 中存在 `config.json`
|
||||
- **THEN** 系统 SHALL 直接读取文件内容作为 parsed 产物
|
||||
|
||||
#### Scenario: sources 目录为空
|
||||
- **WHEN** sources/ 中没有任何文件
|
||||
- **THEN** 系统 SHALL 提示用户 sources/ 中无待处理文件
|
||||
|
||||
### Requirement: 同名不同扩展名冲突检测
|
||||
系统 SHALL 在解析前检测 sources/ 中是否存在同名但不同扩展名的文件(如 `技术方案.pdf` 和 `技术方案.docx`),因为 parsed 产物都会命名为相同的 `.md` 文件。同时也需检测 sources/ 中的文件名是否与 manifest.json 中已有记录的不同扩展名文件冲突。
|
||||
|
||||
#### Scenario: sources 中存在同名不同扩展名文件
|
||||
- **WHEN** sources/ 中同时存在 `技术方案.pdf` 和 `技术方案.docx`
|
||||
- **THEN** 系统 SHALL 拒绝处理这两个文件,并提示用户重命名其中一个以消除冲突
|
||||
|
||||
#### Scenario: sources 中文件与已入库文件同名但不同扩展名
|
||||
- **WHEN** manifest.json 中已有 `技术方案`(ext: `.pdf`)的记录,且 sources/ 中出现 `技术方案.docx`
|
||||
- **THEN** 系统 SHALL 拒绝处理该文件,并提示用户重命名
|
||||
|
||||
### Requirement: 生成 parsed markdown 文件
|
||||
系统 SHALL 将解析后的内容写入 `parsed/<文件名>.md`,文件头部 MUST 包含元信息注释:
|
||||
```
|
||||
<!-- source: <原始文件名含扩展名> -->
|
||||
<!-- archived: archive/<文件名_时间戳>.<扩展名> -->
|
||||
<!-- parsed_at: <解析时间 YYYY-MM-DD HH:mm> -->
|
||||
```
|
||||
若 parsed/ 中已存在同名文件(同一文档的更新版本),SHALL 覆盖旧文件。
|
||||
|
||||
#### Scenario: 首次解析文件
|
||||
- **WHEN** 解析 `需求文档.docx`,parsed/ 中不存在 `需求文档.md`
|
||||
- **THEN** 系统创建 `parsed/需求文档.md`,头部包含 source、archived、parsed_at 元信息,正文为解析后的 markdown 内容
|
||||
|
||||
#### Scenario: 更新已有文件
|
||||
- **WHEN** 解析 `技术方案.pdf`,parsed/ 中已存在 `技术方案.md`(上一版本)
|
||||
- **THEN** 系统覆盖 `parsed/技术方案.md`,元信息更新为最新版本的 archive 路径和时间
|
||||
|
||||
### Requirement: 归档原始文件
|
||||
系统 SHALL 将已解析的原始文件从 `sources/` 移动到 `archive/`,文件名格式为 `<文件名_YYYYMMDDHHmm>.<扩展名>`。每个进入 archive 的文件都 MUST 带有时间戳后缀,即使该文件只有一个版本。
|
||||
|
||||
#### Scenario: 归档文件
|
||||
- **WHEN** `需求文档.docx` 解析完成,当前时间为 2026-02-18 16:00
|
||||
- **THEN** 原始文件移动为 `archive/需求文档_202602181600.docx`
|
||||
|
||||
#### Scenario: 同名文件多次入库
|
||||
- **WHEN** `技术方案.pdf` 第二次入库,archive 中已有 `技术方案_202602181600.pdf`
|
||||
- **THEN** 新版本归档为 `技术方案_202602181725.pdf`(以当前时间为时间戳),两个版本并存于 archive
|
||||
|
||||
### Requirement: 更新 manifest.json
|
||||
系统 SHALL 在每个文件处理完成后更新 manifest.json:
|
||||
- 新文件:在 files 数组中追加新条目,包含 name、ext、parsed 路径,versions 数组包含首个版本的 archived 路径、hash 和 ingested_at
|
||||
- 已有文件更新:在对应条目的 versions 数组中追加新版本记录
|
||||
- last_ingest 时间戳 SHALL 更新为当前 ingest 的时间
|
||||
|
||||
#### Scenario: 新文件入库更新 manifest
|
||||
- **WHEN** `需求文档.docx` 首次解析完成
|
||||
- **THEN** manifest.json 的 files 数组中追加 `{"name": "需求文档", "ext": ".docx", "parsed": "parsed/需求文档.md", "versions": [{"archived": "archive/需求文档_202602181600.docx", "hash": "sha256:...", "ingested_at": "2026-02-18T16:00"}]}`
|
||||
|
||||
#### Scenario: 已有文件更新 manifest
|
||||
- **WHEN** `技术方案.pdf` 第二次入库
|
||||
- **THEN** manifest.json 中该文件条目的 versions 数组追加新版本记录,不删除旧版本记录
|
||||
38
openspec/specs/kb-knowledge-query/spec.md
Normal file
38
openspec/specs/kb-knowledge-query/spec.md
Normal file
@@ -0,0 +1,38 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 渐进式查询策略
|
||||
系统 SHALL 采用渐进式查询策略回答用户问题,以节省 token 消耗:
|
||||
1. 首先读取 project.md 获取整体概述和文件索引
|
||||
2. 根据用户问题和文件索引判断需要查阅哪些 parsed 文件
|
||||
3. 按需读取相关 parsed 文件的全部或部分内容
|
||||
4. 基于获取的信息回答问题
|
||||
|
||||
#### Scenario: 问题可通过摘要回答
|
||||
- **WHEN** 用户提问"这个项目主要做什么?",且 project.md 的概述中已包含足够信息
|
||||
- **THEN** 系统仅基于 project.md 内容回答,不加载 parsed 文件
|
||||
|
||||
#### Scenario: 问题需要查阅具体文件
|
||||
- **WHEN** 用户提问"系统的权限控制是怎么设计的?",且 project.md 文件索引中 `需求文档` 的摘要提到了权限相关内容
|
||||
- **THEN** 系统读取 `parsed/需求文档.md` 获取详细信息后回答
|
||||
|
||||
### Requirement: 来源标注
|
||||
系统在回答中引用具体信息时 SHALL 标注文件来源,格式为:「根据《文件名》(parsed/文件名.md),...」。来源标注 MUST 指向 parsed 目录下的具体文件。
|
||||
|
||||
#### Scenario: 回答中包含来源标注
|
||||
- **WHEN** 系统从 `parsed/技术方案.md` 中获取信息来回答问题
|
||||
- **THEN** 回答中 SHALL 包含类似「根据《技术方案》(parsed/技术方案.md),系统采用微服务架构...」的来源标注
|
||||
|
||||
#### Scenario: 回答综合多个文件
|
||||
- **WHEN** 回答需要综合 `parsed/需求文档.md` 和 `parsed/技术方案.md` 的信息
|
||||
- **THEN** 回答中 SHALL 分别标注各信息点的来源文件
|
||||
|
||||
### Requirement: 会话问答模式
|
||||
系统 SHALL 在 ask 模式下保持会话上下文,用户可以连续提问而无需每次重新加载知识库。会话的退出由用户自然决定(开启新话题或新会话),系统不主动终止会话。
|
||||
|
||||
#### Scenario: 多轮追问
|
||||
- **WHEN** 用户先问"系统用了什么技术栈?",接着追问"数据库选型的理由是什么?"
|
||||
- **THEN** 系统在第二次回答时保持之前的上下文,可复用已加载的 parsed 文件内容
|
||||
|
||||
#### Scenario: 知识库中无相关信息
|
||||
- **WHEN** 用户提出的问题在 project.md 和所有 parsed 文件中均无相关信息
|
||||
- **THEN** 系统 SHALL 明确告知用户当前知识库中未找到相关信息,而非编造答案
|
||||
24
openspec/specs/kb-project-management/spec.md
Normal file
24
openspec/specs/kb-project-management/spec.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 初始化知识项目目录结构
|
||||
系统 SHALL 在 CWD 下创建以指定名称命名的子目录,并在其中生成以下固定结构:
|
||||
- `project.md`:初始内容包含项目名称标题、空的概述/关键信息段落、空的文件索引表和空的更新记录
|
||||
- `manifest.json`:初始内容包含项目名称、创建时间、空的 files 数组
|
||||
- `parsed/` 目录
|
||||
- `sources/` 目录
|
||||
- `archive/` 目录
|
||||
|
||||
#### Scenario: 成功初始化新项目
|
||||
- **WHEN** 用户执行 `/lyxy-kb-init my-project`,且 CWD 下不存在 `my-project` 目录
|
||||
- **THEN** 系统创建 `my-project/` 目录及完整子结构(project.md、manifest.json、parsed/、sources/、archive/),并提示用户将文档放入 sources/ 目录
|
||||
|
||||
#### Scenario: 目标目录已存在
|
||||
- **WHEN** 用户执行 `/lyxy-kb-init my-project`,且 CWD 下已存在 `my-project` 目录
|
||||
- **THEN** 系统 SHALL 提示用户该目录已存在,不覆盖任何现有内容
|
||||
|
||||
### Requirement: 项目目录结构规范
|
||||
知识项目 SHALL 遵循固定的目录结构:`project.md`、`manifest.json`、`parsed/`、`sources/`、`archive/`。所有 command 和 skill 操作 SHALL 基于此结构进行,不在结构外创建额外文件或目录。
|
||||
|
||||
#### Scenario: 验证项目结构完整性
|
||||
- **WHEN** 任何 command(ingest/rebuild/ask)在指定项目目录上执行
|
||||
- **THEN** 系统 SHALL 先检查目录结构是否完整(包含 project.md、manifest.json、parsed/、sources/、archive/),若不完整则提示用户先执行 init
|
||||
53
openspec/specs/kb-project-summary/spec.md
Normal file
53
openspec/specs/kb-project-summary/spec.md
Normal file
@@ -0,0 +1,53 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: project.md 格式规范
|
||||
project.md SHALL 遵循以下固定结构:
|
||||
|
||||
```markdown
|
||||
# <项目名称>
|
||||
|
||||
## 概述
|
||||
(高度总结的项目信息)
|
||||
|
||||
## 关键信息
|
||||
(从所有文档中提炼的核心要点)
|
||||
|
||||
## 文件索引
|
||||
|
||||
| 文件名 | 解析文件 | 最新归档 | 摘要 |
|
||||
|--------|----------|----------|------|
|
||||
|
||||
## 更新记录
|
||||
```
|
||||
|
||||
初始化时概述和关键信息为空,文件索引表为空表头,更新记录为空。
|
||||
|
||||
#### Scenario: 初始化后的 project.md
|
||||
- **WHEN** 执行 `/lyxy-kb-init my-project`
|
||||
- **THEN** 生成的 project.md 包含 `# my-project` 标题、空的概述/关键信息段落、空的文件索引表(仅表头)和空的更新记录
|
||||
|
||||
### Requirement: 增量追加模式
|
||||
执行 ingest 时,系统 SHALL 以增量方式更新 project.md:
|
||||
- 在文件索引表中追加新解析文件的行(文件名、parsed 路径、最新 archive 路径、该文件的简要摘要)
|
||||
- 在更新记录中追加本次 ingest 的条目(时间和处理的文件列表)
|
||||
- 已有文件更新时:覆盖文件索引表中对应行的最新归档路径和摘要
|
||||
- 概述和关键信息部分 SHALL NOT 在增量模式下自动更新
|
||||
|
||||
#### Scenario: 首次 ingest 追加索引
|
||||
- **WHEN** 首次 ingest 解析了 `需求文档.docx`
|
||||
- **THEN** project.md 文件索引表中追加一行,更新记录中追加 `- 2026-02-18 16:00: 解析 需求文档.docx`
|
||||
|
||||
#### Scenario: 已有文件更新时追加索引
|
||||
- **WHEN** `技术方案.pdf` 第二次入库
|
||||
- **THEN** project.md 文件索引表中该文件的最新归档路径和摘要被更新,更新记录追加新条目
|
||||
|
||||
### Requirement: 全量重写模式
|
||||
执行 rebuild 时,系统 SHALL 读取所有 `parsed/*.md` 文件,重新生成整个 project.md:
|
||||
- 概述:基于所有 parsed 文件内容重新生成高度总结
|
||||
- 关键信息:重新提炼核心要点
|
||||
- 文件索引:基于 manifest.json 和 parsed 文件重新生成完整索引表
|
||||
- 更新记录:保留历史记录,追加本次 rebuild 条目
|
||||
|
||||
#### Scenario: 全量重写
|
||||
- **WHEN** 用户执行 `/lyxy-kb-rebuild my-project`,项目中有 3 个 parsed 文件
|
||||
- **THEN** 系统读取所有 3 个 parsed 文件,重新生成 project.md 的概述、关键信息和文件索引,更新记录追加 rebuild 条目
|
||||
115
openspec/specs/office-document-parsing/spec.md
Normal file
115
openspec/specs/office-document-parsing/spec.md
Normal file
@@ -0,0 +1,115 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: 优先使用 lyxy-reader-office 解析办公文档
|
||||
大模型在遇到 .docx、.xlsx、.pptx、.pdf 文件时,SHALL 优先激活并使用 lyxy-reader-office skill 来读取文件内容。
|
||||
|
||||
#### Scenario: 用户请求读取支持的文件格式
|
||||
- **WHEN** 用户请求读取或解析 .docx、.xlsx、.pptx 或 .pdf 文件
|
||||
- **THEN** 大模型 SHALL 激活 lyxy-reader-office skill
|
||||
- **AND** 使用 skill 目录下的 `scripts/parser.py` 执行解析
|
||||
|
||||
#### Scenario: 文件扩展名自动识别
|
||||
- **WHEN** 用户提供的文件路径以 .docx、.xlsx、.pptx 或 .pdf 结尾
|
||||
- **THEN** 大模型 SHALL 自动识别为 lyxy-reader-office skill 的处理范围
|
||||
- **AND** 无需用户显式指定使用哪个 skill
|
||||
|
||||
### Requirement: 必须通过 lyxy-runner-python 执行脚本
|
||||
当环境中存在 lyxy-runner-python skill 时,大模型 SHALL 必须使用该 skill 来运行 parser.py 脚本。
|
||||
|
||||
#### Scenario: lyxy-runner-python 可用
|
||||
- **WHEN** 大模型环境中存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 通过 lyxy-runner-python skill 执行 parser.py
|
||||
- **AND** 利用 lyxy-runner-python 的自动依赖管理功能(uv)安装所需的 Python 包
|
||||
|
||||
#### Scenario: lyxy-runner-python 不可用
|
||||
- **WHEN** 大模型环境中不存在 lyxy-runner-python skill
|
||||
- **THEN** 大模型 SHALL 降级到直接使用 Python 执行 parser.py
|
||||
- **AND** 提示用户当前使用直接执行模式
|
||||
- **AND** 禁止自动执行 pip install 安装依赖
|
||||
|
||||
### Requirement: 引导阅读 README 获取详细用法
|
||||
大模型在需要了解 parser.py 的详细使用方式时,SHALL 阅读 `scripts/README.md` 文件。
|
||||
|
||||
#### Scenario: 首次使用 skill 执行解析
|
||||
- **WHEN** 大模型首次使用 lyxy-reader-office skill 或不确定具体参数用法
|
||||
- **THEN** 大模型 SHALL 阅读 `scripts/README.md` 获取命令行参数、依赖安装和使用示例等详细信息
|
||||
|
||||
#### Scenario: 遇到特殊参数需求
|
||||
- **WHEN** 用户请求使用特殊功能(如 PDF OCR、章节提取、正则搜索等)
|
||||
- **THEN** 大模型 SHALL 参考 `scripts/README.md` 中的对应参数说明
|
||||
|
||||
### Requirement: 支持多格式文档解析
|
||||
系统 SHALL 支持 DOCX、PPTX、XLSX、PDF 四种格式的文档解析,将文件转换为 Markdown 格式输出。
|
||||
|
||||
#### Scenario: 解析 DOCX 文件
|
||||
- **WHEN** 用户请求解析 .docx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 保留标题、列表、表格、粗体、斜体等格式
|
||||
- **AND** 移除图片
|
||||
|
||||
#### Scenario: 解析 PPTX 文件
|
||||
- **WHEN** 用户请求解析 .pptx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 每张幻灯片以 `## Slide N` 为标题
|
||||
- **AND** 幻灯片之间以 `---` 分隔
|
||||
|
||||
#### Scenario: 解析 XLSX 文件
|
||||
- **WHEN** 用户请求解析 .xlsx 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 以 `## SheetName` 区分工作表
|
||||
- **AND** 数据以 Markdown 表格呈现
|
||||
|
||||
#### Scenario: 解析 PDF 文件
|
||||
- **WHEN** 用户请求解析 .pdf 文件
|
||||
- **THEN** 系统返回完整的 Markdown 格式文本
|
||||
- **AND** 默认使用普通文本提取模式
|
||||
|
||||
#### Scenario: PDF OCR 高精度模式
|
||||
- **WHEN** 用户请求对 PDF 文件启用 OCR 或高精度解析
|
||||
- **THEN** 系统使用 `--high-res` 参数启用 OCR 版面分析
|
||||
- **AND** 通过 Docling OCR 或 unstructured hi_res 策略处理
|
||||
|
||||
### Requirement: 统一的查询功能
|
||||
系统 SHALL 对所有支持的文件格式提供统一的查询接口,包括全文提取、元数据查询、标题提取、章节提取和正则搜索。
|
||||
|
||||
#### Scenario: 获取文档字数
|
||||
- **WHEN** 用户请求获取文档的字数
|
||||
- **THEN** 系统使用 `-c` 参数返回文档的总字符数
|
||||
|
||||
#### Scenario: 获取文档行数
|
||||
- **WHEN** 用户请求获取文档的行数
|
||||
- **THEN** 系统使用 `-l` 参数返回文档的总行数
|
||||
|
||||
#### Scenario: 提取所有标题
|
||||
- **WHEN** 用户请求提取文档的标题结构
|
||||
- **THEN** 系统使用 `-t` 参数返回所有 1-6 级标题
|
||||
|
||||
#### Scenario: 提取指定章节内容
|
||||
- **WHEN** 用户请求提取特定标题名称的章节内容
|
||||
- **THEN** 系统使用 `-tc` 参数返回该章节的完整内容
|
||||
- **AND** 包含完整的上级标题链和所有下级内容
|
||||
|
||||
#### Scenario: 正则表达式搜索
|
||||
- **WHEN** 用户请求在文档中搜索关键词或模式
|
||||
- **THEN** 系统使用 `-s` 参数返回所有匹配结果及上下文
|
||||
- **AND** 默认包含前后各 2 行非空行上下文
|
||||
- **AND** 支持 `-n` 参数自定义上下文行数
|
||||
|
||||
### Requirement: 多策略解析降级
|
||||
系统 SHALL 对每种文件格式按优先级尝试多种解析策略,确保最大的兼容性。
|
||||
|
||||
#### Scenario: 解析器按优先级降级
|
||||
- **WHEN** 优先级最高的解析器不可用或解析失败
|
||||
- **THEN** 系统自动尝试下一优先级的解析器
|
||||
- **AND** 记录每个解析器的失败原因
|
||||
|
||||
#### Scenario: 所有解析器失败
|
||||
- **WHEN** 某种格式的所有解析策略均失败
|
||||
- **THEN** 系统返回详细的失败信息
|
||||
- **AND** 列出每种解析策略的失败原因
|
||||
- **AND** 以退出码 1 退出
|
||||
|
||||
#### Scenario: DOCX/PPTX/XLSX 无依赖运行
|
||||
- **WHEN** 未安装任何第三方解析库
|
||||
- **THEN** DOCX、PPTX、XLSX 文件 SHALL 仍可通过内置 XML 原生解析工作
|
||||
- **AND** PDF 至少需要 pypdf 才能解析
|
||||
83
openspec/specs/skill-progressive-disclosure/spec.md
Normal file
83
openspec/specs/skill-progressive-disclosure/spec.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# skill-progressive-disclosure
|
||||
|
||||
## Purpose
|
||||
|
||||
定义 skill 的渐进式披露(Progressive Disclosure)结构规范,将 skill 内容按加载时机分为三层:YAML 前置元数据、SKILL.md 正文、references/ 详细文档。通过分层加载减少 token 消耗,提高 Claude 的响应效率。
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 三层渐进式披露结构
|
||||
|
||||
每个 skill 目录 SHALL 采用三层渐进式披露结构,按加载时机分为:第一层(YAML 前置元数据)、第二层(SKILL.md 正文)、第三层(references/ 目录)。
|
||||
|
||||
#### Scenario: skill 目录包含完整三层结构
|
||||
- **WHEN** 检查任意 skill 目录结构
|
||||
- **THEN** 目录 SHALL 包含 SKILL.md 文件和 references/ 子目录
|
||||
|
||||
#### Scenario: 第一层始终加载
|
||||
- **WHEN** Claude 启动会话时
|
||||
- **THEN** 所有 skill 的 YAML 前置元数据 SHALL 被加载到系统提示中
|
||||
|
||||
### Requirement: YAML 前置元数据内容规范
|
||||
|
||||
YAML 前置元数据 SHALL 仅包含触发判断所需的最小信息,使 Claude 能够判断何时使用该 skill。
|
||||
|
||||
#### Scenario: 必需字段
|
||||
- **WHEN** 编写 SKILL.md 的 YAML 前置元数据
|
||||
- **THEN** SHALL 包含 name 和 description 字段
|
||||
|
||||
#### Scenario: description 格式
|
||||
- **WHEN** 编写 description 字段
|
||||
- **THEN** SHALL 同时包含「做什么」和「何时使用」两部分信息
|
||||
|
||||
#### Scenario: description 长度限制
|
||||
- **WHEN** 检查 description 字段
|
||||
- **THEN** 内容 SHALL 少于 1024 个字符
|
||||
|
||||
#### Scenario: runner skill 的 description 宽泛性
|
||||
- **WHEN** skill 用途为通用脚本执行(如 Python/JavaScript runner)
|
||||
- **THEN** description SHALL 保持宽泛,以匹配所有相关处理任务
|
||||
|
||||
### Requirement: SKILL.md 正文内容规范
|
||||
|
||||
SKILL.md 正文 SHALL 包含核心工作流程和关键指令,保持精简以减少 token 消耗。
|
||||
|
||||
#### Scenario: SKILL.md 必需章节
|
||||
- **WHEN** 编写 SKILL.md 正文
|
||||
- **THEN** SHALL 包含以下章节:Purpose、When to Use、Quick Reference、Workflow、References 链接
|
||||
|
||||
#### Scenario: SKILL.md 长度建议
|
||||
- **WHEN** 检查 SKILL.md 总字数
|
||||
- **THEN** 建议控制在 5000 字以内(含 YAML 前置元数据)
|
||||
|
||||
#### Scenario: 详细内容迁移
|
||||
- **WHEN** SKILL.md 包含详细示例、完整错误处理、最佳实践等冗长内容
|
||||
- **THEN** 这些内容 SHALL 迁移到 references/ 目录,SKILL.md 中保留链接引用
|
||||
|
||||
### Requirement: references 目录结构规范
|
||||
|
||||
每个 skill SHALL 包含 references/ 子目录,用于存放按需加载的详细文档。
|
||||
|
||||
#### Scenario: references 目录存在性
|
||||
- **WHEN** 检查 skill 目录结构
|
||||
- **THEN** SHALL 存在 references/ 子目录
|
||||
|
||||
#### Scenario: references 文件分类
|
||||
- **WHEN** 组织 references/ 目录内容
|
||||
- **THEN** SHALL 按主题分类为独立的 markdown 文件,如 examples.md、error-handling.md、best-practices.md
|
||||
|
||||
#### Scenario: SKILL.md 引用 references
|
||||
- **WHEN** SKILL.md 需要引用详细文档
|
||||
- **THEN** SHALL 在 SKILL.md 中明确列出 references/ 中的文件并说明用途
|
||||
|
||||
### Requirement: 内容迁移完整性
|
||||
|
||||
从 SKILL.md 迁移内容到 references/ 时,SHALL 确保所有原有信息被完整保留。
|
||||
|
||||
#### Scenario: 迁移前后对比
|
||||
- **WHEN** 完成单个 skill 的内容迁移
|
||||
- **THEN** references/ 中的文件 SHALL 包含原 SKILL.md 中被移除的所有详细内容
|
||||
|
||||
#### Scenario: 功能不变性
|
||||
- **WHEN** 完成结构优化后
|
||||
- **THEN** skill 的功能逻辑 SHALL 保持不变,仅文档结构发生变化
|
||||
134
publish.sh
Normal file
134
publish.sh
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Skill 发布脚本
|
||||
#
|
||||
# 使用方式:
|
||||
# ./publish.sh <skill-name>
|
||||
#
|
||||
# 示例:
|
||||
# ./publish.sh lyxy-kb
|
||||
# ./publish.sh lyxy-reader-office
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# 配置
|
||||
TARGET_REPO_URL="https://github.com/lanyuanxiaoyao/skills.git"
|
||||
TEMP_DIR_BASE="${TMPDIR:-/tmp}/lyxy-skill-publish"
|
||||
|
||||
# 颜色输出
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印信息
|
||||
info() {
|
||||
echo -e "${GREEN}>>>${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}错误:${NC} $1"
|
||||
}
|
||||
|
||||
# 显示使用说明
|
||||
show_usage() {
|
||||
echo "使用方式:"
|
||||
echo " $0 <skill-name>"
|
||||
echo ""
|
||||
echo "可用的 skills:"
|
||||
for dir in skills/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
name="$(basename "$dir")"
|
||||
echo " - $name"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 lyxy-kb"
|
||||
echo " $0 lyxy-reader-office"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 检查参数
|
||||
if [ $# -eq 0 ]; then
|
||||
error "缺少 skill 名称参数"
|
||||
show_usage
|
||||
fi
|
||||
|
||||
SKILL_NAME="$1"
|
||||
SKILL_DIR="$SCRIPT_DIR/skills/$SKILL_NAME"
|
||||
TARGET_PATH="skills/$SKILL_NAME"
|
||||
|
||||
# 检查 skill 目录是否存在
|
||||
if [ ! -d "$SKILL_DIR" ]; then
|
||||
error "Skill 目录不存在: $SKILL_DIR"
|
||||
echo ""
|
||||
echo "可用的 skills:"
|
||||
for dir in skills/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
name="$(basename "$dir")"
|
||||
echo " - $name"
|
||||
fi
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "发布 Skill: $SKILL_NAME"
|
||||
info "目标仓库: $TARGET_REPO_URL"
|
||||
info "目标路径: $TARGET_PATH"
|
||||
echo ""
|
||||
|
||||
# 创建临时目录
|
||||
TIMESTAMP=$(date +%s)
|
||||
TEMP_DIR="$TEMP_DIR_BASE-$TIMESTAMP"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
if [ -d "$TEMP_DIR" ]; then
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Clone 仓库
|
||||
info "[1/4] Clone 仓库..."
|
||||
REPO_DIR="$TEMP_DIR/skills-repo"
|
||||
git clone --depth 1 "$TARGET_REPO_URL" "$REPO_DIR"
|
||||
|
||||
# 清空目标目录
|
||||
info "[2/4] 清空目标目录..."
|
||||
TARGET_DIR="$REPO_DIR/$TARGET_PATH"
|
||||
if [ -d "$TARGET_DIR" ]; then
|
||||
rm -rf "$TARGET_DIR"
|
||||
fi
|
||||
mkdir -p "$TARGET_DIR"
|
||||
|
||||
# 复制内容
|
||||
info "[3/4] 复制 Skill 文件..."
|
||||
# 复制除了 __pycache__ 之外的所有内容
|
||||
for item in "$SKILL_DIR"/*; do
|
||||
basename_item="$(basename "$item")"
|
||||
# 跳过 __pycache__
|
||||
if [ "$basename_item" = "__pycache__" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$item" ]; then
|
||||
cp -r "$item" "$TARGET_DIR/"
|
||||
else
|
||||
cp "$item" "$TARGET_DIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
# 提交并推送
|
||||
info "[4/4] 提交并推送到 GitHub..."
|
||||
cd "$REPO_DIR"
|
||||
git add .
|
||||
git commit -m "publish: $SKILL_NAME"
|
||||
git push
|
||||
|
||||
info "发布成功!"
|
||||
@@ -1,279 +0,0 @@
|
||||
---
|
||||
name: lyxy-reader-docx
|
||||
description: 优先解析 docx 文档的 skill,将 DOCX 文件转换为纯文本内容,不支持图片和格式提取。
|
||||
compatibility: Requires Python 3.6+ and at least one of: markitdown or python-docx
|
||||
---
|
||||
|
||||
# DOCX 文档解析 Skill
|
||||
|
||||
将 Microsoft Word (.docx) 文档解析为纯文本内容,支持多种解析模式和检索功能。
|
||||
|
||||
## Purpose
|
||||
|
||||
**依赖选项**: 此 skill 可以使用 lyxy-runner-python skill(推荐)或直接使用 Python 执行。
|
||||
|
||||
### 优先使用 lyxy-runner-python
|
||||
|
||||
如果环境中存在 lyxy-runner-python skill,应优先使用它来执行 docx_parser.py 脚本:
|
||||
- lyxy-runner-python 使用 uv 管理依赖,自动安装 markitdown 或 python-docx
|
||||
- 环境隔离,不污染系统 Python
|
||||
- 跨平台兼容(Windows/macOS/Linux)
|
||||
|
||||
### 降级到直接执行
|
||||
|
||||
如果环境中不存在 lyxy-runner-python skill,则直接使用 Python 执行 docx_parser.py:
|
||||
- 需要手动安装 markitdown 或 python-docx
|
||||
- 脚本内部实现了多策略解析降级:MarkItDown → python-docx → XML 原生
|
||||
|
||||
## When to Use
|
||||
|
||||
任何需要读取或解析 .docx 文件内容的任务都应使用此 skill。
|
||||
|
||||
### 典型场景
|
||||
- **文档内容提取**: 将 Word 文档转换为可读的文本内容
|
||||
- **文档元数据**: 获取文档的字数、行数等信息
|
||||
- **标题分析**: 提取文档的标题结构
|
||||
- **章节提取**: 提取特定章节的内容
|
||||
- **内容搜索**: 在文档中搜索关键词或模式
|
||||
|
||||
### 不适用场景
|
||||
- ✗ 需要提取图片内容(仅支持纯文本)
|
||||
- ✗ 需要保留复杂的格式信息(如字体、颜色、布局)
|
||||
- ✗ 需要编辑或修改 .docx 文件
|
||||
- ✗ 需要处理 .doc 或其他文档格式
|
||||
|
||||
## Capabilities
|
||||
|
||||
### 1. 全文转换为 Markdown
|
||||
将完整的 DOCX 文档解析为 Markdown 格式文本,移除所有图片但保留文本格式。
|
||||
|
||||
**支持格式转换**:
|
||||
- 标题(1-6级)
|
||||
- 列表(有序和无序)
|
||||
- 表格
|
||||
- 粗体、斜体、下划线
|
||||
- 连续空行规范化
|
||||
|
||||
### 2. 获取文档元信息
|
||||
提供文档的基本统计信息:
|
||||
- 字数统计(使用 `-c` 参数)
|
||||
- 行数统计(使用 `-l` 参数)
|
||||
|
||||
### 3. 标题列表提取
|
||||
提取文档中的所有标题(1-6级),按原始层级关系返回。
|
||||
|
||||
### 4. 指定章节内容提取
|
||||
根据标题名称提取特定章节的完整内容,包括:
|
||||
- 完整的上级标题链
|
||||
- 该标题下的所有下级内容
|
||||
|
||||
支持同名标题提取,返回所有匹配章节。
|
||||
|
||||
### 5. 正则表达式搜索
|
||||
在文档中搜索关键词或模式:
|
||||
- 支持标准正则表达式
|
||||
- 可自定义上下文行数(默认前后各 2 行)
|
||||
- 多个匹配结果用 `---` 分隔
|
||||
- 上下文行数不包含空行
|
||||
|
||||
## Triggers
|
||||
|
||||
### 中文触发词
|
||||
- "读取 docx"
|
||||
- "解析 docx"
|
||||
- "打开 word 文档"
|
||||
- "提取 word 文档内容"
|
||||
- "解析 word 文件"
|
||||
|
||||
### 英文触发词
|
||||
- "read docx"
|
||||
- "parse docx"
|
||||
- "extract from word document"
|
||||
- "parse word file"
|
||||
- "read word document"
|
||||
|
||||
### 文件扩展名
|
||||
- `.docx` 文件
|
||||
|
||||
## Execution
|
||||
|
||||
### 优先使用 lyxy-runner-python
|
||||
|
||||
**推荐执行方式**(如果 lyxy-runner-python skill 可用):
|
||||
|
||||
```bash
|
||||
# 使用 lyxy-runner-python 执行(推荐)
|
||||
uv run --with markitdown skills/lyxy-reader-docx/scripts/docx_parser.py /path/to/document.docx
|
||||
|
||||
# 或使用 python-docx
|
||||
uv run --with python-docx skills/lyxy-reader-docx/scripts/docx_parser.py /path/to/document.docx
|
||||
```
|
||||
|
||||
**大模型应优先检查 lyxy-runner-python skill 是否存在,如果存在则使用它执行。**
|
||||
|
||||
### 降级到直接执行
|
||||
|
||||
如果 lyxy-runner-python skill 不可用,则直接使用 Python:
|
||||
|
||||
```bash
|
||||
# 直接使用 Python 执行
|
||||
python3 skills/lyxy-reader-docx/scripts/docx_parser.py /path/to/document.docx
|
||||
```
|
||||
|
||||
**提示用户**: 建议用户安装 markitdown 或 python-docx 以获得更好的解析效果:
|
||||
```
|
||||
建议:安装 markitdown 或 python-docx 以获得更好的解析效果
|
||||
|
||||
安装命令:
|
||||
pip install markitdown # 推荐
|
||||
pip install python-docx # 备选
|
||||
```
|
||||
|
||||
**禁止自动安装**: 不得自动执行 pip install 命令安装依赖。仅向用户提示安装建议即可。
|
||||
|
||||
**降级策略说明**: 脚本内部实现了多策略解析降级(MarkItDown → python-docx → XML 原生),即使未安装第三方库也能通过 XML 原生解析工作,只是功能可能受限。
|
||||
|
||||
## Command Usage
|
||||
|
||||
### 基本语法
|
||||
```bash
|
||||
python3 docx_parser.py [options] <file_path>
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数 | 说明 |
|
||||
| ----------- | ------------------- |
|
||||
| `file_path` | DOCX 文件的绝对路径 |
|
||||
|
||||
### 选项参数
|
||||
|
||||
| 参数 | 长参数 | 类型 | 默认值 | 说明 |
|
||||
| ---- | ----------- | ---- | ------ | -------------------------------------------------------------- |
|
||||
| `-n` | `--context` | 整数 | 2 | 与 `-s` 配合使用,指定每个检索结果包含的前后行数(不包含空行) |
|
||||
|
||||
### 互斥参数(只能使用其中一个)
|
||||
|
||||
| 参数 | 长参数 | 说明 |
|
||||
| ----- | ----------------- | ----------------------------------------------------- |
|
||||
| `-c` | `--count` | 返回解析后的 markdown 文档的总字数 |
|
||||
| `-l` | `--lines` | 返回解析后的 markdown 文档的总行数 |
|
||||
| `-t` | `--titles` | 返回解析后的 markdown 文档的标题行(1-6级) |
|
||||
| `-tc` | `--title-content` | 指定标题名称,输出该标题及其下级内容(不包含#号) |
|
||||
| `-s` | --search | 使用正则表达式搜索文档,返回所有匹配结果(用---分隔) |
|
||||
|
||||
## Examples
|
||||
|
||||
### 示例 1: 提取完整文档内容
|
||||
```bash
|
||||
# 提取完整文档
|
||||
python3 docx_parser.py /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:完整的 Markdown 格式文档内容
|
||||
|
||||
### 示例 2: 获取文档字数
|
||||
```bash
|
||||
# 获取字数
|
||||
python3 docx_parser.py -c /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:文档总字数(数字)
|
||||
|
||||
### 示例 3: 提取所有标题
|
||||
```bash
|
||||
# 提取标题
|
||||
python3 docx_parser.py -t /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:所有 1-6 级标题列表
|
||||
|
||||
### 示例 4: 提取指定章节
|
||||
```bash
|
||||
# 提取 "第一章" 内容
|
||||
python3 docx_parser.py -tc "第一章" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:该章节的完整内容(包含上级标题链和所有下级内容)
|
||||
|
||||
### 示例 5: 搜索关键词
|
||||
```bash
|
||||
# 搜索关键词(默认 2 行上下文)
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx
|
||||
|
||||
# 自定义 5 行上下文
|
||||
python3 docx_parser.py -s "关键词" -n 5 /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:所有匹配结果及其上下文,用 `---` 分隔
|
||||
|
||||
## 依赖安装
|
||||
|
||||
### 推荐方式:使用 lyxy-runner-python
|
||||
|
||||
如果使用 lyxy-runner-python skill,依赖会自动管理,无需手动安装。
|
||||
|
||||
### 手动安装(降级模式)
|
||||
|
||||
如果直接使用 Python 执行,需要手动安装至少一个解析库:
|
||||
|
||||
```bash
|
||||
# 安装 MarkItDown(推荐)
|
||||
pip install markitdown
|
||||
|
||||
# 安装 python-docx(备选)
|
||||
pip install python-docx
|
||||
```
|
||||
|
||||
**重要限制**:
|
||||
- ✗ **禁止自动安装**: 不得自动执行 pip install 命令安装依赖
|
||||
- ✗ **仅提示即可**: 向用户展示安装建议,但由用户决定是否安装
|
||||
- ✗ **不阻塞执行**: 即使未安装依赖,脚本也能通过 XML 原生解析运行
|
||||
|
||||
### 多策略解析
|
||||
|
||||
脚本自动尝试以下解析方法,确保最大兼容性:
|
||||
1. **MarkItDown**(微软官方库,效果最佳)
|
||||
2. **python-docx**(成熟的 Python 库)
|
||||
3. **XML 原生解析**(备选方案,无需任何依赖)
|
||||
|
||||
即使未安装任何依赖库,脚本也会尝试使用 XML 原生解析,但功能可能受限。
|
||||
|
||||
## Error Handling
|
||||
|
||||
### 常见错误
|
||||
|
||||
| 错误类型 | 说明 | 解决方案 |
|
||||
| --------- | ---- | -------- |
|
||||
| 文件不存在 | 提供的文件路径无效 | 检查文件路径是否正确 |
|
||||
| 无效的 DOCX | 文件不是有效的 DOCX 格式或已损坏 | 确认文件格式正确 |
|
||||
| 未找到标题 | 指定的标题名称不存在 | 使用 `-t` 参数查看所有标题 |
|
||||
| 正则表达式无效 | 提供的正则表达式格式错误 | 检查正则表达式语法 |
|
||||
| 解析库未安装 | 未安装 markitdown 或 python-docx | 提示用户安装以获得更好的解析效果,但禁止自动安装。脚本会自动降级到 XML 原生解析。 |
|
||||
|
||||
## Notes
|
||||
|
||||
### 为什么选择 lyxy-runner-python?
|
||||
|
||||
| 特性 | 优势 |
|
||||
| ------ | ------ |
|
||||
| 环境隔离 | 不污染系统 Python |
|
||||
| 自动依赖 | 自动安装 markitdown 或 python-docx |
|
||||
| 快速启动 | 比 venv 快 10-100 倍 |
|
||||
| 跨平台 | 自动适配 Windows/macOS/Linux |
|
||||
| 零配置 | 开箱即用,无需预安装依赖 |
|
||||
|
||||
### 最佳实践
|
||||
|
||||
1. **优先使用 lyxy-runner-python**: 如果环境中存在,应优先使用 lyxy-runner-python 执行脚本
|
||||
2. **大文件处理**: 对于大文档,建议使用章节提取或关键词搜索来限制处理范围
|
||||
3. **依赖管理**: 使用 lyxy-runner-python 可以自动管理依赖,避免环境配置问题
|
||||
4. **错误处理**: 脚本会自动尝试多种解析方法,确保最大兼容性
|
||||
5. **禁止自动安装**: 在降级到直接 Python 执行时,仅向用户提示安装依赖,不得自动执行 pip install
|
||||
|
||||
### 限制
|
||||
|
||||
- ✗ 不支持图片提取(仅纯文本)
|
||||
- ✗ 不支持复杂的格式保留(字体、颜色、布局等)
|
||||
- ✗ 不支持文档编辑或修改
|
||||
- ✗ 仅支持 .docx 格式(不支持 .doc 或其他格式)
|
||||
@@ -1,319 +0,0 @@
|
||||
# DOCX 解析器使用说明
|
||||
|
||||
## 简介
|
||||
|
||||
`docx_parser.py` 是一个功能强大的 DOCX 文件解析工具,支持将 Microsoft Word (.docx) 文档转换为 Markdown 格式。该脚本采用多策略解析机制,按优先级尝试以下解析方法:
|
||||
|
||||
1. **MarkItDown**(微软官方库)
|
||||
2. **python-docx**(成熟的 Python 库)
|
||||
3. **XML 原生解析**(备选方案)
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.6+
|
||||
- pip
|
||||
|
||||
## 安装依赖
|
||||
|
||||
根据你的需求安装相应的解析库:
|
||||
|
||||
```bash
|
||||
# 安装 MarkItDown(推荐)
|
||||
pip install markitdown
|
||||
|
||||
# 安装 python-docx(备选)
|
||||
pip install python-docx
|
||||
```
|
||||
|
||||
> 注意:建议至少安装一种解析库。如果未安装任何库,脚本会尝试使用 XML 原生解析,但功能可能受限。
|
||||
|
||||
## 命令行参数
|
||||
|
||||
### 基本语法
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py [options] <file_path>
|
||||
```
|
||||
|
||||
### 位置参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
| ----------- | ------------------- |
|
||||
| `file_path` | DOCX 文件的绝对路径 |
|
||||
|
||||
### 选项参数
|
||||
|
||||
| 参数 | 长参数 | 类型 | 默认值 | 说明 |
|
||||
| ---- | ----------- | ---- | ------ | -------------------------------------------------------------- |
|
||||
| `-n` | `--context` | 整数 | 2 | 与 `-s` 配合使用,指定每个检索结果包含的前后行数(不包含空行) |
|
||||
|
||||
### 互斥参数
|
||||
|
||||
以下参数只能使用其中一个:
|
||||
|
||||
| 参数 | 长参数 | 说明 |
|
||||
| ----- | ----------------- | ----------------------------------------------------- |
|
||||
| `-c` | `--count` | 返回解析后的 markdown 文档的总字数 |
|
||||
| `-l` | `--lines` | 返回解析后的 markdown 文档的总行数 |
|
||||
| `-t` | `--titles` | 返回解析后的 markdown 文档的标题行(1-6级) |
|
||||
| `-tc` | `--title-content` | 指定标题名称,输出该标题及其下级内容(不包含#号) |
|
||||
| `-s` | `--search` | 使用正则表达式搜索文档,返回所有匹配结果(用---分隔) |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 输出完整 Markdown 内容
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:完整的 Markdown 格式文档内容
|
||||
|
||||
### 2. 获取文档字数
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -c /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:文档总字数(数字)
|
||||
|
||||
### 3. 获取文档行数
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -l /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:文档总行数(数字)
|
||||
|
||||
### 4. 提取所有标题
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -t /path/to/document.docx
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
# 主标题
|
||||
## 第一章
|
||||
### 1.1 简介
|
||||
### 1.2 内容
|
||||
## 第二章
|
||||
```
|
||||
|
||||
### 5. 提取指定标题内容
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -tc "第一章" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:包含所有上级标题的指定章节内容
|
||||
|
||||
**特点:**
|
||||
|
||||
- 支持多个同名标题
|
||||
- 自动包含完整的上级标题链
|
||||
- 包含所有下级内容
|
||||
|
||||
示例输出:
|
||||
|
||||
```
|
||||
# 主标题
|
||||
## 第一章
|
||||
这是第一章的内容
|
||||
包含所有子章节...
|
||||
|
||||
### 1.1 简介
|
||||
简介内容
|
||||
|
||||
### 1.2 内容
|
||||
详细内容
|
||||
```
|
||||
|
||||
### 6. 搜索关键词
|
||||
|
||||
#### 6.1 基本搜索
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:所有匹配关键词的内容片段,默认前后各 2 行上下文,用 `---` 分隔
|
||||
|
||||
#### 6.2 自定义上下文行数
|
||||
|
||||
```bash
|
||||
# 前后各 5 行
|
||||
python3 docx_parser.py -s "关键词" -n 5 /path/to/document.docx
|
||||
|
||||
# 不包含上下文
|
||||
python3 docx_parser.py -s "关键词" -n 0 /path/to/document.docx
|
||||
```
|
||||
|
||||
#### 6.3 正则表达式搜索
|
||||
|
||||
```bash
|
||||
# 搜索包含数字的行
|
||||
python3 docx_parser.py -s r"数字\d+" /path/to/document.docx
|
||||
|
||||
# 搜索邮箱地址
|
||||
python3 docx_parser.py -s r"\b[\w.-]+@[\w.-]+\.\w+\b" /path/to/document.docx
|
||||
|
||||
# 搜索日期格式
|
||||
python3 docx_parser.py -s r"\d{4}-\d{2}-\d{2}" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
这是前一行
|
||||
包含匹配关键词
|
||||
这是后一行
|
||||
---
|
||||
另一个匹配
|
||||
---
|
||||
第三个匹配
|
||||
```
|
||||
|
||||
### 7. 将输出保存到文件
|
||||
|
||||
```bash
|
||||
# 保存完整 Markdown
|
||||
python3 docx_parser.py /path/to/document.docx > output.md
|
||||
|
||||
# 保存标题内容
|
||||
python3 docx_parser.py -tc "第一章" /path/to/document.docx > chapter1.md
|
||||
|
||||
# 保存搜索结果
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx > search_results.md
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 多策略解析
|
||||
|
||||
脚本自动尝试三种解析方法,确保最大的兼容性:
|
||||
|
||||
1. **MarkItDown**:微软官方库,解析效果最佳
|
||||
2. **python-docx**:功能完善的第三方库
|
||||
3. **XML 原生解析**:不依赖任何库的备选方案
|
||||
|
||||
### 智能匹配
|
||||
|
||||
#### 标题提取
|
||||
|
||||
- 支持 1-6 级标题识别
|
||||
- 自动处理不同样式的标题(Title、Heading 1-6)
|
||||
- 保留原始标题层级关系
|
||||
|
||||
#### 标题内容提取
|
||||
|
||||
- 支持同名标题提取
|
||||
- 自动构建完整上级标题链
|
||||
- 包含所有下级内容
|
||||
- 保持文档结构完整
|
||||
|
||||
#### 搜索功能
|
||||
|
||||
- 支持正则表达式
|
||||
- 智能合并相近匹配
|
||||
- 上下文行数控制(不包含空行)
|
||||
- 结果用 `---` 清晰分隔
|
||||
|
||||
### 文档处理
|
||||
|
||||
- 自动移除 Markdown 图片
|
||||
- 规范化空白行(连续多个空行合并为一个)
|
||||
- 支持表格、列表、粗体、斜体、下划线等格式
|
||||
|
||||
### 错误处理
|
||||
|
||||
- 文件存在性检查
|
||||
- DOCX 格式验证
|
||||
- 解析失败时自动尝试下一种方法
|
||||
- 详细的错误提示信息
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何处理大文档?
|
||||
|
||||
A: 对于非常大的文档,建议:
|
||||
|
||||
1. 使用 `-tc` 参数只提取需要的章节
|
||||
2. 使用 `-s` 参数搜索特定内容
|
||||
3. 将输出重定向到文件进行处理
|
||||
|
||||
### Q: 搜索功能支持哪些正则表达式?
|
||||
|
||||
A: 支持所有 Python 标准正则表达式语法。需要注意特殊字符的转义:
|
||||
|
||||
```bash
|
||||
# 错误:括号需要转义
|
||||
python3 docx_parser.py -s "(关键词)" /path/to/document.docx
|
||||
|
||||
# 正确
|
||||
python3 docx_parser.py -s "\(关键词\)" /path/to/document.docx
|
||||
```
|
||||
|
||||
### Q: 如何获取更多上下文?
|
||||
|
||||
A: 使用 `-n` 参数调整上下文行数:
|
||||
|
||||
```bash
|
||||
# 默认 2 行(推荐)
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx
|
||||
|
||||
# 更多上下文(5 行)
|
||||
python3 docx_parser.py -s "关键词" -n 5 /path/to/document.docx
|
||||
|
||||
# 不包含上下文
|
||||
python3 docx_parser.py -s "关键词" -n 0 /path/to/document.docx
|
||||
```
|
||||
|
||||
### Q: 多个同名标题如何处理?
|
||||
|
||||
A: `-tc` 参数会返回所有同名标题,每个标题都包含其完整的上级标题链:
|
||||
|
||||
```markdown
|
||||
# 主标题
|
||||
|
||||
## 同名标题 1
|
||||
|
||||
内容1
|
||||
|
||||
# 主标题
|
||||
|
||||
## 同名标题 2
|
||||
|
||||
内容2
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 标题识别规则
|
||||
|
||||
| 样式名称 | Markdown 标题级别 |
|
||||
| --------- | ----------------- |
|
||||
| Title | # |
|
||||
| Heading 1 | # |
|
||||
| Heading 2 | ## |
|
||||
| Heading 3 | ### |
|
||||
| Heading 4 | #### |
|
||||
| Heading 5 | ##### |
|
||||
| Heading 6 | ###### |
|
||||
|
||||
### 列表识别规则
|
||||
|
||||
| 样式名称 | Markdown 列表格式 |
|
||||
| -------------------- | ----------------- |
|
||||
| List Bullet / Bullet | - (无序列表) |
|
||||
| List Number / Number | 1. (有序列表) |
|
||||
|
||||
### 文本格式支持
|
||||
|
||||
| 格式 | 转换结果 |
|
||||
| ------ | ----------------- |
|
||||
| 粗体 | `**文本**` |
|
||||
| 斜体 | `*文本*` |
|
||||
| 下划线 | `<u>文本</u>` |
|
||||
| 表格 | Markdown 表格格式 |
|
||||
@@ -1,551 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""整合的 DOCX 解析器,按优先级尝试多种解析方法:
|
||||
1. MarkItDown (微软官方库)
|
||||
2. python-docx (成熟的 Python 库)
|
||||
3. XML 原生解析 (备选方案)
|
||||
|
||||
代码风格要求:
|
||||
- Python 3.6+ 兼容
|
||||
- 遵循 PEP 8 规范
|
||||
- 所有公共 API 函数添加类型提示
|
||||
- 字符串优先内联使用,不提取为常量,除非被使用超过3次
|
||||
- 其他被多次使用的对象根据具体情况可考虑被提取为常量(如正则表达式)
|
||||
- 模块级和公共 API 函数保留文档字符串
|
||||
- 内部辅助函数不添加文档字符串(函数名足够描述)
|
||||
- 变量命名清晰,避免单字母变量名
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
IMAGE_PATTERN = re.compile(r"!\[[^\]]*\]\([^)]+\)")
|
||||
|
||||
|
||||
def normalize_markdown_whitespace(content: str) -> str:
|
||||
lines = content.split("\n")
|
||||
result = []
|
||||
empty_count = 0
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
empty_count += 1
|
||||
if empty_count == 1:
|
||||
result.append(line)
|
||||
else:
|
||||
empty_count = 0
|
||||
result.append(line)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def is_valid_docx(file_path: str) -> bool:
|
||||
try:
|
||||
with zipfile.ZipFile(file_path, "r") as zip_file:
|
||||
required_files = ["[Content_Types].xml", "_rels/.rels", "word/document.xml"]
|
||||
for required in required_files:
|
||||
if required not in zip_file.namelist():
|
||||
return False
|
||||
return True
|
||||
except (zipfile.BadZipFile, zipfile.LargeZipFile):
|
||||
return False
|
||||
|
||||
|
||||
def remove_markdown_images(markdown_text: str) -> str:
|
||||
return IMAGE_PATTERN.sub("", markdown_text)
|
||||
|
||||
|
||||
def extract_titles(markdown_text: str) -> List[str]:
|
||||
"""提取 markdown 文本中的所有标题行(1-6级)"""
|
||||
title_lines = []
|
||||
for line in markdown_text.split("\n"):
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("#"):
|
||||
level = 0
|
||||
for char in stripped:
|
||||
if char == "#":
|
||||
level += 1
|
||||
else:
|
||||
break
|
||||
if 1 <= level <= 6:
|
||||
title_lines.append(stripped)
|
||||
return title_lines
|
||||
|
||||
|
||||
def get_heading_level(line: str) -> int:
|
||||
stripped = line.lstrip()
|
||||
if not stripped.startswith("#"):
|
||||
return 0
|
||||
level = 0
|
||||
for char in stripped:
|
||||
if char == "#":
|
||||
level += 1
|
||||
else:
|
||||
break
|
||||
return level if 1 <= level <= 6 else 0
|
||||
|
||||
|
||||
def extract_title_content(markdown_text: str, title_name: str) -> Optional[str]:
|
||||
"""提取所有指定标题及其下级内容(每个包含上级标题)"""
|
||||
lines = markdown_text.split("\n")
|
||||
match_indices = []
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
level = get_heading_level(line)
|
||||
if level > 0:
|
||||
stripped = line.lstrip()
|
||||
title_text = stripped[level:].strip()
|
||||
if title_text == title_name:
|
||||
match_indices.append(i)
|
||||
|
||||
if not match_indices:
|
||||
return None
|
||||
|
||||
result_lines = []
|
||||
for idx in match_indices:
|
||||
target_level = get_heading_level(lines[idx])
|
||||
|
||||
parent_titles = []
|
||||
current_level = target_level
|
||||
for i in range(idx - 1, -1, -1):
|
||||
line_level = get_heading_level(lines[i])
|
||||
if line_level > 0 and line_level < current_level:
|
||||
parent_titles.append(lines[i])
|
||||
current_level = line_level
|
||||
if current_level == 1:
|
||||
break
|
||||
|
||||
parent_titles.reverse()
|
||||
result_lines.extend(parent_titles)
|
||||
|
||||
result_lines.append(lines[idx])
|
||||
for i in range(idx + 1, len(lines)):
|
||||
line = lines[i]
|
||||
line_level = get_heading_level(line)
|
||||
if line_level == 0 or line_level > target_level:
|
||||
result_lines.append(line)
|
||||
else:
|
||||
break
|
||||
|
||||
return "\n".join(result_lines)
|
||||
|
||||
|
||||
def search_markdown(
|
||||
content: str, pattern: str, context_lines: int = 0
|
||||
) -> Optional[str]:
|
||||
"""使用正则表达式搜索 markdown 文档,返回匹配结果及其上下文"""
|
||||
try:
|
||||
regex = re.compile(pattern)
|
||||
except re.error:
|
||||
return None
|
||||
|
||||
lines = content.split("\n")
|
||||
|
||||
non_empty_indices = []
|
||||
non_empty_to_original = {}
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip():
|
||||
non_empty_indices.append(i)
|
||||
non_empty_to_original[i] = len(non_empty_indices) - 1
|
||||
|
||||
matched_non_empty_indices = []
|
||||
for orig_idx in non_empty_indices:
|
||||
if regex.search(lines[orig_idx]):
|
||||
matched_non_empty_indices.append(non_empty_to_original[orig_idx])
|
||||
|
||||
if not matched_non_empty_indices:
|
||||
return None
|
||||
|
||||
merged_ranges = []
|
||||
current_start = matched_non_empty_indices[0]
|
||||
current_end = matched_non_empty_indices[0]
|
||||
|
||||
for idx in matched_non_empty_indices[1:]:
|
||||
if idx - current_end <= context_lines * 2:
|
||||
current_end = idx
|
||||
else:
|
||||
merged_ranges.append((current_start, current_end))
|
||||
current_start = idx
|
||||
current_end = idx
|
||||
merged_ranges.append((current_start, current_end))
|
||||
|
||||
results = []
|
||||
for start, end in merged_ranges:
|
||||
actual_start = max(0, start - context_lines)
|
||||
actual_end = min(len(non_empty_indices) - 1, end + context_lines)
|
||||
|
||||
start_line_idx = non_empty_indices[actual_start]
|
||||
end_line_idx = non_empty_indices[actual_end]
|
||||
|
||||
selected_indices = set(non_empty_indices[actual_start : actual_end + 1])
|
||||
result_lines = [
|
||||
line
|
||||
for i, line in enumerate(lines)
|
||||
if start_line_idx <= i <= end_line_idx
|
||||
and (line.strip() or i in selected_indices)
|
||||
]
|
||||
results.append("\n".join(result_lines))
|
||||
|
||||
return "\n---\n".join(results)
|
||||
|
||||
|
||||
def parse_with_markitdown(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
try:
|
||||
from markitdown import MarkItDown
|
||||
|
||||
md = MarkItDown()
|
||||
result = md.convert(file_path)
|
||||
if not result.text_content.strip():
|
||||
return None, "文档为空"
|
||||
return result.text_content, None
|
||||
except ImportError:
|
||||
return None, "MarkItDown 库未安装"
|
||||
except Exception as e:
|
||||
return None, f"MarkItDown 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def parse_with_python_docx(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
try:
|
||||
from docx import Document
|
||||
except ImportError:
|
||||
return None, "python-docx 库未安装"
|
||||
|
||||
try:
|
||||
doc = Document(file_path)
|
||||
|
||||
def get_heading_level(para) -> int:
|
||||
if para.style and para.style.name:
|
||||
style_name = para.style.name
|
||||
if "Heading 1" in style_name or "Title" in style_name:
|
||||
return 1
|
||||
elif "Heading 2" in style_name:
|
||||
return 2
|
||||
elif "Heading 3" in style_name:
|
||||
return 3
|
||||
elif "Heading 4" in style_name:
|
||||
return 4
|
||||
elif "Heading 5" in style_name:
|
||||
return 5
|
||||
elif "Heading 6" in style_name:
|
||||
return 6
|
||||
return 0
|
||||
|
||||
def get_list_style(para) -> Optional[str]:
|
||||
if not para.style or not para.style.name:
|
||||
return None
|
||||
style_name = para.style.name
|
||||
if "List Bullet" in style_name or "Bullet" in style_name:
|
||||
return "bullet"
|
||||
elif "List Number" in style_name or "Number" in style_name:
|
||||
return "number"
|
||||
return None
|
||||
|
||||
def convert_runs_to_markdown(runs) -> str:
|
||||
result = []
|
||||
for run in runs:
|
||||
text = run.text
|
||||
if not text:
|
||||
continue
|
||||
if run.bold:
|
||||
text = f"**{text}**"
|
||||
if run.italic:
|
||||
text = f"*{text}*"
|
||||
if run.underline:
|
||||
text = f"<u>{text}</u>"
|
||||
result.append(text)
|
||||
return "".join(result)
|
||||
|
||||
def convert_table_to_markdown(table) -> str:
|
||||
md_lines = []
|
||||
for i, row in enumerate(table.rows):
|
||||
cells = []
|
||||
for cell in row.cells:
|
||||
cell_text = cell.text.strip().replace("\n", " ")
|
||||
cells.append(cell_text)
|
||||
if cells:
|
||||
md_line = "| " + " | ".join(cells) + " |"
|
||||
md_lines.append(md_line)
|
||||
if i == 0:
|
||||
sep_line = "| " + " | ".join(["---"] * len(cells)) + " |"
|
||||
md_lines.append(sep_line)
|
||||
return "\n".join(md_lines)
|
||||
|
||||
markdown_lines = []
|
||||
|
||||
for para in doc.paragraphs:
|
||||
text = convert_runs_to_markdown(para.runs)
|
||||
if not text.strip():
|
||||
continue
|
||||
heading_level = get_heading_level(para)
|
||||
if heading_level > 0:
|
||||
markdown_lines.append(f"{'#' * heading_level} {text}")
|
||||
else:
|
||||
list_style = get_list_style(para)
|
||||
if list_style == "bullet":
|
||||
markdown_lines.append(f"- {text}")
|
||||
elif list_style == "number":
|
||||
markdown_lines.append(f"1. {text}")
|
||||
else:
|
||||
markdown_lines.append(text)
|
||||
markdown_lines.append("")
|
||||
|
||||
for table in doc.tables:
|
||||
table_md = convert_table_to_markdown(table)
|
||||
markdown_lines.append(table_md)
|
||||
markdown_lines.append("")
|
||||
|
||||
content = "\n".join(markdown_lines)
|
||||
if not content.strip():
|
||||
return None, "文档为空"
|
||||
return content, None
|
||||
except Exception as e:
|
||||
return None, f"python-docx 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def parse_with_xml(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
word_namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
namespaces = {"w": word_namespace}
|
||||
|
||||
def safe_open_zip(zip_file: zipfile.ZipFile, name: str):
|
||||
if name.startswith("..") or "/" not in name:
|
||||
return None
|
||||
return zip_file.open(name)
|
||||
|
||||
def get_heading_level(style_id: Optional[str], style_to_level: dict) -> int:
|
||||
return style_to_level.get(style_id, 0)
|
||||
|
||||
def get_list_style(style_id: Optional[str], style_to_list: dict) -> Optional[str]:
|
||||
return style_to_list.get(style_id, None)
|
||||
|
||||
def extract_text_with_formatting(para, namespaces: dict) -> str:
|
||||
texts = []
|
||||
for run in para.findall(".//w:r", namespaces=namespaces):
|
||||
text_elem = run.find(".//w:t", namespaces=namespaces)
|
||||
if text_elem is not None and text_elem.text:
|
||||
text = text_elem.text
|
||||
bold = run.find(".//w:b", namespaces=namespaces) is not None
|
||||
italic = run.find(".//w:i", namespaces=namespaces) is not None
|
||||
if bold:
|
||||
text = f"**{text}**"
|
||||
if italic:
|
||||
text = f"*{text}*"
|
||||
texts.append(text)
|
||||
return "".join(texts).strip()
|
||||
|
||||
def convert_table_to_markdown(table_elem, namespaces: dict) -> str:
|
||||
rows = table_elem.findall(".//w:tr", namespaces=namespaces)
|
||||
if not rows:
|
||||
return ""
|
||||
md_lines = []
|
||||
for i, row in enumerate(rows):
|
||||
cells = row.findall(".//w:tc", namespaces=namespaces)
|
||||
cell_texts = []
|
||||
for cell in cells:
|
||||
cell_text = extract_text_with_formatting(cell, namespaces)
|
||||
cell_text = cell_text.replace("\n", " ").strip()
|
||||
cell_texts.append(cell_text if cell_text else "")
|
||||
if cell_texts:
|
||||
md_line = "| " + " | ".join(cell_texts) + " |"
|
||||
md_lines.append(md_line)
|
||||
if i == 0:
|
||||
sep_line = "| " + " | ".join(["---"] * len(cell_texts)) + " |"
|
||||
md_lines.append(sep_line)
|
||||
return "\n".join(md_lines)
|
||||
|
||||
try:
|
||||
style_to_level = {}
|
||||
style_to_list = {}
|
||||
markdown_lines = []
|
||||
|
||||
with zipfile.ZipFile(file_path) as zip_file:
|
||||
try:
|
||||
styles_file = safe_open_zip(zip_file, "word/styles.xml")
|
||||
if styles_file:
|
||||
styles_root = ET.parse(styles_file)
|
||||
for style in styles_root.findall(
|
||||
".//w:style", namespaces=namespaces
|
||||
):
|
||||
style_id = style.get(f"{{{word_namespace}}}styleId")
|
||||
style_name_elem = style.find("w:name", namespaces=namespaces)
|
||||
if style_id and style_name_elem is not None:
|
||||
style_name = style_name_elem.get(f"{{{word_namespace}}}val")
|
||||
if style_name:
|
||||
if style_name == "Title":
|
||||
style_to_level[style_id] = 1
|
||||
elif style_name == "heading 1":
|
||||
style_to_level[style_id] = 1
|
||||
elif style_name == "heading 2":
|
||||
style_to_level[style_id] = 2
|
||||
elif style_name == "heading 3":
|
||||
style_to_level[style_id] = 3
|
||||
elif style_name == "heading 4":
|
||||
style_to_level[style_id] = 4
|
||||
elif style_name == "heading 5":
|
||||
style_to_level[style_id] = 5
|
||||
elif style_name == "heading 6":
|
||||
style_to_level[style_id] = 6
|
||||
elif (
|
||||
"List Bullet" in style_name
|
||||
or "Bullet" in style_name
|
||||
):
|
||||
style_to_list[style_id] = "bullet"
|
||||
elif (
|
||||
"List Number" in style_name
|
||||
or "Number" in style_name
|
||||
):
|
||||
style_to_list[style_id] = "number"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
document_file = safe_open_zip(zip_file, "word/document.xml")
|
||||
if not document_file:
|
||||
return None, "document.xml 不存在或无法访问"
|
||||
|
||||
root = ET.parse(document_file)
|
||||
body = root.find(".//w:body", namespaces=namespaces)
|
||||
if body is None:
|
||||
return None, "document.xml 中未找到 w:body 元素"
|
||||
|
||||
for child in body.findall("./*", namespaces=namespaces):
|
||||
if child.tag.endswith("}p"):
|
||||
style_elem = child.find(".//w:pStyle", namespaces=namespaces)
|
||||
style_id = (
|
||||
style_elem.get(f"{{{word_namespace}}}val")
|
||||
if style_elem is not None
|
||||
else None
|
||||
)
|
||||
|
||||
heading_level = get_heading_level(style_id, style_to_level)
|
||||
list_style = get_list_style(style_id, style_to_list)
|
||||
para_text = extract_text_with_formatting(child, namespaces)
|
||||
|
||||
if para_text:
|
||||
if heading_level > 0:
|
||||
markdown_lines.append(f"{'#' * heading_level} {para_text}")
|
||||
elif list_style == "bullet":
|
||||
markdown_lines.append(f"- {para_text}")
|
||||
elif list_style == "number":
|
||||
markdown_lines.append(f"1. {para_text}")
|
||||
else:
|
||||
markdown_lines.append(para_text)
|
||||
markdown_lines.append("")
|
||||
|
||||
elif child.tag.endswith("}tbl"):
|
||||
table_md = convert_table_to_markdown(child, namespaces)
|
||||
if table_md:
|
||||
markdown_lines.append(table_md)
|
||||
markdown_lines.append("")
|
||||
|
||||
content = "\n".join(markdown_lines)
|
||||
if not content.strip():
|
||||
return None, "文档为空"
|
||||
return content, None
|
||||
except Exception as e:
|
||||
return None, f"XML 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="将 DOCX 文件解析为 Markdown")
|
||||
|
||||
parser.add_argument("file_path", help="DOCX 文件的绝对路径")
|
||||
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--context",
|
||||
type=int,
|
||||
default=2,
|
||||
help="与 -s 配合使用,指定每个检索结果包含的前后行数(不包含空行)",
|
||||
)
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"-c", "--count", action="store_true", help="返回解析后的 markdown 文档的总字数"
|
||||
)
|
||||
group.add_argument(
|
||||
"-l", "--lines", action="store_true", help="返回解析后的 markdown 文档的总行数"
|
||||
)
|
||||
group.add_argument(
|
||||
"-t",
|
||||
"--titles",
|
||||
action="store_true",
|
||||
help="返回解析后的 markdown 文档的标题行(1-6级)",
|
||||
)
|
||||
group.add_argument(
|
||||
"-tc",
|
||||
"--title-content",
|
||||
help="指定标题名称,输出该标题及其下级内容(不包含#号)",
|
||||
)
|
||||
group.add_argument(
|
||||
"-s",
|
||||
"--search",
|
||||
help="使用正则表达式搜索文档,返回所有匹配结果(用---分隔)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.file_path):
|
||||
print(f"错误: 文件不存在: {args.file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not args.file_path.lower().endswith(".docx"):
|
||||
print(f"警告: 文件扩展名不是 .docx: {args.file_path}")
|
||||
|
||||
if not is_valid_docx(args.file_path):
|
||||
print(f"错误: 文件不是有效的 DOCX 格式或已损坏: {args.file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
parsers = [
|
||||
("MarkItDown", parse_with_markitdown),
|
||||
("python-docx", parse_with_python_docx),
|
||||
("XML 原生解析", parse_with_xml),
|
||||
]
|
||||
|
||||
failures = []
|
||||
content = None
|
||||
|
||||
for parser_name, parser_func in parsers:
|
||||
content, error = parser_func(args.file_path)
|
||||
if content is not None:
|
||||
content = remove_markdown_images(content)
|
||||
content = normalize_markdown_whitespace(content)
|
||||
break
|
||||
else:
|
||||
failures.append(f"- {parser_name}: {error}")
|
||||
|
||||
if content is None:
|
||||
print("所有解析方法均失败:")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
sys.exit(1)
|
||||
|
||||
if args.count:
|
||||
print(len(content.replace("\n", "")))
|
||||
elif args.lines:
|
||||
print(len(content.split("\n")))
|
||||
elif args.titles:
|
||||
titles = extract_titles(content)
|
||||
for title in titles:
|
||||
print(title)
|
||||
elif args.title_content:
|
||||
title_content = extract_title_content(content, args.title_content)
|
||||
if title_content is None:
|
||||
print(f"错误: 未找到标题 '{args.title_content}'")
|
||||
sys.exit(1)
|
||||
print(title_content, end="")
|
||||
elif args.search:
|
||||
search_result = search_markdown(content, args.search, args.context)
|
||||
if search_result is None:
|
||||
print(f"错误: 正则表达式无效或未找到匹配: '{args.search}'")
|
||||
sys.exit(1)
|
||||
print(search_result, end="")
|
||||
else:
|
||||
print(content, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,319 +8,69 @@ compatibility: Requires Bun runtime (https://bun.sh)
|
||||
|
||||
基于 Bun 的 JavaScript/TypeScript 执行技能,提供隔离的脚本执行和自动依赖管理。
|
||||
|
||||
## 快速参考
|
||||
## Purpose
|
||||
|
||||
根据您的需求选择使用方式:
|
||||
**必需依赖**: 此 skill 需要 Bun 运行时,不兼容其他 JavaScript 运行时。
|
||||
|
||||
| 场景 | 描述 | 命令 |
|
||||
| --------- | ------------------------ | ---------------------------------- |
|
||||
| **场景1** | 直接执行已存在的脚本文件 | `bun <script-file>` |
|
||||
| **场景2** | 在指定路径创建脚本并执行 | 使用 Write 工具创建 → `bun <path>` |
|
||||
| **场景3** | 使用临时路径执行(默认) | 生成临时路径 → `bun <temp-file>` |
|
||||
|
||||
**重要提示:** 所有场景在执行脚本前都必须先检查 Bun 环境:`bun --version`
|
||||
|
||||
## 前置条件
|
||||
|
||||
### 安装 Bun
|
||||
|
||||
lyxy-runner-js 需要安装 Bun。Bun 是一个快速的 JavaScript 运行时,内置包管理器。
|
||||
|
||||
**macOS/Linux:**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
```powershell
|
||||
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||
```
|
||||
|
||||
**验证安装:**
|
||||
|
||||
```bash
|
||||
bun --version
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
**执行流程决策树:**
|
||||
|
||||
1. **步骤1:检查 Bun 环境**
|
||||
- 执行:`bun --version`
|
||||
- 失败 → 输出错误信息(包含安装说明)并停止执行
|
||||
- 成功 → 进入下一步
|
||||
- **重要:** 禁止使用 nodejs、npm、yarn、pnpm 等其他工具
|
||||
|
||||
2. **步骤2:选择执行场景**
|
||||
- 场景1:用户提供了已存在的脚本文件路径?
|
||||
- 是 → 直接执行:`bun <script-file>`
|
||||
- 否 → 进入下一步
|
||||
- 场景2:用户指定了脚本的生成路径?
|
||||
- 是 → 使用 Write 工具创建脚本,然后执行
|
||||
- 否 → 进入场景3
|
||||
|
||||
3. **场景3(默认)**:使用临时路径执行
|
||||
- 生成临时文件路径
|
||||
- 将脚本内容写入临时文件
|
||||
- 使用 Bun 运行脚本
|
||||
- 临时文件由系统自动处理
|
||||
|
||||
---
|
||||
|
||||
### 场景1:执行已存在的脚本文件
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 直接执行已存在的脚本
|
||||
bun ./scripts/my-script.js
|
||||
|
||||
# 脚本的输出将自动显示
|
||||
```
|
||||
|
||||
**关键特点:**
|
||||
- ✅ **无需生成临时文件** - 直接执行用户提供的脚本
|
||||
- ✅ **保持脚本位置** - 脚本留在原位置,不会被移动或复制
|
||||
- ✅ **简洁快速** - 跳过文件生成步骤,直接执行
|
||||
|
||||
### 场景2:在指定路径创建并执行脚本
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 使用 Write 工具在指定路径创建脚本
|
||||
# (以下步骤由大模型使用 Write 工具完成)
|
||||
# write content to "./scripts/new-script.js"
|
||||
|
||||
const greeting = "Hello from custom path!";
|
||||
console.log(greeting);
|
||||
|
||||
# 步骤 3: 执行脚本
|
||||
bun ./scripts/new-script.js
|
||||
```
|
||||
|
||||
**关键特点:**
|
||||
- ✅ **自定义路径** - 脚本创建到用户指定的位置
|
||||
- ✅ **持久化存储** - 脚本文件保存在指定位置,不会被自动清理
|
||||
- ✅ **灵活控制** - 用户可以精确控制脚本位置和命名
|
||||
|
||||
### 场景3:使用临时路径执行(默认流程)
|
||||
|
||||
当用户未提供任何路径信息时,使用临时路径执行脚本(默认流程):
|
||||
|
||||
#### 基础示例
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 生成临时文件路径
|
||||
TEMP_FILE=$(bun skills/lyxy-runner-js/scripts/get_temp_path.js js)
|
||||
|
||||
# 步骤 3: 将脚本内容写入临时文件
|
||||
cat <<EOF > "$TEMP_FILE"
|
||||
const greeting = "Hello from lyxy-runner-js!";
|
||||
console.log(greeting);
|
||||
EOF
|
||||
|
||||
# 步骤 4: 执行脚本
|
||||
bun "$TEMP_FILE"
|
||||
|
||||
# 步骤 5: 输出已在上面捕获
|
||||
# 临时文件将由系统自动清理
|
||||
```
|
||||
|
||||
#### TypeScript 示例
|
||||
|
||||
```bash
|
||||
# 生成 TypeScript 临时文件
|
||||
TEMP_TS=$(bun skills/lyxy-runner-js/scripts/get_temp_path.js ts)
|
||||
|
||||
# 写入 TypeScript 脚本
|
||||
cat <<EOF > "$TEMP_TS"
|
||||
const message: string = 'TypeScript execution';
|
||||
console.log(message);
|
||||
EOF
|
||||
|
||||
# 执行 - Bun 会自动转译 TypeScript
|
||||
bun "$TEMP_TS"
|
||||
```
|
||||
|
||||
**Bun 自动处理:**
|
||||
- 检测 `import` 语句
|
||||
Bun 特性:
|
||||
- 自动检测和下载依赖(无需 package.json)
|
||||
- 即时转译 TypeScript
|
||||
- 下载并缓存依赖(到 `~/.bun/install/cache`)
|
||||
- 无需 `package.json` 或手动安装
|
||||
- 跨平台兼容(Windows/macOS/Linux)
|
||||
|
||||
## 依赖管理
|
||||
**重要**: 如果 Bun 未安装,立即停止任务并引导用户安装。禁止使用 nodejs、npm、yarn、pnpm 等替代工具。
|
||||
|
||||
Bun 提供自动依赖管理,无需手动配置:
|
||||
## When to Use
|
||||
|
||||
### 导入外部包
|
||||
任何 JavaScript/TypeScript 处理任务都应使用此 skill。
|
||||
|
||||
```javascript
|
||||
// ESM import(推荐)
|
||||
import axios from 'axios'
|
||||
import lodash from 'lodash'
|
||||
### 典型场景
|
||||
- **数据处理**: JSON/CSV 解析、数据转换
|
||||
- **API 交互**: HTTP 请求、Web API 调用
|
||||
- **文件操作**: 文件读写、批量处理
|
||||
- **脚本自动化**: 构建脚本、任务自动化
|
||||
|
||||
// CommonJS import(也支持)
|
||||
const axios = require('axios')
|
||||
```
|
||||
### 不适用场景
|
||||
- ✗ 需要 Node.js 特定 API(如部分原生模块)
|
||||
- ✗ 需要持久化进程(如服务器)
|
||||
|
||||
首次执行带有外部导入的脚本时,Bun 会:
|
||||
## Quick Reference
|
||||
|
||||
1. 分析导入
|
||||
2. 从 npm 下载缺失的依赖
|
||||
3. 全局缓存到 `~/.bun/install/cache`
|
||||
4. 后续运行使用缓存版本
|
||||
| 场景 | 描述 | 命令 |
|
||||
|------|------|------|
|
||||
| 场景1 | 直接执行已存在的脚本 | `bun <script-file>` |
|
||||
| 场景2 | 在指定路径创建脚本并执行 | 使用 Write 工具创建 → `bun <path>` |
|
||||
| 场景3 | 使用临时路径执行(默认) | 生成临时路径 → `bun <temp-file>` |
|
||||
|
||||
### 不需要 package.json
|
||||
**重要**: 所有场景执行前必须先检查 Bun 环境:`bun --version`
|
||||
|
||||
与 Node.js 不同,你无需创建 `package.json` 或单独运行 `bun install`。Bun 在运行时自动处理所有操作。
|
||||
## Workflow
|
||||
|
||||
## 辅助函数 API
|
||||
1. **检查 Bun 环境**:执行 `bun --version`,失败则停止并提示安装
|
||||
2. **选择执行场景**:根据用户意图选择场景1/2/3
|
||||
3. **执行脚本**:使用 `bun <script>` 运行
|
||||
4. **捕获输出**:stdout/stderr 分别处理
|
||||
|
||||
### `get_temp_path.js`
|
||||
|
||||
为脚本执行生成唯一的临时文件路径。
|
||||
|
||||
**CLI 使用方式:**
|
||||
### 临时路径执行(场景3)
|
||||
|
||||
```bash
|
||||
bun skills/lyxy-runner-js/scripts/get_temp_path.js <extension>
|
||||
# 生成临时文件路径
|
||||
TEMP_FILE=$(bun scripts/get_temp_path.js js)
|
||||
|
||||
# 写入脚本内容
|
||||
cat <<EOF > "$TEMP_FILE"
|
||||
console.log("Hello from lyxy-runner-js!");
|
||||
EOF
|
||||
|
||||
# 执行脚本
|
||||
bun "$TEMP_FILE"
|
||||
```
|
||||
|
||||
**参数:**
|
||||
## References
|
||||
|
||||
- `extension` (可选): 文件扩展名。默认为 `js`。常用值: `js`, `ts`, `mjs`, `mts`
|
||||
详细文档请参阅 `references/` 目录:
|
||||
|
||||
**输出:** 返回类似 `/var/folders/.../lyxy-runner-js-1234567890-abc123.js` 的路径
|
||||
|
||||
**路径格式:**
|
||||
|
||||
- 使用操作系统临时目录(Unix 上为 `/tmp`,Windows 上为 `%TEMP%`)
|
||||
- 前缀: `lyxy-runner-js-`
|
||||
- 时间戳: 自纪元以来的毫秒数
|
||||
- 随机字符串: 7 字符字母数字
|
||||
- 扩展名: 参数中提供的值
|
||||
|
||||
**示例:**
|
||||
|
||||
```bash
|
||||
$ bun skills/lyxy-runner-js/scripts/get_temp_path.js js
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/lyxy-runner-js-1770257905333-na6ujx.js
|
||||
|
||||
$ bun skills/lyxy-runner-js/scripts/get_temp_path.js ts
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/lyxy-runner-js-1770257905333-v8yzt.ts
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 未安装 Bun
|
||||
|
||||
**症状:** `bun --version` 失败或返回 "command not found: bun"
|
||||
|
||||
**错误处理:**
|
||||
|
||||
当检测到 Bun 未安装时,必须:
|
||||
|
||||
1. **停止执行** - 不进行任何后续操作
|
||||
2. **输出明确错误信息** - 清晰说明 "Bun 运行时未安装" 或类似提示
|
||||
3. **提供安装说明** - 参考"前置条件"章节的安装命令
|
||||
|
||||
**重要限制:**
|
||||
|
||||
- ❌ **禁止自动安装** - 不要尝试自动安装 Bun,由用户自行决定
|
||||
- ❌ **禁止使用其他工具** - 不要尝试使用 nodejs、npm、yarn、pnpm 等其他 JavaScript 运行时或包管理工具
|
||||
- ❌ **禁止格式转换** - 不要建议用户将脚本转换为其他运行时格式
|
||||
|
||||
**正确做法:**
|
||||
|
||||
- ✅ 仅输出错误信息和安装说明
|
||||
- ✅ 等待用户安装 Bun 后再继续
|
||||
- ✅ 保持使用 Bun 作为唯一运行时
|
||||
|
||||
### 脚本语法错误
|
||||
|
||||
Bun 提供详细的语法错误信息:
|
||||
|
||||
```bash
|
||||
$ bun "$TEMP_FILE"
|
||||
error: Unexpected token
|
||||
--> /var/folders/.../script.js:2:10
|
||||
|
|
||||
2 | const = 123;
|
||||
| ^
|
||||
```
|
||||
|
||||
错误信息包括:
|
||||
|
||||
- 文件路径和行号
|
||||
- 错误的确切位置
|
||||
- 问题描述
|
||||
|
||||
### 运行时错误
|
||||
|
||||
运行时错误包含完整的堆栈跟踪:
|
||||
|
||||
```bash
|
||||
$ bun "$TEMP_FILE"
|
||||
ReferenceError: foo is not defined
|
||||
at script.js:3:5
|
||||
at main (script.js:1:1)
|
||||
```
|
||||
|
||||
### 其他错误
|
||||
|
||||
其他任何形式的错误都原样输出
|
||||
|
||||
## 输出处理
|
||||
|
||||
### 标准输出
|
||||
|
||||
所有 `console.log()`, `console.info()`, `console.warn()` 输出都到 stdout:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" # stdout 由调用代码捕获
|
||||
```
|
||||
|
||||
### 错误输出
|
||||
|
||||
`console.error()` 输出到 stderr:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" 2>error.log # 单独捕获 stderr
|
||||
```
|
||||
|
||||
### 退出码
|
||||
|
||||
脚本可以设置自定义退出码:
|
||||
|
||||
```javascript
|
||||
process.exit(1) // 错误
|
||||
process.exit(0) // 成功
|
||||
```
|
||||
|
||||
调用者接收这些退出码以确定执行状态。
|
||||
|
||||
## 临时文件管理
|
||||
|
||||
执行后 **不会主动删除** 临时文件。这是设计使然:
|
||||
|
||||
- 操作系统自动管理临时目录空间
|
||||
- 文件可以保留用于调试目的
|
||||
- 大多数操作系统定期清理旧的临时文件
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **始终先检查 Bun 环境** - 所有场景第一步都执行 `bun --version`
|
||||
2. **根据用户意图选择场景** - 查看快速参考选择合适的使用方式
|
||||
3. **单独处理 stdout/stderr** - 以区分输出和错误
|
||||
4. **检查退出码** - 以检测脚本失败
|
||||
5. **使用 ESM imports** - 使用 `import from` 编写现代 JavaScript
|
||||
6. **捕获并显示错误** - 以帮助用户调试问题
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `references/examples.md` | 各场景完整示例、TypeScript 示例、依赖管理示例 |
|
||||
| `references/error-handling.md` | Bun 未安装、语法错误、运行时错误处理 |
|
||||
| `references/best-practices.md` | 输出处理、临时文件管理、辅助函数 API |
|
||||
|
||||
83
skills/lyxy-runner-js/references/best-practices.md
Normal file
83
skills/lyxy-runner-js/references/best-practices.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 最佳实践和输出处理
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **始终先检查 Bun 环境** - 所有场景第一步都执行 `bun --version`
|
||||
2. **根据用户意图选择场景** - 查看快速参考选择合适的使用方式
|
||||
3. **单独处理 stdout/stderr** - 以区分输出和错误
|
||||
4. **检查退出码** - 以检测脚本失败
|
||||
5. **使用 ESM imports** - 使用 `import from` 编写现代 JavaScript
|
||||
6. **捕获并显示错误** - 以帮助用户调试问题
|
||||
|
||||
## 输出处理
|
||||
|
||||
### 标准输出
|
||||
|
||||
所有 `console.log()`, `console.info()`, `console.warn()` 输出都到 stdout:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" # stdout 由调用代码捕获
|
||||
```
|
||||
|
||||
### 错误输出
|
||||
|
||||
`console.error()` 输出到 stderr:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" 2>error.log # 单独捕获 stderr
|
||||
```
|
||||
|
||||
### 退出码
|
||||
|
||||
脚本可以设置自定义退出码:
|
||||
|
||||
```javascript
|
||||
process.exit(1) // 错误
|
||||
process.exit(0) // 成功
|
||||
```
|
||||
|
||||
调用者接收这些退出码以确定执行状态。
|
||||
|
||||
## 临时文件管理
|
||||
|
||||
执行后 **不会主动删除** 临时文件。这是设计使然:
|
||||
|
||||
- 操作系统自动管理临时目录空间
|
||||
- 文件可以保留用于调试目的
|
||||
- 大多数操作系统定期清理旧的临时文件
|
||||
|
||||
## 辅助函数 API
|
||||
|
||||
### `get_temp_path.js`
|
||||
|
||||
为脚本执行生成唯一的临时文件路径。
|
||||
|
||||
**CLI 使用方式:**
|
||||
|
||||
```bash
|
||||
bun scripts/get_temp_path.js <extension>
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `extension` (可选): 文件扩展名。默认为 `js`。常用值: `js`, `ts`, `mjs`, `mts`
|
||||
|
||||
**输出:** 返回类似 `/var/folders/.../lyxy-runner-js-1234567890-abc123.js` 的路径
|
||||
|
||||
**路径格式:**
|
||||
|
||||
- 使用操作系统临时目录(Unix 上为 `/tmp`,Windows 上为 `%TEMP%`)
|
||||
- 前缀: `lyxy-runner-js-`
|
||||
- 时间戳: 自纪元以来的毫秒数
|
||||
- 随机字符串: 7 字符字母数字
|
||||
- 扩展名: 参数中提供的值
|
||||
|
||||
**示例:**
|
||||
|
||||
```bash
|
||||
$ bun scripts/get_temp_path.js js
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/lyxy-runner-js-1770257905333-na6ujx.js
|
||||
|
||||
$ bun scripts/get_temp_path.js ts
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/lyxy-runner-js-1770257905333-v8yzt.ts
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user