chore: commit remaining changes before merge

refactor3.0
Lxy 2 weeks ago
parent fd07fc1298
commit 0ee6a3dab7

@ -19,5 +19,4 @@ android_app/
data/
logs/
*.log
.env
.env.local

@ -1,153 +0,0 @@
---
name: OPSX: Apply
description: "Implement tasks from an OpenSpec change (Experimental)"
---
Implement tasks from an OpenSpec change.
**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue`
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
**Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files.
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]``- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! You can archive this change with `/opsx:archive`.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly

@ -1,158 +0,0 @@
---
name: OPSX: Archive
description: "Archive a completed change in the experimental workflow"
---
Archive a completed change in the experimental workflow.
**Input**: Optionally specify a change name after `/opsx:archive` (e.g., `/opsx:archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos.
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Prompt user for confirmation to continue
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Prompt user for confirmation to continue
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Spec sync status (synced / sync skipped / no delta specs)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs
All artifacts complete. All tasks complete.
```
**Output On Success (No Delta Specs)**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** No delta specs
All artifacts complete. All tasks complete.
```
**Output On Success With Warnings**
```
## Archive Complete (with warnings)
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** Sync skipped (user chose to skip)
**Warnings:**
- Archived with 2 incomplete artifacts
- Archived with 3 incomplete tasks
- Delta spec sync was skipped (user chose to skip)
Review the archive if this was not intentional.
```
**Output On Error (Archive Exists)**
```
## Archive Failed
**Change:** <change-name>
**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
Target archive directory already exists.
**Options:**
1. Rename the existing archive
2. Delete the existing archive if it's a duplicate
3. Wait until a different date to archive
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting

@ -1,242 +0,0 @@
---
name: OPSX: Bulk Archive
description: "Archive multiple completed changes at once"
---
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`, `artifacts`, `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`
- Note which artifacts are `done` vs other states
If any selected change reports `actionContext.mode: "workspace-planning"`, explain that workspace bulk archive is not supported in this slice and STOP before syncing specs or moving changes. Do not fall back to repo-local paths or edit linked repos.
b. **Task completion** - Read `artifactPaths.tasks.existingOutputPaths` from status JSON
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
- If no tasks file exists, note as "No tasks"
c. **Delta specs** - Check `artifactPaths.specs.existingOutputPaths` from status JSON
- 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 "<planningHome.changesDir>/archive"
mv "<changeRoot>" "<planningHome.changesDir>/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. Create a new change to get started.
```
**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,113 +0,0 @@
---
name: OPSX: Continue
description: "Continue working on a change - create the next artifact (Experimental)"
---
Continue working on a change by creating the next artifact.
**Input**: Optionally specify a change name after `/opsx:continue` (e.g., `/opsx:continue add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to 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
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
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 with `/opsx:apply` or archive it with `/opsx:archive`."
- 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
- `resolvedOutputPath`: Resolved path or pattern 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 `resolvedOutputPath` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and workspace planning context
- 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: "Run `/opsx:continue` to create the next artifact"
**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,170 +0,0 @@
---
name: OPSX: Explore
description: "Enter explore mode - think through ideas, investigate problems, clarify requirements"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Input**: The argument after `/opsx:explore` is whatever the user wants to think about. Could be:
- A vague idea: "real-time collaboration"
- A specific problem: "the auth system is getting unwieldy"
- A change name: "add-dark-mode" (to explore in context of that change)
- A comparison: "postgres vs sqlite for this"
- Nothing (just enter explore mode)
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
If the user mentioned a specific change name, read its artifacts for context.
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own

@ -1,96 +0,0 @@
---
name: OPSX: Fast Forward
description: "Create a change and generate all artifacts needed for implementation in one go"
---
Fast-forward through artifact creation - generate everything needed to start implementation.
**Input**: The argument after `/opsx:ff` is the change name (kebab-case), OR a description of what the user wants to build.
**Steps**
1. **If no input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "✓ Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` to start implementing."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next

@ -1,67 +0,0 @@
---
name: OPSX: New
description: "Start a new change using the experimental artifact workflow (OPSX)"
---
Start a new change using the experimental artifact-driven approach.
**Input**: The argument after `/opsx:new` 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. **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 in the planning home resolved by the CLI.
4. **Show the artifact status**
```bash
openspec status --change "<name>" --json
```
Use the returned `planningHome`, `changeRoot`, `artifactPaths`, and `nextSteps` instead of assuming repo-local paths.
5. **Get instructions for the first artifact**
The first artifact depends on the schema. 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? Run `/opsx:continue` or just describe what this change is about and I'll draft it."
**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 using `/opsx:continue` instead
- Pass --schema if using a non-default workflow

@ -1,546 +0,0 @@
---
name: OPSX: Onboard
description: "Guided onboarding - walk through a complete OpenSpec workflow cycle with narration"
---
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 the OpenSpec CLI is installed:
```bash
# 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 CLI not installed:**
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.
Stop here if not installed.
---
## 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
# 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
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 at the `changeRoot` reported by `openspec status --change "<name>" --json` 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: <changeRoot from status JSON>
The folder structure:
```
<changeRoot>/
├── 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 the `resolvedOutputPath` from `openspec instructions proposal --change "<name>" --json`.
```
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:** Resolve where the spec file should be created:
```bash
openspec instructions specs --change "<name>" --json
# Use resolvedOutputPath from the JSON. If it is a glob, choose the concrete file path using the schema instruction and workspace planning context.
```
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 the concrete file path chosen from `resolvedOutputPath`.
---
## 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 the `resolvedOutputPath` from `openspec instructions design --change "<name>" --json`.
---
## 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 the `resolvedOutputPath` from `openspec instructions tasks --change "<name>" --json`.
---
## 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. The archive path is derived from `planningHome.changesDir` and the date.
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: `<planningHome.changesDir>/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
**Core workflow:**
| Command | What it does |
|-------------------|--------------------------------------------|
| `/opsx:propose` | Create a change and generate all artifacts |
| `/opsx:explore` | Think through problems before/during work |
| `/opsx:apply` | Implement tasks from a change |
| `/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:propose` 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 the `changeRoot` reported by `openspec status --change "<name>" --json`.
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
**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:apply <name>` | Implement tasks |
| `/opsx:archive <name>` | Archive when done |
**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.
---
## 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,105 +0,0 @@
---
name: OPSX: Propose
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 in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` to start implementing."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next

@ -1,141 +0,0 @@
---
name: OPSX: Sync
description: "Sync delta specs from a change to main specs"
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Input**: Optionally specify a change name after `/opsx:sync` (e.g., `/opsx:sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
If status reports `actionContext.mode: "workspace-planning"`, explain that workspace spec sync is not supported in this slice and STOP. Do not fall back to repo-local paths or edit linked repos.
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result

@ -1,165 +0,0 @@
---
name: OPSX: Verify
description: "Verify implementation matches change artifacts before archiving"
---
Verify that an implementation matches the change artifacts (specs, tasks, design).
**Input**: Optionally specify a change name after `/opsx:verify` (e.g., `/opsx:verify add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have 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")
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- Which artifacts exist for this change
If status reports `actionContext.mode: "workspace-planning"`, explain that full workspace implementation verification is not supported in this slice and STOP. Do not infer repo-local implementation ownership or edit linked repos.
3. **Get planning context and load artifacts**
```bash
openspec instructions apply --change "<name>" --json
```
This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). 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 `contextFiles.tasks` exists, read every file path in 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 `contextFiles.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 `contextFiles.design` exists:
- 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"

@ -1,125 +0,0 @@
# Comet 阶段感知(防漂移规则)
> 此规则每轮注入,防止长上下文时遗忘 Comet 流程状态。
> Hook 平台额外执行 `comet-hook-guard.sh` 进行硬性拦截;
> 此 Rule 是所有平台通用的软性防线。
## 全局规则
### 阶段感知(最高优先级)
有活跃 comet change 时(`openspec/changes/<name>/.comet.yaml` 存在),**每次开始执行操作前**必须读取 `phase` 字段确认当前阶段。
**阶段与允许操作:**
| 阶段 | 允许 | 禁止 |
|------|------|------|
| `open` | 创建 proposal/design/tasks, 运行 guard | 写源代码 |
| `design` | brainstorming, 创建 Design Doc, 运行 guard | 写源代码 |
| `build` | 写源代码、测试、执行计划 | 跳过用户确认点 |
| `verify` | 验证、branch handling | 跳过失败处理 |
| `archive` | 确认归档、运行归档脚本 | 写源代码 |
### 阶段进入自洽性校验(写源代码前必查)
仅看 `phase` 字段不够——还必须确认"是如何到达这个阶段的"。每次准备写源代码前,先用下表自检 `.comet.yaml` 是否处于**非法空跳**状态(绕过了前置阶段)。命中任一行,立即停止写源代码,按动作回到对应阶段补齐产物,不得信任 `phase` 字段直接续跑。
| 检测到 | 判定 | 动作 |
|--------|------|------|
| `phase: build` + `workflow: full` + `design_doc` 为空/null | 绕过 design 空跳 | 停止写源代码,运行 `/comet-design` 补 Design Doc 并过 guard |
| `phase: build/verify` + proposal/design/tasks 任一缺失或为空 | 绕过 open 空跳 | 回 `/comet-open` 补齐三件套 |
| `phase: archive` + `verify_result``pass` | 绕过 verify 空跳 | 回 `/comet-verify` 完成验证 |
预设例外:`workflow: hotfix/tweak` 本就跳过 design`design_doc` 为空属正常,不算非法。
### Skill 调用(不可用普通对话替代)
以下操作必须通过 Skill 工具加载Skill 不可用时应停止流程并提示安装:
- **brainstorming** — design 阶段、build 阶段中等规模 spec 变更
- **writing-plans** — build 阶段创建实现计划
- **executing-plans** / **subagent-driven-development** — build 阶段执行
- **test-driven-development**`executing-plans` 由主会话在第一个 task 前加载;`subagent-driven-development` 由每个后台 implementer 和修复 agent 加载
- **systematic-debugging** — 遇到崩溃/测试失败/构建失败时
- **verification-before-completion** — verify 阶段
- **using-git-worktrees** — build 阶段选择 worktree 隔离时
### 脚本执行(不可跳过)
- **阶段退出**: `comet-guard <name> <phase> --apply`(必须看到 ALL CHECKS PASSED
- **压缩恢复**: `comet-state check <name> <phase> --recover`
- **状态更新**: 关键操作后通过 `comet-state set` 更新字段,禁止手工编辑 .comet.yaml
- **阶段推进只能经 guard/transition**: 禁止用 `comet-state set <name> phase <值>` 手动跳阶段(会绕过证据校验,脚本已硬拦截);确需修复畸形状态时才用 `COMET_FORCE_PHASE=1` 逃生阀
- **handoff 生成**: `comet-handoff <name> design --write`(禁止手写摘要)
### 用户确认(不可自动跳过)
以下决策点必须暂停等待用户明确选择,不得根据推荐规则自动填写:
- **open**: 需求澄清完成确认、artifact 评审确认
- **design**: brainstorming 方案确认(确认前不得创建 Design Doc
- **build**: plan-ready 暂停、isolation/build_mode/tdd_mode 选择、spec 大规模变更确认
- **verify**: 验证失败处理策略、branch handling 选择
- **archive**: 归档前最终确认
## Design 阶段专项
1. 第一个脚本操作 = `comet-handoff <name> design --write`(未生成 handoff 禁止加载 brainstorming
2. brainstorming in progress: incrementally update brainstorm-summary.md每轮澄清或方案迭代后增量更新恢复检查点未确认内容标注为待确认/候选)
3. brainstorming 完成后下一步 = brainstorm-summary.md 定稿 → Design Doc → guard
4. active compaction gate: brainstorm-summary.md 定稿后、创建 Design Doc 前,优先触发宿主平台原生上下文压缩;无法程序化触发时暂停提示用户手动压缩或确认继续
5. **绝对不能直接开始写实现代码** — 必须先创建 Design Doc 并通过 guard
## Build 阶段专项
1. plan 创建后必须询问用户选择继续或暂停(`build_pause` 机制)
2. 每个 task 验收后必须: tasks.md 打勾 → git commit不得积攒。`subagent-driven-development` 必须等 spec compliance 与 code quality 两个审查都通过,再由协调者按任务唯一文本定向勾选和验证;不得用未完成任务总表代替当前任务验证
3. 遇到失败必须加载 **systematic-debugging** skill根因未定位前不得提出源码修复
4. spec 变更分级: 小改直接编辑 | 中改加载 brainstorming | 大改暂停等用户确认拆分
## Verify 阶段专项
1. 第一步运行 `comet-state scale <name>` 确定验证级别
2. 验证失败后列出失败项等用户选择CRITICAL 必须修
3. 连续 3 次失败后必须让用户选择接受偏差或继续修
## 上下文压缩恢复
如果怀疑发生上下文压缩(之前对话被摘要、找不到之前讨论的内容),立即运行:
```bash
"$COMET_BASH" "$COMET_STATE" check <name> <phase> --recover
```
按脚本输出的 **Recovery action** 决定下一步。
恢复后必须先用「阶段进入自洽性校验」表复查一遍:若发现 `phase` 与产物不自洽design_doc/三件套/verify_result 任一不匹配),按非法空跳处理,回对应阶段补齐,不得信任 `phase` 字段直接续跑。
**特别注意 `build_mode`**:若恢复脚本输出 `build_mode: subagent-driven-development`,你是协调者,不是执行者。必须:
1. 使用 Skill 工具重新加载 Superpowers `subagent-driven-development` 技能 (Use the Skill tool to reload the Superpowers `subagent-driven-development` skill)
2. 读取 `comet/reference/subagent-dispatch.md` 获取 Comet 专属扩展 (re-read `comet/reference/subagent-dispatch.md` for Comet-specific extensions)
3. 读取 `openspec/changes/<name>/.comet/subagent-progress.md` 恢复精确阶段、证据和审查-修复轮次 (Read `openspec/changes/<name>/.comet/subagent-progress.md` to recover the exact stage, evidence, and review-fix round)
4. 禁止在主会话中直接执行 task (Do not execute the pending task directly in the main window)
5. 按检查点恢复;缺失或不匹配时才从第一个未勾选 task 开始
6. 已提交但未通过双审查的 task 保持未勾选,继续审查/修复循环
7. task 通过双审查和定向勾选验证后立即继续下一个 task不得总结或询问是否继续
## 阶段退出后自动过渡
guard `--apply` 成功后,不得在本规则中硬编码下一阶段 skill。必须先运行
```bash
comet-state next <change-name>
```
若已通过 `comet-env.sh` 定位脚本,等价运行:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
按脚本输出决定下一步:
- `NEXT: auto` → 使用 Skill 工具加载 `SKILL` 指向的 skill
- `NEXT: manual` → 不加载下一 skill`HINT` 提示用户手动继续
- `NEXT: done` → 流程已完成,无需继续

@ -1,100 +0,0 @@
---
name: comet-archive
description: "Comet 阶段 5归档。用 /comet-archive 调用。按 OpenSpec delta 语义合并主 spec归档 change。"
---
# Comet 阶段 5归档Archive
## 前置条件
- 验证已通过(阶段 4 完成)
- 分支已处理
- `openspec/changes/<name>/.comet.yaml``verify_result: pass`
## 步骤
### 0. 输出语言约束
归档摘要和生命周期闭环说明必须使用触发本次工作流的用户请求语言。
### 0b. 入口状态验证Entry Check
执行入口验证:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
"$COMET_BASH" "$COMET_STATE" check <name> archive
```
验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。
### 1. 归档前最终确认(阻塞点)
入口验证通过后,**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户确认是否立即归档**。不得在用户确认前运行 `"$COMET_BASH" "$COMET_ARCHIVE" "<change-name>"`
确认前必须向用户展示简短摘要:
- change 名称
- 验证报告路径和结论
- 分支处理状态
- 本次归档将执行的不可逆动作:按 OpenSpec delta 语义合并主 spec、标注 design doc / plan、移动 change 到 archive 目录
用户确认问题必须以单选题形式呈现,包含以下选项:
- 「确认归档」— 立即执行归档脚本,完成 spec 合并和 change 移动
- 「需要调整或重新验证」— 不执行归档;运行 `"$COMET_BASH" "$COMET_STATE" transition <change-name> archive-reopen` 回到 `phase: verify`,再调用 `/comet-verify`。若验证阶段确认需要修复,再按 `/comet-verify` 的验证失败决策回到 `/comet-build`
- 「暂不归档」— 不执行归档,保留当前 `phase: archive` 状态,等待用户稍后再次调用 `/comet-archive`
只有用户选择「确认归档」后,才允许继续 Step 2。用户选择「需要调整或重新验证」后必须先执行 `archive-reopen` 状态回退,不得手动编辑 `.comet.yaml`
### 2. 执行归档
运行归档脚本,自动完成以下全部步骤:
```bash
"$COMET_BASH" "$COMET_ARCHIVE" "<change-name>"
```
脚本自动执行:
1. 入口状态验证phase=archive, verify_result=pass, archived=false
2. Design doc 前置元数据标注archived-with, status
3. Plan 前置元数据标注archived-with
4. 调用 OpenSpec archive 按 delta 语义合并主 spec 并移动 change 到归档目录
5. 校验主 spec 未残留 delta-only section 标题
6. 通过 `comet-state transition <archive-name> archived` 更新 `archived: true`
如脚本返回非零退出码,报告错误并停止。
如脚本返回零退出码,归档完成。
脚本摘要中的 `X/Y steps succeeded` 以真实执行步骤计数,不会因 delta spec 同步或文档标注重复累计。
脚本会调用 OpenSpec 归档能力按 `ADDED/MODIFIED/REMOVED/RENAMED` 语义合并主 spec并在归档后校验主 spec 中没有残留 delta-only section 标题。
如需预览而不实际执行,使用 `--dry-run` 参数。
### 3. 生命周期闭环
Spec 生命周期在此完成:
```
brainstorming → delta spec → 实施 → 验证 → 主 spec 合并 → design doc 标注 → 归档
```
## 退出条件
- 归档脚本执行成功(退出码 0
- 归档目录 `openspec/changes/archive/YYYY-MM-DD-<change-name>/` 存在
- 归档后的 `.comet.yaml``archived: true`
归档脚本会把 `openspec/changes/<name>/` 移动到 `openspec/changes/archive/YYYY-MM-DD-<name>/`
> **WARNING**: 归档成功后**不要再对原 change 名运行** `"$COMET_BASH" "$COMET_GUARD" <change-name> archive`,因为原活跃目录已经不存在。误调会导致 guard 报错"change directory not found"。归档完整性以脚本退出码和归档目录状态为准。
## 完成
Comet 流程全部完成。如需开始新工作,调用 `/comet``/comet-open`
## 上下文压缩恢复
`comet/reference/context-recovery.md` 执行phase 参数为 `archive`。若 `archived: true` 且归档目录存在,归档已完成,无需再次执行归档操作。

@ -1,317 +0,0 @@
---
name: comet-build
description: "Comet 阶段 3计划与构建。用 /comet-build 调用。制定计划并选择执行方式subagent 或直接执行)实施。"
---
# Comet 阶段 3计划与构建Build
## 前置条件
- Design Doc 已创建(阶段 2 完成)
- 活跃 change 存在
## 步骤
### 0. 入口状态验证Entry Check
执行入口验证:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
"$COMET_BASH" "$COMET_STATE" check <name> build
```
验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。
**幂等性**build 阶段所有操作可安全重复执行。读取 `.comet.yaml``phase` 字段确认仍在 build 阶段,读取 plan 文件头的 `base-ref`,再用 `grep -n '\- \[ \]' tasks.md | head -1` 找到第一个未勾选任务继续执行。已提交的任务不得重复提交。
### 1. 制定计划Subagent Offload
通过 subagent 创建实施计划,避免 planning skill 占用主 session 上下文。计划文件和执行反馈必须使用触发本次工作流的用户请求语言。
**Subagent 指令**
你是实施计划专家。基于以下输入创建实施计划:
1. **立即执行:** 使用 Skill 工具加载 Superpowers `writing-plans` 技能。禁止跳过此步骤。技能加载后ARGUMENTS 必须包含:`Language: 使用触发本次工作流的用户请求语言输出`
2. 读取 Design Doc`docs/superpowers/specs/` 下的技术设计文档)
3. 读取 `openspec/changes/<name>/tasks.md`(任务边界)
4. 按技能指引创建计划
计划要求:
- 保存至 `docs/superpowers/plans/YYYY-MM-DD-<feature>.md`
- 引用设计文档,拆分为可执行任务
- **Plan 文件头必须包含关联元数据**
```yaml
---
change: <openspec-change-name>
design-doc: docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md
base-ref: <git rev-parse HEAD before implementation>
---
```
`base-ref` 用于验证阶段跨提交统计改动规模。创建计划时先记录当前提交:
```bash
git rev-parse HEAD
```
将计划写入文件后,返回文件路径。
**执行 subagent**:使用当前平台的 subagent 调度机制派发上述任务。
Subagent 完成后:
- 若返回有效文件路径且文件存在,记录为 plan
- 若 subagent 失败或返回路径无效,在主 session 内联加载 Superpowers `writing-plans` 技能创建计划(降级回退)
### 2. 更新计划状态并提供 plan-ready 暂停点
先记录 plan 路径:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> plan docs/superpowers/plans/YYYY-MM-DD-feature.md
```
无需手动更新 phase阶段守卫guard `--apply`)会在退出条件满足后推进 `phase` 字段。
计划写入后,立即提供一个新的用户决策点:
| 选项 | 行为 | 说明 |
|------|------|------|
| A | 继续执行 | 保持在当前模型中,进入 Step 3 选择工作区隔离和执行方式 |
| B | 暂停切换模型 | 记录 `build_pause: plan-ready`,本次 `/comet-build` 停止,用户稍后可从 `/comet``/comet-build` 恢复 |
这是用户决策点。**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户明确选择**,不得自动继续,也不得把暂停写入 `build_mode`
用户选择继续时:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> build_pause null
```
用户选择暂停时:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> build_pause plan-ready
```
设置 `build_pause: plan-ready` 后,当前调用停止。不要选择 `isolation``build_mode`,不要加载执行技能。
### 3. 选择工作方式
如果恢复时检测到 `build_pause: plan-ready``plan` 文件存在,不要重新运行 `writing-plans`。先告知用户当前停在 plan-ready 暂停点;用户确认继续后,设置:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> build_pause null
```
然后继续本步骤选择工作区隔离和执行方式。
计划已写入当前分支。在开始执行前,**一次性询问用户**选择工作区隔离方式、执行方式、TDD 模式和代码审查模式:
**工作区隔离**
| 选项 | 方式 | 说明 |
|------|------|------|
| A | 创建分支 | 在当前仓库创建新分支,简单快速 |
| B | 创建 Worktree | 隔离工作区,完全独立,适合并行开发 |
**推荐规则**
- 变更涉及 ≤ 3 个文件 → 推荐 A
- 需要并行开发、当前分支有未提交工作 → 推荐 B
**执行方式**
| 选项 | 技能 | 适用场景 |
|------|------|---------|
| A | Superpowers `subagent-driven-development` | 任务独立、复杂度高、需要双阶段审查 |
| B | Superpowers `executing-plans` | 任务简单、无子agent环境、轻量快速 |
**执行方式推荐规则**
- 任务数 ≥ 3 → 推荐 A
- 任务数 ≤ 2 且无跨模块依赖 → 推荐 B
- 来自 hotfix 路径 → 推荐 B
这是用户决策点。**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户明确选择隔离方式、执行方式、TDD 模式和代码审查模式**,不得根据推荐规则自行选择 `branch``worktree`也不得根据推荐规则自行选择执行方式、TDD 模式或代码审查模式。推荐规则只能用于说明建议,不能替代用户确认。
用户选择后,更新 `isolation`、执行方式、TDD 模式和代码审查模式相关字段:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> isolation <branch|worktree>
```
- 若用户选择 `executing-plans`:运行 `"$COMET_BASH" "$COMET_STATE" set <name> subagent_dispatch null`,再运行 `"$COMET_BASH" "$COMET_STATE" set <name> build_mode executing-plans`
- 若用户选择 `subagent-driven-development`:先确认当前平台存在可调用的真实后台 subagent / Task / multi-agent 调度能力;确认后先运行 `"$COMET_BASH" "$COMET_STATE" set <name> subagent_dispatch confirmed`,再运行 `"$COMET_BASH" "$COMET_STATE" set <name> build_mode subagent-driven-development`
- 若无法确认真实后台调度能力,不得写入 `build_mode: subagent-driven-development`;必须暂停等待用户改选 `executing-plans`
**TDD 模式**
| 选项 | 含义 | 适用场景 |
|------|------|---------|
| `tdd` | 每个任务先写失败测试再写实现 | 推荐。变更涉及业务逻辑、新功能、API |
| `direct` | 直接实现,不强制 TDD 流程 | 变更不需要测试覆盖或用户选择跳过测试直接写代码。hotfix/tweak preset 默认使用 `direct` |
运行 `"$COMET_BASH" "$COMET_STATE" set <name> tdd_mode <tdd|direct>`
**代码审查模式**
| 选项 | 含义 | 适用场景 |
|------|------|---------|
| `off` | 不自动派发代码审查 | 文档、配置、文案、小范围低风险任务 |
| `standard` | 只在任务完成后运行一次最终轻量代码审查;若发现问题,最多自动修复一轮,然后交给用户决策 | 默认推荐,适合大多数普通改动 |
| `thorough` | 按批次或风险边界运行合并审查,最后再运行一次完整审查 | 高风险、多模块、架构或安全相关改动 |
运行 `"$COMET_BASH" "$COMET_STATE" set <name> review_mode <off|standard|thorough>`
`isolation` 是脚本级硬约束。full workflow 初始化时可以为 `null`,但只允许存在到本步骤之前。若保持 `null``build → verify` 的 guard 和 `comet-state transition build-complete` 都会失败。
`subagent_dispatch` 是脚本级硬约束。`build_mode: subagent-driven-development` 离开 build 阶段前必须同时满足 `subagent_dispatch: confirmed`,否则 `comet-guard.sh build --apply``comet-state transition build-complete` 都会失败。
`tdd_mode` 是脚本级硬约束。full workflow 离开 build 阶段前 `tdd_mode` 必须已选择为 `tdd``direct`,否则 `comet-guard.sh build --apply``comet-state transition build-complete` 都会失败。
`review_mode` 是脚本级硬约束。新建 full workflow 离开 build 阶段前 `review_mode` 必须已选择为 `off`、`standard` 或 `thorough`,否则 `comet-guard.sh build --apply``comet-state transition build-complete` 都会失败。旧状态文件若没有该字段,按兼容路径继续,但恢复时应补写该字段。
`build_mode` 默认仅 hotfix/tweak preset 使用 `direct`。full workflow 不得默认使用 `direct`。只有用户明确要求跳过计划执行技能,且你已记录显式 override 时,才允许:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> direct_override true
"$COMET_BASH" "$COMET_STATE" set <name> build_mode direct
```
没有 `direct_override: true`full workflow 的 `build_mode=direct` 会被 guard 和状态转换同时拦截。
**执行隔离**
- **branch**:根据 workflow 类型和当前日期推荐分支名,然后让用户确认或输入自定义名称。这是用户决策点——**必须使用当前平台可用的用户输入/确认机制暂停并等待用户明确确认或覆盖分支名**,不得跳过此步骤直接创建分支。
分支命名规范:
- 读取 `.comet.yaml``workflow` 字段确定前缀
- `workflow: full` → 推荐 `feature/YYYYMMDD/<change-name>`
- `workflow: hotfix` → 推荐 `hotfix/YYYYMMDD/<change-name>`
- `workflow: tweak` → 推荐 `tweak/YYYYMMDD/<change-name>`
- 日期取运行时 `date +%Y%m%d` 的结果
示例:如果 change 名称为 `fix-login-bug`,今天是 2026-06-09则推荐 `feature/20260609/fix-login-bug`
用户确认或提供自定义分支名后,执行 `git checkout -b <branch-name>`,后续工作在新分支上进行。
- **worktree**:必须使用 Skill 工具加载 Superpowers `using-git-worktrees` 技能创建隔离工作区。禁止用普通 shell 命令或原生工具绕过该技能;如该技能不可用,停止流程并提示安装或启用 Superpowers 技能。
创建隔离后确认计划文件可访问分支方式天然可访问worktree 方式需确认计划已提交)。若 worktree 模式下计划文件尚未提交,先提交计划文件再创建 worktree
```bash
git add docs/superpowers/plans/YYYY-MM-DD-feature.md
git commit -m "chore: add implementation plan"
```
**执行计划**:必须按 `build_mode` 的真实运行位置处理。
- `build_mode: executing-plans`**立即执行:** 使用 Skill 工具加载 Superpowers `executing-plans` 技能。禁止跳过此步骤。若该技能不可用停止流程并提示安装或启用对应技能不要用普通对话替代该步骤。技能加载后ARGUMENTS 必须包含与 Step 1 相同的 Language 约束:`Language: 使用触发本次工作流的用户请求语言输出`。按计划执行。
- `build_mode: subagent-driven-development`:主会话只负责协调,禁止直接编写实现代码。**立即执行:** 使用 Skill 工具加载 Superpowers `subagent-driven-development` 技能。技能加载后,读取 `comet/reference/subagent-dispatch.md` 获取 Comet 专属扩展真实后台调度、任务隔离、勾选验证、TDD 约束、连续执行、上下文恢复),与技能工作流配合应用。若两者发生冲突,以更具体的 Comet 扩展为准。
- 如果当前平台没有真实后台 agent 调度能力,必须暂停并等待用户选择改用主窗口执行。用户选择改用主窗口执行后,必须先运行 `"$COMET_BASH" "$COMET_STATE" set <name> build_mode executing-plans`,再按 `build_mode: executing-plans` 分支加载 Superpowers `executing-plans` 技能。用户未明确选择前,不得继续执行任务。
**TDD 模式执行约束**
`tdd_mode: tdd`
- `build_mode: executing-plans`:加载执行技能后、执行第一个任务前,**立即执行:** 使用 Skill 工具加载 Superpowers `test-driven-development` 技能一次。禁止跳过此步骤。技能加载后,从第一个未勾选任务开始,对每个任务遵循已加载的 TDD Red-Green-Refactor 循环执行。不得跳过失败测试验证阶段。后续任务不再重新加载该技能,直接遵循已加载流程。若上下文压缩后恢复,重新运行本步骤加载 TDD 技能一次,然后从第一个未勾选任务继续。
- `build_mode: subagent-driven-development`:主会话不加载 TDD skillTDD 约束和证据门槛已在 `comet/reference/subagent-dispatch.md` 中定义,每个后台 implementer 和修复 agent 必须自行使用 Skill 工具加载 Superpowers `test-driven-development` 技能,并遵循 Comet 注入的 TDD 硬约束。
`tdd_mode: direct`:按正常流程执行,不强制 TDD。
**`executing-plans` review gate**
`build_mode``executing-plans``review_mode``standard``thorough` 时,在所有计划任务完成后、运行 build → verify 阶段守卫前,必须使用 Skill 工具加载 Superpowers `requesting-code-review` 技能并请求一次代码审查。`review_mode: off` 时跳过自动代码审查,不加载 `requesting-code-review`,并在验证报告草稿或 tasks.md 中记录跳过原因。
要求:
- `requesting-code-review` 技能必须在 `"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply` 之前加载
- 若 `requesting-code-review` 技能不可用,跳过 review gate 但必须在 tasks.md 中记录 `<!-- review skipped: skill unavailable -->`,并继续 guard 流转
- CRITICAL review 发现(安全漏洞、数据丢失风险、构建/测试失败)必须先修复,不得带入 verify
- 非 CRITICAL review 发现如选择接受,必须在 tasks.md、commit body、验证报告草稿或其他持久产物中记录接受原因和影响范围
### 3b. 执行中异常调试(异常调试协议)
执行任务期间,只要运行程序、测试、构建或手动验证时出现崩溃、异常行为、测试失败或构建失败,必须使用 Skill 工具加载 Superpowers `systematic-debugging` 技能。在完成根因调查前,不得提出或实施源码修复。
具体调查、最小失败测试、修复验证和保持当前 change 验证闭环的要求,按 `comet/reference/debug-gate.md` 执行。
### 4. Spec 增量更新
实施过程中发现初版 spec 不完整时,按变更规模分级处理:
| 规模 | 触发条件 | 做法 |
|------|---------|------|
| 小 | 遗漏验收场景、边界条件 | 直接编辑 delta spec + design.md追加 tasks.md 任务 |
| 中 | 接口变更、新增组件、数据流变化 | **使用当前平台可用的用户输入/确认机制暂停并等待用户确认后**,必须使用 Skill 工具加载 Superpowers `brainstorming` 更新 Design Doc + delta spec |
| 大 | 全新 capability 需求 | **必须使用当前平台可用的用户输入/确认机制暂停并等待用户确认拆分**;用户确认后,通过 `/comet-open` 创建独立 change |
**50% 阈值判定**:以 tasks.md 初始任务总数为基准,若新增任务数超过该总数的一半,视为超出原计划范围,**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户决定是否拆分为新 change**。
创建独立 change 时必须调用 `/comet-open`,不得直接调用 `/opsx:new`。`/comet-open` 会同时创建 OpenSpec 产物和 `.comet.yaml`,避免新 change 脱离 Comet 状态机。
**用户选择必须包含**
- 「拆分为新 change」— 通过 `/comet-open` 创建独立 change
- 「继续在当前 change 内完成」— 记录范围扩展决策,更新 tasks.md 和 delta spec 后继续
**原则**
- delta spec 是活文档,本阶段期间随时可修改
- 每次更新应提交commit message 说明变更原因
- 不提前同步到 main spec归档时统一同步
- 小规模增量直接改 delta spec 时,应在 commit message 中注明,便于归档时判断 design doc 漂移
### 5. 上下文管理
Build 是最长阶段,可能跨越大量任务。为支持上下文压缩后断点恢复:
- **每完成一个 task**:按当前执行分支和 `review_mode` 完成验收后再勾选对应任务并提交。`subagent-driven-development` 在 `standard``off` 时不做 per-task reviewer`thorough` 时只按批次或风险边界做合并审查,不做每 task 双审查。所有模式都必须按任务唯一文本完成定向检查。可用 `grep -c '\- \[ \]' tasks.md` 检查剩余未勾选数,无需重新读取整个文件
- **上下文压缩后恢复**:按 `comet/reference/context-recovery.md` 执行phase 参数为 `build`
- **用户手动修改恢复**:按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。该协议定义了检查步骤、归因分类和禁令。build 阶段的特殊处理:
1. 归因后,若 diff 暗示计划或 spec 已变化,按 Step 4「Spec 增量更新」分级处理
- **长任务拆分**:单任务超过 200 行代码变更时,考虑拆分为多个子任务分别提交
## 退出条件
- tasks.md 全部勾选
- 代码已提交
- 已显式运行项目对应的构建/测试命令并通过(不要只依赖 guard 自动猜测)
- `isolation` 已写为 `branch``worktree`
- `build_mode` 已写为 `subagent-driven-development`、`executing-plans` 或带显式 override 的 `direct`;若为 `subagent-driven-development``subagent_dispatch` 必须为 `confirmed`
- `tdd_mode` 已写为 `tdd``direct`
- `review_mode` 已写为 `off`、`standard` 或 `thorough`
- 若 `review_mode``standard``thorough`,已按对应模式完成代码审查,且 CRITICAL review 发现已修复或非 CRITICAL review 发现已记录接受理由;若 `review_mode: off`,已在持久产物中记录跳过自动代码审查的原因
- **阶段守卫**:运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply`,全部 PASS 后由守卫推进到 `phase: verify`(此步骤更新 `phase` 字段,与 `auto_transition` 无关)
Guard 会优先读取项目配置中的命令:
```yaml
build_command: <build command>
verify_command: <verify command>
```
配置位置可为 change 的 `.comet.yaml`,也可为仓库根目录的 `.comet.yaml` / `comet.yaml` / `.comet.yml` / `comet.yml`
未配置时才回退到 `npm run build`、Maven 或 Cargo 的默认探测。构建失败时 guard 会打印失败命令输出,作为排查证据。
退出前运行阶段守卫推进 phase此步骤与 `auto_transition` 无关):
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply
```
状态文件自动更新为 `phase: verify`、`verify_result: pending`。
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续

@ -1,264 +0,0 @@
---
name: comet-design
description: "Comet 阶段 2深度设计。用 /comet-design 调用。通过 brainstorming 产出 Design Doc 和 delta spec。"
---
# Comet 阶段 2深度设计Design
## 前置条件
- 活跃 change 已存在proposal.md、design.md、tasks.md
- 无 Design Doc`docs/superpowers/specs/` 下无对应文件)
## 步骤
### 0. 入口状态验证Entry Check
执行入口验证:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
"$COMET_BASH" "$COMET_STATE" check <name> design
```
验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。
**幂等性**:所有 design 阶段操作可以安全重试。如果 `handoff_context``handoff_hash` 已存在,先确认它们与当前产物一致再决定是否重新生成。
### 1a. 生成 OpenSpec → Superpowers 交接包
**必须由脚本生成,不允许 agent 临场手写 summary 代替。**
```bash
"$COMET_BASH" "$COMET_HANDOFF" <change-name> design --write
```
脚本会根据 change `.comet.yaml``context_compression` 快照生成并记录交接包。
默认 `context_compression: off` 时生成:
```text
openspec/changes/<name>/.comet/handoff/design-context.json
openspec/changes/<name>/.comet/handoff/design-context.md
```
启用 beta项目 `.comet/config.yaml``context_compression: beta`,创建 change 时快照进入 `.comet.yaml`)时生成:
```text
openspec/changes/<name>/.comet/handoff/spec-context.json
openspec/changes/<name>/.comet/handoff/spec-context.md
```
并在 `.comet.yaml` 写入:
```yaml
handoff_context: openspec/changes/<name>/.comet/handoff/design-context.json
handoff_hash: <sha256>
```
默认交接包是 **compact 可追溯摘录**,不是 agent summary
- `design-context.json`:机器索引,包含 change、phase、canonical spec、source paths、hash
- `design-context.md`:供 Superpowers 阅读的上下文包含脚本标记、source path、line range、sha256、确定性摘录
- 超出摘录预算时标记 `[TRUNCATED]`,并保留 Full source 路径
beta 交接包是 **结构化 spec projection**,用于减少 OpenSpec 原文 token 占用但避免实现漂移:
- `spec-context.json`:机器索引,包含 change、phase、mode=beta、source paths、context_hash、files 角色
- `spec-context.md`:供 Superpowers 阅读的紧凑上下文verbatim 投影 delta spec 文件并按 hash 引用支撑产物
- OpenSpec delta spec 仍是 canonical specprojection 缺失或过期时必须重新生成或读取源 spec不得用 agent summary 替代
如确实需要全文上下文,可显式运行:
```bash
"$COMET_BASH" "$COMET_HANDOFF" <change-name> design --write --full
```
交接包来源来自 OpenSpec open 阶段产物:
- `proposal.md`:目标、动机、范围、非目标
- `design.md`:高层架构决策、方案约束
- `tasks.md`:初始任务边界
- `specs/*/spec.md`delta 能力规格
### 1b. 执行 Brainstorming带上下文
**立即执行:** 使用 Skill 工具加载 Superpowers `brainstorming` 技能。禁止跳过此步骤。
技能加载时ARGUMENTS 必须包含:
```text
Language: 使用触发本次工作流的用户请求语言输出
```
技能加载后,按其指引使用以下上下文:
```text
Change: <change-name>
OpenSpec Context Pack: openspec/changes/<name>/.comet/handoff/design-context.md
Machine handoff: openspec/changes/<name>/.comet/handoff/design-context.json
如 context_compression: beta则使用
OpenSpec Context Pack: openspec/changes/<name>/.comet/handoff/spec-context.md
Machine handoff: openspec/changes/<name>/.comet/handoff/spec-context.json
OpenSpec 产物是上游事实源,但不得用“跳过重复上下文探索”削弱 Superpowers `brainstorming` 的澄清流程。
你的任务是基于交接包做深度技术设计:实现方案、技术风险、测试策略、边界条件。
如发现目标、范围、非目标、验收场景或关键约束仍不清楚,必须先继续提问并形成设计方案,不得只进行一轮问答就创建 Design Doc。
不要重写 proposal/spec如发现 OpenSpec delta spec 缺少验收场景,只能提出 Spec Patch并回写 OpenSpec delta spec不要在 Design Doc 中创建第二份需求 spec。Spec Patch 仅限于补充验收场景、修正歧义描述或添加边界条件,不得大幅重写 delta spec 的结构或范围——如需大幅修改,应标记为设计发现并回到 brainstorming 确认。
Design Doc frontmatter 必须最小化,只包含:
---
comet_change: <change-name>
role: technical-design
canonical_spec: openspec
---
按 Superpowers `brainstorming` 技能原流程推进澄清问题、2-3 个方案、分段确认设计。不得提前写入 Design Doc。
```
禁止在未加载该技能的情况下继续。
如 Superpowers `brainstorming` 技能不可用,停止流程并提示安装或启用 Superpowers 技能,不要用普通对话替代该步骤。
技能加载后,按其指引产出设计方案(以对话形式呈现):
- 技术方案:架构、数据流、关键技术选型与风险
- 测试策略
- 需求/范围缺口与需回写的 Spec Patch
- 如需补充验收场景,标明将回写的 delta spec 变更
brainstorming 阶段不写入 Design Doc 文件,仅产出设计方案供 Step 1c 用户确认。确认后才创建 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` 并回写 delta spec。
但为了上下文压缩恢复brainstorming 过程中必须增量更新 `brainstorm-summary.md`。每轮澄清或方案迭代后,只要产生新的已确认事实、关键约束、候选方案、取舍/风险、测试策略或 Spec Patch 候选,就更新该文件;未确认内容必须标注为“待确认”或“候选”。该文件是恢复检查点,不是 Design Doc也不得替代 Step 1c 的用户确认。
### 1c. 用户确认设计方案(阻塞点)
brainstorming 产出设计方案后,**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户明确确认设计方案**。不得在用户确认前创建最终 Design Doc、写入 `design_doc`、运行 design guard或进入 `/comet-build`
暂停时只展示必要摘要:
- 采用的技术方案
- 关键取舍与风险
- 测试策略
- 如有 Spec Patch列出将回写的 delta spec 变更
用户明确确认后,才继续 Step 2。若用户要求调整继续 brainstorming 迭代,直到用户确认。
### 1d. Brainstorming 检查点定稿
用户确认设计方案后,在创建 Design Doc 前,创建或更新已增量维护的检查点文件,将其定稿为确认后的设计方案摘要:
```bash
mkdir -p openspec/changes/<name>/.comet/handoff
```
`openspec/changes/<name>/.comet/handoff/brainstorm-summary.md` 结构:
```markdown
# Brainstorm Summary
- Change: <change-name>
- Date: <YYYY-MM-DD>
## 确认的技术方案
<用户确认的方案摘要>
## 关键取舍与风险
<主要取舍和风险>
## 测试策略
<测试方法概述>
## Spec Patch
<将回写的 delta spec 变更,无则写"无">
```
**上下文压缩说明**:每次增量更新 `brainstorm-summary.md`都是相对安全的压缩恢复点。Brainstorming 完成后,如上下文窗口紧张,应优先在此处进行压缩。压缩后重新加载以下文件继续 Step 2
- `openspec/changes/<name>/.comet/handoff/brainstorm-summary.md`
- `openspec/changes/<name>/.comet/handoff/design-context.md`(或 beta 模式的 `spec-context.md`
- `openspec/changes/<name>/.comet/handoff/design-context.json`(或 beta 模式的 `spec-context.json`
### 1e. 主动式上下文压缩
完成 Step 1d 并确认 `brainstorm-summary.md` 已写入后,进入 Design Doc 创建前的主动式上下文压缩。此时 OpenSpec 交接包、brainstorming 决策和待确认项都已落盘,应主动释放前面读取 Spec 和 brainstorming 消耗的上下文,为 Step 2 及后续 Build 阶段保留窗口。
执行规则:
- 如果当前平台提供原生上下文压缩/清理机制(例如宿主 Agent 的 compact/compaction 命令、工具或 UI 操作),必须在这里触发一次主动压缩;不要尝试用 shell 脚本伪造压缩命令。
- 压缩恢复提示必须包含 change 名称、当前步骤Design Step 2、以及上方三类需重新加载的 handoff 文件。
- 如果当前平台无法由 agent 程序化触发压缩,必须暂停并提示用户在宿主平台执行手动压缩;用户确认无法压缩或要求继续时,才继续 Step 2。
### 2. 创建 Design Doc
基于 brainstorming 对话的完整上下文(仍在主 session 中),创建 Design Doc。
Design Doc frontmatter 必须最小化:
```yaml
---
comet_change: <change-name>
role: technical-design
canonical_spec: openspec
---
```
将 Design Doc 写入 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
如需回写 delta specSpec Patch同时编辑对应的 `specs/*/spec.md`
**上下文压缩恢复**:若上下文已被压缩,从 `brainstorm-summary.md` + handoff 上下文恢复。若用户尚未确认设计方案,回到 Step 1b/1c 继续 brainstorming若用户已确认继续创建 Design Doc。brainstorm-summary.md 是压缩恢复的落盘点,不是 Design Doc 的唯一输入——创建时应尽可能利用恢复后的完整上下文。
### 3. 更新 Comet 状态
先记录 design_doc 路径。如果 Spec Patch 回写了 delta spec新增或修改了 `specs/*/spec.md`),必须重新生成 handoff 以更新 hash
```bash
# 记录 design_doc 路径
"$COMET_BASH" "$COMET_STATE" set <name> design_doc docs/superpowers/specs/YYYY-MM-DD-topic-design.md
# 如有 delta spec 变更,重新生成 handoff更新 hash
"$COMET_BASH" "$COMET_HANDOFF" <change-name> design --write
# 阶段守卫推进 phase 到下一阶段
"$COMET_BASH" "$COMET_GUARD" <change-name> design --apply
```
如果没有 delta spec 变更,跳过 handoff 重新生成步骤。状态文件自动更新,无需手动编辑其他字段。
## 退出条件
- Design Doc 已创建并保存
- Design Doc frontmatter 包含 `comet_change`、`role: technical-design`、`canonical_spec: openspec`
- `handoff_context``handoff_hash` 已写入 `.comet.yaml`(由 guard 强制校验)
- `handoff_hash` 与当前 OpenSpec open 阶段产物一致(由 guard 强制校验)
- `design-context.md` 或 beta `spec-context.md` 必须是脚本生成,且包含 source path、mode、sha256 等可追溯标记(由 guard 强制校验)
- beta 模式下,`spec-context.json` 必须结构合法且引用当前源文件(由 guard 强制校验)
- 如有新能力或补充验收场景OpenSpec delta spec 已创建/更新
- `design_doc` 已写入 `.comet.yaml`
- **阶段守卫**:运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> design --apply`,全部 PASS 后由守卫推进到 `phase: build`(此步骤更新 `phase` 字段,与 `auto_transition` 无关)
退出前必须使用 `--apply`
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> design --apply
```
## 上下文压缩恢复
`comet/reference/context-recovery.md` 执行phase 参数为 `design`
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续

@ -1,200 +0,0 @@
---
name: comet-hotfix
description: "Comet 预设路径Bug fix / 热修复。跳过 brainstorming直接 open → build → verify → archive。适用于行为修复、不涉及新 capability 设计的场景。"
---
# Comet 预设路径Hotfix
快速 bug fix 工作流open → build → verify → archive。跳过 brainstorming 和完整 plan适用于行为修复、不涉及新 capability 设计的场景。
**适用条件**(必须全部满足):
1. 修复已有功能的 bug不新增 capability
2. 不涉及接口变更或架构调整
3. 改动范围可预估(通常 ≤ 2 个文件)
**不适用**:如修复过程发现需要架构调整,应升级为完整 `/comet` 流程。
---
## 流程preset workflow6 步)
### 0. 输出语言约束
精简版 OpenSpec 产物必须使用触发本次工作流的用户请求语言。
执行链路open → build → verify → archive。Hotfix 为每个阶段提供默认决策:精简开启、直接构建、按规模验证、验证通过后进入归档前最终确认。
开始前先定位 Comet 脚本:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
```
### 1. 快速开启preset open
复用 Comet open 能力创建 change但使用 hotfix 默认值:不执行 `openspec-explore` 长探索,直接进入精简 change 创建。
**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。禁止跳过此步骤。
技能加载后,按其指引创建精简版产物:
- `proposal.md` — 问题描述 + 根因分析 + 修复目标(无需方案对比)
- `design.md` — 修复方案1 个即可,无需多方案对比)
- `tasks.md` — 修复任务清单
- **无需 delta spec**(除非修复改变了已有 spec 的验收场景)
初始化 Comet 状态文件:
```bash
"$COMET_BASH" "$COMET_STATE" init <name> hotfix
```
初始化后验证状态:
```bash
"$COMET_BASH" "$COMET_STATE" check <name> open
```
阶段守卫完成 open → build 过渡:
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> open --apply
```
检查 `auto_transition` 决定是否继续:
```bash
"$COMET_BASH" "$COMET_STATE" next <name>
```
- `NEXT: auto` → 继续 Step 2
- `NEXT: manual` → 暂停,按 `HINT` 提示用户手动运行 `/<SKILL>`
### 2. 直接构建preset build
使用 hotfix 默认值:`build_mode: direct`,默认 `review_mode: off`。跳过 Superpowers `brainstorming``writing-plans`(除非任务 > 3 个;若超过 3 个任务,转入 `/comet-build` 的计划与执行方式选择——注意这不触发 full workflow 升级,仅切换执行方式)。
继续或开始修改前,按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。若归因后发现修复范围超出 hotfix按本文件“升级条件”处理。
**立即执行:** 按 tasks.md 逐个执行任务:
1. 读取 `openspec/changes/<name>/tasks.md`,获取未完成任务列表
2. 对每个未完成任务:
- 根据任务描述修改代码
- 运行项目格式化命令(如 `mvn spotless:apply`、`npm run format` 等)
- 运行相关测试确认通过
- 将 tasks.md 中对应 `- [ ]` 勾选为 `- [x]`
- 提交代码commit message 格式:`fix: <简述修复>`
3. 全部任务完成后,显式运行项目相关测试和构建命令
执行 hotfix 期间,只要运行程序、测试、构建或手动验证时出现崩溃、异常行为、测试失败或构建失败,必须使用 Skill 工具加载 Superpowers `systematic-debugging` 技能。在完成根因调查前,不得提出或实施源码修复。
具体调查、最小失败测试、修复验证和保持当前 change 验证闭环的要求,按 `comet/reference/debug-gate.md` 执行。
**如修复影响已有 spec 验收场景**
- 在 `openspec/changes/<name>/specs/<capability>/spec.md` 创建 delta spec
- 仅包含 `## MODIFIED Requirements` 部分
### 3. 根因消除检查
**在运行 build guard 之前执行**,确保修复确实消除了问题根因:
1. 读取 proposal.md 中的 bug 描述和根因
2. 搜索验证问题代码不再存在
3. 如根因未消除,回到 Step 2 继续修复(此时仍在 build 阶段,无需状态回退)
**升级条件**
- 根因消除检查发现深层架构问题 → 停止 hotfix按升级条件阻塞确认处理
- 修复需要额外接口变更 → 停止 hotfix按升级条件阻塞确认处理
根因确认消除后,运行阶段守卫完成 build → verify 过渡:
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply
```
状态文件自动更新为 `phase: verify`、`verify_result: pending`,然后进入验证。
### 4. 验证preset verify
复用 `/comet-verify`,由 comet-verify 的规模评估决定轻量或完整验证。
**立即执行:** 使用 Skill 工具加载 `comet-verify` 技能。禁止跳过此步骤。
无 delta spec 的小范围 hotfix 通常满足轻量验证条件(≤ 3 tasks、≤ 2 filescomet-verify 的规模评估会选择轻量验证路径6 项快速检查;默认 `review_mode: off` 时不自动派发代码审查)。若用户希望增加审查,可在验证前运行 `"$COMET_BASH" "$COMET_STATE" set <name> review_mode standard``thorough`。若 hotfix 创建了 delta spec则根据 comet-verify 的规模评估规则进入完整验证路径。
验证通过后,按 `/comet-verify` 的规则将 `.comet.yaml``verify_result` 记录为 `pass`,归档前不得跳过该状态。验证通过后仍必须进入 `/comet-archive` 的归档前最终确认,不得自动运行归档脚本。
### 5. 归档preset archive
复用 `/comet-archive`。归档前必须满足 `.comet.yaml``verify_result: pass`,并等待 `/comet-archive` 的归档前最终确认。
**立即执行:** 使用 Skill 工具加载 `comet-archive` 技能进行归档。禁止跳过此步骤。
如有 delta spec按 comet-archive 规则同步到 main spec并处理关联 Design Doc 与 Plan 的归档标注。
---
## 连续执行模式
<IMPORTANT>
Hotfix 流程默认 **一次性连续执行**。调用 `/comet-hotfix`agent 在 hotfix 自有步骤间自动推进,不主动停顿。**例外**:若 `auto_transition: false`,则在每个 phase 边界build/verify/archive 之间)停下,由用户手动运行下一阶段命令——此时连续执行降级为逐阶段手动推进,详见下方「自动衔接下一阶段」。但无论 `auto_transition` 取何值,以下情况都必须暂停等待用户确认:
1. 遇到升级条件(见"升级条件"章节),**必须使用当前平台可用的用户输入/确认机制暂停并等待用户明确确认**升级为完整流程
2. 任务超过 3 个转入 `/comet-build` 时的工作区隔离和执行方式选择
3. 验证阶段comet-verify的验证失败决策和分支处理决策
4. 归档前最终确认comet-archive 执行归档脚本前)
执行顺序:快速开启 → 直接构建 → 根因消除检查 → 验证 → 归档 → 完成
每个阶段完成后立即进入下一阶段。阶段内部仍必须按上文要求调用对应 Comet/OpenSpec/Superpowers skill被调用的 skill 如有自己的用户决策点,按该 skill 规则执行。
</IMPORTANT>
---
## 升级条件
满足以下**任一**条件时,停止 hotfix 流程,升级为完整 `/comet`
| 条件 | 说明 |
|------|------|
| 改动涉及 **3+ 文件** | 超出单点修复范围 |
| 架构变更 | 新模块、新接口、新依赖 |
| 数据库 schema 变更 | 结构性调整 |
| 引入新的 public API | 修复产生了新的对外接口 |
| 修复范围超出单一函数/模块 | 需要多处协调修改 |
满足升级条件时**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户明确确认**升级为完整 `/comet` 流程。不得直接进入 `/comet-design`,不得自动补充 Design Doc。
用户确认升级后,**必须先更新 workflow 和 phase 字段**再进入完整流程:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> workflow full
"$COMET_BASH" "$COMET_STATE" set <name> phase design
```
然后在当前 change 基础上补充 Design Doc**立即使用 Skill 工具加载 `comet-design` skill**,后续正常走完整流程。若用户不确认升级,停止 hotfix 并报告当前变更已超出 hotfix 适用范围。
---
## 退出条件
- Bug 已修复,测试通过
- change 已归档
- 如有 spec 变更,已同步到 main spec
- **阶段守卫**build → verify 前运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply`verify → archive 前按 `/comet-verify` 规则运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> verify --apply`
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 继续 hotfix 流程(`phase: build` 返回 `comet-hotfix``verify` 返回 `comet-verify``archive` 返回 `comet-archive`
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续

@ -1,220 +0,0 @@
---
name: comet-open
description: "Comet 阶段 1开启。用 /comet-open 调用。通过 OpenSpec 探索想法、确认需求澄清,再创建 change 结构proposal + design + tasks。"
---
# Comet 阶段 1开启Open
## 前置条件
- 无活跃 change或用户希望创建新 change
## 步骤
### 0. 输出语言约束
传递给 OpenSpec 的所有提问和产物要求都必须包含输出语言约束:使用触发本次工作流的用户请求语言。恢复已有 change 且产物已有明确主语言时,除非用户明确要求切换,否则保持该语言。
### 1. 探索想法与需求澄清
**立即执行:** 使用 Skill 工具加载 `openspec-explore` 技能。禁止跳过此步骤。
技能加载后,按其指引探索问题空间,但不得把一次问答视为足够澄清。必须围绕下列内容继续提问、对齐并形成澄清摘要:
- 目标:用户真正要解决的问题和期望结果
- 非目标:本次明确不做的内容
- 范围边界:涉及/不涉及的模块、用户、平台或数据
- 关键未知项:仍不确定的假设、风险或依赖
- 验收场景草案:至少覆盖核心成功场景和关键边界场景
澄清摘要必须包含:目标、非目标、范围边界、关键未知项、验收场景草案。
### 1a. PRD 拆分预检(阻塞点)
当用户输入是大型 PRD、路线图、完整产品方案或澄清摘要显示包含多个独立能力、模块、用户路径或里程碑时必须在创建 OpenSpec artifacts 前评估是否需要拆分为多个 change。
拆分预检必须基于已澄清的信息,输出候选拆分清单。每个候选拆分项必须包含:
- 建议 change 名称
- 目标与范围边界
- 明确非目标
- 依赖关系或推荐执行顺序
- 对应的核心验收场景
满足任一条件时,应推荐拆分:
- PRD 包含多个可独立设计、构建、验证、归档的 capability
- 涉及多个模块或用户路径,且其中一部分可独立交付
- 存在明显分阶段里程碑
- 预计会产生多个 delta spec 或超过 3 个大任务
- 任一部分失败或延期不应阻塞其他部分进入后续阶段
如推荐拆分,必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户选择。
用户选择必须包含:
- 「创建多个 OpenSpec changes」— 按候选拆分逐个创建独立 change
- 「保持为一个 change」— 继续单 change 流程,并在 proposal/design/tasks 中记录不拆分原因
- 「调整拆分方案后继续」— 用户说明调整方向后,重新输出候选拆分清单并再次确认
每个被接受的拆分项都必须通过 `/comet-open` 创建独立 change不得直接调用 `/opsx:new`。`/comet-open` 负责同时创建 OpenSpec artifacts 和 `.comet.yaml`,确保每个 change 都进入 Comet 状态机。
不得在用户完成 PRD 拆分选择前创建 proposal.md、design.md 或 tasks.md。若用户选择创建多个 change当前 `/comet-open` 调用只负责完成拆分确认与调度,随后按用户确认的顺序分别进入每个拆分项的 `/comet-open`
批量拆分模式下,进入每个拆分项的 `/comet-open` 时必须明确标注「已确认拆分项」并携带该拆分项的目标、范围、非目标和验收场景。已确认拆分项默认跳过 PRD 拆分预检,除非该拆分项本身仍明显包含多个独立 capability。
批量拆分模式下,单个拆分项完成 open 阶段后不得自动流转到 `/comet-design`。拆分完毕后必须暂停询问用户开始哪一个 change用户选择后只推进该 change 进入 `/comet-design`,其他 change 保持 active稍后通过 `/comet` 恢复。
最小断点恢复规则:不新增专用批量状态文件。若批量拆分过程中断,恢复时先检查已创建的 active changes已存在且包含 `.comet.yaml` 的拆分项不得重复创建,未创建的拆分项按用户已确认的拆分清单继续通过 `/comet-open` 创建。若对话中已确认的拆分清单不可恢复,必须重新向用户确认拆分清单后再继续。
### 1b. 需求澄清完成确认(阻塞点)
创建 OpenSpec artifacts 前,必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户确认需求澄清完成。
暂停时必须展示澄清摘要:目标、非目标、范围边界、关键未知项、验收场景草案。
不得在用户确认需求澄清完成前创建 proposal.md、design.md 或 tasks.md也不得使用 Skill 工具加载 `openspec-propose` 技能一次性生成全部 artifacts。
### 1c. Change 名称确认(阻塞点)
创建 change 目录(`openspec new change`)前,必须按 `comet/reference/decision-point.md` 的协议暂停,让用户决定 change 名称。不得自动生成或静默推断 change 名称。
OpenSpec change 名称必须是 **kebab-case 英文**(小写字母、数字、连字符;如 `refine-requirements-doc`)。中文或其他不合规名称无效。
暂停时必须展示:
- 基于已确认澄清摘要派生的 **2-3 个推荐 kebab-case 英文名**,每个附一行说明其隐含范围
- 一个让用户 **自行输入名称** 的明确选项
- 提示:**若用户输入中文(或任何非 kebab-case 文本),会被转换为合规的 kebab-case 英文名**,转换结果必须回显给用户确认后才能使用
决策选项必须包含:
- 选择某个推荐名称
- 「自行输入名称」——接收用户输入;若已是合规 kebab-case 英文则直接使用;若为中文或其他不合规形式,则转换为合规 kebab-case 英文并回显转换后的名称,确认后再继续
不得在用户确认最终 change 名称前运行 `openspec new change` 或创建 `.comet.yaml`。若选定/转换后的名称与已有 change 冲突,必须报告冲突并请用户另选名称。
### 2. 创建 Change 结构 + 初始化状态
**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。禁止跳过此步骤。
完整 `/comet` 流程默认不得使用 Skill 工具加载 `openspec-propose` 技能;只有用户明确要求一次性生成提案和 artifacts 时才允许加载。
技能加载后,按其指引创建 change 骨架,但当 Step 1b 的已确认澄清摘要已存在于对话上下文时,覆盖其"STOP and wait for user direction"行为。
如果用户已确认澄清摘要Step 1b直接使用该摘要填充产物内容。如果不存在澄清摘要边缘情况回退到技能的默认行为询问用户。
change 骨架创建后,按以下标准产物循环逐个生成 `proposal`、`design`、`tasks`
**标准产物循环**(对每个 `artifact-id``proposal` → `design``tasks`
1. 刷新状态:`openspec status --change "<name>" --json`
2. 获取产物指令:
```bash
openspec instructions proposal --change "<name>" --json
openspec instructions design --change "<name>" --json
openspec instructions tasks --change "<name>" --json
```
3. 对返回的 JSON 指令载荷,必须:
- 读取 `dependencies` 中列出的每个已完成依赖产物
- 以 `template` 作为产物结构
- 遵循 `instruction` 的指引
- 将 `context``rules` 作为约束条件应用,**不得复制到 artifact 内容中**
- 写入 `resolvedOutputPath`
- 验证输出文件存在且非空
4. 每创建一个 artifact 后,重新运行 `openspec status --change "<name>" --json` 确认状态,然后继续下一个 artifact
**失败处理**:如果 `openspec instructions` 失败、返回无效 JSON、报告未满足的 `dependencies`、或未提供可用的 `resolvedOutputPath`,必须立即停止 artifact 创建并报告 OpenSpec 错误。不得回退为硬编码文档结构,因为那样会绕过项目规则。
**命名与范围守卫**change name 必须使用 Step 1c 中用户确认的 kebab-case 英文名,不得自动生成、推断或使用非 kebab-case如中文名称。变更范围必须与用户描述一致不得自行扩大或缩小。
确认以下产物已创建:
```
openspec/changes/<name>/
├── .openspec.yaml
├── .comet.yaml
├── proposal.md # Why + What问题、目标、范围
├── design.md # How高层架构决策、方案选型
└── tasks.md # 任务清单(勾选框)
```
创建 `.comet.yaml` 状态文件:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
if [ -z "$COMET_STATE" ] || [ -z "$COMET_GUARD" ]; then
echo "ERROR: Comet scripts not found. Ensure the comet skill is installed." >&2
return 1
fi
"$COMET_BASH" "$COMET_STATE" init <name> full
```
### 3. 入口状态验证
验证状态机已正确初始化:
```bash
"$COMET_BASH" "$COMET_STATE" check <name> open
```
验证通过后继续 Step 4。验证失败时脚本会输出具体失败原因。
**幂等性**open 阶段所有操作可安全重复执行。如 `.comet.yaml` 已处于 `phase: open` 且三个产物文件均已存在,跳过已完成步骤,从第一个缺失步骤继续。
### 4. 内容完整性检查
确认三个文档内容完整:
- **proposal.md**:问题背景、目标、范围、非目标
- **design.md**:高层架构决策、方案选型、数据流
- **tasks.md**:任务列表,每个任务有明确描述
**文件存在性验证**:逐个确认三个文件路径存在且非空。任一文件缺失或为空时,不得进入 Step 5 或执行阶段守卫,必须回到创建步骤补充。
### 5. 用户审视确认(阻塞点)
三个文档创建完成且内容完整性检查通过后,**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户确认**。不得在用户确认前执行阶段守卫或自动流转。
用户确认问题必须以单选题形式呈现,包含以下摘要和选项:
**摘要内容**
- **proposal.md**:问题背景、目标、范围
- **design.md**:高层架构决策、方案选型
- **tasks.md**:任务数量和关键任务描述
**选项**
- 「确认,继续下一阶段」— 产物符合预期,执行阶段守卫流转
- 「需要调整」— 附带调整说明,修改后重新请求确认
用户选择「确认」后继续执行退出条件。用户选择「需要调整」时,按其说明修改对应文件,然后重新请求确认。
## 退出条件
- proposal.md、design.md、tasks.md 均已创建且内容完整
- **用户已确认** proposal、design、tasks 内容符合预期
- **阶段守卫**:运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> open --apply`,全部 PASS 后由守卫推进到下一阶段(此步骤更新 `phase` 字段,与 `auto_transition` 无关)
退出前必须使用 `--apply`,否则 `.comet.yaml` 仍停留在 `phase: open`,下一阶段入口检查会失败。
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> open --apply
```
完整流程会自动更新为 `phase: design`hotfix/tweak preset 会自动更新为 `phase: build`
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续
hotfix/tweak preset 由对应 preset skill 控制后续流转phase 直接进入 build`next` 会返回对应 preset skill。

@ -1,176 +0,0 @@
---
name: comet-tweak
description: "Comet 预设路径:非 bug 的小改动tweak。跳过 brainstorming 和完整 plan直接 open → lightweight build → light verify → archive。适用于文案、配置、文档或 prompt 的局部优化。"
---
# Comet 预设路径Tweak
Tweak 是 Comet 五阶段能力的预设工作流,不是独立的平行流程。它复用 open、build、verify、archive 能力,仅跳过 brainstorming 和完整 plan。
适用于非 bug 的小范围变更,例如文案调整、配置调整、文档或 prompt 的局部优化。
**适用条件**(必须全部满足):
1. 不新增 capability
2. 不改变架构
3. 不涉及接口变化
4. 通常不超过 3 个 tasks文件数约束见下方升级条件
**不适用**:如变更过程中发现需要 capability、架构或接口调整应升级为完整 `/comet` 流程。
---
## 流程preset workflow4 阶段)
### 0. 输出语言约束
精简版 OpenSpec 产物必须使用触发本次工作流的用户请求语言。
执行链路open → lightweight build → light verify → archive。Tweak 为每个阶段提供默认决策:精简开启、轻量构建、轻量验证、验证通过后进入归档前最终确认。
开始前先定位 Comet 脚本:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
```
### 1. 快速开启preset open
复用 Comet open 能力创建 change但使用 tweak 默认值:不执行 `openspec-explore` 长探索,直接进入精简 change 创建。
**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。禁止跳过此步骤。
技能加载后,按其指引创建精简版产物:
- `proposal.md` — 变更动机 + 目标 + 范围
- `design.md` — 简短实现说明(无需方案对比)
- `tasks.md` — 不超过 3 个任务
- **无需 delta spec**(除非变更改变了已有 spec 的验收场景;一旦需要 delta spec升级为完整 `/comet`
初始化 Comet 状态文件:
```bash
"$COMET_BASH" "$COMET_STATE" init <name> tweak
```
初始化后验证状态:
```bash
"$COMET_BASH" "$COMET_STATE" check <name> open
```
阶段守卫完成 open → build 过渡:
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> open --apply
```
### 2. 轻量构建preset build
使用 tweak 默认值:`build_mode: direct`。跳过 Superpowers `brainstorming``writing-plans`
继续或开始修改前,按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。若归因后发现范围超出 tweak按本文件“升级条件”处理。
**立即执行:** 按 tasks.md 逐个执行任务:
1. 读取 `openspec/changes/<name>/tasks.md`,获取未完成任务列表
2. 对每个未完成任务:
- 根据任务描述修改目标文件
- 运行项目格式化命令(如 `mvn spotless:apply`、`npm run format` 等)
- 运行相关测试确认通过
- 将 tasks.md 中对应 `- [ ]` 勾选为 `- [x]`
- 提交代码commit message 格式:`tweak: <简述变更>`
3. 全部任务完成后,显式运行项目相关测试和构建命令
4. 运行阶段守卫完成 build → verify 过渡:
执行 tweak 期间,只要运行程序、测试、构建或手动验证时出现崩溃、异常行为、测试失败或构建失败,必须使用 Skill 工具加载 Superpowers `systematic-debugging` 技能。在完成根因调查前,不得提出或实施源码修复。
具体调查、最小失败测试、修复验证和保持当前 change 验证闭环的要求,按 `comet/reference/debug-gate.md` 执行。
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply
```
状态文件自动更新为 `phase: verify`、`verify_result: pending`,然后进入验证。
### 3. 轻量验证preset verify
复用 `/comet-verify`。Tweak 必须保持轻量验证条件:≤ 3 tasks、≤ 4 files、无 delta spec、无新 capability。
**立即执行:** 使用 Skill 工具加载 `comet-verify` 技能。禁止跳过此步骤。
如规模评估进入完整验证路径,停止 tweak按升级条件阻塞确认处理。
验证通过后,按 `/comet-verify` 的规则将 `.comet.yaml``verify_result` 记录为 `pass`,归档前不得跳过该状态。验证通过后仍必须进入 `/comet-archive` 的归档前最终确认,不得自动运行归档脚本。
### 4. 归档preset archive
复用 `/comet-archive`。归档前必须满足 `.comet.yaml``verify_result: pass`,并等待 `/comet-archive` 的归档前最终确认。
**立即执行:** 使用 Skill 工具加载 `comet-archive` 技能进行归档。禁止跳过此步骤。
---
## 连续执行模式
<IMPORTANT>
Tweak 流程默认 **一次性连续执行**。调用 `/comet-tweak`agent 在 tweak 自有步骤间自动推进,不主动停顿。**例外**:若 `auto_transition: false`,则在每个 phase 边界build/verify/archive 之间)停下,由用户手动运行下一阶段命令——此时连续执行降级为逐阶段手动推进,详见下方「自动衔接下一阶段」。但无论 `auto_transition` 取何值,以下情况都必须暂停等待用户确认:
1. 遇到升级条件(见"升级条件"章节),**必须使用当前平台可用的用户输入/确认机制暂停并等待用户明确确认**升级为完整流程
2. 验证阶段comet-verify的验证失败决策和分支处理决策
3. 归档前最终确认comet-archive 执行归档脚本前)
执行顺序:快速开启 → 轻量构建 → 轻量验证 → 归档 → 完成
每个阶段完成后立即进入下一阶段。阶段内部仍必须按上文要求调用对应 Comet/OpenSpec/Superpowers skill被调用的 skill 如有自己的用户决策点,按该 skill 规则执行。
</IMPORTANT>
---
## 升级条件
满足以下**任一**条件时,停止 tweak 流程,升级为完整 `/comet`
| 条件 | 说明 |
|------|------|
| 改动涉及 **5+ 文件** | 超出小改动范围 |
| 多模块协调修改 | 需要跨组件协调 |
| 需要新增测试用例 **5+** | 变更复杂度上升 |
| 配置项新增或删除 | 非值修改的配置变更 |
| 需要新增 capability | 超出局部优化 |
| 需要 delta spec | 影响了已有规格 |
满足升级条件时**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户明确确认**升级为完整 `/comet` 流程。不得直接进入 `/comet-design`,不得自动补充 Design Doc。
用户确认升级后,**必须先更新 workflow 和 phase 字段**再进入完整流程:
```bash
"$COMET_BASH" "$COMET_STATE" set <name> workflow full
"$COMET_BASH" "$COMET_STATE" set <name> phase design
```
然后在当前 change 基础上补充 Design Doc**立即使用 Skill 工具加载 `comet-design` skill**,后续正常走完整流程。若用户不确认升级,停止 tweak 并报告当前变更已超出 tweak 适用范围。
---
## 退出条件
- 小改动已完成,测试通过
- change 已归档
- 未新增 capability、架构调整或接口变化
- **阶段守卫**build → verify 前运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> build --apply`verify → archive 前按 `/comet-verify` 规则运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> verify --apply`
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 继续 tweak 流程(`phase: build` 返回 `comet-tweak``verify` 返回 `comet-verify``archive` 返回 `comet-archive`
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续

@ -1,232 +0,0 @@
---
name: comet-verify
description: "Comet 阶段 4验证与收尾。用 /comet-verify 调用。验证实现符合设计,处理开发分支。"
---
# Comet 阶段 4验证与收尾Verify
## 前置条件
- 代码已提交(阶段 3 完成)
- tasks.md 全部任务已完成
## 步骤
### 0a. 输出语言约束
验证报告和分支处理说明必须使用触发本次工作流的用户请求语言。
### 0b. 入口状态验证Entry Check
执行入口验证:
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
"$COMET_BASH" "$COMET_STATE" check <change-name> verify
```
验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。
**幂等性**verify 阶段所有检查可安全重复执行。如 `verify_result` 已为 `pass``branch_status` 已为 `handled`,说明验证已完成,直接执行 guard 流转。如 `verify_result``pending`,从头开始验证。
### 1. 改动规模评估
执行规模评估:
```bash
"$COMET_BASH" "$COMET_STATE" scale <change-name>
```
脚本自动统计任务数、增量规格数、变更文件数,判断使用 light 或 full 验证模式,并设置 verify_mode 字段。判定规则(满足任一即 full任务数 > 3、delta spec 能力数 > 1、变更文件数 > 4。
验证开始前,按 `comet/reference/dirty-worktree.md` 协议检查并处理未提交改动。verify 阶段的特殊处理:
1. 若 dirty diff 属于当前 change 且涉及实现、测试、tasks、delta spec 或 design doc 变更,不在 verify 阶段直接修复或提交;报告失败项并进入 Step 1b 的验证失败决策阻塞点
2. 若 dirty diff 只是 verify 本阶段产物(例如验证报告草稿、分支处理记录),可继续在 verify 阶段完成并记录状态
3. 若 dirty diff 已实现但 tasks.md 未勾选,视为 build 状态滞后;报告失败项并进入 Step 1b由用户决定回退修复或接受偏差
用户选择修复后,才允许回退到 build 阶段:
```bash
# 仅在用户确认修复后执行
"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail
```
注意verify-fail 回退到 build 时 `branch_status` 不会被重置。如果首次 verify 已完成分支处理,修复后再次进入 verify 时跳过已完成的分支处理步骤,直接使用 `"$COMET_BASH" "$COMET_STATE" set <change-name> branch_status handled` 保留原有分支处理结果。
注意:如果 build 阶段每个任务都已提交,脚本基于工作区 diff 的文件数可能低估改动规模。此时必须读取 plan 文件头的 `base-ref` 并用提交区间复核:
```bash
PLAN=$("$COMET_BASH" "$COMET_STATE" get <change-name> plan)
BASE_REF=$(grep '^base-ref:' "$PLAN" 2>/dev/null | head -1 | sed 's/^base-ref: *//')
git diff --stat "$BASE_REF"...HEAD
```
若提交区间显示改动超过轻量阈值(> 4 个文件、跨模块协调、或 delta spec 超过 1 个 capability手动设置为完整验证
```bash
"$COMET_BASH" "$COMET_STATE" set <change-name> verify_mode full
```
**覆盖机制**:如 agent 或用户认为自动评估结果不合适,可随时通过 `"$COMET_BASH" "$COMET_STATE" set <change-name> verify_mode <light|full>` 手动覆盖。
### 1b. 验证失败决策(阻塞点)
验证不通过时**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户决定修复或接受偏差**。不得自动运行 `"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail`,也不得自动调用 `/comet-build`
暂停时必须列出:
- 失败项
- 是否属于 CRITICAL 或 IMPORTANT构建失败、测试失败、安全问题、核心验收场景失败、简化代码审查发现的正确性/安全/边界问题)
- 推荐处理方式
**不确定性原则**无法确定严重程度时降级处理SUGGESTION > WARNING > CRITICAL。仅对构建失败、测试失败、安全问题使用 CRITICAL模糊或不确定的问题标为 WARNING 或 SUGGESTION。
用户选择后按以下方式继续:
- **全部修复**:运行 `"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail`,然后调用 `/comet-build` 修复
- **逐项处理**CRITICAL 或 IMPORTANT 失败项必须修复WARNING/SUGGESTION 失败项可选择接受偏差,但必须在验证报告中记录接受原因和影响范围。若存在任何 CRITICAL 或 IMPORTANT 失败项,不允许跳过修复直接全部接受
### 2. 产物上下文加载Hash 按需读)
验证需要读取 OpenSpec 产物时,先检查产物是否自 design 阶段以来发生变化:
```bash
RECORDED_HASH=$("$COMET_BASH" "$COMET_STATE" get <change-name> handoff_hash)
CURRENT_HASH=$("$COMET_BASH" "$COMET_HANDOFF" <change-name> --hash-only 2>/dev/null || echo "")
```
- 若 `RECORDED_HASH` = `CURRENT_HASH` 且均非空且均非 `null`OpenSpec 产物未变化,**tasks.md 无需重新读取全文**(用 `grep -c '\- \[ \]' tasks.md` 确认完成数即可。proposal.md、design.md、delta spec 仍需读取用于对照检查。
- 若 `RECORDED_HASH` 为空、为 `null`、或与 `CURRENT_HASH` 不一致:产物已变化或 hash 未记录,正常读取所有所需文件全文。
此优化仅跳过 tasks.md 的重复全文读取。proposal.md 和 design.md 包含验证检查项所需的完整上下文,不得因 hash 匹配而跳过。
**立即执行:** 使用 Skill 工具加载 Superpowers `verification-before-completion` 技能。禁止跳过此步骤。
技能加载后,按 verify_mode 分支执行:
### 2a. 轻量验证(小改动)
按以下 6 项进行检查:
1. tasks.md 全部任务已完成 `[x]`
2. 改动文件与 tasks.md 描述一致(`git diff --stat` / `git diff --cached --stat` / `git diff --stat <base-ref>...HEAD` 对照 tasks 内容)
3. 编译通过(执行项目对应的构建命令,如 `npm run build`、`mvn compile`、`cargo build` 等)
4. 相关测试通过
5. 无明显安全问题(无硬编码密钥、无新增 unsafe 操作)
6. 代码审查策略:当 `review_mode: standard``thorough` 时,必须使用 Skill 工具加载 Superpowers `requesting-code-review` 技能,请求只检查正确性、安全、边界条件的轻量代码审查;当 `review_mode: off` 时跳过自动代码审查,并在验证报告中记录跳过原因
简化代码审查的输入应限定为本次改动 diff、tasks.md 和必要的测试结果;审查范围只覆盖实现正确性、安全风险和边界条件,不执行 spec 覆盖率、Design Doc 一致性或漂移检查。若审查发现 CRITICAL 或 IMPORTANT 问题,按验证失败处理并进入 Step 1b。`review_mode: off` 只跳过自动 code review不跳过构建、测试、安全检查或异常调试协议。
**通过标准**6 项全部 OK无 CRITICAL 或 IMPORTANT 问题。
**不通过时**:报告失败项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/comet-build` 修复:
```bash
# 仅在用户确认修复后执行
"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail
```
**报告格式**:简表列出 6 项检查结果 + PASS/FAIL。
**跳过项**(不在轻量验证中检查):
- spec scenario 覆盖率
- design doc 一致性深度比对
- 不影响正确性、安全、边界条件的 code pattern consistency 建议
- delta spec 与 design doc 漂移检测
### 2b. 完整验证(大改动)
当规模评估结果为"大"时:
**立即执行:** 使用 Skill 工具加载 `openspec-verify-change` 技能。禁止跳过此步骤。
技能加载后,按其指引验证。检查项:
1. tasks.md 全部任务已完成(`[x]`
2. 实现符合 `openspec/changes/<name>/design.md` 高层设计决策
3. 实现符合 Design Doc`docs/superpowers/specs/` 下的技术设计文档)
4. 能力规格场景全部通过
5. proposal.md 目标已满足
6. delta spec 与 design doc 无矛盾(若 Build 阶段有增量修改 spec检查 design doc 是否有对应记录)
7. `docs/superpowers/specs/` 关联的设计文档可定位(文件存在且与当前 change 相关)
验证不通过时:报告缺失项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/comet-build` 补充:
```bash
# 仅在用户确认修复后执行
"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail
```
**Spec 漂移处理**(用户决策点):
- 若检查项 6 发现矛盾delta spec 有内容但 design doc 未体现),**必须使用当前平台可用的用户输入/确认机制以单选题形式暂停并等待用户选择处理方式**,不得自动选择。选项:
- 选项 A在 design doc 追加 "Implementation Divergence" 节记录偏差原因。选项 A 属于 verify 阶段允许产物;写入后不得因该 design doc 变更再次触发 Step 1b dirty-worktree 决策
- 选项 B用户选择 B 后,运行 `"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail`,然后调用 `/comet-build`;由 `/comet-build` 的 Spec 增量更新规则加载 Superpowers `brainstorming` 更新 Design Doc + delta spec
- 选项 C确认偏差可接受继续验证归档时 design doc 将标记为 `superseded-by-main-spec`
### 3. 收尾Superpowers
**立即执行:** 使用 Skill 工具加载 Superpowers `finishing-a-development-branch` 技能。禁止跳过此步骤。
如 Superpowers `finishing-a-development-branch` 技能不可用,停止流程并提示安装或启用 Superpowers 技能,不要用普通对话替代该步骤。
技能加载后,按其指引收尾。分支处理选项:
1. 本地合并到主分支
2. 推送并创建 PR
3. 保持分支(稍后处理)
4. 丢弃工作
这是用户决策点。**必须按 `comet/reference/decision-point.md` 的协议暂停并等待用户选择分支处理方式**,不得根据推荐、默认值或当前分支状态自行选择。只有在用户完成选择且对应操作完成后,才允许写入 `branch_status: handled`
**确认项**
- 全部测试通过
- 无硬编码密钥或安全问题
### 4. 记录验证证据
验证报告必须落盘,并在 `.comet.yaml` 中记录;分支处理完成后也必须写入状态字段。不要手动设置 `verify_result: pass`,由阶段守卫 `--apply` 推进。
```bash
mkdir -p docs/superpowers/reports
# 将本次验证结论写入报告文件,例如:
# docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md
"$COMET_BASH" "$COMET_STATE" set <change-name> verification_report docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md
"$COMET_BASH" "$COMET_STATE" set <change-name> branch_status handled
```
## 退出条件
- 验证报告通过
- 分支已处理
- `.comet.yaml``verification_report` 指向已存在的验证报告文件
- `.comet.yaml``branch_status: handled`
- **阶段守卫**:运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> verify --apply`,全部 PASS 后由守卫通过 `comet-state transition verify-pass` 推进到 `phase: archive`(此步骤更新 `phase` 字段,与 `auto_transition` 无关)
验证和分支处理均完成后,运行阶段守卫推进 phase此步骤与 `auto_transition` 无关):
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> verify --apply
```
状态文件自动更新为 `phase: archive`、`verify_result: pass`、`verified_at: YYYY-MM-DD`。
## 上下文压缩恢复
`comet/reference/context-recovery.md` 执行phase 参数为 `verify`
## 自动衔接下一阶段
`comet/reference/auto-transition.md` 执行。关键命令:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续
注意:无论 `NEXT``auto` 还是 `manual``comet-archive` 进入后必须先执行归档前最终确认阻塞点,等待用户明确选择「确认归档」后才允许运行归档脚本。不得因为验证已通过就自动归档。

@ -1,269 +0,0 @@
---
name: comet
description: "Comet — OpenSpec + Superpowers 双星开发流程。用 /comet 启动,自动检测阶段并分发到子命令。五阶段:开启 → 深度设计 → 计划与构建 → 验证与收尾 → 归档。"
---
# Comet — OpenSpec + Superpowers 双星开发流程
OpenSpec 与 Superpowers 如双星系统围绕同一目标运转。
```
OpenSpec 负责 WHAT — 大纲、提案、spec 生命周期、归档
Superpowers 负责 HOW — 技术设计、计划、执行、收尾
```
**核心原则brainstorming 必不可跳过。每次变更都必须经过深度设计hotfix 和 tweak preset 除外)。**
---
## 决策核心Decision Core
agent 做决策只需读本节,参考附录按需查阅。
### 输出语言规则
以触发本次工作流的用户请求语言作为默认输出语言。恢复已有 change 时,如果现有产物有明确主语言,除非用户明确要求切换,否则保持该语言。
### 阶段自动检测
**Step 0: 活跃 Change 发现与意图判定**
1. 先做 Preset 检测;命中 hotfix/tweak 时直接调用对应 preset skill不进入普通 open 分支
2. 未命中 preset 时,运行 `openspec list --json` 获取所有活跃 change
**Preset 检测优先级最高**
- 用户明确描述为 bug fix / 热修复 + 满足 hotfix 条件 → 直接 `/comet-hotfix`
- 用户明确描述为文案/配置/文档/prompt 小调整 + 满足 tweak 条件 → 直接 `/comet-tweak`
- 未命中 preset → 按下表处理
| 活跃 change | 用户输入 | 行为 |
|-------------|---------|------|
| 无 | 非 preset 输入 | → 调用 `/comet-open` |
| 恰好 1 个 | `/comet <描述>` | → **询问**:继续该变更 or 创建新变更 |
| 多个 | `/comet <描述>` | → **询问**:继续现有变更 or 创建新变更;若选继续 → 列出清单让用户选择 |
| 恰好 1 个 | `/comet`(无描述) | → 自动选中,进入 Step 1 |
| 多个 | `/comet`(无描述) | → 列出清单让用户选择 |
<IMPORTANT>
当用户选择「创建新变更」时,**必须调用 `/comet-open`**(禁止直接调用 `/opsx:new`)。
`/comet-open` 负责完整双初始化OpenSpec artifacts由内部 `/opsx:new` 创建)+ `.comet.yaml` 状态文件。
直接调用 `/opsx:new` 会缺失 `.comet.yaml`,导致后续阶段判定失败。
</IMPORTANT>
**Step 1: 读取 `.comet.yaml` 状态元数据**
优先读取 `openspec/changes/<name>/.comet.yaml`。不存在时回退到 `openspec status --change "<name>" --json`、`tasks.md` 和 `docs/superpowers/` 文件检查。
**断点恢复规则**
- 每次恢复上下文时,先重新执行 Step 0 和 Step 1不依赖对话历史判断阶段
- 只要存在 active change 且工作区有未提交改动,必须按 `comet/reference/dirty-worktree.md` 协议处理。该协议定义了检查步骤、归因分类和禁令,本文件不重复
- 若 `phase: build`,先检查 `build_pause`、`plan`、`build_mode` 和 `isolation`(详见下方):
- 若 `build_pause: plan-ready``isolation``build_mode` 已经设置,则视为 stale pause先输出 `[COMET] 检测到 stale pausebuild_pause=plan-ready 但 isolation/build_mode 已设置),自动清除并继续`,再运行 `"$COMET_BASH" "$COMET_STATE" set <name> build_pause null`,然后读取 tasks.md 的下一个未勾选任务并按 `build_mode` 恢复执行
- 若 `build_pause: plan-ready` 且 plan 文件存在,但 `isolation``build_mode` 尚未设置,回到 `/comet-build` 的 plan-ready 恢复点,提示用户继续选择隔离方式和执行方式,不重新生成 plan
- 若 `build_pause: plan-ready` 但 plan 文件缺失,回到 `/comet-build` 处理状态损坏或重新生成 plan
- 若 `build_mode`、`isolation` 或 `tdd_mode` 未设置,回到 `/comet-build` 对应步骤补充后再执行
- 若均已设置,读取 tasks.md 的下一个未勾选任务,并按 `build_mode` 恢复执行:
- 若 `build_mode: subagent-driven-development`,不得在主窗口直接执行任务;必须回到 `/comet-build` 的后台 subagent 调度规则,由主窗口只做协调
- 其他执行方式按 `/comet-build` 的对应规则继续
- 若 `phase: verify``verify_result: fail`,进入验证失败决策阻塞点:暂停并询问用户修复或接受偏差;用户选择修复后才运行 `"$COMET_BASH" "$COMET_STATE" transition <name> verify-fail` 并调用 `/comet-build`
- 若 `phase: open` 但 proposal/design/tasks 已完整,先运行 `"$COMET_BASH" "$COMET_GUARD" <change-name> open --apply` 修正状态,再继续判定
- 若 `phase: archive`,只允许调用 `/comet-archive``/comet-archive` 必须先等待归档前最终确认,归档成功后 change 会移动到 archive 目录,不再对原活跃目录运行 guard
**Step 2: 阶段判定**(按顺序,命中即停)
1. `archived: true` 或 change 已移入 archive → 流程已完成
2. `verify_result: pass``archived` 不是 `true``/comet-archive`(先进行归档前最终确认)
3. `verify_result: fail` → 进入验证失败决策阻塞点(暂停询问修复或接受偏差;用户选择修复后才 `verify-fail``/comet-build`
4. `phase: verify` 或 tasks.md 全部勾选 → `/comet-verify`
5. `phase: build` 或已有 Design Doc 但计划/执行未完成 → 优先按 workflow 路由:`hotfix` → `/comet-hotfix``tweak` → `/comet-tweak``full` → `/comet-build`
6. `phase: design` 或有 change 但无 Design Doc → `/comet-design`
7. `phase: open` 或有活跃 change 但 `.comet.yaml` 缺失 → `/comet-open`
8. 无活跃 change → `/comet-open`
如果元数据与文件状态冲突,以文件状态为准,修正 `.comet.yaml` 后继续。
### 预设升级条件
**hotfix → full**(满足任一即升级):
- 改动涉及 **3+ 文件**
- 涉及架构变更(新模块、新接口、新依赖)
- 涉及数据库 schema 变更
- 修复引入新的 public API
- 修复范围超出单一函数/模块
**tweak → full**(满足任一即升级):
- 改动涉及 **5+ 文件**
- 涉及多个模块的协调修改
- 需要新增测试用例 **5+**
- 涉及配置项的新增或删除(非值修改)
- 需要新增 capability
- 需要 delta spec影响了已有规格
### 错误处理速查
| 场景 | 处理方式 |
|------|---------|
| `openspec list --json` 失败 | 检查 openspec 是否已安装,提示 `openspec init` |
| 子 skill 不可用 | 停止流程,提示安装或启用对应 skill |
| `.comet.yaml` 格式异常或缺失 | 以文件状态为准,用 `"$COMET_BASH" "$COMET_STATE" set` 修正后继续 |
| 构建/测试失败 | 返回 build 阶段修复,不进入 verify |
| change 目录结构不完整 | 按 `comet-open` 产物要求补齐 |
### 阶段衔接
<IMPORTANT>
单次 `/comet` 调用从检测到的阶段开始,退出条件满足后进入下一阶段。
流转链open → design → build → verify → archive
**连续执行要求**从检测到的阶段开始agent 自动推进后续阶段。但**自动推进仅适用于没有用户决策的衔接点**。遇到用户决策点时,**必须使用当前平台可用的用户输入/确认机制暂停并等待用户明确回复**,不得用推荐规则、默认值或历史偏好代替用户确认,也不得仅输出文字提示后继续执行。
**阶段推进与自动衔接的区分**:每个子 skill 退出前都会运行阶段守卫 `--apply` 推进 `.comet.yaml``phase` 字段——这一步**始终发生**,与 `auto_transition` 无关。之后子 skill 运行 `"$COMET_BASH" "$COMET_STATE" next <name>` 解析下一步:`auto_transition` 不为 `false` 时输出 `NEXT: auto`(自动调用下一 skill`false` 时输出 `NEXT: manual`(不调用下一 skill提示用户手动运行。因此 `auto_transition` **只控制是否自动调用下一个 skill不影响 phase 推进**。无论 `auto_transition` 取何值,下方的用户决策点都必须阻塞等待。
**决策点是阻塞点**:只要到达下列任一节点,当前 `/comet` 调用必须停住,并按 `comet/reference/decision-point.md` 的协议获取用户明确选择。用户明确选择后才能写入对应状态字段、执行对应操作,随后再继续自动流转。
需要用户参与的节点(仅在这些节点暂停):
1. open 阶段 proposal/design/tasks 审视确认
2. brainstorming 确认设计方案
3. build 阶段 plan-ready 暂停选择,以及随后选择工作方式(隔离方式 + 执行方式)
4. verify 不通过时决定修复或接受偏差(含 Spec 漂移处理方式选择)
5. finishing-branch 选择分支处理方式
6. archive 阶段执行归档脚本前的最终确认
7. 遇到升级条件hotfix/tweak → 完整流程)
8. build 阶段范围扩张需重新设计或拆分新 change
9. open 阶段大型 PRD 需确认拆分为多个 change
agent 不应跳过这些决策点;其他明确无歧义的阶段衔接必须自动继续推进,不得中途退出。到达决策点时,**禁止跳过用户确认或自动选择——必须通过当前平台可用的用户输入/确认机制明确获取用户选择后才能继续**。
**红旗清单** — 以下想法出现时立即停止并检查:
| Agent 心理 | 实际风险 |
|-----------|---------|
| "用户应该会同意这个方案" | 不能替用户决策,必须等待用户明确选择 |
| "这只是个小改动,不需要确认" | 决策点无大小之分,阻塞点必须等待 |
| "用户之前选过 A这次也选 A" | 历史偏好不能替代当前确认 |
| "我已经解释了方案,用户没反对" | 没反对 ≠ 同意,必须用工具获取明确选择 |
| "流程走到这里应该没问题了" | 验证不通过 ≠ 通过,检查 verify_result |
</IMPORTANT>
---
## 子命令速查
| 命令 | 阶段 | 归属 | 产物 |
|------|------|------|------|
| `/comet-open` | 1. 开启 | OpenSpec | proposal.md、design.md、tasks.md |
| `/comet-design` | 2. 深度设计 | Superpowers | Design Doc、delta spec |
| `/comet-build` | 3. 计划与构建 | Superpowers | 实施计划、代码提交 |
| `/comet-verify` | 4. 验证与收尾 | Both | 验证报告、分支处理 |
| `/comet-archive` | 5. 归档 | OpenSpec | delta→main spec 同步、design doc 标注、归档 |
| `/comet-hotfix` | 预设路径 | Both | 快速修复(跳过 brainstorming |
| `/comet-tweak` | 预设路径 | Both | 小改动(跳过 brainstorming 和完整 plan |
```
/comet
↓ 自动检测
/comet-open ──→ /comet-design ──→ /comet-build ──→ /comet-verify ──→ /comet-archive
(OpenSpec) (Superpowers) (Superpowers) (Both) (OpenSpec)
/comet-hotfix预设路径跳过 brainstorming
open ──→ build ──→ verify ──→ archive
↑ 如触发升级条件 → 阻塞确认升级 → 补充 Design Doc → 回到完整流程
/comet-tweak预设路径跳过 brainstorming 和完整 plan
open ──→ lightweight build ──→ light verify ──→ archive
↑ 如触发升级条件 → 阻塞确认升级 → 补充 Design Doc → 回到完整流程
```
---
## 参考附录Reference Appendix
> 字段说明、文件结构和自动衔接协议已提取为渐进式加载参考文档,按需查阅:
> - **`.comet.yaml` 完整字段表**:按 `comet/reference/comet-yaml-fields.md` 查阅(含必需字段、可选字段和完整示例)
> - **文件结构**:按 `comet/reference/file-structure.md` 查阅
> - **自动衔接协议**:按 `comet/reference/auto-transition.md` 查阅
> - **上下文压缩恢复**:按 `comet/reference/context-recovery.md` 查阅
> - **用户决策点协议**:按 `comet/reference/decision-point.md` 查阅
> - **异常调试协议**:按 `comet/reference/debug-gate.md` 查阅
### 状态机硬约束
- `build → verify` 前,`isolation` 必须是 `branch``worktree`
- `build → verify` 前,`build_mode` 必须已选择
- `build_mode: subagent-driven-development` 必须同时有 `subagent_dispatch: confirmed`
- full workflow 离开 build 阶段前 `tdd_mode` 必须已选择为 `tdd``direct`
- `build_mode: direct` 默认只允许 `hotfix` / `tweak`full workflow 需要 `direct_override: true`
- `build_pause` 不是执行方式,不得写入 `build_mode`
- 这些约束同时存在于 `comet-guard.sh build --apply``comet-state.sh transition <name> build-complete`
### 脚本定位
Comet 脚本随 skill 包分发在 `comet/scripts/` 下。**不硬编码路径** — 定位一次,缓存到环境变量。此块为标准样板,在每个子 skill 中独立重复以确保可独立加载;修改时必须保持所有文件同步(样板版本: `v2`,变更时更新此版本号便于定位需要同步的文件):
```bash
COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}"
if [ -z "$COMET_ENV" ]; then
echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2
return 1
fi
. "$COMET_ENV"
# 脚本定位失败时停止流程
if [ -z "$COMET_GUARD" ] || [ -z "$COMET_STATE" ] || [ -z "$COMET_HANDOFF" ] || [ -z "$COMET_ARCHIVE" ]; then
echo "ERROR: Comet scripts not found. Ensure the comet skill is installed." >&2
echo "Expected path pattern: */comet/scripts/comet-*.sh under project or platform skill directories" >&2
return 1
fi
```
**自动状态更新**guard 支持 `--apply` 参数,验证通过后自动更新 `.comet.yaml` 状态字段:
```bash
"$COMET_BASH" "$COMET_GUARD" <change-name> <phase> --apply
```
`--apply` 内部委托给 `comet-state transition`。需要直接表达状态事件时使用:
```bash
"$COMET_BASH" "$COMET_STATE" transition <change-name> open-complete
"$COMET_BASH" "$COMET_STATE" transition <change-name> design-complete
"$COMET_BASH" "$COMET_STATE" transition <change-name> build-complete
"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-pass
"$COMET_BASH" "$COMET_STATE" transition <change-name> verify-fail
"$COMET_BASH" "$COMET_STATE" transition <archive-name> archived
```
**解析下一步**:阶段守卫推进 phase 后,用 `next` 子命令解析是否自动调用下一个 skill
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
输出 `NEXT: auto|manual|done` + `SKILL: <skill-name>``done` 时省略)+ `HINT`(仅 `manual` 时)。`auto_transition: false` 时输出 `manual`,只暂停下一 skill 调用,不影响已发生的 phase 推进。
**归档脚本**:一键完成归档全部步骤:
```bash
"$COMET_BASH" "$COMET_ARCHIVE" <change-name>
```
加载 comet 后agent 应执行以上变量赋值一次,后续全程复用 `$COMET_GUARD`、`$COMET_STATE`、`$COMET_HANDOFF`、`$COMET_ARCHIVE`。
### 文件结构
`comet/reference/file-structure.md` 查阅完整目录结构。
### 最佳实践
1. **brainstorming 不可跳过** — 每次变更必须经过深度设计hotfix 和 tweak 除外)
2. **delta spec 是活文档** — 阶段 3 期间可自由修改,归档时同步
3. **交接包由脚本生成** — OpenSpec → Superpowers 的上下文必须通过 `comet-handoff.sh` 生成 compact 可追溯摘录(需要全文时用 `--full`),并由 guard 校验 source/hash/mode
4. **保持 tasks.md 同步** — 完成一个勾一个
5. **频繁提交** — 每个任务一次提交message 体现设计意图
6. **先验证再确认归档** — `/comet-verify` 通过后进入 `/comet-archive`,但运行归档脚本前必须等待用户最终确认
7. **增量更新分级** — 小编辑、中重 brainstorming、大新 change
8. **Plan 必须关联 change** — 文件头包含 `change:``design-doc:` 元数据
9. **归档闭环** — design doc 和 plan 必须标注 `archived-with` 状态
10. **修改已有功能** — 直接 open 新 change 即可
11. **Preset 有上限** — hotfix/tweak 满足升级条件时及时切换到完整流程

@ -1,27 +0,0 @@
# 自动衔接下一阶段协议
规范路径:`comet/reference/auto-transition.md`
本协议由所有 comet 子 skill 共享,定义阶段守卫推进后的自动衔接规则。
## 术语区分
「阶段守卫推进」由 guard `--apply` 完成,更新 `.comet.yaml``phase` 字段——这一步**始终发生**,与 `auto_transition` 无关。本协议的「自动衔接」只决定**是否自动调用下一个 skill**,由 `auto_transition` 控制。
## 执行方式
退出条件满足且阶段守卫推进 phase 后,运行:
```bash
"$COMET_BASH" "$COMET_STATE" next <change-name>
```
脚本根据 `phase`、`workflow`、`auto_transition` 输出确定性的下一步:
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
- `NEXT: manual` → 不要调用下一 skill`HINT` 提示用户手动运行 `/<SKILL>`
- `NEXT: done` → 流程已完成,无需继续
## preset 路由
`workflow: hotfix` 时,`phase: build` 返回 `comet-hotfix``workflow: tweak` 时返回 `comet-tweak`。其余 phase`verify`、`archive`)按标准 skill 名称返回(`comet-verify`、`comet-archive`),不受 workflow 类型影响。preset skill 内部的"连续执行模式"可能覆盖 `auto_transition` 行为——详见对应 preset 的 `<IMPORTANT>` 块。

@ -1,71 +0,0 @@
# .comet.yaml 字段说明
规范路径:`comet/reference/comet-yaml-fields.md`
本文件是 `.comet.yaml` 状态文件的字段参考。按需查阅,不随 skill 一次性加载。
## 示例
```yaml
workflow: full
phase: build
design_doc: docs/superpowers/specs/YYYY-MM-DD-topic-design.md
plan: docs/superpowers/plans/YYYY-MM-DD-feature.md
base_ref: a1b2c3d4e5f6...
build_mode: subagent-driven-development
build_pause: null
subagent_dispatch: confirmed
tdd_mode: tdd
review_mode: standard
isolation: branch
verify_mode: light
verify_result: pending
verification_report: null
branch_status: pending
created_at: 2026-05-26
verified_at: null
archived: false
```
## 必需字段
| 字段 | 含义 |
|------|------|
| `workflow` | `full`、`hotfix` 或 `tweak` |
| `phase` | 当前阶段:`open`、`design`、`build`、`verify`、`archive`init 统一设为 `open`guard 负责过渡) |
| `design_doc` | 关联的 Superpowers Design Doc 路径,可为空 |
| `plan` | 关联的 Superpowers Plan 路径,可为空 |
| `base_ref` | init 时记录的 git commit SHA用于 scale 评估。无 plan 时作为改动文件数统计基准 |
| `build_mode` | 已选择的执行方式,可为空 |
| `build_pause` | build 阶段内部暂停点。`null` 表示无暂停,`plan-ready` 表示 plan 已生成,用户选择切换模型后暂停 |
| `subagent_dispatch` | `null``confirmed`。仅当已确认当前平台存在真实后台 subagent / Task / multi-agent 调度能力时,`build_mode: subagent-driven-development` 才能写入并用于离开 build 阶段 |
| `tdd_mode` | `tdd``direct`。full workflow 离开 build 阶段前必须已选择。`tdd` 强制每个任务先写失败测试再实现;`direct` 不强制 TDD。hotfix/tweak 默认 `direct` |
| `review_mode` | `off`、`standard` 或 `thorough`。full workflow 离开 build 阶段前必须已选择hotfix/tweak 默认 `off` |
| `isolation` | `branch``worktree`工作区隔离方式。full 初始化可为 `null`,但只允许持续到 `/comet-build` Step 3 前hotfix/tweak 默认 `branch` |
| `verify_mode` | `light``full`,可为空 |
| `auto_transition` | `true``false`。只控制阶段守卫推进 phase 后是否自动调用下一个 skill`false` 时由 `comet-state next` 输出 `manual`,暂停下一 skill 调用,但不阻止 phase 字段更新 |
| `verify_result` | `pending`、`pass` 或 `fail` |
| `verification_report` | 验证报告文件路径verify 通过前必须指向已存在文件 |
| `branch_status` | `pending``handled`,分支处理完成后设为 `handled` |
| `created_at` | change 创建日期init 时自动写入),格式 `YYYY-MM-DD` |
| `verified_at` | 验证通过时间,可为空 |
| `archived` | change 是否已归档 |
## 可选字段
| 字段 | 含义 |
|------|------|
| `direct_override` | `true`/`false`。full workflow 如需使用 `build_mode: direct`,必须显式设为 `true` |
| `build_command` | 项目构建命令。guard 优先运行该命令,失败时打印命令输出 |
| `verify_command` | 项目验证命令。verify guard 优先运行该命令,未配置时回退到构建命令 |
## 状态机硬约束
- `build → verify` 前,`isolation` 必须是 `branch``worktree`
- `build → verify` 前,`build_mode` 必须已选择
- `build_mode: subagent-driven-development` 必须同时有 `subagent_dispatch: confirmed`
- full workflow 离开 build 阶段前 `tdd_mode` 必须已选择为 `tdd``direct`
- full workflow 离开 build 阶段前 `review_mode` 必须已选择为 `off`、`standard` 或 `thorough`
- `build_mode: direct` 默认只允许 `hotfix` / `tweak`full workflow 需要 `direct_override: true`
- `build_pause` 不是执行方式,不得写入 `build_mode`
- 这些约束同时存在于 `comet-guard.sh build --apply``comet-state.sh transition <name> build-complete`

@ -1,35 +0,0 @@
# 上下文压缩恢复协议
规范路径:`comet/reference/context-recovery.md`
本协议由所有可能触发上下文压缩的 comet 子 skill 共享。当 agent 怀疑发生上下文压缩(之前对话被摘要、找不到之前讨论的内容)时,按本协议恢复。
## 恢复步骤
```bash
"$COMET_BASH" "$COMET_STATE" check <change-name> <phase> --recover
```
脚本输出结构化恢复上下文phase、已完成字段、待完成字段、恢复动作。按 **Recovery action** 决定下一步。
## build 阶段特殊恢复
若恢复脚本输出 `build_mode: subagent-driven-development`
1. 使用 Skill 工具重新加载 Superpowers `subagent-driven-development` 技能
2. 重新阅读 `comet/reference/subagent-dispatch.md` 获取 Comet 专属扩展
3. 读取 `openspec/changes/<name>/.comet/subagent-progress.md`,恢复当前 task 或 final review、实现提交、RED/GREEN 证据、已通过审查、未解决反馈和审查-修复轮次
4. 禁止在主会话中直接执行 task
5. 按检查点记录的精确阶段恢复;检查点缺失或不匹配时才从第一个未勾选 task 的 implementer 派发开始
6. task 按 `review_mode` 完成验收并完成定向勾选验证后,立即继续下一个 task不得总结或询问是否继续
## design 阶段特殊恢复
- 若用户尚未确认设计方案,回到 brainstorming 继续
- 若用户已确认,继续创建 Design Doc
- 恢复时重新加载 `brainstorm-summary.md` + handoff 上下文文件
## verify/archive 阶段恢复
- verify脚本输出验证状态、分支状态和恢复动作
- archive`archived: true` 且归档目录存在,归档已完成,无需再次执行

@ -1,17 +0,0 @@
# 异常调试协议
规范路径:`comet/reference/debug-gate.md`
本协议由 build、hotfix、tweak 等会直接修改代码的 comet 子 skill 共享。当运行程序、测试、构建或手动验证时出现崩溃、异常行为、测试失败或构建失败,必须进入异常调试协议。
## 核心规则
- 立即使用 Skill 工具加载 Superpowers `systematic-debugging` 技能
- 在完成根因调查前,不得提出或实施源码修复
## 四阶段流程
1. 先复现并定位根因,读取完整错误、检查近期变更、追踪数据流
2. 若根因指向源码 bug先补充能复现该崩溃/异常的最小失败测试,再修改源码
3. 修复后运行该失败测试、相关测试和项目构建/验证命令,确认全部通过
4. 将测试、源码修复和 tasks.md 勾选保留在当前 change 内;不得通过另起一个“写测试用例”的 change 来替代当前 change 的验证闭环

@ -1,20 +0,0 @@
# 用户决策点协议
规范路径:`comet/reference/decision-point.md`
本协议由所有包含用户决策点的 comet 子 skill 共享。凡标注为“阻塞点”或“用户决策点”的步骤,都必须按本协议处理。
## 核心规则
- 决策点是阻塞点。到达决策点时必须暂停,等待用户明确选择后才能继续
- 必须使用当前平台可用的用户输入/确认机制获取选择
- 若当前平台没有结构化提问工具,则必须在对话中提出明确选项并停止流程,等待用户回复
- 不得用推荐规则、默认值、历史偏好或“用户应该会同意”的推断代替当前确认
- 用户明确选择前,不得写入对应状态字段、执行对应分支操作或自动继续下一阶段
## 最低呈现要求
- 说明当前决策点正在决定什么
- 给出清晰可选项;需要用户单选时,选项必须互斥且可执行
- 如有推荐,只能作为说明,不能替代用户确认
- 用户选择后,再执行对应命令或状态更新

@ -1,59 +0,0 @@
# Dirty Worktree 协议
规范路径:`comet/reference/dirty-worktree.md`
本协议由所有涉及代码修改的 comet 子 skill 共享。当 agent 恢复上下文或继续执行时,必须按本协议处理未提交的工作区改动。各子 skill 可在本协议基础上定义阶段特例(如 verify 阶段对实现改动的特殊处理),详见对应子 skill 文件。本文件不重复阶段特例。
## 1. 检查步骤
继续或开始修改前,必须先运行以下命令:
```bash
git status --short
git diff --stat
git diff --cached --stat
git ls-files --others --exclude-standard
```
必要时再查看 `git diff` / `git diff --cached` / 新建文件内容。
## 2. 核心规则
- 用户可能不会说明自己改了哪里。只要存在 dirty worktree包括 Git 状态里显示为 `??` 的新建文件),就先假设改动可能来自用户或混合来源
- **构建产物排除**`??` 文件若匹配 `.gitignore` 中的模式(如 `node_modules/`、`dist/`、`__pycache__/`、`*.o`、`target/`、`build/` 等),自动跳过归因,不视为用户改动
- dirty worktree 只代表代码事实,不会自动推进 `.comet.yaml``phase` 或勾选 `tasks.md`;只有完成归因、验证、同步必要文档,并通过对应阶段 guard 后,才允许推进 Comet 状态
## 3. 归因分类
将 dirty diff 分为三类:
1. **属于当前 change**:文件和内容能对应当前 change 的目标、tasks.md、plan 或 delta spec。将其纳入当前任务继续不重复改同一处
2. **不属于当前 change**:文件或内容与当前目标无关。暂停并询问用户:并入当前 change、拆成新 change、保留不处理或明确授权丢弃
3. **来源不确定**:无法从 diff 和文档判断归属。暂停并向用户汇报文件列表和判断依据,不继续推进阶段
## 4. 常见处理模式
### 已实现但 tasks.md 未勾选
先验证实现(运行构建和测试),通过后补勾任务。不要因为任务未勾选就重做一遍,也不要因为状态文件滞后而忽略代码事实。若当前子 skill 定义了阶段特例,以子 skill 为准。
### 暗示计划或范围已变化
按当前子 skill 的升级、增量更新或回退规则处理,本协议不重复阶段特例。
### 模糊恢复意图
用户说"继续""接着跑""我改了一点""刚才不满意""重新弄""代码动过""先按现在的来"等模糊恢复意图时,按本协议处理。不要要求用户先回忆具体改了哪里。
### open/design 阶段出现代码改动
若当前仍处于 `open``design`,但 dirty worktree 已经包含代码改动,先按本协议归因,不要直接推进阶段:
- 属于当前 change 的改动:作为需求或设计输入记录到 proposal/design/spec/design doc/tasks 中;进入 build 前仍需完成对应阶段 guard
- 不属于当前 change 或来源不确定:暂停询问用户是并入当前 change、拆成新 change、保留不处理还是明确授权丢弃
- 禁止在 open/design 阶段直接把代码改动当作已完成实现并推进到 verify
## 5. 禁令
- 禁止在未理解 dirty diff 来源时覆盖、回滚、格式化重写或忽略用户改动
- 禁止在 dirty diff 未解释清楚时判定验证通过

@ -1,28 +0,0 @@
# 文件结构参考
规范路径:`comet/reference/file-structure.md`
本文件是 Comet 项目文件结构参考。按需查阅,不随 skill 一次性加载。
```text
openspec/ # OpenSpec — WHAT
├── config.yaml
├── changes/
│ ├── <name>/ # 活跃 change
│ │ ├── .openspec.yaml
│ │ ├── .comet.yaml
│ │ ├── proposal.md # Why + What
│ │ ├── design.md # 高层架构决策
│ │ ├── specs/<capability>/spec.md # Delta 能力规格
│ │ ├── .comet/handoff/ # 脚本生成的阶段交接包
│ │ └── tasks.md # 任务清单
│ └── archive/YYYY-MM-DD-<name>/ # 已归档
└── specs/<capability>/spec.md # 主 specs归档时按 OpenSpec delta 语义合并)
docs/superpowers/ # Superpowers — HOW
├── specs/YYYY-MM-DD-<topic>-design.md # 设计文档(技术 RFC归档时标注状态
└── plans/YYYY-MM-DD-<feature>.md # 实施计划(文件头含 change 关联元数据)
.comet/
└── config.yaml # Comet 项目配置context_compression 默认 off可设 beta
```

@ -1,120 +0,0 @@
# Subagent 驱动开发的 Comet 扩展
规范路径:`comet/reference/subagent-dispatch.md`
本文档提供在 Superpowers `subagent-driven-development` 技能**之上**应用的 Comet 专属扩展。该技能负责核心派发循环(每个 task 派发全新 implementer → spec compliance review → code quality review → 下一个 task并强制连续执行。本文档添加 Comet 特有的真实后台调度、任务追踪、状态验证、代码审查模式和上下文恢复。若 Superpowers 技能与本文档发生冲突时,以本文档中更具体的 Comet 约束为准。
> **⚠️ 关键约束 — 任务之间禁止暂停**
>
> 当一个 task 按 `review_mode` 完成验收并被勾选后,**立即派发下一个 task**,不得停止、总结或询问用户是否继续。用户期望所有 task 按顺序自动执行,无需手动干预。任务之间暂停会中断工作流,导致用户每次都需要手动恢复。
>
> 仅在以下情况才停止并等待用户输入:
> - 任务处于 **BLOCKED** 状态(`review_mode: standard` 下一轮轻量复查仍未通过,或 `review_mode: thorough` 下批次/最终审查 2 轮审查-修复仍未通过)
> - 存在无法从仓库、计划或既有上下文消除的真实歧义
> - 平台没有真实后台 agent 调度能力,需要用户改选 `executing-plans`
> - 用户**明确**要求暂停
>
> 此规则适用于整个派发循环,而非单个任务。
## 开始前
1. 读取计划一次,按顺序提取所有未勾选 task 的完整文本。
2. 为每个 task 保存唯一标识plan 中 checkbox 后的完整任务文本,以及它映射的 OpenSpec task 完整文本(若存在)。若文本不唯一,停止并先修正计划,禁止依赖"第一个匹配项"。
3. 尊重依赖关系;依赖尚未完成的 task 不得提前派发。
## 每个 Task 的 Comet 扩展
在每个 task 上应用这些扩展,叠加在 Superpowers 技能的派发循环之上:
### 0. 派发强制约束(关键)
主会话**仅负责协调**,禁止直接执行 task。主会话禁止修改源代码。协调者唯一允许的文件修改是 plan、OpenSpec task 和 subagent 进度检查点的持久化更新。不得把多个 task 打包给同一个 agent。每个 task 派发一个全新的后台 implementer agent`review_mode` 需要审查或修复时spec reviewer、code quality reviewer、修复 agent 和 final reviewer 也必须分别使用全新的后台 agent
- **Claude Code**:对每个 implementer以及 `review_mode` 要求的 spec reviewer、code quality reviewer、修复 agent 和 final reviewer 使用 `Agent` 工具并设置 `run_in_background: true`。禁止内联执行 task禁止错误进入需要预先创建 team 的团队模式。
- **其他平台**:使用平台等效的后台 agent / Task / 多 agent 派发机制。
- **禁止**跨 task 或角色复用 implementer、reviewer 或修复 agent。每个 agent 拥有全新的隔离上下文,并且只接收当前角色所需的单个 task 上下文。
- 若平台无真实后台派发能力,不得继续;暂停并等待用户改选 `build_mode: executing-plans`
### 1. 派发 Prompt 与回报契约
每个 implementer 或修复 agent prompt 必须包含:
- 当前单个 task 的完整文本、架构背景和依赖上下文
- `Language: 使用触发本次工作流的用户请求语言输出`
- 允许修改的文件范围和禁止修改的范围
- 必须执行的测试命令和提交要求
- 修复 agent 还必须收到对应 reviewer 的完整反馈
agent 回报状态必须为 `DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT`,并包含实现内容、测试结果、提交哈希、变更文件和顾虑。进入审查前,主会话必须确认提交和文件在当前工作树可见;若平台使用隔离副本,先拉取或合并变更。
`review_mode` 需要 reviewer 时,每个 reviewer prompt 必须包含完整 task、实现提交或差异以及 RED/GREEN 证据(`tdd_mode: tdd` 时。reviewer 不得只依据 implementer 的总结进行审查。
### 2. Implementer 范围限制
implementer 只负责实现、测试和提交代码。**implementer 不得勾选 plan 或 OpenSpec task**,也不得只更新内置 Todo 或对话 checklist。
### 3. TDD 硬约束
`tdd_mode: tdd`,每个 implementer 和修复 agent 必须先使用 Skill 工具加载 Superpowers `test-driven-development` 技能,并在 prompt 中同时注入:
```text
You MUST follow TDD: write a failing test first, watch it fail, then write minimal code to pass. No production code without a failing test first.
```
implementer 或修复 agent 回报必须提供 **RED 失败命令与失败摘要**、**GREEN 通过命令与通过摘要**缺少任一证据不得进入审查。spec compliance reviewer 和 code quality reviewer 都必须核验 RED/GREEN 证据与测试覆盖。
### 4. 持久进度检查点
主会话必须维护 `openspec/changes/<name>/.comet/subagent-progress.md`并在每次派发、agent 回报、审查结果、修复轮次变化和 task 勾选后立即更新。检查点至少记录:
- 当前 plan task 唯一文本及映射的 OpenSpec task 文本
- 当前阶段:`implementing | spec-review | quality-review | checkoff | done | blocked | final-review | final-fix`
- 实现提交哈希、变更文件和 RED/GREEN 证据
- 已选择的 `review_mode`
- 已通过的审查阶段及尚未解决的 reviewer 反馈
- 当前 task、批次或 final review 的审查-修复轮次(`standard` 最多 1 轮,`thorough` 最多 2 轮,`off` 为 0 轮)
该文件只保存恢复所需的协调状态,不替代 plan 或 OpenSpec checkbox。当前 task 完成后保留其最终记录,开始下一个 task 时用下一 task 的记录替换。
### 5. 代码审查模式与轮次限制
`review_mode: standard` 时,每个 task 不自动派发 per-task reviewerimplementer 必须自测、提交并回报证据,协调者完成定向勾选验证。所有 task 完成后只派发一次最终轻量 code reviewer审查范围限定为正确性、安全和边界条件。若最终轻量审查发现 CRITICAL 或 IMPORTANT 问题,最多自动派发一轮修复 agent 并复查一次;复查仍未通过时标记 **BLOCKED**,暂停并把反馈交给用户。非 CRITICAL 发现可记录接受理由后继续。
`review_mode: thorough` 时,不执行每 task 双审查。协调者按批次或风险边界运行合并审查:每完成最多 3 个 task、或完成一个跨模块/高风险边界时,派发一个 reviewer 同时检查 spec compliance 与 code quality。若总 task 数不超过 3 且没有高风险边界,可跳过中途批次审查,只做最终完整审查。所有 task 完成后再派发一次最终完整 reviewer。批次和最终审查各最多 2 轮审查-修复;仍未通过则标记 **BLOCKED**,暂停并把累计反馈交给用户。
`review_mode: off` 时,不自动派发 spec reviewer、code quality reviewer、final reviewer 或审查修复 agent。任务完成依据 implementer 的测试/构建证据、当前工作树确认、任务唯一文本勾选验证和用户显式要求。若执行过程中出现测试失败、构建失败或异常行为,仍必须按异常调试协议处理,不得用 `off` 跳过真实问题。
### 6. Task 勾选与验证
**按 `review_mode` 完成验收后**,主会话:
1. 将 plan 中保存的唯一 task 文本从 `- [ ]` 改为 `- [x]`
2. 若存在映射,再同步勾选 OpenSpec task
3. 提交这次进度更新
4. 运行定向验证:
```bash
"$COMET_BASH" "$COMET_STATE" task-checkoff "$PLAN_FILE" "$PLAN_TASK_TEXT"
"$COMET_BASH" "$COMET_STATE" task-checkoff "openspec/changes/<name>/tasks.md" "$OPENSPEC_TASK_TEXT"
```
仅在对应映射存在时运行第二条。脚本会要求任务文本恰好出现一次且该项已勾选;验证失败时不得进入下一个 task。
## 收尾
- **自动继续**:按 `review_mode` 完成验收并勾选 task 后,立即派发下一个未勾选的 task。禁止总结、禁止询问用户是否继续、禁止在任务之间等待用户输入。这是不可协商的 —— Superpowers 技能强制连续执行,文档顶部的关键约束进一步强化此规则。
- 所有 task 完成后,若 `review_mode: standard`,将检查点切换为 `final-review`,只派发一次最终轻量 code reviewer。CRITICAL 或 IMPORTANT 问题最多自动修复和复查一轮;仍未通过则暂停交给用户。通过或接受非 CRITICAL 发现后继续返回 `comet-build`
- 所有 task 完成后,若 `review_mode: thorough`,将检查点切换为 `final-review`,派发一次最终完整 reviewer。CRITICAL 或 IMPORTANT 问题最多自动修复和复查两轮;仍未通过则暂停交给用户。通过或接受非 CRITICAL 发现后继续返回 `comet-build`
- 所有 task 完成后,若 `review_mode: off`,不进入 `final-review``final-fix`,但必须在持久产物中记录跳过自动代码审查的原因,然后返回 `comet-build`
- final review 通过后,结束的只是 subagent 派发循环,不是 Comet workflow。不得加载 `finishing-a-development-branch`,不得停下来询问用户下一步;必须返回 `comet-build` 继续执行退出条件、阶段守卫和后续阶段衔接。
## 上下文恢复
重新加载 Superpowers `subagent-driven-development` 技能并重新阅读本文档。先读取 `openspec/changes/<name>/.comet/subagent-progress.md`,再与第一个未勾选 task 和当前工作树核对:
- 检查点与未勾选 task 匹配时从记录的精确阶段恢复保留实现提交、RED/GREEN 证据、`review_mode`、已通过的审查阶段、未解决反馈和当前审查-修复轮次;不得重置轮次或重复已经通过的阶段。
- 检查点缺失或与未勾选 task 不匹配时,为第一个未勾选 task 创建新检查点并从 implementer 派发开始。
- 检查点中的提交或文件在当前工作树不可见时,先拉取、合并或恢复对应变更;不得假定实现已存在。
- 所有 task 已勾选且检查点处于 `final-review``final-fix` 时,从最终审查的精确阶段恢复,并保留最终反馈和审查-修复轮次;不得重新进入已完成的 task。
已提交但未按 `review_mode` 完成验收的 task 保持未勾选,并按检查点重新进入对应的验证、审查或修复流程。

@ -1,311 +0,0 @@
#!/bin/bash
# Comet Archive — automates the archive phase in one command
# Usage: comet-archive.sh <change-name> [--dry-run]
# Exit 0 = archive complete, exit 1 = fatal error
set -euo pipefail
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
COMET_OPENSPEC="${COMET_OPENSPEC:-openspec}"
red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
yellow() { echo -e "\033[33m$1\033[0m" >&2; }
DRY_RUN=0
if [[ "${2:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi
# Input validation
validate_change_name() {
local name="$1"
if [ -z "$name" ]; then
red "FATAL: Change name cannot be empty"
exit 1
fi
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
red "FATAL: Invalid change name: '$name'"
red "Valid characters: a-z, A-Z, 0-9, -, _"
exit 1
fi
if [[ "$name" =~ \.\. ]]; then
red "FATAL: Change name cannot contain '..'"
exit 1
fi
}
CHANGE="$1"
validate_change_name "$CHANGE"
CHANGE_DIR="openspec/changes/$CHANGE"
YAML="$CHANGE_DIR/.comet.yaml"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
STATE_SH="$SCRIPT_DIR/comet-state.sh"
TODAY=$(date -u +%Y-%m-%d)
ARCHIVE_NAME="${TODAY}-${CHANGE}"
ARCHIVE_DIR="openspec/changes/archive/${ARCHIVE_NAME}"
STEPS_OK=0
STEPS_TOTAL=0
step_ok() {
green " [OK] $1"
STEPS_OK=$((STEPS_OK + 1))
STEPS_TOTAL=$((STEPS_TOTAL + 1))
}
step_fail() {
red " [FAIL] $1"
STEPS_TOTAL=$((STEPS_TOTAL + 1))
}
step_dry_run() {
yellow " [DRY-RUN] $1"
STEPS_OK=$((STEPS_OK + 1))
STEPS_TOTAL=$((STEPS_TOTAL + 1))
}
echo "=== Comet Archive: $CHANGE ===" >&2
# --- Step 1: Read .comet.yaml, extract paths ---
yaml_field() {
local field="$1"
if [ -f "$STATE_SH" ]; then
"$COMET_BASH" "$STATE_SH" get "$CHANGE" "$field" 2>/dev/null
else
if [ -f "$YAML" ]; then
local value
value=$(grep "^${field}:" "$YAML" 2>/dev/null | sed "s/^${field}: *//" || true)
value=$(strip_inline_comment "$value")
strip_wrapping_quotes "$value"
fi
fi
}
strip_inline_comment() {
local value="$1"
printf '%s\n' "$value" | awk -v squote="'" '
{
out = ""
quote = ""
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (quote == "") {
if (c == "\"" || c == squote) {
quote = c
} else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
sub(/[[:space:]]+$/, "", out)
print out
next
}
} else if (c == quote) {
quote = ""
}
out = out c
}
print out
}
'
}
strip_wrapping_quotes() {
local value="$1"
case "$value" in
\"*\") printf '%s\n' "${value:1:${#value}-2}" ;;
\'*\') printf '%s\n' "${value:1:${#value}-2}" ;;
*) printf '%s\n' "$value" ;;
esac
}
if [ ! -f "$YAML" ]; then
red "FATAL: .comet.yaml not found in $CHANGE_DIR/"
exit 1
fi
DESIGN_DOC=$(yaml_field "design_doc")
PLAN_PATH=$(yaml_field "plan")
# --- Step 2: Validate entry state ---
PHASE_VAL=$(yaml_field "phase")
VERIFY_VAL=$(yaml_field "verify_result")
ARCHIVED_VAL=$(yaml_field "archived")
if [ "$PHASE_VAL" != "archive" ]; then
red "FATAL: phase is '$PHASE_VAL', expected 'archive'"
exit 1
fi
if [ "$VERIFY_VAL" != "pass" ]; then
red "FATAL: verify_result is '$VERIFY_VAL', expected 'pass'. Run comet-verify first."
exit 1
fi
if [ "$ARCHIVED_VAL" = "true" ]; then
red "FATAL: change already archived"
exit 1
fi
step_ok "Entry state verified"
# --- Step 3: Check archive target ---
if [ -d "$ARCHIVE_DIR" ]; then
red "FATAL: archive target already exists: $ARCHIVE_DIR"
exit 1
fi
step_ok "Archive target available"
# --- Step 4: Prepare document frontmatter annotation ---
annotate_frontmatter() {
local file="$1"
local extra_fields="$2"
if [ ! -f "$file" ]; then
return 0
fi
if [ "$DRY_RUN" -eq 1 ]; then
step_dry_run "Would annotate: $file"
return 0
fi
if head -1 "$file" | grep -q '^---'; then
local tmp_file
tmp_file=$(mktemp)
chmod 600 "$tmp_file"
awk -v archive="$ARCHIVE_NAME" -v extra="$extra_fields" '
/^archived-with:/ { next }
NR==1 && /^---/ { print; next }
/^---/ && NR>1 {
print "archived-with: " archive
if (extra != "") print extra
print; next
}
{ print }
' "$file" > "$tmp_file"
mv "$tmp_file" "$file"
else
local tmp_file
tmp_file=$(mktemp)
chmod 600 "$tmp_file"
{
echo "---"
echo "archived-with: $ARCHIVE_NAME"
if [ -n "$extra_fields" ]; then
echo "$extra_fields"
fi
echo "status: final"
echo "---"
cat "$file"
} > "$tmp_file"
mv "$tmp_file" "$file"
fi
step_ok "Annotated: $file"
}
# --- Step 5: Run OpenSpec archive for delta merge and move ---
verify_main_specs_clean() {
if [ ! -d "openspec/specs" ]; then
return 0
fi
local found=0
local matches
for spec_file in openspec/specs/*/spec.md; do
[ -f "$spec_file" ] || continue
matches=$(grep -nE '^## (ADDED|MODIFIED|REMOVED|RENAMED) Requirements$' "$spec_file" 2>/dev/null || true)
if [ -n "$matches" ]; then
red "FATAL: delta-only section heading leaked into main spec: $spec_file"
printf '%s\n' "$matches" >&2
found=1
fi
done
if [ "$found" -ne 0 ]; then
return 1
fi
return 0
}
resolve_archive_dir() {
if [ -d "$ARCHIVE_DIR" ]; then
return 0
fi
# Fallback: search for any directory matching *-$CHANGE in archive
local found
found=$(find "openspec/changes/archive" -maxdepth 1 -mindepth 1 -type d -name "*-$CHANGE" 2>/dev/null | head -1 || true)
if [ -n "$found" ]; then
ARCHIVE_DIR="$found"
ARCHIVE_NAME=$(basename "$found")
return 0
fi
return 1
}
if [ "$DRY_RUN" -eq 1 ]; then
step_dry_run "Would run OpenSpec archive: $CHANGE"
else
if ! command -v "$COMET_OPENSPEC" >/dev/null 2>&1; then
red "FATAL: OpenSpec CLI not found: $COMET_OPENSPEC"
red "Install OpenSpec or set COMET_OPENSPEC to the openspec executable."
exit 1
fi
"$COMET_OPENSPEC" archive "$CHANGE" --yes >&2
if ! resolve_archive_dir; then
step_fail "OpenSpec archive output not found"
exit 1
else
step_ok "OpenSpec archive completed: $ARCHIVE_DIR"
fi
verify_main_specs_clean
step_ok "Main specs verified clean"
fi
# --- Step 6: Annotate design doc and plan frontmatter ---
if [ -n "$DESIGN_DOC" ] && [ "$DESIGN_DOC" != "null" ]; then
annotate_frontmatter "$DESIGN_DOC" "status: final"
fi
if [ -n "$PLAN_PATH" ] && [ "$PLAN_PATH" != "null" ]; then
annotate_frontmatter "$PLAN_PATH" ""
fi
# --- Step 7: Mark archived via comet-state transition ---
ARCHIVE_YAML="$ARCHIVE_DIR/.comet.yaml"
if [ "$DRY_RUN" -eq 1 ]; then
step_dry_run "Would set archived: true in $ARCHIVE_YAML"
else
if [ -f "$ARCHIVE_YAML" ]; then
"$COMET_BASH" "$STATE_SH" transition "$ARCHIVE_NAME" archived >/dev/null
step_ok "archived: true"
else
step_fail "archived: true (.comet.yaml not found after move)"
fi
fi
# --- Step 8: Print summary ---
echo "" >&2
if [ "$DRY_RUN" -eq 1 ]; then
yellow "Dry run complete. $STEPS_OK/$STEPS_TOTAL steps would succeed."
else
green "Archive complete. $STEPS_OK/$STEPS_TOTAL steps succeeded."
fi
if [ "$STEPS_OK" -lt "$STEPS_TOTAL" ]; then
exit 1
fi
exit 0

@ -1,110 +0,0 @@
#!/bin/bash
# Comet script locator — source this file to export paths to bundled scripts.
#
# Usage:
# . /path/to/comet/scripts/comet-env.sh
#
# This file is sourced by workflow snippets. Do not set global shell options here.
_comet_env_source="${BASH_SOURCE[0]:-$0}"
_comet_script_dir="$(cd "$(dirname "$_comet_env_source")" && pwd -P)"
_comet_env_sourced=0
(return 0 2>/dev/null) && _comet_env_sourced=1
export COMET_GUARD="${COMET_GUARD:-${_comet_script_dir}/comet-guard.sh}"
export COMET_STATE="${COMET_STATE:-${_comet_script_dir}/comet-state.sh}"
export COMET_HANDOFF="${COMET_HANDOFF:-${_comet_script_dir}/comet-handoff.sh}"
export COMET_ARCHIVE="${COMET_ARCHIVE:-${_comet_script_dir}/comet-archive.sh}"
export COMET_YAML_VALIDATE="${COMET_YAML_VALIDATE:-${_comet_script_dir}/comet-yaml-validate.sh}"
_comet_bash_is_usable() {
local _comet_bash_candidate="$1"
if [ -z "$_comet_bash_candidate" ]; then
return 1
fi
case "$_comet_bash_candidate" in
*/Windows/System32/bash.exe|*/windows/system32/bash.exe|*\\Windows\\System32\\bash.exe|*\\windows\\system32\\bash.exe)
return 1
;;
esac
"$_comet_bash_candidate" -lc 'printf comet-bash-ok' >/dev/null 2>&1
}
_comet_resolve_bash() {
local _comet_bash_candidate
if _comet_bash_is_usable "${COMET_BASH:-}"; then
printf '%s\n' "$COMET_BASH"
return 0
fi
if _comet_bash_is_usable "${BASH:-}"; then
printf '%s\n' "$BASH"
return 0
fi
_comet_bash_candidate="$(command -v sh 2>/dev/null | awk '{ sub(/\/sh(\.exe)?$/, "/bash.exe"); print }')"
if _comet_bash_is_usable "$_comet_bash_candidate"; then
printf '%s\n' "$_comet_bash_candidate"
return 0
fi
_comet_bash_candidate="$(command -v bash 2>/dev/null || true)"
if _comet_bash_is_usable "$_comet_bash_candidate"; then
printf '%s\n' "$_comet_bash_candidate"
return 0
fi
return 1
}
COMET_BASH="$(_comet_resolve_bash || true)"
export COMET_BASH
_comet_env_fail() {
echo "ERROR: Comet scripts not found. Ensure the comet skill is installed completely." >&2
echo "Expected path pattern: */comet/scripts/comet-*.sh under project or platform skill directories" >&2
}
_comet_bash_fail() {
echo "ERROR: usable bash not found. Install Git Bash or set COMET_BASH to a working bash executable." >&2
echo "Windows WSL launcher bash.exe is not supported for Comet scripts." >&2
}
_comet_env_abort() {
local _comet_env_was_sourced="$_comet_env_sourced"
unset _comet_env_source _comet_script_dir _comet_script _comet_env_missing _comet_env_sourced
unset _comet_bash_candidate
unset -f _comet_env_fail _comet_bash_fail _comet_bash_is_usable _comet_resolve_bash
if [ "$_comet_env_was_sourced" -eq 1 ]; then
unset -f _comet_env_abort
return 1
fi
exit 1
}
_comet_env_missing=0
if [ -z "$COMET_BASH" ]; then
_comet_bash_fail
_comet_env_missing=1
fi
for _comet_script in \
"$COMET_GUARD" \
"$COMET_STATE" \
"$COMET_HANDOFF" \
"$COMET_ARCHIVE" \
"$COMET_YAML_VALIDATE"; do
if [ ! -f "$_comet_script" ]; then
_comet_env_fail
_comet_env_missing=1
break
fi
done
if [ "$_comet_env_missing" -ne 0 ]; then
_comet_env_abort
else
unset _comet_env_source _comet_script_dir _comet_script _comet_env_missing _comet_env_sourced
unset _comet_bash_candidate
unset -f _comet_env_fail _comet_bash_fail _comet_bash_is_usable _comet_resolve_bash _comet_env_abort
fi

@ -1,778 +0,0 @@
#!/bin/bash
# Comet Phase Guard — validates exit conditions before phase transitions
# Usage: comet-guard.sh <change-name> <current-phase> [--apply]
# Phases: open, design, build, verify, archive
# Exit 0 = all checks pass, exit 1 = blocked (reasons printed to stderr)
# shellcheck disable=SC2329 # Functions called indirectly via check() dispatch
set -euo pipefail
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
warn() { echo -e "\033[33m$1\033[0m" >&2; }
# Input validation - prevent path traversal
validate_change_name() {
local name="$1"
# Reject empty names
if [ -z "$name" ]; then
red "ERROR: Change name cannot be empty" >&2
exit 1
fi
# Only allow alphanumeric, hyphens, and underscores
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
red "ERROR: Invalid change name: '$name'" >&2
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
exit 1
fi
# Reject path traversal attempts
if [[ "$name" =~ \.\. ]]; then
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
exit 1
fi
}
if [ "${COMET_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
CHANGE="${CHANGE:-}"
PHASE="${PHASE:-}"
APPLY="${APPLY:-0}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
CHANGE_DIR="${CHANGE_DIR:-}"
else
validate_change_name "$1"
CHANGE="$1"
PHASE="$2"
APPLY=0
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
if [[ "${3:-}" == "--apply" ]]; then
APPLY=1
fi
CHANGE_DIR="openspec/changes/$CHANGE"
if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
CHANGE_DIR="openspec/changes/archive/$CHANGE"
fi
fi
BLOCK=0
check() {
local desc="$1"
shift
local output
if output=$("$@" 2>&1); then
green " [PASS] $desc"
else
red " [FAIL] $desc"
if [ -n "$output" ]; then
while IFS= read -r line; do
red " $line"
done <<< "$output"
fi
BLOCK=1
fi
}
# --- Helper functions ---
tasks_all_done() {
local tasks="$CHANGE_DIR/tasks.md"
if [ ! -f "$tasks" ]; then
echo "tasks.md is missing at $tasks" >&2
echo "Next: restore or create tasks.md for this change before leaving build." >&2
return 1
fi
if ! grep -q '\- \[x\]' "$tasks"; then
echo "tasks.md has no completed tasks." >&2
echo "Next: complete implementation tasks and mark them with '- [x]'." >&2
return 1
fi
if grep -q '\- \[ \]' "$tasks"; then
echo "Unfinished tasks:" >&2
grep -n '\- \[ \]' "$tasks" >&2 || true
echo "Next: complete or explicitly remove unfinished tasks, then mark tasks.md with '- [x]'." >&2
return 1
fi
return 0
}
tasks_has_any() {
local tasks="$CHANGE_DIR/tasks.md"
[ -f "$tasks" ] && grep -q '\- \[' "$tasks"
}
plan_tasks_all_done() {
local plan
plan=$(yaml_field_value "plan" 2>/dev/null || true)
if [ -z "$plan" ] || [ "$plan" = "null" ]; then
return 0
fi
if [ ! -f "$plan" ]; then
echo "plan file is missing at $plan" >&2
echo "Next: restore the Superpowers plan file or update .comet.yaml plan before leaving build." >&2
return 1
fi
if grep -q '^[[:space:]]*- \[ \]' "$plan"; then
echo "Unfinished Superpowers plan tasks:" >&2
grep -n '^[[:space:]]*- \[ \]' "$plan" >&2 || true
echo "Next: check off corresponding completed plan tasks, then commit the plan update." >&2
return 1
fi
return 0
}
yaml_field_value() {
local field="$1"
local yaml="$CHANGE_DIR/.comet.yaml"
if [ -f "$yaml" ]; then
local value
value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true)
value=$(strip_inline_comment "$value")
strip_wrapping_quotes "$value"
fi
}
strip_inline_comment() {
local value="$1"
printf '%s\n' "$value" | awk -v squote="'" '
{
out = ""
quote = ""
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (quote == "") {
if (c == "\"" || c == squote) {
quote = c
} else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
sub(/[[:space:]]+$/, "", out)
print out
next
}
} else if (c == quote) {
quote = ""
}
out = out c
}
print out
}
'
}
strip_wrapping_quotes() {
local value="$1"
case "$value" in
\"*\")
printf '%s\n' "${value:1:${#value}-2}"
;;
\'*\')
printf '%s\n' "${value:1:${#value}-2}"
;;
*)
printf '%s\n' "$value"
;;
esac
}
project_config_value() {
local field="$1"
local value
value=$(yaml_field_value "$field" 2>/dev/null || true)
if [ -n "$value" ] && [ "$value" != "null" ]; then
echo "$value"
return 0
fi
for config in ".comet.yaml" "comet.yaml" ".comet.yml" "comet.yml"; do
if [ -f "$config" ]; then
value=$(grep "^${field}:" "$config" 2>/dev/null | sed "s/^${field}: *//" || true)
value=$(strip_inline_comment "$value")
value=$(strip_wrapping_quotes "$value")
if [ -n "$value" ] && [ "$value" != "null" ]; then
echo "$value"
return 0
fi
fi
done
}
file_nonempty() {
[ -f "$1" ] && [ -s "$1" ]
}
is_windows_bash() {
case "$(uname -s 2>/dev/null || true)" in
MINGW*|MSYS*|CYGWIN*) return 0 ;;
*) return 1 ;;
esac
}
run_command_string() {
local command="$1"
if [ -z "$command" ]; then
red "ERROR: build/verify command is empty" >&2
return 1
fi
# Basic command injection guard: reject dangerous shell metacharacters
# Quotes are allowed to support paths with spaces (e.g. Windows)
if [[ "$command" =~ [\;\|\&\$\`] ]]; then
red "ERROR: build/verify command contains shell metacharacters: $command" >&2
red "Allowed: alphanumeric, spaces, hyphens, underscores, dots, colons, forward slashes, quotes" >&2
return 1
fi
echo "+ $command" >&2
"$COMET_BASH" -lc "$command"
}
hash_stream() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 | awk '{print $1}'
else
echo "sha256sum or shasum is required" >&2
return 1
fi
}
hash_file() {
local file="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$file" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" | awk '{print $1}'
else
echo "sha256sum or shasum is required" >&2
return 1
fi
}
handoff_source_files() {
printf '%s\n' "$CHANGE_DIR/proposal.md"
printf '%s\n' "$CHANGE_DIR/design.md"
printf '%s\n' "$CHANGE_DIR/tasks.md"
if [ -d "$CHANGE_DIR/specs" ]; then
find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort
fi
}
compute_handoff_hash() {
local hash_input
hash_input=$(handoff_source_files | while IFS= read -r file; do
if [ -f "$file" ]; then
printf 'path:%s\n' "$file"
printf 'sha256:%s\n' "$(hash_file "$file")"
fi
done)
printf '%s' "$hash_input" | hash_stream
}
preflight() {
if [ ! -d "$CHANGE_DIR" ]; then
red "FATAL: change directory not found: $CHANGE_DIR"
exit 1
fi
if [ ! -f "$CHANGE_DIR/.comet.yaml" ]; then
red "FATAL: .comet.yaml not found in $CHANGE_DIR"
exit 1
fi
# Schema validation
local validate_script
validate_script="$SCRIPT_DIR/comet-yaml-validate.sh"
if [ -f "$validate_script" ]; then
if ! "$COMET_BASH" "$validate_script" "$CHANGE" 2>/dev/null; then
"$COMET_BASH" "$validate_script" "$CHANGE" || true
red "FATAL: .comet.yaml schema validation failed"
exit 1
fi
fi
}
build_passes() {
if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then
return 0
fi
local configured_build
configured_build=$(project_config_value "build_command" 2>/dev/null || true)
if [ -n "$configured_build" ]; then
run_command_string "$configured_build"
return $?
fi
if [ -f "package.json" ] && grep -q '"build"' "package.json"; then
npm run build
return $?
fi
if [ -f "pom.xml" ]; then
if [ -x "./mvnw" ]; then
./mvnw compile -q
elif is_windows_bash && command -v mvn.cmd >/dev/null 2>&1; then
mvn.cmd compile -q
else
mvn compile -q
fi
return $?
fi
if [ -f "Cargo.toml" ]; then
cargo build
return $?
fi
return 1
}
verification_command_passes() {
if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then
return 0
fi
local configured_verify
configured_verify=$(project_config_value "verify_command" 2>/dev/null || true)
if [ -n "$configured_verify" ]; then
run_command_string "$configured_verify"
return $?
fi
build_passes
}
isolation_selected() {
local isolation
isolation=$(yaml_field_value "isolation" 2>/dev/null || true)
case "$isolation" in
branch|worktree) return 0 ;;
*)
echo "isolation must be branch or worktree, got '${isolation:-null}'" >&2
echo "Next: ask the user to choose branch or worktree, create the chosen isolation, then run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE isolation <branch|worktree>" >&2
return 1
;;
esac
}
build_mode_selected() {
local build_mode
build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
case "$build_mode" in
subagent-driven-development|executing-plans|direct) return 0 ;;
*)
echo "build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
echo "Next: ask the user to choose an execution mode, then run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE build_mode <subagent-driven-development|executing-plans>" >&2
return 1
;;
esac
}
build_mode_allowed_for_workflow() {
local workflow build_mode direct_override
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
direct_override=$(yaml_field_value "direct_override" 2>/dev/null || true)
if [ "$build_mode" != "direct" ]; then
return 0
fi
case "$workflow" in
hotfix|tweak) return 0 ;;
*)
if [ "$direct_override" = "true" ]; then
return 0
fi
echo "build_mode=direct is only allowed for hotfix/tweak unless direct_override: true is recorded" >&2
echo "Next: choose executing-plans or subagent-driven-development, or stop and ask the user for an explicit direct override." >&2
return 1
;;
esac
}
subagent_dispatch_confirmed() {
local build_mode subagent_dispatch
build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
subagent_dispatch=$(yaml_field_value "subagent_dispatch" 2>/dev/null || true)
if [ "$build_mode" != "subagent-driven-development" ]; then
return 0
fi
if [ "$subagent_dispatch" = "confirmed" ]; then
return 0
fi
echo "subagent_dispatch must be confirmed before using build_mode=subagent-driven-development" >&2
echo "Next: confirm the current platform has a real background subagent/Task/multi-agent dispatcher, then run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE subagent_dispatch confirmed" >&2
echo "Or ask the user to switch to executing-plans and run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE build_mode executing-plans" >&2
return 1
}
tdd_mode_selected() {
local workflow tdd_mode
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
tdd_mode=$(yaml_field_value "tdd_mode" 2>/dev/null || true)
case "$workflow" in
hotfix|tweak) return 0 ;;
esac
case "$tdd_mode" in
tdd|direct) return 0 ;;
*)
echo "tdd_mode must be tdd or direct for full workflow, got '${tdd_mode:-null}'" >&2
echo "Next: ask the user to choose TDD enforcement level, then run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE tdd_mode <tdd|direct>" >&2
return 1
;;
esac
}
review_mode_selected() {
local workflow review_mode
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
review_mode=$(yaml_field_value "review_mode" 2>/dev/null || true)
case "$workflow" in
hotfix|tweak) return 0 ;;
esac
case "$review_mode" in
off|standard|thorough) return 0 ;;
*)
echo "review_mode must be off, standard, or thorough before leaving build, got '${review_mode:-null}'" >&2
echo "Next: ask the user to choose code review mode, then run:" >&2
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE review_mode <off|standard|thorough>" >&2
return 1
;;
esac
}
verify_result_is_pass() {
local result
result=$(yaml_field_value "verify_result" 2>/dev/null || true)
[ "$result" = "pass" ]
}
verification_report_exists() {
local report
report=$(yaml_field_value "verification_report" 2>/dev/null || true)
[ -n "$report" ] && [ "$report" != "null" ] && [ -f "$report" ]
}
branch_status_handled() {
local status
status=$(yaml_field_value "branch_status" 2>/dev/null || true)
[ "$status" = "handled" ]
}
design_handoff_context_valid() {
local context recorded_hash actual_hash markdown
context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
recorded_hash=$(yaml_field_value "handoff_hash" 2>/dev/null || true)
if [ -z "$context" ] || [ "$context" = "null" ]; then
echo "handoff_context is missing from .comet.yaml" >&2
echo "Next: run \"\$COMET_BASH\" \"\$COMET_HANDOFF\" $CHANGE design --write before invoking Superpowers." >&2
return 1
fi
if [ ! -s "$context" ]; then
echo "handoff_context does not point to a non-empty file: $context" >&2
echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
return 1
fi
if [[ ! "$recorded_hash" =~ ^[a-f0-9]{64}$ ]]; then
echo "handoff_hash is missing or invalid: ${recorded_hash:-null}" >&2
echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
return 1
fi
actual_hash=$(compute_handoff_hash)
if [ "$actual_hash" != "$recorded_hash" ]; then
echo "OpenSpec artifacts changed after handoff was generated." >&2
echo "Expected handoff_hash: $recorded_hash" >&2
echo "Actual handoff_hash: $actual_hash" >&2
echo "Next: rerun comet-handoff.sh so Superpowers receives the current OpenSpec context." >&2
return 1
fi
markdown="${context%.json}.md"
if [ ! -s "$markdown" ]; then
echo "design handoff markdown is missing or empty: $markdown" >&2
echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
return 1
fi
}
design_handoff_markdown_traceable() {
local context markdown missing=0
context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
if [ -z "$context" ] || [ "$context" = "null" ]; then
echo "handoff_context is missing from .comet.yaml" >&2
return 1
fi
markdown="${context%.json}.md"
if [ ! -s "$markdown" ]; then
echo "design handoff markdown is missing or empty: $markdown" >&2
return 1
fi
grep -q '^Generated-by: comet-handoff\.sh$' "$markdown" || {
echo "handoff markdown is missing Generated-by marker" >&2
missing=1
}
grep -Eq '^- Mode: (compact|full|beta)$' "$markdown" || {
echo "handoff markdown is missing Mode marker" >&2
missing=1
}
handoff_source_files | while IFS= read -r file; do
[ -f "$file" ] || continue
if ! grep -q "^- Source: $file$" "$markdown"; then
echo "handoff markdown is missing source reference: $file" >&2
exit 2
fi
if ! grep -q "^- SHA256: $(hash_file "$file")$" "$markdown"; then
echo "handoff markdown is missing current sha256 for: $file" >&2
exit 2
fi
done || missing=1
[ "$missing" -eq 0 ]
}
context_compression_mode() {
local mode
mode=$(yaml_field_value "context_compression" 2>/dev/null || true)
printf '%s\n' "${mode:-off}"
}
beta_spec_json_structurally_valid() {
local context missing=0
if [ "$(context_compression_mode)" != "beta" ]; then
return 0
fi
context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
if [ -z "$context" ] || [ "$context" = "null" ]; then
echo "handoff_context is missing from .comet.yaml" >&2
return 1
fi
if [ ! -s "$context" ]; then
echo "spec-context.json is missing or empty: $context" >&2
return 1
fi
# Validate required JSON fields
grep -q '"change"' "$context" || { echo "spec-context.json missing 'change' field" >&2; return 1; }
grep -q '"phase"' "$context" || { echo "spec-context.json missing 'phase' field" >&2; return 1; }
grep -q '"mode": "beta"' "$context" || { echo "spec-context.json mode is not beta" >&2; return 1; }
grep -q '"files"' "$context" || { echo "spec-context.json missing 'files' field" >&2; return 1; }
grep -q '"context_hash"' "$context" || { echo "spec-context.json missing 'context_hash' field" >&2; return 1; }
# Verify all source files are referenced in the JSON
handoff_source_files | while IFS= read -r file; do
[ -f "$file" ] || continue
if ! grep -qF "$file" "$context"; then
echo "spec-context.json missing source file reference: $file" >&2
exit 2
fi
done || missing=1
[ "$missing" -eq 0 ]
}
design_doc_frontmatter_has() {
local design_doc="$1"
local field="$2"
local expected="$3"
awk '
{
line = $0
sub(/^\357\273\277/, "", line)
}
!in_fm && line == "---" { in_fm = 1; next }
in_fm && line == "---" { exit }
in_fm { print line }
' "$design_doc" | grep -Eq "^${field}: ['\"]?${expected}['\"]?[[:space:]]*$"
}
design_doc_links_current_change() {
local design_doc
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
if [ -z "$design_doc" ] || [ "$design_doc" = "null" ] || [ ! -s "$design_doc" ]; then
echo "design_doc must point to an existing Superpowers Design Doc before leaving design." >&2
return 1
fi
design_doc_frontmatter_has "$design_doc" "comet_change" "$CHANGE"
}
design_doc_declares_technical_role() {
local design_doc
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
[ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] &&
design_doc_frontmatter_has "$design_doc" "role" "technical-design"
}
design_doc_declares_canonical_spec() {
local design_doc
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
[ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] &&
design_doc_frontmatter_has "$design_doc" "canonical_spec" "openspec"
}
archived_is_true() {
local val
val=$(yaml_field_value "archived" 2>/dev/null || true)
[ "$val" = "true" ]
}
# --- Phase-specific checks ---
guard_open() {
echo "=== Guard: open → next ===" >&2
local workflow
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md"
if [ "$workflow" = "full" ]; then
check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
fi
check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
check "tasks.md has at least one task" tasks_has_any
}
guard_design() {
echo "=== Guard: design → build ===" >&2
local design_doc workflow
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
check "design handoff context exists" design_handoff_context_valid
check "design handoff markdown is traceable" design_handoff_markdown_traceable
if [ "$(context_compression_mode)" = "beta" ]; then
check "beta spec-context.json is structurally valid" beta_spec_json_structurally_valid
fi
if [ "$workflow" = "full" ]; then
# Full workflow: design_doc is REQUIRED
check "design_doc is recorded for full workflow" design_doc_recorded
fi
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
check "Design Doc frontmatter links current change" design_doc_links_current_change
check "Design Doc declares technical design role" design_doc_declares_technical_role
check "Design Doc declares OpenSpec as canonical spec" design_doc_declares_canonical_spec
elif [ "$workflow" != "full" ]; then
warn " [WARN] No design_doc recorded in .comet.yaml (optional for hotfix/tweak)"
fi
}
design_doc_recorded() {
local design_doc
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
return 0
fi
echo "design_doc must point to an existing Superpowers Design Doc for full workflow before leaving design." >&2
echo "Next: create the Design Doc and run: \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE design_doc <path>" >&2
return 1
}
guard_build() {
echo "=== Guard: build → verify ===" >&2
check "isolation selected" isolation_selected
check "build_mode selected" build_mode_selected
check "build_mode allowed for workflow" build_mode_allowed_for_workflow
check "subagent dispatch confirmed" subagent_dispatch_confirmed
check "tdd_mode selected" tdd_mode_selected
check "review_mode selected" review_mode_selected
check "tasks.md all tasks checked" tasks_all_done
check "Superpowers plan all tasks checked" plan_tasks_all_done
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
check "Build passes" build_passes
}
guard_verify() {
echo "=== Guard: verify → archive ===" >&2
check "tasks.md all tasks checked" tasks_all_done
check "Build passes" verification_command_passes
check "verification_report exists" verification_report_exists
check "branch_status=handled" branch_status_handled
}
guard_archive() {
echo "=== Guard: archive completeness ===" >&2
check "archived is true" archived_is_true
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
check "tasks.md all tasks checked" tasks_all_done
}
apply_state_update() {
local state_sh="$SCRIPT_DIR/comet-state.sh"
local p="$1"
if [ -f "$state_sh" ]; then
case "$p" in
open) "$COMET_BASH" "$state_sh" transition "$CHANGE" open-complete ;;
design) "$COMET_BASH" "$state_sh" transition "$CHANGE" design-complete ;;
build) "$COMET_BASH" "$state_sh" transition "$CHANGE" build-complete ;;
verify) "$COMET_BASH" "$state_sh" transition "$CHANGE" verify-pass ;;
esac
else
red "FATAL: comet-state.sh not found; cannot apply state transition"
exit 1
fi
}
# --- Main ---
if [ "${COMET_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
return 0 2>/dev/null
# shellcheck disable=SC2317 # unreachable if sourced; fallback for direct execution
red "ERROR: COMET_GUARD_SOURCE_ONLY=1 is only for sourcing, not direct execution" >&2
# shellcheck disable=SC2317
exit 1
fi
case "$PHASE" in
open) preflight ; guard_open ;;
design) preflight ; guard_design ;;
build) preflight ; guard_build ;;
verify) preflight ; guard_verify ;;
archive) preflight ; guard_archive ;;
*)
red "Unknown phase: $PHASE"
echo "Valid phases: open, design, build, verify, archive" >&2
exit 1
;;
esac
if [ "$BLOCK" -eq 1 ]; then
echo "" >&2
red "BLOCKED — fix failing checks before proceeding to next phase"
exit 1
else
echo "" >&2
green "ALL CHECKS PASSED — ready for next phase"
if [ "$APPLY" -eq 1 ]; then
apply_state_update "$PHASE"
case "$PHASE" in
open)
new_phase=$(yaml_field_value "phase")
green " [APPLY] .comet.yaml updated: phase=$new_phase"
;;
design) green " [APPLY] .comet.yaml updated: phase=build" ;;
build) green " [APPLY] .comet.yaml updated: phase=verify, verify_result=pending" ;;
verify) green " [APPLY] .comet.yaml updated: phase=archive, verify_result=pass" ;;
esac
fi
exit 0
fi

@ -1,390 +0,0 @@
#!/bin/bash
# Comet Handoff — creates machine-owned context packages between phases
# Usage: comet-handoff.sh <change-name> design --write [--full]
# comet-handoff.sh <change-name> --hash-only
set -euo pipefail
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
warn() { echo -e "\033[33m$1\033[0m" >&2; }
validate_change_name() {
local name="$1"
if [ -z "$name" ]; then
red "ERROR: Change name cannot be empty"
exit 1
fi
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
red "ERROR: Invalid change name: '$name'"
red "Valid characters: a-z, A-Z, 0-9, -, _"
exit 1
fi
if [[ "$name" =~ \.\. ]]; then
red "ERROR: Change name cannot contain '..' (path traversal not allowed)"
exit 1
fi
}
strip_wrapping_quotes() {
local value="$1"
case "$value" in
\"*\") printf '%s\n' "${value:1:${#value}-2}" ;;
\'*\') printf '%s\n' "${value:1:${#value}-2}" ;;
*) printf '%s\n' "$value" ;;
esac
}
strip_inline_comment() {
local value="$1"
printf '%s\n' "$value" | awk -v squote="'" '
{
out = ""
quote = ""
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (quote == "") {
if (c == "\"" || c == squote) {
quote = c
} else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
sub(/[[:space:]]+$/, "", out)
print out
next
}
} else if (c == quote) {
quote = ""
}
out = out c
}
print out
}
'
}
yaml_field_value() {
local field="$1"
local yaml="$CHANGE_DIR/.comet.yaml"
local value
value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true)
value=$(strip_inline_comment "$value")
strip_wrapping_quotes "$value"
}
hash_stream() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 | awk '{print $1}'
else
red "ERROR: sha256sum or shasum is required"
exit 1
fi
}
hash_file() {
local file="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$file" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" | awk '{print $1}'
else
red "ERROR: sha256sum or shasum is required"
exit 1
fi
}
source_files() {
printf '%s\n' "$CHANGE_DIR/proposal.md"
printf '%s\n' "$CHANGE_DIR/design.md"
printf '%s\n' "$CHANGE_DIR/tasks.md"
if [ -d "$CHANGE_DIR/specs" ]; then
find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort
fi
}
compute_context_hash() {
local hash_input
hash_input=$(source_files | while IFS= read -r file; do
if [ -f "$file" ]; then
printf 'path:%s\n' "$file"
printf 'sha256:%s\n' "$(hash_file "$file")"
fi
done)
printf '%s' "$hash_input" | hash_stream
}
json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
file_line_count() {
local file="$1"
wc -l < "$file" | tr -d ' '
}
write_file_excerpt() {
local file="$1"
local max_lines="$2"
local total_lines
total_lines=$(file_line_count "$file")
echo "## $file"
echo ""
echo "- Source: $file"
echo "- Lines: 1-$total_lines"
echo "- SHA256: $(hash_file "$file")"
echo ""
if [ "$HANDOFF_MODE" = "full" ] || [ "$total_lines" -le "$max_lines" ]; then
echo '```md'
cat "$file"
echo '```'
else
echo "[TRUNCATED]"
echo ""
echo '```md'
sed -n "1,${max_lines}p" "$file"
echo '```'
echo ""
echo "Full source: $file"
fi
echo ""
}
write_markdown_context() {
local output="$1"
{
echo "# Comet Design Handoff"
echo ""
echo "- Change: $CHANGE"
echo "- Phase: design"
echo "- Mode: $HANDOFF_MODE"
echo "- Context hash: $CONTEXT_HASH"
echo ""
echo "Generated-by: comet-handoff.sh"
echo ""
echo "OpenSpec remains the canonical capability spec. This handoff is a deterministic, source-traceable context pack, not an agent-authored summary."
echo ""
source_files | while IFS= read -r file; do
[ -f "$file" ] || continue
write_file_excerpt "$file" 80
done
} > "$output"
}
write_json_context() {
local output="$1"
{
echo "{"
echo " \"change\": \"$(json_escape "$CHANGE")\","
echo " \"phase\": \"design\","
echo " \"mode\": \"$HANDOFF_MODE\","
echo " \"canonical_spec\": \"openspec\","
echo " \"generated_by\": \"comet-handoff.sh\","
echo " \"context_hash\": \"$CONTEXT_HASH\","
echo " \"files\": ["
local first=1
while IFS= read -r file; do
[ -f "$file" ] || continue
if [ "$first" -eq 0 ]; then
echo ","
fi
first=0
printf ' { "path": "%s", "sha256": "%s" }' "$(json_escape "$file")" "$(hash_file "$file")"
done < <(source_files)
echo ""
echo " ]"
echo "}"
} > "$output"
}
write_spec_projection_for_file() {
local file="$1"
echo "## $file"
echo ""
echo "- Source: $file"
echo "- Lines: 1-$(file_line_count "$file")"
echo "- SHA256: $(hash_file "$file")"
echo ""
echo '```md'
cat "$file"
echo '```'
echo ""
}
write_spec_markdown_context() {
local output="$1"
{
echo "# Comet Spec Context"
echo ""
echo "- Change: $CHANGE"
echo "- Phase: design"
echo "- Mode: beta"
echo "- Context hash: $CONTEXT_HASH"
echo ""
echo "Generated-by: comet-handoff.sh"
echo ""
echo "OpenSpec remains the canonical capability spec. This beta context pack verbatim-projects spec files and references supporting artifacts by hash, not an agent-authored summary."
echo ""
echo "## Source References"
echo ""
source_files | while IFS= read -r file; do
[ -f "$file" ] || continue
echo "- Source: $file"
echo "- SHA256: $(hash_file "$file")"
done
echo ""
echo "## Acceptance Projection"
echo ""
if [ -d "$CHANGE_DIR/specs" ]; then
find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort | while IFS= read -r file; do
write_spec_projection_for_file "$file"
done
else
echo "No delta spec files found."
echo ""
fi
echo "Full source files remain canonical. If a required heading or scenario is missing here, regenerate the handoff or read the source spec directly. Supporting files (proposal, design, tasks) are referenced by hash only."
} > "$output"
}
write_spec_json_context() {
local output="$1"
{
echo "{"
echo " \"change\": \"$(json_escape "$CHANGE")\","
echo " \"phase\": \"design\","
echo " \"mode\": \"beta\","
echo " \"canonical_spec\": \"openspec\","
echo " \"generated_by\": \"comet-handoff.sh\","
echo " \"context_hash\": \"$CONTEXT_HASH\","
echo " \"files\": ["
local first_file=1
while IFS= read -r file; do
[ -f "$file" ] || continue
local role="supporting"
case "$file" in
*/specs/*/spec.md) role="spec" ;;
esac
if [ "$first_file" -eq 0 ]; then
echo ","
fi
first_file=0
printf ' { "path": "%s", "sha256": "%s", "role": "%s" }' "$(json_escape "$file")" "$(hash_file "$file")" "$role"
done < <(source_files)
echo ""
echo " ]"
echo "}"
} > "$output"
}
CHANGE="${1:-}"
PHASE="${2:-}"
MODE="${3:-}"
FULL_FLAG="${4:-}"
validate_change_name "$CHANGE"
# --hash-only: compute and output context hash without generating handoff files
if [ "${PHASE:-}" = "--hash-only" ]; then
CHANGE_DIR="openspec/changes/$CHANGE"
if [ ! -d "$CHANGE_DIR" ]; then
red "ERROR: change directory not found: $CHANGE_DIR"
exit 1
fi
for required in proposal.md design.md tasks.md; do
if [ ! -s "$CHANGE_DIR/$required" ]; then
red "ERROR: required file missing or empty: $CHANGE_DIR/$required"
exit 1
fi
done
CONTEXT_HASH="$(compute_context_hash)"
printf '%s
' "$CONTEXT_HASH"
exit 0
fi
if [ "$PHASE" != "design" ] || [ "$MODE" != "--write" ]; then
red "Usage: comet-handoff.sh <change-name> design --write [--full]"
exit 1
fi
case "$FULL_FLAG" in
"") HANDOFF_MODE="compact" ;;
--full) HANDOFF_MODE="full" ;;
*)
red "Usage: comet-handoff.sh <change-name> design --write [--full]"
exit 1
;;
esac
CHANGE_DIR="openspec/changes/$CHANGE"
YAML="$CHANGE_DIR/.comet.yaml"
SCRIPT_DIR="$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")"
STATE_SH="$SCRIPT_DIR/comet-state.sh"
if [ ! -d "$CHANGE_DIR" ]; then
red "ERROR: change directory not found: $CHANGE_DIR"
exit 1
fi
if [ ! -f "$YAML" ]; then
red "ERROR: .comet.yaml not found at $YAML"
exit 1
fi
if [ "$(yaml_field_value phase)" != "design" ]; then
red "ERROR: design handoff requires phase: design"
exit 1
fi
for required in proposal.md design.md tasks.md; do
if [ ! -s "$CHANGE_DIR/$required" ]; then
red "ERROR: required OpenSpec artifact missing or empty: $CHANGE_DIR/$required"
exit 1
fi
done
HANDOFF_DIR="$CHANGE_DIR/.comet/handoff"
CONTEXT_COMPRESSION="$(yaml_field_value context_compression 2>/dev/null || true)"
CONTEXT_COMPRESSION="${CONTEXT_COMPRESSION:-off}"
case "$CONTEXT_COMPRESSION" in
off)
CONTEXT_JSON="$HANDOFF_DIR/design-context.json"
CONTEXT_MD="$HANDOFF_DIR/design-context.md"
;;
beta)
if [ "$HANDOFF_MODE" = "full" ]; then
warn "[HANDOFF] --full is ignored in beta mode; spec files are projected verbatim"
fi
HANDOFF_MODE="beta"
CONTEXT_JSON="$HANDOFF_DIR/spec-context.json"
CONTEXT_MD="$HANDOFF_DIR/spec-context.md"
;;
*)
red "ERROR: invalid context_compression: $CONTEXT_COMPRESSION"
red "Valid values: off, beta"
exit 1
;;
esac
mkdir -p "$HANDOFF_DIR"
CONTEXT_HASH="$(compute_context_hash)"
if [ "$CONTEXT_COMPRESSION" = "beta" ]; then
write_spec_markdown_context "$CONTEXT_MD"
write_spec_json_context "$CONTEXT_JSON"
else
write_markdown_context "$CONTEXT_MD"
write_json_context "$CONTEXT_JSON"
fi
if [ -x "$STATE_SH" ] || [ -f "$STATE_SH" ]; then
"$COMET_BASH" "$STATE_SH" set "$CHANGE" handoff_context "$CONTEXT_JSON" >/dev/null
"$COMET_BASH" "$STATE_SH" set "$CHANGE" handoff_hash "$CONTEXT_HASH" >/dev/null
else
red "ERROR: comet-state.sh not found; cannot record handoff fields"
exit 1
fi
green "[HANDOFF] wrote $CONTEXT_JSON"
green "[HANDOFF] wrote $CONTEXT_MD"
green "[HANDOFF] handoff_hash=$CONTEXT_HASH"

@ -1,336 +0,0 @@
#!/bin/bash
# comet-hook-guard.sh — PreToolUse hook for Comet phase enforcement
#
# Blocks file writes (Write/Edit) when the active Comet change is in
# a phase that does not allow source code modifications (open/design/archive).
#
# Usage (called by harness, not directly):
# PreToolUse matcher "Write|Edit" → this script
# Stdin: JSON {"tool_name":"Write|Edit","tool_input":{"file_path":"..."}}
# Exit 0 = allow
# Exit 2 = blocked (stderr message shown to user)
#
# Cross-platform: macOS / Linux / Windows Git Bash
# shellcheck disable=SC2329
set -euo pipefail
# ── Extract target file path ──────────────────────────────────────
TARGET=""
# Method 1: FILE_PATH environment variable (set by some harnesses)
if [ -n "${FILE_PATH:-}" ]; then
TARGET="$FILE_PATH"
fi
# Method 2: Parse stdin JSON
if [ -z "$TARGET" ]; then
INPUT=""
if [ ! -t 0 ]; then
INPUT=$(cat 2>/dev/null || true)
fi
if [ -n "$INPUT" ]; then
# Extract file_path value — works for both Write and Edit tool inputs
TARGET=$(printf '%s' "$INPUT" \
| grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' 2>/dev/null \
| head -1 \
| sed 's/^"file_path"[[:space:]]*:[[:space:]]*"//' \
| sed 's/"$//' \
|| true)
fi
fi
# No target found — allow (not a file-path-bearing operation)
if [ -z "$TARGET" ]; then
echo "[COMET-HOOK] allowed: no file path in tool input" >&2
exit 0
fi
# Normalize to forward slashes, collapse doubles from JSON escaping (\\ → //)
TARGET=$(printf '%s' "$TARGET" | sed 's|\\|/|g' | sed 's|///*|/|g')
# ── Resolve to project-relative path ─────────────────────────────
# Normalize helper: forward slashes only
norm() { printf '%s' "$1" | sed 's|\\|/|g'; }
RELPATH=$(norm "$TARGET")
# If already relative, use as-is
case "$RELPATH" in
/*|[A-Za-z]:/*)
# Absolute — try stripping CWD prefixes
CWD_UNIX=$(norm "$(pwd)")
CWD_PHYS=$(norm "$(pwd -P 2>/dev/null || pwd)")
# Try: TARGET as-is vs CWD logical
if [ "${RELPATH#"$CWD_UNIX"/}" != "$RELPATH" ]; then
RELPATH="${RELPATH#"$CWD_UNIX"/}"
# Try: TARGET as-is vs CWD physical (macOS /var → /private/var)
elif [ "${RELPATH#"$CWD_PHYS"/}" != "$RELPATH" ]; then
RELPATH="${RELPATH#"$CWD_PHYS"/}"
else
# Resolve TARGET's parent through filesystem (handles symlinked TARGET path)
_PDIR=$(cd "$(dirname "$TARGET")" 2>/dev/null && pwd -P 2>/dev/null || true)
if [ -n "$_PDIR" ]; then
_TRESOLVED=$(norm "${_PDIR}/$(basename "$TARGET")")
if [ "${_TRESOLVED#"$CWD_UNIX"/}" != "$_TRESOLVED" ]; then
RELPATH="${_TRESOLVED#"$CWD_UNIX"/}"
elif [ "${_TRESOLVED#"$CWD_PHYS"/}" != "$_TRESOLVED" ]; then
RELPATH="${_TRESOLVED#"$CWD_PHYS"/}"
fi
fi
fi
;;
esac
# ── Helpers to read .comet.yaml fields ───────────────────────────
is_archived() {
grep "^archived:" "$1" 2>/dev/null \
| awk '{print $2}' | tr -d '[:space:][:cntrl:]' || true
}
read_phase() {
grep "^phase:" "$1" 2>/dev/null \
| awk '{print $2}' | tr -d '[:space:][:cntrl:]' || true
}
read_field() {
grep "^$1:" "$2" 2>/dev/null \
| head -1 | awk '{print $2}' | tr -d '[:space:][:cntrl:]' || true
}
# ── Determine the governing Comet change + phase ─────────────────
#
# A write targeting a specific change directory (openspec/changes/<name>/...)
# must be governed by THAT change's own phase — never by an unrelated
# active change. Otherwise a change left in the `archive` phase would
# wrongly block artifact writes for a brand-new change created alongside it.
PHASE=""
# Path to the .comet.yaml that governs this write (used for deeper invariant checks)
GOV_YAML=""
case "$RELPATH" in
openspec/changes/*/*)
_rest="${RELPATH#openspec/changes/}"
_own_change="${_rest%%/*}"
if [ -n "$_own_change" ] && [ "$_own_change" != "archive" ]; then
_own_yaml="openspec/changes/${_own_change}/.comet.yaml"
if [ -f "$_own_yaml" ]; then
if [ "$(is_archived "$_own_yaml")" = "true" ]; then
# This change is already archived — its own writes are unrestricted
echo "[COMET-HOOK] allowed: $RELPATH (own change archived)" >&2
exit 0
fi
PHASE=$(read_phase "$_own_yaml")
GOV_YAML="$_own_yaml"
else
# Change directory exists but state file not yet written
# (artifacts are created before .comet.yaml during /comet-open).
# Treat as `open` so proposal/design/tasks/specs are allowed.
PHASE="open"
fi
fi
;;
esac
# Fallback: writes outside a specific change directory are governed by
# the first active (non-archived) change.
if [ -z "$PHASE" ]; then
YAML_FILE=""
if [ -d "openspec/changes" ]; then
for dir in openspec/changes/*/; do
[ -d "$dir" ] || continue
# Skip archived changes directory
case "$dir" in
*/archive/*) continue ;;
esac
if [ -f "${dir}.comet.yaml" ]; then
# Skip changes already marked as archived
if [ "$(is_archived "${dir}.comet.yaml")" = "true" ]; then
continue
fi
YAML_FILE="${dir}.comet.yaml"
break
fi
done
fi
# No active change — allow all writes
if [ -z "$YAML_FILE" ]; then
echo "[COMET-HOOK] allowed: no active comet change" >&2
exit 0
fi
PHASE=$(read_phase "$YAML_FILE")
GOV_YAML="$YAML_FILE"
fi
if [ -z "$PHASE" ]; then
echo "[COMET-HOOK] allowed: no phase in .comet.yaml" >&2
exit 0
fi
# ── Whitelist: phase-aware allowed paths ─────────────────────────
case "$RELPATH" in
openspec/*)
# OpenSpec artifacts — phase-aware sub-check
case "$PHASE" in
open)
# open: allow proposal, design, tasks, yaml, handoff, specs
case "$RELPATH" in
*/proposal.md|*/design.md|*/tasks.md|*/.openspec.yaml|*/.comet.yaml|*/.comet/*|*/specs/*)
echo "[COMET-HOOK] allowed: $RELPATH (phase: open, openspec artifacts)" >&2
exit 0
;;
esac
;;
design)
# design: allow handoff, delta spec (Spec Patch), proposal/design/tasks (minor refinements), .comet.yaml
case "$RELPATH" in
*/proposal.md|*/design.md|*/tasks.md|*/.comet/*|*/specs/*|*/.comet.yaml|*/.openspec.yaml)
echo "[COMET-HOOK] allowed: $RELPATH (phase: design, handoff/spec)" >&2
exit 0
;;
esac
;;
build)
# build: allow delta spec (incremental update), tasks, .comet.yaml
case "$RELPATH" in
*/specs/*|*/tasks.md|*/.comet.yaml|*/.openspec.yaml)
echo "[COMET-HOOK] allowed: $RELPATH (phase: build, spec/tasks)" >&2
exit 0
;;
esac
;;
verify)
# verify: allow tasks (post-check), .comet.yaml
case "$RELPATH" in
*/tasks.md|*/.comet.yaml|*/.openspec.yaml)
echo "[COMET-HOOK] allowed: $RELPATH (phase: verify, tasks/state)" >&2
exit 0
;;
esac
;;
archive)
# archive: allow .comet.yaml state updates only
case "$RELPATH" in
*/.comet.yaml|*/.openspec.yaml)
echo "[COMET-HOOK] allowed: $RELPATH (phase: archive, state)" >&2
exit 0
;;
esac
;;
esac
;;
docs/superpowers/*)
# Superpowers artifacts — phase-aware sub-check
case "$PHASE" in
design)
echo "[COMET-HOOK] allowed: $RELPATH (phase: design, superpowers)" >&2
exit 0
;;
build)
echo "[COMET-HOOK] allowed: $RELPATH (phase: build, superpowers)" >&2
exit 0
;;
verify)
echo "[COMET-HOOK] allowed: $RELPATH (phase: verify, superpowers)" >&2
exit 0
;;
esac
# open/archive: block docs/superpowers writes
;;
.comet/*|*/.comet/*)
# Comet config
echo "[COMET-HOOK] allowed: $RELPATH (whitelist: comet config)" >&2
exit 0
;;
.claude/*)
# Claude settings/rules
echo "[COMET-HOOK] allowed: $RELPATH (whitelist: claude config)" >&2
exit 0
;;
CLAUDE.md|CHANGELOG.md|README.md|*.md)
# Root-level markdown files
case "$RELPATH" in
*/*) ;; # subdirectory .md — NOT whitelisted, fall through
*)
echo "[COMET-HOOK] allowed: $RELPATH (whitelist: root markdown)" >&2
exit 0
;;
esac
;;
.comet.yaml|comet.yaml|.comet.yml|comet.yml)
# Project-level comet config
echo "[COMET-HOOK] allowed: $RELPATH (whitelist: comet config)" >&2
exit 0
;;
esac
# ── Phase-based enforcement ──────────────────────────────────────
case "$PHASE" in
build|verify)
# Full workflow must have a Design Doc before any source write in build/verify.
# Catches illegal open→build / design→build jumps that skipped the design phase
# (e.g. misclassified preset, direct `set phase`, or bare transition).
if [ -n "$GOV_YAML" ]; then
_wf=$(read_field "workflow" "$GOV_YAML")
_dd=$(read_field "design_doc" "$GOV_YAML")
if [ "$_wf" = "full" ] && { [ -z "$_dd" ] || [ "$_dd" = "null" ]; }; then
echo "" >&2
echo "╔══════════════════════════════════════════╗" >&2
echo "║ COMET PHASE GUARD — WRITE BLOCKED ║" >&2
echo "╚══════════════════════════════════════════╝" >&2
echo "" >&2
echo " Current phase: $PHASE (workflow: full), but design_doc is empty" >&2
echo " Target file: $RELPATH" >&2
echo "" >&2
echo " ❌ Illegal phase jump detected: full workflow entered $PHASE without a Design Doc" >&2
echo " ✅ Correct flow: create the Design Doc in design phase, then run comet-guard design --apply" >&2
echo " 💡 Run /comet-design to fill the missing design; for repair, set design_doc with comet-state" >&2
echo "" >&2
exit 2
fi
fi
# Code writes allowed in build and verify
echo "[COMET-HOOK] allowed: $RELPATH (phase: $PHASE)" >&2
exit 0
;;
open|design|archive)
echo "" >&2
echo "╔══════════════════════════════════════════╗" >&2
echo "║ COMET PHASE GUARD — WRITE BLOCKED ║" >&2
echo "╚══════════════════════════════════════════╝" >&2
echo "" >&2
echo " Current phase: $PHASE" >&2
echo " Target file: $RELPATH" >&2
echo "" >&2
case "$PHASE" in
open)
echo " ❌ open phase does not allow source code writes" >&2
echo " ✅ Allowed: create proposal/design/tasks and run guard" >&2
echo " 💡 After clarification and artifact creation, run guard --apply" >&2
;;
design)
echo " ❌ design phase does not allow source code writes" >&2
echo " ✅ Allowed: brainstorming, create the Design Doc, and run guard" >&2
echo " 💡 After the Design Doc is ready, run comet-guard design --apply to enter build" >&2
;;
archive)
echo " ❌ archive phase does not allow source code writes" >&2
echo " ✅ Allowed: confirm archive intent and run the archive script" >&2
;;
esac
echo "" >&2
exit 2
;;
esac
echo "[COMET-HOOK] allowed: $RELPATH (phase: $PHASE)" >&2
exit 0

File diff suppressed because it is too large Load Diff

@ -1,225 +0,0 @@
#!/bin/bash
# Comet YAML Schema Validator — validates .comet.yaml structure
# Usage: comet-yaml-validate.sh <change-name>
# Exit 0 = valid, exit 1 = errors found (printed to stderr)
set -euo pipefail
red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
warn() { echo -e "\033[33m$1\033[0m" >&2; }
# Input validation - prevent path traversal
validate_change_name() {
local name="$1"
# Reject empty names
if [ -z "$name" ]; then
red "ERROR: Change name cannot be empty" >&2
exit 1
fi
# Only allow alphanumeric, hyphens, and underscores
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
red "ERROR: Invalid change name: '$name'" >&2
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
exit 1
fi
# Reject path traversal attempts
if [[ "$name" =~ \.\. ]]; then
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
exit 1
fi
}
validate_change_name "$1"
CHANGE="$1"
CHANGE_DIR="openspec/changes/$CHANGE"
if [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
CHANGE_DIR="openspec/changes/archive/$CHANGE"
fi
YAML="$CHANGE_DIR/.comet.yaml"
ERRORS=0
WARNINGS=0
# Helper: get value of a top-level field (handles null, empty, quoted)
field_value() {
local value
value=$(grep "^${1}:" "$YAML" 2>/dev/null | sed "s/^${1}: *//" || true)
value=$(strip_inline_comment "$value")
strip_wrapping_quotes "$value"
}
strip_inline_comment() {
local value="$1"
printf '%s\n' "$value" | awk -v squote="'" '
{
out = ""
quote = ""
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (quote == "") {
if (c == "\"" || c == squote) {
quote = c
} else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
sub(/[[:space:]]+$/, "", out)
print out
next
}
} else if (c == quote) {
quote = ""
}
out = out c
}
print out
}
'
}
strip_wrapping_quotes() {
local value="$1"
case "$value" in
\"*\")
printf '%s\n' "${value:1:${#value}-2}"
;;
\'*\')
printf '%s\n' "${value:1:${#value}-2}"
;;
*)
printf '%s\n' "$value"
;;
esac
}
fail() { red " FAIL: $1"; ERRORS=$((ERRORS + 1)); }
warn_msg() { warn " WARN: $1"; WARNINGS=$((WARNINGS + 1)); }
echo "[VALIDATE] $YAML" >&2
# --- Required fields ---
REQUIRED_FIELDS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verified_at archived"
for field in $REQUIRED_FIELDS; do
if ! grep -q "^${field}:" "$YAML" 2>/dev/null; then
fail "missing required field '$field'"
fi
done
# --- Enum validation ---
validate_enum() {
local field="$1" value="$2"
shift 2
local valid_values="$*"
# null or empty is always acceptable
if [ -z "$value" ] || [ "$value" = "null" ]; then
return 0
fi
for v in $valid_values; do
if [ "$value" = "$v" ]; then
return 0
fi
done
fail "$field='$value' is not valid. Expected: $valid_values"
}
validate_required_enum() {
local field="$1" value="$2"
shift 2
local valid_values="$*"
if [ -z "$value" ] || [ "$value" = "null" ]; then
fail "$field='${value:-}' is not valid. Expected: $valid_values"
return 0
fi
validate_enum "$field" "$value" "$@"
}
workflow=$(field_value "workflow")
phase=$(field_value "phase")
context_compression=$(field_value "context_compression")
build_mode=$(field_value "build_mode")
build_pause=$(field_value "build_pause")
subagent_dispatch=$(field_value "subagent_dispatch")
tdd_mode=$(field_value "tdd_mode")
review_mode=$(field_value "review_mode")
isolation=$(field_value "isolation")
verify_mode=$(field_value "verify_mode")
auto_transition=$(field_value "auto_transition")
verify_result=$(field_value "verify_result")
branch_status=$(field_value "branch_status")
archived=$(field_value "archived")
direct_override=$(field_value "direct_override")
design_doc=$(field_value "design_doc")
plan=$(field_value "plan")
handoff_context=$(field_value "handoff_context")
handoff_hash=$(field_value "handoff_hash")
validate_enum "workflow" "$workflow" "full hotfix tweak"
validate_enum "phase" "$phase" "open design build verify archive"
validate_enum "context_compression" "$context_compression" "off beta"
validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
validate_enum "build_pause" "$build_pause" "null plan-ready"
validate_enum "subagent_dispatch" "$subagent_dispatch" "null confirmed"
validate_enum "tdd_mode" "$tdd_mode" "tdd direct null"
validate_enum "review_mode" "$review_mode" "off standard thorough"
validate_enum "isolation" "$isolation" "branch worktree"
validate_enum "verify_mode" "$verify_mode" "light full"
if grep -q "^auto_transition:" "$YAML" 2>/dev/null; then
validate_required_enum "auto_transition" "$auto_transition" "true false"
fi
validate_enum "verify_result" "$verify_result" "pending pass fail"
validate_enum "branch_status" "$branch_status" "pending handled"
validate_enum "archived" "$archived" "true false"
validate_enum "direct_override" "$direct_override" "true false"
# --- Path validation ---
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
if [ ! -f "$design_doc" ]; then
fail "design_doc='$design_doc' does not exist on disk"
fi
fi
if [ -n "$plan" ] && [ "$plan" != "null" ]; then
if [ ! -f "$plan" ]; then
fail "plan='$plan' does not exist on disk"
fi
fi
if [ -n "$handoff_context" ] && [ "$handoff_context" != "null" ]; then
if [ ! -f "$handoff_context" ]; then
fail "handoff_context='$handoff_context' does not exist on disk"
fi
fi
if [ -n "$handoff_hash" ] && [ "$handoff_hash" != "null" ]; then
if [[ ! "$handoff_hash" =~ ^[a-f0-9]{64}$ ]]; then
fail "handoff_hash='$handoff_hash' is not a sha256 hex digest"
fi
fi
# --- Unknown keys check ---
KNOWN_KEYS="workflow phase context_compression design_doc plan build_mode build_pause subagent_dispatch tdd_mode review_mode isolation verify_mode auto_transition verify_result verification_report branch_status verified_at created_at archived direct_override build_command verify_command handoff_context handoff_hash base_ref"
while IFS=: read -r key _; do
key="${key// /}"
[ -z "$key" ] && continue
found=0
for known in $KNOWN_KEYS; do
[ "$key" = "$known" ] && found=1 && break
done
if [ "$found" -eq 0 ]; then
warn_msg "unknown field '$key' found"
fi
done < "$YAML"
# --- Summary ---
echo "" >&2
if [ "$ERRORS" -gt 0 ]; then
red "$ERRORS error(s), $WARNINGS warning(s) — validation FAILED"
exit 1
else
green "0 errors, $WARNINGS warning(s) — validation PASSED"
exit 0
fi

@ -1,159 +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.4.2"
---
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")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
**Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files.
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]``- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! Ready to archive this change.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly

@ -1,117 +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.4.2"
---
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
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- `artifacts`: List of artifacts with their status (`done` or other)
If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos.
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash
mkdir -p "<planningHome.changesDir>/archive"
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move `changeRoot` to the archive directory
```bash
mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Whether specs were synced (if applicable)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
All artifacts complete. All tasks complete.
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use openspec-sync-specs approach (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting

@ -1,248 +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.4.2"
---
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`, `artifacts`, `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`
- Note which artifacts are `done` vs other states
If any selected change reports `actionContext.mode: "workspace-planning"`, explain that workspace bulk archive is not supported in this slice and STOP before syncing specs or moving changes. Do not fall back to repo-local paths or edit linked repos.
b. **Task completion** - Read `artifactPaths.tasks.existingOutputPaths` from status JSON
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
- If no tasks file exists, note as "No tasks"
c. **Delta specs** - Check `artifactPaths.specs.existingOutputPaths` from status JSON
- 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 "<planningHome.changesDir>/archive"
mv "<changeRoot>" "<planningHome.changesDir>/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. Create a new change to get started.
```
**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,119 +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.4.2"
---
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
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
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
- `resolvedOutputPath`: Resolved path or pattern 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 `resolvedOutputPath` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and workspace planning context
- 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,287 +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.4.2"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Resolve and read existing artifacts for context**
- Run `openspec status --change "<name>" --json`.
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Handling Different Entry Points
**User brings a vague idea:**
```
User: I'm thinking about adding real-time collaboration
You: Real-time collab is a big space. Let me think about this...
COLLABORATION SPECTRUM
════════════════════════════════════════════
Awareness Coordination Sync
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Presence│ │Cursors │ │ CRDT │
│ "3 │ │ Multi │ │Conflict│
│online" │ │ select │ │ free │
└────────┘ └────────┘ └────────┘
│ │ │
trivial moderate complex
Where's your head at?
```
**User brings a specific problem:**
```
User: The auth system is a mess
You: [reads codebase]
┌─────────────────────────────────────────────┐
│ CURRENT AUTH FLOW │
└─────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Google │ │ GitHub │ │ Email │
│ OAuth │ │ OAuth │ │ Magic │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
┌───────────┐
│ Session │
└─────┬─────┘
┌───────────┐
│ Perms │
└───────────┘
I see three tangles. Which one's burning?
```
**User is stuck mid-implementation:**
```
User: /opsx:explore add-auth-system
The OAuth integration is more complex than expected
You: [reads change artifacts]
You're on task 4: "Implement OAuth flow"
Let me trace what's involved...
[draws diagram, explores options, suggests paths]
Want to update the design to reflect this?
Or add a spike task to investigate?
```
**User wants to compare options:**
```
User: Should we use Postgres or SQLite?
You: Generic answer is boring. What's the context?
User: A CLI tool that tracks local dev environments
You: That changes everything.
┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘
Key constraints:
• No daemon running
• Must work offline
• Single user
SQLite Postgres
Deployment embedded ✓ needs server ✗
Offline yes ✓ no ✗
Single file yes ✓ no ✗
SQLite. Not even close.
Unless... is there a sync component?
```
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Create a change proposal
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own

@ -1,102 +0,0 @@
---
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.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.4.2"
---
Fast-forward through artifact creation - generate everything needed to start implementation in one go.
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change in the planning home resolved by the CLI.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "✓ Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, suggest continuing that change instead
- Verify each artifact file exists after writing before proceeding to next

@ -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.4.2"
---
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 in the planning home resolved by the CLI.
4. **Show the artifact status**
```bash
openspec status --change "<name>" --json
```
Use the returned `planningHome`, `changeRoot`, `artifactPaths`, and `nextSteps` instead of assuming repo-local paths.
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,552 +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.4.2"
---
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 the OpenSpec CLI is installed:
```bash
# 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 CLI not installed:**
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.
Stop here if not installed.
---
## 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
# 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
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 at the `changeRoot` reported by `openspec status --change "<name>" --json` 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: <changeRoot from status JSON>
The folder structure:
```
<changeRoot>/
├── 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 the `resolvedOutputPath` from `openspec instructions proposal --change "<name>" --json`.
```
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:** Resolve where the spec file should be created:
```bash
openspec instructions specs --change "<name>" --json
# Use resolvedOutputPath from the JSON. If it is a glob, choose the concrete file path using the schema instruction and workspace planning context.
```
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 the concrete file path chosen from `resolvedOutputPath`.
---
## 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 the `resolvedOutputPath` from `openspec instructions design --change "<name>" --json`.
---
## 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 the `resolvedOutputPath` from `openspec instructions tasks --change "<name>" --json`.
---
## 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. The archive path is derived from `planningHome.changesDir` and the date.
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: `<planningHome.changesDir>/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
**Core workflow:**
| Command | What it does |
|-------------------|--------------------------------------------|
| `/opsx:propose` | Create a change and generate all artifacts |
| `/opsx:explore` | Think through problems before/during work |
| `/opsx:apply` | Implement tasks from a change |
| `/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:propose` 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 the `changeRoot` reported by `openspec status --change "<name>" --json`.
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
**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:apply <name>` | Implement tasks |
| `/opsx:archive <name>` | Archive when done |
**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.
---
## 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,111 +0,0 @@
---
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.4.2"
---
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 in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next

@ -1,147 +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.4.2"
---
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. **Resolve change context**
Run:
```bash
openspec status --change "<name>" --json
```
If status reports `actionContext.mode: "workspace-planning"`, explain that workspace spec sync is not supported in this slice and STOP. Do not fall back to repo-local paths or edit linked repos.
3. **Find delta specs**
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
4. **For each delta spec, apply changes to main specs**
For each repo-local capability delta spec path returned by the CLI:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
5. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result

@ -1,171 +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.4.2"
---
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")
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
- Which artifacts exist for this change
If status reports `actionContext.mode: "workspace-planning"`, explain that full workspace implementation verification is not supported in this slice and STOP. Do not infer repo-local implementation ownership or edit linked repos.
3. **Get planning context and load artifacts**
```bash
openspec instructions apply --change "<name>" --json
```
This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). 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 `contextFiles.tasks` exists, read every file path in 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 `contextFiles.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 `contextFiles.design` exists:
- 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"

@ -75,17 +75,12 @@ def init_analysis_db():
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
TradeRecord, TradeImportBatch,
TradePair, TradeReflection, DailyReflection, TradeTag, TradeRecordTag, TradeExperience,
TradeAIAnalysis,
)
AnalysisBase.metadata.create_all(bind=analysis_engine)
# 迁移为已有表添加新增列SQLite 不支持 IF NOT EXISTS用异常处理
_migrate_add_columns()
# 初始化预设标签
_init_preset_tags()
def init_analysis_mysql(mysql_engine):
"""初始化期货智析数据库表MySQL"""
@ -95,8 +90,6 @@ def init_analysis_mysql(mysql_engine):
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
TradeRecord, TradeImportBatch,
TradePair, TradeReflection, DailyReflection, TradeTag, TradeRecordTag, TradeExperience,
TradeAIAnalysis,
)
AnalysisBase.metadata.create_all(bind=mysql_engine)
logger.info("MySQL analysis 表结构初始化完成")
@ -122,53 +115,3 @@ def _migrate_add_columns():
conn.commit()
conn.close()
def _init_preset_tags():
"""初始化预设标签"""
from app.analysis_models import TradeTag
from sqlalchemy import inspect
session = AnalysisSessionLocal()
try:
# 检查表是否存在
inspector = inspect(session.get_bind())
if 'trade_tags' not in inspector.get_table_names():
return
# 检查是否已有预设标签
existing = session.query(TradeTag).filter_by(is_preset=True).first()
if existing:
return
preset_tags = [
# 操作类
TradeTag(name="追涨杀跌", category="operation", is_preset=True),
TradeTag(name="逆市操作", category="operation", is_preset=True),
TradeTag(name="提前入场", category="operation", is_preset=True),
TradeTag(name="延迟出场", category="operation", is_preset=True),
TradeTag(name="加仓过早", category="operation", is_preset=True),
TradeTag(name="减仓过晚", category="operation", is_preset=True),
# 心态类
TradeTag(name="情绪化交易", category="mindset", is_preset=True),
TradeTag(name="恐惧平仓", category="mindset", is_preset=True),
TradeTag(name="贪婪持仓", category="mindset", is_preset=True),
TradeTag(name="侥幸心理", category="mindset", is_preset=True),
# 纪律类
TradeTag(name="严格执行计划", category="discipline", is_preset=True),
TradeTag(name="违反止损", category="discipline", is_preset=True),
TradeTag(name="超仓交易", category="discipline", is_preset=True),
TradeTag(name="频繁交易", category="discipline", is_preset=True),
# 技术类
TradeTag(name="突破交易", category="technical", is_preset=True),
TradeTag(name="回调交易", category="technical", is_preset=True),
TradeTag(name="趋势跟踪", category="technical", is_preset=True),
TradeTag(name="震荡交易", category="technical", is_preset=True),
TradeTag(name="反转交易", category="technical", is_preset=True),
]
session.add_all(preset_tags)
session.commit()
except Exception:
session.rollback()
finally:
session.close()

@ -323,155 +323,3 @@ class TradeImportBatch(AnalysisBase):
def __repr__(self):
return f"<TradeImportBatch {self.batch_id} {self.source_file}>"
# ==================== 交易反思系统模型 ====================
class TradePair(AnalysisBase):
"""交易配对表 - 将开仓和平仓配对为一个完整交易"""
__tablename__ = "trade_pairs"
id = Column(Integer, primary_key=True, autoincrement=True)
variety = Column(String(16), nullable=False, index=True, comment="品种代码 AG")
direction = Column(String(8), nullable=False, comment="方向: long/short")
open_record_ids = Column(JSON, nullable=False, comment="开仓记录ID列表 [id1, id2, ...]")
close_record_ids = Column(JSON, nullable=False, comment="平仓记录ID列表 [id1, id2, ...]")
open_price = Column(Float, nullable=True, comment="平均开仓价")
close_price = Column(Float, nullable=True, comment="平均平仓价")
total_volume = Column(Float, nullable=True, comment="总手数")
close_pnl = Column(Float, nullable=True, default=0.0, comment="平仓盈亏")
total_commission = Column(Float, nullable=True, default=0.0, comment="总手续费")
net_pnl = Column(Float, nullable=True, default=0.0, comment="净盈亏 = 平仓盈亏 - 手续费")
open_date = Column(String(16), nullable=True, index=True, comment="开仓日期")
close_date = Column(String(16), nullable=True, index=True, comment="平仓日期")
created_at = Column(DateTime, nullable=False, default=datetime.now)
__table_args__ = (
Index('ix_trade_pairs_variety_date', 'variety', 'open_date'),
)
def __repr__(self):
return f"<TradePair {self.variety} {self.direction} pnl={self.net_pnl}>"
class TradeReflection(AnalysisBase):
"""交易反思表 - 每笔交易的反思内容"""
__tablename__ = "trade_reflections"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_pair_id = Column(Integer, nullable=False, index=True, comment="关联交易配对ID")
trade_date = Column(String(16), nullable=False, index=True, comment="交易日期")
# 模板字段
entry_reason = Column(Text, nullable=True, comment="入场理由")
entry_timing = Column(String(8), nullable=True, comment="入场时机评价: good/fair/poor")
position_management = Column(Text, nullable=True, comment="仓位管理反思")
exit_reason = Column(Text, nullable=True, comment="出场理由")
exit_timing = Column(String(8), nullable=True, comment="出场时机评价: good/fair/poor")
discipline_score = Column(Integer, nullable=True, comment="纪律评分 1-5")
# 自由反思
free_reflection = Column(Text, nullable=True, comment="自由反思内容")
# 状态
ai_analyzed = Column(Boolean, nullable=False, default=False, comment="是否已AI分析")
ai_version = Column(Integer, nullable=True, comment="AI分析版本号")
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return f"<TradeReflection pair_id={self.trade_pair_id} date={self.trade_date}>"
class DailyReflection(AnalysisBase):
"""当日反思表 - 每天的整体反思"""
__tablename__ = "daily_reflections"
id = Column(Integer, primary_key=True, autoincrement=True)
reflection_date = Column(String(16), nullable=False, unique=True, index=True, comment="反思日期 YYYY-MM-DD")
emotion_state = Column(String(16), nullable=True, comment="情绪状态: 平静/兴奋/焦虑/恐惧/贪婪")
market_judgment = Column(Text, nullable=True, comment="市场判断")
discipline_score = Column(Integer, nullable=True, comment="执行纪律评分 1-5")
overall_rating = Column(Integer, nullable=True, comment="总体评价 1-5")
summary = Column(Text, nullable=True, comment="总结")
improvements = Column(Text, nullable=True, comment="改进方向")
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return f"<DailyReflection {self.reflection_date}>"
class TradeTag(AnalysisBase):
"""标签定义表"""
__tablename__ = "trade_tags"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32), nullable=False, unique=True, index=True, comment="标签名称")
category = Column(String(16), nullable=False, comment="分类: operation/mindset/discipline/technical/custom")
is_preset = Column(Boolean, nullable=False, default=False, comment="是否预设标签")
color = Column(String(16), nullable=True, comment="标签颜色(前端用)")
created_at = Column(DateTime, nullable=False, default=datetime.now)
def __repr__(self):
return f"<TradeTag {self.name} [{self.category}]>"
class TradeRecordTag(AnalysisBase):
"""交易-标签关联表"""
__tablename__ = "trade_record_tags"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_pair_id = Column(Integer, nullable=False, index=True, comment="交易配对ID")
tag_id = Column(Integer, nullable=False, index=True, comment="标签ID")
created_at = Column(DateTime, nullable=False, default=datetime.now)
__table_args__ = (
UniqueConstraint("trade_pair_id", "tag_id", name="uq_pair_tag"),
)
def __repr__(self):
return f"<TradeRecordTag pair_id={self.trade_pair_id} tag_id={self.tag_id}>"
class TradeExperience(AnalysisBase):
"""经验教训表"""
__tablename__ = "trade_experiences"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(128), nullable=False, comment="经验标题")
content = Column(Text, nullable=False, comment="经验内容")
exp_type = Column(String(16), nullable=False, comment="类型: lesson/tip/warning")
source_pair_id = Column(Integer, nullable=True, index=True, comment="来源交易配对ID")
source_date = Column(String(16), nullable=True, index=True, comment="来源交易日期")
tags = Column(JSON, nullable=True, comment="关联标签名称列表")
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return f"<TradeExperience {self.title} [{self.exp_type}]>"
class TradeAIAnalysis(AnalysisBase):
"""交易 AI 分析结果表(多版本)"""
__tablename__ = "trade_ai_analysis"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_pair_id = Column(Integer, nullable=False, index=True, comment="交易配对ID")
version = Column(Integer, nullable=False, comment="分析版本号")
# AI 输出结构化字段
overall_evaluation = Column(Text, nullable=True, comment="综合评价")
strengths = Column(JSON, nullable=True, comment="优点列表")
weaknesses = Column(JSON, nullable=True, comment="不足列表")
experience_suggestion = Column(Text, nullable=True, comment="经验提炼建议")
suggestion_type = Column(String(16), nullable=True, comment="建议类型: lesson/tip/warning")
raw_response = Column(Text, nullable=True, comment="AI 原始响应")
prompt_snapshot = Column(Text, nullable=True, comment="提示词快照")
# 是否已保存到经验库
saved_to_experience = Column(Boolean, nullable=False, default=False, comment="是否已保存到经验库")
experience_id = Column(Integer, nullable=True, comment="关联经验ID")
created_at = Column(DateTime, nullable=False, default=datetime.now)
__table_args__ = (
Index('ix_trade_ai_analysis_pair_version', 'trade_pair_id', 'version', unique=True),
)
def __repr__(self):
return f"<TradeAIAnalysis pair_id={self.trade_pair_id} version={self.version}>"

@ -18,18 +18,6 @@ from app.services.trade_parser import (
calc_overall_statistics,
get_trade_pairs,
)
from app.services.trade_pairing_engine import (
auto_pair_trades,
get_daily_trades_with_pairs,
create_manual_pair,
delete_pair,
)
from app.services.reflection_ai_analysis import (
analyze_with_reflection,
get_analysis_history,
get_latest_analysis,
save_suggestion_as_experience,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/trade-review", tags=["交易复盘"])
@ -600,538 +588,3 @@ async def analyze_trade(
except Exception as e:
logger.exception("AI分析交易失败")
return {"success": False, "message": f"AI分析失败: {str(e)}"}
# ==================== 交易反思系统 API ====================
from app.analysis_models import (
TradePair, TradeReflection, DailyReflection,
TradeTag, TradeRecordTag, TradeExperience,
)
from pydantic import BaseModel
# --- 按天交易视图 ---
@router.get("/daily-trades/{trade_date}")
def api_get_daily_trades(
trade_date: str,
db: Session = Depends(get_analysis_db),
):
"""获取指定日期的交易+配对+反思状态"""
# 先执行自动配对
auto_pair_trades(db, trade_date)
db.commit()
data = get_daily_trades_with_pairs(db, trade_date)
return {"success": True, "data": data}
# --- 手动配对 ---
class ManualPairRequest(BaseModel):
open_record_ids: list[int]
close_record_ids: list[int]
@router.post("/trade-pairs")
def api_create_manual_pair(
req: ManualPairRequest,
db: Session = Depends(get_analysis_db),
):
"""手动创建交易配对"""
try:
pair = create_manual_pair(db, req.open_record_ids, req.close_record_ids)
db.commit()
return {"success": True, "data": {"id": pair.id}}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
db.rollback()
logger.exception("创建配对失败")
raise HTTPException(status_code=500, detail=f"创建配对失败: {str(e)}")
@router.delete("/trade-pairs/{pair_id}")
def api_delete_pair(
pair_id: int,
db: Session = Depends(get_analysis_db),
):
"""删除交易配对"""
success = delete_pair(db, pair_id)
if not success:
raise HTTPException(status_code=404, detail="配对不存在")
db.commit()
return {"success": True, "message": "配对已删除"}
# --- 反思 CRUD ---
class ReflectionRequest(BaseModel):
trade_pair_id: int
trade_date: str
entry_reason: Optional[str] = None
entry_timing: Optional[str] = None
position_management: Optional[str] = None
exit_reason: Optional[str] = None
exit_timing: Optional[str] = None
discipline_score: Optional[int] = None
free_reflection: Optional[str] = None
@router.post("/reflections")
def api_create_reflection(
req: ReflectionRequest,
db: Session = Depends(get_analysis_db),
):
"""创建交易反思"""
# 检查是否已存在
existing = db.query(TradeReflection).filter(
TradeReflection.trade_pair_id == req.trade_pair_id
).first()
if existing:
raise HTTPException(status_code=400, detail="该交易已有反思,请使用更新接口")
reflection = TradeReflection(
trade_pair_id=req.trade_pair_id,
trade_date=req.trade_date,
entry_reason=req.entry_reason,
entry_timing=req.entry_timing,
position_management=req.position_management,
exit_reason=req.exit_reason,
exit_timing=req.exit_timing,
discipline_score=req.discipline_score,
free_reflection=req.free_reflection,
)
db.add(reflection)
db.commit()
return {"success": True, "data": {"id": reflection.id}}
@router.put("/reflections/{reflection_id}")
def api_update_reflection(
reflection_id: int,
req: ReflectionRequest,
db: Session = Depends(get_analysis_db),
):
"""更新交易反思"""
reflection = db.query(TradeReflection).filter(TradeReflection.id == reflection_id).first()
if not reflection:
raise HTTPException(status_code=404, detail="反思不存在")
# 更新字段
for field in ['entry_reason', 'entry_timing', 'position_management',
'exit_reason', 'exit_timing', 'discipline_score', 'free_reflection']:
val = getattr(req, field)
if val is not None:
setattr(reflection, field, val)
# 重新保存时重置 AI 分析状态(因为内容变了)
reflection.ai_analyzed = False
reflection.ai_version = None
db.commit()
return {"success": True, "message": "反思已更新"}
@router.get("/reflections")
def api_get_reflections(
trade_pair_id: Optional[int] = Query(None),
trade_date: Optional[str] = Query(None),
db: Session = Depends(get_analysis_db),
):
"""查询反思列表"""
query = db.query(TradeReflection)
if trade_pair_id:
query = query.filter(TradeReflection.trade_pair_id == trade_pair_id)
if trade_date:
query = query.filter(TradeReflection.trade_date == trade_date)
reflections = query.order_by(TradeReflection.created_at.desc()).all()
return {"success": True, "data": [{
"id": r.id,
"trade_pair_id": r.trade_pair_id,
"trade_date": r.trade_date,
"entry_reason": r.entry_reason,
"entry_timing": r.entry_timing,
"position_management": r.position_management,
"exit_reason": r.exit_reason,
"exit_timing": r.exit_timing,
"discipline_score": r.discipline_score,
"free_reflection": r.free_reflection,
"ai_analyzed": r.ai_analyzed,
"ai_version": r.ai_version,
"created_at": r.created_at.strftime('%Y-%m-%d %H:%M:%S') if r.created_at else None,
"updated_at": r.updated_at.strftime('%Y-%m-%d %H:%M:%S') if r.updated_at else None,
} for r in reflections]}
# --- 当日反思 CRUD ---
class DailyReflectionRequest(BaseModel):
reflection_date: str
emotion_state: Optional[str] = None
market_judgment: Optional[str] = None
discipline_score: Optional[int] = None
overall_rating: Optional[int] = None
summary: Optional[str] = None
improvements: Optional[str] = None
@router.post("/daily-reflections")
def api_create_daily_reflection(
req: DailyReflectionRequest,
db: Session = Depends(get_analysis_db),
):
"""创建或更新当日反思"""
existing = db.query(DailyReflection).filter(
DailyReflection.reflection_date == req.reflection_date
).first()
if existing:
# 更新
for field in ['emotion_state', 'market_judgment', 'discipline_score',
'overall_rating', 'summary', 'improvements']:
val = getattr(req, field)
if val is not None:
setattr(existing, field, val)
db.commit()
return {"success": True, "data": {"id": existing.id, "action": "updated"}}
else:
# 创建
dr = DailyReflection(
reflection_date=req.reflection_date,
emotion_state=req.emotion_state,
market_judgment=req.market_judgment,
discipline_score=req.discipline_score,
overall_rating=req.overall_rating,
summary=req.summary,
improvements=req.improvements,
)
db.add(dr)
db.commit()
return {"success": True, "data": {"id": dr.id, "action": "created"}}
@router.get("/daily-reflections/{reflection_date}")
def api_get_daily_reflection(
reflection_date: str,
db: Session = Depends(get_analysis_db),
):
"""获取当日反思"""
dr = db.query(DailyReflection).filter(
DailyReflection.reflection_date == reflection_date
).first()
if not dr:
return {"success": True, "data": None}
return {"success": True, "data": {
"id": dr.id,
"reflection_date": dr.reflection_date,
"emotion_state": dr.emotion_state,
"market_judgment": dr.market_judgment,
"discipline_score": dr.discipline_score,
"overall_rating": dr.overall_rating,
"summary": dr.summary,
"improvements": dr.improvements,
}}
# --- 标签系统 ---
class TagRequest(BaseModel):
name: str
category: str = "custom"
@router.get("/tags")
def api_get_tags(
category: Optional[str] = Query(None),
db: Session = Depends(get_analysis_db),
):
"""获取所有标签"""
query = db.query(TradeTag)
if category:
query = query.filter(TradeTag.category == category)
tags = query.order_by(TradeTag.is_preset.desc(), TradeTag.name).all()
return {"success": True, "data": [{
"id": t.id,
"name": t.name,
"category": t.category,
"is_preset": t.is_preset,
} for t in tags]}
@router.post("/tags")
def api_create_tag(
req: TagRequest,
db: Session = Depends(get_analysis_db),
):
"""创建自定义标签(含去重)"""
existing = db.query(TradeTag).filter(TradeTag.name == req.name).first()
if existing:
return {"success": True, "data": {"id": existing.id, "action": "exists"}}
tag = TradeTag(name=req.name, category=req.category, is_preset=False)
db.add(tag)
db.commit()
return {"success": True, "data": {"id": tag.id, "action": "created"}}
@router.post("/trade-pairs/{pair_id}/tags")
def api_add_tag_to_trade(
pair_id: int,
tag_id: int,
db: Session = Depends(get_analysis_db),
):
"""给交易打标签"""
# 检查是否已存在
existing = db.query(TradeRecordTag).filter(
TradeRecordTag.trade_pair_id == pair_id,
TradeRecordTag.tag_id == tag_id,
).first()
if existing:
return {"success": True, "message": "标签已存在"}
relation = TradeRecordTag(trade_pair_id=pair_id, tag_id=tag_id)
db.add(relation)
db.commit()
return {"success": True, "message": "标签已添加"}
@router.delete("/trade-pairs/{pair_id}/tags/{tag_id}")
def api_remove_tag_from_trade(
pair_id: int,
tag_id: int,
db: Session = Depends(get_analysis_db),
):
"""移除交易标签"""
db.query(TradeRecordTag).filter(
TradeRecordTag.trade_pair_id == pair_id,
TradeRecordTag.tag_id == tag_id,
).delete()
db.commit()
return {"success": True, "message": "标签已移除"}
@router.get("/trade-pairs/{pair_id}/tags")
def api_get_trade_tags(
pair_id: int,
db: Session = Depends(get_analysis_db),
):
"""获取交易的标签列表"""
relations = db.query(TradeRecordTag).filter(
TradeRecordTag.trade_pair_id == pair_id
).all()
tag_ids = [r.tag_id for r in relations]
tags = db.query(TradeTag).filter(TradeTag.id.in_(tag_ids)).all()
return {"success": True, "data": [{
"id": t.id,
"name": t.name,
"category": t.category,
} for t in tags]}
# --- 经验库 ---
class ExperienceRequest(BaseModel):
title: str
content: str
exp_type: str # lesson/tip/warning
source_pair_id: Optional[int] = None
source_date: Optional[str] = None
tags: Optional[list[str]] = None
@router.get("/experiences")
def api_get_experiences(
exp_type: Optional[str] = Query(None),
tag: Optional[str] = Query(None),
keyword: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: Session = Depends(get_analysis_db),
):
"""获取经验列表(支持筛选和分页)"""
query = db.query(TradeExperience)
if exp_type:
query = query.filter(TradeExperience.exp_type == exp_type)
if tag:
query = query.filter(TradeExperience.tags.contains([tag]))
if keyword:
query = query.filter(
(TradeExperience.title.contains(keyword)) |
(TradeExperience.content.contains(keyword))
)
total = query.count()
experiences = query.order_by(
TradeExperience.created_at.desc()
).offset((page - 1) * page_size).limit(page_size).all()
return {"success": True, "data": {
"total": total,
"page": page,
"page_size": page_size,
"items": [{
"id": e.id,
"title": e.title,
"content": e.content,
"exp_type": e.exp_type,
"source_pair_id": e.source_pair_id,
"source_date": e.source_date,
"tags": e.tags or [],
"created_at": e.created_at.strftime('%Y-%m-%d %H:%M:%S') if e.created_at else None,
} for e in experiences],
}}
@router.post("/experiences")
def api_create_experience(
req: ExperienceRequest,
db: Session = Depends(get_analysis_db),
):
"""创建经验"""
exp = TradeExperience(
title=req.title,
content=req.content,
exp_type=req.exp_type,
source_pair_id=req.source_pair_id,
source_date=req.source_date,
tags=req.tags or [],
)
db.add(exp)
db.commit()
return {"success": True, "data": {"id": exp.id}}
@router.put("/experiences/{exp_id}")
def api_update_experience(
exp_id: int,
req: ExperienceRequest,
db: Session = Depends(get_analysis_db),
):
"""更新经验"""
exp = db.query(TradeExperience).filter(TradeExperience.id == exp_id).first()
if not exp:
raise HTTPException(status_code=404, detail="经验不存在")
for field in ['title', 'content', 'exp_type', 'source_pair_id', 'source_date', 'tags']:
val = getattr(req, field)
if val is not None:
setattr(exp, field, val)
db.commit()
return {"success": True, "message": "经验已更新"}
@router.delete("/experiences/{exp_id}")
def api_delete_experience(
exp_id: int,
db: Session = Depends(get_analysis_db),
):
"""删除经验"""
exp = db.query(TradeExperience).filter(TradeExperience.id == exp_id).first()
if not exp:
raise HTTPException(status_code=404, detail="经验不存在")
db.delete(exp)
db.commit()
return {"success": True, "message": "经验已删除"}
@router.get("/experiences/tag-stats")
def api_get_experience_tag_stats(
db: Session = Depends(get_analysis_db),
):
"""按标签统计经验数量"""
from collections import Counter
experiences = db.query(TradeExperience).all()
tag_counter = Counter()
for exp in experiences:
for tag in (exp.tags or []):
tag_counter[tag] += 1
# 同时返回所有标签的定义信息
tags = db.query(TradeTag).all()
tag_map = {t.name: {"id": t.id, "category": t.category, "is_preset": t.is_preset} for t in tags}
data = []
for name, count in tag_counter.most_common():
info = tag_map.get(name, {})
data.append({
"name": name,
"count": count,
"tag_id": info.get("id"),
"category": info.get("category", "custom"),
"is_preset": info.get("is_preset", False),
})
return {"success": True, "data": data}
# ==================== AI 重新分析 ====================
class ReanalyzeRequest(BaseModel):
trade_pair_id: int
@router.post("/reanalyze")
async def api_reanalyze_trade(
req: ReanalyzeRequest,
db: Session = Depends(get_analysis_db),
):
"""基于反思内容重新 AI 分析交易"""
result = analyze_with_reflection(db, req.trade_pair_id)
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("message", "分析失败"))
return result
@router.get("/reanalyze/{trade_pair_id}/history")
def api_get_analysis_history(
trade_pair_id: int,
db: Session = Depends(get_analysis_db),
):
"""获取交易的 AI 分析历史版本"""
history = get_analysis_history(db, trade_pair_id)
return {"success": True, "data": history}
@router.get("/reanalyze/{trade_pair_id}/latest")
def api_get_latest_analysis(
trade_pair_id: int,
db: Session = Depends(get_analysis_db),
):
"""获取交易最新 AI 分析结果"""
analysis = get_latest_analysis(db, trade_pair_id)
return {"success": True, "data": analysis}
class SaveSuggestionRequest(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
tags: Optional[list[str]] = None
@router.post("/ai-analysis/{analysis_id}/save-experience")
def api_save_suggestion_as_experience(
analysis_id: int,
req: SaveSuggestionRequest,
db: Session = Depends(get_analysis_db),
):
"""将 AI 分析建议保存到经验库"""
result = save_suggestion_as_experience(
db, analysis_id,
title=req.title,
content=req.content,
tags=req.tags,
)
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("message", "保存失败"))
return result

@ -200,12 +200,6 @@ def review_plan_page():
return FileResponse(str(STATIC_DIR / "review_plan.html"))
@app.get("/trade-reflection-demo")
def trade_reflection_demo_page():
"""交易反思系统 Demo 页面"""
return FileResponse(str(STATIC_DIR / "trade_reflection_demo.html"))
@app.get("/api/v1/health")
def health():
return {"status": "ok", "service": "market-data-buffer"}

@ -1,335 +0,0 @@
"""
交易反思 AI 分析服务 - 基于反思内容重新分析交易并提炼经验
"""
import json
import logging
import re
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from app.analysis_models import TradePair, TradeReflection, TradeAIAnalysis, TradeExperience
from app.services.ai_analysis import AIFuturesAnalyzer
logger = logging.getLogger(__name__)
REFLECTION_ANALYSIS_PROMPT = """你是一位资深的交易心理与策略复盘教练。请基于以下交易数据和交易者的反思内容,进行深度分析。
=== 交易基础信息 ===
品种{variety}
方向{direction_text}
开仓日期时间{open_date} {open_time}
平仓日期时间{close_date} {close_time}
开仓均价{open_price}
平仓均价{close_price}
交易手数{volume}
平仓盈亏{close_pnl}
手续费{commission}
净盈亏{net_pnl}
=== 交易者反思内容 ===
入场理由{entry_reason}
入场时机评价{entry_timing}
仓位管理反思{position_management}
出场理由{exit_reason}
出场时机评价{exit_timing}
纪律评分1-5{discipline_score}
自由反思{free_reflection}
=== 当日整体反思 ===
情绪状态{emotion_state}
市场判断{market_judgment}
当日总结{daily_summary}
改进方向{daily_improvements}
=== 任务要求 ===
请严格按以下 JSON 格式输出分析结果不要输出任何其他内容
{{
"overall_evaluation": "综合评价:这笔交易的执行质量、决策逻辑、与市场的匹配度等",
"strengths": ["优点1", "优点2", "优点3"],
"weaknesses": ["不足1", "不足2", "不足3"],
"experience_suggestion": "提炼出的核心经验教训或改进建议,语言简洁 actionable",
"suggestion_type": "lesson",
"actionable_plan": ["下次可以做的具体改进1", "下次可以做的具体改进2"]
}}
注意
1. suggestion_type 只能是 lesson经验tip技巧warning警告三者之一
2. 必须结合反思内容进行分析不要脱离交易者自己的总结
3. 如果交易亏损重点分析亏损原因和避免方案
4. 如果交易盈利重点分析盈利是否来自计划还是运气
"""
def _timing_text(timing: Optional[str]) -> str:
"""时机评价转中文"""
mapping = {"good": "良好", "fair": "一般", "poor": "较差"}
return mapping.get(timing, timing or "未评价")
def _direction_text(direction: Optional[str]) -> str:
"""方向转中文"""
mapping = {"long": "多头", "short": "空头"}
return mapping.get(direction, direction or "未知")
def _get_records_text(db: Session, pair: TradePair) -> str:
"""获取配对关联的原始记录信息"""
from app.analysis_models import TradeRecord
record_ids = (pair.open_record_ids or []) + (pair.close_record_ids or [])
if not record_ids:
return ""
records = db.query(TradeRecord).filter(TradeRecord.id.in_(record_ids)).all()
lines = []
for r in sorted(records, key=lambda x: f"{x.trade_date} {x.trade_time or ''}"):
lines.append(f"{r.trade_date} {r.trade_time or ''} {r.symbol} {r.direction}{r.offset} {r.price}×{r.volume}")
return "\n".join(lines)
def build_reflection_prompt(db: Session, pair: TradePair, reflection: TradeReflection,
daily_reflection: Optional[Dict] = None) -> str:
"""构建反思增强分析提示词"""
daily = daily_reflection or {}
# 获取第一条开仓和第一条平仓记录的时间
from app.analysis_models import TradeRecord
open_time = ""
close_time = ""
open_record_ids = pair.open_record_ids or []
close_record_ids = pair.close_record_ids or []
if open_record_ids:
open_rec = db.query(TradeRecord).filter(TradeRecord.id == open_record_ids[0]).first()
if open_rec:
open_time = open_rec.trade_time or ""
if close_record_ids:
close_rec = db.query(TradeRecord).filter(TradeRecord.id == close_record_ids[0]).first()
if close_rec:
close_time = close_rec.trade_time or ""
return REFLECTION_ANALYSIS_PROMPT.format(
variety=pair.variety,
direction_text=_direction_text(pair.direction),
open_date=pair.open_date or "",
open_time=open_time,
close_date=pair.close_date or "",
close_time=close_time,
open_price=pair.open_price,
close_price=pair.close_price,
volume=pair.total_volume,
close_pnl=pair.close_pnl,
commission=pair.total_commission,
net_pnl=pair.net_pnl,
entry_reason=reflection.entry_reason or "未填写",
entry_timing=_timing_text(reflection.entry_timing),
position_management=reflection.position_management or "未填写",
exit_reason=reflection.exit_reason or "未填写",
exit_timing=_timing_text(reflection.exit_timing),
discipline_score=reflection.discipline_score or "未评分",
free_reflection=reflection.free_reflection or "未填写",
emotion_state=daily.get("emotion_state", "未填写"),
market_judgment=daily.get("market_judgment", "未填写"),
daily_summary=daily.get("summary", "未填写"),
daily_improvements=daily.get("improvements", "未填写"),
)
def _parse_ai_response(response: str) -> Dict:
"""解析 AI 返回的 JSON"""
try:
# 尝试直接解析
data = json.loads(response)
return data
except json.JSONDecodeError:
pass
# 尝试从 Markdown 代码块中提取
code_block_pattern = re.compile(r'```(?:json)?\s*([\s\S]*?)```', re.MULTILINE)
matches = code_block_pattern.findall(response)
for match in matches:
try:
return json.loads(match.strip())
except json.JSONDecodeError:
continue
# 尝试从文本中提取第一个 JSON 对象
json_pattern = re.compile(r'\{[\s\S]*\}')
matches = json_pattern.findall(response)
for match in matches:
try:
return json.loads(match)
except json.JSONDecodeError:
continue
# 解析失败,返回原始内容作为 overall_evaluation
return {
"overall_evaluation": response,
"strengths": [],
"weaknesses": [],
"experience_suggestion": "",
"suggestion_type": "lesson",
"actionable_plan": [],
}
def _get_next_version(db: Session, trade_pair_id: int) -> int:
"""获取下一个版本号"""
latest = db.query(TradeAIAnalysis).filter(
TradeAIAnalysis.trade_pair_id == trade_pair_id
).order_by(TradeAIAnalysis.version.desc()).first()
return (latest.version + 1) if latest else 1
def analyze_with_reflection(db: Session, trade_pair_id: int) -> Dict:
"""
基于反思内容重新分析交易
返回{success, data/version, message}
"""
pair = db.query(TradePair).filter(TradePair.id == trade_pair_id).first()
if not pair:
return {"success": False, "message": "交易配对不存在"}
reflection = db.query(TradeReflection).filter(
TradeReflection.trade_pair_id == trade_pair_id
).first()
if not reflection:
return {"success": False, "message": "该交易尚未填写反思,无法进行分析"}
# 获取当日反思
daily_reflection = None
from app.analysis_models import DailyReflection
dr = db.query(DailyReflection).filter(
DailyReflection.reflection_date == pair.close_date
).first()
if dr:
daily_reflection = {
"emotion_state": dr.emotion_state,
"market_judgment": dr.market_judgment,
"summary": dr.summary,
"improvements": dr.improvements,
}
# 构建提示词
prompt = build_reflection_prompt(db, pair, reflection, daily_reflection)
# 调用 AI
analyzer = AIFuturesAnalyzer(db)
model = analyzer.get_active_model()
if not model:
return {"success": False, "message": "未配置AI模型或模型未激活"}
response = analyzer.call_ai_model(prompt, model)
if not response:
return {"success": False, "message": "AI模型返回空响应请稍后重试"}
# 解析结果
parsed = _parse_ai_response(response)
# 计算版本号
version = _get_next_version(db, trade_pair_id)
# 保存分析结果
analysis = TradeAIAnalysis(
trade_pair_id=trade_pair_id,
version=version,
overall_evaluation=parsed.get("overall_evaluation", ""),
strengths=parsed.get("strengths", []),
weaknesses=parsed.get("weaknesses", []),
experience_suggestion=parsed.get("experience_suggestion", ""),
suggestion_type=parsed.get("suggestion_type", "lesson"),
raw_response=response,
prompt_snapshot=prompt,
)
db.add(analysis)
# 更新反思的 AI 分析状态
reflection.ai_analyzed = True
reflection.ai_version = version
db.commit()
return {
"success": True,
"data": {
"version": version,
"overall_evaluation": analysis.overall_evaluation,
"strengths": analysis.strengths,
"weaknesses": analysis.weaknesses,
"experience_suggestion": analysis.experience_suggestion,
"suggestion_type": analysis.suggestion_type,
"actionable_plan": parsed.get("actionable_plan", []),
},
}
def get_analysis_history(db: Session, trade_pair_id: int) -> List[Dict]:
"""获取交易的所有 AI 分析版本"""
analyses = db.query(TradeAIAnalysis).filter(
TradeAIAnalysis.trade_pair_id == trade_pair_id
).order_by(TradeAIAnalysis.version.desc()).all()
return [{
"id": a.id,
"version": a.version,
"overall_evaluation": a.overall_evaluation,
"strengths": a.strengths,
"weaknesses": a.weaknesses,
"experience_suggestion": a.experience_suggestion,
"suggestion_type": a.suggestion_type,
"saved_to_experience": a.saved_to_experience,
"experience_id": a.experience_id,
"created_at": a.created_at.strftime('%Y-%m-%d %H:%M:%S') if a.created_at else None,
} for a in analyses]
def get_latest_analysis(db: Session, trade_pair_id: int) -> Optional[Dict]:
"""获取最新 AI 分析结果"""
analysis = db.query(TradeAIAnalysis).filter(
TradeAIAnalysis.trade_pair_id == trade_pair_id
).order_by(TradeAIAnalysis.version.desc()).first()
if not analysis:
return None
return {
"id": analysis.id,
"version": analysis.version,
"overall_evaluation": analysis.overall_evaluation,
"strengths": analysis.strengths,
"weaknesses": analysis.weaknesses,
"experience_suggestion": analysis.experience_suggestion,
"suggestion_type": analysis.suggestion_type,
"saved_to_experience": analysis.saved_to_experience,
"experience_id": analysis.experience_id,
"created_at": analysis.created_at.strftime('%Y-%m-%d %H:%M:%S') if analysis.created_at else None,
}
def save_suggestion_as_experience(db: Session, analysis_id: int, title: Optional[str] = None,
content: Optional[str] = None, tags: Optional[List[str]] = None) -> Dict:
"""将 AI 分析建议保存为经验"""
analysis = db.query(TradeAIAnalysis).filter(TradeAIAnalysis.id == analysis_id).first()
if not analysis:
return {"success": False, "message": "分析记录不存在"}
pair = db.query(TradePair).filter(TradePair.id == analysis.trade_pair_id).first()
exp_title = title or analysis.experience_suggestion[:50] if analysis.experience_suggestion else "AI提炼经验"
exp_content = content or analysis.experience_suggestion or ""
exp = TradeExperience(
title=exp_title,
content=exp_content,
exp_type=analysis.suggestion_type or "lesson",
source_pair_id=analysis.trade_pair_id,
source_date=pair.close_date if pair else None,
tags=tags or [],
)
db.add(exp)
db.flush()
analysis.saved_to_experience = True
analysis.experience_id = exp.id
db.commit()
return {"success": True, "data": {"experience_id": exp.id}}

@ -1,336 +0,0 @@
"""
交易配对引擎 - 将开仓和平仓配对为完整交易持久化到 TradePair
"""
import logging
from typing import Optional
from sqlalchemy.orm import Session
from collections import defaultdict
from app.analysis_models import TradeRecord, TradePair, TradeReflection, DailyReflection
logger = logging.getLogger(__name__)
def auto_pair_trades(db: Session, trade_date: str) -> dict:
"""
自动配对指定日期及之前未平仓的交易记录
按品种+方向分组FIFO 规则配对
返回{created: 新建配对数, updated: 更新配对数, unpaired: 未配对记录数}
"""
# 查询指定日期的记录
current_records = db.query(TradeRecord).filter(
TradeRecord.trade_date == trade_date
).order_by(
TradeRecord.variety, TradeRecord.symbol, TradeRecord.trade_date, TradeRecord.trade_time
).all()
if not current_records:
return {"created": 0, "updated": 0, "unpaired": 0}
# 查询之前未配对的记录(跨日持仓)
# 找出所有已经配对的记录 ID
all_pairs = db.query(TradePair).all()
paired_ids = set()
for p in all_pairs:
paired_ids.update(p.open_record_ids or [])
paired_ids.update(p.close_record_ids or [])
# 查询 trade_date 之前的未配对记录
previous_unpaired = db.query(TradeRecord).filter(
TradeRecord.trade_date < trade_date,
TradeRecord.id.notin_(paired_ids) if paired_ids else True,
).order_by(
TradeRecord.variety, TradeRecord.symbol, TradeRecord.trade_date, TradeRecord.trade_time
).all()
# 合并当前记录和之前未配对记录
all_records = previous_unpaired + current_records
# 按品种分组
by_variety = defaultdict(list)
for r in all_records:
by_variety[r.variety].append(r)
result = {"created": 0, "updated": 0, "unpaired": 0}
for variety, recs in by_variety.items():
pair_result = _pair_single_variety(db, variety, recs)
result["created"] += pair_result["created"]
result["updated"] += pair_result["updated"]
result["unpaired"] += pair_result["unpaired"]
return result
def _pair_single_variety(db: Session, variety: str, records: list) -> dict:
"""单个品种的配对逻辑"""
# 分离开仓和平仓
opens = [r for r in records if r.offset == '']
closes = [r for r in records if r.offset == '']
result = {"created": 0, "updated": 0, "unpaired": 0}
# 按方向分组
long_opens = [r for r in opens if r.direction == '']
short_opens = [r for r in opens if r.direction == '']
long_closes = [r for r in closes if r.direction == ''] # 卖平 = 多头平仓
short_closes = [r for r in closes if r.direction == ''] # 买平 = 空头平仓
# 多头配对
result["created"] += _pair_direction(db, variety, long_opens, long_closes, "long")
# 空头配对
result["created"] += _pair_direction(db, variety, short_opens, short_closes, "short")
# 统计未配对
all_paired_open_ids = set()
all_paired_close_ids = set()
existing_pairs = db.query(TradePair).filter(
TradePair.variety == variety,
).all()
for p in existing_pairs:
all_paired_open_ids.update(p.open_record_ids or [])
all_paired_close_ids.update(p.close_record_ids or [])
unpaired_count = 0
for r in records:
if r.id not in all_paired_open_ids and r.id not in all_paired_close_ids:
unpaired_count += 1
result["unpaired"] = unpaired_count
return result
def _pair_direction(db: Session, variety: str, opens: list, closes: list, direction: str) -> int:
"""单方向配对FIFO支持多开合并返回新建配对数
每个平仓记录会尽可能与前面的开仓记录配对形成一个完整配对
如果多个开仓合并才能匹配一个平仓则这些开仓记录会归入同一个配对
"""
created = 0
# 开仓队列,每个元素是 (record, remaining_volume)
open_queue = [(r, r.volume or 0) for r in opens]
close_queue = [(r, r.volume or 0) for r in closes]
while open_queue and close_queue:
close_rec, close_remaining = close_queue[0]
# 从开仓队列中取足够手数来匹配这个平仓
needed = close_remaining
used_opens = [] # (record, take_volume)
while needed > 0 and open_queue:
open_rec, open_remaining = open_queue[0]
take = min(open_remaining, needed)
used_opens.append((open_rec, take))
needed -= take
if take >= open_remaining:
open_queue.pop(0)
else:
open_queue[0] = (open_rec, open_remaining - take)
if not used_opens:
break
# 实际匹配的手数
matched_volume = close_remaining - needed
# 更新平仓队列
if matched_volume >= close_remaining:
close_queue.pop(0)
else:
close_queue[0] = (close_rec, close_remaining - matched_volume)
# 计算均价(按手数加权)
open_volume_sum = sum(take for _, take in used_opens)
open_price = sum((r.price or 0) * take for r, take in used_opens) / open_volume_sum if open_volume_sum > 0 else 0
close_price = close_rec.price or 0
# 计算盈亏和手续费
# 平仓盈亏按比例分配到该平仓记录
close_pnl = (close_rec.close_pnl or 0) * (matched_volume / close_rec.volume) if close_rec.volume else 0
total_commission = sum((r.commission or 0) * (take / r.volume) for r, take in used_opens if r.volume) + \
(close_rec.commission or 0) * (matched_volume / close_rec.volume) if close_rec.volume else 0
net_pnl = close_pnl - total_commission
# 最早开仓日期和最早平仓日期
open_date = min(r.trade_date for r, _ in used_opens)
close_date = close_rec.trade_date
pair = TradePair(
variety=variety,
direction=direction,
open_record_ids=[r.id for r, _ in used_opens],
close_record_ids=[close_rec.id],
open_price=round(open_price, 2),
close_price=round(close_price, 2),
total_volume=matched_volume,
close_pnl=round(close_pnl, 2),
total_commission=round(total_commission, 2),
net_pnl=round(net_pnl, 2),
open_date=open_date,
close_date=close_date,
)
db.add(pair)
created += 1
db.flush()
return created
def get_daily_trades_with_pairs(db: Session, trade_date: str) -> dict:
"""
获取指定日期的交易数据包含配对信息和反思状态
返回{date, stats, pairs, unpaired_records, daily_reflection}
"""
# 获取所有配对
pairs = db.query(TradePair).filter(
(TradePair.open_date == trade_date) | (TradePair.close_date == trade_date)
).order_by(TradePair.open_date, TradePair.close_date).all()
# 获取所有交易记录
records = db.query(TradeRecord).filter(
TradeRecord.trade_date == trade_date
).order_by(TradeRecord.trade_date, TradeRecord.trade_time).all()
# 计算统计
total_pnl = sum(p.net_pnl or 0 for p in pairs)
total_trades = len(pairs) + len(records) # 配对 + 未配对
winning_trades = sum(1 for p in pairs if (p.net_pnl or 0) > 0)
win_rate = round(winning_trades / total_trades * 100, 1) if total_trades > 0 else 0
# 获取反思状态
pair_ids = [p.id for p in pairs]
reflections = db.query(TradeReflection).filter(
TradeReflection.trade_pair_id.in_(pair_ids)
).all()
reflection_map = {r.trade_pair_id: r for r in reflections}
# 获取当日反思
daily_reflection = db.query(DailyReflection).filter(
DailyReflection.reflection_date == trade_date
).first()
# 构建返回数据
pairs_data = []
for p in pairs:
reflection = reflection_map.get(p.id)
pairs_data.append({
"id": p.id,
"variety": p.variety,
"direction": p.direction,
"open_price": p.open_price,
"close_price": p.close_price,
"total_volume": p.total_volume,
"close_pnl": p.close_pnl,
"total_commission": p.total_commission,
"net_pnl": p.net_pnl,
"open_date": p.open_date,
"close_date": p.close_date,
"open_record_ids": p.open_record_ids,
"close_record_ids": p.close_record_ids,
"reflection_status": "done" if reflection else ("pending" if _has_unpaired_records(db, p) else "none"),
"ai_analyzed": reflection.ai_analyzed if reflection else False,
"ai_version": reflection.ai_version if reflection else None,
})
# 未配对记录
paired_record_ids = set()
for p in pairs:
paired_record_ids.update(p.open_record_ids or [])
paired_record_ids.update(p.close_record_ids or [])
unpaired = [r for r in records if r.id not in paired_record_ids]
unpaired_data = [{
"id": r.id,
"symbol": r.symbol,
"variety": r.variety,
"direction": r.direction,
"offset": r.offset,
"price": r.price,
"volume": r.volume,
"trade_date": r.trade_date,
"trade_time": r.trade_time,
} for r in unpaired]
return {
"date": trade_date,
"stats": {
"total_pnl": round(total_pnl, 2),
"total_trades": total_trades,
"winning_trades": winning_trades,
"win_rate": win_rate,
"paired_count": len(pairs),
"unpaired_count": len(unpaired),
"reflected_count": sum(1 for r in reflection_map.values()),
},
"pairs": pairs_data,
"unpaired_records": unpaired_data,
"daily_reflection": {
"id": daily_reflection.id if daily_reflection else None,
"emotion_state": daily_reflection.emotion_state if daily_reflection else None,
"market_judgment": daily_reflection.market_judgment if daily_reflection else None,
"discipline_score": daily_reflection.discipline_score if daily_reflection else None,
"overall_rating": daily_reflection.overall_rating if daily_reflection else None,
"summary": daily_reflection.summary if daily_reflection else None,
"improvements": daily_reflection.improvements if daily_reflection else None,
} if daily_reflection else None,
}
def _has_unpaired_records(db: Session, pair: TradePair) -> bool:
"""检查配对是否有未配对的开仓或平仓记录"""
# 简化逻辑:如果开仓日期和平仓日期不同,可能存在跨日未配对
return pair.open_date != pair.close_date
def create_manual_pair(db: Session, open_record_ids: list[int], close_record_ids: list[int]) -> TradePair:
"""手动创建配对"""
open_records = db.query(TradeRecord).filter(TradeRecord.id.in_(open_record_ids)).all()
close_records = db.query(TradeRecord).filter(TradeRecord.id.in_(close_record_ids)).all()
if not open_records or not close_records:
raise ValueError("开仓或平仓记录不存在")
variety = open_records[0].variety
direction = "long" if open_records[0].direction == '' else "short"
# 计算统计
total_volume = sum(r.volume or 0 for r in open_records)
total_close_pnl = sum(r.close_pnl or 0 for r in close_records)
total_commission = sum((r.commission or 0) for r in open_records) + sum((r.commission or 0) for r in close_records)
net_pnl = total_close_pnl - total_commission
avg_open_price = sum(r.price or 0 for r in open_records) / len(open_records) if open_records else 0
avg_close_price = sum(r.price or 0 for r in close_records) / len(close_records) if close_records else 0
pair = TradePair(
variety=variety,
direction=direction,
open_record_ids=open_record_ids,
close_record_ids=close_record_ids,
open_price=round(avg_open_price, 2),
close_price=round(avg_close_price, 2),
total_volume=total_volume,
close_pnl=total_close_pnl,
total_commission=total_commission,
net_pnl=round(net_pnl, 2),
open_date=open_records[0].trade_date,
close_date=close_records[-1].trade_date,
)
db.add(pair)
db.flush()
return pair
def delete_pair(db: Session, pair_id: int) -> bool:
"""删除配对(不删除原始交易记录)"""
pair = db.query(TradePair).filter(TradePair.id == pair_id).first()
if not pair:
return False
# 删除关联的反思
db.query(TradeReflection).filter(TradeReflection.trade_pair_id == pair_id).delete()
# 删除配对
db.delete(pair)
db.flush()
return True

@ -495,7 +495,6 @@
.tr-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; }
.tr-toolbar-left, .tr-toolbar-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tr-btn { padding: 10px 20px; border-radius: 12px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; display: flex; align-items: center; gap: 8px; transition: all 0.2s; font-family: inherit; }
.tr-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.tr-btn-primary { background: var(--color-brand); color: #fff; }
.tr-btn-primary:hover { background: #0066d6; box-shadow: 0 4px 10px rgba(0,122,255,0.3); }
.tr-btn-secondary { background: #fff; color: var(--text-secondary); box-shadow: var(--shadow-sm); border: 1px solid rgba(0,0,0,0.05); }
@ -583,146 +582,12 @@
.tr-empty-icon { font-size: 56px; opacity: 0.3; }
.tr-empty-text { font-size: 15px; color: var(--text-tertiary); }
/* ============================================
交易反思子系统样式
============================================ */
.tr-subtabs { display: flex; gap: 8px; margin-bottom: 24px; background: rgba(255,255,255,0.6); padding: 4px; border-radius: 14px; backdrop-filter: blur(8px); width: fit-content; }
.tr-subtab { padding: 8px 20px; border-radius: 10px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; background: transparent; color: var(--text-secondary); transition: all 0.2s; font-family: inherit; }
.tr-subtab:hover { background: rgba(0,0,0,0.03); color: var(--text-primary); }
.tr-subtab.active { background: #fff; color: var(--color-brand); box-shadow: var(--shadow-sm); font-weight: 600; }
.tr-tab-panel { display: none; }
.tr-tab-panel.active { display: block; }
/* 交易反思 - 日期导航 */
.tr-refl-datebar { display: flex; align-items: center; justify-content: center; gap: 16px; margin-bottom: 24px; }
.tr-refl-datebtn { width: 36px; height: 36px; border-radius: 50%; border: none; background: #fff; color: var(--text-secondary); cursor: pointer; box-shadow: var(--shadow-sm); transition: all 0.2s; font-size: 16px; }
.tr-refl-datebtn:hover { background: var(--color-brand); color: #fff; }
.tr-refl-datetext { font-size: 20px; font-weight: 700; color: var(--text-primary); min-width: 120px; text-align: center; }
.tr-refl-actions { display: flex; gap: 10px; margin-left: auto; }
/* 反思统计卡片 */
.tr-refl-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.tr-refl-statcard { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; text-align: center; box-shadow: var(--shadow-sm); transition: transform 0.2s; }
.tr-refl-statcard:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.tr-refl-statlabel { font-size: 12px; color: var(--text-tertiary); margin-bottom: 8px; font-weight: 600; }
.tr-refl-statvalue { font-size: 26px; font-weight: 700; }
.tr-refl-statvalue.profit { color: var(--color-down); }
.tr-refl-statvalue.loss { color: var(--color-up); }
.tr-refl-reflected { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 9999px; font-size: 12px; font-weight: 600; }
.tr-refl-reflected.yes { background: rgba(52,199,89,0.12); color: var(--color-down); }
.tr-refl-reflected.no { background: rgba(255,59,48,0.12); color: var(--color-up); }
/* 当日反思区域 */
.tr-refl-daily { background: var(--bg-card); border-radius: var(--radius-card); padding: 24px; margin-bottom: 24px; box-shadow: var(--shadow-sm); }
.tr-refl-daily-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.tr-refl-daily-title { font-size: 17px; font-weight: 700; color: var(--text-primary); }
.tr-refl-daily-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.tr-refl-daily-item { background: #F5F5F7; border-radius: 12px; padding: 14px; }
.tr-refl-daily-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 6px; font-weight: 600; text-transform: uppercase; }
.tr-refl-daily-value { font-size: 14px; color: var(--text-primary); font-weight: 500; line-height: 1.4; }
.tr-refl-daily-value.empty { color: var(--text-tertiary); font-style: italic; }
/* 交易配对卡片 */
.tr-refl-section-title { font-size: 17px; font-weight: 700; margin-bottom: 16px; color: var(--text-primary); }
.tr-refl-pairs { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; margin-bottom: 24px; }
.tr-refl-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; box-shadow: var(--shadow-sm); transition: transform 0.2s, box-shadow 0.2s; position: relative; overflow: hidden; }
.tr-refl-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.tr-refl-card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--color-brand); }
.tr-refl-card.profit::before { background: var(--color-down); }
.tr-refl-card.loss::before { background: var(--color-up); }
.tr-refl-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
.tr-refl-card-symbol { font-size: 16px; font-weight: 700; }
.tr-refl-card-dir { font-size: 12px; padding: 3px 10px; border-radius: 6px; font-weight: 600; }
.tr-refl-card-dir.long { background: rgba(52,199,89,0.12); color: var(--color-down); }
.tr-refl-card-dir.short { background: rgba(255,59,48,0.12); color: var(--color-up); }
.tr-refl-card-pnl { font-size: 20px; font-weight: 700; text-align: right; }
.tr-refl-card-pnl.profit { color: var(--color-down); }
.tr-refl-card-pnl.loss { color: var(--color-up); }
.tr-refl-card-nodes { font-size: 13px; color: var(--text-secondary); margin-bottom: 14px; line-height: 1.6; }
.tr-refl-card-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.tr-refl-tag { font-size: 11px; padding: 4px 10px; border-radius: 9999px; background: #F5F5F7; color: var(--text-secondary); font-weight: 500; }
.tr-refl-tag.active { background: rgba(0,122,255,0.12); color: var(--color-brand); }
.tr-refl-card-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.tr-refl-card-actions .tr-btn { padding: 6px 12px; font-size: 12px; }
/* 未配对交易 */
.tr-refl-unpaired { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; box-shadow: var(--shadow-sm); }
.tr-refl-unpaired-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.tr-refl-unpaired-header .tr-refl-section-title { margin-bottom: 0; }
.tr-refl-unpaired-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.tr-refl-unpaired-table th { background: #F5F5F7; padding: 10px 8px; text-align: left; font-weight: 600; color: var(--text-tertiary); font-size: 12px; }
.tr-refl-unpaired-table td { padding: 10px 8px; border-bottom: 1px solid rgba(0,0,0,0.04); color: var(--text-primary); }
.tr-refl-unpaired-table input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--color-brand); cursor: pointer; }
/* 经验库 */
.tr-exp-toolbar { display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; align-items: center; }
.tr-exp-search { background: #fff; border: 1px solid rgba(0,0,0,0.05); border-radius: 12px; padding: 0 14px; height: 40px; font-size: 14px; width: 260px; outline: none; }
.tr-exp-search:focus { border-color: var(--color-brand); box-shadow: 0 0 0 3px rgba(0,122,255,0.15); }
.tr-exp-filter { background: #fff; border: 1px solid rgba(0,0,0,0.05); border-radius: 10px; padding: 0 12px; height: 36px; font-size: 13px; color: var(--text-primary); outline: none; }
.tr-exp-viewtabs { display: flex; gap: 6px; margin-left: auto; }
.tr-exp-viewtab { padding: 6px 14px; border-radius: 10px; font-size: 13px; cursor: pointer; border: none; background: #fff; color: var(--text-secondary); transition: all 0.2s; }
.tr-exp-viewtab:hover { background: #F5F5F7; }
.tr-exp-viewtab.active { background: var(--color-brand); color: #fff; }
.tr-exp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
.tr-exp-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; box-shadow: var(--shadow-sm); transition: all 0.2s; }
.tr-exp-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.tr-exp-card-title { font-size: 15px; font-weight: 700; margin-bottom: 10px; color: var(--text-primary); }
.tr-exp-card-meta { font-size: 12px; color: var(--text-tertiary); margin-bottom: 12px; }
.tr-exp-card-content { font-size: 13px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 14px; }
.tr-exp-card-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.tr-exp-pagination { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 24px; }
.tr-exp-pageinfo { font-size: 13px; color: var(--text-secondary); font-weight: 500; }
.tr-exp-detail-meta { font-size: 12px; color: var(--text-tertiary); margin-bottom: 16px; }
.tr-exp-detail-content { font-size: 14px; color: var(--text-primary); line-height: 1.8; background: #F5F5F7; border-radius: 12px; padding: 16px; margin-bottom: 16px; white-space: pre-wrap; }
.tr-exp-detail-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.tr-exp-detail-source { font-size: 13px; color: var(--text-secondary); }
.tr-exp-detail-source .link { cursor: pointer; }
/* Modal 表单 */
.tr-form-group { margin-bottom: 18px; }
.tr-form-label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px; }
.tr-form-input, .tr-form-select, .tr-form-textarea { width: 100%; background: #F5F5F7; border: 1px solid transparent; border-radius: 10px; padding: 10px 12px; font-size: 14px; color: var(--text-primary); font-family: inherit; outline: none; transition: all 0.2s; }
.tr-form-input:focus, .tr-form-select:focus, .tr-form-textarea:focus { border-color: var(--color-brand); background: #fff; box-shadow: 0 0 0 3px rgba(0,122,255,0.1); }
.tr-form-textarea { min-height: 90px; resize: vertical; }
.tr-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.tr-form-rating { display: flex; gap: 6px; }
.tr-form-star { font-size: 22px; color: #E5E5E7; cursor: pointer; transition: color 0.15s; }
.tr-form-star.active { color: #FF9F0A; }
.tr-form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 24px; }
/* 标签管理 */
.tr-tag-section { margin-bottom: 18px; }
.tr-tag-section-title { font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 10px; }
.tr-tag-pool { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.tr-tag-chip { padding: 6px 12px; border-radius: 9999px; font-size: 12px; cursor: pointer; border: 1px solid rgba(0,0,0,0.06); background: #fff; color: var(--text-secondary); transition: all 0.15s; }
.tr-tag-chip:hover { border-color: var(--color-brand); color: var(--color-brand); }
.tr-tag-chip.selected { background: rgba(0,122,255,0.12); border-color: var(--color-brand); color: var(--color-brand); font-weight: 500; }
.tr-tag-custom { display: flex; gap: 8px; margin-top: 12px; }
.tr-tag-custom .tr-form-input { flex: 1; }
/* AI 分析结果 */
.tr-ai-version-row { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; padding: 12px 16px; background: #F5F5F7; border-radius: 12px; }
.tr-ai-version-label { font-size: 13px; font-weight: 600; color: var(--text-tertiary); }
.tr-ai-version-row .tr-form-select { flex: 1; min-width: 200px; margin: 0; }
.tr-ai-result-section { margin-bottom: 18px; }
.tr-ai-result-title { font-size: 14px; font-weight: 700; color: var(--color-brand); margin-bottom: 8px; }
.tr-ai-result-content { font-size: 14px; color: var(--text-primary); line-height: 1.8; background: #F5F5F7; border-radius: 12px; padding: 14px; }
.tr-ai-list { padding-left: 18px; margin: 0; }
.tr-ai-list li { margin-bottom: 6px; line-height: 1.6; }
@media (max-width: 1200px) {
.tr-stats-grid { grid-template-columns: repeat(3, 1fr); }
.tr-refl-stats { grid-template-columns: repeat(2, 1fr); }
.tr-refl-daily-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.tr-stats-grid { grid-template-columns: repeat(2, 1fr); }
.tr-overview-grid { grid-template-columns: repeat(2, 1fr); }
.tr-refl-stats { grid-template-columns: 1fr; }
.tr-refl-daily-grid { grid-template-columns: 1fr; }
.tr-form-row { grid-template-columns: 1fr; }
.tr-refl-pairs { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
@ -1109,429 +974,82 @@
<!-- 交易复盘视图 -->
<div id="trade-review-view" class="view">
<!-- 子 Tab 切换栏 -->
<div class="tr-subtabs" id="tr-subtabs">
<button class="tr-subtab active" data-subtab="stats">统计分析</button>
<button class="tr-subtab" data-subtab="reflection">交易反思</button>
<button class="tr-subtab" data-subtab="experience">经验库</button>
</div>
<!-- 统计分析子 Tab -->
<div id="tr-tab-stats" class="tr-tab-panel active">
<!-- 工具栏 -->
<div class="tr-toolbar">
<div class="tr-toolbar-left">
<input type="file" id="tr-file-input" accept=".xls,.xlsx" style="display:none" />
<input type="file" id="tr-batch-file-input" accept=".xls,.xlsx" multiple style="display:none" />
<button id="tr-btn-import" class="tr-btn tr-btn-primary">
<span>📥</span><span>导入结算单</span>
</button>
<button id="tr-btn-batch-import" class="tr-btn tr-btn-primary" title="同时导入多个结算单文件">
<span>📂</span><span>批量导入</span>
</button>
<input type="date" id="tr-start-date" class="tr-date-input" title="开始日期" />
<span style="color:var(--text-tertiary);font-size:13px;">~</span>
<input type="date" id="tr-end-date" class="tr-date-input" title="结束日期" />
<button id="tr-btn-query" class="tr-btn tr-btn-secondary">查询</button>
</div>
<div class="tr-toolbar-right">
<button id="tr-btn-delete-date" class="tr-btn tr-btn-danger" title="删除当前查询日期的所有交易记录">
<span>🗑️</span><span>删除当日</span>
</button>
<button id="tr-btn-batches" class="tr-btn tr-btn-secondary">
<span>📋</span><span>批次管理</span>
</button>
</div>
</div>
<!-- 6个统计卡片 -->
<div id="tr-stats-grid" class="tr-stats-grid"></div>
<!-- 账户权益曲线 -->
<div class="tr-chart-box">
<div class="tr-chart-title">账户权益曲线(累计净盈亏 + 每日盈亏)</div>
<div class="tr-chart-container" id="tr-equity-chart"></div>
</div>
<!-- 品种盈亏分布气泡图 -->
<div class="tr-chart-box">
<div class="tr-chart-title">品种盈亏分布(气泡大小=手续费占比,颜色=净盈亏)</div>
<div class="tr-bubble-container" id="tr-bubble-chart"></div>
</div>
<!-- 每日交易详情表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">每日交易详情(点击日期查看详情)</div>
<table class="tr-table">
<thead>
<tr>
<th>日期</th><th>笔数</th><th>平仓盈亏</th><th>手续费</th>
<th>净盈亏</th><th>盈利笔</th><th>亏损笔</th><th>胜率</th>
<th>最大盈利</th><th>最大亏损</th><th>品种数</th><th>操作</th>
</tr>
</thead>
<tbody id="tr-daily-body"></tbody>
</table>
<div id="tr-daily-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-icon">📊</div>
<div class="tr-empty-text">暂无交易数据,请先导入结算单</div>
</div>
</div>
<!-- 品种盈亏排行表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">品种盈亏排行(点击查看品种详情)</div>
<table class="tr-table">
<thead>
<tr>
<th>排名</th><th>代码</th><th>名称</th><th>净盈亏</th>
<th>平仓盈亏</th><th>手续费</th><th>胜率</th>
<th>盈亏比</th><th>笔数</th><th>操作</th>
</tr>
</thead>
<tbody id="tr-variety-body"></tbody>
</table>
<div id="tr-variety-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-icon">📋</div>
<div class="tr-empty-text">暂无品种数据</div>
</div>
</div>
</div>
<!-- 交易反思子 Tab -->
<div id="tr-tab-reflection" class="tr-tab-panel">
<div class="tr-refl-datebar">
<button class="tr-refl-datebtn" id="tr-refl-prev" title="前一天"></button>
<span class="tr-refl-datetext" id="tr-refl-current-date">-</span>
<button class="tr-refl-datebtn" id="tr-refl-next" title="后一天"></button>
<div class="tr-refl-actions">
<button class="tr-btn tr-btn-secondary" id="tr-refl-edit-daily">
<span>✏️</span><span>当日反思编辑</span>
</button>
</div>
</div>
<div class="tr-refl-stats">
<div class="tr-refl-statcard">
<div class="tr-refl-statlabel">总盈亏</div>
<div class="tr-refl-statvalue" id="tr-refl-stat-pnl">-</div>
</div>
<div class="tr-refl-statcard">
<div class="tr-refl-statlabel">交易笔数</div>
<div class="tr-refl-statvalue" id="tr-refl-stat-trades">-</div>
</div>
<div class="tr-refl-statcard">
<div class="tr-refl-statlabel">胜率</div>
<div class="tr-refl-statvalue" id="tr-refl-stat-winrate">-</div>
</div>
<div class="tr-refl-statcard">
<div class="tr-refl-statlabel">已反思</div>
<div id="tr-refl-stat-reflected">-</div>
</div>
</div>
<div class="tr-refl-daily">
<div class="tr-refl-daily-header">
<div class="tr-refl-daily-title">当日反思</div>
</div>
<div class="tr-refl-daily-grid">
<div class="tr-refl-daily-item">
<div class="tr-refl-daily-label">情绪状态</div>
<div class="tr-refl-daily-value" id="tr-refl-emotion">-</div>
</div>
<div class="tr-refl-daily-item">
<div class="tr-refl-daily-label">执行纪律</div>
<div class="tr-refl-daily-value" id="tr-refl-discipline">-</div>
</div>
<div class="tr-refl-daily-item">
<div class="tr-refl-daily-label">总体评价</div>
<div class="tr-refl-daily-value" id="tr-refl-overall">-</div>
</div>
<div class="tr-refl-daily-item">
<div class="tr-refl-daily-label">市场判断</div>
<div class="tr-refl-daily-value" id="tr-refl-market">-</div>
</div>
</div>
</div>
<div class="tr-refl-section-title">交易配对</div>
<div id="tr-refl-pairs" class="tr-refl-pairs"></div>
<div class="tr-refl-unpaired">
<div class="tr-refl-unpaired-header">
<div class="tr-refl-section-title">未配对交易</div>
<button class="tr-btn tr-btn-primary" id="tr-refl-manual-pair-btn">
<span>🔗</span><span>手动配对</span>
</button>
</div>
<table class="tr-refl-unpaired-table">
<thead>
<tr><th><input type="checkbox" id="tr-refl-unpaired-select-all" title="全选"></th><th>时间</th><th>品种</th><th>开平</th><th>方向</th><th>价格</th><th>手数</th><th>盈亏</th></tr>
</thead>
<tbody id="tr-refl-unpaired-body"></tbody>
</table>
<div id="tr-refl-unpaired-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-text">暂无未配对交易</div>
</div>
</div>
</div>
<!-- 经验库子 Tab -->
<div id="tr-tab-experience" class="tr-tab-panel">
<div class="tr-exp-toolbar">
<input type="text" class="tr-exp-search" id="tr-exp-search" placeholder="搜索经验...">
<select class="tr-exp-filter" id="tr-exp-tag-filter">
<option value="">全部标签</option>
</select>
<select class="tr-exp-filter" id="tr-exp-type-filter">
<option value="">全部类型</option>
<option value="lesson">教训</option>
<option value="insight">心得</option>
<option value="strategy">策略</option>
<option value="discipline">纪律</option>
</select>
<div class="tr-exp-viewtabs">
<button class="tr-exp-viewtab active" data-view="list">列表</button>
<button class="tr-exp-viewtab" data-view="timeline">时间线</button>
<button class="tr-exp-viewtab" data-view="category">分类</button>
</div>
</div>
<div id="tr-exp-container" class="tr-exp-grid"></div>
<div id="tr-exp-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-icon">📚</div>
<div class="tr-empty-text">暂无经验记录</div>
<!-- 工具栏 -->
<div class="tr-toolbar">
<div class="tr-toolbar-left">
<input type="file" id="tr-file-input" accept=".xls,.xlsx" style="display:none" />
<input type="file" id="tr-batch-file-input" accept=".xls,.xlsx" multiple style="display:none" />
<button id="tr-btn-import" class="tr-btn tr-btn-primary">
<span>📥</span><span>导入结算单</span>
</button>
<button id="tr-btn-batch-import" class="tr-btn tr-btn-primary" title="同时导入多个结算单文件">
<span>📂</span><span>批量导入</span>
</button>
<input type="date" id="tr-start-date" class="tr-date-input" title="开始日期" />
<span style="color:var(--text-tertiary);font-size:13px;">~</span>
<input type="date" id="tr-end-date" class="tr-date-input" title="结束日期" />
<button id="tr-btn-query" class="tr-btn tr-btn-secondary">查询</button>
</div>
<div class="tr-exp-pagination" id="tr-exp-pagination" style="display:none;">
<button class="tr-btn tr-btn-secondary" id="tr-exp-prev">上一页</button>
<span class="tr-exp-pageinfo" id="tr-exp-pageinfo"></span>
<button class="tr-btn tr-btn-secondary" id="tr-exp-next">下一页</button>
<div class="tr-toolbar-right">
<button id="tr-btn-delete-date" class="tr-btn tr-btn-danger" title="删除当前查询日期的所有交易记录">
<span>🗑️</span><span>删除当日</span>
</button>
<button id="tr-btn-batches" class="tr-btn tr-btn-secondary">
<span>📋</span><span>批次管理</span>
</button>
</div>
</div>
<!-- 反思编辑 Modal -->
<div id="tr-refl-edit-modal" class="tr-modal-overlay" onclick="if(event.target===this)trReflCloseEditModal()">
<div class="tr-modal" style="max-width:720px;">
<div class="tr-modal-header">
<span class="tr-modal-title">写反思</span>
<button class="tr-modal-close" onclick="trReflCloseEditModal()"></button>
</div>
<div class="tr-modal-body">
<form id="tr-refl-edit-form">
<input type="hidden" id="tr-refl-edit-pair-id">
<div class="tr-form-row">
<div class="tr-form-group">
<label class="tr-form-label">入场理由</label>
<textarea class="tr-form-textarea" id="tr-refl-entry-reason" placeholder="当时为什么入场?"></textarea>
</div>
<div class="tr-form-group">
<label class="tr-form-label">出场理由</label>
<textarea class="tr-form-textarea" id="tr-refl-exit-reason" placeholder="为什么在这个位置出场?"></textarea>
</div>
</div>
<div class="tr-form-row">
<div class="tr-form-group">
<label class="tr-form-label">入场时机评价</label>
<select class="tr-form-select" id="tr-refl-entry-timing">
<option value="">请选择</option>
<option value="excellent">优秀</option>
<option value="good">良好</option>
<option value="average">一般</option>
<option value="poor">较差</option>
<option value="bad">糟糕</option>
</select>
</div>
<div class="tr-form-group">
<label class="tr-form-label">出场时机评价</label>
<select class="tr-form-select" id="tr-refl-exit-timing">
<option value="">请选择</option>
<option value="excellent">优秀</option>
<option value="good">良好</option>
<option value="average">一般</option>
<option value="poor">较差</option>
<option value="bad">糟糕</option>
</select>
</div>
</div>
<div class="tr-form-group">
<label class="tr-form-label">仓位管理反思</label>
<textarea class="tr-form-textarea" id="tr-refl-position" placeholder="仓位是否合理?是否有改进空间?"></textarea>
</div>
<div class="tr-form-group">
<label class="tr-form-label">纪律评分</label>
<div class="tr-form-rating" id="tr-refl-discipline-score">
<span class="tr-form-star" data-score="1"></span>
<span class="tr-form-star" data-score="2"></span>
<span class="tr-form-star" data-score="3"></span>
<span class="tr-form-star" data-score="4"></span>
<span class="tr-form-star" data-score="5"></span>
</div>
</div>
<div class="tr-form-group">
<label class="tr-form-label">自由反思</label>
<textarea class="tr-form-textarea" id="tr-refl-free" placeholder="还有什么想记录的?"></textarea>
</div>
<div class="tr-form-group">
<label class="tr-form-label">标签</label>
<div id="tr-refl-edit-tags" class="tr-tag-pool"></div>
</div>
<div class="tr-form-actions">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trReflCloseEditModal()">取消</button>
<button type="submit" class="tr-btn tr-btn-primary">保存反思</button>
</div>
</form>
</div>
</div>
</div>
<!-- 6个统计卡片 -->
<div id="tr-stats-grid" class="tr-stats-grid"></div>
<!-- 当日反思编辑 Modal -->
<div id="tr-refl-daily-modal" class="tr-modal-overlay" onclick="if(event.target===this)trReflCloseDailyModal()">
<div class="tr-modal" style="max-width:640px;">
<div class="tr-modal-header">
<span class="tr-modal-title">当日反思编辑</span>
<button class="tr-modal-close" onclick="trReflCloseDailyModal()"></button>
</div>
<div class="tr-modal-body">
<form id="tr-refl-daily-form">
<div class="tr-form-row">
<div class="tr-form-group">
<label class="tr-form-label">情绪状态</label>
<select class="tr-form-select" id="tr-refl-daily-emotion">
<option value="">请选择</option>
<option value="calm">平静</option>
<option value="confident">自信</option>
<option value="anxious">焦虑</option>
<option value="greedy">贪婪</option>
<option value="fearful">恐惧</option>
<option value="impulsive">冲动</option>
<option value="tired">疲惫</option>
</select>
</div>
<div class="tr-form-group">
<label class="tr-form-label">执行纪律评分</label>
<div class="tr-form-rating" id="tr-refl-daily-discipline-score">
<span class="tr-form-star" data-score="1"></span>
<span class="tr-form-star" data-score="2"></span>
<span class="tr-form-star" data-score="3"></span>
<span class="tr-form-star" data-score="4"></span>
<span class="tr-form-star" data-score="5"></span>
</div>
</div>
</div>
<div class="tr-form-row">
<div class="tr-form-group">
<label class="tr-form-label">总体评价</label>
<select class="tr-form-select" id="tr-refl-daily-overall">
<option value="">请选择</option>
<option value="excellent">优秀</option>
<option value="good">良好</option>
<option value="average">一般</option>
<option value="poor">较差</option>
<option value="bad">糟糕</option>
</select>
</div>
<div class="tr-form-group">
<label class="tr-form-label">市场判断</label>
<select class="tr-form-select" id="tr-refl-daily-market">
<option value="">请选择</option>
<option value="accurate">准确</option>
<option value="basically">基本准确</option>
<option value="deviated">有所偏差</option>
<option value="wrong">错误</option>
</select>
</div>
</div>
<div class="tr-form-group">
<label class="tr-form-label">总结</label>
<textarea class="tr-form-textarea" id="tr-refl-daily-summary" placeholder="今天交易的最大收获是什么?"></textarea>
</div>
<div class="tr-form-group">
<label class="tr-form-label">改进方向</label>
<textarea class="tr-form-textarea" id="tr-refl-daily-improvement" placeholder="明天要注意什么?"></textarea>
</div>
<div class="tr-form-actions">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trReflCloseDailyModal()">取消</button>
<button type="submit" class="tr-btn tr-btn-primary">保存当日反思</button>
</div>
</form>
</div>
</div>
<!-- 账户权益曲线 -->
<div class="tr-chart-box">
<div class="tr-chart-title">账户权益曲线(累计净盈亏 + 每日盈亏)</div>
<div class="tr-chart-container" id="tr-equity-chart"></div>
</div>
<!-- 标签管理 Modal -->
<div id="tr-refl-tags-modal" class="tr-modal-overlay" onclick="if(event.target===this)trReflCloseTagsModal()">
<div class="tr-modal" style="max-width:560px;">
<div class="tr-modal-header">
<span class="tr-modal-title">标签管理</span>
<button class="tr-modal-close" onclick="trReflCloseTagsModal()"></button>
</div>
<div class="tr-modal-body">
<input type="hidden" id="tr-refl-tags-pair-id">
<div id="tr-refl-tags-preset"></div>
<div class="tr-tag-custom">
<input type="text" class="tr-form-input" id="tr-refl-tag-input" placeholder="输入自定义标签">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trReflAddCustomTag()">添加</button>
</div>
<div class="tr-form-actions">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trReflCloseTagsModal()">取消</button>
<button type="button" class="tr-btn tr-btn-primary" onclick="trReflSaveTags()">保存标签</button>
</div>
</div>
</div>
<!-- 品种盈亏分布气泡图 -->
<div class="tr-chart-box">
<div class="tr-chart-title">品种盈亏分布(气泡大小=手续费占比,颜色=净盈亏)</div>
<div class="tr-bubble-container" id="tr-bubble-chart"></div>
</div>
<!-- 经验详情 Modal -->
<div id="tr-exp-detail-modal" class="tr-modal-overlay" onclick="if(event.target===this)trExpCloseDetailModal()">
<div class="tr-modal" style="max-width:600px;">
<div class="tr-modal-header">
<span class="tr-modal-title" id="tr-exp-detail-title">经验详情</span>
<button class="tr-modal-close" onclick="trExpCloseDetailModal()"></button>
</div>
<div class="tr-modal-body">
<div class="tr-exp-detail-meta" id="tr-exp-detail-meta"></div>
<div class="tr-exp-detail-content" id="tr-exp-detail-content"></div>
<div class="tr-exp-detail-tags" id="tr-exp-detail-tags"></div>
<div class="tr-exp-detail-source" id="tr-exp-detail-source"></div>
<div class="tr-form-actions">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trExpCloseDetailModal()">关闭</button>
</div>
</div>
<!-- 每日交易详情表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">每日交易详情(点击日期查看详情)</div>
<table class="tr-table">
<thead>
<tr>
<th>日期</th><th>笔数</th><th>平仓盈亏</th><th>手续费</th>
<th>净盈亏</th><th>盈利笔</th><th>亏损笔</th><th>胜率</th>
<th>最大盈利</th><th>最大亏损</th><th>品种数</th>
</tr>
</thead>
<tbody id="tr-daily-body"></tbody>
</table>
<div id="tr-daily-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-icon">📊</div>
<div class="tr-empty-text">暂无交易数据,请先导入结算单</div>
</div>
</div>
<!-- AI 分析结果 Modal -->
<div id="tr-refl-ai-modal" class="tr-modal-overlay" onclick="if(event.target===this)trReflCloseAIModal()">
<div class="tr-modal" style="max-width:680px;">
<div class="tr-modal-header">
<span class="tr-modal-title">AI 分析结果</span>
<button class="tr-modal-close" onclick="trReflCloseAIModal()"></button>
</div>
<div class="tr-modal-body">
<input type="hidden" id="tr-refl-ai-pair-id">
<div class="tr-ai-version-row">
<label class="tr-ai-version-label" for="tr-refl-ai-version">版本</label>
<select class="tr-form-select" id="tr-refl-ai-version"></select>
</div>
<div class="tr-ai-result-section">
<div class="tr-ai-result-title">综合评价</div>
<div class="tr-ai-result-content" id="tr-refl-ai-overall">-</div>
</div>
<div class="tr-ai-result-section">
<div class="tr-ai-result-title">优点</div>
<ul class="tr-ai-list" id="tr-refl-ai-strengths"></ul>
</div>
<div class="tr-ai-result-section">
<div class="tr-ai-result-title">不足</div>
<ul class="tr-ai-list" id="tr-refl-ai-weaknesses"></ul>
</div>
<div class="tr-ai-result-section">
<div class="tr-ai-result-title">经验提炼建议</div>
<div class="tr-ai-result-content" id="tr-refl-ai-suggestion">-</div>
</div>
<div class="tr-form-actions">
<button type="button" class="tr-btn tr-btn-secondary" onclick="trReflCloseAIModal()">关闭</button>
<button type="button" class="tr-btn tr-btn-primary" onclick="trReflSaveExperience()">保存到经验库</button>
</div>
</div>
<!-- 品种盈亏排行表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">品种盈亏排行(点击查看品种详情)</div>
<table class="tr-table">
<thead>
<tr>
<th>排名</th><th>代码</th><th>名称</th><th>净盈亏</th>
<th>平仓盈亏</th><th>手续费</th><th>胜率</th>
<th>盈亏比</th><th>笔数</th><th>操作</th>
</tr>
</thead>
<tbody id="tr-variety-body"></tbody>
</table>
<div id="tr-variety-empty" class="tr-empty" style="display:none;">
<div class="tr-empty-icon">📋</div>
<div class="tr-empty-text">暂无品种数据</div>
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff

@ -1,539 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<title>交易反思系统 - Demo</title>
<style>
:root {
--bg-page: #F5F5F7;
--bg-card: #FFFFFF;
--text-primary: #1D1D1F;
--text-secondary: #6E6E73;
--text-tertiary: #86868B;
--color-up: #FF3B30;
--color-down: #34C759;
--color-neutral: #FF9F0A;
--color-brand: #007AFF;
--color-ai: #AF52DE;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.08);
--radius-card: 20px;
--radius-btn: 12px;
--radius-pill: 9999px;
--radius-sm: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
background-color: var(--bg-page);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
padding-bottom: 40px;
}
.header {
position: sticky; top: 0; z-index: 100; height: 64px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-bottom: 1px solid rgba(0,0,0,0.05);
display: flex; align-items: center; justify-content: space-between; padding: 0 32px;
}
.logo { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; }
.demo-badge { background: var(--color-up); color: #fff; padding: 4px 12px; border-radius: var(--radius-pill); font-size: 11px; font-weight: 600; }
.container { max-width: 1200px; margin: 0 auto; padding: 24px 32px; }
/* 子 Tab 栏 */
.sub-tabs {
display: flex; gap: 8px; margin-bottom: 24px;
background: var(--bg-card); padding: 8px; border-radius: var(--radius-btn);
box-shadow: var(--shadow-sm); width: fit-content;
}
.sub-tab {
padding: 10px 24px; border-radius: 8px; font-size: 14px; font-weight: 500;
cursor: pointer; transition: all 0.2s; color: var(--text-secondary);
border: none; background: transparent;
}
.sub-tab:hover { background: var(--bg-page); color: var(--text-primary); }
.sub-tab.active { background: var(--color-brand); color: #fff; box-shadow: 0 2px 8px rgba(0,122,255,0.25); }
.view { display: none; }
.view.active { display: block; }
/* ========== 统计分析视图(完全复制原始交易复盘页面) ========== */
.tr-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; }
.tr-toolbar-left, .tr-toolbar-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tr-btn { padding: 10px 20px; border-radius: var(--radius-btn); font-size: 14px; font-weight: 600; cursor: pointer; border: none; display: flex; align-items: center; gap: 8px; transition: all 0.2s; font-family: inherit; }
.tr-btn-primary { background: var(--color-brand); color: #fff; }
.tr-btn-primary:hover { background: #0066d6; box-shadow: 0 4px 10px rgba(0,122,255,0.3); }
.tr-btn-secondary { background: #fff; color: var(--text-secondary); box-shadow: var(--shadow-sm); border: 1px solid rgba(0,0,0,0.05); }
.tr-btn-secondary:hover { background: var(--bg-page); }
.tr-btn-danger { background: rgba(255,59,48,0.1); color: var(--color-up); border: 1px solid rgba(255,59,48,0.2); }
.tr-btn-danger:hover { background: rgba(255,59,48,0.2); }
.tr-btn-link { background: transparent; color: var(--color-brand); padding: 6px 12px; font-size: 13px; font-weight: 500; cursor: pointer; border: none; font-family: inherit; }
.tr-btn-link:hover { text-decoration: underline; }
.tr-date-input { background: #fff; border: 1px solid rgba(0,0,0,0.05); border-radius: var(--radius-btn); padding: 0 14px; height: 40px; font-size: 13px; color: var(--text-primary); box-shadow: var(--shadow-sm); outline: none; font-family: inherit; }
.tr-date-input:focus { border-color: var(--color-brand); box-shadow: 0 0 0 3px rgba(0,122,255,0.15); }
.tr-stats-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; margin-bottom: 24px; }
.tr-stat-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 18px; text-align: center; box-shadow: var(--shadow-sm); transition: transform 0.2s; }
.tr-stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.tr-stat-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 6px; font-weight: 600; }
.tr-stat-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
.tr-stat-value.profit { color: var(--color-down); }
.tr-stat-value.loss { color: var(--color-up); }
.tr-stat-sub { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; }
.tr-chart-box { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; margin-bottom: 24px; box-shadow: var(--shadow-sm); }
.tr-chart-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 16px; }
.tr-chart-container { width: 100%; height: 350px; }
.tr-bubble-container { width: 100%; height: 400px; }
.tr-chart-placeholder { width: 100%; height: 200px; background: var(--bg-page); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; color: var(--text-tertiary); font-size: 14px; }
.tr-tbl-box { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; margin-bottom: 24px; box-shadow: var(--shadow-sm); overflow-x: auto; }
.tr-tbl-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 16px; }
.tr-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.tr-table th { background: var(--bg-page); padding: 12px 8px; text-align: left; font-weight: 600; color: var(--text-tertiary); font-size: 12px; white-space: nowrap; border-bottom: 1px solid rgba(0,0,0,0.06); }
.tr-table td { padding: 12px 8px; border-bottom: 1px solid rgba(0,0,0,0.04); color: var(--text-primary); white-space: nowrap; }
.tr-table tbody tr { transition: background 0.15s; }
.tr-table tbody tr:hover { background: rgba(0,122,255,0.03); }
.tr-pnl-positive { color: var(--color-down); font-weight: 600; }
.tr-pnl-negative { color: var(--color-up); font-weight: 600; }
.tr-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 200px; gap: 12px; }
.tr-empty-icon { font-size: 56px; opacity: 0.3; }
.tr-empty-text { font-size: 15px; color: var(--text-tertiary); }
/* ========== 反思视图 ========== */
.ref-date-selector { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; background: var(--bg-card); padding: 12px 20px; border-radius: var(--radius-btn); box-shadow: var(--shadow-sm); }
.ref-date-btn { background: var(--bg-page); border: none; color: var(--text-primary); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; }
.ref-date-btn:hover { background: #E5E5EA; }
.ref-date-display { font-size: 18px; color: var(--text-primary); font-weight: 600; min-width: 120px; text-align: center; }
.ref-stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.ref-stat-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 18px; box-shadow: var(--shadow-sm); }
.ref-stat-label { font-size: 12px; color: var(--text-tertiary); margin-bottom: 4px; font-weight: 600; }
.ref-stat-value { font-size: 24px; font-weight: 700; }
.ref-stat-value.profit { color: var(--color-down); }
.ref-daily-section { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; margin-bottom: 24px; box-shadow: var(--shadow-sm); border-left: 4px solid var(--color-brand); }
.ref-daily-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.ref-daily-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
.ref-daily-btn { background: var(--color-brand); color: #fff; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 500; }
.ref-daily-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.ref-daily-item { background: var(--bg-page); border-radius: var(--radius-sm); padding: 12px; }
.ref-daily-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; font-weight: 600; }
.ref-daily-value { font-size: 14px; color: var(--text-primary); font-weight: 500; }
.ref-stars { color: var(--color-neutral); }
.ref-pair-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; margin-bottom: 16px; box-shadow: var(--shadow-sm); }
.ref-pair-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.ref-pair-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
.ref-pair-pnl { font-size: 18px; font-weight: 700; }
.ref-pair-pnl.profit { color: var(--color-down); }
.ref-pair-pnl.loss { color: var(--color-up); }
.ref-flow { display: flex; align-items: center; gap: 16px; }
.ref-node { background: var(--bg-page); padding: 12px 20px; border-radius: var(--radius-sm); flex: 1; }
.ref-node-type { font-size: 11px; color: var(--color-brand); margin-bottom: 4px; font-weight: 600; }
.ref-node-detail { font-size: 14px; color: var(--text-primary); }
.ref-arrow { color: var(--color-brand); font-size: 24px; }
.ref-actions { display: flex; gap: 8px; margin-top: 12px; align-items: center; flex-wrap: wrap; }
.ref-status-badge { padding: 6px 12px; border-radius: var(--radius-pill); font-size: 12px; font-weight: 600; }
.ref-status-badge.done { background: rgba(52,199,89,0.1); color: var(--color-down); }
.ref-status-badge.pending { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
.ref-status-badge.none { background: rgba(0,0,0,0.04); color: var(--text-tertiary); }
.ref-action-btn { padding: 6px 14px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.08); background: var(--bg-card); color: var(--text-secondary); cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s; }
.ref-action-btn:hover { background: var(--bg-page); color: var(--text-primary); }
.ref-action-btn.primary { background: var(--color-brand); color: #fff; border-color: var(--color-brand); }
.ref-action-btn.primary:hover { background: #0066d6; }
.ref-unpaired { background: var(--bg-card); border-radius: var(--radius-card); padding: 16px; margin-bottom: 12px; border: 1px dashed rgba(0,0,0,0.12); box-shadow: var(--shadow-sm); }
.ref-unpaired-header { display: flex; align-items: center; justify-content: space-between; }
.ref-unpaired-info { color: var(--text-primary); font-size: 14px; }
.ref-unpaired-sub { color: var(--text-tertiary); font-size: 12px; margin-left: 8px; }
/* ========== 经验库视图 ========== */
.exp-filters { display: flex; gap: 12px; margin-bottom: 20px; }
.exp-search { flex: 1; background: var(--bg-card); border: 1px solid rgba(0,0,0,0.08); border-radius: var(--radius-btn); padding: 10px 16px; font-size: 14px; color: var(--text-primary); outline: none; box-shadow: var(--shadow-sm); }
.exp-search:focus { border-color: var(--color-brand); box-shadow: 0 0 0 3px rgba(0,122,255,0.15); }
.exp-select { background: var(--bg-card); border: 1px solid rgba(0,0,0,0.08); border-radius: var(--radius-btn); padding: 10px 16px; font-size: 14px; color: var(--text-primary); outline: none; box-shadow: var(--shadow-sm); }
.exp-view-switcher { display: flex; gap: 8px; margin-bottom: 16px; }
.exp-view-btn { padding: 8px 16px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.08); background: var(--bg-card); color: var(--text-secondary); cursor: pointer; font-size: 13px; font-weight: 500; }
.exp-view-btn.active { background: var(--color-brand); color: #fff; border-color: var(--color-brand); }
.exp-card { background: var(--bg-card); border-radius: var(--radius-card); padding: 20px; margin-bottom: 16px; box-shadow: var(--shadow-sm); border-left: 4px solid var(--color-neutral); }
.exp-card.lesson { border-left-color: var(--color-brand); }
.exp-card.warning { border-left-color: var(--color-up); }
.exp-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.exp-date { font-size: 12px; color: var(--text-tertiary); }
.exp-type { padding: 4px 10px; border-radius: var(--radius-pill); font-size: 12px; font-weight: 600; }
.exp-type.lesson { background: rgba(0,122,255,0.1); color: var(--color-brand); }
.exp-type.tip { background: rgba(52,199,89,0.1); color: var(--color-down); }
.exp-type.warning { background: rgba(255,59,48,0.1); color: var(--color-up); }
.exp-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; }
.exp-content { font-size: 14px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 12px; }
.exp-tags { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.exp-tag { padding: 4px 10px; border-radius: var(--radius-pill); font-size: 11px; font-weight: 500; background: var(--bg-page); color: var(--text-secondary); }
.exp-actions { display: flex; gap: 8px; }
.exp-action-link { color: var(--color-brand); text-decoration: none; font-size: 13px; font-weight: 500; }
.exp-action-link:hover { text-decoration: underline; }
/* Modal - Apple 风格 */
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 2000; align-items: center; justify-content: center; }
.modal-overlay.show { display: flex; }
.modal { background: var(--bg-card); border-radius: var(--radius-card); width: 90%; max-width: 600px; max-height: 85vh; overflow-y: auto; box-shadow: var(--shadow-lg); animation: modal-in 0.25s ease; }
@keyframes modal-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid rgba(0,0,0,0.06); }
.modal-title { font-size: 18px; font-weight: 700; color: var(--text-primary); }
.modal-close { width: 32px; height: 32px; border-radius: 50%; border: none; background: var(--bg-page); font-size: 16px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: all 0.2s; }
.modal-close:hover { background: #E5E5EA; }
.modal-body { padding: 24px; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; font-size: 13px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 500; }
.form-textarea { width: 100%; background: var(--bg-page); border: 1px solid rgba(0,0,0,0.08); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 14px; color: var(--text-primary); outline: none; min-height: 80px; resize: vertical; font-family: inherit; }
.form-textarea:focus { border-color: var(--color-brand); box-shadow: 0 0 0 3px rgba(0,122,255,0.15); }
.form-select { width: 100%; background: var(--bg-page); border: 1px solid rgba(0,0,0,0.08); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 14px; color: var(--text-primary); outline: none; font-family: inherit; }
.radio-group { display: flex; gap: 16px; }
.radio-label { display: flex; align-items: center; gap: 6px; color: var(--text-primary); cursor: pointer; font-size: 14px; }
.modal-footer { display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; padding-top: 16px; border-top: 1px solid rgba(0,0,0,0.06); }
.btn-cancel { background: var(--bg-page); color: var(--text-secondary); border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-cancel:hover { background: #E5E5EA; }
.btn-save { background: var(--color-brand); color: #fff; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-save:hover { background: #0066d6; }
.btn-analyze { background: var(--color-down); color: #fff; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-analyze:hover { background: #2db84e; }
.tag-selector { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-item { padding: 6px 12px; border-radius: var(--radius-pill); font-size: 12px; cursor: pointer; border: 1px solid rgba(0,0,0,0.08); background: var(--bg-page); color: var(--text-secondary); transition: all 0.2s; }
.tag-item:hover { background: #E5E5EA; }
.tag-item.selected { background: var(--color-brand); color: #fff; border-color: var(--color-brand); }
.tag-category { width: 100%; color: var(--text-tertiary); font-size: 11px; font-weight: 600; margin-top: 8px; }
.ai-box { background: linear-gradient(135deg, #f0f7ff 0%, #e6f4ff 100%); border: 1px solid #bae0ff; border-radius: var(--radius-sm); padding: 20px; margin-top: 16px; }
.ai-box-title { font-size: 16px; font-weight: 600; color: #0958d9; margin-bottom: 12px; }
.ai-box-content { font-size: 14px; color: var(--text-primary); line-height: 1.8; margin-bottom: 12px; }
@media (max-width: 1200px) { .tr-stats-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 768px) { .tr-stats-grid { grid-template-columns: repeat(2, 1fr); } }
</style>
</head>
<body>
<header class="header">
<div class="logo">◈ 期货智析 - 交易复盘</div>
<span class="demo-badge">DEMO</span>
</header>
<div class="container">
<!-- 子 Tab 栏 -->
<div class="sub-tabs">
<button class="sub-tab active" onclick="switchSubTab('stats')">统计分析</button>
<button class="sub-tab" onclick="switchSubTab('reflection')">交易反思</button>
<button class="sub-tab" onclick="switchSubTab('experience')">经验库</button>
</div>
<!-- ========== 统计分析视图(完整保留所有原始功能) ========== -->
<div id="stats-view" class="view active">
<!-- 工具栏 -->
<div class="tr-toolbar">
<div class="tr-toolbar-left">
<input type="file" id="tr-file-input" accept=".xls,.xlsx" style="display:none" />
<input type="file" id="tr-batch-file-input" accept=".xls,.xlsx" multiple style="display:none" />
<button id="tr-btn-import" class="tr-btn tr-btn-primary"><span>📥</span><span>导入结算单</span></button>
<button id="tr-btn-batch-import" class="tr-btn tr-btn-primary" title="同时导入多个结算单文件"><span>📂</span><span>批量导入</span></button>
<input type="date" id="tr-start-date" class="tr-date-input" title="开始日期" />
<span style="color:var(--text-tertiary);font-size:13px;">~</span>
<input type="date" id="tr-end-date" class="tr-date-input" title="结束日期" />
<button id="tr-btn-query" class="tr-btn tr-btn-secondary">查询</button>
</div>
<div class="tr-toolbar-right">
<button id="tr-btn-delete-date" class="tr-btn tr-btn-danger" title="删除当前查询日期的所有交易记录"><span>🗑️</span><span>删除当日</span></button>
<button id="tr-btn-batches" class="tr-btn tr-btn-secondary"><span>📋</span><span>批次管理</span></button>
</div>
</div>
<!-- 6个统计卡片 -->
<div id="tr-stats-grid" class="tr-stats-grid">
<div class="tr-stat-card"><div class="tr-stat-label">总盈亏</div><div class="tr-stat-value profit">+¥3,200</div><div class="tr-stat-sub">平仓盈亏</div></div>
<div class="tr-stat-card"><div class="tr-stat-label">净盈亏</div><div class="tr-stat-value profit">+¥2,800</div><div class="tr-stat-sub">扣除手续费</div></div>
<div class="tr-stat-card"><div class="tr-stat-label">胜率</div><div class="tr-stat-value">75%</div><div class="tr-stat-sub">3盈1亏</div></div>
<div class="tr-stat-card"><div class="tr-stat-label">盈亏比</div><div class="tr-stat-value">1.5</div><div class="tr-stat-sub">平均盈利/平均亏损</div></div>
<div class="tr-stat-card"><div class="tr-stat-label">交易笔数</div><div class="tr-stat-value">4</div><div class="tr-stat-sub">期货+期权</div></div>
<div class="tr-stat-card"><div class="tr-stat-label">最大回撤</div><div class="tr-stat-value loss">-¥800</div><div class="tr-stat-sub">单笔最大亏损</div></div>
</div>
<!-- 账户权益曲线 -->
<div class="tr-chart-box">
<div class="tr-chart-title">账户权益曲线(累计净盈亏 + 每日盈亏)</div>
<div class="tr-chart-placeholder">📈 ECharts 图表区域(现有功能)</div>
</div>
<!-- 品种盈亏分布气泡图 -->
<div class="tr-chart-box">
<div class="tr-chart-title">品种盈亏分布(气泡大小=手续费占比,颜色=净盈亏)</div>
<div class="tr-bubble-container" style="height:300px;background:var(--bg-page);border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;color:var(--text-tertiary);font-size:14px;">🫧 ECharts 气泡图区域(现有功能)</div>
</div>
<!-- 每日交易详情表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">每日交易详情(点击日期查看详情)</div>
<table class="tr-table">
<thead>
<tr><th>日期</th><th>笔数</th><th>平仓盈亏</th><th>手续费</th><th>净盈亏</th><th>盈利笔</th><th>亏损笔</th><th>胜率</th><th>最大盈利</th><th>最大亏损</th><th>品种数</th><th>操作</th></tr>
</thead>
<tbody>
<tr>
<td>2026-07-05</td><td>4</td><td class="tr-pnl-positive">+¥3,600</td><td>-¥400</td>
<td class="tr-pnl-positive">+¥3,200</td><td>3</td><td>1</td><td>75%</td>
<td class="tr-pnl-positive">+¥2,800</td><td class="tr-pnl-negative">-¥800</td><td>3</td>
<td><button class="tr-btn tr-btn-link" onclick="showDailyDetail()">查看详情</button></td>
</tr>
<tr>
<td>2026-07-04</td><td>3</td><td class="tr-pnl-positive">+¥1,500</td><td>-¥300</td>
<td class="tr-pnl-positive">+¥1,200</td><td>2</td><td>1</td><td>67%</td>
<td class="tr-pnl-positive">+¥1,000</td><td class="tr-pnl-negative">-¥500</td><td>2</td>
<td><button class="tr-btn tr-btn-link" onclick="showDailyDetail()">查看详情</button></td>
</tr>
<tr>
<td>2026-07-03</td><td>2</td><td class="tr-pnl-negative">-¥500</td><td>-¥200</td>
<td class="tr-pnl-negative">-¥700</td><td>1</td><td>1</td><td>50%</td>
<td class="tr-pnl-positive">+¥300</td><td class="tr-pnl-negative">-¥800</td><td>2</td>
<td><button class="tr-btn tr-btn-link" onclick="showDailyDetail()">查看详情</button></td>
</tr>
</tbody>
</table>
</div>
<!-- 品种盈亏排行表 -->
<div class="tr-tbl-box">
<div class="tr-tbl-title">品种盈亏排行(点击查看品种详情)</div>
<table class="tr-table">
<thead>
<tr><th>排名</th><th>代码</th><th>名称</th><th>净盈亏</th><th>平仓盈亏</th><th>手续费</th><th>胜率</th><th>盈亏比</th><th>笔数</th><th>操作</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>IF</td><td>沪深300</td><td class="tr-pnl-positive">+¥2,800</td><td>+¥3,000</td><td>-¥200</td><td>100%</td><td>--</td><td>1</td><td><button class="tr-btn tr-btn-link">查看详情</button></td></tr>
<tr><td>2</td><td>AG</td><td>沪银</td><td class="tr-pnl-positive">+¥1,200</td><td>+¥1,400</td><td>-¥200</td><td>100%</td><td>--</td><td>1</td><td><button class="tr-btn tr-btn-link">查看详情</button></td></tr>
<tr><td>3</td><td>RB</td><td>螺纹钢</td><td class="tr-pnl-negative">-¥800</td><td>-¥600</td><td>-¥200</td><td>0%</td><td>--</td><td>1</td><td><button class="tr-btn tr-btn-link">查看详情</button></td></tr>
<tr><td>4</td><td>CU</td><td>沪铜</td><td class="tr-pnl-negative">-¥400</td><td>-¥200</td><td>-¥200</td><td>0%</td><td>--</td><td>1</td><td><button class="tr-btn tr-btn-link">查看详情</button></td></tr>
</tbody>
</table>
</div>
</div>
<!-- ========== 交易反思视图 ========== -->
<div id="reflection-view" class="view">
<div class="ref-date-selector">
<button class="ref-date-btn" onclick="prevDay()">← 前一天</button>
<div class="ref-date-display" id="currentDate">2026-07-05</div>
<button class="ref-date-btn" onclick="nextDay()">后一天 →</button>
</div>
<div class="ref-stats-row">
<div class="ref-stat-card"><div class="ref-stat-label">总盈亏</div><div class="ref-stat-value profit">+¥3,200</div></div>
<div class="ref-stat-card"><div class="ref-stat-label">交易笔数</div><div class="ref-stat-value">4 笔</div></div>
<div class="ref-stat-card"><div class="ref-stat-label">胜率</div><div class="ref-stat-value">75%</div></div>
<div class="ref-stat-card"><div class="ref-stat-label">已反思</div><div class="ref-stat-value">2/4</div></div>
</div>
<div class="ref-daily-section">
<div class="ref-daily-header">
<div class="ref-daily-title">当日反思</div>
<button class="ref-daily-btn" onclick="openDailyReflection()">编辑</button>
</div>
<div class="ref-daily-grid">
<div class="ref-daily-item"><div class="ref-daily-label">情绪状态</div><div class="ref-daily-value">平静</div></div>
<div class="ref-daily-item"><div class="ref-daily-label">执行纪律</div><div class="ref-daily-value ref-stars">★★★★☆</div></div>
<div class="ref-daily-item"><div class="ref-daily-label">总体评价</div><div class="ref-daily-value ref-stars">★★★★☆</div></div>
<div class="ref-daily-item"><div class="ref-daily-label">市场判断</div><div class="ref-daily-value">震荡偏多</div></div>
</div>
</div>
<div class="ref-pair-card">
<div class="ref-pair-header"><div class="ref-pair-title">AG2608 多头</div><div class="ref-pair-pnl profit">+¥1,200</div></div>
<div class="ref-flow">
<div class="ref-node"><div class="ref-node-type">开仓</div><div class="ref-node-detail">09:30 @ 7,850 × 2手</div></div>
<div class="ref-arrow"></div>
<div class="ref-node"><div class="ref-node-type">平仓</div><div class="ref-node-detail">14:20 @ 7,910 × 2手</div></div>
</div>
<div class="ref-actions">
<span class="ref-status-badge done">反思 ✓</span>
<span class="ref-status-badge done">AI分析 ✓</span>
<button class="ref-action-btn" onclick="openTradeTags()">标签 (2)</button>
<button class="ref-action-btn primary" onclick="showAIResult()">查看分析</button>
</div>
</div>
<div class="ref-pair-card">
<div class="ref-pair-header"><div class="ref-pair-title">RB2610 空头</div><div class="ref-pair-pnl loss">-¥800</div></div>
<div class="ref-flow">
<div class="ref-node"><div class="ref-node-type">开仓</div><div class="ref-node-detail">10:15 @ 3,680 × 3手</div></div>
<div class="ref-arrow"></div>
<div class="ref-node"><div class="ref-node-type">平仓</div><div class="ref-node-detail">11:30 @ 3,710 × 3手</div></div>
</div>
<div class="ref-actions">
<span class="ref-status-badge done">反思 ✓</span>
<span class="ref-status-badge none">未分析</span>
<button class="ref-action-btn" onclick="openTradeTags()">标签 (1)</button>
<button class="ref-action-btn primary" onclick="triggerReanalyze()">重新AI分析</button>
</div>
</div>
<div class="ref-pair-card">
<div class="ref-pair-header"><div class="ref-pair-title">IF2609 多头</div><div class="ref-pair-pnl profit">+¥2,800</div></div>
<div class="ref-flow">
<div class="ref-node"><div class="ref-node-type">开仓</div><div class="ref-node-detail">09:45 @ 4,120 × 1手</div></div>
<div class="ref-arrow"></div>
<div class="ref-node"><div class="ref-node-type">平仓</div><div class="ref-node-detail">15:00 @ 4,190 × 1手</div></div>
</div>
<div class="ref-actions">
<span class="ref-status-badge pending">待分析</span>
<button class="ref-action-btn" onclick="openReflection()">写反思</button>
<button class="ref-action-btn" onclick="openTradeTags()">标签</button>
</div>
</div>
<div class="ref-unpaired">
<div class="ref-unpaired-header">
<div><span class="ref-unpaired-info">CU2609 买开 09:45 @ 78,500 × 1手</span><span class="ref-unpaired-sub">未平仓</span></div>
<div style="display:flex;gap:8px;">
<button class="ref-action-btn" onclick="openReflection()">写反思</button>
<button class="ref-action-btn">手动配对</button>
</div>
</div>
</div>
</div>
<!-- ========== 经验库视图 ========== -->
<div id="experience-view" class="view">
<div class="exp-filters">
<input type="text" class="exp-search" placeholder="搜索经验...">
<select class="exp-select"><option value="">全部标签</option><option>追涨杀跌</option><option>严格执行计划</option><option>违反止损</option></select>
<select class="exp-select"><option value="">全部类型</option><option value="lesson">经验</option><option value="tip">技巧</option><option value="warning">警告</option></select>
</div>
<div class="exp-view-switcher">
<button class="exp-view-btn active">列表视图</button>
<button class="exp-view-btn">时间线视图</button>
<button class="exp-view-btn">分类视图</button>
</div>
<div class="exp-card warning">
<div class="exp-meta"><span class="exp-date">2026-07-05</span><span class="exp-type warning">⚠️ 警告</span></div>
<div class="exp-title">追涨杀跌导致亏损 - AG2608</div>
<div class="exp-content">在突破不明显的情况下追多,未设止损,持仓期间市场反转导致亏损。应等待明确突破信号后再入场,并严格执行止损。</div>
<div class="exp-tags"><span class="exp-tag">追涨杀跌</span><span class="exp-tag">违反止损</span></div>
<div class="exp-actions"><a href="#" class="exp-action-link">查看原交易</a><a href="#" class="exp-action-link">编辑</a></div>
</div>
<div class="exp-card lesson">
<div class="exp-meta"><span class="exp-date">2026-07-04</span><span class="exp-type lesson">💡 经验</span></div>
<div class="exp-title">严格执行计划带来稳定收益 - RB2610</div>
<div class="exp-content">按计划入场,按止损出场,避免情绪干扰。即使最终止损,也保持了良好的交易纪律,长期来看这是正确的做法。</div>
<div class="exp-tags"><span class="exp-tag">严格执行计划</span><span class="exp-tag">趋势跟踪</span></div>
<div class="exp-actions"><a href="#" class="exp-action-link">查看原交易</a><a href="#" class="exp-action-link">编辑</a></div>
</div>
<div class="exp-card lesson">
<div class="exp-meta"><span class="exp-date">2026-07-03</span><span class="exp-type tip">✨ 技巧</span></div>
<div class="exp-title">回调入场时机把握 - IF2609</div>
<div class="exp-content">在上升趋势中等待回调至 MA20 附近入场,胜率显著提高。配合 RSI 超卖信号,入场点更加精准。</div>
<div class="exp-tags"><span class="exp-tag">回调交易</span><span class="exp-tag">趋势跟踪</span></div>
<div class="exp-actions"><a href="#" class="exp-action-link">查看原交易</a><a href="#" class="exp-action-link">编辑</a></div>
</div>
</div>
</div>
<!-- 反思编辑 Modal -->
<div class="modal-overlay" id="reflectionModal">
<div class="modal">
<div class="modal-header"><div class="modal-title">反思编辑 - AG2608 多头</div><button class="modal-close" onclick="closeModal('reflectionModal')"></button></div>
<div class="modal-body">
<div class="form-group"><label class="form-label">入场理由</label><textarea class="form-textarea" placeholder="为什么选择在这个时机入场?">看到突破 7800 阻力位,配合成交量放大</textarea></div>
<div class="form-group"><label class="form-label">入场时机</label><div class="radio-group"><label class="radio-label"><input type="radio" name="entryTiming"> 良好</label><label class="radio-label"><input type="radio" name="entryTiming" checked> 一般</label><label class="radio-label"><input type="radio" name="entryTiming"> 较差</label></div></div>
<div class="form-group"><label class="form-label">仓位管理反思</label><textarea class="form-textarea">仓位 2 手合理,但未预留加仓空间</textarea></div>
<div class="form-group"><label class="form-label">出场理由</label><textarea class="form-textarea">达到目标位 7910按计划平仓</textarea></div>
<div class="form-group"><label class="form-label">出场时机</label><div class="radio-group"><label class="radio-label"><input type="radio" name="exitTiming" checked> 良好</label><label class="radio-label"><input type="radio" name="exitTiming"> 一般</label><label class="radio-label"><input type="radio" name="exitTiming"> 较差</label></div></div>
<div class="form-group"><label class="form-label">纪律评分</label><div class="radio-group"><label class="radio-label"><input type="radio" name="discipline"></label><label class="radio-label"><input type="radio" name="discipline"> ☆☆</label><label class="radio-label"><input type="radio" name="discipline"> ☆☆☆</label><label class="radio-label"><input type="radio" name="discipline" checked> ★★★★</label><label class="radio-label"><input type="radio" name="discipline"> ★★★★★</label></div></div>
<div class="form-group"><label class="form-label">自由反思</label><textarea class="form-textarea">整体执行较好,但入场稍显急躁</textarea></div>
<div class="form-group"><label class="form-label">标签</label><div class="tag-selector"><div class="tag-category">操作类</div><span class="tag-item" onclick="toggleTag(this)">追涨杀跌</span><span class="tag-item" onclick="toggleTag(this)">逆市操作</span><div class="tag-category">纪律类</div><span class="tag-item selected" onclick="toggleTag(this)">严格执行计划</span><span class="tag-item" onclick="toggleTag(this)">违反止损</span><div class="tag-category">技术类</div><span class="tag-item selected" onclick="toggleTag(this)">突破交易</span></div></div>
</div>
<div class="modal-footer"><button class="btn-cancel" onclick="closeModal('reflectionModal')">取消</button><button class="btn-save" onclick="saveReflection()">保存</button><button class="btn-analyze" onclick="saveAndAnalyze()">保存并 AI 分析</button></div>
</div>
</div>
<!-- 当日反思 Modal -->
<div class="modal-overlay" id="dailyReflectionModal">
<div class="modal">
<div class="modal-header"><div class="modal-title">当日反思 - 2026-07-05</div><button class="modal-close" onclick="closeModal('dailyReflectionModal')"></button></div>
<div class="modal-body">
<div class="form-group"><label class="form-label">情绪状态</label><select class="form-select"><option>平静</option><option>兴奋</option><option>焦虑</option></select></div>
<div class="form-group"><label class="form-label">市场判断</label><textarea class="form-textarea">震荡偏多,上午突破后回落,下午企稳</textarea></div>
<div class="form-group"><label class="form-label">执行纪律</label><div class="radio-group"><label class="radio-label"><input type="radio" name="dailyDiscipline"></label><label class="radio-label"><input type="radio" name="dailyDiscipline" checked> ★★★★</label><label class="radio-label"><input type="radio" name="dailyDiscipline"> ★★★★★</label></div></div>
<div class="form-group"><label class="form-label">总结</label><textarea class="form-textarea">今天整体表现不错,严格执行了计划</textarea></div>
<div class="form-group"><label class="form-label">改进方向</label><textarea class="form-textarea">入场时机可以再耐心一些</textarea></div>
</div>
<div class="modal-footer"><button class="btn-cancel" onclick="closeModal('dailyReflectionModal')">取消</button><button class="btn-save" onclick="saveDailyReflection()">保存</button></div>
</div>
</div>
<!-- AI 分析结果 Modal -->
<div class="modal-overlay" id="aiResultModal">
<div class="modal">
<div class="modal-header"><div class="modal-title">AI 分析结果 - AG2608 多头 (版本 1)</div><button class="modal-close" onclick="closeModal('aiResultModal')"></button></div>
<div class="modal-body">
<div class="ai-box"><div class="ai-box-title">综合评价</div><div class="ai-box-content">这笔交易整体执行良好。入场时机选择在突破 7800 阻力位后,配合成交量放大,符合突破交易的基本原则。</div></div>
<div class="ai-box"><div class="ai-box-title">✓ 优点</div><ul><li>入场信号明确(突破+放量)</li><li>严格执行目标位出场</li></ul></div>
<div class="ai-box"><div class="ai-box-title">✗ 不足</div><ul><li>入场稍显急躁,可等待更多确认</li></ul></div>
<div style="margin-top:16px;padding-top:16px;border-top:1px solid rgba(0,0,0,0.06);">
<div class="ai-box-title" style="color:var(--color-down);">经验提炼建议</div>
<div class="exp-card lesson" style="margin-top:12px;">
<div class="exp-title">突破交易确认信号的重要性</div>
<div class="exp-content">在突破交易中,等待价格突破后回踩确认再入场,可以显著提高胜率。</div>
<div class="exp-tags"><span class="exp-tag">突破交易</span><span class="exp-tag">严格执行计划</span></div>
<div class="exp-actions" style="margin-top:8px;"><button class="btn-save" onclick="saveExperience()">保存到经验库</button><button class="btn-cancel">编辑</button></div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn-cancel" onclick="closeModal('aiResultModal')">关闭</button></div>
</div>
</div>
<script>
function switchSubTab(tab) {
document.querySelectorAll('.sub-tab').forEach((t, i) => {
t.classList.toggle('active', ['stats','reflection','experience'][i] === tab);
});
const map = { 'stats': 'stats-view', 'reflection': 'reflection-view', 'experience': 'experience-view' };
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === map[tab]));
}
let currentDate = new Date('2026-07-05');
function formatDate(d) { return d.toISOString().split('T')[0]; }
function prevDay() { currentDate.setDate(currentDate.getDate() - 1); document.getElementById('currentDate').textContent = formatDate(currentDate); }
function nextDay() { currentDate.setDate(currentDate.getDate() + 1); document.getElementById('currentDate').textContent = formatDate(currentDate); }
function openModal(id) { document.getElementById(id).classList.add('show'); }
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
function openReflection() { openModal('reflectionModal'); }
function openDailyReflection() { openModal('dailyReflectionModal'); }
function openTradeTags() { alert('Demo: 打开标签管理弹窗'); }
function showAIResult() { openModal('aiResultModal'); }
function showDailyDetail() { alert('Demo: 打开每日详情弹窗(现有功能)'); }
function toggleTag(el) { el.classList.toggle('selected'); }
function saveReflection() { alert('Demo: 反思已保存'); closeModal('reflectionModal'); }
function saveAndAnalyze() { alert('Demo: 反思已保存,正在触发 AI 分析...'); closeModal('reflectionModal'); setTimeout(() => { alert('Demo: AI 分析完成!'); openModal('aiResultModal'); }, 1500); }
function saveDailyReflection() { alert('Demo: 当日反思已保存'); closeModal('dailyReflectionModal'); }
function triggerReanalyze() { alert('Demo: 正在重新 AI 分析...'); setTimeout(() => { alert('Demo: AI 分析完成!版本 2 已保存。'); }, 1500); }
function saveExperience() { alert('Demo: 经验已保存到经验库'); }
</script>
</body>
</html>

@ -1,14 +1,14 @@
{
"models": [
{
"api_key": "sk-sp-7695dcf1428841d3808dd3fc6c3440d5",
"enabled": true,
"model_name": "qwen3.6-plus",
"provider": "bailian",
"api_key": "sk-sp-51d0695ab1114470b913146d21baf68f",
"api_base": "https://coding.dashscope.aliyuncs.com/v1",
"model_id": "qwen3.6-plus",
"provider": "bailian",
"temperature": 0.7,
"max_tokens": 2000,
"model_name": "qwen3.6-plus",
"temperature": 0.7
"enabled": true
}
],
"active_model": "bailian",

Binary file not shown.

@ -1,322 +0,0 @@
# 期货智析futures_analysis模块 — 功能与逻辑文档
## 一、模块概述
期货智析模块是一个集**数据采集、技术分析、AI智能分析、复盘计划生成、交易复盘**于一体的期货分析平台。采用前后端分离架构,前端为 SPA 单页应用,后端提供 RESTful API。
---
## 二、文件结构
### 后端
| 文件 | 说明 |
|------|------|
| `app/api/futures_analysis.py` | 主 API 路由(~970行前缀 `/api/v1/futures` |
| `app/api/review_plan.py` | 复盘计划 API前缀 `/api/v1/review` |
| `app/api/trade_review.py` | 交易复盘 API前缀 `/api/v1/trade-review` |
| `app/analysis_models.py` | 数据模型定义(~325行14 个 ORM 模型类 |
| `app/analysis_db.py` | 独立数据库初始化SQLite + MySQL 动态切换) |
| `app/services/ai_analysis.py` | AI 四维联合分析服务(~513行 |
| `app/services/cache.py` | 缓存服务SQLite/Redis/MySQL 三级存储) |
| `app/services/collector.py` | 数据采集服务(封装底层采集脚本) |
| `app/services/plan_generator.py` | 复盘计划生成器 |
| `app/services/trade_parser.py` | 结算单解析器 |
| `app/config_store.py` | 统一配置存储MySQL 优先 + JSON 文件兜底) |
### 前端
| 文件 | 说明 |
|------|------|
| `app/static/futures_analysis.html` | 主页面(~1232行 |
| `app/static/futures_analysis.js` | 前端逻辑(~3855行 |
| `app/static/futures_analysis.css` | 样式表(~3124行 |
### 数据库
| 文件 | 说明 |
|------|------|
| `data/futures_analysis.db` | SQLite 数据库(兜底存储) |
---
## 三、整体分析逻辑流程
### 3.1 数据流全链路
```
数据采集(collector.py) → 缓存存储(cache.py) → API接口(futures_analysis.py) → 前端渲染(JS)
AI分析服务(ai_analysis.py)
AI结果缓存(AIAnalysisCache表)
```
### 3.2 品种列表加载流程
1. **前端** `loadFuturesList()` 调用 `GET /api/v1/futures/list`
2. **后端** `get_futures_list()` 执行:
- 从 `config_store` 加载品种配置(品种名称 → 合约代码映射)
- 遍历每个品种,调用 `get_cached_data()` 从缓存获取 K 线数据
- 对每个品种计算:
- 当前价格、涨跌幅
- 操作建议(`_get_suggestion`
- 多周期趋势(`_get_period_trends`
- 成功率(`_calc_success_rate`
- 趋势评分(`_calc_trend_score`
- 压力支撑位Pivot Point 公式)
3. **前端** 渲染品种卡片网格,同时异步调用 `loadAllAIAnalysis()` 分批加载每个品种的 AI 分析结果
### 3.3 单品种详情分析流程
1. **前端** `showDetailView(symbol)` 切换到详情视图
2. 并行执行三个请求:
- `loadFuturesDetail(symbol)``GET /api/v1/futures/detail/{symbol}` — 详细行情和技术指标
- `loadKlineData(symbol, period)``GET /api/v1/futures/kline/{symbol}` — K 线数据(图表用)
- `loadHistoryListForAnalysis(symbol)``GET /api/v1/futures/ai-analysis/{symbol}/history` — AI 分析历史
3. **后端** `get_futures_detail()` 计算完整技术指标:
- **MACD**`_calc_macd`EMA12/EMA26 → DIF/DEA → 金叉/死叉信号
- **RSI**`_calc_rsi`14 周期 RSI → 超买/超卖/正常判断
- **布林带**`_calc_boll`20 周期 MA ± 2 倍标准差 → 上轨/中轨/下轨信号
- **KDJ**`_calc_kdj`9 周期 RSV → K/D 值 → 偏多/偏空/中性
- **Pivot Point**PP = (H+L+C)/3R1/R2/S1/S2 标准公式
4. **前端** 使用 ECharts 渲染 K 线图(含 MA5/MA10/MA20 均线、成交量柱、MACD 子图)
### 3.4 AI 智能分析流程(核心)
1. **前端** `runAIAnalysis()` 调用 `POST /api/v1/futures/ai-analysis/{symbol}`
2. **后端** `AIFuturesAnalyzer.analyze()` 执行完整流程:
```
步骤1: get_active_model()
└─ 从配置中获取当前激活的 AI 模型(支持 OpenAI 兼容接口)
步骤2: prepare_multi_period_data()
└─ 准备 5 个周期5min/15min/30min/60min/daily的 K 线数据
└─ 每个周期计算技术指标MA10/MA20/MACD/KDJ/量比等)
步骤3: AIAnalysisPrompt.build_prompt()
└─ 构建"四维联合判断分析法(4D-XV)"提示词
└─ 系统角色20年经验的资深金融分析师
└─ 要求输出 JSON 格式的四维分析结果
步骤4: call_ai_model()
└─ 通过 HTTP POST 调用 AI 模型OpenAI 兼容格式180秒超时
步骤5: parse_ai_response()
└─ 正则提取 JSON 响应
步骤6: save_analysis_cache()
└─ 保存结果到 AIAnalysisCache 表,附带 K 线时间戳
```
3. **智能缓存策略**`should_reanalyze`
- 分析超过 15 分钟 → 重新分析
- K 线数据时间戳比分析时间新 → 重新分析
- 否则返回缓存结果
4. **前端** `displayAIAnalysisResult()` 渲染分析结果,并通过 `syncAIToPanels()` 同步数据到各个面板
### 3.5 AI 分析输出结构4D-XV 四维联合分析)
AI 分析返回的 JSON 包含以下字段:
| 字段 | 说明 |
|------|------|
| `summary` | 分析摘要 |
| `four_dimensional` | 四维分析MACD趋势、成交量资金、KDJ时机、多周期方向 |
| `kdj_diagnosis` | KDJ 诊断详情 |
| `pivot_points` | 压力支撑位 |
| `red_lines_check` | 红线审查(不可违反的交易纪律) |
| `discipline_score` | 纪律评分 |
| `trading_suggestion` | 交易建议(方向、置信度、入场区间、止损、仓位) |
| `scenario_plans` | 情景预案(突破/震荡/反转/消息影响) |
| `risk_warnings` | 风险提示 |
| `experience_lessons` | 经验教训 |
---
## 四、全部功能清单
### 4.1 品种分析(主页面)
| 功能 | 描述 |
|------|------|
| 品种列表展示 | 卡片网格展示所有期货品种,含价格、涨跌、建议、成功率、趋势评分、压力支撑位 |
| 分类筛选 | 全部 / 能源 / 金属 / 农产品 / 金融 四大分类 |
| 搜索 | 按品种名称或合约代码搜索 |
| 趋势筛选 | 点击统计卡片按上涨 / 下跌 / 震荡筛选 |
| 数据刷新 | 单个品种刷新或全部刷新(异步后台执行,进度轮询) |
| 自选品种 | 添加/取消自选,独立自选页面 |
| AI 批量分析 | 一键对所有合约进行 AI 分析(分批 3 个并发2 秒间隔) |
### 4.2 详情分析页
| 功能 | 描述 |
|------|------|
| 行情概览 | 品种名称、合约代码、当前价、涨跌、开/高/低/量 |
| K 线图表 | ECharts 渲染,支持 5M/15M/30M/1H 四个周期切换,含 MA 均线、成交量、MACD 子图 |
| 技术指标面板 | MACD金叉/死叉、RSI超买/超卖、布林带、KDJ |
| 关键点位 | Pivot Point 计算的压力位(R1/R2)、支撑位(S1/S2)、中枢(PP) |
| 多周期趋势 | 5/15/30/60 分钟四个周期的趋势方向 |
| AI 思维分析 | 四维联合分析结果展示(摘要、方向、置信度、入场区间、止损、仓位) |
| AI 分析详情弹窗 | 完整的 4D-XV 信号表、红线审查、纪律评分、情景预案、经验教训 |
| 历史分析记录 | 展示该品种的历史 AI 分析记录列表,可点击查看完整报告 |
| 情景预案 | 突破/震荡/反转/消息影响四种情景的概率和应对策略 |
### 4.3 复盘计划V2
| 功能 | 描述 |
|------|------|
| 生成复盘报告 | 选择日期,调用 `POST /api/v1/review/generate` 自动生成 |
| 核心结论 | Hero 区域展示日期、核心结论、数据基准 |
| 三档分类 | 绿色(交易机会)/ 黄色(重点关注)/ 红色(规避) |
| 交易机会卡片 | 含方向、入场区间、止损、目标位、触发条件、四维评分条 |
| 全品种排名表 | 支持按综合评分/活跃度/量比/方向排序 |
| 品种详情弹窗 | 评分明细、多周期趋势、枢轴点位、交易计划 |
| 板块热度 | 各板块均分、趋势、龙头品种 |
| 风险提示 | 全局风险提示横幅 |
### 4.4 交易复盘
| 功能 | 描述 |
|------|------|
| 结算单导入 | 支持单个/批量导入 .xls/.xlsx 期货结算单 |
| 去重校验 | 按交易日+品种+时间+价格逐条去重 |
| 6 维统计卡片 | 总盈亏、净盈亏、胜率/盈亏比、交易笔数、最大回撤、日均盈亏 |
| 账户权益曲线 | ECharts 折线+柱状图(累计净盈亏 + 每日盈亏) |
| 品种盈亏气泡图 | 气泡大小=手续费占比,颜色=净盈亏 |
| 每日交易详情表 | 点击日期查看详情弹窗(含 AI 诊断报告、品种盈亏统计、交易流水) |
| 品种盈亏排行表 | 含盈亏比异步计算 |
| 品种详情弹窗 | K 线走势(多周期切换)+ 该品种交易记录 |
| 交易详情弹窗 | 单笔交易详情 + K 线买卖点标注 + AI 交易分析 |
| 批次管理 | 查看/删除导入批次 |
| 按日期删除 | 删除指定日期的所有交易记录 |
---
## 五、数据模型14 个 ORM 类)
| 模型类 | 表名 | 用途 |
|--------|------|------|
| `FuturesAnalysis` | `futures_analysis` | 期货分析报告(建议、指标、评分、点位) |
| `WatchedSymbol` | `watched_symbols` | 用户关注品种 |
| `AIModelConfig` | `ai_model_configs` | AI 模型配置(提供商、密钥、参数) |
| `AnalysisSettings` | `analysis_settings` | 分析设置(单例 JSON 配置) |
| `AIAnalysisCache` | `ai_analysis_cache` | AI 分析结果缓存(含 K 线时间戳用于智能刷新) |
| `ReviewDate` | `review_dates` | 复盘日期V1 |
| `SymbolRanking` | `symbol_rankings` | 品种排名V1 |
| `TradingPlan` | `trading_plans` | 交易计划V1 |
| `SymbolScoreV2` | `symbol_scores_v2` | V2 品种多维度评分5维度+综合+关键点位+方向标签) |
| `TradingPlanV2` | `trading_plans_v2` | V2 交易计划(入场/止损/目标/触发条件) |
| `SectorHeat` | `sector_heat` | 板块热度(均分/趋势/龙头/成员) |
| `ReviewPlanV2` | `review_plans_v2` | V2 复盘计划总表(元数据+统计) |
| `TradeRecord` | `trade_records` | 交易记录(从结算单导入的逐笔明细) |
| `TradeImportBatch` | `trade_import_batches` | 导入批次元信息 |
---
## 六、组件连接关系
```
+-------------------+
| 前端 SPA 页面 |
| futures_analysis |
| .html / .js / .css|
+---------+---------+
|
+-------------------+-------------------+
| | |
/api/v1/futures/* /api/v1/review/* /api/v1/trade-review/*
| | |
+---------+--------+ +------+-------+ +--------+--------+
|futures_analysis.py| |review_plan.py| | trade_review.py |
+----+------+------+ +------+-------+ +--------+--------+
| | | |
| +-------+--------+----------+--------+
| | |
+----+----+ +------+-------+ +--------+--------+
|cache.py | |plan_generator| | trade_parser |
+----+----+ +--------------+ +-----------------+
|
+----+----+ +------------------+
|collector| --> | futures_data_ |
| .py | | collector(脚本) |
+---------+ +------------------+
|
+----+----+ +------------------+
| storage | --> | Redis / MySQL / |
| Manager | | SQLite(兜底) |
+---------+ +------------------+
|
+----+----+
|config | --> symbols_config.json / ai_config.json / MySQL AppConfig
|_store |
+---------+
|
+----+-----------+
|ai_analysis.py | --> 外部 AI 模型 API (OpenAI 兼容)
|(AIFuturesAnalyzer)
+----------------+
```
---
## 七、存储架构
采用**三级降级策略**
1. **MySQL 优先** — 生产环境主存储
2. **Redis / StorageManager** — 缓存层加速
3. **SQLite 兜底** — 开发/单机环境自动降级
`analysis_db.py` 中的 `get_analysis_db()` 动态检测 MySQL 可用性,透明切换后端,业务代码无需感知底层存储变化。
---
## 八、路由注册
`app/main.py` 中注册三个路由:
```python
# 第 179-182 行
app.include_router(futures_analysis.router) # /api/v1/futures
app.include_router(review_plan.router) # /api/v1/review
app.include_router(trade_review.router) # /api/v1/trade-review
```
页面入口:`GET /futures-analysis`
---
## 九、技术指标计算说明
### MACDMoving Average Convergence Divergence
- EMA12 = 前一日EMA12 × (1 - 2/13) + 今日收盘价 × 2/13
- EMA26 = 前一日EMA26 × (1 - 2/27) + 今日收盘价 × 2/27
- DIF = EMA12 - EMA26
- DEA = 前一日DEA × (1 - 2/10) + 今日DIF × 2/10
- 信号DIF > DEA → 金叉看多DIF < DEA
### RSIRelative Strength Index
- 14 周期 RSI = 100 - 100 / (1 + RS)RS = 平均上涨幅度 / 平均下跌幅度
- 信号RSI > 70 → 超买RSI < 30 30 RSI 70
### 布林带Bollinger Bands
- 中轨 = 20 周期 MA
- 上轨 = 中轨 + 2 × 标准差
- 下轨 = 中轨 - 2 × 标准差
- 信号:价格触及上轨 → 压力;价格触及下轨 → 支撑
### KDJ随机指标
- RSV = (收盘价 - 9日最低价) / (9日最高价 - 9日最低价) × 100
- K = 2/3 × 前一日K + 1/3 × RSV
- D = 2/3 × 前一日D + 1/3 × K
- 信号K > D → 偏多K < D
### Pivot Point枢轴点
- PP = (最高价 + 最低价 + 收盘价) / 3
- R1 = 2 × PP - 最低价
- R2 = PP + (最高价 - 最低价)
- S1 = 2 × PP - 最高价
- S2 = PP - (最高价 - 最低价)

@ -1,575 +0,0 @@
---
comet_change: trade-reflection-system
role: technical-design
canonical_spec: openspec
archived-with: trade-reflection-system
---
# 交易反思系统 (Trade Reflection System) - Design Doc
## 1. 概述
在现有交易复盘模块基础上,构建「人工反思 → AI 提炼 → 经验沉淀」的学习闭环。核心能力包括按天组织交易视图、跨日交易自动配对、结构化反思、标签系统、AI 重新分析、历史经验库。
## 2. 架构设计
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 前 端 层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 按天交易视图 │ │ 反思编辑面板 │ │ 经验库页面 │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ └─────────────────┴────────────────────┘ │
│ 现有交易复盘页面 (改造) │
└─────────────────────────┬───────────────────────────────────┘
│ REST API
┌─────────────────────────┴───────────────────────────────────┐
│ 后 端 层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ trade_review.py (现有 + 8个新端点) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │trade_parser │ │ ai_analysis.py (扩展) │ │
│ │(不变) │ │ └── 反思增强分析 + 版本存储 │ │
│ └──────────────┘ └──────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ SQLAlchemy ORM
┌─────────────────────────┴───────────────────────────────────┐
│ 数 据 层 │
│ TradeRecord(扩展) + 6新表 + SQLite/MySQL │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 模块职责
| 模块 | 职责 | 变更类型 |
|------|------|---------|
| `trade_review.py` | 新增 8 个 API 端点 | 扩展 |
| `analysis_models.py` | 扩展 TradeRecord + 新增 6 个模型 | 扩展 |
| `ai_analysis.py` | 新增反思增强分析提示词 + 版本化存储 | 扩展 |
| `trade_parser.py` | 不变 | 无变更 |
| `futures_analysis.js` | 新增前端视图和交互逻辑 | 扩展 |
## 3. 数据模型详细设计
### 3.1 TradeRecord (扩展)
在现有表上新增字段:
```python
# 新增字段
reflection_status = Column(String(16), default='none') # none/pending/done
```
- `none`: 未写反思
- `pending`: 已写反思,待 AI 分析
- `done`: 已完成 AI 分析
### 3.2 TradePair (新表)
存储跨日交易配对关系:
```python
class TradePair(AnalysisBase):
__tablename__ = "trade_pairs"
id = Column(Integer, primary_key=True, autoincrement=True)
pair_type = Column(String(16), nullable=False) # auto/manual
symbol = Column(String(32), nullable=False, index=True)
variety = Column(String(16), nullable=False, index=True)
direction = Column(String(8), nullable=False) # long/short
open_trade_id = Column(Integer, nullable=False, index=True)
open_date = Column(String(16), nullable=False)
open_time = Column(String(32), nullable=False)
open_price = Column(Float, nullable=True)
close_trade_ids = Column(JSON, nullable=True) # [id1, id2, ...]
close_date = Column(String(16), nullable=True)
close_time = Column(String(32), nullable=True)
close_price = Column(Float, nullable=True)
status = Column(String(16), nullable=False, default='open') # open/closed
pnl = Column(Float, nullable=True) # 配对盈亏
commission_total = Column(Float, nullable=True) # 总手续费
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
```
### 3.3 TradeReflection (新表)
每笔交易的结构化反思:
```python
class TradeReflection(AnalysisBase):
__tablename__ = "trade_reflections"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_id = Column(Integer, nullable=False, index=True)
trade_pair_id = Column(Integer, nullable=True, index=True)
# 模板字段
entry_reason = Column(Text, nullable=True) # 入场理由
entry_timing = Column(String(16), nullable=True) # good/fair/poor
position_management = Column(Text, nullable=True) # 仓位管理反思
exit_reason = Column(Text, nullable=True) # 出场理由
exit_timing = Column(String(16), nullable=True) # good/fair/poor
discipline_score = Column(Integer, nullable=True) # 1-5
# 自由文本
free_reflection = Column(Text, nullable=True)
# AI 分析状态
ai_analysis_status = Column(String(16), default='none') # none/pending/done
ai_analysis_version = Column(Integer, default=0)
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
```
### 3.4 DailyReflection (新表)
当日整体反思:
```python
class DailyReflection(AnalysisBase):
__tablename__ = "daily_reflections"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_date = Column(String(16), nullable=False, unique=True, index=True)
emotion_state = Column(String(32), nullable=True) # 情绪状态
market_judgment = Column(Text, nullable=True) # 市场判断
execution_discipline = Column(Integer, nullable=True) # 1-5
overall_rating = Column(Integer, nullable=True) # 1-5
summary = Column(Text, nullable=True) # 总结
improvements = Column(Text, nullable=True) # 改进方向
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
```
### 3.5 TradeTag (新表)
标签定义:
```python
class TradeTag(AnalysisBase):
__tablename__ = "trade_tags"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False, unique=True)
category = Column(String(32), nullable=True) # operation/mindset/discipline/technical/custom
description = Column(Text, nullable=True)
is_preset = Column(Boolean, nullable=False, default=False)
usage_count = Column(Integer, nullable=False, default=0)
created_at = Column(DateTime, nullable=False, default=datetime.now)
```
预设标签清单:
- 操作类: 追涨杀跌, 逆市操作, 提前入场, 延迟出场, 加仓过早
- 心态类: 情绪化交易, 恐惧平仓, 贪婪持仓, 报复性交易
- 纪律类: 严格执行计划, 违反止损, 超仓交易, 频繁交易
- 技术类: 突破交易, 回调交易, 趋势跟踪, 震荡交易
### 3.6 TradeRecordTag (关联表)
```python
class TradeRecordTag(AnalysisBase):
__tablename__ = "trade_record_tags"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_id = Column(Integer, nullable=False, index=True)
tag_id = Column(Integer, nullable=False, index=True)
__table_args__ = (
Index('ix_trade_tag_unique', 'trade_id', 'tag_id', unique=True),
)
```
### 3.7 TradeExperience (新表)
经验教训:
```python
class TradeExperience(AnalysisBase):
__tablename__ = "trade_experiences"
id = Column(Integer, primary_key=True, autoincrement=True)
trade_id = Column(Integer, nullable=False, index=True)
trade_pair_id = Column(Integer, nullable=True, index=True)
reflection_id = Column(Integer, nullable=True, index=True)
title = Column(String(128), nullable=False) # 经验标题
content = Column(Text, nullable=False) # 经验内容
lesson_type = Column(String(16), nullable=False) # lesson/tip/warning
tags = Column(JSON, nullable=True) # [tag_name, ...]
ai_version = Column(Integer, nullable=True) # AI分析版本号
created_from = Column(String(16), nullable=False) # ai/manual
created_at = Column(DateTime, nullable=False, default=datetime.now)
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
```
## 4. API 详细设计
### 4.1 按天获取交易+配对
```
GET /api/v1/trade-review/daily-trades/{date}
Response:
{
"success": true,
"data": {
"trade_date": "2026-07-05",
"trades": [...], // 原始交易记录
"pairs": [...], // 配对结果
"daily_reflection": {...}, // 当日反思 (如有)
"statistics": {...} // 当天统计
}
}
```
### 4.2 配对 CRUD
```
POST /api/v1/trade-review/trade-pairs
{
"pair_type": "manual",
"symbol": "AG2608",
"open_trade_id": 123,
"close_trade_ids": [456, 789]
}
PUT /api/v1/trade-review/trade-pairs/{id}
DELETE /api/v1/trade-review/trade-pairs/{id}
```
### 4.3 反思 CRUD
```
POST /api/v1/trade-review/reflections
{
"trade_id": 123,
"entry_reason": "...",
"entry_timing": "good",
"position_management": "...",
"exit_reason": "...",
"exit_timing": "fair",
"discipline_score": 4,
"free_reflection": "..."
}
PUT /api/v1/trade-review/reflections/{id}
GET /api/v1/trade-review/reflections?trade_id=123
```
### 4.4 当日反思 CRUD
```
POST /api/v1/trade-review/daily-reflections
{
"trade_date": "2026-07-05",
"emotion_state": "平静",
"market_judgment": "...",
"execution_discipline": 4,
"overall_rating": 4,
"summary": "...",
"improvements": "..."
}
PUT /api/v1/trade-review/daily-reflections/{id}
GET /api/v1/trade-review/daily-reflections/{date}
```
### 4.5 标签管理
```
GET /api/v1/trade-review/tags # 获取所有标签
POST /api/v1/trade-review/tags # 创建自定义标签
{
"name": "我的标签",
"category": "custom"
}
POST /api/v1/trade-review/records/{trade_id}/tags
{
"tag_ids": [1, 2, 3]
}
DELETE /api/v1/trade-review/records/{trade_id}/tags/{tag_id}
```
### 4.6 AI 重新分析
```
POST /api/v1/trade-review/reanalyze
{
"trade_id": 123, # 或 trade_pair_id
"reflection_id": 456
}
Response:
{
"success": true,
"data": {
"analysis_id": 789,
"version": 2,
"analysis": "...",
"experience_suggestions": [...] # 经验提炼建议
}
}
```
### 4.7 经验库 CRUD
```
GET /api/v1/trade-review/experiences
?tag=追涨杀跌
&lesson_type=warning
&keyword=止损
&page=1&page_size=20
POST /api/v1/trade-review/experiences
{
"trade_id": 123,
"title": "...",
"content": "...",
"lesson_type": "lesson",
"tags": ["追涨杀跌", "违反止损"]
}
PUT /api/v1/trade-review/experiences/{id}
DELETE /api/v1/trade-review/experiences/{id}
```
## 5. 核心业务流程
### 5.1 交易配对流程
```
1. 用户导入结算单 → TradeRecord 表
2. 用户选择日期 → 触发配对逻辑
3. 配对引擎:
a. 按 variety + trade_date 筛选
b. 按品种分组
c. 对每个品种:
- 找出所有开仓记录 (offset='开')
- 找出所有平仓记录 (offset='平')
- 按时间顺序配对 (FIFO)
- 同一方向连续开仓合并为一个头寸
d. 生成 TradePair 记录 (pair_type='auto')
4. 前端展示配对结果
5. 用户可手动修正 (创建 pair_type='manual' 记录)
```
### 5.2 反思 → AI 分析 → 经验提炼流程
```
1. 用户给某笔交易写反思 → POST /reflections
2. 系统更新 TradeRecord.reflection_status = 'pending'
3. 用户点击「重新 AI 分析」→ POST /reanalyze
4. 后端:
a. 获取交易详情 + 多周期 K 线
b. 获取反思内容
c. 构建增强提示词 (交易信息 + K 线 + 反思)
d. 调用 AI 模型
e. 解析响应,保存为 TradeExperience (ai_version=N)
f. 更新 TradeReflection.ai_analysis_status = 'done'
g. 更新 TradeReflection.ai_analysis_version = N
h. 更新 TradeRecord.reflection_status = 'done'
5. 前端展示 AI 分析结果和经验建议
6. 用户可编辑经验建议后保存
```
### 5.3 AI 提示词设计
```python
prompt = f"""
你是一个有20年经验的期货交易教练。请分析以下交易及其反思提炼经验教训。
【交易信息】
品种: {symbol}
方向: {direction}
开仓: {open_date} {open_time} @ {open_price}
平仓: {close_date} {close_time} @ {close_price}
盈亏: {pnl}
手续费: {commission}
【多周期 K 线 context】
{kline_context}
【交易者反思】
入场理由: {entry_reason}
入场时机评价: {entry_timing}
仓位管理反思: {position_management}
出场理由: {exit_reason}
出场时机评价: {exit_timing}
纪律评分: {discipline_score}/5
自由反思: {free_reflection}
请从以下维度分析并输出 JSON:
{{
"trade_analysis": "对这笔交易的综合评价",
"strengths": ["优点1", "优点2"],
"weaknesses": ["不足1", "不足2"],
"experience_lessons": [
{{
"title": "经验标题",
"content": "经验内容",
"lesson_type": "lesson/tip/warning",
"tags": ["标签1", "标签2"]
}}
]
}}
"""
```
## 6. 前端设计
### 6.1 按天交易视图
```
┌─────────────────────────────────────────────────────────────┐
│ 日期选择器: [2026-07-05] [<] [>] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 当日反思 [编辑] │ │
│ │ 情绪: 平静 | 纪律: ★★★★ | 总体: ★★★★ │ │
│ │ 总结: ... │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 交易配对 #1 (AG2608 多头) 盈亏: +1200 │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 开 09:30│→ │ 平 14:20│ [反思] [标签] [AI分析] │ │
│ │ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 交易配对 #2 (RB2610 空头) 盈亏: -800 │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 开 10:15│→ │ 平 11:30│ [反思✓] [标签] [AI分析✓] │ │
│ │ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 未配对交易: CU2609 买开 09:45 @ 78500 │ │
│ │ [手动配对] [反思] [标签] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 6.2 反思编辑面板 (Modal)
```
┌─────────────────────────────────────────────────────────────┐
│ 反思编辑 - AG2608 多头 09:30→14:20 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 入场理由: [文本框] │
│ 入场时机: ○ 良好 ○ 一般 ○ 较差 │
│ │
│ 仓位管理反思: [文本框] │
│ │
│ 出场理由: [文本框] │
│ 出场时机: ○ 良好 ○ 一般 ○ 较差 │
│ │
│ 纪律评分: ☆☆☆☆☆ │
│ │
│ ─── 自由反思 ─── │
│ [多行文本框] │
│ │
│ ─── 标签 ─── │
│ [预设标签选择] + [自定义标签输入] │
│ │
│ [保存] [保存并AI分析] [取消] │
└─────────────────────────────────────────────────────────────┘
```
### 6.3 经验库页面
```
┌─────────────────────────────────────────────────────────────┐
│ 历史经验库 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [搜索框] [标签筛选: 全部▼] [类型: 全部▼] [视图: 列表▼] │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2026-07-05 | ⚠️ 警告 │ │
│ │ 追涨杀跌导致亏损 - AG2608 │ │
│ │ 在突破不明显的情况下追多,未设止损... │ │
│ │ 标签: 追涨杀跌 | 违反止损 │ │
│ │ [查看原交易] [编辑] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2026-07-04 | 💡 经验 │ │
│ │ 严格执行计划带来稳定收益 - RB2610 │ │
│ │ 按计划入场,按止损出场,避免情绪干扰... │ │
│ │ 标签: 严格执行计划 | 趋势跟踪 │ │
│ │ [查看原交易] [编辑] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [时间线视图] [分类视图] │
└─────────────────────────────────────────────────────────────┘
```
## 7. 错误处理
| 场景 | 处理策略 |
|------|---------|
| 配对逻辑无法匹配 | 标记为「未配对交易」,用户手动配对 |
| AI 模型调用失败 | 返回错误提示,保留 pending 状态,可重试 |
| 反思为空时触发 AI 分析 | 允许,但提示用户「补充反思可获得更精准的分析」 |
| 标签重复创建 | 自动去重,返回已存在标签 |
| 经验保存时交易已删除 | 软删除标记,经验保留但标记「来源已删除」 |
## 8. Demo 确认环节
在设计完成后、执行代码前,需要创建一个交互式 Demo 展示以下关键交互流程:
1. 按天查看交易 + 配对展示
2. 编辑反思 + 打标签
3. 触发 AI 重新分析
4. 经验提炼和保存
5. 经验库浏览和筛选
Demo 使用静态数据模拟,确认交互后再进入实际开发。
## 9. 测试策略
### 9.1 单元测试
- `test_trade_pairing.py`: 配对逻辑测试 (FIFO、跨日、多次开仓)
- `test_ai_prompt_builder.py`: AI 提示词构建测试
- `test_experience_extractor.py`: 经验提炼逻辑测试
### 9.2 API 测试
- `test_reflection_api.py`: 反思 CRUD
- `test_tag_api.py`: 标签管理
- `test_experience_api.py`: 经验库 CRUD
- `test_reanalyze_api.py`: AI 重新分析
### 9.3 集成测试
- `test_reflection_to_experience_flow.py`: 完整流程测试
### 9.4 边界测试
- 空反思 AI 分析
- 跨日配对边界 (周末/节假日)
- 标签去重
- 经验库分页/搜索/筛选
## 10. 非目标确认
- 不改变现有结算单导入逻辑
- 不改变 K 线数据获取和展示
- 不改变复盘计划 V2 生成逻辑
- 不改变品种分析主页
- 不重构现有交易复盘统计功能

16
node_modules/.bin/node-which generated vendored

@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../which/bin/node-which" "$@"
else
exec node "$basedir/../which/bin/node-which" "$@"
fi

17
node_modules/.bin/node-which.cmd generated vendored

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*

28
node_modules/.bin/node-which.ps1 generated vendored

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
} else {
& "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../which/bin/node-which" $args
} else {
& "node$exe" "$basedir/../which/bin/node-which" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/openspec generated vendored

@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@fission-ai/openspec/bin/openspec.js" "$@"
else
exec node "$basedir/../@fission-ai/openspec/bin/openspec.js" "$@"
fi

17
node_modules/.bin/openspec.cmd generated vendored

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@fission-ai\openspec\bin\openspec.js" %*

28
node_modules/.bin/openspec.ps1 generated vendored

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@fission-ai/openspec/bin/openspec.js" $args
} else {
& "$basedir/node$exe" "$basedir/../@fission-ai/openspec/bin/openspec.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@fission-ai/openspec/bin/openspec.js" $args
} else {
& "node$exe" "$basedir/../@fission-ai/openspec/bin/openspec.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/yaml generated vendored

@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../yaml/bin.mjs" "$@"
else
exec node "$basedir/../yaml/bin.mjs" "$@"
fi

17
node_modules/.bin/yaml.cmd generated vendored

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\yaml\bin.mjs" %*

28
node_modules/.bin/yaml.ps1 generated vendored

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../yaml/bin.mjs" $args
} else {
& "$basedir/node$exe" "$basedir/../yaml/bin.mjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../yaml/bin.mjs" $args
} else {
& "node$exe" "$basedir/../yaml/bin.mjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

1122
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

@ -1,22 +0,0 @@
MIT License
Copyright (c) 2024 OpenSpec Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,227 +0,0 @@
<p align="center">
<a href="https://github.com/Fission-AI/OpenSpec">
<picture>
<source srcset="assets/openspec_bg.png">
<img src="assets/openspec_bg.png" alt="OpenSpec logo">
</picture>
</a>
</p>
<p align="center">
<a href="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@fission-ai/openspec"><img alt="npm version" src="https://img.shields.io/npm/v/@fission-ai/openspec?style=flat-square" /></a>
<a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" /></a>
<a href="https://discord.gg/YctCnvvshC"><img alt="Discord" src="https://img.shields.io/discord/1411657095639601154?style=flat-square&logo=discord&logoColor=white&label=Discord&suffix=%20online" /></a>
</p>
<details>
<summary><strong>The most loved spec framework.</strong></summary>
[![Stars](https://img.shields.io/github/stars/Fission-AI/OpenSpec?style=flat-square&label=Stars)](https://github.com/Fission-AI/OpenSpec/stargazers)
[![Downloads](https://img.shields.io/npm/dm/@fission-ai/openspec?style=flat-square&label=Downloads/mo)](https://www.npmjs.com/package/@fission-ai/openspec)
[![Contributors](https://img.shields.io/github/contributors/Fission-AI/OpenSpec?style=flat-square&label=Contributors)](https://github.com/Fission-AI/OpenSpec/graphs/contributors)
</details>
<p></p>
Our philosophy:
```text
→ fluid not rigid
→ iterative not waterfall
→ easy not complex
→ built for brownfield not just greenfield
→ scalable from personal projects to enterprises
```
> [!TIP]
> **New workflow now available!** We've rebuilt OpenSpec with a new artifact-guided workflow.
>
> Run `/opsx:propose "your idea"` to get started. → [Learn more here](docs/opsx.md)
<p align="center">
Follow <a href="https://x.com/0xTab">@0xTab on X</a> for updates · Join the <a href="https://discord.gg/YctCnvvshC">OpenSpec Discord</a> for help and questions.
</p>
<!-- TODO: Add GIF demo of /opsx:propose → /opsx:archive workflow -->
## See it in action
```text
You: /opsx:explore
AI: What would you like to explore?
You: I want dark mode but I'm not sure how to do it cleanly.
AI: Let me look at your styling setup...
Cleanest path here: CSS variables + a small theme context,
with system-preference detection. No new dependencies. Scope it?
You: Yes, let's do it.
You: /opsx:propose add-dark-mode
AI: Created openspec/changes/add-dark-mode/
✓ proposal.md — why we're doing this, what's changing
✓ specs/ — requirements and scenarios
✓ design.md — technical approach
✓ tasks.md — implementation checklist
Ready for implementation!
You: /opsx:apply
AI: Implementing tasks...
✓ 1.1 Add theme context provider
✓ 1.2 Create toggle component
✓ 2.1 Add CSS variables
✓ 2.2 Wire up localStorage
All tasks complete!
You: /opsx:archive
AI: Archived to openspec/changes/archive/2025-01-23-add-dark-mode/
Specs updated. Ready for the next feature.
```
<details>
<summary><strong>OpenSpec Dashboard</strong></summary>
<p align="center">
<img src="assets/openspec_dashboard.png" alt="OpenSpec dashboard preview" width="90%">
</p>
</details>
## Quick Start
**Requires Node.js 20.19.0 or higher.**
Install OpenSpec globally:
```bash
npm install -g @fission-ai/openspec@latest
```
Then navigate to your project directory and initialize:
```bash
cd your-project
openspec init
```
Now talk to your AI:
- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before anything is written. ([Explore guide](docs/explore.md))
- **Already know what you want?** Go straight to `/opsx:propose <what-you-want-to-build>`.
Both are in the default profile. If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
> [!NOTE]
> Not sure if your tool is supported? [View the full list](docs/supported-tools.md) we support 25+ tools and growing.
>
> Also works with pnpm, yarn, bun, and nix. [See installation options](docs/installation.md).
## Docs
**Start here:** the **[Documentation Home](docs/README.md)** maps everything. New to OpenSpec? Read [Getting Started](docs/getting-started.md), then [How Commands Work](docs/how-commands-work.md) (where you actually type `/opsx:propose`).
**[Getting Started](docs/getting-started.md)**: first steps<br>
**[Explore First](docs/explore.md)**: think it through with `/opsx:explore` before you commit<br>
**[How Commands Work](docs/how-commands-work.md)**: where slash commands run vs the CLI<br>
**[Core Concepts at a Glance](docs/overview.md)**: the whole mental model, one page<br>
**[Examples & Recipes](docs/examples.md)**: real changes, start to finish<br>
**[Workflows](docs/workflows.md)**: combos and patterns<br>
**[Existing Projects](docs/existing-projects.md)**: adopt OpenSpec on a brownfield codebase<br>
**[Editing a Change](docs/editing-changes.md)**: update artifacts, go back, reconcile manual edits<br>
**[Commands](docs/commands.md)**: slash commands & skills<br>
**[CLI](docs/cli.md)**: terminal reference<br>
**[Stores](docs/stores-beta/user-guide.md)**: plan in a separate repo, shared across your team (beta)<br>
**[Supported Tools](docs/supported-tools.md)**: tool integrations & install paths<br>
**[Concepts](docs/concepts.md)**: how it all fits<br>
**[Multi-Language](docs/multi-language.md)**: multi-language support<br>
**[Customization](docs/customization.md)**: make it yours<br>
**[FAQ](docs/faq.md)** · **[Troubleshooting](docs/troubleshooting.md)** · **[Glossary](docs/glossary.md)**: quick help
## Community schemas
Third-party schema bundles distributed via standalone repositories — these provide opinionated workflows that integrate OpenSpec with other tools, similar to how [github/spec-kit's community extension catalog](https://github.com/github/spec-kit/tree/main/extensions) handles tool integrations.
**[Browse the catalog](docs/customization.md#community-schemas)** in the customization docs.
## Why OpenSpec?
AI coding assistants are powerful but unpredictable when requirements live only in chat history. OpenSpec adds a lightweight spec layer so you agree on what to build before any code is written.
- **Agree before you build** — human and AI align on specs before code gets written
- **Stay organized** — each change gets its own folder with proposal, specs, design, and tasks
- **Work fluidly** — update any artifact anytime, no rigid phase gates
- **Use your tools** — works with 20+ AI assistants via slash commands
### How we compare
**vs. [Spec Kit](https://github.com/github/spec-kit)** (GitHub) — Thorough but heavyweight. Rigid phase gates, lots of Markdown, Python setup. OpenSpec is lighter and lets you iterate freely.
**vs. [Kiro](https://kiro.dev)** (AWS) — Powerful but you're locked into their IDE and limited to Claude models. OpenSpec works with the tools you already use.
**vs. nothing** — AI coding without specs means vague prompts and unpredictable results. OpenSpec brings predictability without the ceremony.
## Updating OpenSpec
**Upgrade the package**
```bash
npm install -g @fission-ai/openspec@latest
```
**Refresh agent instructions**
Run this inside each project to regenerate AI guidance and ensure the latest slash commands are active:
```bash
openspec update
```
## Usage Notes
**Model selection**: OpenSpec works best with high-reasoning models. We recommend Codex 5.5 and Opus 4.7 for both planning and implementation.
**Context hygiene**: OpenSpec benefits from a clean context window. Clear your context before starting implementation and maintain good context hygiene throughout your session.
## Contributing
**Small fixes** — Bug fixes, typo corrections, and minor improvements can be submitted directly as PRs.
**Larger changes** — For new features, significant refactors, or architectural changes, please submit an OpenSpec change proposal first so we can align on intent and goals before implementation begins.
When writing proposals, keep the OpenSpec philosophy in mind: we serve a wide variety of users across different coding agents, models, and use cases. Changes should work well for everyone.
**AI-generated code is welcome** — as long as it's been tested and verified. PRs containing AI-generated code should mention the coding agent and model used (e.g., "Generated with Claude Code using claude-opus-4-5-20251101").
### Development
- Install dependencies: `pnpm install`
- Build: `pnpm run build`
- Test: `pnpm test`
- Develop CLI locally: `pnpm run dev` or `pnpm run dev:cli`
- Conventional commits (one-line): `type(scope): subject`
## Other
<details>
<summary><strong>Telemetry</strong></summary>
OpenSpec collects anonymous usage stats.
We collect only command names and version to understand usage patterns. No arguments, paths, content, or PII. Automatically disabled in CI.
**Opt-out:** `export OPENSPEC_TELEMETRY=0` or `export DO_NOT_TRACK=1`
</details>
<details>
<summary><strong>Maintainers & Advisors</strong></summary>
See [MAINTAINERS.md](MAINTAINERS.md) for the list of core maintainers and advisors who help guide the project.
</details>
## License
MIT

@ -1,5 +0,0 @@
#!/usr/bin/env node
import { runCli } from '../dist/cli/index.js';
runCli();

@ -1,82 +0,0 @@
{
"name": "@fission-ai/openspec",
"version": "1.5.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
"specs",
"cli",
"ai",
"development"
],
"homepage": "https://github.com/Fission-AI/OpenSpec",
"repository": {
"type": "git",
"url": "https://github.com/Fission-AI/OpenSpec"
},
"license": "MIT",
"author": "OpenSpec Contributors",
"type": "module",
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"bin": {
"openspec": "./bin/openspec.js"
},
"files": [
"dist",
"bin",
"schemas",
"scripts/postinstall.js",
"!dist/**/*.test.js",
"!dist/**/__tests__",
"!dist/**/*.map"
],
"engines": {
"node": ">=20.19.0"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.27.7",
"@types/node": "^24.2.0",
"@vitest/ui": "^3.2.6",
"eslint": "^9.39.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.62.0",
"vitest": "^3.2.6"
},
"dependencies": {
"@inquirer/core": "^10.3.2",
"@inquirer/prompts": "^7.10.1",
"chalk": "^5.5.0",
"commander": "^14.0.0",
"cross-spawn": "7.0.6",
"fast-glob": "^3.3.3",
"ora": "^8.2.0",
"posthog-node": "^5.20.0",
"yaml": "^2.8.2",
"zod": "^4.0.17"
},
"scripts": {
"lint": "eslint src/",
"build": "node build.js",
"dev": "tsc --watch",
"dev:cli": "pnpm build && node bin/openspec.js",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:postinstall": "node scripts/postinstall.js",
"postinstall": "node scripts/postinstall.js",
"check:pack-version": "node scripts/pack-version-check.mjs",
"release": "pnpm run release:ci",
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
"changeset": "changeset"
}
}

@ -1,153 +0,0 @@
name: spec-driven
version: 1
description: Default OpenSpec workflow - proposal → specs → design → tasks
artifacts:
- id: proposal
generates: proposal.md
description: Initial proposal document outlining the change
template: proposal.md
instruction: |
Create the proposal document that establishes WHY this change is needed.
Sections:
- **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
- **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
- **Capabilities**: Identify which specs will be created or modified:
- **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<name>/spec.md`. Use kebab-case names (e.g., `user-auth`, `data-export`).
- **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Check `openspec/specs/` for existing spec names. Leave empty if no requirement changes.
- **Impact**: Affected code, APIs, dependencies, or systems.
IMPORTANT: The Capabilities section is critical. It creates the contract between
proposal and specs phases. Research existing specs before filling this in.
Each capability listed here will need a corresponding spec file.
Keep it concise (1-2 pages). Focus on the "why" not the "how" -
implementation details belong in design.md.
This is the foundation - specs, design, and tasks all build on this.
requires: []
- id: specs
generates: "specs/**/*.md"
description: Detailed specifications for the change
template: spec.md
instruction: |
Create specification files that define WHAT the system should do.
Create one spec file per capability listed in the proposal's Capabilities section.
- New capabilities: use the exact kebab-case name from the proposal (specs/<capability>/spec.md).
- Modified capabilities: use the existing spec folder name from openspec/specs/<capability>/ when creating the delta spec at specs/<capability>/spec.md.
Delta operations (use ## headers):
- **ADDED Requirements**: New capabilities
- **MODIFIED Requirements**: Changed behavior - MUST include full updated content
- **REMOVED Requirements**: Deprecated features - MUST include **Reason** and **Migration**
- **RENAMED Requirements**: Name changes only - use FROM:/TO: format
Format requirements:
- Each requirement: `### Requirement: <name>` followed by description
- Use SHALL/MUST for normative requirements (avoid should/may)
- Each scenario: `#### Scenario: <name>` with WHEN/THEN format
- **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
- Every requirement MUST have at least one scenario.
MODIFIED requirements workflow:
1. Locate the existing requirement in openspec/specs/<capability>/spec.md
2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios)
3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior
4. Ensure header text matches exactly (whitespace-insensitive)
Common pitfall: Using MODIFIED with partial content loses detail at archive time.
If adding new concerns without changing existing behavior, use ADDED instead.
Example:
```
## ADDED Requirements
### Requirement: User can export data
The system SHALL allow users to export their data in CSV format.
#### Scenario: Successful export
- **WHEN** user clicks "Export" button
- **THEN** system downloads a CSV file with all user data
## REMOVED Requirements
### Requirement: Legacy export
**Reason**: Replaced by new export system
**Migration**: Use new export endpoint at /api/v2/export
```
Specs should be testable - each scenario is a potential test case.
requires:
- proposal
- id: design
generates: design.md
description: Technical design document with implementation details
template: design.md
instruction: |
Create the design document that explains HOW to implement the change.
When to include design.md (create only if any apply):
- Cross-cutting change (multiple services/modules) or new architectural pattern
- New external dependency or significant data model changes
- Security, performance, or migration complexity
- Ambiguity that benefits from technical decisions before coding
Sections:
- **Context**: Background, current state, constraints, stakeholders
- **Goals / Non-Goals**: What this design achieves and explicitly excludes
- **Decisions**: Key technical choices with rationale (why X over Y?). Include alternatives considered for each decision.
- **Risks / Trade-offs**: Known limitations, things that could go wrong. Format: [Risk] → Mitigation
- **Migration Plan**: Steps to deploy, rollback strategy (if applicable)
- **Open Questions**: Outstanding decisions or unknowns to resolve
Focus on architecture and approach, not line-by-line implementation.
Reference the proposal for motivation and specs for requirements.
Good design docs explain the "why" behind technical decisions.
requires:
- proposal
- id: tasks
generates: tasks.md
description: Implementation checklist with trackable tasks
template: tasks.md
instruction: |
Create the task list that breaks down the implementation work.
**IMPORTANT: Follow the template below exactly.** The apply phase parses
checkbox format to track progress. Tasks not using `- [ ]` won't be tracked.
Guidelines:
- Group related tasks under ## numbered headings
- Each task MUST be a checkbox: `- [ ] X.Y Task description`
- Tasks should be small enough to complete in one session
- Order tasks by dependency (what must be done first?)
Example:
```
## 1. Setup
- [ ] 1.1 Create new module structure
- [ ] 1.2 Add dependencies to package.json
## 2. Core Implementation
- [ ] 2.1 Implement data export function
- [ ] 2.2 Add CSV formatting utilities
```
Reference specs for what needs to be built, design for how to build it.
Each task should be verifiable - you know when it's done.
requires:
- specs
- design
apply:
requires: [tasks]
tracks: tasks.md
instruction: |
Read context files, work through pending tasks, mark complete as you go.
Pause if you hit blockers or need clarification.

@ -1,19 +0,0 @@
## Context
<!-- Background and current state -->
## Goals / Non-Goals
**Goals:**
<!-- What this design aims to achieve -->
**Non-Goals:**
<!-- What is explicitly out of scope -->
## Decisions
<!-- Key design decisions and rationale -->
## Risks / Trade-offs
<!-- Known risks and trade-offs -->

@ -1,23 +0,0 @@
## Why
<!-- Explain the motivation for this change. What problem does this solve? Why now? -->
## What Changes
<!-- Describe what will change. Be specific about new capabilities, modifications, or removals. -->
## Capabilities
### New Capabilities
<!-- Capabilities being introduced. Replace <name> with kebab-case identifier (e.g., user-auth, data-export, api-rate-limiting). Each creates specs/<name>/spec.md -->
- `<name>`: <brief description of what this capability covers>
### Modified Capabilities
<!-- Existing capabilities whose REQUIREMENTS are changing (not just implementation).
Only list here if spec-level behavior changes. Each needs a delta spec file.
Use existing spec names from openspec/specs/. Leave empty if no requirement changes. -->
- `<existing-name>`: <what requirement is changing>
## Impact
<!-- Affected code, APIs, dependencies, systems -->

@ -1,8 +0,0 @@
## ADDED Requirements
### Requirement: <!-- requirement name -->
<!-- requirement text -->
#### Scenario: <!-- scenario name -->
- **WHEN** <!-- condition -->
- **THEN** <!-- expected outcome -->

@ -1,9 +0,0 @@
## 1. <!-- Task Group Name -->
- [ ] 1.1 <!-- Task description -->
- [ ] 1.2 <!-- Task description -->
## 2. <!-- Task Group Name -->
- [ ] 2.1 <!-- Task description -->
- [ ] 2.2 <!-- Task description -->

@ -1,83 +0,0 @@
#!/usr/bin/env node
/**
* Postinstall script that hints about shell completions
*
* Completion installation is opt-in: the user must run
* `openspec completion install` explicitly. This script only
* prints a one-line tip after npm install.
*
* The tip is suppressed when:
* - CI=true environment variable is set
* - OPENSPEC_NO_COMPLETIONS=1 environment variable is set
* - dist/ directory doesn't exist (dev setup scenario)
*
* The script never fails npm install - all errors are caught and handled gracefully.
*/
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Check if we should skip installation
*/
function shouldSkipInstallation() {
// Skip in CI environments
if (process.env.CI === 'true' || process.env.CI === '1') {
return { skip: true, reason: 'CI environment detected' };
}
// Skip if user opted out
if (process.env.OPENSPEC_NO_COMPLETIONS === '1') {
return { skip: true, reason: 'OPENSPEC_NO_COMPLETIONS=1 set' };
}
return { skip: false };
}
/**
* Check if dist/ directory exists
*/
async function distExists() {
const distPath = path.join(__dirname, '..', 'dist');
try {
const stat = await fs.stat(distPath);
return stat.isDirectory();
} catch {
return false;
}
}
/**
* Main function
*/
async function main() {
try {
// Check if we should skip
const skipCheck = shouldSkipInstallation();
if (skipCheck.skip) {
// Silent skip - no output
return;
}
// Check if dist/ exists (skip silently if not - expected during dev setup)
if (!(await distExists())) {
return;
}
// Completions are opt-in — just print a hint
console.log(`\nTip: Run 'openspec completion install' for shell completions`);
} catch (error) {
// Fail gracefully - never break npm install
}
}
// Run main and handle any unhandled errors
main().catch(() => {
// Silent failure - never break npm install
process.exit(0);
});

@ -1,22 +0,0 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,89 +0,0 @@
# @inquirer/ansi
A lightweight package providing ANSI escape sequences for terminal cursor manipulation and screen clearing.
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/ansi
```
</td>
<td>
```sh
yarn add @inquirer/ansi
```
</td>
</tr>
</table>
## Usage
```js
import {
cursorUp,
cursorDown,
cursorTo,
cursorLeft,
cursorHide,
cursorShow,
eraseLines,
} from '@inquirer/ansi';
// Move cursor up 3 lines
process.stdout.write(cursorUp(3));
// Move cursor to specific position (x: 10, y: 5)
process.stdout.write(cursorTo(10, 5));
// Hide/show cursor
process.stdout.write(cursorHide);
process.stdout.write(cursorShow);
// Clear 5 lines
process.stdout.write(eraseLines(5));
```
Or when used inside an inquirer prompt:
```js
import { cursorHide } from '@inquirer/ansi';
import { createPrompt } from '@inquirer/core';
export default createPrompt((config, done: (value: void) => void) => {
return `Choose an option${cursorHide}`;
});
```
## API
### Cursor Movement
- **`cursorUp(count?: number)`** - Move cursor up by `count` lines (default: 1)
- **`cursorDown(count?: number)`** - Move cursor down by `count` lines (default: 1)
- **`cursorTo(x: number, y?: number)`** - Move cursor to position (x, y). If y is omitted, only moves horizontally
- **`cursorLeft`** - Move cursor to beginning of line
### Cursor Visibility
- **`cursorHide`** - Hide the cursor
- **`cursorShow`** - Show the cursor
### Screen Manipulation
- **`eraseLines(count: number)`** - Clear `count` lines and position cursor at the beginning of the first cleared line
# License
Copyright (c) 2025 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

@ -1,97 +0,0 @@
{
"name": "@inquirer/ansi",
"version": "1.0.2",
"engines": {
"node": ">=18"
},
"author": "Simon Boudrias <admin@simonboudrias.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh",
"ansi"
],
"sideEffects": false,
"files": [
"dist"
],
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@repo/tsconfig": "0.0.0",
"tshy": "^3.0.3"
},
"tshy": {
"exclude": [
"src/**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"scripts": {
"tsc": "tshy",
"attw": "attw --pack"
},
"type": "module",
"publishConfig": {
"access": "public"
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"main": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts",
"module": "./dist/esm/index.js",
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/ansi/README.md",
"gitHead": "6881993e517e76fa891b72e1f5086fd11f7676ac"
}

@ -1,22 +0,0 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save