Compare commits
1 Commits
1614a61617
...
chatendpoi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
711df97ce9 |
@@ -1,242 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Bulk Archive"
|
||||
description: Archive multiple completed changes at once
|
||||
category: Workflow
|
||||
tags: [workflow, archive, experimental, bulk]
|
||||
---
|
||||
|
||||
Archive multiple completed changes in a single operation.
|
||||
|
||||
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
|
||||
|
||||
**Input**: None required (prompts for selection)
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Get active changes**
|
||||
|
||||
Run `openspec list --json` to get all active changes.
|
||||
|
||||
If no active changes exist, inform user and stop.
|
||||
|
||||
2. **Prompt for change selection**
|
||||
|
||||
Use **AskUserQuestion tool** with multi-select to let user choose changes:
|
||||
- Show each change with its schema
|
||||
- Include an option for "All changes"
|
||||
- Allow any number of selections (1+ works, 2+ is the typical use case)
|
||||
|
||||
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
|
||||
|
||||
3. **Batch validation - gather status for all selected changes**
|
||||
|
||||
For each selected change, collect:
|
||||
|
||||
a. **Artifact status** - Run `openspec status --change "<name>" --json`
|
||||
- Parse `schemaName` and `artifacts` list
|
||||
- Note which artifacts are `done` vs other states
|
||||
|
||||
b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
|
||||
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- If no tasks file exists, note as "No tasks"
|
||||
|
||||
c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
|
||||
- List which capability specs exist
|
||||
- For each, extract requirement names (lines matching `### Requirement: <name>`)
|
||||
|
||||
4. **Detect spec conflicts**
|
||||
|
||||
Build a map of `capability -> [changes that touch it]`:
|
||||
|
||||
```
|
||||
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
|
||||
api -> [change-c] <- OK (only 1 change)
|
||||
```
|
||||
|
||||
A conflict exists when 2+ selected changes have delta specs for the same capability.
|
||||
|
||||
5. **Resolve conflicts agentically**
|
||||
|
||||
**For each conflict**, investigate the codebase:
|
||||
|
||||
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
|
||||
|
||||
b. **Search the codebase** for implementation evidence:
|
||||
- Look for code implementing requirements from each delta spec
|
||||
- Check for related files, functions, or tests
|
||||
|
||||
c. **Determine resolution**:
|
||||
- If only one change is actually implemented -> sync that one's specs
|
||||
- If both implemented -> apply in chronological order (older first, newer overwrites)
|
||||
- If neither implemented -> skip spec sync, warn user
|
||||
|
||||
d. **Record resolution** for each conflict:
|
||||
- Which change's specs to apply
|
||||
- In what order (if both)
|
||||
- Rationale (what was found in codebase)
|
||||
|
||||
6. **Show consolidated status table**
|
||||
|
||||
Display a table summarizing all changes:
|
||||
|
||||
```
|
||||
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|
||||
|---------------------|-----------|-------|---------|-----------|--------|
|
||||
| schema-management | Done | 5/5 | 2 delta | None | Ready |
|
||||
| project-config | Done | 3/3 | 1 delta | None | Ready |
|
||||
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
|
||||
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
|
||||
```
|
||||
|
||||
For conflicts, show the resolution:
|
||||
```
|
||||
* Conflict resolution:
|
||||
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
|
||||
```
|
||||
|
||||
For incomplete changes, show warnings:
|
||||
```
|
||||
Warnings:
|
||||
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
|
||||
```
|
||||
|
||||
7. **Confirm batch operation**
|
||||
|
||||
Use **AskUserQuestion tool** with a single confirmation:
|
||||
|
||||
- "Archive N changes?" with options based on status
|
||||
- Options might include:
|
||||
- "Archive all N changes"
|
||||
- "Archive only N ready changes (skip incomplete)"
|
||||
- "Cancel"
|
||||
|
||||
If there are incomplete changes, make clear they'll be archived with warnings.
|
||||
|
||||
8. **Execute archive for each confirmed change**
|
||||
|
||||
Process changes in the determined order (respecting conflict resolution):
|
||||
|
||||
a. **Sync specs** if delta specs exist:
|
||||
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
|
||||
- For conflicts, apply in resolved order
|
||||
- Track if sync was done
|
||||
|
||||
b. **Perform the archive**:
|
||||
```bash
|
||||
mkdir -p openspec/changes/archive
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
```
|
||||
|
||||
c. **Track outcome** for each change:
|
||||
- Success: archived successfully
|
||||
- Failed: error during archive (record error)
|
||||
- Skipped: user chose not to archive (if applicable)
|
||||
|
||||
9. **Display summary**
|
||||
|
||||
Show final results:
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived 3 changes:
|
||||
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
|
||||
- project-config -> archive/2026-01-19-project-config/
|
||||
- add-oauth -> archive/2026-01-19-add-oauth/
|
||||
|
||||
Skipped 1 change:
|
||||
- add-verify-skill (user chose not to archive incomplete)
|
||||
|
||||
Spec sync summary:
|
||||
- 4 delta specs synced to main specs
|
||||
- 1 conflict resolved (auth: applied both in chronological order)
|
||||
```
|
||||
|
||||
If any failures:
|
||||
```
|
||||
Failed 1 change:
|
||||
- some-change: Archive directory already exists
|
||||
```
|
||||
|
||||
**Conflict Resolution Examples**
|
||||
|
||||
Example 1: Only one implemented
|
||||
```
|
||||
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
|
||||
|
||||
Checking add-oauth:
|
||||
- Delta adds "OAuth Provider Integration" requirement
|
||||
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
|
||||
|
||||
Checking add-jwt:
|
||||
- Delta adds "JWT Token Handling" requirement
|
||||
- Searching codebase... no JWT implementation found
|
||||
|
||||
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
|
||||
```
|
||||
|
||||
Example 2: Both implemented
|
||||
```
|
||||
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
|
||||
|
||||
Checking add-rest-api (created 2026-01-10):
|
||||
- Delta adds "REST Endpoints" requirement
|
||||
- Searching codebase... found src/api/rest.ts
|
||||
|
||||
Checking add-graphql (created 2026-01-15):
|
||||
- Delta adds "GraphQL Schema" requirement
|
||||
- Searching codebase... found src/api/graphql.ts
|
||||
|
||||
Resolution: Both implemented. Will apply add-rest-api specs first,
|
||||
then add-graphql specs (chronological order, newer takes precedence).
|
||||
```
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
|
||||
|
||||
Spec sync summary:
|
||||
- N delta specs synced to main specs
|
||||
- No conflicts (or: M conflicts resolved)
|
||||
```
|
||||
|
||||
**Output On Partial Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete (partial)
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
|
||||
Skipped M changes:
|
||||
- <change-2> (user chose not to archive incomplete)
|
||||
|
||||
Failed K changes:
|
||||
- <change-3>: Archive directory already exists
|
||||
```
|
||||
|
||||
**Output When No Changes**
|
||||
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. 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,114 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Continue"
|
||||
description: Continue working on a change - create the next artifact (Experimental)
|
||||
category: Workflow
|
||||
tags: [workflow, artifacts, 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
|
||||
|
||||
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
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- **Create the artifact file**:
|
||||
- Read any completed dependency files for context
|
||||
- Use `template` as the structure - fill in its sections
|
||||
- Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file
|
||||
- Write to the output path specified in instructions
|
||||
- Show what was created and what's now unlocked
|
||||
- STOP after creating ONE artifact
|
||||
|
||||
---
|
||||
|
||||
**If no artifacts are ready (all blocked)**:
|
||||
- This shouldn't happen with a valid schema
|
||||
- Show status and suggest checking for issues
|
||||
|
||||
4. **After creating an artifact, show progress**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After each invocation, show:
|
||||
- Which artifact was created
|
||||
- Schema workflow being used
|
||||
- Current progress (N/M complete)
|
||||
- What artifacts are now unlocked
|
||||
- Prompt: "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,97 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Fast Forward"
|
||||
description: Create a change and generate all artifacts needed for implementation in one go
|
||||
category: Workflow
|
||||
tags: [workflow, artifacts, experimental]
|
||||
---
|
||||
|
||||
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 at `openspec/changes/<name>/`.
|
||||
|
||||
3. **Get the artifact build order**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to get:
|
||||
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||
- `artifacts`: list of all artifacts with their status and dependencies
|
||||
|
||||
4. **Create artifacts in sequence until apply-ready**
|
||||
|
||||
Use the **TodoWrite tool** to track progress through the artifacts.
|
||||
|
||||
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||
|
||||
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||
- Get instructions:
|
||||
```bash
|
||||
openspec instructions <artifact-id> --change "<name>" --json
|
||||
```
|
||||
- The instructions JSON includes:
|
||||
- `context`: Project background (constraints for you - do NOT include in output)
|
||||
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||
- `template`: The structure to use for your output file
|
||||
- `instruction`: Schema-specific guidance for this artifact type
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- Read any completed dependency files for context
|
||||
- Create the artifact file using `template` as the structure
|
||||
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||
- Show brief progress: "✓ Created <artifact-id>"
|
||||
|
||||
b. **Continue until all `applyRequires` artifacts are complete**
|
||||
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
|
||||
- Stop when all `applyRequires` artifacts are done
|
||||
|
||||
c. **If an artifact requires user input** (unclear context):
|
||||
- Use **AskUserQuestion tool** to clarify
|
||||
- Then continue with creation
|
||||
|
||||
5. **Show final status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After completing all artifacts, summarize:
|
||||
- Change name and location
|
||||
- List of artifacts created with brief descriptions
|
||||
- What's ready: "All artifacts created! Ready for implementation."
|
||||
- Prompt: "Run `/opsx:apply` to start implementing."
|
||||
|
||||
**Artifact Creation Guidelines**
|
||||
|
||||
- Follow the `instruction` field from `openspec instructions` for each artifact type
|
||||
- The schema defines what each artifact should contain - follow it
|
||||
- Read dependency artifacts for context before creating new ones
|
||||
- Use `template` as the structure for your output file - fill in its sections
|
||||
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||
- These guide what you write, but should never appear in the output
|
||||
|
||||
**Guardrails**
|
||||
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
|
||||
- Always read dependency artifacts before creating a new one
|
||||
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||
- If a change with that name already exists, ask if user wants to continue it or create a new one
|
||||
- Verify each artifact file exists after writing before proceeding to next
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: "OPSX: New"
|
||||
description: Start a new change using the experimental artifact workflow (OPSX)
|
||||
category: Workflow
|
||||
tags: [workflow, artifacts, experimental]
|
||||
---
|
||||
|
||||
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 at `openspec/changes/<name>/` with the selected schema.
|
||||
|
||||
4. **Show the artifact status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
This shows which artifacts need to be created and which are ready (dependencies satisfied).
|
||||
|
||||
5. **Get instructions for the first artifact**
|
||||
The first artifact depends on the schema. 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,550 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Onboard"
|
||||
description: Guided onboarding - walk through a complete OpenSpec workflow cycle with narration
|
||||
category: Workflow
|
||||
tags: [workflow, onboarding, tutorial, learning]
|
||||
---
|
||||
|
||||
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 in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
|
||||
|
||||
Let me create one for our task.
|
||||
```
|
||||
|
||||
**DO:** Create the change with a derived kebab-case name:
|
||||
```bash
|
||||
openspec new change "<derived-name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Created: `openspec/changes/<name>/`
|
||||
|
||||
The folder structure:
|
||||
```
|
||||
openspec/changes/<name>/
|
||||
├── proposal.md ← Why we're doing this (empty, we'll fill it)
|
||||
├── design.md ← How we'll build it (empty)
|
||||
├── specs/ ← Detailed requirements (empty)
|
||||
└── tasks.md ← Implementation checklist (empty)
|
||||
```
|
||||
|
||||
Now let's fill in the first artifact—the proposal.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Proposal
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## The Proposal
|
||||
|
||||
The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
|
||||
|
||||
I'll draft one based on our task.
|
||||
```
|
||||
|
||||
**DO:** Draft the proposal content (don't save yet):
|
||||
|
||||
```
|
||||
Here's a draft proposal:
|
||||
|
||||
---
|
||||
|
||||
## Why
|
||||
|
||||
[1-2 sentences explaining the problem/opportunity]
|
||||
|
||||
## What Changes
|
||||
|
||||
[Bullet points of what will be different]
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `<capability-name>`: [brief description]
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- If modifying existing behavior -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/path/to/file.ts`: [what changes]
|
||||
- [other files if applicable]
|
||||
|
||||
---
|
||||
|
||||
Does this capture the intent? I can adjust before we save it.
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user approval/feedback.
|
||||
|
||||
After approval, save the proposal:
|
||||
```bash
|
||||
openspec instructions proposal --change "<name>" --json
|
||||
```
|
||||
Then write the content to `openspec/changes/<name>/proposal.md`.
|
||||
|
||||
```
|
||||
Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
|
||||
|
||||
Next up: specs.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Specs
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Specs
|
||||
|
||||
Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
|
||||
|
||||
For a small task like this, we might only need one spec file.
|
||||
```
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
|
||||
```
|
||||
Here's the spec:
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: <Name>
|
||||
|
||||
<Description of what the system should do>
|
||||
|
||||
#### Scenario: <Scenario name>
|
||||
|
||||
- **WHEN** <trigger condition>
|
||||
- **THEN** <expected outcome>
|
||||
- **AND** <additional outcome if needed>
|
||||
|
||||
---
|
||||
|
||||
This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Design
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Design
|
||||
|
||||
The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
|
||||
|
||||
For small changes, this might be brief. That's fine—not every change needs deep design discussion.
|
||||
```
|
||||
|
||||
**DO:** Draft design.md:
|
||||
|
||||
```
|
||||
Here's the design:
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
[Brief context about the current state]
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- [What we're trying to achieve]
|
||||
|
||||
**Non-Goals:**
|
||||
- [What's explicitly out of scope]
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: [Key decision]
|
||||
|
||||
[Explanation of approach and rationale]
|
||||
|
||||
---
|
||||
|
||||
For a small task, this captures the key decisions without over-engineering.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Tasks
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Tasks
|
||||
|
||||
Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
|
||||
|
||||
These should be small, clear, and in logical order.
|
||||
```
|
||||
|
||||
**DO:** Generate tasks based on specs and design:
|
||||
|
||||
```
|
||||
Here are the implementation tasks:
|
||||
|
||||
---
|
||||
|
||||
## 1. [Category or file]
|
||||
|
||||
- [ ] 1.1 [Specific task]
|
||||
- [ ] 1.2 [Specific task]
|
||||
|
||||
## 2. Verify
|
||||
|
||||
- [ ] 2.1 [Verification step]
|
||||
|
||||
---
|
||||
|
||||
Each checkbox becomes a unit of work in the apply phase. Ready to implement?
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user to confirm they're ready to implement.
|
||||
|
||||
Save to `openspec/changes/<name>/tasks.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Apply (Implementation)
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Implementation
|
||||
|
||||
Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
|
||||
```
|
||||
|
||||
**DO:** For each task:
|
||||
|
||||
1. Announce: "Working on task N: [description]"
|
||||
2. Implement the change in the codebase
|
||||
3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
|
||||
4. Mark complete in tasks.md: `- [ ]` → `- [x]`
|
||||
5. Brief status: "✓ Task N complete"
|
||||
|
||||
Keep narration light—don't over-explain every line of code.
|
||||
|
||||
After all tasks:
|
||||
|
||||
```
|
||||
## Implementation Complete
|
||||
|
||||
All tasks done:
|
||||
- [x] Task 1
|
||||
- [x] Task 2
|
||||
- [x] ...
|
||||
|
||||
The change is implemented! One more step—let's archive it.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Archive
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Archiving
|
||||
|
||||
When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
|
||||
|
||||
Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
|
||||
```
|
||||
|
||||
**DO:**
|
||||
```bash
|
||||
openspec archive "<name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
|
||||
|
||||
The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 11: Recap & Next Steps
|
||||
|
||||
```
|
||||
## Congratulations!
|
||||
|
||||
You just completed a full OpenSpec cycle:
|
||||
|
||||
1. **Explore** - Thought through the problem
|
||||
2. **New** - Created a change container
|
||||
3. **Proposal** - Captured WHY
|
||||
4. **Specs** - Defined WHAT in detail
|
||||
5. **Design** - Decided HOW
|
||||
6. **Tasks** - Broke it into steps
|
||||
7. **Apply** - Implemented the work
|
||||
8. **Archive** - Preserved the record
|
||||
|
||||
This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
---
|
||||
|
||||
## Command Reference
|
||||
|
||||
**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 `openspec/changes/<name>/`.
|
||||
|
||||
To pick up where we left off later:
|
||||
- `/opsx:continue <name>` - Resume artifact creation
|
||||
- `/opsx:apply <name>` - Jump to implementation (if tasks exist)
|
||||
|
||||
The work won't be lost. Come back whenever you're ready.
|
||||
```
|
||||
|
||||
Exit gracefully without pressure.
|
||||
|
||||
### User just wants command reference
|
||||
|
||||
If the user says they just want to see the commands or skip the tutorial:
|
||||
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**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,134 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Sync"
|
||||
description: Sync delta specs from a change to main specs
|
||||
category: Workflow
|
||||
tags: [workflow, specs, experimental]
|
||||
---
|
||||
|
||||
Sync delta specs from a change to main specs.
|
||||
|
||||
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
|
||||
|
||||
**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. **Find delta specs**
|
||||
|
||||
Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`.
|
||||
|
||||
Each delta spec file contains sections like:
|
||||
- `## ADDED Requirements` - New requirements to add
|
||||
- `## MODIFIED Requirements` - Changes to existing requirements
|
||||
- `## REMOVED Requirements` - Requirements to remove
|
||||
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
|
||||
|
||||
If no delta specs found, inform user and stop.
|
||||
|
||||
3. **For each delta spec, apply changes to main specs**
|
||||
|
||||
For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`:
|
||||
|
||||
a. **Read the delta spec** to understand the intended changes
|
||||
|
||||
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
|
||||
|
||||
c. **Apply changes intelligently**:
|
||||
|
||||
**ADDED Requirements:**
|
||||
- If requirement doesn't exist in main spec → add it
|
||||
- If requirement already exists → update it to match (treat as implicit MODIFIED)
|
||||
|
||||
**MODIFIED Requirements:**
|
||||
- Find the requirement in main spec
|
||||
- Apply the changes - this can be:
|
||||
- Adding new scenarios (don't need to copy existing ones)
|
||||
- Modifying existing scenarios
|
||||
- Changing the requirement description
|
||||
- Preserve scenarios/content not mentioned in the delta
|
||||
|
||||
**REMOVED Requirements:**
|
||||
- Remove the entire requirement block from main spec
|
||||
|
||||
**RENAMED Requirements:**
|
||||
- Find the FROM requirement, rename to TO
|
||||
|
||||
d. **Create new main spec** if capability doesn't exist yet:
|
||||
- Create `openspec/specs/<capability>/spec.md`
|
||||
- Add Purpose section (can be brief, mark as TBD)
|
||||
- Add Requirements section with the ADDED requirements
|
||||
|
||||
4. **Show summary**
|
||||
|
||||
After applying all changes, summarize:
|
||||
- Which capabilities were updated
|
||||
- What changes were made (requirements added/modified/removed/renamed)
|
||||
|
||||
**Delta Spec Format Reference**
|
||||
|
||||
```markdown
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: New Feature
|
||||
The system SHALL do something new.
|
||||
|
||||
#### Scenario: Basic case
|
||||
- **WHEN** user does X
|
||||
- **THEN** system does Y
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Existing Feature
|
||||
#### Scenario: New scenario to add
|
||||
- **WHEN** user does A
|
||||
- **THEN** system does B
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Deprecated Feature
|
||||
|
||||
## RENAMED Requirements
|
||||
|
||||
- FROM: `### Requirement: Old Name`
|
||||
- TO: `### Requirement: New Name`
|
||||
```
|
||||
|
||||
**Key Principle: Intelligent Merging**
|
||||
|
||||
Unlike programmatic merging, you can apply **partial updates**:
|
||||
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
|
||||
- The delta represents *intent*, not a wholesale replacement
|
||||
- Use your judgment to merge changes sensibly
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Specs Synced: <change-name>
|
||||
|
||||
Updated main specs:
|
||||
|
||||
**<capability-1>**:
|
||||
- Added requirement: "New Feature"
|
||||
- Modified requirement: "Existing Feature" (added 1 scenario)
|
||||
|
||||
**<capability-2>**:
|
||||
- Created new spec file
|
||||
- Added requirement: "Another Feature"
|
||||
|
||||
Main specs are now updated. The change remains active - archive when implementation is complete.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
- Read both delta and main specs before making changes
|
||||
- Preserve existing content not mentioned in delta
|
||||
- If something is unclear, ask for clarification
|
||||
- Show what you're changing as you go
|
||||
- The operation should be idempotent - running twice should give same result
|
||||
@@ -1,164 +0,0 @@
|
||||
---
|
||||
name: "OPSX: Verify"
|
||||
description: Verify implementation matches change artifacts before archiving
|
||||
category: Workflow
|
||||
tags: [workflow, verify, experimental]
|
||||
---
|
||||
|
||||
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")
|
||||
- Which artifacts exist for this change
|
||||
|
||||
3. **Get the change directory and load artifacts**
|
||||
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
```
|
||||
|
||||
This returns the change directory and context files. Read all available artifacts from `contextFiles`.
|
||||
|
||||
4. **Initialize verification report structure**
|
||||
|
||||
Create a report structure with three dimensions:
|
||||
- **Completeness**: Track tasks and spec coverage
|
||||
- **Correctness**: Track requirement implementation and scenario coverage
|
||||
- **Coherence**: Track design adherence and pattern consistency
|
||||
|
||||
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
||||
|
||||
5. **Verify Completeness**
|
||||
|
||||
**Task Completion**:
|
||||
- If tasks.md exists in contextFiles, read it
|
||||
- Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- Count complete vs total tasks
|
||||
- If incomplete tasks exist:
|
||||
- Add CRITICAL issue for each incomplete task
|
||||
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
||||
|
||||
**Spec Coverage**:
|
||||
- If delta specs exist in `openspec/changes/<name>/specs/`:
|
||||
- Extract all requirements (marked with "### Requirement:")
|
||||
- For each requirement:
|
||||
- Search codebase for keywords related to the requirement
|
||||
- Assess if implementation likely exists
|
||||
- If requirements appear unimplemented:
|
||||
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
||||
- Recommendation: "Implement requirement X: <description>"
|
||||
|
||||
6. **Verify Correctness**
|
||||
|
||||
**Requirement Implementation Mapping**:
|
||||
- For each requirement from delta specs:
|
||||
- Search codebase for implementation evidence
|
||||
- If found, note file paths and line ranges
|
||||
- Assess if implementation matches requirement intent
|
||||
- If divergence detected:
|
||||
- Add WARNING: "Implementation may diverge from spec: <details>"
|
||||
- Recommendation: "Review <file>:<lines> against requirement X"
|
||||
|
||||
**Scenario Coverage**:
|
||||
- For each scenario in delta specs (marked with "#### Scenario:"):
|
||||
- Check if conditions are handled in code
|
||||
- Check if tests exist covering the scenario
|
||||
- If scenario appears uncovered:
|
||||
- Add WARNING: "Scenario not covered: <scenario name>"
|
||||
- Recommendation: "Add test or implementation for scenario: <description>"
|
||||
|
||||
7. **Verify Coherence**
|
||||
|
||||
**Design Adherence**:
|
||||
- If design.md exists in contextFiles:
|
||||
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
||||
- Verify implementation follows those decisions
|
||||
- If contradiction detected:
|
||||
- Add WARNING: "Design decision not followed: <decision>"
|
||||
- Recommendation: "Update implementation or revise design.md to match reality"
|
||||
- If no design.md: Skip design adherence check, note "No design.md to verify against"
|
||||
|
||||
**Code Pattern Consistency**:
|
||||
- Review new code for consistency with project patterns
|
||||
- Check file naming, directory structure, coding style
|
||||
- If significant deviations found:
|
||||
- Add SUGGESTION: "Code pattern deviation: <details>"
|
||||
- Recommendation: "Consider following project pattern: <example>"
|
||||
|
||||
8. **Generate Verification Report**
|
||||
|
||||
**Summary Scorecard**:
|
||||
```
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|------------------|
|
||||
| Completeness | X/Y tasks, N reqs|
|
||||
| Correctness | M/N reqs covered |
|
||||
| Coherence | Followed/Issues |
|
||||
```
|
||||
|
||||
**Issues by Priority**:
|
||||
|
||||
1. **CRITICAL** (Must fix before archive):
|
||||
- Incomplete tasks
|
||||
- Missing requirement implementations
|
||||
- Each with specific, actionable recommendation
|
||||
|
||||
2. **WARNING** (Should fix):
|
||||
- Spec/design divergences
|
||||
- Missing scenario coverage
|
||||
- Each with specific recommendation
|
||||
|
||||
3. **SUGGESTION** (Nice to fix):
|
||||
- Pattern inconsistencies
|
||||
- Minor improvements
|
||||
- Each with specific recommendation
|
||||
|
||||
**Final Assessment**:
|
||||
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
||||
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
||||
- If all clear: "All checks passed. Ready for archive."
|
||||
|
||||
**Verification Heuristics**
|
||||
|
||||
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
|
||||
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
|
||||
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
|
||||
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
||||
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
||||
|
||||
**Graceful Degradation**
|
||||
|
||||
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
||||
- If tasks + specs exist: verify completeness and correctness, skip design
|
||||
- If full artifacts: verify all three dimensions
|
||||
- Always note which checks were skipped and why
|
||||
|
||||
**Output Format**
|
||||
|
||||
Use clear markdown with:
|
||||
- Table for summary scorecard
|
||||
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
|
||||
- Code references in format: `file.ts:123`
|
||||
- Specific, actionable recommendations
|
||||
- No vague suggestions like "consider reviewing"
|
||||
@@ -1,246 +0,0 @@
|
||||
---
|
||||
name: openspec-bulk-archive-change
|
||||
description: Archive multiple completed changes at once. Use when archiving several parallel changes.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Archive multiple completed changes in a single operation.
|
||||
|
||||
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
|
||||
|
||||
**Input**: None required (prompts for selection)
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Get active changes**
|
||||
|
||||
Run `openspec list --json` to get all active changes.
|
||||
|
||||
If no active changes exist, inform user and stop.
|
||||
|
||||
2. **Prompt for change selection**
|
||||
|
||||
Use **AskUserQuestion tool** with multi-select to let user choose changes:
|
||||
- Show each change with its schema
|
||||
- Include an option for "All changes"
|
||||
- Allow any number of selections (1+ works, 2+ is the typical use case)
|
||||
|
||||
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
|
||||
|
||||
3. **Batch validation - gather status for all selected changes**
|
||||
|
||||
For each selected change, collect:
|
||||
|
||||
a. **Artifact status** - Run `openspec status --change "<name>" --json`
|
||||
- Parse `schemaName` and `artifacts` list
|
||||
- Note which artifacts are `done` vs other states
|
||||
|
||||
b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
|
||||
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- If no tasks file exists, note as "No tasks"
|
||||
|
||||
c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
|
||||
- List which capability specs exist
|
||||
- For each, extract requirement names (lines matching `### Requirement: <name>`)
|
||||
|
||||
4. **Detect spec conflicts**
|
||||
|
||||
Build a map of `capability -> [changes that touch it]`:
|
||||
|
||||
```
|
||||
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
|
||||
api -> [change-c] <- OK (only 1 change)
|
||||
```
|
||||
|
||||
A conflict exists when 2+ selected changes have delta specs for the same capability.
|
||||
|
||||
5. **Resolve conflicts agentically**
|
||||
|
||||
**For each conflict**, investigate the codebase:
|
||||
|
||||
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
|
||||
|
||||
b. **Search the codebase** for implementation evidence:
|
||||
- Look for code implementing requirements from each delta spec
|
||||
- Check for related files, functions, or tests
|
||||
|
||||
c. **Determine resolution**:
|
||||
- If only one change is actually implemented -> sync that one's specs
|
||||
- If both implemented -> apply in chronological order (older first, newer overwrites)
|
||||
- If neither implemented -> skip spec sync, warn user
|
||||
|
||||
d. **Record resolution** for each conflict:
|
||||
- Which change's specs to apply
|
||||
- In what order (if both)
|
||||
- Rationale (what was found in codebase)
|
||||
|
||||
6. **Show consolidated status table**
|
||||
|
||||
Display a table summarizing all changes:
|
||||
|
||||
```
|
||||
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|
||||
|---------------------|-----------|-------|---------|-----------|--------|
|
||||
| schema-management | Done | 5/5 | 2 delta | None | Ready |
|
||||
| project-config | Done | 3/3 | 1 delta | None | Ready |
|
||||
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
|
||||
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
|
||||
```
|
||||
|
||||
For conflicts, show the resolution:
|
||||
```
|
||||
* Conflict resolution:
|
||||
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
|
||||
```
|
||||
|
||||
For incomplete changes, show warnings:
|
||||
```
|
||||
Warnings:
|
||||
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
|
||||
```
|
||||
|
||||
7. **Confirm batch operation**
|
||||
|
||||
Use **AskUserQuestion tool** with a single confirmation:
|
||||
|
||||
- "Archive N changes?" with options based on status
|
||||
- Options might include:
|
||||
- "Archive all N changes"
|
||||
- "Archive only N ready changes (skip incomplete)"
|
||||
- "Cancel"
|
||||
|
||||
If there are incomplete changes, make clear they'll be archived with warnings.
|
||||
|
||||
8. **Execute archive for each confirmed change**
|
||||
|
||||
Process changes in the determined order (respecting conflict resolution):
|
||||
|
||||
a. **Sync specs** if delta specs exist:
|
||||
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
|
||||
- For conflicts, apply in resolved order
|
||||
- Track if sync was done
|
||||
|
||||
b. **Perform the archive**:
|
||||
```bash
|
||||
mkdir -p openspec/changes/archive
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
```
|
||||
|
||||
c. **Track outcome** for each change:
|
||||
- Success: archived successfully
|
||||
- Failed: error during archive (record error)
|
||||
- Skipped: user chose not to archive (if applicable)
|
||||
|
||||
9. **Display summary**
|
||||
|
||||
Show final results:
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived 3 changes:
|
||||
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
|
||||
- project-config -> archive/2026-01-19-project-config/
|
||||
- add-oauth -> archive/2026-01-19-add-oauth/
|
||||
|
||||
Skipped 1 change:
|
||||
- add-verify-skill (user chose not to archive incomplete)
|
||||
|
||||
Spec sync summary:
|
||||
- 4 delta specs synced to main specs
|
||||
- 1 conflict resolved (auth: applied both in chronological order)
|
||||
```
|
||||
|
||||
If any failures:
|
||||
```
|
||||
Failed 1 change:
|
||||
- some-change: Archive directory already exists
|
||||
```
|
||||
|
||||
**Conflict Resolution Examples**
|
||||
|
||||
Example 1: Only one implemented
|
||||
```
|
||||
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
|
||||
|
||||
Checking add-oauth:
|
||||
- Delta adds "OAuth Provider Integration" requirement
|
||||
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
|
||||
|
||||
Checking add-jwt:
|
||||
- Delta adds "JWT Token Handling" requirement
|
||||
- Searching codebase... no JWT implementation found
|
||||
|
||||
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
|
||||
```
|
||||
|
||||
Example 2: Both implemented
|
||||
```
|
||||
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
|
||||
|
||||
Checking add-rest-api (created 2026-01-10):
|
||||
- Delta adds "REST Endpoints" requirement
|
||||
- Searching codebase... found src/api/rest.ts
|
||||
|
||||
Checking add-graphql (created 2026-01-15):
|
||||
- Delta adds "GraphQL Schema" requirement
|
||||
- Searching codebase... found src/api/graphql.ts
|
||||
|
||||
Resolution: Both implemented. Will apply add-rest-api specs first,
|
||||
then add-graphql specs (chronological order, newer takes precedence).
|
||||
```
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
|
||||
|
||||
Spec sync summary:
|
||||
- N delta specs synced to main specs
|
||||
- No conflicts (or: M conflicts resolved)
|
||||
```
|
||||
|
||||
**Output On Partial Success**
|
||||
|
||||
```
|
||||
## Bulk Archive Complete (partial)
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
|
||||
Skipped M changes:
|
||||
- <change-2> (user chose not to archive incomplete)
|
||||
|
||||
Failed K changes:
|
||||
- <change-3>: Archive directory already exists
|
||||
```
|
||||
|
||||
**Output When No Changes**
|
||||
|
||||
```
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. 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,118 +0,0 @@
|
||||
---
|
||||
name: openspec-continue-change
|
||||
description: Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Continue working on a change by creating the next artifact.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on.
|
||||
|
||||
Present the top 3-4 most recently modified changes as options, showing:
|
||||
- Change name
|
||||
- Schema (from `schema` field if present, otherwise "spec-driven")
|
||||
- Status (e.g., "0/5 tasks", "complete", "no tasks")
|
||||
- How recently it was modified (from `lastModified` field)
|
||||
|
||||
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue.
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check current status**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to understand current state. The response includes:
|
||||
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
|
||||
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
|
||||
- `isComplete`: Boolean indicating if all artifacts are complete
|
||||
|
||||
3. **Act based on status**:
|
||||
|
||||
---
|
||||
|
||||
**If all artifacts are complete (`isComplete: true`)**:
|
||||
- Congratulate the user
|
||||
- Show final status including the schema used
|
||||
- Suggest: "All artifacts created! You can now implement this change or archive it."
|
||||
- STOP
|
||||
|
||||
---
|
||||
|
||||
**If artifacts are ready to create** (status shows artifacts with `status: "ready"`):
|
||||
- Pick the FIRST artifact with `status: "ready"` from the status output
|
||||
- Get its instructions:
|
||||
```bash
|
||||
openspec instructions <artifact-id> --change "<name>" --json
|
||||
```
|
||||
- Parse the JSON. The key fields are:
|
||||
- `context`: Project background (constraints for you - do NOT include in output)
|
||||
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||
- `template`: The structure to use for your output file
|
||||
- `instruction`: Schema-specific guidance
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- **Create the artifact file**:
|
||||
- Read any completed dependency files for context
|
||||
- Use `template` as the structure - fill in its sections
|
||||
- Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file
|
||||
- Write to the output path specified in instructions
|
||||
- Show what was created and what's now unlocked
|
||||
- STOP after creating ONE artifact
|
||||
|
||||
---
|
||||
|
||||
**If no artifacts are ready (all blocked)**:
|
||||
- This shouldn't happen with a valid schema
|
||||
- Show status and suggest checking for issues
|
||||
|
||||
4. **After creating an artifact, show progress**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After each invocation, show:
|
||||
- Which artifact was created
|
||||
- Schema workflow being used
|
||||
- Current progress (N/M complete)
|
||||
- What artifacts are now unlocked
|
||||
- Prompt: "Want to continue? Just ask me to continue or tell me what to do next."
|
||||
|
||||
**Artifact Creation Guidelines**
|
||||
|
||||
The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create.
|
||||
|
||||
Common artifact patterns:
|
||||
|
||||
**spec-driven schema** (proposal → specs → design → tasks):
|
||||
- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact.
|
||||
- The Capabilities section is critical - each capability listed will need a spec file.
|
||||
- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name).
|
||||
- **design.md**: Document technical decisions, architecture, and implementation approach.
|
||||
- **tasks.md**: Break down implementation into checkboxed tasks.
|
||||
|
||||
For other schemas, follow the `instruction` field from the CLI output.
|
||||
|
||||
**Guardrails**
|
||||
- Create ONE artifact per invocation
|
||||
- Always read dependency artifacts before creating a new one
|
||||
- Never skip artifacts or create out of order
|
||||
- If context is unclear, ask the user before creating
|
||||
- Verify the artifact file exists after writing before marking progress
|
||||
- Use the schema's artifact sequence, don't assume specific artifact names
|
||||
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||
- These guide what you write, but should never appear in the output
|
||||
@@ -1,101 +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.2.0"
|
||||
---
|
||||
|
||||
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 at `openspec/changes/<name>/`.
|
||||
|
||||
3. **Get the artifact build order**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to get:
|
||||
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||
- `artifacts`: list of all artifacts with their status and dependencies
|
||||
|
||||
4. **Create artifacts in sequence until apply-ready**
|
||||
|
||||
Use the **TodoWrite tool** to track progress through the artifacts.
|
||||
|
||||
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||
|
||||
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||
- Get instructions:
|
||||
```bash
|
||||
openspec instructions <artifact-id> --change "<name>" --json
|
||||
```
|
||||
- The instructions JSON includes:
|
||||
- `context`: Project background (constraints for you - do NOT include in output)
|
||||
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||
- `template`: The structure to use for your output file
|
||||
- `instruction`: Schema-specific guidance for this artifact type
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- Read any completed dependency files for context
|
||||
- Create the artifact file using `template` as the structure
|
||||
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||
- Show brief progress: "✓ Created <artifact-id>"
|
||||
|
||||
b. **Continue until all `applyRequires` artifacts are complete**
|
||||
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
|
||||
- Stop when all `applyRequires` artifacts are done
|
||||
|
||||
c. **If an artifact requires user input** (unclear context):
|
||||
- Use **AskUserQuestion tool** to clarify
|
||||
- Then continue with creation
|
||||
|
||||
5. **Show final status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After completing all artifacts, summarize:
|
||||
- Change name and location
|
||||
- List of artifacts created with brief descriptions
|
||||
- What's ready: "All artifacts created! Ready for implementation."
|
||||
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
|
||||
|
||||
**Artifact Creation Guidelines**
|
||||
|
||||
- Follow the `instruction` field from `openspec instructions` for each artifact type
|
||||
- The schema defines what each artifact should contain - follow it
|
||||
- Read dependency artifacts for context before creating new ones
|
||||
- Use `template` as the structure for your output file - fill in its sections
|
||||
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||
- These guide what you write, but should never appear in the output
|
||||
|
||||
**Guardrails**
|
||||
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
|
||||
- Always read dependency artifacts before creating a new one
|
||||
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||
- If a change with that name already exists, 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.2.0"
|
||||
---
|
||||
|
||||
Start a new change using the experimental artifact-driven approach.
|
||||
|
||||
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no clear input provided, ask what they want to build**
|
||||
|
||||
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
|
||||
> "What change do you want to work on? Describe what you want to build or fix."
|
||||
|
||||
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
|
||||
|
||||
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
|
||||
|
||||
2. **Determine the workflow schema**
|
||||
|
||||
Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow.
|
||||
|
||||
**Use a different schema only if the user mentions:**
|
||||
- A specific schema name → use `--schema <name>`
|
||||
- "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose
|
||||
|
||||
**Otherwise**: Omit `--schema` to use the default.
|
||||
|
||||
3. **Create the change directory**
|
||||
```bash
|
||||
openspec new change "<name>"
|
||||
```
|
||||
Add `--schema <name>` only if the user requested a specific workflow.
|
||||
This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema.
|
||||
|
||||
4. **Show the artifact status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
This shows which artifacts need to be created and which are ready (dependencies satisfied).
|
||||
|
||||
5. **Get instructions for the first artifact**
|
||||
The first artifact depends on the schema (e.g., `proposal` for spec-driven).
|
||||
Check the status output to find the first artifact with status "ready".
|
||||
```bash
|
||||
openspec instructions <first-artifact-id> --change "<name>"
|
||||
```
|
||||
This outputs the template and context for creating the first artifact.
|
||||
|
||||
6. **STOP and wait for user direction**
|
||||
|
||||
**Output**
|
||||
|
||||
After completing the steps, summarize:
|
||||
- Change name and location
|
||||
- Schema/workflow being used and its artifact sequence
|
||||
- Current status (0/N artifacts complete)
|
||||
- The template for the first artifact
|
||||
- Prompt: "Ready to create the first artifact? Just describe what this change is about and I'll draft it, or ask me to continue."
|
||||
|
||||
**Guardrails**
|
||||
- Do NOT create any artifacts yet - just show the instructions
|
||||
- Do NOT advance beyond showing the first artifact template
|
||||
- If the name is invalid (not kebab-case), ask for a valid name
|
||||
- If a change with that name already exists, suggest continuing that change instead
|
||||
- Pass --schema if using a non-default workflow
|
||||
@@ -1,554 +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.2.0"
|
||||
---
|
||||
|
||||
Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
|
||||
|
||||
---
|
||||
|
||||
## 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 in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
|
||||
|
||||
Let me create one for our task.
|
||||
```
|
||||
|
||||
**DO:** Create the change with a derived kebab-case name:
|
||||
```bash
|
||||
openspec new change "<derived-name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Created: `openspec/changes/<name>/`
|
||||
|
||||
The folder structure:
|
||||
```
|
||||
openspec/changes/<name>/
|
||||
├── proposal.md ← Why we're doing this (empty, we'll fill it)
|
||||
├── design.md ← How we'll build it (empty)
|
||||
├── specs/ ← Detailed requirements (empty)
|
||||
└── tasks.md ← Implementation checklist (empty)
|
||||
```
|
||||
|
||||
Now let's fill in the first artifact—the proposal.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Proposal
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## The Proposal
|
||||
|
||||
The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
|
||||
|
||||
I'll draft one based on our task.
|
||||
```
|
||||
|
||||
**DO:** Draft the proposal content (don't save yet):
|
||||
|
||||
```
|
||||
Here's a draft proposal:
|
||||
|
||||
---
|
||||
|
||||
## Why
|
||||
|
||||
[1-2 sentences explaining the problem/opportunity]
|
||||
|
||||
## What Changes
|
||||
|
||||
[Bullet points of what will be different]
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `<capability-name>`: [brief description]
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- If modifying existing behavior -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/path/to/file.ts`: [what changes]
|
||||
- [other files if applicable]
|
||||
|
||||
---
|
||||
|
||||
Does this capture the intent? I can adjust before we save it.
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user approval/feedback.
|
||||
|
||||
After approval, save the proposal:
|
||||
```bash
|
||||
openspec instructions proposal --change "<name>" --json
|
||||
```
|
||||
Then write the content to `openspec/changes/<name>/proposal.md`.
|
||||
|
||||
```
|
||||
Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
|
||||
|
||||
Next up: specs.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Specs
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Specs
|
||||
|
||||
Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
|
||||
|
||||
For a small task like this, we might only need one spec file.
|
||||
```
|
||||
|
||||
**DO:** Create the spec file:
|
||||
```bash
|
||||
# Unix/macOS
|
||||
mkdir -p openspec/changes/<name>/specs/<capability-name>
|
||||
# Windows (PowerShell)
|
||||
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
|
||||
```
|
||||
|
||||
Draft the spec content:
|
||||
|
||||
```
|
||||
Here's the spec:
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: <Name>
|
||||
|
||||
<Description of what the system should do>
|
||||
|
||||
#### Scenario: <Scenario name>
|
||||
|
||||
- **WHEN** <trigger condition>
|
||||
- **THEN** <expected outcome>
|
||||
- **AND** <additional outcome if needed>
|
||||
|
||||
---
|
||||
|
||||
This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Design
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Design
|
||||
|
||||
The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
|
||||
|
||||
For small changes, this might be brief. That's fine—not every change needs deep design discussion.
|
||||
```
|
||||
|
||||
**DO:** Draft design.md:
|
||||
|
||||
```
|
||||
Here's the design:
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
[Brief context about the current state]
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- [What we're trying to achieve]
|
||||
|
||||
**Non-Goals:**
|
||||
- [What's explicitly out of scope]
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: [Key decision]
|
||||
|
||||
[Explanation of approach and rationale]
|
||||
|
||||
---
|
||||
|
||||
For a small task, this captures the key decisions without over-engineering.
|
||||
```
|
||||
|
||||
Save to `openspec/changes/<name>/design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Tasks
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Tasks
|
||||
|
||||
Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
|
||||
|
||||
These should be small, clear, and in logical order.
|
||||
```
|
||||
|
||||
**DO:** Generate tasks based on specs and design:
|
||||
|
||||
```
|
||||
Here are the implementation tasks:
|
||||
|
||||
---
|
||||
|
||||
## 1. [Category or file]
|
||||
|
||||
- [ ] 1.1 [Specific task]
|
||||
- [ ] 1.2 [Specific task]
|
||||
|
||||
## 2. Verify
|
||||
|
||||
- [ ] 2.1 [Verification step]
|
||||
|
||||
---
|
||||
|
||||
Each checkbox becomes a unit of work in the apply phase. Ready to implement?
|
||||
```
|
||||
|
||||
**PAUSE** - Wait for user to confirm they're ready to implement.
|
||||
|
||||
Save to `openspec/changes/<name>/tasks.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Apply (Implementation)
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Implementation
|
||||
|
||||
Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
|
||||
```
|
||||
|
||||
**DO:** For each task:
|
||||
|
||||
1. Announce: "Working on task N: [description]"
|
||||
2. Implement the change in the codebase
|
||||
3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
|
||||
4. Mark complete in tasks.md: `- [ ]` → `- [x]`
|
||||
5. Brief status: "✓ Task N complete"
|
||||
|
||||
Keep narration light—don't over-explain every line of code.
|
||||
|
||||
After all tasks:
|
||||
|
||||
```
|
||||
## Implementation Complete
|
||||
|
||||
All tasks done:
|
||||
- [x] Task 1
|
||||
- [x] Task 2
|
||||
- [x] ...
|
||||
|
||||
The change is implemented! One more step—let's archive it.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Archive
|
||||
|
||||
**EXPLAIN:**
|
||||
```
|
||||
## Archiving
|
||||
|
||||
When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
|
||||
|
||||
Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
|
||||
```
|
||||
|
||||
**DO:**
|
||||
```bash
|
||||
openspec archive "<name>"
|
||||
```
|
||||
|
||||
**SHOW:**
|
||||
```
|
||||
Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
|
||||
|
||||
The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 11: Recap & Next Steps
|
||||
|
||||
```
|
||||
## Congratulations!
|
||||
|
||||
You just completed a full OpenSpec cycle:
|
||||
|
||||
1. **Explore** - Thought through the problem
|
||||
2. **New** - Created a change container
|
||||
3. **Proposal** - Captured WHY
|
||||
4. **Specs** - Defined WHAT in detail
|
||||
5. **Design** - Decided HOW
|
||||
6. **Tasks** - Broke it into steps
|
||||
7. **Apply** - Implemented the work
|
||||
8. **Archive** - Preserved the record
|
||||
|
||||
This same rhythm works for any size change—a small fix or a major feature.
|
||||
|
||||
---
|
||||
|
||||
## Command Reference
|
||||
|
||||
**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 `openspec/changes/<name>/`.
|
||||
|
||||
To pick up where we left off later:
|
||||
- `/opsx:continue <name>` - Resume artifact creation
|
||||
- `/opsx:apply <name>` - Jump to implementation (if tasks exist)
|
||||
|
||||
The work won't be lost. Come back whenever you're ready.
|
||||
```
|
||||
|
||||
Exit gracefully without pressure.
|
||||
|
||||
### User just wants command reference
|
||||
|
||||
If the user says they just want to see the commands or skip the tutorial:
|
||||
|
||||
```
|
||||
## OpenSpec Quick Reference
|
||||
|
||||
**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,138 +0,0 @@
|
||||
---
|
||||
name: openspec-sync-specs
|
||||
description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Sync delta specs from a change to main specs.
|
||||
|
||||
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
|
||||
Show changes that have delta specs (under `specs/` directory).
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Find delta specs**
|
||||
|
||||
Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`.
|
||||
|
||||
Each delta spec file contains sections like:
|
||||
- `## ADDED Requirements` - New requirements to add
|
||||
- `## MODIFIED Requirements` - Changes to existing requirements
|
||||
- `## REMOVED Requirements` - Requirements to remove
|
||||
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
|
||||
|
||||
If no delta specs found, inform user and stop.
|
||||
|
||||
3. **For each delta spec, apply changes to main specs**
|
||||
|
||||
For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`:
|
||||
|
||||
a. **Read the delta spec** to understand the intended changes
|
||||
|
||||
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
|
||||
|
||||
c. **Apply changes intelligently**:
|
||||
|
||||
**ADDED Requirements:**
|
||||
- If requirement doesn't exist in main spec → add it
|
||||
- If requirement already exists → update it to match (treat as implicit MODIFIED)
|
||||
|
||||
**MODIFIED Requirements:**
|
||||
- Find the requirement in main spec
|
||||
- Apply the changes - this can be:
|
||||
- Adding new scenarios (don't need to copy existing ones)
|
||||
- Modifying existing scenarios
|
||||
- Changing the requirement description
|
||||
- Preserve scenarios/content not mentioned in the delta
|
||||
|
||||
**REMOVED Requirements:**
|
||||
- Remove the entire requirement block from main spec
|
||||
|
||||
**RENAMED Requirements:**
|
||||
- Find the FROM requirement, rename to TO
|
||||
|
||||
d. **Create new main spec** if capability doesn't exist yet:
|
||||
- Create `openspec/specs/<capability>/spec.md`
|
||||
- Add Purpose section (can be brief, mark as TBD)
|
||||
- Add Requirements section with the ADDED requirements
|
||||
|
||||
4. **Show summary**
|
||||
|
||||
After applying all changes, summarize:
|
||||
- Which capabilities were updated
|
||||
- What changes were made (requirements added/modified/removed/renamed)
|
||||
|
||||
**Delta Spec Format Reference**
|
||||
|
||||
```markdown
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: New Feature
|
||||
The system SHALL do something new.
|
||||
|
||||
#### Scenario: Basic case
|
||||
- **WHEN** user does X
|
||||
- **THEN** system does Y
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Existing Feature
|
||||
#### Scenario: New scenario to add
|
||||
- **WHEN** user does A
|
||||
- **THEN** system does B
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Deprecated Feature
|
||||
|
||||
## RENAMED Requirements
|
||||
|
||||
- FROM: `### Requirement: Old Name`
|
||||
- TO: `### Requirement: New Name`
|
||||
```
|
||||
|
||||
**Key Principle: Intelligent Merging**
|
||||
|
||||
Unlike programmatic merging, you can apply **partial updates**:
|
||||
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
|
||||
- The delta represents *intent*, not a wholesale replacement
|
||||
- Use your judgment to merge changes sensibly
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Specs Synced: <change-name>
|
||||
|
||||
Updated main specs:
|
||||
|
||||
**<capability-1>**:
|
||||
- Added requirement: "New Feature"
|
||||
- Modified requirement: "Existing Feature" (added 1 scenario)
|
||||
|
||||
**<capability-2>**:
|
||||
- Created new spec file
|
||||
- Added requirement: "Another Feature"
|
||||
|
||||
Main specs are now updated. The change remains active - archive when implementation is complete.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
- Read both delta and main specs before making changes
|
||||
- Preserve existing content not mentioned in delta
|
||||
- If something is unclear, ask for clarification
|
||||
- Show what you're changing as you go
|
||||
- The operation should be idempotent - running twice should give same result
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
name: openspec-verify-change
|
||||
description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.2.0"
|
||||
---
|
||||
|
||||
Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
|
||||
Show changes that have implementation tasks (tasks artifact exists).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to understand:
|
||||
- `schemaName`: The workflow being used (e.g., "spec-driven")
|
||||
- Which artifacts exist for this change
|
||||
|
||||
3. **Get the change directory and load artifacts**
|
||||
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
```
|
||||
|
||||
This returns the change directory and context files. Read all available artifacts from `contextFiles`.
|
||||
|
||||
4. **Initialize verification report structure**
|
||||
|
||||
Create a report structure with three dimensions:
|
||||
- **Completeness**: Track tasks and spec coverage
|
||||
- **Correctness**: Track requirement implementation and scenario coverage
|
||||
- **Coherence**: Track design adherence and pattern consistency
|
||||
|
||||
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
||||
|
||||
5. **Verify Completeness**
|
||||
|
||||
**Task Completion**:
|
||||
- If tasks.md exists in contextFiles, read it
|
||||
- Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
|
||||
- Count complete vs total tasks
|
||||
- If incomplete tasks exist:
|
||||
- Add CRITICAL issue for each incomplete task
|
||||
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
||||
|
||||
**Spec Coverage**:
|
||||
- If delta specs exist in `openspec/changes/<name>/specs/`:
|
||||
- Extract all requirements (marked with "### Requirement:")
|
||||
- For each requirement:
|
||||
- Search codebase for keywords related to the requirement
|
||||
- Assess if implementation likely exists
|
||||
- If requirements appear unimplemented:
|
||||
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
||||
- Recommendation: "Implement requirement X: <description>"
|
||||
|
||||
6. **Verify Correctness**
|
||||
|
||||
**Requirement Implementation Mapping**:
|
||||
- For each requirement from delta specs:
|
||||
- Search codebase for implementation evidence
|
||||
- If found, note file paths and line ranges
|
||||
- Assess if implementation matches requirement intent
|
||||
- If divergence detected:
|
||||
- Add WARNING: "Implementation may diverge from spec: <details>"
|
||||
- Recommendation: "Review <file>:<lines> against requirement X"
|
||||
|
||||
**Scenario Coverage**:
|
||||
- For each scenario in delta specs (marked with "#### Scenario:"):
|
||||
- Check if conditions are handled in code
|
||||
- Check if tests exist covering the scenario
|
||||
- If scenario appears uncovered:
|
||||
- Add WARNING: "Scenario not covered: <scenario name>"
|
||||
- Recommendation: "Add test or implementation for scenario: <description>"
|
||||
|
||||
7. **Verify Coherence**
|
||||
|
||||
**Design Adherence**:
|
||||
- If design.md exists in contextFiles:
|
||||
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
||||
- Verify implementation follows those decisions
|
||||
- If contradiction detected:
|
||||
- Add WARNING: "Design decision not followed: <decision>"
|
||||
- Recommendation: "Update implementation or revise design.md to match reality"
|
||||
- If no design.md: Skip design adherence check, note "No design.md to verify against"
|
||||
|
||||
**Code Pattern Consistency**:
|
||||
- Review new code for consistency with project patterns
|
||||
- Check file naming, directory structure, coding style
|
||||
- If significant deviations found:
|
||||
- Add SUGGESTION: "Code pattern deviation: <details>"
|
||||
- Recommendation: "Consider following project pattern: <example>"
|
||||
|
||||
8. **Generate Verification Report**
|
||||
|
||||
**Summary Scorecard**:
|
||||
```
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|------------------|
|
||||
| Completeness | X/Y tasks, N reqs|
|
||||
| Correctness | M/N reqs covered |
|
||||
| Coherence | Followed/Issues |
|
||||
```
|
||||
|
||||
**Issues by Priority**:
|
||||
|
||||
1. **CRITICAL** (Must fix before archive):
|
||||
- Incomplete tasks
|
||||
- Missing requirement implementations
|
||||
- Each with specific, actionable recommendation
|
||||
|
||||
2. **WARNING** (Should fix):
|
||||
- Spec/design divergences
|
||||
- Missing scenario coverage
|
||||
- Each with specific recommendation
|
||||
|
||||
3. **SUGGESTION** (Nice to fix):
|
||||
- Pattern inconsistencies
|
||||
- Minor improvements
|
||||
- Each with specific recommendation
|
||||
|
||||
**Final Assessment**:
|
||||
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
||||
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
||||
- If all clear: "All checks passed. Ready for archive."
|
||||
|
||||
**Verification Heuristics**
|
||||
|
||||
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
|
||||
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
|
||||
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
|
||||
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
||||
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
||||
|
||||
**Graceful Degradation**
|
||||
|
||||
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
||||
- If tasks + specs exist: verify completeness and correctness, skip design
|
||||
- If full artifacts: verify all three dimensions
|
||||
- Always note which checks were skipped and why
|
||||
|
||||
**Output Format**
|
||||
|
||||
Use clear markdown with:
|
||||
- Table for summary scorecard
|
||||
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
|
||||
- Code references in format: `file.ts:123`
|
||||
- Specific, actionable recommendations
|
||||
- No vague suggestions like "consider reviewing"
|
||||
96
ARCHITECTURE.md
Normal file
96
ARCHITECTURE.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Architecture Decisions
|
||||
|
||||
## Blazor Hosting Model: Server vs WebAssembly
|
||||
|
||||
### How They Differ
|
||||
|
||||
| Aspect | Blazor Server | Blazor WASM |
|
||||
|--------|--------------|-------------|
|
||||
| Code runs | On the server | In the browser |
|
||||
| UI updates | Via SignalR WebSocket (server pushes diffs) | Locally in browser (no round-trip) |
|
||||
| Calling backend services | Direct — code is already server-side | Needs HTTP calls if accessing server resources |
|
||||
| Offline capable | No (requires persistent connection) | Yes |
|
||||
| Startup speed | Fast | Slower (downloads .NET runtime to browser) |
|
||||
|
||||
### Decision: Blazor WASM
|
||||
|
||||
This project uses **Blazor WebAssembly (standalone)** with a separate ASP.NET Core Web API backend.
|
||||
|
||||
## When Do You Need a Separate API Project?
|
||||
|
||||
There are two distinct concerns that are easy to conflate:
|
||||
|
||||
### 1. Consuming an External API (e.g. OpenAI)
|
||||
|
||||
Both Blazor Server and WASM can call external APIs directly from C# — no controller needed. The difference is **where that code executes**:
|
||||
|
||||
- **Blazor Server**: Code runs on the server. A service class calls OpenAI directly. API keys live safely in server memory. No controller, no exposed endpoint required.
|
||||
- **Blazor WASM**: Code runs in the browser. You *can* call OpenAI directly, but any **API key would be embedded in the browser download** — anyone could inspect it via browser dev tools. This is the primary reason to add a backend proxy.
|
||||
|
||||
### 2. Exposing an API Endpoint (e.g. ChatAgent.Api)
|
||||
|
||||
This is a separate concern. You expose an API endpoint when:
|
||||
|
||||
- Another application needs to communicate with your chat system
|
||||
- You want a REST API for mobile clients, scripts, or integrations
|
||||
- You need a server-side proxy to protect secrets from the browser (the WASM case above)
|
||||
|
||||
**Key insight:** You don't need an API endpoint to *use* an external service. You only need one in WASM to **keep secrets out of the browser**.
|
||||
|
||||
### 3. Component Updates Do Not Require an API
|
||||
|
||||
In both hosting models, Blazor components update themselves locally:
|
||||
|
||||
- Components hold state in fields/properties
|
||||
- Calling `StateHasChanged()` triggers a re-render
|
||||
- Components can call injected C# services directly
|
||||
- No HTTP round-trip is needed for UI updates
|
||||
|
||||
For example, a chat component that echoes "success msg!" back needs no API at all — a simple injected service handles it entirely within the client project.
|
||||
|
||||
## API Key Management Scenarios
|
||||
|
||||
The need for an API project in WASM depends on how secrets are managed:
|
||||
|
||||
| Scenario | WASM needs API project? | Why |
|
||||
|----------|------------------------|-----|
|
||||
| Raw API key in config | Yes | To keep the key out of browser-downloadable code |
|
||||
| Azure Key Vault | Yes | Browser sandbox cannot access Key Vault or managed identity |
|
||||
| API Management gateway (Azure APIM) with token auth | No | WASM calls the gateway directly; gateway handles auth via managed identity |
|
||||
| Blazor Server (any scenario) | No | Code is already server-side; secrets never leave the server |
|
||||
|
||||
### Enterprise Pattern: API Gateway
|
||||
|
||||
In enterprise environments, a common pattern avoids the need for a custom API proxy entirely:
|
||||
|
||||
1. An **API Management gateway** (e.g. Azure APIM) sits in front of the external service (e.g. OpenAI)
|
||||
2. The gateway authenticates via managed identity and handles secret retrieval
|
||||
3. The gateway exposes a public endpoint requiring only a subscription key or OAuth token
|
||||
4. WASM calls the gateway directly — no secrets in the browser, no custom API project needed
|
||||
|
||||
The "proxy" becomes infrastructure rather than application code.
|
||||
|
||||
## Current Project Structure
|
||||
|
||||
```
|
||||
ChatAgent.sln
|
||||
src/
|
||||
ChatAgent.Client/ -- Blazor WASM (standalone)
|
||||
Pages/ -- Routable page components
|
||||
Layout/ -- MainLayout, NavMenu
|
||||
Services/ -- Client-side services (e.g. ChatApiClient)
|
||||
Program.cs -- Client entry point, DI registration
|
||||
ChatAgent.Api/ -- ASP.NET Core Web API (backend proxy)
|
||||
Controllers/ -- API controllers (e.g. HealthController)
|
||||
Program.cs -- Server entry point, middleware config
|
||||
ChatAgent.Shared/ -- Models shared between Client and Api
|
||||
Models/ -- DTOs (e.g. HealthResponse)
|
||||
```
|
||||
|
||||
### Why Three Projects?
|
||||
|
||||
- **ChatAgent.Client**: The Blazor WASM app running in the browser
|
||||
- **ChatAgent.Api**: Exists to proxy requests that require server-side secrets (e.g. future OpenAI calls). Not needed for basic component interactions.
|
||||
- **ChatAgent.Shared**: Models referenced by both Client and Api, avoiding duplication
|
||||
|
||||
For the initial "echo success" phase, only ChatAgent.Client is actively used. The Api and Shared projects exist to support future integration with external services that require secret management.
|
||||
42
CLAUDE.md
42
CLAUDE.md
@@ -1,5 +1,43 @@
|
||||
## Project
|
||||
|
||||
Chat Agent WebApp — a personal AI chat app built with Blazor WebAssembly and the OpenAI GPT API that doubles as an incremental Blazor tutorial.
|
||||
**Chat Agent WebApp**
|
||||
|
||||
For full project details, constraints, and technology stack, see `openspec/specs/`.
|
||||
A personal AI chat web application built with Blazor WebAssembly and MudBlazor. Users send messages through a ChatGPT-style interface and receive responses from a backend service. The project is an incremental learning journey — each phase introduces one concept at a time, making it suitable for a C# developer experienced in backend work but new to web application frameworks.
|
||||
|
||||
**Core Value:** A working chat interface where every line of code is intentional and explained, so the builder learns Blazor patterns while shipping a real product.
|
||||
|
||||
**Current Phase:** Echo — the backend returns "success msg!" for every user message. No external API integration yet.
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Tech stack**: C# / Blazor WebAssembly — non-negotiable
|
||||
- **Hosting model**: Blazor WASM (standalone) with separate ASP.NET Core Web API backend
|
||||
- **UI library**: MudBlazor
|
||||
- **Code style**: Simple, well-documented. Every Blazor concept introduced must have inline comments explaining what it does, why it's done that way, and what idiomatic alternatives exist
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| .NET SDK | 9.0.x | Runtime and tooling |
|
||||
| Blazor WebAssembly Standalone | .NET 9 | Client SPA running in-browser |
|
||||
| ASP.NET Core Web API | .NET 9 | Backend proxy (for future external API calls) |
|
||||
| MudBlazor | latest | Material Design component library |
|
||||
| System.Text.Json | built-in | JSON serialization |
|
||||
|
||||
## Architecture
|
||||
|
||||
See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed hosting model discussion, API design decisions, and project structure rationale.
|
||||
|
||||
**Summary:**
|
||||
- Three-project solution: Client (WASM), Api (backend proxy), Shared (models)
|
||||
- Components update locally — no API needed for UI rendering
|
||||
- The Api project exists for future use when external services require server-side secrets
|
||||
- For the current echo phase, only the Client project is actively used
|
||||
|
||||
## Conventions
|
||||
|
||||
- Inline comments on every new Blazor concept: what it does, why, and idiomatic alternatives
|
||||
- Emphasize framework idiom and explain choices — written for a C# developer new to web/Blazor
|
||||
- Keep code simple; avoid abstractions until they are clearly needed
|
||||
- One concept per phase — do not introduce multiple new patterns at once
|
||||
|
||||
58
README.md
58
README.md
@@ -1,2 +1,58 @@
|
||||
# AgenticCode
|
||||
# ChatAgent
|
||||
|
||||
A personal AI chat web application built with Blazor WebAssembly and MudBlazor. Currently in the **Echo phase** — the bot responds with "success msg!" to every message.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
ChatAgent.Client/ Blazor WASM app (runs in the browser)
|
||||
ChatAgent.Api/ ASP.NET Core Web API (backend proxy)
|
||||
ChatAgent.Shared/ Shared models between Client and Api
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
### Chat UI only (Echo phase)
|
||||
|
||||
The echo phase doesn't need the API backend — the client handles everything locally.
|
||||
|
||||
```bash
|
||||
cd src/ChatAgent.Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Open http://localhost:5100 in your browser.
|
||||
|
||||
### Full stack (Client + API)
|
||||
|
||||
Run both projects in separate terminals:
|
||||
|
||||
```bash
|
||||
# Terminal 1 — API backend
|
||||
cd src/ChatAgent.Api
|
||||
dotnet run
|
||||
|
||||
# Terminal 2 — Blazor WASM client
|
||||
cd src/ChatAgent.Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
| Service | HTTP | HTTPS |
|
||||
|---------|------|-------|
|
||||
| Client | http://localhost:5100 | https://localhost:5200 |
|
||||
| API | http://localhost:7000 | https://localhost:7100 |
|
||||
|
||||
The health check page is available at `/health` when the API is running.
|
||||
|
||||
## Build
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
dotnet build
|
||||
```
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-03
|
||||
@@ -1,33 +0,0 @@
|
||||
## Context
|
||||
|
||||
CLAUDE.md is a monolithic file containing project identity, tech stack research, and stale GSD workflow sections. OpenSpec is now initialized and provides a structured home for this content as specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Move project description and constraints into `openspec/specs/project/spec.md`
|
||||
- Move technology stack research into `openspec/specs/stack/spec.md`
|
||||
- Populate `openspec/config.yaml` context so AI agents get project context when creating artifacts
|
||||
- Reduce CLAUDE.md to a slim file that points to OpenSpec for project knowledge
|
||||
|
||||
**Non-Goals:**
|
||||
- Rewriting or editing the migrated content (faithful move, not a rewrite)
|
||||
- Creating conventions or architecture specs (those are still empty placeholders)
|
||||
- Changing any application code
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Spec file format
|
||||
|
||||
The main specs in `openspec/specs/` will use a prose/reference format (not the WHEN/THEN delta format). The delta specs in the change use WHEN/THEN for requirements tracking, but the actual spec content is the migrated prose — tables, lists, and all.
|
||||
|
||||
### Decision 2: CLAUDE.md post-migration content
|
||||
|
||||
CLAUDE.md will retain only:
|
||||
- A one-line project summary
|
||||
- A pointer to `openspec/specs/` for project knowledge
|
||||
- Any workflow instructions specific to Claude Code (not project specs)
|
||||
|
||||
### Decision 3: config.yaml context
|
||||
|
||||
The `context` field in `openspec/config.yaml` will get a brief project summary and tech stack headline, so artifact generation has baseline context without reading full specs.
|
||||
@@ -1,27 +0,0 @@
|
||||
## Why
|
||||
|
||||
CLAUDE.md currently holds all project knowledge — description, constraints, and a large tech stack research block. With OpenSpec initialized, this content belongs in `openspec/specs/` where it can be managed as proper specs, referenced by changes, and won't conflict with CLAUDE.md's role as a slim workflow/instruction file.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Extract project description and constraints into a `project` spec
|
||||
- Extract full technology stack research into a `stack` spec
|
||||
- Populate `openspec/config.yaml` with project context
|
||||
- Slim CLAUDE.md down to workflow instructions with pointers to OpenSpec
|
||||
- Remove stale GSD placeholder sections (conventions, architecture, profile)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `project`: Project identity, core value statement, and non-negotiable constraints
|
||||
- `stack`: Technology stack decisions — packages, versions, alternatives, patterns, compatibility, and sources
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- None — no existing specs yet -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `CLAUDE.md`: Reduced from ~147 lines to a slim pointer file
|
||||
- `openspec/specs/project/spec.md`: New file with project identity
|
||||
- `openspec/specs/stack/spec.md`: New file with stack research
|
||||
- `openspec/config.yaml`: Updated with project context
|
||||
@@ -1,24 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Project identity
|
||||
|
||||
The project spec SHALL contain the project name, a description paragraph, and a core value statement that communicates the project's dual purpose: working AI chat interface and Blazor learning journey.
|
||||
|
||||
#### Scenario: Project description present
|
||||
|
||||
- **WHEN** an AI agent or developer reads the project spec
|
||||
- **THEN** they find the project name ("Chat Agent WebApp"), a description of what the app does, and the core value statement
|
||||
|
||||
### Requirement: Project constraints
|
||||
|
||||
The project spec SHALL enumerate all non-negotiable constraints that govern technical decisions across the project.
|
||||
|
||||
#### Scenario: Constraints enumerated
|
||||
|
||||
- **WHEN** a decision is made about technology, architecture, or approach
|
||||
- **THEN** the project spec provides the authoritative list of constraints to check against:
|
||||
- Tech stack: .NET / C# / Blazor WebAssembly
|
||||
- LLM provider: OpenAI GPT API
|
||||
- Storage: JSON files on local disk
|
||||
- Architecture: WASM client + backend API (API key stays server-side)
|
||||
- Code style: Every Blazor concept introduced MUST have inline comments explaining what it does and why
|
||||
@@ -1,51 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Core technology stack
|
||||
|
||||
The stack spec SHALL document the recommended core technologies with version, purpose, and rationale for each.
|
||||
|
||||
#### Scenario: Core stack documented
|
||||
|
||||
- **WHEN** a developer needs to add or update a dependency
|
||||
- **THEN** the stack spec provides the authoritative record of: .NET 9 SDK, Blazor WebAssembly Standalone, ASP.NET Core Web API, C# 13, OpenAI SDK 2.9.1, Markdig 1.1.1, MudBlazor 9.2.0, and System.Text.Json
|
||||
|
||||
### Requirement: Supporting libraries and tools
|
||||
|
||||
The stack spec SHALL document supporting libraries, development tools, and installation notes.
|
||||
|
||||
#### Scenario: Supporting libraries referenced
|
||||
|
||||
- **WHEN** a developer evaluates adding a new dependency
|
||||
- **THEN** the stack spec lists supporting libraries with guidance on when to use them (e.g., Microsoft.Extensions.AI — skip for v1)
|
||||
|
||||
### Requirement: Alternatives and exclusions
|
||||
|
||||
The stack spec SHALL document considered alternatives and explicitly excluded technologies with rationale.
|
||||
|
||||
#### Scenario: Alternative considered
|
||||
|
||||
- **WHEN** a developer proposes an alternative package or approach
|
||||
- **THEN** the stack spec provides a record of alternatives already evaluated and why the current choice was made
|
||||
|
||||
#### Scenario: Excluded technology referenced
|
||||
|
||||
- **WHEN** a developer considers using a technology on the exclusion list
|
||||
- **THEN** the stack spec explains why it was excluded and what to use instead
|
||||
|
||||
### Requirement: Stack patterns
|
||||
|
||||
The stack spec SHALL document implementation patterns that govern how stack technologies are used together (streaming, storage, markdown rendering).
|
||||
|
||||
#### Scenario: Pattern referenced during implementation
|
||||
|
||||
- **WHEN** a developer implements streaming, storage, or markdown rendering
|
||||
- **THEN** the stack spec provides the canonical pattern to follow
|
||||
|
||||
### Requirement: Version compatibility matrix
|
||||
|
||||
The stack spec SHALL maintain a compatibility matrix and list of authoritative sources for version decisions.
|
||||
|
||||
#### Scenario: Compatibility check
|
||||
|
||||
- **WHEN** a package version is being upgraded
|
||||
- **THEN** the stack spec provides the compatibility matrix to verify cross-package compatibility
|
||||
@@ -1,16 +0,0 @@
|
||||
## 1. Create main specs
|
||||
|
||||
- [x] 1.1 Create `openspec/specs/project/spec.md` with project description, core value, and constraints from CLAUDE.md
|
||||
- [x] 1.2 Create `openspec/specs/stack/spec.md` with full technology stack content from CLAUDE.md
|
||||
|
||||
## 2. Update config
|
||||
|
||||
- [x] 2.1 Populate `openspec/config.yaml` context field with project summary and tech stack headline
|
||||
|
||||
## 3. Slim down CLAUDE.md
|
||||
|
||||
- [x] 3.1 Replace CLAUDE.md contents with slim pointer file
|
||||
|
||||
## 4. Verify
|
||||
|
||||
- [x] 4.1 Confirm no content was lost — all substantive information is in specs
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-03
|
||||
@@ -1,48 +0,0 @@
|
||||
## Context
|
||||
|
||||
The project has a working Blazor WASM client and ASP.NET Core API with health check connectivity proven. The client currently uses Bootstrap for layout and has template pages (Counter, Weather). No UI component library is installed. This change introduces MudBlazor and builds the first real feature — a chat interface with hardcoded responses.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Install and configure MudBlazor as the UI component library
|
||||
- Build a ChatGPT/Gemini-inspired chat interface
|
||||
- Establish the message model and UI patterns that future phases will build on
|
||||
- Keep hardcoded responses so the UI is testable without API wiring
|
||||
|
||||
**Non-Goals:**
|
||||
- OpenAI API integration (future phase)
|
||||
- Markdown rendering of messages (future phase — Markdig)
|
||||
- Conversation persistence or history (future phase)
|
||||
- Multiple conversations / sidebar navigation (future phase)
|
||||
- Responsive mobile layout optimization
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: MudBlazor component choices
|
||||
|
||||
**Chat message list**: Use `MudPaper` cards inside a scrollable `div` (or `MudStack`). Each message gets a `MudPaper` with `Elevation="0"` and a background color to distinguish user vs assistant.
|
||||
|
||||
**Input area**: `MudTextField` with `Variant="Outlined"` and an `Adornment` send button (icon). This gives a single-line input with integrated send — similar to ChatGPT.
|
||||
|
||||
**Layout**: `MudLayout` + `MudAppBar` + `MudMainContent`. No drawer/sidebar yet — that comes when we add conversation management.
|
||||
|
||||
**Alternative considered**: Building with raw HTML/CSS. Rejected because MudBlazor is in the tech stack spec and provides the component patterns needed for later phases (dialogs, drawers, lists).
|
||||
|
||||
### Decision 2: ChatMessage model location
|
||||
|
||||
Place `ChatMessage.cs` in `ChatAgent.Shared` so it's available to both Client and API when API integration comes. Fields: `Role` (string: "user" or "assistant"), `Content` (string), `Timestamp` (DateTime).
|
||||
|
||||
### Decision 3: Chat page structure
|
||||
|
||||
The Chat.razor component owns the message list (`List<ChatMessage>`) and handles input. No separate service layer yet — the hardcoded response is inline in the component. When AI integration comes, a service will be extracted.
|
||||
|
||||
### Decision 4: Template page cleanup
|
||||
|
||||
Remove Counter.razor and Weather.razor. Move Home.razor from `/` to `/health` so the health check is still accessible but the chat page takes the root route.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [MudBlazor WASM bundle size] → Acceptable for a personal tool; AOT is deferred per stack spec
|
||||
- [No service abstraction for responses] → Intentional; extracting too early adds complexity before the pattern is clear. Will refactor when adding API integration.
|
||||
- [Removing template pages] → Low risk; they were scaffolding. Health check preserved at `/health`.
|
||||
@@ -1,35 +0,0 @@
|
||||
## Why
|
||||
|
||||
The project exists to be a working AI chat interface, but there is no chat UI yet — only a health check page. This change builds the foundational chat experience using MudBlazor components, with hardcoded responses so the UI can be developed and tested independently of the OpenAI API integration.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Install and configure MudBlazor in the Client project (NuGet, CSS/JS, providers, imports)
|
||||
- Replace the Bootstrap navbar/layout with a MudBlazor layout (MudLayout, MudAppBar, MudMainContent)
|
||||
- Create a Chat page with a message list and text input, styled after ChatGPT/Gemini
|
||||
- Add a shared `ChatMessage` model (role + content + timestamp)
|
||||
- Wire the input to append user messages and reply with a hardcoded bot response
|
||||
- Remove template pages (Counter, Weather) that are no longer needed
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `chat-ui`: The chat interface — message display, input handling, auto-scroll, and layout
|
||||
- `mudblazor-setup`: MudBlazor installation, theming, and provider configuration
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- None -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/ChatAgent.Client/ChatAgent.Client.csproj`: Add MudBlazor package
|
||||
- `src/ChatAgent.Client/wwwroot/index.html`: Add MudBlazor CSS/JS/font links
|
||||
- `src/ChatAgent.Client/_Imports.razor`: Add MudBlazor using
|
||||
- `src/ChatAgent.Client/Program.cs`: Add MudBlazor services
|
||||
- `src/ChatAgent.Client/Layout/MainLayout.razor`: Replace with MudBlazor layout
|
||||
- `src/ChatAgent.Client/Layout/NavMenu.razor`: Replace or remove Bootstrap nav
|
||||
- `src/ChatAgent.Client/Pages/Chat.razor`: New chat page (becomes default route)
|
||||
- `src/ChatAgent.Client/Pages/Home.razor`: Demote from `/` route or keep as `/health`
|
||||
- `src/ChatAgent.Shared/Models/ChatMessage.cs`: New shared message model
|
||||
- `src/ChatAgent.Client/Pages/Counter.razor`: Remove
|
||||
- `src/ChatAgent.Client/Pages/Weather.razor`: Remove
|
||||
@@ -1,66 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Message display
|
||||
|
||||
The chat page SHALL display messages in a vertically scrolling list, with each message showing the sender role (user or assistant), the message content, and a visual distinction between user and assistant messages (e.g., alignment, color, or avatar).
|
||||
|
||||
#### Scenario: User message displayed
|
||||
|
||||
- **WHEN** the user sends a message
|
||||
- **THEN** the message appears in the message list aligned or styled to indicate it is from the user
|
||||
|
||||
#### Scenario: Assistant message displayed
|
||||
|
||||
- **WHEN** the assistant responds
|
||||
- **THEN** the response appears in the message list with distinct styling from user messages (different alignment, color, or avatar)
|
||||
|
||||
#### Scenario: Message ordering
|
||||
|
||||
- **WHEN** multiple messages exist in the conversation
|
||||
- **THEN** messages are displayed in chronological order, oldest at top
|
||||
|
||||
### Requirement: Message input
|
||||
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages.
|
||||
|
||||
#### Scenario: Submit via button
|
||||
|
||||
- **WHEN** the user types text and clicks the send button
|
||||
- **THEN** the message is added to the conversation and the input is cleared
|
||||
|
||||
#### Scenario: Submit via Enter key
|
||||
|
||||
- **WHEN** the user types text and presses Enter
|
||||
- **THEN** the message is submitted (same as clicking send)
|
||||
|
||||
#### Scenario: Empty input blocked
|
||||
|
||||
- **WHEN** the user attempts to send an empty or whitespace-only message
|
||||
- **THEN** nothing is sent and no message is added
|
||||
|
||||
### Requirement: Hardcoded response
|
||||
|
||||
In this phase, the assistant SHALL reply with a hardcoded message to every user input. This stubs the AI integration point for future phases.
|
||||
|
||||
#### Scenario: Bot replies to any input
|
||||
|
||||
- **WHEN** the user sends any message
|
||||
- **THEN** the assistant replies with a hardcoded response (e.g., "This is a placeholder response. AI integration coming soon!")
|
||||
|
||||
### Requirement: Auto-scroll
|
||||
|
||||
The message list SHALL automatically scroll to the newest message when a new message is added.
|
||||
|
||||
#### Scenario: New message scrolls into view
|
||||
|
||||
- **WHEN** a new message (user or assistant) is added to the conversation
|
||||
- **THEN** the message list scrolls to the bottom so the new message is visible
|
||||
|
||||
### Requirement: Chat page is default route
|
||||
|
||||
The chat page SHALL be the default route (`/`) of the application.
|
||||
|
||||
#### Scenario: App opens to chat
|
||||
|
||||
- **WHEN** the user navigates to the root URL
|
||||
- **THEN** the chat page is displayed
|
||||
@@ -1,46 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: MudBlazor package installed
|
||||
|
||||
The Client project SHALL have MudBlazor 9.2.0 installed as a NuGet dependency.
|
||||
|
||||
#### Scenario: Package reference present
|
||||
|
||||
- **WHEN** the Client project is built
|
||||
- **THEN** MudBlazor 9.2.0 is resolved as a dependency
|
||||
|
||||
### Requirement: MudBlazor services registered
|
||||
|
||||
MudBlazor services SHALL be registered in the Client's DI container via `AddMudServices()`.
|
||||
|
||||
#### Scenario: Services available
|
||||
|
||||
- **WHEN** the application starts
|
||||
- **THEN** MudBlazor services (snackbar, dialog, etc.) are available for injection
|
||||
|
||||
### Requirement: MudBlazor assets loaded
|
||||
|
||||
The Client's `index.html` SHALL include MudBlazor CSS, JS, and font references.
|
||||
|
||||
#### Scenario: Styles and scripts present
|
||||
|
||||
- **WHEN** the application loads in the browser
|
||||
- **THEN** MudBlazor CSS (`_content/MudBlazor/MudBlazor.min.css`), JS (`_content/MudBlazor/MudBlazor.min.js`), and Material Design Icons font are loaded
|
||||
|
||||
### Requirement: MudBlazor layout providers
|
||||
|
||||
The app root SHALL include `MudThemeProvider`, `MudPopoverProvider`, and `MudDialogProvider` so MudBlazor components function correctly.
|
||||
|
||||
#### Scenario: Providers present
|
||||
|
||||
- **WHEN** any MudBlazor component is rendered
|
||||
- **THEN** it functions correctly because the required providers are in the component tree
|
||||
|
||||
### Requirement: MudBlazor layout replaces Bootstrap
|
||||
|
||||
The application layout SHALL use MudBlazor layout components (`MudLayout`, `MudAppBar`, `MudMainContent`) instead of the current Bootstrap navbar.
|
||||
|
||||
#### Scenario: Layout renders with MudBlazor
|
||||
|
||||
- **WHEN** any page is displayed
|
||||
- **THEN** the page is wrapped in a MudBlazor layout with an app bar showing the application name
|
||||
@@ -1,38 +0,0 @@
|
||||
## 1. MudBlazor Setup
|
||||
|
||||
- [x] 1.1 Install MudBlazor 9.2.0 NuGet package in ChatAgent.Client
|
||||
- [x] 1.2 Add MudBlazor CSS, JS, and Material Design Icons font to index.html (remove Bootstrap CSS)
|
||||
- [x] 1.3 Add `@using MudBlazor` to _Imports.razor
|
||||
- [x] 1.4 Register MudBlazor services (`AddMudServices()`) in Program.cs
|
||||
- [x] 1.5 Add MudThemeProvider, MudPopoverProvider, MudDialogProvider to MainLayout.razor
|
||||
|
||||
## 2. Layout Migration
|
||||
|
||||
- [x] 2.1 Replace MainLayout.razor with MudBlazor layout (MudLayout, MudAppBar, MudMainContent)
|
||||
- [x] 2.2 Remove NavMenu.razor (Bootstrap navbar no longer needed)
|
||||
- [x] 2.3 Remove MainLayout.razor.css (MudBlazor handles styling)
|
||||
|
||||
## 3. Shared Model
|
||||
|
||||
- [x] 3.1 Create ChatMessage.cs in ChatAgent.Shared/Models with Role, Content, Timestamp
|
||||
|
||||
## 4. Chat Page
|
||||
|
||||
- [x] 4.1 Create Chat.razor at route `/` with message list and input area
|
||||
- [x] 4.2 Implement message display with MudPaper cards (distinct styling for user vs assistant)
|
||||
- [x] 4.3 Implement text input with MudTextField and send button adornment
|
||||
- [x] 4.4 Wire Enter key and send button to submit handler
|
||||
- [x] 4.5 Block empty/whitespace-only submissions
|
||||
- [x] 4.6 Add hardcoded assistant response after each user message
|
||||
- [x] 4.7 Implement auto-scroll to bottom on new messages
|
||||
|
||||
## 5. Cleanup
|
||||
|
||||
- [x] 5.1 Move Home.razor route from `/` to `/health`
|
||||
- [x] 5.2 Remove Counter.razor and Weather.razor
|
||||
- [x] 5.3 Update app.css — remove Bootstrap-specific styles, keep custom styles that still apply
|
||||
|
||||
## 6. Verify
|
||||
|
||||
- [x] 6.1 Run `dotnet build` on the solution to confirm no errors
|
||||
- [ ] 6.2 Manually verify: chat page loads at `/`, messages display correctly, hardcoded response works
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-04
|
||||
@@ -1,66 +0,0 @@
|
||||
## Context
|
||||
|
||||
The chat UI has a hardcoded response stub. A local OpenAI-compatible proxy at `localhost:8317` serves the Responses API (`POST /v1/responses`) with Claude models. The existing architecture has a WASM client calling the API backend — we add a new endpoint that proxies to the Responses API and streams tokens back.
|
||||
|
||||
The Responses API streaming format uses SSE with events like `response.output_text.delta` carrying a `delta` field with text fragments.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Wire real AI responses through the existing client → backend → proxy chain
|
||||
- Stream tokens to the UI for responsive feel
|
||||
- Keep the proxy URL and model configurable (server-side only)
|
||||
- Show a thinking indicator while waiting for first token
|
||||
|
||||
**Non-Goals:**
|
||||
- Conversation history / multi-turn context (future phase)
|
||||
- Model selection UI (future phase)
|
||||
- Retry logic or rate limiting
|
||||
- Markdown rendering of responses (future phase — Markdig)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Backend proxies the Responses API
|
||||
|
||||
The WASM client cannot call `localhost:8317` directly (different origin, and we keep external service URLs server-side). The API backend gets a new `ChatController` that:
|
||||
1. Receives messages from the client
|
||||
2. Forwards them to `POST /v1/responses` with `"stream": true`
|
||||
3. Reads the SSE stream, extracts `response.output_text.delta` events
|
||||
4. Re-emits the text deltas as simple SSE events to the client
|
||||
|
||||
**Format for client SSE**: `data: {"text": "<delta>"}\n\n` for each token, and `data: [DONE]\n\n` at the end. This is simpler than forwarding the full Responses API event structure.
|
||||
|
||||
**Alternative considered**: Having the client call `localhost:8317` directly via CORS. Rejected — breaks the architecture constraint of keeping external URLs server-side.
|
||||
|
||||
### Decision 2: Client-side streaming with SetBrowserResponseStreamingEnabled
|
||||
|
||||
Per the stack spec, the client uses:
|
||||
- `SetBrowserResponseStreamingEnabled(true)` on the `HttpRequestMessage`
|
||||
- `HttpCompletionOption.ResponseHeadersRead` to start reading before the full response arrives
|
||||
- Line-by-line iteration of the response stream
|
||||
|
||||
This avoids any JavaScript interop for streaming.
|
||||
|
||||
### Decision 3: Simple DTOs in Shared project
|
||||
|
||||
Add `ChatRequest` (list of messages) and keep the existing `ChatMessage` model. The SSE parsing happens in `ChatApiClient` — no DTO needed for individual stream events since they're parsed inline.
|
||||
|
||||
### Decision 4: Configuration via appsettings.json
|
||||
|
||||
The API's `appsettings.json` gets:
|
||||
```json
|
||||
{
|
||||
"ResponsesApi": {
|
||||
"BaseUrl": "http://localhost:8317",
|
||||
"Model": "claude-sonnet-4-6"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the API project's appsettings (server-side, not exposed to the browser).
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Proxy adds latency] → Minimal for localhost; acceptable tradeoff for keeping URLs server-side
|
||||
- [No conversation history] → Intentional; each request is single-turn for now. Multi-turn comes in a future phase.
|
||||
- [No retry on stream failure] → If the stream breaks mid-response, the partial text stays visible and an error is shown. Good enough for phase 1.
|
||||
@@ -1,30 +0,0 @@
|
||||
## Why
|
||||
|
||||
The chat UI currently returns hardcoded responses. A local OpenAI-compatible proxy is running at `localhost:8317` that exposes the Responses API (`POST /v1/responses`) backed by Anthropic Claude models. This change wires the chat to produce real AI responses via streaming, replacing the hardcoded stub.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a chat endpoint to the API backend that proxies requests to the local Responses API
|
||||
- Stream tokens from the Responses API back to the WASM client as SSE
|
||||
- Update ChatApiClient with a streaming chat method
|
||||
- Replace the hardcoded response in Chat.razor with live streaming from the API
|
||||
- Add a "thinking" indicator while the assistant is responding
|
||||
- Disable input during streaming to prevent overlapping requests
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `chat-streaming`: Streaming AI responses from the Responses API proxy through the backend to the WASM client
|
||||
|
||||
### Modified Capabilities
|
||||
- `chat-ui`: Replace hardcoded response with streaming AI response, add typing indicator, disable input during streaming
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/ChatAgent.Api/ChatAgent.Api.csproj`: Add no new packages (uses built-in HttpClient)
|
||||
- `src/ChatAgent.Api/Controllers/ChatController.cs`: New controller proxying to Responses API
|
||||
- `src/ChatAgent.Api/Program.cs`: Register HttpClient for the proxy, add configuration
|
||||
- `src/ChatAgent.Api/appsettings.json`: New — configure Responses API base URL and model
|
||||
- `src/ChatAgent.Client/Services/ChatApiClient.cs`: Add streaming chat method
|
||||
- `src/ChatAgent.Client/Pages/Chat.razor`: Replace hardcoded response with streaming call
|
||||
- `src/ChatAgent.Shared/Models/`: New request/response DTOs for the chat endpoint
|
||||
@@ -1,51 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Chat endpoint proxies to Responses API
|
||||
|
||||
The API backend SHALL expose `POST /api/chat` that accepts a list of messages and proxies the request to the local Responses API at a configurable base URL using the `POST /v1/responses` endpoint.
|
||||
|
||||
#### Scenario: Successful proxy request
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat` with a message list
|
||||
- **THEN** the API forwards the messages to the Responses API with the configured model and returns the response
|
||||
|
||||
### Requirement: Streaming response delivery
|
||||
|
||||
The API backend SHALL stream the Responses API's SSE events back to the WASM client as `text/event-stream`, forwarding `response.output_text.delta` events so the client can render tokens incrementally.
|
||||
|
||||
#### Scenario: Tokens stream to client
|
||||
|
||||
- **WHEN** the Responses API emits `response.output_text.delta` events
|
||||
- **THEN** the backend forwards each delta as an SSE event to the client containing the text fragment
|
||||
|
||||
#### Scenario: Stream completes
|
||||
|
||||
- **WHEN** the Responses API emits `response.completed`
|
||||
- **THEN** the backend signals stream completion to the client
|
||||
|
||||
### Requirement: Configurable proxy target
|
||||
|
||||
The Responses API base URL and model name SHALL be configurable via `appsettings.json` in the API project, not hardcoded.
|
||||
|
||||
#### Scenario: Configuration read at startup
|
||||
|
||||
- **WHEN** the API starts
|
||||
- **THEN** it reads `ResponsesApi:BaseUrl` and `ResponsesApi:Model` from configuration
|
||||
|
||||
### Requirement: Client streams from backend
|
||||
|
||||
The WASM client SHALL call `POST /api/chat` with `SetBrowserResponseStreamingEnabled(true)` and `HttpCompletionOption.ResponseHeadersRead`, then iterate the SSE stream to update the UI token by token.
|
||||
|
||||
#### Scenario: Client reads streaming response
|
||||
|
||||
- **WHEN** the client sends a chat request
|
||||
- **THEN** it reads the response stream incrementally and appends each text delta to the assistant message in real time
|
||||
|
||||
### Requirement: Error propagation
|
||||
|
||||
If the Responses API returns an error or is unreachable, the API backend SHALL return an appropriate HTTP error status and the client SHALL display the error to the user.
|
||||
|
||||
#### Scenario: Proxy unreachable
|
||||
|
||||
- **WHEN** the Responses API is not running
|
||||
- **THEN** the client displays an error message instead of an assistant response
|
||||
@@ -1,50 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Hardcoded response
|
||||
|
||||
The assistant SHALL reply with a real AI response streamed from the backend API, replacing the previous hardcoded stub. Tokens appear incrementally as they arrive.
|
||||
|
||||
#### Scenario: Bot replies with streamed AI response
|
||||
|
||||
- **WHEN** the user sends any message
|
||||
- **THEN** the assistant message appears and grows token by token as the stream delivers text
|
||||
|
||||
### Requirement: Message input
|
||||
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages.
|
||||
|
||||
#### Scenario: Submit via button
|
||||
|
||||
- **WHEN** the user types text and clicks the send button
|
||||
- **THEN** the message is added to the conversation and the input is cleared
|
||||
|
||||
#### Scenario: Submit via Enter key
|
||||
|
||||
- **WHEN** the user types text and presses Enter
|
||||
- **THEN** the message is submitted (same as clicking send)
|
||||
|
||||
#### Scenario: Empty input blocked
|
||||
|
||||
- **WHEN** the user attempts to send an empty or whitespace-only message
|
||||
- **THEN** nothing is sent and no message is added
|
||||
|
||||
#### Scenario: Input disabled during streaming
|
||||
|
||||
- **WHEN** the assistant is currently streaming a response
|
||||
- **THEN** the input field and send button are disabled until streaming completes
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Thinking indicator
|
||||
|
||||
The chat page SHALL show a visual indicator while waiting for the first token from the assistant.
|
||||
|
||||
#### Scenario: Indicator shown during wait
|
||||
|
||||
- **WHEN** the user sends a message and the assistant has not yet started streaming
|
||||
- **THEN** a thinking indicator (e.g., animated dots) is shown in the assistant message area
|
||||
|
||||
#### Scenario: Indicator replaced by content
|
||||
|
||||
- **WHEN** the first token arrives from the stream
|
||||
- **THEN** the thinking indicator is replaced by the streamed text
|
||||
@@ -1,29 +0,0 @@
|
||||
## 1. Shared Models
|
||||
|
||||
- [ ] 1.1 Create ChatRequest.cs in ChatAgent.Shared/Models with a Messages list property
|
||||
|
||||
## 2. API Backend
|
||||
|
||||
- [ ] 2.1 Add appsettings.json to ChatAgent.Api with ResponsesApi:BaseUrl and ResponsesApi:Model
|
||||
- [ ] 2.2 Register an HttpClient for the Responses API proxy in Api Program.cs
|
||||
- [ ] 2.3 Create ChatController with POST /api/chat that proxies to the Responses API with streaming
|
||||
- [ ] 2.4 Parse Responses API SSE stream, extract response.output_text.delta events, re-emit as simplified SSE to client
|
||||
|
||||
## 3. Client Streaming
|
||||
|
||||
- [ ] 3.1 Add a streaming SendChatAsync method to ChatApiClient that uses SetBrowserResponseStreamingEnabled and HttpCompletionOption.ResponseHeadersRead
|
||||
- [ ] 3.2 Parse the simplified SSE stream line-by-line, yielding text deltas
|
||||
|
||||
## 4. Chat Page Updates
|
||||
|
||||
- [ ] 4.1 Replace hardcoded response in Chat.razor with a call to ChatApiClient.SendChatAsync
|
||||
- [ ] 4.2 Append tokens to the assistant message incrementally with StateHasChanged after each delta
|
||||
- [ ] 4.3 Add a thinking indicator shown until the first token arrives
|
||||
- [ ] 4.4 Disable input field and send button while streaming is in progress
|
||||
- [ ] 4.5 Handle errors — display error message if API call fails
|
||||
- [ ] 4.6 Auto-scroll during streaming (not just at the end)
|
||||
|
||||
## 5. Verify
|
||||
|
||||
- [ ] 5.1 Run dotnet build to confirm no errors
|
||||
- [ ] 5.2 Manually verify: send a message, see streaming response from Claude
|
||||
@@ -1,9 +1,20 @@
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
Chat Agent WebApp — personal AI chat app built with Blazor WebAssembly + OpenAI GPT API.
|
||||
Tech stack: .NET 9, C# 13, Blazor WASM (standalone client), ASP.NET Core Web API (backend proxy).
|
||||
Key libraries: OpenAI SDK 2.9.1, Markdig 1.1.1, MudBlazor 9.2.0, System.Text.Json.
|
||||
Storage: JSON files on local disk. Single-user, no auth.
|
||||
The project is also a Blazor tutorial — every concept must have inline comments explaining what and why.
|
||||
See openspec/specs/project/ and openspec/specs/stack/ for full details.
|
||||
# Project context (optional)
|
||||
# This is shown to AI when creating artifacts.
|
||||
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
||||
# Example:
|
||||
# context: |
|
||||
# Tech stack: TypeScript, React, Node.js
|
||||
# We use conventional commits
|
||||
# Domain: e-commerce platform
|
||||
|
||||
# Per-artifact rules (optional)
|
||||
# Add custom rules for specific artifacts.
|
||||
# Example:
|
||||
# rules:
|
||||
# proposal:
|
||||
# - Keep proposals under 500 words
|
||||
# - Always include a "Non-goals" section
|
||||
# tasks:
|
||||
# - Break tasks into chunks of max 2 hours
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
## Purpose
|
||||
|
||||
Define the chat interface — message display, input handling, auto-scroll, and routing.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Message display
|
||||
|
||||
The chat page SHALL display messages in a vertically scrolling list, with each message showing the sender role (user or assistant), the message content, and a visual distinction between user and assistant messages (e.g., alignment, color, or avatar).
|
||||
|
||||
#### Scenario: User message displayed
|
||||
|
||||
- **WHEN** the user sends a message
|
||||
- **THEN** the message appears in the message list aligned or styled to indicate it is from the user
|
||||
|
||||
#### Scenario: Assistant message displayed
|
||||
|
||||
- **WHEN** the assistant responds
|
||||
- **THEN** the response appears in the message list with distinct styling from user messages (different alignment, color, or avatar)
|
||||
|
||||
#### Scenario: Message ordering
|
||||
|
||||
- **WHEN** multiple messages exist in the conversation
|
||||
- **THEN** messages are displayed in chronological order, oldest at top
|
||||
|
||||
### Requirement: Message input
|
||||
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages.
|
||||
|
||||
#### Scenario: Submit via button
|
||||
|
||||
- **WHEN** the user types text and clicks the send button
|
||||
- **THEN** the message is added to the conversation and the input is cleared
|
||||
|
||||
#### Scenario: Submit via Enter key
|
||||
|
||||
- **WHEN** the user types text and presses Enter
|
||||
- **THEN** the message is submitted (same as clicking send)
|
||||
|
||||
#### Scenario: Empty input blocked
|
||||
|
||||
- **WHEN** the user attempts to send an empty or whitespace-only message
|
||||
- **THEN** nothing is sent and no message is added
|
||||
|
||||
### Requirement: Hardcoded response
|
||||
|
||||
In this phase, the assistant SHALL reply with a hardcoded message to every user input. This stubs the AI integration point for future phases.
|
||||
|
||||
#### Scenario: Bot replies to any input
|
||||
|
||||
- **WHEN** the user sends any message
|
||||
- **THEN** the assistant replies with a hardcoded response (e.g., "This is a placeholder response. AI integration coming soon!")
|
||||
|
||||
### Requirement: Auto-scroll
|
||||
|
||||
The message list SHALL automatically scroll to the newest message when a new message is added.
|
||||
|
||||
#### Scenario: New message scrolls into view
|
||||
|
||||
- **WHEN** a new message (user or assistant) is added to the conversation
|
||||
- **THEN** the message list scrolls to the bottom so the new message is visible
|
||||
|
||||
### Requirement: Chat page is default route
|
||||
|
||||
The chat page SHALL be the default route (`/`) of the application.
|
||||
|
||||
#### Scenario: App opens to chat
|
||||
|
||||
- **WHEN** the user navigates to the root URL
|
||||
- **THEN** the chat page is displayed
|
||||
@@ -1,50 +0,0 @@
|
||||
## Purpose
|
||||
|
||||
Define MudBlazor installation, theming, and provider configuration for the Client project.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: MudBlazor package installed
|
||||
|
||||
The Client project SHALL have MudBlazor 9.2.0 installed as a NuGet dependency.
|
||||
|
||||
#### Scenario: Package reference present
|
||||
|
||||
- **WHEN** the Client project is built
|
||||
- **THEN** MudBlazor 9.2.0 is resolved as a dependency
|
||||
|
||||
### Requirement: MudBlazor services registered
|
||||
|
||||
MudBlazor services SHALL be registered in the Client's DI container via `AddMudServices()`.
|
||||
|
||||
#### Scenario: Services available
|
||||
|
||||
- **WHEN** the application starts
|
||||
- **THEN** MudBlazor services (snackbar, dialog, etc.) are available for injection
|
||||
|
||||
### Requirement: MudBlazor assets loaded
|
||||
|
||||
The Client's `index.html` SHALL include MudBlazor CSS, JS, and font references.
|
||||
|
||||
#### Scenario: Styles and scripts present
|
||||
|
||||
- **WHEN** the application loads in the browser
|
||||
- **THEN** MudBlazor CSS (`_content/MudBlazor/MudBlazor.min.css`), JS (`_content/MudBlazor/MudBlazor.min.js`), and Material Design Icons font are loaded
|
||||
|
||||
### Requirement: MudBlazor layout providers
|
||||
|
||||
The app root SHALL include `MudThemeProvider`, `MudPopoverProvider`, and `MudDialogProvider` so MudBlazor components function correctly.
|
||||
|
||||
#### Scenario: Providers present
|
||||
|
||||
- **WHEN** any MudBlazor component is rendered
|
||||
- **THEN** it functions correctly because the required providers are in the component tree
|
||||
|
||||
### Requirement: MudBlazor layout replaces Bootstrap
|
||||
|
||||
The application layout SHALL use MudBlazor layout components (`MudLayout`, `MudAppBar`, `MudMainContent`) instead of the current Bootstrap navbar.
|
||||
|
||||
#### Scenario: Layout renders with MudBlazor
|
||||
|
||||
- **WHEN** any page is displayed
|
||||
- **THEN** the page is wrapped in a MudBlazor layout with an app bar showing the application name
|
||||
@@ -1,27 +0,0 @@
|
||||
## Purpose
|
||||
|
||||
Define the project identity, core value, and non-negotiable constraints for Chat Agent WebApp.
|
||||
## Requirements
|
||||
### Requirement: Project identity
|
||||
|
||||
The project spec SHALL contain the project name, a description paragraph, and a core value statement that communicates the project's dual purpose: working AI chat interface and Blazor learning journey.
|
||||
|
||||
#### Scenario: Project description present
|
||||
|
||||
- **WHEN** an AI agent or developer reads the project spec
|
||||
- **THEN** they find the project name ("Chat Agent WebApp"), a description of what the app does, and the core value statement
|
||||
|
||||
### Requirement: Project constraints
|
||||
|
||||
The project spec SHALL enumerate all non-negotiable constraints that govern technical decisions across the project.
|
||||
|
||||
#### Scenario: Constraints enumerated
|
||||
|
||||
- **WHEN** a decision is made about technology, architecture, or approach
|
||||
- **THEN** the project spec provides the authoritative list of constraints to check against:
|
||||
- Tech stack: .NET / C# / Blazor WebAssembly
|
||||
- LLM provider: OpenAI GPT API
|
||||
- Storage: JSON files on local disk
|
||||
- Architecture: WASM client + backend API (API key stays server-side)
|
||||
- Code style: Every Blazor concept introduced MUST have inline comments explaining what it does and why
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
## Purpose
|
||||
|
||||
Document the technology stack decisions for Chat Agent WebApp.
|
||||
## Requirements
|
||||
### Requirement: Core technology stack
|
||||
|
||||
The stack spec SHALL document the recommended core technologies with version, purpose, and rationale for each.
|
||||
|
||||
#### Scenario: Core stack documented
|
||||
|
||||
- **WHEN** a developer needs to add or update a dependency
|
||||
- **THEN** the stack spec provides the authoritative record of: .NET 9 SDK, Blazor WebAssembly Standalone, ASP.NET Core Web API, C# 13, OpenAI SDK 2.9.1, Markdig 1.1.1, MudBlazor 9.2.0, and System.Text.Json
|
||||
|
||||
### Requirement: Supporting libraries and tools
|
||||
|
||||
The stack spec SHALL document supporting libraries, development tools, and installation notes.
|
||||
|
||||
#### Scenario: Supporting libraries referenced
|
||||
|
||||
- **WHEN** a developer evaluates adding a new dependency
|
||||
- **THEN** the stack spec lists supporting libraries with guidance on when to use them (e.g., Microsoft.Extensions.AI — skip for v1)
|
||||
|
||||
### Requirement: Alternatives and exclusions
|
||||
|
||||
The stack spec SHALL document considered alternatives and explicitly excluded technologies with rationale.
|
||||
|
||||
#### Scenario: Alternative considered
|
||||
|
||||
- **WHEN** a developer proposes an alternative package or approach
|
||||
- **THEN** the stack spec provides a record of alternatives already evaluated and why the current choice was made
|
||||
|
||||
#### Scenario: Excluded technology referenced
|
||||
|
||||
- **WHEN** a developer considers using a technology on the exclusion list
|
||||
- **THEN** the stack spec explains why it was excluded and what to use instead
|
||||
|
||||
### Requirement: Stack patterns
|
||||
|
||||
The stack spec SHALL document implementation patterns that govern how stack technologies are used together (streaming, storage, markdown rendering).
|
||||
|
||||
#### Scenario: Pattern referenced during implementation
|
||||
|
||||
- **WHEN** a developer implements streaming, storage, or markdown rendering
|
||||
- **THEN** the stack spec provides the canonical pattern to follow
|
||||
|
||||
### Requirement: Version compatibility matrix
|
||||
|
||||
The stack spec SHALL maintain a compatibility matrix and list of authoritative sources for version decisions.
|
||||
|
||||
#### Scenario: Compatibility check
|
||||
|
||||
- **WHEN** a package version is being upgraded
|
||||
- **THEN** the stack spec provides the compatibility matrix to verify cross-package compatibility
|
||||
|
||||
91
src/ChatAgent.Client/Components/ChatInput.razor
Normal file
91
src/ChatAgent.Client/Components/ChatInput.razor
Normal file
@@ -0,0 +1,91 @@
|
||||
@*
|
||||
ChatInput.razor -- The message input bar at the bottom of the chat.
|
||||
|
||||
KEY BLAZOR CONCEPTS:
|
||||
- @bind-Value: Two-way data binding. When the user types, _messageText updates.
|
||||
When _messageText changes in code, the input reflects it. This is Blazor's
|
||||
equivalent of React's controlled components or Angular's [(ngModel)].
|
||||
- EventCallback: A typed delegate for child-to-parent communication. When the
|
||||
user clicks Send, this component invokes OnMessageSent, and the parent handles it.
|
||||
EventCallback is the Blazor pattern for "events flow up" (opposite of parameters
|
||||
which flow data down).
|
||||
- @onkeydown: Blazor's way to handle DOM events. The @ prefix binds a C# method
|
||||
to a JavaScript event. Blazor intercepts it via JS interop and calls your C# code.
|
||||
*@
|
||||
|
||||
@* max-width and margin:auto center the input bar to match the message list width. *@
|
||||
<div class="pa-3" style="border-top: 1px solid var(--mud-palette-lines-default); background: var(--mud-palette-background);">
|
||||
<div style="max-width: 768px; width: 100%; margin: 0 auto;">
|
||||
@* MudStack arranges children in a row. Spacing="2" adds gap between items.
|
||||
AlignItems centers them vertically within the row. *@
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
@* MudTextField is a Material Design text input.
|
||||
@bind-Value creates two-way binding to _messageText.
|
||||
Immediate="true" means the binding updates on every keystroke
|
||||
(default is on blur/focus-loss). We need this so Enter key handling
|
||||
always sees the latest typed text.
|
||||
Variant.Outlined draws a bordered input (vs Filled or Text). *@
|
||||
<MudTextField @bind-Value="_messageText"
|
||||
Placeholder="Type a message..."
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
@onkeydown="HandleKeyDown"
|
||||
FullWidth="true"
|
||||
Disabled="@_isSending" />
|
||||
@* MudIconButton renders a Material icon as a clickable button.
|
||||
Icons.Material.Filled.Send is a built-in MudBlazor icon constant.
|
||||
Disabled prevents double-sends while processing. *@
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Send"
|
||||
Color="Color.Primary"
|
||||
OnClick="SendMessage"
|
||||
Disabled="@(string.IsNullOrWhiteSpace(_messageText) || _isSending)" />
|
||||
</MudStack>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// Private field bound to the text input via @bind-Value.
|
||||
// The underscore prefix is a C# convention for private fields.
|
||||
private string _messageText = string.Empty;
|
||||
private bool _isSending;
|
||||
|
||||
/// <summary>
|
||||
/// EventCallback for notifying the parent when the user sends a message.
|
||||
/// The parent provides a handler method:
|
||||
/// <ChatInput OnMessageSent="@HandleNewMessage" />
|
||||
/// EventCallback<string> means the event carries a string payload (the message text).
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<string> OnMessageSent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Send button click (or Enter key). Invokes the parent's callback
|
||||
/// with the message text, then clears the input.
|
||||
/// </summary>
|
||||
private async Task SendMessage()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_messageText) || _isSending)
|
||||
return;
|
||||
|
||||
_isSending = true;
|
||||
|
||||
// InvokeAsync triggers the parent's event handler and passes the message text.
|
||||
// The parent (Chat.razor) will call ChatService.SendMessageAsync with this text.
|
||||
await OnMessageSent.InvokeAsync(_messageText);
|
||||
|
||||
_messageText = string.Empty;
|
||||
_isSending = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the message when the user presses Enter (without Shift).
|
||||
/// Shift+Enter can be used for multi-line input in a future phase.
|
||||
/// </summary>
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter" && !e.ShiftKey)
|
||||
{
|
||||
await SendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/ChatAgent.Client/Components/ChatMessageList.razor
Normal file
77
src/ChatAgent.Client/Components/ChatMessageList.razor
Normal file
@@ -0,0 +1,77 @@
|
||||
@*
|
||||
ChatMessageList.razor -- Renders the scrollable list of chat messages.
|
||||
|
||||
This is a "presentational" component -- it receives data via a [Parameter]
|
||||
and renders it. It does not manage state or call services directly.
|
||||
|
||||
KEY BLAZOR CONCEPTS:
|
||||
- [Parameter]: A property decorated with [Parameter] receives its value from
|
||||
the parent component's markup (like an HTML attribute). This is how data
|
||||
flows downward in Blazor's component tree (parent -> child).
|
||||
- @foreach: Razor syntax for looping. The @ prefix switches from HTML to C#.
|
||||
- Conditional CSS classes: We use a ternary expression to pick alignment
|
||||
based on IsUser, so user messages appear on the right and bot messages on the left.
|
||||
*@
|
||||
|
||||
@using ChatAgent.Client.Models
|
||||
|
||||
@* flex:1 fills available space; overflow-y:auto enables scrolling when messages exceed
|
||||
the visible area. flex-direction:column-reverse anchors content to the bottom,
|
||||
so the newest messages are always visible (like ChatGPT). *@
|
||||
<div style="flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column-reverse;">
|
||||
@* We wrap the messages in an inner div because column-reverse reverses the visual order
|
||||
of direct children. By putting all messages in a single child div, we keep their
|
||||
natural top-to-bottom order while the outer container anchors to the bottom. *@
|
||||
<div style="max-width: 768px; width: 100%; margin: 0 auto;">
|
||||
@if (Messages is null || Messages.Count == 0)
|
||||
{
|
||||
@* MudText is a MudBlazor typography component. Typo.Body1 sets the text style.
|
||||
Using MudBlazor components instead of raw HTML gives consistent Material Design styling. *@
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mt-4" Style="color: var(--mud-palette-text-secondary);">
|
||||
Send a message to get started.
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var message in Messages)
|
||||
{
|
||||
@* d-flex = display:flex (MudBlazor utility class, similar to Bootstrap).
|
||||
justify-end/justify-start control horizontal alignment of the chat bubble. *@
|
||||
<div class="d-flex @(message.IsUser ? "justify-end" : "justify-start") mb-3">
|
||||
@* MudPaper is a Material Design surface (a "card" with elevation/shadow).
|
||||
Elevation="1" adds a subtle shadow. Class applies padding and max-width. *@
|
||||
<MudPaper Elevation="1" Class="pa-3" Style="@GetBubbleStyle(message.IsUser)">
|
||||
<MudText Typo="Typo.body1">@message.Text</MudText>
|
||||
<MudText Typo="Typo.caption" Style="opacity: 0.6; margin-top: 4px;">
|
||||
@message.Timestamp.ToString("h:mm tt")
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The list of messages to display. Passed in from the parent Chat page.
|
||||
/// [Parameter] tells Blazor this value comes from the parent's markup:
|
||||
/// <ChatMessageList Messages="@someList" />
|
||||
/// Blazor re-renders this component when the parameter value changes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IReadOnlyList<ChatMessage>? Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Builds the inline style for a chat bubble. User messages get the primary color;
|
||||
/// bot messages get the surface color. This is in a method because Blazor component
|
||||
/// attributes do not support mixed C# and markup inline (RZ9986).
|
||||
/// </summary>
|
||||
private static string GetBubbleStyle(bool isUser)
|
||||
{
|
||||
var bg = isUser
|
||||
? "background-color: var(--mud-palette-primary); color: var(--mud-palette-primary-text);"
|
||||
: "background-color: var(--mud-palette-surface);";
|
||||
return $"max-width: 75%; border-radius: 16px; {bg}";
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,43 @@
|
||||
@* MainLayout.razor -- The root layout component using MudBlazor.
|
||||
@* MainLayout.razor -- The root layout component for the application.
|
||||
|
||||
MudBlazor requires three providers in the component tree for its components to work:
|
||||
- MudThemeProvider: Supplies the Material Design theme (colors, typography, spacing)
|
||||
- MudPopoverProvider: Manages popover/dropdown positioning (used by MudSelect, MudMenu, etc.)
|
||||
- MudDialogProvider: Enables the dialog service to render modal dialogs
|
||||
In Blazor, layout components wrap page content. Every routed page (@page)
|
||||
is rendered inside the layout's @Body placeholder. This is similar to
|
||||
_Layout.cshtml in MVC or master pages in Web Forms.
|
||||
|
||||
The layout uses MudLayout + MudAppBar + MudMainContent to create a standard
|
||||
Material Design app shell. MudMainContent automatically accounts for the AppBar
|
||||
height so page content doesn't render underneath it.
|
||||
MudBlazor requires certain provider components to be placed at the layout level:
|
||||
- MudThemeProvider: Applies the Material Design theme (colors, typography, spacing)
|
||||
- MudPopoverProvider: Renders popovers/tooltips outside the normal DOM flow
|
||||
so they are not clipped by parent overflow styles
|
||||
- MudDialogProvider: Renders dialogs (modal windows) at the root level
|
||||
- MudSnackbarProvider: Renders toast notifications at the root level
|
||||
*@
|
||||
|
||||
@* @inherits LayoutComponentBase makes this a layout component.
|
||||
LayoutComponentBase provides the Body property, which is a RenderFragment
|
||||
containing the routed page's content. Without this base class, @Body
|
||||
would not be available. *@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@* MudBlazor providers -- must be in the layout so they wrap all page content *@
|
||||
<MudThemeProvider />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudLayout>
|
||||
@* MudAppBar provides the top application bar. Dense reduces its height.
|
||||
The fixed position keeps it visible while scrolling. *@
|
||||
<MudAppBar Elevation="1" Dense="true">
|
||||
<MudText Typo="Typo.h6">Chat Agent</MudText>
|
||||
@* MudAppBar is a Material Design top bar. Elevation adds shadow depth.
|
||||
Fixed="true" keeps it pinned at the top when the page scrolls. *@
|
||||
<MudAppBar Elevation="1" Fixed="true">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Chat" Class="mr-2" />
|
||||
<MudText Typo="Typo.h6">ChatAgent</MudText>
|
||||
</MudAppBar>
|
||||
|
||||
@* MudMainContent renders the routed page content (same role as @Body in plain Blazor).
|
||||
It automatically adds top padding to clear the AppBar. *@
|
||||
<MudMainContent>
|
||||
@* MudMainContent automatically adds top padding to account for the fixed app bar,
|
||||
so page content doesn't render behind it. *@
|
||||
<MudMainContent Style="height: 100vh; display: flex; flex-direction: column;">
|
||||
@* @Body is where the routed page content renders.
|
||||
When the user navigates to "/", the Chat.razor component's markup
|
||||
appears here. The layout stays the same -- only @Body changes. *@
|
||||
@Body
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
77
src/ChatAgent.Client/Layout/MainLayout.razor.css
Normal file
77
src/ChatAgent.Client/Layout/MainLayout.razor.css
Normal file
@@ -0,0 +1,77 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
39
src/ChatAgent.Client/Layout/NavMenu.razor
Normal file
39
src/ChatAgent.Client/Layout/NavMenu.razor
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">ChatAgent.Client</a>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||
<nav class="nav flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="counter">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="weather">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
}
|
||||
83
src/ChatAgent.Client/Layout/NavMenu.razor.css
Normal file
83
src/ChatAgent.Client/Layout/NavMenu.razor.css
Normal file
@@ -0,0 +1,83 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
29
src/ChatAgent.Client/Models/ChatMessage.cs
Normal file
29
src/ChatAgent.Client/Models/ChatMessage.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// ChatMessage.cs -- Represents a single message in the chat conversation.
|
||||
//
|
||||
// This is a plain C# model (sometimes called a DTO -- Data Transfer Object).
|
||||
// It holds the data for one chat bubble: who sent it, what they said, and when.
|
||||
// Both the UI components and the ChatService reference this model.
|
||||
|
||||
namespace ChatAgent.Client.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A single message in the chat conversation.
|
||||
/// </summary>
|
||||
public class ChatMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The display text of the message.
|
||||
/// </summary>
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// True if the message was sent by the user; false if it came from the bot.
|
||||
/// The UI uses this to style and align the bubble differently.
|
||||
/// </summary>
|
||||
public bool IsUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the message was created. Used for display ordering.
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||
}
|
||||
@@ -1,164 +1,56 @@
|
||||
@* Chat.razor -- The main chat interface.
|
||||
@*
|
||||
Chat.razor -- The main chat page that composes ChatMessageList and ChatInput.
|
||||
|
||||
This is the primary page of the application, mapped to the root route "/".
|
||||
It displays a vertically scrolling message list and a text input at the bottom,
|
||||
styled after ChatGPT/Gemini.
|
||||
|
||||
Key Blazor concepts demonstrated:
|
||||
- @page routing (this component owns "/")
|
||||
- Two-way binding with @bind-Value on MudTextField
|
||||
- Event handling with @onclick and OnKeyDown
|
||||
- List rendering with @foreach over a List<T>
|
||||
- StateHasChanged() for manual re-render triggers
|
||||
- IJSRuntime for calling JavaScript (auto-scroll)
|
||||
- Conditional CSS classes based on data (user vs assistant styling)
|
||||
KEY BLAZOR CONCEPTS:
|
||||
- @page "/": This directive registers the component as a routable page.
|
||||
When the browser navigates to "/", Blazor's Router (in App.razor) renders
|
||||
this component inside MainLayout's @Body. The URL path is how Blazor picks
|
||||
which page component to show.
|
||||
- @inject: Requests a service from the DI container. This is the Razor syntax
|
||||
equivalent of constructor injection in regular C# classes. The service must be
|
||||
registered in Program.cs first.
|
||||
- StateHasChanged(): Tells Blazor "my state changed, please re-render."
|
||||
Blazor calls this automatically after event handlers (like button clicks),
|
||||
but since we update state inside an awaited service call, we call it explicitly
|
||||
to ensure the UI reflects the new messages immediately.
|
||||
*@
|
||||
|
||||
@page "/"
|
||||
@using ChatAgent.Client.Components
|
||||
@using ChatAgent.Client.Models
|
||||
|
||||
@* IJSRuntime lets us call JavaScript from C#. We use it to scroll the message
|
||||
container to the bottom after adding a new message, because Blazor has no
|
||||
built-in scroll API. *@
|
||||
@inject IJSRuntime JS
|
||||
@* @inject pulls ChatService from the DI container registered in Program.cs.
|
||||
"ChatService" is the type; "ChatService" after it is the property name we use in code. *@
|
||||
@inject ChatService ChatService
|
||||
|
||||
<PageTitle>Chat Agent</PageTitle>
|
||||
@* PageTitle sets the browser tab title. It works via the HeadOutlet
|
||||
registered in Program.cs (which manages <head> elements from components). *@
|
||||
<PageTitle>Chat</PageTitle>
|
||||
|
||||
@* Chat container: uses flexbox to fill available height.
|
||||
The message area grows to fill space; the input stays pinned at the bottom. *@
|
||||
<div class="chat-container">
|
||||
@* flex:1 makes this container fill all remaining space below the app bar.
|
||||
The inner flex-column stacks the message list (scrollable) above the input (fixed). *@
|
||||
<div style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
||||
@* ChatMessageList is our presentational component. We pass the message list as a parameter.
|
||||
The = sign in Messages="@..." assigns the value; the @ prefix evaluates the C# expression. *@
|
||||
<ChatMessageList Messages="@ChatService.Messages" />
|
||||
|
||||
@* Message list: scrollable area that grows to fill available space.
|
||||
The @ref directive captures a reference to this DOM element so we can
|
||||
scroll it programmatically via JavaScript interop. *@
|
||||
<div class="message-list" @ref="_messageListRef">
|
||||
@if (_messages.Count == 0)
|
||||
{
|
||||
@* Empty state shown before any messages are sent *@
|
||||
<div class="empty-state">
|
||||
<MudText Typo="Typo.h5" Align="Align.Center" Class="mb-2"
|
||||
Style="color: var(--mud-palette-text-secondary);">
|
||||
Chat Agent
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center"
|
||||
Style="color: var(--mud-palette-text-disabled);">
|
||||
Type a message to get started
|
||||
</MudText>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Render each message as a MudPaper card.
|
||||
@foreach iterates the list; Blazor re-renders this block when _messages changes.
|
||||
The CSS class changes based on Role to align user messages right, assistant left. *@
|
||||
@foreach (var message in _messages)
|
||||
{
|
||||
<div class="message-row @(message.Role == "user" ? "message-user" : "message-assistant")">
|
||||
<MudPaper Class="@($"message-bubble {(message.Role == "user" ? "bubble-user" : "bubble-assistant")}")"
|
||||
Elevation="0">
|
||||
<MudText Typo="Typo.body1">@message.Content</MudText>
|
||||
</MudPaper>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@* Input area: pinned at the bottom of the chat container.
|
||||
MudTextField with an Adornment provides the send button inside the text field,
|
||||
similar to ChatGPT's input design. *@
|
||||
<div class="input-area">
|
||||
<MudTextField @bind-Value="_userInput"
|
||||
Placeholder="Type a message..."
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@Icons.Material.Filled.Send"
|
||||
AdornmentColor="Color.Primary"
|
||||
OnAdornmentClick="SendMessage"
|
||||
OnKeyDown="HandleKeyDown"
|
||||
Immediate="true"
|
||||
FullWidth="true"
|
||||
AutoFocus="true" />
|
||||
</div>
|
||||
@* ChatInput fires OnMessageSent when the user clicks Send or presses Enter.
|
||||
We bind that event to our HandleNewMessage method. *@
|
||||
<ChatInput OnMessageSent="@HandleNewMessage" />
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// The conversation messages, displayed in the message list.
|
||||
// Using a simple List<T> since we only add to the end — no complex state management needed.
|
||||
private List<ChatMessage> _messages = new();
|
||||
|
||||
// The current text in the input field. Bound two-way via @bind-Value.
|
||||
private string _userInput = string.Empty;
|
||||
|
||||
// DOM reference to the message list div, used for auto-scrolling via JS interop.
|
||||
private ElementReference _messageListRef;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Enter key press to submit the message.
|
||||
/// KeyboardEventArgs gives us the key that was pressed.
|
||||
/// Called when ChatInput fires its OnMessageSent event.
|
||||
/// Delegates to ChatService, then triggers a re-render so the new messages appear.
|
||||
/// </summary>
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||||
private async Task HandleNewMessage(string messageText)
|
||||
{
|
||||
if (e.Key == "Enter" && !e.ShiftKey)
|
||||
{
|
||||
await SendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the user's message and appends a hardcoded assistant response.
|
||||
/// In future phases, this will call the API instead of using a hardcoded reply.
|
||||
/// </summary>
|
||||
private async Task SendMessage()
|
||||
{
|
||||
// Block empty or whitespace-only submissions
|
||||
if (string.IsNullOrWhiteSpace(_userInput))
|
||||
return;
|
||||
|
||||
// Add the user's message
|
||||
_messages.Add(new ChatMessage
|
||||
{
|
||||
Role = "user",
|
||||
Content = _userInput.Trim(),
|
||||
Timestamp = DateTime.UtcNow
|
||||
});
|
||||
|
||||
// Clear the input field
|
||||
_userInput = string.Empty;
|
||||
|
||||
// Add a hardcoded assistant response.
|
||||
// This is the stub that will be replaced with an API call in the next phase.
|
||||
_messages.Add(new ChatMessage
|
||||
{
|
||||
Role = "assistant",
|
||||
Content = "This is a placeholder response. AI integration coming soon!",
|
||||
Timestamp = DateTime.UtcNow
|
||||
});
|
||||
await ChatService.SendMessageAsync(messageText);
|
||||
|
||||
// StateHasChanged() tells Blazor to re-render this component.
|
||||
// It's needed here because we modified _messages after the initial render cycle.
|
||||
// Without this call, the new messages wouldn't appear until the next UI event.
|
||||
// After re-render, the updated ChatService.Messages list flows down
|
||||
// to ChatMessageList via its [Parameter], updating the UI.
|
||||
StateHasChanged();
|
||||
|
||||
// Auto-scroll to the bottom after rendering the new messages.
|
||||
// We use a small delay to ensure the DOM has updated before scrolling.
|
||||
await Task.Delay(50);
|
||||
await ScrollToBottom();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls the message list to the bottom using JavaScript interop.
|
||||
/// Blazor has no built-in scroll API, so we call a tiny JS snippet directly.
|
||||
/// InvokeVoidAsync calls a JS function that returns nothing (void).
|
||||
/// </summary>
|
||||
private async Task ScrollToBottom()
|
||||
{
|
||||
try
|
||||
{
|
||||
await JS.InvokeVoidAsync("eval",
|
||||
"document.querySelector('.message-list').scrollTop = document.querySelector('.message-list').scrollHeight");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore scroll errors — non-critical UI enhancement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/* Chat.razor.css -- Scoped styles for the chat interface.
|
||||
*
|
||||
* Blazor CSS isolation: this file is automatically scoped to Chat.razor.
|
||||
* Styles here only apply to elements rendered by this component, preventing
|
||||
* conflicts with other pages. The build system adds a unique attribute
|
||||
* (e.g., b-abc123) to both the CSS selectors and the rendered HTML.
|
||||
*
|
||||
* ::deep is needed for styles that target child component markup (like MudPaper)
|
||||
* because those elements are rendered by MudBlazor, not directly by this component.
|
||||
*/
|
||||
|
||||
/* Chat container: flexbox column that fills the viewport below the AppBar.
|
||||
* The message-list grows to fill available space; input-area stays at the bottom. */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px); /* 48px = MudAppBar Dense height */
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Scrollable message area */
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 1rem 0.5rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Empty state centered in the message area */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Message row: controls horizontal alignment */
|
||||
.message-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.message-user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.message-assistant {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Message bubbles — ::deep is required because MudPaper renders its own elements */
|
||||
::deep .message-bubble {
|
||||
max-width: 75%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 1rem;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
::deep .bubble-user {
|
||||
background-color: var(--mud-palette-primary);
|
||||
color: white;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
|
||||
::deep .bubble-assistant {
|
||||
background-color: var(--mud-palette-surface);
|
||||
border: 1px solid var(--mud-palette-lines-default);
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* Input area pinned at the bottom */
|
||||
.input-area {
|
||||
padding: 0.75rem 1rem 1rem 1rem;
|
||||
border-top: 1px solid var(--mud-palette-lines-default);
|
||||
background-color: var(--mud-palette-background);
|
||||
}
|
||||
18
src/ChatAgent.Client/Pages/Counter.razor
Normal file
18
src/ChatAgent.Client/Pages/Counter.razor
Normal file
@@ -0,0 +1,18 @@
|
||||
@page "/counter"
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@* Home.razor -- The health check page for ChatAgent.
|
||||
@* Home.razor -- The landing page for ChatAgent.
|
||||
|
||||
@page "/health" maps this component to the /health URL. Chat.razor now owns "/".
|
||||
The Blazor router renders this component inside MainLayout's @Body placeholder.
|
||||
@page "/" maps this component to the root URL. When a user navigates to "/",
|
||||
the Blazor router renders this component inside MainLayout's @Body placeholder.
|
||||
|
||||
This page demonstrates the health check round-trip:
|
||||
1. On load, it calls the API's /api/health endpoint via ChatApiClient
|
||||
@@ -10,7 +10,7 @@
|
||||
*@
|
||||
|
||||
@* @page directive maps this component to a URL route.
|
||||
"/health" provides access to the health check page (Chat.razor now owns "/"). *@
|
||||
"/health" keeps the health check accessible while "/" is now the chat page. *@
|
||||
@page "/health"
|
||||
|
||||
@* Import the service and model namespaces for this component.
|
||||
|
||||
57
src/ChatAgent.Client/Pages/Weather.razor
Normal file
57
src/ChatAgent.Client/Pages/Weather.razor
Normal file
@@ -0,0 +1,57 @@
|
||||
@page "/weather"
|
||||
@inject HttpClient Http
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates fetching data from the server.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th aria-label="Temperature in Celsius">Temp. (C)</th>
|
||||
<th aria-label="Temperature in Farenheit">Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[]? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
|
||||
}
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public string? Summary { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,6 @@ var apiBaseUrl = isHttps
|
||||
? builder.Configuration["ApiBaseUrl_Https"] ?? "https://localhost:7100"
|
||||
: builder.Configuration["ApiBaseUrl_Http"] ?? "http://localhost:7000";
|
||||
|
||||
// AddMudServices registers MudBlazor's internal services (snackbar, dialog, popover, etc.)
|
||||
// into the DI container. This is required before any MudBlazor component will work.
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
// AddHttpClient<ChatApiClient> registers a typed HttpClient using IHttpClientFactory.
|
||||
// IHttpClientFactory manages the underlying HttpMessageHandler lifetime to prevent
|
||||
// socket exhaustion (a common problem with raw HttpClient in long-running apps).
|
||||
@@ -54,4 +50,13 @@ builder.Services.AddHttpClient<ChatApiClient>(client =>
|
||||
client.BaseAddress = new Uri(apiBaseUrl);
|
||||
});
|
||||
|
||||
// AddMudServices registers MudBlazor's internal services (theme, popover, scroll, etc.)
|
||||
// into the DI container. Required for MudBlazor components to function.
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
// Register ChatService as a Singleton. In Blazor WASM, Singleton means "one instance per
|
||||
// browser tab" -- there is no server-side shared state. This keeps the message list
|
||||
// alive across page navigations within the same tab session.
|
||||
builder.Services.AddSingleton<ChatService>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
|
||||
65
src/ChatAgent.Client/Services/ChatService.cs
Normal file
65
src/ChatAgent.Client/Services/ChatService.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
// ChatService.cs -- Client-side service that manages chat state and produces responses.
|
||||
//
|
||||
// WHY A SERVICE? In Blazor, services registered in DI live for the lifetime of the app
|
||||
// (when registered as Singleton in WASM -- there is only one "user" per browser tab).
|
||||
// This keeps chat state (the message list) separate from UI components, following the
|
||||
// "service extracts logic from components" pattern. Components inject this service
|
||||
// and call its methods, rather than holding all state themselves.
|
||||
//
|
||||
// CURRENT PHASE (Echo): SendMessageAsync always returns "success msg!".
|
||||
// FUTURE: This service will call ChatApiClient to reach the backend, which will
|
||||
// forward the message to an external AI service.
|
||||
|
||||
using ChatAgent.Client.Models;
|
||||
|
||||
namespace ChatAgent.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the chat conversation state and produces responses.
|
||||
/// Registered as a Singleton in Program.cs so all components share the same message list.
|
||||
/// In Blazor WASM, Singleton means "one instance per browser tab" (there is no shared server state).
|
||||
/// </summary>
|
||||
public class ChatService
|
||||
{
|
||||
// The conversation history. Components read this list to render messages.
|
||||
// Using a List<T> (not IReadOnlyList) for simplicity in this phase.
|
||||
private readonly List<ChatMessage> _messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// The full conversation history, oldest first.
|
||||
/// Components bind to this to render the chat bubbles.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ChatMessage> Messages => _messages;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user's message to the conversation, generates a response, and adds that too.
|
||||
/// Returns the bot's response message.
|
||||
///
|
||||
/// The method is async (returns Task) even though the current echo implementation is synchronous.
|
||||
/// This is intentional -- when we later replace the echo with an HTTP call to the API,
|
||||
/// the method signature won't need to change, and all callers already await it.
|
||||
/// </summary>
|
||||
public Task<ChatMessage> SendMessageAsync(string userText)
|
||||
{
|
||||
// Add the user's message to the conversation
|
||||
var userMessage = new ChatMessage
|
||||
{
|
||||
Text = userText,
|
||||
IsUser = true,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
_messages.Add(userMessage);
|
||||
|
||||
// Echo phase: always respond with "success msg!"
|
||||
// Future: replace this with an HTTP call via ChatApiClient
|
||||
var botMessage = new ChatMessage
|
||||
{
|
||||
Text = "success msg!",
|
||||
IsUser = false,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
_messages.Add(botMessage);
|
||||
|
||||
return Task.FromResult(botMessage);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using MudBlazor
|
||||
@using ChatAgent.Client
|
||||
@using ChatAgent.Client.Layout
|
||||
@using ChatAgent.Client.Services
|
||||
@using ChatAgent.Shared.Models
|
||||
@using MudBlazor
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
/* app.css -- Application-wide styles for ChatAgent.
|
||||
/* app.css -- Application styles for ChatAgent (Phase 1).
|
||||
*
|
||||
* MudBlazor handles most styling via its component library.
|
||||
* This file contains only:
|
||||
* - Blazor framework styles (error UI, loading progress) that must stay
|
||||
* - Global overrides if needed
|
||||
* Phase 1 uses plain HTML/CSS (D-10) with a light theme (D-11).
|
||||
* MudBlazor will be introduced in Phase 5 for UI polish.
|
||||
* These styles provide a clean, minimal appearance for the health check page.
|
||||
*/
|
||||
|
||||
html, body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.health-status {
|
||||
padding: 1rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.health-status p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
padding: 1rem;
|
||||
border: 1px solid #d32f2f;
|
||||
border-radius: 8px;
|
||||
background-color: #fce4ec;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: #666666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Blazor error UI -- shown when an unhandled exception occurs.
|
||||
* This is built into the Blazor template's index.html and should be kept. */
|
||||
#blazor-error-ui {
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Chat Agent</title>
|
||||
<base href="/" />
|
||||
|
||||
<!-- MudBlazor CSS — provides all component styles and Material Design baseline -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="ChatAgent.Client.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
|
||||
<!-- MudBlazor JS — required for popover positioning, scroll handling, etc. -->
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ChatAgent.Client</title>
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="ChatAgent.Client.styles.css" rel="stylesheet" />
|
||||
<!-- MudBlazor CSS: Material Design styles for all MudBlazor components -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<!-- MudBlazor JS: required for interactive features (popover positioning, scroll, etc.) -->
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// ChatMessage.cs -- Shared DTO representing a single message in a conversation.
|
||||
//
|
||||
// This model lives in ChatAgent.Shared so both the WASM client and the API can use it.
|
||||
// In Phase 1, messages only exist in-memory on the client. Later phases will serialize
|
||||
// these to JSON files on the server for persistence.
|
||||
|
||||
namespace ChatAgent.Shared.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single chat message with a sender role, text content, and timestamp.
|
||||
/// Role is "user" for messages the human typed, "assistant" for AI (or hardcoded) replies.
|
||||
/// </summary>
|
||||
public class ChatMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Who sent the message: "user" or "assistant".
|
||||
/// Uses string rather than enum so it matches the OpenAI API's role format directly.
|
||||
/// </summary>
|
||||
public string Role { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the message.
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// When the message was created (UTC).
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user