Compare commits
12 Commits
697e9dce23
...
chatendpoi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
956ec243c5 | ||
|
|
d46b179221 | ||
|
|
5b027eb0db | ||
|
|
7a5c22593a | ||
|
|
d3300c7db9 | ||
|
|
471e9ce935 | ||
|
|
3278a408b9 | ||
|
|
17a5a58e73 | ||
|
|
00e7df2802 | ||
|
|
1614a61617 | ||
|
|
a462b7dbc7 | ||
|
|
7dd4243f01 |
152
.claude/commands/opsx/apply.md
Normal file
152
.claude/commands/opsx/apply.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Apply"
|
||||||
|
description: Implement tasks from an OpenSpec change (Experimental)
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, artifacts, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement tasks from an OpenSpec change.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
|
||||||
|
|
||||||
|
2. **Check status to understand the schema**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used (e.g., "spec-driven")
|
||||||
|
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
|
||||||
|
|
||||||
|
3. **Get apply instructions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openspec instructions apply --change "<name>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns:
|
||||||
|
- Context file paths (varies by schema)
|
||||||
|
- Progress (total, complete, remaining)
|
||||||
|
- Task list with status
|
||||||
|
- Dynamic instruction based on current state
|
||||||
|
|
||||||
|
**Handle states:**
|
||||||
|
- If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue`
|
||||||
|
- If `state: "all_done"`: congratulate, suggest archive
|
||||||
|
- Otherwise: proceed to implementation
|
||||||
|
|
||||||
|
4. **Read context files**
|
||||||
|
|
||||||
|
Read the files listed in `contextFiles` from the apply instructions output.
|
||||||
|
The files depend on the schema being used:
|
||||||
|
- **spec-driven**: proposal, specs, design, tasks
|
||||||
|
- Other schemas: follow the contextFiles from CLI output
|
||||||
|
|
||||||
|
5. **Show current progress**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Schema being used
|
||||||
|
- Progress: "N/M tasks complete"
|
||||||
|
- Remaining tasks overview
|
||||||
|
- Dynamic instruction from CLI
|
||||||
|
|
||||||
|
6. **Implement tasks (loop until done or blocked)**
|
||||||
|
|
||||||
|
For each pending task:
|
||||||
|
- Show which task is being worked on
|
||||||
|
- Make the code changes required
|
||||||
|
- Keep changes minimal and focused
|
||||||
|
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
||||||
|
- Continue to next task
|
||||||
|
|
||||||
|
**Pause if:**
|
||||||
|
- Task is unclear → ask for clarification
|
||||||
|
- Implementation reveals a design issue → suggest updating artifacts
|
||||||
|
- Error or blocker encountered → report and wait for guidance
|
||||||
|
- User interrupts
|
||||||
|
|
||||||
|
7. **On completion or pause, show status**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Tasks completed this session
|
||||||
|
- Overall progress: "N/M tasks complete"
|
||||||
|
- If all done: suggest archive
|
||||||
|
- If paused: explain why and wait for guidance
|
||||||
|
|
||||||
|
**Output During Implementation**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementing: <change-name> (schema: <schema-name>)
|
||||||
|
|
||||||
|
Working on task 3/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
|
||||||
|
Working on task 4/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Completion**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 7/7 tasks complete ✓
|
||||||
|
|
||||||
|
### Completed This Session
|
||||||
|
- [x] Task 1
|
||||||
|
- [x] Task 2
|
||||||
|
...
|
||||||
|
|
||||||
|
All tasks complete! You can archive this change with `/opsx:archive`.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Pause (Issue Encountered)**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Paused
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 4/7 tasks complete
|
||||||
|
|
||||||
|
### Issue Encountered
|
||||||
|
<description of the issue>
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
1. <option 1>
|
||||||
|
2. <option 2>
|
||||||
|
3. Other approach
|
||||||
|
|
||||||
|
What would you like to do?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Keep going through tasks until done or blocked
|
||||||
|
- Always read context files before starting (from the apply instructions output)
|
||||||
|
- If task is ambiguous, pause and ask before implementing
|
||||||
|
- If implementation reveals issues, pause and suggest artifact updates
|
||||||
|
- Keep code changes minimal and scoped to each task
|
||||||
|
- Update task checkbox immediately after completing each task
|
||||||
|
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||||
|
- Use contextFiles from CLI output, don't assume specific file names
|
||||||
|
|
||||||
|
**Fluid Workflow Integration**
|
||||||
|
|
||||||
|
This skill supports the "actions on a change" model:
|
||||||
|
|
||||||
|
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
|
||||||
|
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
|
||||||
157
.claude/commands/opsx/archive.md
Normal file
157
.claude/commands/opsx/archive.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Archive"
|
||||||
|
description: Archive a completed change in the experimental workflow
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, archive, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Archive a completed change in the experimental workflow.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name after `/opsx:archive` (e.g., `/opsx:archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **If no change name provided, prompt for selection**
|
||||||
|
|
||||||
|
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||||
|
|
||||||
|
Show only active changes (not already archived).
|
||||||
|
Include the schema used for each change if available.
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||||
|
|
||||||
|
2. **Check artifact completion status**
|
||||||
|
|
||||||
|
Run `openspec status --change "<name>" --json` to check artifact completion.
|
||||||
|
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used
|
||||||
|
- `artifacts`: List of artifacts with their status (`done` or other)
|
||||||
|
|
||||||
|
**If any artifacts are not `done`:**
|
||||||
|
- Display warning listing incomplete artifacts
|
||||||
|
- Prompt user for confirmation to continue
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
3. **Check task completion status**
|
||||||
|
|
||||||
|
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
|
||||||
|
|
||||||
|
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
|
||||||
|
|
||||||
|
**If incomplete tasks found:**
|
||||||
|
- Display warning showing count of incomplete tasks
|
||||||
|
- Prompt user for confirmation to continue
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
**If no tasks file exists:** Proceed without task-related warning.
|
||||||
|
|
||||||
|
4. **Assess delta spec sync state**
|
||||||
|
|
||||||
|
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
|
||||||
|
|
||||||
|
**If delta specs exist:**
|
||||||
|
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
|
||||||
|
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||||
|
- Show a combined summary before prompting
|
||||||
|
|
||||||
|
**Prompt options:**
|
||||||
|
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||||
|
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||||
|
|
||||||
|
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
|
||||||
|
|
||||||
|
5. **Perform the archive**
|
||||||
|
|
||||||
|
Create the archive directory if it doesn't exist:
|
||||||
|
```bash
|
||||||
|
mkdir -p openspec/changes/archive
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate target name using current date: `YYYY-MM-DD-<change-name>`
|
||||||
|
|
||||||
|
**Check if target already exists:**
|
||||||
|
- If yes: Fail with error, suggest renaming existing archive or using different date
|
||||||
|
- If no: Move the change directory to archive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Display summary**
|
||||||
|
|
||||||
|
Show archive completion summary including:
|
||||||
|
- Change name
|
||||||
|
- Schema that was used
|
||||||
|
- Archive location
|
||||||
|
- Spec sync status (synced / sync skipped / no delta specs)
|
||||||
|
- Note about any warnings (incomplete artifacts/tasks)
|
||||||
|
|
||||||
|
**Output On Success**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Archive Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||||
|
**Specs:** ✓ Synced to main specs
|
||||||
|
|
||||||
|
All artifacts complete. All tasks complete.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Success (No Delta Specs)**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Archive Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||||
|
**Specs:** No delta specs
|
||||||
|
|
||||||
|
All artifacts complete. All tasks complete.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Success With Warnings**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Archive Complete (with warnings)
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||||
|
**Specs:** Sync skipped (user chose to skip)
|
||||||
|
|
||||||
|
**Warnings:**
|
||||||
|
- Archived with 2 incomplete artifacts
|
||||||
|
- Archived with 3 incomplete tasks
|
||||||
|
- Delta spec sync was skipped (user chose to skip)
|
||||||
|
|
||||||
|
Review the archive if this was not intentional.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Error (Archive Exists)**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Archive Failed
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||||
|
|
||||||
|
Target archive directory already exists.
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
1. Rename the existing archive
|
||||||
|
2. Delete the existing archive if it's a duplicate
|
||||||
|
3. Wait until a different date to archive
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Always prompt for change selection if not provided
|
||||||
|
- Use artifact graph (openspec status --json) for completion checking
|
||||||
|
- Don't block archive on warnings - just inform and confirm
|
||||||
|
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||||
|
- Show clear summary of what happened
|
||||||
|
- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
|
||||||
|
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
|
||||||
242
.claude/commands/opsx/bulk-archive.md
Normal file
242
.claude/commands/opsx/bulk-archive.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
114
.claude/commands/opsx/continue.md
Normal file
114
.claude/commands/opsx/continue.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
173
.claude/commands/opsx/explore.md
Normal file
173
.claude/commands/opsx/explore.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Explore"
|
||||||
|
description: "Enter explore mode - think through ideas, investigate problems, clarify requirements"
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, explore, experimental, thinking]
|
||||||
|
---
|
||||||
|
|
||||||
|
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||||
|
|
||||||
|
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||||
|
|
||||||
|
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||||
|
|
||||||
|
**Input**: The argument after `/opsx:explore` is whatever the user wants to think about. Could be:
|
||||||
|
- A vague idea: "real-time collaboration"
|
||||||
|
- A specific problem: "the auth system is getting unwieldy"
|
||||||
|
- A change name: "add-dark-mode" (to explore in context of that change)
|
||||||
|
- A comparison: "postgres vs sqlite for this"
|
||||||
|
- Nothing (just enter explore mode)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Stance
|
||||||
|
|
||||||
|
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||||
|
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||||
|
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||||
|
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||||
|
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||||
|
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Might Do
|
||||||
|
|
||||||
|
Depending on what the user brings, you might:
|
||||||
|
|
||||||
|
**Explore the problem space**
|
||||||
|
- Ask clarifying questions that emerge from what they said
|
||||||
|
- Challenge assumptions
|
||||||
|
- Reframe the problem
|
||||||
|
- Find analogies
|
||||||
|
|
||||||
|
**Investigate the codebase**
|
||||||
|
- Map existing architecture relevant to the discussion
|
||||||
|
- Find integration points
|
||||||
|
- Identify patterns already in use
|
||||||
|
- Surface hidden complexity
|
||||||
|
|
||||||
|
**Compare options**
|
||||||
|
- Brainstorm multiple approaches
|
||||||
|
- Build comparison tables
|
||||||
|
- Sketch tradeoffs
|
||||||
|
- Recommend a path (if asked)
|
||||||
|
|
||||||
|
**Visualize**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Use ASCII diagrams liberally │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ State │────────▶│ State │ │
|
||||||
|
│ │ A │ │ B │ │
|
||||||
|
│ └────────┘ └────────┘ │
|
||||||
|
│ │
|
||||||
|
│ System diagrams, state machines, │
|
||||||
|
│ data flows, architecture sketches, │
|
||||||
|
│ dependency graphs, comparison tables │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Surface risks and unknowns**
|
||||||
|
- Identify what could go wrong
|
||||||
|
- Find gaps in understanding
|
||||||
|
- Suggest spikes or investigations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenSpec Awareness
|
||||||
|
|
||||||
|
You have full context of the OpenSpec system. Use it naturally, don't force it.
|
||||||
|
|
||||||
|
### Check for context
|
||||||
|
|
||||||
|
At the start, quickly check what exists:
|
||||||
|
```bash
|
||||||
|
openspec list --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells you:
|
||||||
|
- If there are active changes
|
||||||
|
- Their names, schemas, and status
|
||||||
|
- What the user might be working on
|
||||||
|
|
||||||
|
If the user mentioned a specific change name, read its artifacts for context.
|
||||||
|
|
||||||
|
### When no change exists
|
||||||
|
|
||||||
|
Think freely. When insights crystallize, you might offer:
|
||||||
|
|
||||||
|
- "This feels solid enough to start a change. Want me to create a proposal?"
|
||||||
|
- Or keep exploring - no pressure to formalize
|
||||||
|
|
||||||
|
### When a change exists
|
||||||
|
|
||||||
|
If the user mentions a change or you detect one is relevant:
|
||||||
|
|
||||||
|
1. **Read existing artifacts for context**
|
||||||
|
- `openspec/changes/<name>/proposal.md`
|
||||||
|
- `openspec/changes/<name>/design.md`
|
||||||
|
- `openspec/changes/<name>/tasks.md`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
2. **Reference them naturally in conversation**
|
||||||
|
- "Your design mentions using Redis, but we just realized SQLite fits better..."
|
||||||
|
- "The proposal scopes this to premium users, but we're now thinking everyone..."
|
||||||
|
|
||||||
|
3. **Offer to capture when decisions are made**
|
||||||
|
|
||||||
|
| Insight Type | Where to Capture |
|
||||||
|
|--------------|------------------|
|
||||||
|
| New requirement discovered | `specs/<capability>/spec.md` |
|
||||||
|
| Requirement changed | `specs/<capability>/spec.md` |
|
||||||
|
| Design decision made | `design.md` |
|
||||||
|
| Scope changed | `proposal.md` |
|
||||||
|
| New work identified | `tasks.md` |
|
||||||
|
| Assumption invalidated | Relevant artifact |
|
||||||
|
|
||||||
|
Example offers:
|
||||||
|
- "That's a design decision. Capture it in design.md?"
|
||||||
|
- "This is a new requirement. Add it to specs?"
|
||||||
|
- "This changes scope. Update the proposal?"
|
||||||
|
|
||||||
|
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Don't Have To Do
|
||||||
|
|
||||||
|
- Follow a script
|
||||||
|
- Ask the same questions every time
|
||||||
|
- Produce a specific artifact
|
||||||
|
- Reach a conclusion
|
||||||
|
- Stay on topic if a tangent is valuable
|
||||||
|
- Be brief (this is thinking time)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ending Discovery
|
||||||
|
|
||||||
|
There's no required ending. Discovery might:
|
||||||
|
|
||||||
|
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
|
||||||
|
- **Result in artifact updates**: "Updated design.md with these decisions"
|
||||||
|
- **Just provide clarity**: User has what they need, moves on
|
||||||
|
- **Continue later**: "We can pick this up anytime"
|
||||||
|
|
||||||
|
When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||||
|
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||||
|
- **Don't rush** - Discovery is thinking time, not task time
|
||||||
|
- **Don't force structure** - Let patterns emerge naturally
|
||||||
|
- **Don't auto-capture** - Offer to save insights, don't just do it
|
||||||
|
- **Do visualize** - A good diagram is worth many paragraphs
|
||||||
|
- **Do explore the codebase** - Ground discussions in reality
|
||||||
|
- **Do question assumptions** - Including the user's and your own
|
||||||
227
.claude/commands/opsx/export-spec.md
Normal file
227
.claude/commands/opsx/export-spec.md
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Export Spec"
|
||||||
|
description: Export a feature as a compact, portable spec for AI-assisted reimplementation on a sandboxed machine
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, portability, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Export a feature as a portable spec for AI-assisted reimplementation.
|
||||||
|
|
||||||
|
Instead of retyping code, you retype a compact spec. The AI on the sandbox generates the code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: The argument after `/opsx:export-spec` is a change name (active or archived), or a description of the feature. If omitted, prompt for selection.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Identify the source feature**
|
||||||
|
|
||||||
|
Same selection logic as `/opsx:extract-feature`:
|
||||||
|
- Check active changes and archive for the change name
|
||||||
|
- If not found, prompt with **AskUserQuestion tool**
|
||||||
|
- Read all artifacts: `proposal.md`, `design.md`, `tasks.md`, specs
|
||||||
|
|
||||||
|
2. **Analyze dependency chain (cumulative mode)**
|
||||||
|
|
||||||
|
Features often build on each other. Before generating the spec, determine
|
||||||
|
what the target feature depends on.
|
||||||
|
|
||||||
|
a. Read all archived change proposals in `openspec/changes/archive/` (sorted by date).
|
||||||
|
b. Build a dependency graph: what does the target require? What's superseded?
|
||||||
|
c. Ask the user:
|
||||||
|
> "This feature depends on earlier changes. How should I scope the spec?"
|
||||||
|
> 1. **Cumulative** — include all foundation (recommended for fresh codebase)
|
||||||
|
> 2. **Delta only** — just this change (target already has foundation)
|
||||||
|
> 3. **Custom** — pick which dependencies to include
|
||||||
|
|
||||||
|
d. In cumulative mode: merge into one coherent spec, skip superseded components.
|
||||||
|
e. In delta mode: add an "Assumes" section listing what must already exist.
|
||||||
|
|
||||||
|
3. **Read the actual implementation**
|
||||||
|
|
||||||
|
Read all source files created or modified by this feature (and dependencies if cumulative).
|
||||||
|
The spec must reflect what was actually built, not just what was planned.
|
||||||
|
|
||||||
|
4. **Determine the target context**
|
||||||
|
|
||||||
|
Use **AskUserQuestion tool** to ask:
|
||||||
|
> "Tell me about the target codebase:
|
||||||
|
> 1. Project name / root namespace
|
||||||
|
> 2. Existing stack (ASP.NET Core? Blazor? MudBlazor?)
|
||||||
|
> 3. Does it already have any of these? (controllers, DI setup, chat endpoint)
|
||||||
|
> 4. Does the target have OpenSpec? GitHub Copilot? Claude Code?"
|
||||||
|
|
||||||
|
This shapes what the spec assumes vs what it must specify.
|
||||||
|
|
||||||
|
5. **Capture the target GUI layout**
|
||||||
|
|
||||||
|
If the feature has a UI component, the spec MUST include the target's layout context.
|
||||||
|
Without it, the AI will generate components that conflict with the existing shell.
|
||||||
|
|
||||||
|
Ask the user:
|
||||||
|
> "Does the target app have an existing GUI shell? If so, share:
|
||||||
|
> 1. A **screenshot** (drag/drop or file path) — most information-dense
|
||||||
|
> 2. Or describe: AppBar (height? Dense?), Sidebar/Drawer (width? toggled?),
|
||||||
|
> MainContent area, any fixed elements"
|
||||||
|
|
||||||
|
If a screenshot is provided, extract:
|
||||||
|
- AppBar type and height (Dense=48px, Regular=64px, custom)
|
||||||
|
- Sidebar/Drawer presence, width, toggle behavior
|
||||||
|
- Main content area constraints
|
||||||
|
- Existing navigation items
|
||||||
|
|
||||||
|
Then ask:
|
||||||
|
> "How should this feature integrate into the target?
|
||||||
|
> 1. Feature name in the target (e.g., 'Sales Assistant' not 'Chat')
|
||||||
|
> 2. Route path (e.g., `/sales-assistant`)
|
||||||
|
> 3. Navigation: new sidebar item? AppBar button? Floating panel?"
|
||||||
|
|
||||||
|
Generate an **ASCII layout diagram** showing exactly where the feature renders,
|
||||||
|
and include it in both the spec and the OpenSpec design.md.
|
||||||
|
|
||||||
|
6. **Generate the portable spec**
|
||||||
|
|
||||||
|
Create a single markdown document that is:
|
||||||
|
- **Compact**: Target ~30-50 lines for a medium feature
|
||||||
|
- **Precise**: Unambiguous enough for an AI to implement correctly
|
||||||
|
- **Self-contained**: No references to external files or repos
|
||||||
|
- **Stack-aware**: Uses the right terminology for the target stack
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Feature: <Name>
|
||||||
|
## Target: <project name> (<stack>)
|
||||||
|
|
||||||
|
## Assumes
|
||||||
|
<what must already exist on the target>
|
||||||
|
|
||||||
|
## Integration Rule
|
||||||
|
This feature is additive only. DO NOT modify existing files, components,
|
||||||
|
services, or patterns in the target. If the target already has an equivalent
|
||||||
|
service, use it instead of creating a new one. If a task conflicts with
|
||||||
|
existing target code, skip it and note the conflict. Existing applicationX
|
||||||
|
code takes precedence over this spec in all cases.
|
||||||
|
|
||||||
|
## Target Layout
|
||||||
|
<ASCII diagram showing the target app shell and where this feature renders>
|
||||||
|
<AppBar height, Sidebar width, MainContent constraints>
|
||||||
|
<Feature name, route, navigation integration point>
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
<list with versions>
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
<2-3 sentence overview>
|
||||||
|
|
||||||
|
## Components
|
||||||
|
### <Component>: <path hint>
|
||||||
|
- <What it does>
|
||||||
|
- <Key behavior>
|
||||||
|
- <Interface/contract>
|
||||||
|
|
||||||
|
## Contracts
|
||||||
|
<API shapes, model definitions — things that MUST be exact>
|
||||||
|
|
||||||
|
## Critical Patterns
|
||||||
|
<Exact code snippets for traps the AI will otherwise get wrong>
|
||||||
|
<Show the POSITIVE pattern to copy, not just "don't use X">
|
||||||
|
<Include WHY — the mechanism behind the trap>
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
<DI registration, middleware order, config keys — dependency order>
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
<Non-obvious requirements>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compression strategies:**
|
||||||
|
- Use bullet points, not prose
|
||||||
|
- Specify contracts precisely (field names, types, API shapes)
|
||||||
|
- Let the AI infer standard patterns
|
||||||
|
- Only specify non-obvious behavior
|
||||||
|
- Omit anything the AI would do by default
|
||||||
|
|
||||||
|
7. **Estimate typing effort**
|
||||||
|
|
||||||
|
Count characters in the spec. Compare to the code recipe equivalent.
|
||||||
|
Show the compression ratio.
|
||||||
|
|
||||||
|
8. **Optionally generate an OpenSpec-compatible version**
|
||||||
|
|
||||||
|
If the target has OpenSpec, also generate:
|
||||||
|
- A **config.yaml** template — adapted context for the target project, with placeholders
|
||||||
|
- A `proposal.md` (minimal — 5-10 lines)
|
||||||
|
- A `design.md` — compact architectural decisions and rationale (why SSE vs WebSocket,
|
||||||
|
why typed client vs raw, why per-request plugin import, etc.). Without this the AI
|
||||||
|
has tasks but no rationale, and will guess wrong on non-obvious decisions.
|
||||||
|
- A `tasks.md` (implementation steps)
|
||||||
|
- **Setup instructions** at the top: step-by-step procedure for the target machine
|
||||||
|
(create config.yaml, save proposal/design/tasks, run `/opsx:apply`, reference the portable spec)
|
||||||
|
|
||||||
|
Save as: `openspec/exports/<change-name>-openspec.md`
|
||||||
|
|
||||||
|
9. **Write the output**
|
||||||
|
|
||||||
|
Save to: `openspec/exports/<change-name>-spec.md`
|
||||||
|
Display the full content for review.
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Prioritize precision over brevity — ambiguity wastes more time than length
|
||||||
|
- Always include exact field names, types, and API shapes
|
||||||
|
- Include non-obvious gotchas (like /v1 base URL requirements)
|
||||||
|
- Mental test: could an AI implement this correctly without seeing the original code?
|
||||||
|
- If too complex for ~50 lines, split into multiple specs by component
|
||||||
|
- Always show the compression ratio
|
||||||
|
- Must be readable when printed in monospace — no wide tables or long lines
|
||||||
|
- In cumulative mode, the spec must read as one coherent feature, not a list of changes
|
||||||
|
- Skip superseded components — always describe the latest version
|
||||||
|
- In delta mode, add an "Assumes" section so the target AI knows what must exist
|
||||||
|
- In the output header, note which changes were included and which were skipped
|
||||||
|
|
||||||
|
**Integration rule: adapt, never modify**
|
||||||
|
|
||||||
|
The exported feature is a GUEST in the target application. Existing code, patterns,
|
||||||
|
and conventions in the target take absolute precedence. The spec must include an
|
||||||
|
explicit "Integration Rule" section that states:
|
||||||
|
|
||||||
|
- **DO NOT** modify existing files, components, layouts, services, or routing in the target
|
||||||
|
- **DO NOT** replace existing patterns with patterns from the source project
|
||||||
|
(e.g., if the target uses a different HttpClient pattern, use theirs)
|
||||||
|
- **DO** add new files, new nav links, new routes, new DI registrations
|
||||||
|
- **DO** conform to the target's existing code style, naming conventions, and project structure
|
||||||
|
- If a task conflicts with existing target code, **stop and notify the user** — the user decides whether to skip, adapt, or redesign
|
||||||
|
- If the target already has an equivalent service (e.g., its own markdown renderer,
|
||||||
|
HttpClient wrapper), **use the existing one** instead of creating a new one
|
||||||
|
|
||||||
|
When generating the spec, actively identify potential conflict points and add
|
||||||
|
explicit "Adapt to target" notes. Common conflicts:
|
||||||
|
- Program.cs / DI registration style
|
||||||
|
- HttpClient patterns (typed client vs named client vs raw)
|
||||||
|
- Layout components (don't restructure MainLayout)
|
||||||
|
- CSS approach (isolation vs global vs utility classes)
|
||||||
|
- Error handling patterns
|
||||||
|
- Navigation structure
|
||||||
|
|
||||||
|
**Anti-patterns learned from field use**
|
||||||
|
|
||||||
|
These patterns cause the target AI to deviate from the spec even when the spec mentions them:
|
||||||
|
|
||||||
|
1. **"Don't use X" warnings get ignored.** AIs skip parenthetical negations buried in
|
||||||
|
bullet lists because their training data overwhelmingly uses the standard pattern.
|
||||||
|
Instead: add a **"Critical Patterns"** section with the exact code snippet to copy.
|
||||||
|
Show the positive pattern, not just the negative warning. E.g., instead of
|
||||||
|
"don't use EndOfStream", show the exact `while ((line = await ReadLineAsync()) != null)` loop.
|
||||||
|
|
||||||
|
2. **Layout values that depend on other components break silently.** Magic numbers like
|
||||||
|
`calc(100vh - 48px)` assume a specific AppBar height. Instead: document the dependency
|
||||||
|
explicitly ("48px = MudAppBar Dense height"), suggest using a CSS variable as fallback,
|
||||||
|
and note mobile viewport gotchas (`100vh` vs `100dvh`).
|
||||||
|
|
||||||
|
3. **Platform-specific runtime traps need the WHY.** Saying "don't use EndOfStream" is
|
||||||
|
not enough — the AI needs to know WHY (synchronous peek on an async-only stream).
|
||||||
|
When the AI understands the mechanism, it's less likely to reach for an equivalent
|
||||||
|
pattern that has the same problem.
|
||||||
|
|
||||||
|
ARGUMENTS: based on the above
|
||||||
114
.claude/commands/opsx/extract-feature.md
Normal file
114
.claude/commands/opsx/extract-feature.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Extract Feature"
|
||||||
|
description: Extract a feature into a minimal, printable code recipe for manual reimplementation
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, portability, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Extract a feature into a minimal, printable code recipe for manual reimplementation.
|
||||||
|
|
||||||
|
Generates a markdown document with:
|
||||||
|
- Package dependencies
|
||||||
|
- Ordered code blocks (no comments, no boilerplate)
|
||||||
|
- Clear markers for generic vs domain-specific code
|
||||||
|
|
||||||
|
Print it, take it to the sandbox, type it in.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: The argument after `/opsx:extract-feature` is a change name (active or archived), a git commit range, or a list of files. If omitted, prompt for selection.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Identify the source feature**
|
||||||
|
|
||||||
|
If a change name is provided:
|
||||||
|
- Check active changes: `openspec list --json`
|
||||||
|
- Check archive: look in `openspec/changes/archive/` for directories ending with the name
|
||||||
|
- If found, read its artifacts: `proposal.md`, `design.md`, `tasks.md`
|
||||||
|
|
||||||
|
If no name provided:
|
||||||
|
- Run `openspec list --json` and list archived changes
|
||||||
|
- Use **AskUserQuestion tool** to let the user select
|
||||||
|
|
||||||
|
If a file list or commit range is provided instead:
|
||||||
|
- Read those files directly
|
||||||
|
- Identify the feature from the code
|
||||||
|
|
||||||
|
2. **Analyze dependency chain (cumulative mode)**
|
||||||
|
|
||||||
|
Features often build on each other. Before generating the recipe, determine
|
||||||
|
what the target feature depends on.
|
||||||
|
|
||||||
|
a. Read all archived change proposals in `openspec/changes/archive/` (sorted by date).
|
||||||
|
b. Build a dependency graph: what does the target require? What's superseded?
|
||||||
|
c. Ask the user:
|
||||||
|
> "This feature depends on earlier changes. How should I scope the recipe?"
|
||||||
|
> 1. **Cumulative** — include all foundation code (recommended for fresh codebase)
|
||||||
|
> 2. **Delta only** — just this change's code (target already has foundation)
|
||||||
|
> 3. **Custom** — pick which dependencies to include
|
||||||
|
|
||||||
|
d. In cumulative mode: merge the chain, skip superseded code, use final file versions.
|
||||||
|
e. In delta mode: include only the selected change's code.
|
||||||
|
|
||||||
|
3. **Analyze the feature scope**
|
||||||
|
|
||||||
|
From the change artifacts and/or code (across full dependency chain if cumulative), determine:
|
||||||
|
- Which files were created or modified
|
||||||
|
- What NuGet/npm packages were added
|
||||||
|
- What the dependency order is (e.g., models before controllers)
|
||||||
|
- What is generic infrastructure vs domain-specific logic
|
||||||
|
|
||||||
|
Read all relevant source files. Always read the final current version of each file.
|
||||||
|
|
||||||
|
4. **Generate the code recipe**
|
||||||
|
|
||||||
|
Create a markdown document with these sections, in this order:
|
||||||
|
|
||||||
|
**Header**: Feature name, source change, date
|
||||||
|
|
||||||
|
**Prerequisites**: Package references with exact versions
|
||||||
|
|
||||||
|
**Steps**: Ordered by dependency. Each step contains:
|
||||||
|
- Step number and action (e.g., "New file", "Add to existing file", "Modify")
|
||||||
|
- Target path (relative, adaptable)
|
||||||
|
- Namespace placeholder: `__YOUR_NAMESPACE__` where the target project namespace goes
|
||||||
|
- **Code block**: The actual code, stripped of:
|
||||||
|
- All comments (// and /* */ and /// XML docs)
|
||||||
|
- Redundant blank lines
|
||||||
|
- Verbose variable names that can be shortened
|
||||||
|
- Any code not directly related to the feature
|
||||||
|
- If the step modifies an existing file: show only the code to add, with a brief marker for insertion point (e.g., "Add after AddControllers()")
|
||||||
|
|
||||||
|
**Domain-Specific Sections**: Clearly marked with `// ADAPT:` prefix explaining what to change for the target domain
|
||||||
|
|
||||||
|
5. **Optimize for retyping**
|
||||||
|
|
||||||
|
Review the generated document and:
|
||||||
|
- Merge small files if they can be combined
|
||||||
|
- Remove any using statements that the IDE will auto-add
|
||||||
|
- Shorten any unnecessarily verbose code
|
||||||
|
- Ensure no step exceeds ~40 lines (split if needed)
|
||||||
|
- Add line counts per step so the user can estimate effort
|
||||||
|
- Total the overall line count at the top
|
||||||
|
|
||||||
|
6. **Write the output**
|
||||||
|
|
||||||
|
Save to: `openspec/exports/<change-name>-recipe.md`
|
||||||
|
|
||||||
|
Also display the full content so the user can review it immediately.
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Never include comments in code blocks — the goal is minimum keystrokes
|
||||||
|
- Always read the actual current source files, not just the change artifacts
|
||||||
|
- Preserve compilation order: models -> services -> controllers -> DI registration
|
||||||
|
- Mark domain-specific code clearly so the user knows what to adapt vs copy verbatim
|
||||||
|
- Keep each step self-contained — the user may take breaks between steps
|
||||||
|
- If a feature spans more than ~200 lines of stripped code, warn the user and suggest using `/opsx:export-spec` instead
|
||||||
|
- If the feature has UI components, warn that the code recipe does not capture layout context (AppBar height, sidebar width, container sizing). Suggest `/opsx:export-spec` for UI-heavy features — it includes a Target Layout section with ASCII diagram and integration guidance
|
||||||
|
- Output must be valid markdown that renders well when printed
|
||||||
|
- When in cumulative mode, skip superseded code — always use the latest version of each file
|
||||||
|
- In the output header, show which changes were included and which were skipped
|
||||||
|
- If a dependency chain is long (4+ changes), suggest `/opsx:export-spec` as more efficient
|
||||||
|
|
||||||
|
ARGUMENTS: based on the above
|
||||||
97
.claude/commands/opsx/ff.md
Normal file
97
.claude/commands/opsx/ff.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
69
.claude/commands/opsx/new.md
Normal file
69
.claude/commands/opsx/new.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
550
.claude/commands/opsx/onboard.md
Normal file
550
.claude/commands/opsx/onboard.md
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
233
.claude/commands/opsx/porting-guide.md
Normal file
233
.claude/commands/opsx/porting-guide.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Porting Guide"
|
||||||
|
description: Generate a detailed human-readable implementation guide for porting a feature to a target codebase
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, portability, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Generate a detailed human-readable porting guide for implementing an exported feature on a target codebase.
|
||||||
|
|
||||||
|
This is a **companion** to `/opsx:export-spec`. The export-spec is optimized for AI consumption (compact, precise). This guide is optimized for the **human developer** who needs to understand context, troubleshoot issues, and make judgment calls when the AI agent on the target hits problems.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: The argument after `/opsx:porting-guide` is a change name or feature name that has already been exported via `/opsx:export-spec`. If omitted, prompt for selection.
|
||||||
|
|
||||||
|
**Prerequisites**: An export-spec must already exist in `openspec/exports/`. This skill reads the export-spec to stay consistent with it.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Locate the export artifacts**
|
||||||
|
|
||||||
|
a. Check `openspec/exports/` for matching files:
|
||||||
|
- `<name>-spec.md` (portable spec — required)
|
||||||
|
- `<name>-openspec.md` (OpenSpec bundle — optional)
|
||||||
|
b. If not found, prompt with **AskUserQuestion tool**:
|
||||||
|
> "No export-spec found for '<name>'. Available exports: [list]. Which one?"
|
||||||
|
c. Read the export-spec fully — the porting guide must not contradict it.
|
||||||
|
|
||||||
|
2. **Read the source material**
|
||||||
|
|
||||||
|
Same sources as export-spec, but read for **context and rationale** rather than contracts:
|
||||||
|
a. All archived change proposals, designs, and tasks in `openspec/changes/archive/`
|
||||||
|
b. All main specs in `openspec/specs/`
|
||||||
|
c. The actual implementation source files
|
||||||
|
d. Any existing export-spec and OpenSpec bundle
|
||||||
|
|
||||||
|
Focus on extracting:
|
||||||
|
- **Decision rationale**: WHY each design choice was made
|
||||||
|
- **Rejected alternatives**: what was considered and discarded
|
||||||
|
- **Friction points**: where the source implementation hit problems
|
||||||
|
- **Implicit knowledge**: things the source developer knew but didn't document
|
||||||
|
- **Platform traps**: runtime behaviors that aren't obvious from the code
|
||||||
|
|
||||||
|
3. **Gather target context**
|
||||||
|
|
||||||
|
If not already captured in the export-spec, use **AskUserQuestion tool** to learn:
|
||||||
|
> "To write accurate porting notes, I need to understand the target:
|
||||||
|
> 1. What patterns does the target use for DI registration? (manual, Scrutor, Autofac?)
|
||||||
|
> 2. What's the deployment model? (Azure, on-prem, Docker?)
|
||||||
|
> 3. Are there known constraints? (network restrictions, NuGet source limits, proxy requirements?)
|
||||||
|
> 4. Who will be implementing — developer familiarity with the source stack?"
|
||||||
|
|
||||||
|
This shapes the level of explanation and which friction points to highlight.
|
||||||
|
|
||||||
|
4. **Generate the porting guide**
|
||||||
|
|
||||||
|
Create a comprehensive markdown document structured for a human reader.
|
||||||
|
Use narrative prose where it aids understanding, tables for quick reference,
|
||||||
|
and checklists for actionable steps.
|
||||||
|
|
||||||
|
### Structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Porting Guide: <Feature> → <Target>
|
||||||
|
## Source: <source project> | Export: <export-spec filename>
|
||||||
|
|
||||||
|
## How to Use This Guide
|
||||||
|
|
||||||
|
Brief orientation:
|
||||||
|
- Relationship to the AI-targeted export-spec
|
||||||
|
- When to read this vs when to let the AI work
|
||||||
|
- How sections are organized
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
2-3 paragraphs a human can read in 5 minutes to understand the full feature.
|
||||||
|
Written as narrative, not bullet points. Cover:
|
||||||
|
- What the feature does end-to-end (user perspective)
|
||||||
|
- How data flows through the system (request → processing → response)
|
||||||
|
- What external dependencies exist and why
|
||||||
|
- The "one thing you must understand" about this feature
|
||||||
|
|
||||||
|
## Design Decisions (Detailed)
|
||||||
|
|
||||||
|
For EACH significant design decision:
|
||||||
|
|
||||||
|
### <Decision Title>
|
||||||
|
|
||||||
|
**What we chose:** <the approach>
|
||||||
|
|
||||||
|
**Why:** <rationale — the real reason, not the obvious one>
|
||||||
|
|
||||||
|
**What we rejected:**
|
||||||
|
- <Alternative A> — rejected because <specific reason>
|
||||||
|
- <Alternative B> — rejected because <specific reason>
|
||||||
|
|
||||||
|
**When you'd revisit this:** <conditions under which this decision should change>
|
||||||
|
|
||||||
|
**Target adaptation:** <how this maps to the target's patterns, what might need adjusting>
|
||||||
|
|
||||||
|
Decisions to always cover:
|
||||||
|
- Framework/library choices (SK over raw HTTP, SSE over WebSocket, etc.)
|
||||||
|
- Lifetime/scope decisions (singleton vs scoped vs transient)
|
||||||
|
- State management approach
|
||||||
|
- Streaming strategy
|
||||||
|
- Security decisions (sanitization, auth, CORS)
|
||||||
|
- File/folder organization
|
||||||
|
|
||||||
|
## Source → Target Mapping
|
||||||
|
|
||||||
|
Side-by-side mapping table showing how source concepts translate to target:
|
||||||
|
|
||||||
|
| Source (ChatAgent) | Target (CRC) | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `ChatAgent.Api/` | `CRC.Server/` | CRC uses hosted model, not standalone API |
|
||||||
|
| `ChatAgent.Client/` | `CRC.Client/` | Same WASM pattern |
|
||||||
|
| `builder.Services.AddScoped<T>()` | Check if Scrutor handles this | CRC may auto-scan |
|
||||||
|
| ... | ... | ... |
|
||||||
|
|
||||||
|
Flag EVERY known divergence, no matter how small. The small ones cause the most debugging time.
|
||||||
|
|
||||||
|
## Task-by-Task Implementation Notes
|
||||||
|
|
||||||
|
For EACH task in the OpenSpec tasks.md:
|
||||||
|
|
||||||
|
### <Task ID>: <Task Title>
|
||||||
|
|
||||||
|
**Prerequisites:** What must be completed first and verified working.
|
||||||
|
|
||||||
|
**Context:** Why this task exists and what it accomplishes in the bigger picture.
|
||||||
|
A developer who understands the "why" makes better judgment calls when adapting.
|
||||||
|
|
||||||
|
**Step-by-step:**
|
||||||
|
1. <Step with enough detail that someone unfamiliar with the source can follow>
|
||||||
|
2. <Step>
|
||||||
|
3. <Step>
|
||||||
|
|
||||||
|
**Expected friction on target:**
|
||||||
|
- <Specific thing that will likely need adaptation and why>
|
||||||
|
- <CRC pattern that differs from the source approach>
|
||||||
|
|
||||||
|
**Verify it works:**
|
||||||
|
- <Concrete test: "Navigate to X, click Y, you should see Z">
|
||||||
|
- <Log check: "Watch for 'SK: Auto-invoking function' in Serilog output">
|
||||||
|
- <Build check: "dotnet build should produce 0 warnings related to this task">
|
||||||
|
|
||||||
|
**If it breaks — diagnostic checklist:**
|
||||||
|
- Symptom: <what you'll see>
|
||||||
|
Cause: <most likely reason>
|
||||||
|
Fix: <specific remediation>
|
||||||
|
- Symptom: <what you'll see>
|
||||||
|
Cause: <most likely reason>
|
||||||
|
Fix: <specific remediation>
|
||||||
|
|
||||||
|
## Troubleshooting Reference
|
||||||
|
|
||||||
|
Comprehensive symptom → cause → fix table for known porting issues.
|
||||||
|
Organized by category (network, DI, UI, streaming, auth, config).
|
||||||
|
|
||||||
|
| Symptom | Likely Cause | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| 404 on /v1/chat/completions | Base URL missing `/v1` | Add `/v1` to `NlxvaPricer:LlmBaseUrl` |
|
||||||
|
| CORS 403 on SSE endpoint | CORS policy doesn't cover `text/event-stream` | Add origin to CORS policy |
|
||||||
|
| Streaming hangs, no tokens | `SetBrowserResponseStreamingEnabled` missing | Add to HttpRequestMessage |
|
||||||
|
| `EndOfStream` throws | Synchronous peek on async-only WASM stream | Use `ReadLineAsync() != null` loop |
|
||||||
|
| Markdown not rendering | MarkdownService not registered in DI | Add `AddSingleton<MarkdownService>()` |
|
||||||
|
| Tools never called by LLM | Plugin not imported on request | Move ImportPluginFromObject into action method |
|
||||||
|
| ... | ... | ... |
|
||||||
|
|
||||||
|
Include at least 10-15 entries covering the most likely failure modes.
|
||||||
|
|
||||||
|
## Configuration Checklist
|
||||||
|
|
||||||
|
Every config key the feature needs, organized as a checklist:
|
||||||
|
|
||||||
|
- [ ] `NlxvaPricer:LlmBaseUrl` — Where: `appsettings.json` (CRC.Server) — Default: `http://localhost:8317/v1` — What happens if missing: Falls back to default, fails if proxy not running
|
||||||
|
- [ ] `NlxvaPricer:LlmModel` — Where: `appsettings.json` — Default: `claude-sonnet-4-6`
|
||||||
|
- [ ] ... (every key)
|
||||||
|
|
||||||
|
## Dependency & Package Notes
|
||||||
|
|
||||||
|
For each new package:
|
||||||
|
- Package name and version constraint
|
||||||
|
- Why it's needed (one sentence)
|
||||||
|
- .NET version compatibility notes
|
||||||
|
- Known conflicts with packages the target might already have
|
||||||
|
- NuGet source: public nuget.org or internal feed?
|
||||||
|
(CRC uses internal GV Artifactory — flag if a package might not be available there)
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If the feature needs to be removed:
|
||||||
|
- Which files were added (safe to delete)
|
||||||
|
- Which files were modified and what was added (revert specific sections)
|
||||||
|
- Which NuGet packages were added (remove from csproj)
|
||||||
|
- Which config keys were added (remove from appsettings.json)
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Cross-check against export-spec**
|
||||||
|
|
||||||
|
Before writing, verify:
|
||||||
|
- Every contract in the export-spec is explained in the porting guide
|
||||||
|
- Every critical pattern has a troubleshooting entry
|
||||||
|
- No contradictions between the two documents
|
||||||
|
- The porting guide adds CONTEXT, not ALTERNATIVE instructions
|
||||||
|
|
||||||
|
6. **Write the output**
|
||||||
|
|
||||||
|
Save to: `openspec/exports/<name>-porting-guide.md`
|
||||||
|
Display a summary of sections generated and key highlights.
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
|
||||||
|
- **Narrative over telegraphic**: This is for humans. Use complete sentences. Explain the "why."
|
||||||
|
- **Never contradict the export-spec**: The AI spec is the source of truth for contracts and patterns. The porting guide explains and contextualizes, it doesn't override.
|
||||||
|
- **Anticipate the debugging session**: For every task, imagine the developer hitting a wall. What would they need to know? Write that down.
|
||||||
|
- **Be specific about the target**: Generic advice ("check your config") is useless. Specific advice ("CRC uses `gv_web_config.csv` for primary config but `appsettings.json` for secondary — LLM config goes in appsettings") saves hours.
|
||||||
|
- **Include the embarrassing details**: The things that took 30 minutes to figure out in the source (like the `/v1` URL requirement, or `EndOfStream` hanging) — those are the most valuable entries.
|
||||||
|
- **Flag NuGet source issues**: If the target uses an internal feed (like CRC's GV Artifactory with nuget.org disabled), flag every package that needs to come from a specific source.
|
||||||
|
- **No code blocks for code's sake**: The export-spec has the exact code. The porting guide references it ("see Critical Pattern #2 in the export-spec") rather than duplicating it. Only include code when it clarifies a specific porting concern.
|
||||||
|
- **Version-stamp the guide**: Include the date and source commit hash so the reader knows what state the guide reflects.
|
||||||
|
|
||||||
|
**Anti-patterns**
|
||||||
|
|
||||||
|
1. **Don't duplicate the export-spec.** The export-spec has exact contracts and code snippets. The porting guide has context, rationale, and troubleshooting. They complement each other.
|
||||||
|
|
||||||
|
2. **Don't be abstract.** "There might be DI issues" is useless. "CRC uses Scrutor for assembly scanning. If Scrutor auto-registers ExtractionPlugin before your manual AddScoped<ExtractionPlugin>(), you'll get a duplicate registration. Check with `builder.Services.Where(s => s.ServiceType == typeof(ExtractionPlugin))` in a breakpoint" is useful.
|
||||||
|
|
||||||
|
3. **Don't skip the obvious.** The developer may be senior but unfamiliar with Semantic Kernel. Explain SK concepts (Kernel, plugins, FunctionChoiceBehavior) in plain language.
|
||||||
|
|
||||||
|
4. **Don't assume the happy path.** Network restrictions, proxy requirements, package feed limitations, and auth quirks are the norm in enterprise environments. Address them.
|
||||||
|
|
||||||
|
ARGUMENTS: $ARGUMENTS
|
||||||
106
.claude/commands/opsx/propose.md
Normal file
106
.claude/commands/opsx/propose.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
name: "OPSX: Propose"
|
||||||
|
description: Propose a new change - create it and generate all artifacts in one step
|
||||||
|
category: Workflow
|
||||||
|
tags: [workflow, artifacts, experimental]
|
||||||
|
---
|
||||||
|
|
||||||
|
Propose a new change - create the change and generate all artifacts in one step.
|
||||||
|
|
||||||
|
I'll create a change with artifacts:
|
||||||
|
- proposal.md (what & why)
|
||||||
|
- design.md (how)
|
||||||
|
- tasks.md (implementation steps)
|
||||||
|
|
||||||
|
When ready to implement, run /opsx:apply
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: The argument after `/opsx:propose` is the change name (kebab-case), OR a description of what the user wants to build.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **If no input provided, ask what they want to build**
|
||||||
|
|
||||||
|
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
|
||||||
|
> "What change do you want to work on? Describe what you want to build or fix."
|
||||||
|
|
||||||
|
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
|
||||||
|
|
||||||
|
2. **Create the change directory**
|
||||||
|
```bash
|
||||||
|
openspec new change "<name>"
|
||||||
|
```
|
||||||
|
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
|
||||||
|
|
||||||
|
3. **Get the artifact build order**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to get:
|
||||||
|
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||||
|
- `artifacts`: list of all artifacts with their status and dependencies
|
||||||
|
|
||||||
|
4. **Create artifacts in sequence until apply-ready**
|
||||||
|
|
||||||
|
Use the **TodoWrite tool** to track progress through the artifacts.
|
||||||
|
|
||||||
|
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||||
|
|
||||||
|
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||||
|
- Get instructions:
|
||||||
|
```bash
|
||||||
|
openspec instructions <artifact-id> --change "<name>" --json
|
||||||
|
```
|
||||||
|
- The instructions JSON includes:
|
||||||
|
- `context`: Project background (constraints for you - do NOT include in output)
|
||||||
|
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||||
|
- `template`: The structure to use for your output file
|
||||||
|
- `instruction`: Schema-specific guidance for this artifact type
|
||||||
|
- `outputPath`: Where to write the artifact
|
||||||
|
- `dependencies`: Completed artifacts to read for context
|
||||||
|
- Read any completed dependency files for context
|
||||||
|
- Create the artifact file using `template` as the structure
|
||||||
|
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||||
|
- Show brief progress: "Created <artifact-id>"
|
||||||
|
|
||||||
|
b. **Continue until all `applyRequires` artifacts are complete**
|
||||||
|
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||||
|
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
|
||||||
|
- Stop when all `applyRequires` artifacts are done
|
||||||
|
|
||||||
|
c. **If an artifact requires user input** (unclear context):
|
||||||
|
- Use **AskUserQuestion tool** to clarify
|
||||||
|
- Then continue with creation
|
||||||
|
|
||||||
|
5. **Show final status**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
After completing all artifacts, summarize:
|
||||||
|
- Change name and location
|
||||||
|
- List of artifacts created with brief descriptions
|
||||||
|
- What's ready: "All artifacts created! Ready for implementation."
|
||||||
|
- Prompt: "Run `/opsx:apply` to start implementing."
|
||||||
|
|
||||||
|
**Artifact Creation Guidelines**
|
||||||
|
|
||||||
|
- Follow the `instruction` field from `openspec instructions` for each artifact type
|
||||||
|
- The schema defines what each artifact should contain - follow it
|
||||||
|
- Read dependency artifacts for context before creating new ones
|
||||||
|
- Use `template` as the structure for your output file - fill in its sections
|
||||||
|
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||||
|
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||||
|
- These guide what you write, but should never appear in the output
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
|
||||||
|
- Always read dependency artifacts before creating a new one
|
||||||
|
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||||
|
- If a change with that name already exists, ask if user wants to continue it or create a new one
|
||||||
|
- Verify each artifact file exists after writing before proceeding to next
|
||||||
134
.claude/commands/opsx/sync.md
Normal file
134
.claude/commands/opsx/sync.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
164
.claude/commands/opsx/verify.md
Normal file
164
.claude/commands/opsx/verify.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
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"
|
||||||
156
.claude/skills/openspec-apply-change/SKILL.md
Normal file
156
.claude/skills/openspec-apply-change/SKILL.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
name: openspec-apply-change
|
||||||
|
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement tasks from an OpenSpec change.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
|
||||||
|
|
||||||
|
2. **Check status to understand the schema**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used (e.g., "spec-driven")
|
||||||
|
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
|
||||||
|
|
||||||
|
3. **Get apply instructions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openspec instructions apply --change "<name>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns:
|
||||||
|
- Context file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
|
||||||
|
- Progress (total, complete, remaining)
|
||||||
|
- Task list with status
|
||||||
|
- Dynamic instruction based on current state
|
||||||
|
|
||||||
|
**Handle states:**
|
||||||
|
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
|
||||||
|
- If `state: "all_done"`: congratulate, suggest archive
|
||||||
|
- Otherwise: proceed to implementation
|
||||||
|
|
||||||
|
4. **Read context files**
|
||||||
|
|
||||||
|
Read the files listed in `contextFiles` from the apply instructions output.
|
||||||
|
The files depend on the schema being used:
|
||||||
|
- **spec-driven**: proposal, specs, design, tasks
|
||||||
|
- Other schemas: follow the contextFiles from CLI output
|
||||||
|
|
||||||
|
5. **Show current progress**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Schema being used
|
||||||
|
- Progress: "N/M tasks complete"
|
||||||
|
- Remaining tasks overview
|
||||||
|
- Dynamic instruction from CLI
|
||||||
|
|
||||||
|
6. **Implement tasks (loop until done or blocked)**
|
||||||
|
|
||||||
|
For each pending task:
|
||||||
|
- Show which task is being worked on
|
||||||
|
- Make the code changes required
|
||||||
|
- Keep changes minimal and focused
|
||||||
|
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
||||||
|
- Continue to next task
|
||||||
|
|
||||||
|
**Pause if:**
|
||||||
|
- Task is unclear → ask for clarification
|
||||||
|
- Implementation reveals a design issue → suggest updating artifacts
|
||||||
|
- Error or blocker encountered → report and wait for guidance
|
||||||
|
- User interrupts
|
||||||
|
|
||||||
|
7. **On completion or pause, show status**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Tasks completed this session
|
||||||
|
- Overall progress: "N/M tasks complete"
|
||||||
|
- If all done: suggest archive
|
||||||
|
- If paused: explain why and wait for guidance
|
||||||
|
|
||||||
|
**Output During Implementation**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementing: <change-name> (schema: <schema-name>)
|
||||||
|
|
||||||
|
Working on task 3/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
|
||||||
|
Working on task 4/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Completion**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 7/7 tasks complete ✓
|
||||||
|
|
||||||
|
### Completed This Session
|
||||||
|
- [x] Task 1
|
||||||
|
- [x] Task 2
|
||||||
|
...
|
||||||
|
|
||||||
|
All tasks complete! Ready to archive this change.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Pause (Issue Encountered)**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Paused
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 4/7 tasks complete
|
||||||
|
|
||||||
|
### Issue Encountered
|
||||||
|
<description of the issue>
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
1. <option 1>
|
||||||
|
2. <option 2>
|
||||||
|
3. Other approach
|
||||||
|
|
||||||
|
What would you like to do?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Keep going through tasks until done or blocked
|
||||||
|
- Always read context files before starting (from the apply instructions output)
|
||||||
|
- If task is ambiguous, pause and ask before implementing
|
||||||
|
- If implementation reveals issues, pause and suggest artifact updates
|
||||||
|
- Keep code changes minimal and scoped to each task
|
||||||
|
- Update task checkbox immediately after completing each task
|
||||||
|
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||||
|
- Use contextFiles from CLI output, don't assume specific file names
|
||||||
|
|
||||||
|
**Fluid Workflow Integration**
|
||||||
|
|
||||||
|
This skill supports the "actions on a change" model:
|
||||||
|
|
||||||
|
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
|
||||||
|
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
|
||||||
114
.claude/skills/openspec-archive-change/SKILL.md
Normal file
114
.claude/skills/openspec-archive-change/SKILL.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
name: openspec-archive-change
|
||||||
|
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Archive a completed change in the experimental workflow.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **If no change name provided, prompt for selection**
|
||||||
|
|
||||||
|
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||||
|
|
||||||
|
Show only active changes (not already archived).
|
||||||
|
Include the schema used for each change if available.
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||||
|
|
||||||
|
2. **Check artifact completion status**
|
||||||
|
|
||||||
|
Run `openspec status --change "<name>" --json` to check artifact completion.
|
||||||
|
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used
|
||||||
|
- `artifacts`: List of artifacts with their status (`done` or other)
|
||||||
|
|
||||||
|
**If any artifacts are not `done`:**
|
||||||
|
- Display warning listing incomplete artifacts
|
||||||
|
- Use **AskUserQuestion tool** to confirm user wants to proceed
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
3. **Check task completion status**
|
||||||
|
|
||||||
|
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
|
||||||
|
|
||||||
|
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
|
||||||
|
|
||||||
|
**If incomplete tasks found:**
|
||||||
|
- Display warning showing count of incomplete tasks
|
||||||
|
- Use **AskUserQuestion tool** to confirm user wants to proceed
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
**If no tasks file exists:** Proceed without task-related warning.
|
||||||
|
|
||||||
|
4. **Assess delta spec sync state**
|
||||||
|
|
||||||
|
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
|
||||||
|
|
||||||
|
**If delta specs exist:**
|
||||||
|
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
|
||||||
|
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||||
|
- Show a combined summary before prompting
|
||||||
|
|
||||||
|
**Prompt options:**
|
||||||
|
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||||
|
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||||
|
|
||||||
|
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
|
||||||
|
|
||||||
|
5. **Perform the archive**
|
||||||
|
|
||||||
|
Create the archive directory if it doesn't exist:
|
||||||
|
```bash
|
||||||
|
mkdir -p openspec/changes/archive
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate target name using current date: `YYYY-MM-DD-<change-name>`
|
||||||
|
|
||||||
|
**Check if target already exists:**
|
||||||
|
- If yes: Fail with error, suggest renaming existing archive or using different date
|
||||||
|
- If no: Move the change directory to archive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Display summary**
|
||||||
|
|
||||||
|
Show archive completion summary including:
|
||||||
|
- Change name
|
||||||
|
- Schema that was used
|
||||||
|
- Archive location
|
||||||
|
- Whether specs were synced (if applicable)
|
||||||
|
- Note about any warnings (incomplete artifacts/tasks)
|
||||||
|
|
||||||
|
**Output On Success**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Archive Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||||
|
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
|
||||||
|
|
||||||
|
All artifacts complete. All tasks complete.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Always prompt for change selection if not provided
|
||||||
|
- Use artifact graph (openspec status --json) for completion checking
|
||||||
|
- Don't block archive on warnings - just inform and confirm
|
||||||
|
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||||
|
- Show clear summary of what happened
|
||||||
|
- If sync is requested, use openspec-sync-specs approach (agent-driven)
|
||||||
|
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
|
||||||
246
.claude/skills/openspec-bulk-archive-change/SKILL.md
Normal file
246
.claude/skills/openspec-bulk-archive-change/SKILL.md
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
118
.claude/skills/openspec-continue-change/SKILL.md
Normal file
118
.claude/skills/openspec-continue-change/SKILL.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
288
.claude/skills/openspec-explore/SKILL.md
Normal file
288
.claude/skills/openspec-explore/SKILL.md
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
---
|
||||||
|
name: openspec-explore
|
||||||
|
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||||
|
|
||||||
|
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||||
|
|
||||||
|
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Stance
|
||||||
|
|
||||||
|
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||||
|
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||||
|
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||||
|
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||||
|
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||||
|
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Might Do
|
||||||
|
|
||||||
|
Depending on what the user brings, you might:
|
||||||
|
|
||||||
|
**Explore the problem space**
|
||||||
|
- Ask clarifying questions that emerge from what they said
|
||||||
|
- Challenge assumptions
|
||||||
|
- Reframe the problem
|
||||||
|
- Find analogies
|
||||||
|
|
||||||
|
**Investigate the codebase**
|
||||||
|
- Map existing architecture relevant to the discussion
|
||||||
|
- Find integration points
|
||||||
|
- Identify patterns already in use
|
||||||
|
- Surface hidden complexity
|
||||||
|
|
||||||
|
**Compare options**
|
||||||
|
- Brainstorm multiple approaches
|
||||||
|
- Build comparison tables
|
||||||
|
- Sketch tradeoffs
|
||||||
|
- Recommend a path (if asked)
|
||||||
|
|
||||||
|
**Visualize**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Use ASCII diagrams liberally │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ State │────────▶│ State │ │
|
||||||
|
│ │ A │ │ B │ │
|
||||||
|
│ └────────┘ └────────┘ │
|
||||||
|
│ │
|
||||||
|
│ System diagrams, state machines, │
|
||||||
|
│ data flows, architecture sketches, │
|
||||||
|
│ dependency graphs, comparison tables │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Surface risks and unknowns**
|
||||||
|
- Identify what could go wrong
|
||||||
|
- Find gaps in understanding
|
||||||
|
- Suggest spikes or investigations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenSpec Awareness
|
||||||
|
|
||||||
|
You have full context of the OpenSpec system. Use it naturally, don't force it.
|
||||||
|
|
||||||
|
### Check for context
|
||||||
|
|
||||||
|
At the start, quickly check what exists:
|
||||||
|
```bash
|
||||||
|
openspec list --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells you:
|
||||||
|
- If there are active changes
|
||||||
|
- Their names, schemas, and status
|
||||||
|
- What the user might be working on
|
||||||
|
|
||||||
|
### When no change exists
|
||||||
|
|
||||||
|
Think freely. When insights crystallize, you might offer:
|
||||||
|
|
||||||
|
- "This feels solid enough to start a change. Want me to create a proposal?"
|
||||||
|
- Or keep exploring - no pressure to formalize
|
||||||
|
|
||||||
|
### When a change exists
|
||||||
|
|
||||||
|
If the user mentions a change or you detect one is relevant:
|
||||||
|
|
||||||
|
1. **Read existing artifacts for context**
|
||||||
|
- `openspec/changes/<name>/proposal.md`
|
||||||
|
- `openspec/changes/<name>/design.md`
|
||||||
|
- `openspec/changes/<name>/tasks.md`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
2. **Reference them naturally in conversation**
|
||||||
|
- "Your design mentions using Redis, but we just realized SQLite fits better..."
|
||||||
|
- "The proposal scopes this to premium users, but we're now thinking everyone..."
|
||||||
|
|
||||||
|
3. **Offer to capture when decisions are made**
|
||||||
|
|
||||||
|
| Insight Type | Where to Capture |
|
||||||
|
|--------------|------------------|
|
||||||
|
| New requirement discovered | `specs/<capability>/spec.md` |
|
||||||
|
| Requirement changed | `specs/<capability>/spec.md` |
|
||||||
|
| Design decision made | `design.md` |
|
||||||
|
| Scope changed | `proposal.md` |
|
||||||
|
| New work identified | `tasks.md` |
|
||||||
|
| Assumption invalidated | Relevant artifact |
|
||||||
|
|
||||||
|
Example offers:
|
||||||
|
- "That's a design decision. Capture it in design.md?"
|
||||||
|
- "This is a new requirement. Add it to specs?"
|
||||||
|
- "This changes scope. Update the proposal?"
|
||||||
|
|
||||||
|
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Don't Have To Do
|
||||||
|
|
||||||
|
- Follow a script
|
||||||
|
- Ask the same questions every time
|
||||||
|
- Produce a specific artifact
|
||||||
|
- Reach a conclusion
|
||||||
|
- Stay on topic if a tangent is valuable
|
||||||
|
- Be brief (this is thinking time)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Handling Different Entry Points
|
||||||
|
|
||||||
|
**User brings a vague idea:**
|
||||||
|
```
|
||||||
|
User: I'm thinking about adding real-time collaboration
|
||||||
|
|
||||||
|
You: Real-time collab is a big space. Let me think about this...
|
||||||
|
|
||||||
|
COLLABORATION SPECTRUM
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
Awareness Coordination Sync
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌────────┐ ┌────────┐ ┌────────┐
|
||||||
|
│Presence│ │Cursors │ │ CRDT │
|
||||||
|
│ "3 │ │ Multi │ │Conflict│
|
||||||
|
│online" │ │ select │ │ free │
|
||||||
|
└────────┘ └────────┘ └────────┘
|
||||||
|
│ │ │
|
||||||
|
trivial moderate complex
|
||||||
|
|
||||||
|
Where's your head at?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User brings a specific problem:**
|
||||||
|
```
|
||||||
|
User: The auth system is a mess
|
||||||
|
|
||||||
|
You: [reads codebase]
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ CURRENT AUTH FLOW │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────┼─────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||||
|
│ Google │ │ GitHub │ │ Email │
|
||||||
|
│ OAuth │ │ OAuth │ │ Magic │
|
||||||
|
└────┬────┘ └────┬────┘ └────┬────┘
|
||||||
|
│ │ │
|
||||||
|
└─────────────┼─────────────┘
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ Session │
|
||||||
|
└─────┬─────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ Perms │
|
||||||
|
└───────────┘
|
||||||
|
|
||||||
|
I see three tangles. Which one's burning?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User is stuck mid-implementation:**
|
||||||
|
```
|
||||||
|
User: /opsx:explore add-auth-system
|
||||||
|
The OAuth integration is more complex than expected
|
||||||
|
|
||||||
|
You: [reads change artifacts]
|
||||||
|
|
||||||
|
You're on task 4: "Implement OAuth flow"
|
||||||
|
|
||||||
|
Let me trace what's involved...
|
||||||
|
|
||||||
|
[draws diagram, explores options, suggests paths]
|
||||||
|
|
||||||
|
Want to update the design to reflect this?
|
||||||
|
Or add a spike task to investigate?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User wants to compare options:**
|
||||||
|
```
|
||||||
|
User: Should we use Postgres or SQLite?
|
||||||
|
|
||||||
|
You: Generic answer is boring. What's the context?
|
||||||
|
|
||||||
|
User: A CLI tool that tracks local dev environments
|
||||||
|
|
||||||
|
You: That changes everything.
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ CLI TOOL DATA STORAGE │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Key constraints:
|
||||||
|
• No daemon running
|
||||||
|
• Must work offline
|
||||||
|
• Single user
|
||||||
|
|
||||||
|
SQLite Postgres
|
||||||
|
Deployment embedded ✓ needs server ✗
|
||||||
|
Offline yes ✓ no ✗
|
||||||
|
Single file yes ✓ no ✗
|
||||||
|
|
||||||
|
SQLite. Not even close.
|
||||||
|
|
||||||
|
Unless... is there a sync component?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ending Discovery
|
||||||
|
|
||||||
|
There's no required ending. Discovery might:
|
||||||
|
|
||||||
|
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
|
||||||
|
- **Result in artifact updates**: "Updated design.md with these decisions"
|
||||||
|
- **Just provide clarity**: User has what they need, moves on
|
||||||
|
- **Continue later**: "We can pick this up anytime"
|
||||||
|
|
||||||
|
When it feels like things are crystallizing, you might summarize:
|
||||||
|
|
||||||
|
```
|
||||||
|
## What We Figured Out
|
||||||
|
|
||||||
|
**The problem**: [crystallized understanding]
|
||||||
|
|
||||||
|
**The approach**: [if one emerged]
|
||||||
|
|
||||||
|
**Open questions**: [if any remain]
|
||||||
|
|
||||||
|
**Next steps** (if ready):
|
||||||
|
- Create a change proposal
|
||||||
|
- Keep exploring: just keep talking
|
||||||
|
```
|
||||||
|
|
||||||
|
But this summary is optional. Sometimes the thinking IS the value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||||
|
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||||
|
- **Don't rush** - Discovery is thinking time, not task time
|
||||||
|
- **Don't force structure** - Let patterns emerge naturally
|
||||||
|
- **Don't auto-capture** - Offer to save insights, don't just do it
|
||||||
|
- **Do visualize** - A good diagram is worth many paragraphs
|
||||||
|
- **Do explore the codebase** - Ground discussions in reality
|
||||||
|
- **Do question assumptions** - Including the user's and your own
|
||||||
217
.claude/skills/openspec-export-spec/SKILL.md
Normal file
217
.claude/skills/openspec-export-spec/SKILL.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
name: openspec-export-spec
|
||||||
|
description: Export a feature as a compact, portable spec that an AI agent (Copilot, Claude) on a sandboxed machine can implement from. Optimized for minimal hand-typing.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Export a feature as a portable spec for AI-assisted reimplementation on a sandboxed machine.
|
||||||
|
|
||||||
|
Instead of retyping code, you retype a compact spec. The AI on the sandbox (Copilot, Claude, OpenSpec) generates the code from the spec.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: A change name (active or archived), or a description of the feature. If omitted, prompt for selection.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Identify the source feature**
|
||||||
|
|
||||||
|
Same as `openspec-extract-feature` step 1:
|
||||||
|
- Check active changes and archive for the change name
|
||||||
|
- If not found, prompt with **AskUserQuestion tool**
|
||||||
|
- Read all artifacts: `proposal.md`, `design.md`, `tasks.md`, specs
|
||||||
|
|
||||||
|
2. **Analyze dependency chain (cumulative mode)**
|
||||||
|
|
||||||
|
Features often build on each other. Before generating the spec, determine
|
||||||
|
what the target feature depends on.
|
||||||
|
|
||||||
|
a. **Read all archived change proposals** in `openspec/changes/archive/` (sorted by date).
|
||||||
|
For each, read `proposal.md` to understand what it adds and what it depends on.
|
||||||
|
|
||||||
|
b. **Build a dependency graph**:
|
||||||
|
- Which changes does the target feature require?
|
||||||
|
- Which earlier changes are superseded? (e.g., if Change 6 replaces Change 3's
|
||||||
|
implementation, include Change 6's version, not Change 3's)
|
||||||
|
- Which changes are unrelated and can be skipped?
|
||||||
|
|
||||||
|
c. **Ask the user** using **AskUserQuestion tool**:
|
||||||
|
> "This feature depends on earlier changes. How should I scope the spec?"
|
||||||
|
>
|
||||||
|
> Options:
|
||||||
|
> 1. **Cumulative** — include all foundation this feature needs (recommended if target is a fresh codebase)
|
||||||
|
> 2. **Delta only** — just what this specific change adds (use if target already has the foundation)
|
||||||
|
> 3. **Custom** — let me pick which dependencies to include
|
||||||
|
|
||||||
|
Show the dependency chain so the user can decide.
|
||||||
|
|
||||||
|
d. **In cumulative mode**: The spec covers the entire stack from foundation to feature.
|
||||||
|
- Merge packages, components, wiring from all included changes
|
||||||
|
- Skip superseded components (use the latest version)
|
||||||
|
- The spec should read as a single coherent feature, not a list of changes
|
||||||
|
|
||||||
|
e. **In delta mode**: The spec covers only the selected change.
|
||||||
|
- Add a "Assumes" section listing what must already exist in the target
|
||||||
|
|
||||||
|
3. **Read the actual implementation**
|
||||||
|
|
||||||
|
Read all source files that were created or modified by this feature
|
||||||
|
(and its dependencies if cumulative).
|
||||||
|
The spec must reflect what was actually built, not just what was planned.
|
||||||
|
|
||||||
|
4. **Determine the target context**
|
||||||
|
|
||||||
|
Use **AskUserQuestion tool** to ask:
|
||||||
|
> "Tell me about the target codebase:
|
||||||
|
> 1. Project name / root namespace
|
||||||
|
> 2. Existing stack (ASP.NET Core? Blazor? MudBlazor?)
|
||||||
|
> 3. Does it already have any of these? (controllers, DI setup, chat endpoint)
|
||||||
|
> 4. Does the target have OpenSpec? GitHub Copilot? Claude Code?"
|
||||||
|
|
||||||
|
This shapes what the spec assumes vs what it must specify.
|
||||||
|
|
||||||
|
5. **Generate the portable spec**
|
||||||
|
|
||||||
|
Create a single markdown document that is:
|
||||||
|
- **Compact**: Target ~30-50 lines for a medium feature
|
||||||
|
- **Precise**: Unambiguous enough for an AI to implement correctly
|
||||||
|
- **Self-contained**: No references to external files or repos
|
||||||
|
- **Stack-aware**: Uses the right terminology for the target stack
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Feature: <Name>
|
||||||
|
## Target: <project name> (<stack>)
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
<list with versions>
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
<2-3 sentence overview of how the pieces fit together>
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### <Component 1>: <path hint>
|
||||||
|
- <What it does — 1 line>
|
||||||
|
- <Key behavior — 1 line>
|
||||||
|
- <Interface/contract — 1 line>
|
||||||
|
|
||||||
|
### <Component 2>: <path hint>
|
||||||
|
...
|
||||||
|
|
||||||
|
## Contracts
|
||||||
|
<API shapes, model definitions, SSE formats — the things that MUST be exact>
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
<DI registration, middleware order, configuration keys — in dependency order>
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
<Key behavioral requirements that aren't obvious from the structure>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compression strategies:**
|
||||||
|
- Use bullet points, not prose
|
||||||
|
- Specify contracts precisely (field names, types, API shapes)
|
||||||
|
- Let the AI infer standard patterns (error handling, null checks, etc.)
|
||||||
|
- Only specify non-obvious behavior (the surprising parts)
|
||||||
|
- Omit anything the AI would do by default for the given stack
|
||||||
|
|
||||||
|
6. **Estimate typing effort**
|
||||||
|
|
||||||
|
Count characters in the spec. Compare to the code recipe equivalent.
|
||||||
|
Show the compression ratio:
|
||||||
|
```
|
||||||
|
Code recipe: ~120 lines to type
|
||||||
|
This spec: ~35 lines to type
|
||||||
|
Compression: 3.4x
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Optionally generate an OpenSpec-compatible version**
|
||||||
|
|
||||||
|
If the target has OpenSpec, also generate a version structured as:
|
||||||
|
- A `proposal.md` (minimal — 5-10 lines)
|
||||||
|
- A `tasks.md` (implementation steps the target AI follows)
|
||||||
|
|
||||||
|
These can be even more compact because OpenSpec provides the scaffolding.
|
||||||
|
|
||||||
|
Save this variant alongside the main spec.
|
||||||
|
|
||||||
|
8. **Write the output**
|
||||||
|
|
||||||
|
Save to: `openspec/exports/<change-name>-spec.md`
|
||||||
|
If OpenSpec variant: `openspec/exports/<change-name>-openspec.md`
|
||||||
|
|
||||||
|
Display the full content for review.
|
||||||
|
|
||||||
|
**Output Format**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Feature: Semantic Kernel Chat with Tool Calling
|
||||||
|
## Target: ApplicationX (ASP.NET Core + Blazor WASM + MudBlazor)
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
- Microsoft.SemanticKernel 1.74.0
|
||||||
|
- Microsoft.SemanticKernel.Connectors.OpenAI 1.74.0
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
POST /api/chat endpoint accepts messages, runs them through SK's chat completion
|
||||||
|
with auto tool calling enabled, streams response as SSE. An ExtractionPlugin
|
||||||
|
validates structured data extracted by the LLM.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### ChatController: Controllers/ChatController.cs
|
||||||
|
- POST endpoint, injects Kernel via DI
|
||||||
|
- Converts ChatMessage[] to SK ChatHistory
|
||||||
|
- Streams via GetStreamingChatMessageContentsAsync
|
||||||
|
- Outputs SSE: `data: {"text":"..."}\n\n` then `data: [DONE]\n\n`
|
||||||
|
|
||||||
|
### ExtractionPlugin: Plugins/ExtractionPlugin.cs
|
||||||
|
- [KernelFunction("validate_extracted_fields")]
|
||||||
|
- Accepts JSON string, deserializes to ExtractedFields
|
||||||
|
- Returns {"isValid": bool, "errors": string[]}
|
||||||
|
|
||||||
|
### ExtractedFields: Models/ExtractedFields.cs
|
||||||
|
- Required: Client(string), Project(string), Hours(decimal), Rate(decimal), Currency(string), Date(string)
|
||||||
|
- Optional: Description(string), PoNumber(string)
|
||||||
|
|
||||||
|
### ValidationResult: Models/ValidationResult.cs
|
||||||
|
- IsValid(bool), Errors(List<string>)
|
||||||
|
|
||||||
|
### ChatRequest/ChatMessage: Shared/Models/
|
||||||
|
- ChatRequest: Messages(List<ChatMessage>)
|
||||||
|
- ChatMessage: Role(string), Content(string)
|
||||||
|
|
||||||
|
## Wiring (Program.cs, add after AddControllers)
|
||||||
|
- AddOpenAIChatCompletion(model, endpoint with /v1 suffix, apiKey)
|
||||||
|
- AddKernel()
|
||||||
|
- AddSingleton<ExtractionPlugin>()
|
||||||
|
- UseCors for Blazor client origin
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
- FunctionChoiceBehavior.Auto() enables autonomous tool calling
|
||||||
|
- SK's built-in limit prevents runaway tool call loops
|
||||||
|
- Plugin imported per-request via _kernel.ImportPluginFromObject
|
||||||
|
- Base URL must include /v1 — OpenAI SDK appends chat/completions directly
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lines to type: ~35 | Code equivalent: ~150 lines | Compression: 4.3x**
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Prioritize precision over brevity — an ambiguous spec wastes more time than a slightly longer one
|
||||||
|
- Always include exact field names, types, and API shapes — these are the hardest to guess
|
||||||
|
- Include non-obvious gotchas (like the /v1 base URL requirement)
|
||||||
|
- Test the spec mentally: could an AI implement this correctly without seeing the original code?
|
||||||
|
- If the feature is too complex for a single spec page (~50+ lines), split into multiple specs by component
|
||||||
|
- Always show the compression ratio so the user can decide between spec and code recipe
|
||||||
|
- The spec must be readable when printed in monospace — no wide tables or long lines
|
||||||
|
- In cumulative mode, the spec must read as one coherent feature — not a list of sequential changes
|
||||||
|
- Skip superseded components — always describe the latest version of each piece
|
||||||
|
- In delta mode, add an "Assumes" section so the target AI knows what must already exist
|
||||||
|
- In the output header, note which changes were included and which were skipped
|
||||||
170
.claude/skills/openspec-extract-feature/SKILL.md
Normal file
170
.claude/skills/openspec-extract-feature/SKILL.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
name: openspec-extract-feature
|
||||||
|
description: Extract a feature from a change (archived or active) into a minimal, printable code recipe optimized for manual retyping into a sandboxed codebase.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Extract a feature into a minimal, printable code recipe for manual reimplementation.
|
||||||
|
|
||||||
|
Generates a markdown document with:
|
||||||
|
- Package dependencies
|
||||||
|
- Ordered code blocks (no comments, no boilerplate)
|
||||||
|
- Clear markers for generic vs domain-specific code
|
||||||
|
|
||||||
|
Print it, take it to the sandbox, type it in.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: A change name (active or archived), a git commit range, or a list of files. If omitted, prompt for selection.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Identify the source feature**
|
||||||
|
|
||||||
|
If a change name is provided:
|
||||||
|
- Check active changes: `openspec list --json`
|
||||||
|
- Check archive: look in `openspec/changes/archive/` for directories ending with the name
|
||||||
|
- If found, read its artifacts: `proposal.md`, `design.md`, `tasks.md`
|
||||||
|
|
||||||
|
If no name provided:
|
||||||
|
- Run `openspec list --json` and list archived changes
|
||||||
|
- Use **AskUserQuestion tool** to let the user select
|
||||||
|
|
||||||
|
If a file list or commit range is provided instead:
|
||||||
|
- Read those files directly
|
||||||
|
- Identify the feature from the code
|
||||||
|
|
||||||
|
2. **Analyze dependency chain (cumulative mode)**
|
||||||
|
|
||||||
|
Features are often not independent — they build on each other. Before generating
|
||||||
|
the recipe, determine what the target feature depends on.
|
||||||
|
|
||||||
|
a. **Read all archived change proposals** in `openspec/changes/archive/` (sorted by date).
|
||||||
|
For each, read `proposal.md` to understand what it adds and what it depends on.
|
||||||
|
|
||||||
|
b. **Build a dependency graph**:
|
||||||
|
- Which changes does the target feature require?
|
||||||
|
- Which earlier changes are superseded? (e.g., if Change 6 replaces Change 3's
|
||||||
|
implementation, include Change 6's version, not Change 3's)
|
||||||
|
- Which changes are unrelated and can be skipped?
|
||||||
|
|
||||||
|
c. **Ask the user** using **AskUserQuestion tool**:
|
||||||
|
> "This feature depends on earlier changes. How should I scope the recipe?"
|
||||||
|
>
|
||||||
|
> Options:
|
||||||
|
> 1. **Cumulative** — include all foundation code this feature needs (recommended if target is a fresh codebase)
|
||||||
|
> 2. **Delta only** — just the code this specific change adds (use if target already has the foundation)
|
||||||
|
> 3. **Custom** — let me pick which dependencies to include
|
||||||
|
|
||||||
|
Show the dependency chain so the user can decide, e.g.:
|
||||||
|
```
|
||||||
|
migrate-to-semantic-kernel depends on:
|
||||||
|
← basic-chat-interface (UI + shared models)
|
||||||
|
← wire-responses-api (superseded — SK replaces the proxy)
|
||||||
|
← multi-turn-conversations (chat history)
|
||||||
|
```
|
||||||
|
|
||||||
|
d. **In cumulative mode**: Merge the dependency chain into a single coherent recipe.
|
||||||
|
- Walk changes in dependency order
|
||||||
|
- Skip superseded code (use the latest version of each file)
|
||||||
|
- Collapse packages from all changes into one prerequisites section
|
||||||
|
- If a file was created in Change 2 and modified in Change 5, include only the final version
|
||||||
|
|
||||||
|
e. **In delta mode**: Include only the code from the selected change.
|
||||||
|
|
||||||
|
3. **Analyze the feature scope**
|
||||||
|
|
||||||
|
From the change artifacts and/or code, determine:
|
||||||
|
- Which files were created or modified (across the full dependency chain if cumulative)
|
||||||
|
- What NuGet/npm packages were added
|
||||||
|
- What the dependency order is (e.g., models before controllers)
|
||||||
|
- What is generic infrastructure vs domain-specific logic
|
||||||
|
|
||||||
|
Read all relevant source files in the current codebase to get the actual current code.
|
||||||
|
**Always read the final current version of each file**, not intermediate versions.
|
||||||
|
|
||||||
|
3. **Generate the code recipe**
|
||||||
|
|
||||||
|
Create a markdown document with these sections, in this order:
|
||||||
|
|
||||||
|
**Header**: Feature name, source change, date
|
||||||
|
|
||||||
|
**Prerequisites**: Package references with exact versions
|
||||||
|
|
||||||
|
**Steps**: Ordered by dependency. Each step contains:
|
||||||
|
- Step number and action (e.g., "New file", "Add to existing file", "Modify")
|
||||||
|
- Target path (relative, adaptable)
|
||||||
|
- Namespace placeholder: `__YOUR_NAMESPACE__` where the target project namespace goes
|
||||||
|
- **Code block**: The actual code, stripped of:
|
||||||
|
- All comments (// and /* */ and /// XML docs)
|
||||||
|
- Redundant blank lines
|
||||||
|
- Verbose variable names that can be shortened
|
||||||
|
- Any code not directly related to the feature
|
||||||
|
- If the step modifies an existing file: show only the code to add, with a brief marker for insertion point (e.g., "Add after AddControllers()")
|
||||||
|
|
||||||
|
**Domain-Specific Sections**: Clearly marked with `// ADAPT:` prefix explaining what to change for the target domain
|
||||||
|
|
||||||
|
4. **Optimize for retyping**
|
||||||
|
|
||||||
|
Review the generated document and:
|
||||||
|
- Merge small files if they can be combined
|
||||||
|
- Remove any using statements that the IDE will auto-add
|
||||||
|
- Shorten any unnecessarily verbose code
|
||||||
|
- Ensure no step exceeds ~40 lines (split if needed)
|
||||||
|
- Add line counts per step so the user can estimate effort
|
||||||
|
- Total the overall line count at the top
|
||||||
|
|
||||||
|
5. **Write the output**
|
||||||
|
|
||||||
|
Save to: `openspec/exports/<change-name>-recipe.md`
|
||||||
|
|
||||||
|
Also display the full content so the user can review it immediately.
|
||||||
|
|
||||||
|
**Output Format**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Feature Recipe: <Feature Name>
|
||||||
|
**Source**: <change-name> | **Lines to type**: ~<N>
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- `Microsoft.SemanticKernel` 1.74.0
|
||||||
|
- `Microsoft.SemanticKernel.Connectors.OpenAI` 1.74.0
|
||||||
|
|
||||||
|
## Step 1: <Action> — <path> (~N lines)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
<stripped code>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: <Action> — <path> (~N lines)
|
||||||
|
|
||||||
|
Insert after `<marker>`:
|
||||||
|
```csharp
|
||||||
|
<stripped code>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Domain-Specific (adapt these)
|
||||||
|
|
||||||
|
### ExtractionPlugin.cs
|
||||||
|
```csharp
|
||||||
|
// ADAPT: Replace field names and validation logic for your domain
|
||||||
|
<code>
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Never include comments in code blocks — the goal is minimum keystrokes
|
||||||
|
- Always read the actual current source files, not just the change artifacts
|
||||||
|
- Preserve compilation order: models → services → controllers → DI registration
|
||||||
|
- Mark domain-specific code clearly so the user knows what to adapt vs copy verbatim
|
||||||
|
- Keep each step self-contained — the user may take breaks between steps
|
||||||
|
- If a feature spans more than ~200 lines of stripped code, warn the user and suggest using `/opsx:export-spec` instead
|
||||||
|
- Output must be valid markdown that renders well when printed
|
||||||
|
- When in cumulative mode, skip superseded code — always use the latest version of each file
|
||||||
|
- In the output header, show which changes were included and which were skipped (with reason)
|
||||||
|
- If a dependency chain is long (4+ changes), suggest `/opsx:export-spec` as a more efficient alternative
|
||||||
101
.claude/skills/openspec-ff-change/SKILL.md
Normal file
101
.claude/skills/openspec-ff-change/SKILL.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
74
.claude/skills/openspec-new-change/SKILL.md
Normal file
74
.claude/skills/openspec-new-change/SKILL.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
554
.claude/skills/openspec-onboard/SKILL.md
Normal file
554
.claude/skills/openspec-onboard/SKILL.md
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
110
.claude/skills/openspec-propose/SKILL.md
Normal file
110
.claude/skills/openspec-propose/SKILL.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
name: openspec-propose
|
||||||
|
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Propose a new change - create the change and generate all artifacts in one step.
|
||||||
|
|
||||||
|
I'll create a change with artifacts:
|
||||||
|
- proposal.md (what & why)
|
||||||
|
- design.md (how)
|
||||||
|
- tasks.md (implementation steps)
|
||||||
|
|
||||||
|
When ready to implement, run /opsx:apply
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **If no clear input provided, ask what they want to build**
|
||||||
|
|
||||||
|
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
|
||||||
|
> "What change do you want to work on? Describe what you want to build or fix."
|
||||||
|
|
||||||
|
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
|
||||||
|
|
||||||
|
2. **Create the change directory**
|
||||||
|
```bash
|
||||||
|
openspec new change "<name>"
|
||||||
|
```
|
||||||
|
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
|
||||||
|
|
||||||
|
3. **Get the artifact build order**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to get:
|
||||||
|
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||||
|
- `artifacts`: list of all artifacts with their status and dependencies
|
||||||
|
|
||||||
|
4. **Create artifacts in sequence until apply-ready**
|
||||||
|
|
||||||
|
Use the **TodoWrite tool** to track progress through the artifacts.
|
||||||
|
|
||||||
|
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||||
|
|
||||||
|
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||||
|
- Get instructions:
|
||||||
|
```bash
|
||||||
|
openspec instructions <artifact-id> --change "<name>" --json
|
||||||
|
```
|
||||||
|
- The instructions JSON includes:
|
||||||
|
- `context`: Project background (constraints for you - do NOT include in output)
|
||||||
|
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||||
|
- `template`: The structure to use for your output file
|
||||||
|
- `instruction`: Schema-specific guidance for this artifact type
|
||||||
|
- `outputPath`: Where to write the artifact
|
||||||
|
- `dependencies`: Completed artifacts to read for context
|
||||||
|
- Read any completed dependency files for context
|
||||||
|
- Create the artifact file using `template` as the structure
|
||||||
|
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||||
|
- Show brief progress: "Created <artifact-id>"
|
||||||
|
|
||||||
|
b. **Continue until all `applyRequires` artifacts are complete**
|
||||||
|
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||||
|
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
|
||||||
|
- Stop when all `applyRequires` artifacts are done
|
||||||
|
|
||||||
|
c. **If an artifact requires user input** (unclear context):
|
||||||
|
- Use **AskUserQuestion tool** to clarify
|
||||||
|
- Then continue with creation
|
||||||
|
|
||||||
|
5. **Show final status**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
After completing all artifacts, summarize:
|
||||||
|
- Change name and location
|
||||||
|
- List of artifacts created with brief descriptions
|
||||||
|
- What's ready: "All artifacts created! Ready for implementation."
|
||||||
|
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
|
||||||
|
|
||||||
|
**Artifact Creation Guidelines**
|
||||||
|
|
||||||
|
- Follow the `instruction` field from `openspec instructions` for each artifact type
|
||||||
|
- The schema defines what each artifact should contain - follow it
|
||||||
|
- Read dependency artifacts for context before creating new ones
|
||||||
|
- Use `template` as the structure for your output file - fill in its sections
|
||||||
|
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||||
|
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||||
|
- These guide what you write, but should never appear in the output
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
|
||||||
|
- Always read dependency artifacts before creating a new one
|
||||||
|
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||||
|
- If a change with that name already exists, ask if user wants to continue it or create a new one
|
||||||
|
- Verify each artifact file exists after writing before proceeding to next
|
||||||
138
.claude/skills/openspec-sync-specs/SKILL.md
Normal file
138
.claude/skills/openspec-sync-specs/SKILL.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
168
.claude/skills/openspec-verify-change/SKILL.md
Normal file
168
.claude/skills/openspec-verify-change/SKILL.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
---
|
||||||
|
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"
|
||||||
262
.gitignore
vendored
262
.gitignore
vendored
@@ -1,229 +1,57 @@
|
|||||||
# ---> Python
|
# ---> .NET / C#
|
||||||
# Byte-compiled / optimized / DLL files
|
## Build output
|
||||||
__pycache__/
|
bin/
|
||||||
*.py[cod]
|
obj/
|
||||||
*$py.class
|
publish/
|
||||||
|
|
||||||
# C extensions
|
## NuGet
|
||||||
*.so
|
*.nupkg
|
||||||
|
**/packages/
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
# Distribution / packaging
|
## User-specific files
|
||||||
.Python
|
*.user
|
||||||
build/
|
*.suo
|
||||||
develop-eggs/
|
*.userosscache
|
||||||
dist/
|
*.sln.docstates
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
## IDE
|
||||||
# Usually these files are written by a python script from a template
|
.vs/
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
.idea/
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
# ---> JupyterNotebooks
|
|
||||||
# gitignore template for Jupyter Notebooks
|
|
||||||
# website: http://jupyter.org/
|
|
||||||
|
|
||||||
.ipynb_checkpoints
|
|
||||||
*/.ipynb_checkpoints/*
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# Remove previous ipynb_checkpoints
|
|
||||||
# git rm -r .ipynb_checkpoints/
|
|
||||||
|
|
||||||
# ---> VisualStudioCode
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
## Rider
|
||||||
.history/
|
*.sln.iml
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
## Blazor WASM publish artifacts
|
||||||
*.vsix
|
|
||||||
|
|
||||||
# ---> VirtualEnv
|
|
||||||
# Virtualenv
|
|
||||||
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
|
||||||
.Python
|
|
||||||
[Bb]in
|
|
||||||
[Ii]nclude
|
|
||||||
[Ll]ib
|
|
||||||
[Ll]ib64
|
|
||||||
[Ll]ocal
|
|
||||||
[Ss]cripts
|
|
||||||
pyvenv.cfg
|
|
||||||
.venv
|
|
||||||
pip-selfcheck.json
|
|
||||||
|
|
||||||
# ---> .NET / C#
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
*.user
|
|
||||||
*.suo
|
|
||||||
.vs/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
**/wwwroot/_framework/
|
**/wwwroot/_framework/
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
|
## ReSharper / DotSettings
|
||||||
|
*.DotSettings.user
|
||||||
|
_ReSharper*/
|
||||||
|
|
||||||
|
## Test results
|
||||||
|
TestResults/
|
||||||
|
*.trx
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.json
|
||||||
|
|
||||||
|
## Secrets and environment
|
||||||
|
*.env
|
||||||
|
appsettings.*.json
|
||||||
|
!appsettings.json
|
||||||
|
!appsettings.Development.json
|
||||||
|
|
||||||
|
## OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
# Chat Agent WebApp
|
|
||||||
|
|
||||||
## What This Is
|
|
||||||
|
|
||||||
A personal AI chat web application built with Blazor WebAssembly and the OpenAI GPT API. Users send messages, receive streaming AI responses rendered as markdown, and manage multiple persistent conversations. The project doubles as an incremental learning journey — each phase introduces one concept with well-documented, explained code, making it suitable as a Blazor tutorial for a developer experienced in C# but new to the framework.
|
|
||||||
|
|
||||||
## Core Value
|
|
||||||
|
|
||||||
A working, well-understood AI chat interface — every line of code is intentional and explained, so the builder learns Blazor patterns while shipping a real product.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
### Validated
|
|
||||||
|
|
||||||
(None yet — ship to validate)
|
|
||||||
|
|
||||||
### Active
|
|
||||||
|
|
||||||
- [ ] Send messages to OpenAI GPT and display responses
|
|
||||||
- [ ] Stream AI responses token-by-token in real time
|
|
||||||
- [ ] Render AI responses as formatted markdown
|
|
||||||
- [ ] Create, switch between, and delete multiple conversations
|
|
||||||
- [ ] Persist conversation history to JSON files across sessions
|
|
||||||
- [ ] Clean, responsive chat UI with message bubbles and input area
|
|
||||||
- [ ] Well-commented code with inline explanations of Blazor concepts
|
|
||||||
- [ ] Incremental build structure — each phase introduces one concept
|
|
||||||
|
|
||||||
### Out of Scope
|
|
||||||
|
|
||||||
- Authentication/login — single user, no auth needed
|
|
||||||
- Database (SQL Server, PostgreSQL) — JSON file storage is sufficient for personal use
|
|
||||||
- Deployment/hosting — local development only for v1
|
|
||||||
- Multi-user support — personal tool
|
|
||||||
- OAuth or third-party login — not needed
|
|
||||||
- LangChain / agentic workflows — deferred to v2 milestone
|
|
||||||
- RAG (retrieval-augmented generation) — deferred to v2 milestone
|
|
||||||
- MCP servers — deferred to v2 milestone
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
- Builder is experienced in C# but new to Blazor — Blazor-specific patterns (components, lifecycle, dependency injection, SignalR for streaming) need clear inline documentation
|
|
||||||
- Project serves as both a real tool and a structured learning path
|
|
||||||
- v2 milestone will layer agentic capabilities: LangChain for workflow orchestration, RAG for document retrieval, and MCP servers for tool integration
|
|
||||||
- Blazor WebAssembly chosen — runs client-side in the browser, needs a separate backend API for OpenAI calls (API key must not be exposed to client)
|
|
||||||
- OpenAI GPT is the LLM backend (GPT-4o or latest available)
|
|
||||||
- JSON file storage on disk — simplest persistence, no database setup
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- **Tech stack**: .NET / C# / Blazor WebAssembly — non-negotiable
|
|
||||||
- **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
|
|
||||||
|
|
||||||
## Key Decisions
|
|
||||||
|
|
||||||
| Decision | Rationale | Outcome |
|
|
||||||
|----------|-----------|---------|
|
|
||||||
| Blazor WebAssembly over Server | User preference — client-side execution | — Pending |
|
|
||||||
| JSON file storage over SQLite/SQL | Simplest option for single-user personal tool | — Pending |
|
|
||||||
| OpenAI GPT as sole LLM provider | User preference for v1; multi-provider deferred | — Pending |
|
|
||||||
| Tutorial-style incremental phases | Project doubles as Blazor learning path | — Pending |
|
|
||||||
| v1 = chat, v2 = agentic features | Ship working chat first, layer LangChain/RAG/MCP later | — Pending |
|
|
||||||
|
|
||||||
## Evolution
|
|
||||||
|
|
||||||
This document evolves at phase transitions and milestone boundaries.
|
|
||||||
|
|
||||||
**After each phase transition** (via `/gsd:transition`):
|
|
||||||
1. Requirements invalidated? → Move to Out of Scope with reason
|
|
||||||
2. Requirements validated? → Move to Validated with phase reference
|
|
||||||
3. New requirements emerged? → Add to Active
|
|
||||||
4. Decisions to log? → Add to Key Decisions
|
|
||||||
5. "What This Is" still accurate? → Update if drifted
|
|
||||||
|
|
||||||
**After each milestone** (via `/gsd:complete-milestone`):
|
|
||||||
1. Full review of all sections
|
|
||||||
2. Core Value check — still the right priority?
|
|
||||||
3. Audit Out of Scope — reasons still valid?
|
|
||||||
4. Update Context with current state
|
|
||||||
|
|
||||||
---
|
|
||||||
*Last updated: 2026-03-27 after initialization*
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# Requirements: Chat Agent WebApp
|
|
||||||
|
|
||||||
**Defined:** 2026-03-27
|
|
||||||
**Core Value:** A working, well-understood AI chat interface — every line of code is intentional and explained
|
|
||||||
|
|
||||||
## v1 Requirements
|
|
||||||
|
|
||||||
### Chat Core
|
|
||||||
|
|
||||||
- [ ] **CHAT-01**: User can type a message and send it to the OpenAI GPT API
|
|
||||||
- [ ] **CHAT-02**: User receives the AI response displayed in the chat area
|
|
||||||
|
|
||||||
### Conversation Management
|
|
||||||
|
|
||||||
- [ ] **CONV-01**: User can create a new conversation
|
|
||||||
- [ ] **CONV-02**: User can switch between existing conversations
|
|
||||||
- [ ] **CONV-03**: User can delete a conversation
|
|
||||||
- [ ] **CONV-04**: Conversations persist to JSON files and survive app restarts
|
|
||||||
|
|
||||||
### Message Display
|
|
||||||
|
|
||||||
- [ ] **DISP-01**: AI responses render as formatted markdown (headings, lists, bold, links)
|
|
||||||
- [ ] **DISP-02**: Code blocks in AI responses display with syntax highlighting
|
|
||||||
- [ ] **DISP-03**: User can copy a message to clipboard
|
|
||||||
|
|
||||||
### UI/Layout
|
|
||||||
|
|
||||||
- [ ] **UI-01**: App has sidebar with conversation list and main chat area
|
|
||||||
- [ ] **UI-02**: Chat area has text input with send button
|
|
||||||
- [ ] **UI-03**: App layout is responsive across screen sizes
|
|
||||||
|
|
||||||
### Code Quality
|
|
||||||
|
|
||||||
- [ ] **CODE-01**: Every Blazor concept introduced has inline comments explaining what and why
|
|
||||||
- [x] **CODE-02**: Each phase introduces one concept incrementally (tutorial-style progression)
|
|
||||||
|
|
||||||
## v2 Requirements
|
|
||||||
|
|
||||||
### Streaming
|
|
||||||
|
|
||||||
- **STRM-01**: AI responses stream token-by-token in real time
|
|
||||||
- **STRM-02**: User can cancel an in-progress AI response
|
|
||||||
- **STRM-03**: Loading/typing indicator shows during AI generation
|
|
||||||
|
|
||||||
### Agentic Workflows
|
|
||||||
|
|
||||||
- **AGNT-01**: LangChain integration for workflow orchestration
|
|
||||||
- **AGNT-02**: RAG pipeline for document retrieval
|
|
||||||
- **AGNT-03**: MCP server integration for tool use
|
|
||||||
|
|
||||||
### Enhanced Features
|
|
||||||
|
|
||||||
- **ENH-01**: Auto-generated conversation titles from first message
|
|
||||||
- **ENH-02**: System prompt configuration per conversation
|
|
||||||
- **ENH-03**: Model selector (GPT-4o, GPT-4o-mini, etc.)
|
|
||||||
- **ENH-04**: Dark/light theme toggle
|
|
||||||
- **ENH-05**: Auto-scroll during message display
|
|
||||||
- **ENH-06**: Visual distinction between user and AI messages (styled bubbles)
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
| Feature | Reason |
|
|
||||||
|---------|--------|
|
|
||||||
| Authentication/login | Single user, no auth needed |
|
|
||||||
| Database storage | JSON files sufficient for personal use |
|
|
||||||
| Deployment/hosting | Local development only for v1 |
|
|
||||||
| Multi-user support | Personal tool |
|
|
||||||
| OAuth/third-party login | Not needed |
|
|
||||||
| Voice input/output | Complexity, not core to chat value |
|
|
||||||
| Image/multimodal input | Defer to v2+ |
|
|
||||||
| Real-time collaboration | Single user |
|
|
||||||
| Mobile app | Web-first |
|
|
||||||
|
|
||||||
## Traceability
|
|
||||||
|
|
||||||
Which phases cover which requirements. Updated during roadmap creation.
|
|
||||||
|
|
||||||
| Requirement | Phase | Status |
|
|
||||||
|-------------|-------|--------|
|
|
||||||
| CHAT-01 | Phase 3 | Pending |
|
|
||||||
| CHAT-02 | Phase 3 | Pending |
|
|
||||||
| CONV-01 | Phase 2 | Pending |
|
|
||||||
| CONV-02 | Phase 2 | Pending |
|
|
||||||
| CONV-03 | Phase 2 | Pending |
|
|
||||||
| CONV-04 | Phase 2 | Pending |
|
|
||||||
| DISP-01 | Phase 4 | Pending |
|
|
||||||
| DISP-02 | Phase 4 | Pending |
|
|
||||||
| DISP-03 | Phase 4 | Pending |
|
|
||||||
| UI-01 | Phase 5 | Pending |
|
|
||||||
| UI-02 | Phase 5 | Pending |
|
|
||||||
| UI-03 | Phase 5 | Pending |
|
|
||||||
| CODE-01 | Phase 1 | Pending |
|
|
||||||
| CODE-02 | Phase 1 | Complete |
|
|
||||||
|
|
||||||
**Coverage:**
|
|
||||||
- v1 requirements: 14 total
|
|
||||||
- Mapped to phases: 14
|
|
||||||
- Unmapped: 0 ✓
|
|
||||||
|
|
||||||
---
|
|
||||||
*Requirements defined: 2026-03-27*
|
|
||||||
*Last updated: 2026-03-27 after roadmap creation — all 14 requirements mapped*
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Roadmap: Chat Agent WebApp
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Five phases take this project from an empty solution to a complete personal AI chat application. Each phase introduces one Blazor concept and delivers something verifiable. The dependency chain is strict: the WASM/API architecture must exist before storage, storage before conversations, conversations before AI, AI before display polish, and display before UI completeness. Streaming is deferred to v2. The result is a working, well-understood chat interface where every line of code is intentional and explained.
|
|
||||||
|
|
||||||
## Phases
|
|
||||||
|
|
||||||
**Phase Numbering:**
|
|
||||||
- Integer phases (1, 2, 3): Planned milestone work
|
|
||||||
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
|
||||||
|
|
||||||
Decimal phases appear between their surrounding integers in numeric order.
|
|
||||||
|
|
||||||
- [ ] **Phase 1: Architecture Foundation** - Two-project solution scaffold with WASM/API split and shared models; establishes the tutorial commenting convention
|
|
||||||
- [ ] **Phase 2: Conversation Storage** - JSON file persistence and full conversation CRUD via repository pattern and Minimal API endpoints
|
|
||||||
- [ ] **Phase 3: Basic AI Chat** - End-to-end chat loop (send message → OpenAI → display response) without streaming
|
|
||||||
- [ ] **Phase 4: Message Display** - Markdown rendering, syntax-highlighted code blocks, and copy-to-clipboard
|
|
||||||
- [ ] **Phase 5: UI Polish** - Sidebar layout, chat input area, and responsive design across screen sizes
|
|
||||||
|
|
||||||
## Phase Details
|
|
||||||
|
|
||||||
### Phase 1: Architecture Foundation
|
|
||||||
**Goal**: The solution structure is locked in and the critical boundaries (no API key in WASM, no file I/O in WASM, no direct OpenAI calls from WASM) are architecturally enforced before any feature code is written
|
|
||||||
**Depends on**: Nothing (first phase)
|
|
||||||
**Requirements**: CODE-01, CODE-02
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. Running `dotnet run` on both projects starts WASM client and API server without errors
|
|
||||||
2. A test HTTP request from the WASM client reaches the API server and returns a response (CORS working)
|
|
||||||
3. The shared models library is referenced by both projects with no duplication of DTOs
|
|
||||||
4. Running `dotnet publish` on the WASM project completes with no IL trim warnings
|
|
||||||
5. Every file introduced contains inline comments explaining the Blazor concept it demonstrates
|
|
||||||
**Plans**: 2 plans
|
|
||||||
Plans:
|
|
||||||
- [x] 01-01-PLAN.md — Solution scaffold: create three projects, wire references, configure ports
|
|
||||||
- [ ] 01-02-PLAN.md — Health check round-trip: shared DTO, API controller with CORS, typed client, home page
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
### Phase 2: Conversation Storage
|
|
||||||
**Goal**: Users can create, switch between, and delete conversations that persist to disk and survive app restarts — with no AI integration yet
|
|
||||||
**Depends on**: Phase 1
|
|
||||||
**Requirements**: CONV-01, CONV-02, CONV-03, CONV-04
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. User can create a new conversation and see it appear in the conversation list
|
|
||||||
2. User can click a conversation in the list and switch to it (conversation content loads)
|
|
||||||
3. User can delete a conversation and see it removed from the list
|
|
||||||
4. After closing and reopening the browser, all conversations and their messages are still present
|
|
||||||
**Plans**: TBD
|
|
||||||
|
|
||||||
### Phase 3: Basic AI Chat
|
|
||||||
**Goal**: Users can send a message and receive an AI response in the active conversation, with the full request/response cycle working end-to-end
|
|
||||||
**Depends on**: Phase 2
|
|
||||||
**Requirements**: CHAT-01, CHAT-02
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. User can type a message, press send, and see their message appear in the chat area
|
|
||||||
2. User receives an AI response from OpenAI GPT displayed in the chat area
|
|
||||||
3. The conversation history is preserved across the full exchange and saved to disk
|
|
||||||
4. The OpenAI API key is never present in any WASM project file or browser network request
|
|
||||||
**Plans**: TBD
|
|
||||||
|
|
||||||
### Phase 4: Message Display
|
|
||||||
**Goal**: AI responses render as readable, formatted content — not raw markdown strings — with syntax-highlighted code and one-click copy
|
|
||||||
**Depends on**: Phase 3
|
|
||||||
**Requirements**: DISP-01, DISP-02, DISP-03
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. An AI response containing markdown (headings, lists, bold, links) renders as formatted HTML in the chat area
|
|
||||||
2. Code blocks in AI responses display with syntax highlighting appropriate to the language
|
|
||||||
3. User can click a copy button on any message and the text is copied to the clipboard
|
|
||||||
**Plans**: TBD
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
### Phase 5: UI Polish
|
|
||||||
**Goal**: The app has a complete, usable layout — sidebar with conversation list, chat input area, and a responsive design that works across screen sizes
|
|
||||||
**Depends on**: Phase 4
|
|
||||||
**Requirements**: UI-01, UI-02, UI-03
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. App displays a sidebar on the left with the conversation list and a main chat area on the right
|
|
||||||
2. The chat area has a text input field and a send button visible at the bottom of the screen
|
|
||||||
3. The layout reflows gracefully on a narrow viewport (mobile-width browser window) without horizontal scrolling or clipped content
|
|
||||||
**Plans**: TBD
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
## Progress
|
|
||||||
|
|
||||||
**Execution Order:**
|
|
||||||
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
|
|
||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
|
||||||
|-------|----------------|--------|-----------|
|
|
||||||
| 1. Architecture Foundation | 0/2 | Planning complete | - |
|
|
||||||
| 2. Conversation Storage | 0/TBD | Not started | - |
|
|
||||||
| 3. Basic AI Chat | 0/TBD | Not started | - |
|
|
||||||
| 4. Message Display | 0/TBD | Not started | - |
|
|
||||||
| 5. UI Polish | 0/TBD | Not started | - |
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
gsd_state_version: 1.0
|
|
||||||
milestone: v1.0
|
|
||||||
milestone_name: milestone
|
|
||||||
status: executing
|
|
||||||
stopped_at: Completed 01-01-PLAN.md
|
|
||||||
last_updated: "2026-03-27T22:53:36.052Z"
|
|
||||||
last_activity: 2026-03-27
|
|
||||||
progress:
|
|
||||||
total_phases: 5
|
|
||||||
completed_phases: 0
|
|
||||||
total_plans: 2
|
|
||||||
completed_plans: 1
|
|
||||||
percent: 0
|
|
||||||
---
|
|
||||||
|
|
||||||
# Project State
|
|
||||||
|
|
||||||
## Project Reference
|
|
||||||
|
|
||||||
See: .planning/PROJECT.md (updated 2026-03-27)
|
|
||||||
|
|
||||||
**Core value:** A working, well-understood AI chat interface — every line of code is intentional and explained
|
|
||||||
**Current focus:** Phase 01 — architecture-foundation
|
|
||||||
|
|
||||||
## Current Position
|
|
||||||
|
|
||||||
Phase: 01 (architecture-foundation) — EXECUTING
|
|
||||||
Plan: 2 of 2
|
|
||||||
Status: Ready to execute
|
|
||||||
Last activity: 2026-03-27
|
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
|
||||||
|
|
||||||
## Performance Metrics
|
|
||||||
|
|
||||||
**Velocity:**
|
|
||||||
|
|
||||||
- Total plans completed: 0
|
|
||||||
- Average duration: —
|
|
||||||
- Total execution time: 0 hours
|
|
||||||
|
|
||||||
**By Phase:**
|
|
||||||
|
|
||||||
| Phase | Plans | Total | Avg/Plan |
|
|
||||||
|-------|-------|-------|----------|
|
|
||||||
| - | - | - | - |
|
|
||||||
|
|
||||||
**Recent Trend:**
|
|
||||||
|
|
||||||
- Last 5 plans: —
|
|
||||||
- Trend: —
|
|
||||||
|
|
||||||
*Updated after each plan completion*
|
|
||||||
| Phase 01 P01 | 2min | 2 tasks | 26 files |
|
|
||||||
|
|
||||||
## Accumulated Context
|
|
||||||
|
|
||||||
### Decisions
|
|
||||||
|
|
||||||
Decisions are logged in PROJECT.md Key Decisions table.
|
|
||||||
Recent decisions affecting current work:
|
|
||||||
|
|
||||||
- [Init]: Streaming (STRM-01/02/03) deferred to v2 — user chose non-streaming for v1
|
|
||||||
- [Init]: Tutorial convention established in Phase 1 — CODE-01/02 anchor there, applied cross-phase
|
|
||||||
- [Init]: Phase order follows strict dependency chain: scaffold → storage → AI → display → UI polish
|
|
||||||
- [Phase 01]: Pinned .NET 9 via global.json since machine default is .NET 10
|
|
||||||
|
|
||||||
### Pending Todos
|
|
||||||
|
|
||||||
None yet.
|
|
||||||
|
|
||||||
### Blockers/Concerns
|
|
||||||
|
|
||||||
- [Research flag] Phase 4: Verify `Markdown.ColorCode` WASM compatibility before implementing (community-reported, not officially confirmed)
|
|
||||||
- [Research flag] Phase 4: Confirm Markdig `DisableHtml()` decision — accepted XSS risk for single-user tool; document explicitly in code
|
|
||||||
|
|
||||||
## Session Continuity
|
|
||||||
|
|
||||||
Last session: 2026-03-27T22:53:36.049Z
|
|
||||||
Stopped at: Completed 01-01-PLAN.md
|
|
||||||
Resume file: None
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
# Architecture
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Pattern Overview
|
|
||||||
|
|
||||||
**Overall:** Agent-orchestrated agentic code framework (GSD - Get Shit Done)
|
|
||||||
|
|
||||||
**Key Characteristics:**
|
|
||||||
- Multi-agent cooperative system: 18 specialized agents working in coordinated workflows
|
|
||||||
- Document-driven state management: All decisions and progress stored as Markdown files
|
|
||||||
- Workflow-based orchestration: 56 workflows guide agent interactions and user engagement
|
|
||||||
- Agentic code execution: Plans are executable prompts for Claude model execution
|
|
||||||
- Extensible per-runtime: Same architecture replicated across 7 IDE/agent providers (.claude, .agent, .gemini, .codex, .cursor, .windsurf, .opencode)
|
|
||||||
|
|
||||||
## Layers
|
|
||||||
|
|
||||||
**Orchestration Layer:**
|
|
||||||
- Purpose: Coordinate user commands, route to agents, manage phase/milestone progression
|
|
||||||
- Location: `.claude/get-shit-done/workflows/`
|
|
||||||
- Contains: 56 workflow definitions (markdown files), each defining a multi-step process
|
|
||||||
- Depends on: gsd-tools.cjs CLI for state management, git operations, config parsing
|
|
||||||
- Used by: All commands (user entry points)
|
|
||||||
|
|
||||||
**Agent Layer:**
|
|
||||||
- Purpose: Specialized reasoning for different task types (planning, execution, research, verification, mapping)
|
|
||||||
- Location: `.claude/agents/` (18 agents)
|
|
||||||
- Contains: Agent role definitions, instructions, tool access lists
|
|
||||||
- Agents: gsd-planner, gsd-executor, gsd-phase-researcher, gsd-verifier, gsd-debugger, gsd-codebase-mapper, gsd-integration-checker, gsd-ui-auditor, gsd-plan-checker, and 9 others
|
|
||||||
- Depends on: Workflow coordination, tools (Read, Write, Bash, Grep, Glob, WebFetch)
|
|
||||||
- Used by: Orchestration workflows, invoked via `@agent-name` or Task() calls
|
|
||||||
|
|
||||||
**State Management Layer:**
|
|
||||||
- Purpose: Persistent tracking of project progress, decisions, and configuration
|
|
||||||
- Location: `.planning/` (global state), phase directories (phase-local state)
|
|
||||||
- Contains: STATE.md (global), CONTEXT.md (decisions), ROADMAP.md (scope), PLAN.md (task breakdown), SUMMARY.md (execution results), VERIFICATION.md (quality gates)
|
|
||||||
- Depends on: gsd-tools.cjs for parsing, git for history
|
|
||||||
- Used by: All agents and orchestration layer
|
|
||||||
|
|
||||||
**Tool Layer:**
|
|
||||||
- Purpose: Provide execution capabilities for agents
|
|
||||||
- Location: Various (bash, file I/O, git, web)
|
|
||||||
- Tools: Read, Write, Edit, Bash, Grep, Glob, WebFetch, mcp__context7__*
|
|
||||||
- Depends on: Agent invocations
|
|
||||||
- Used by: Agents during execution
|
|
||||||
|
|
||||||
**CLI Tool Layer:**
|
|
||||||
- Purpose: Centralized state/config operations shared across 50+ workflow/agent files
|
|
||||||
- Location: `.claude/get-shit-done/bin/gsd-tools.cjs` (~1000 lines)
|
|
||||||
- Contains: 100+ commands for state management, phase operations, validation, progress tracking
|
|
||||||
- Depends on: Node.js built-ins, git, file system
|
|
||||||
- Used by: Every workflow and agent (called via bash subprocess)
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
**Phase Planning Flow:**
|
|
||||||
|
|
||||||
1. User invokes `/gsd:plan-phase PHASE_NUMBER`
|
|
||||||
2. Orchestrator (plan-phase workflow) loads PHASE state via gsd-tools.cjs
|
|
||||||
3. Planner agent (gsd-planner) reads CONTEXT.md (user decisions), codebase structure, creates PLAN.md
|
|
||||||
4. Plan Checker agent validates PLAN.md structure and quality
|
|
||||||
5. PLAN.md written to phase directory
|
|
||||||
6. User reviews, optionally discusses via `/gsd:discuss-phase` → updates CONTEXT.md
|
|
||||||
7. Flow loops if plan changes needed
|
|
||||||
|
|
||||||
**Phase Execution Flow:**
|
|
||||||
|
|
||||||
1. User invokes `/gsd:execute-phase PHASE_NUMBER`
|
|
||||||
2. Executor orchestrator loads phase plans (multiple per phase)
|
|
||||||
3. Plans grouped by wave (dependency-aware parallelization)
|
|
||||||
4. For each wave:
|
|
||||||
- Executor agent loads PLAN.md
|
|
||||||
- Executes tasks sequentially (each task → code change → test verification)
|
|
||||||
- Commits per-task (atomic commits for rollback safety)
|
|
||||||
- Writes SUMMARY.md with results
|
|
||||||
5. Orchestrator collects all SUMMARY.md files
|
|
||||||
6. Verifier agent checks phase completion against VERIFICATION.md
|
|
||||||
7. STATE.md updated with completion status
|
|
||||||
8. Planning docs committed to git (if commit_docs: true)
|
|
||||||
|
|
||||||
**Verification Flow:**
|
|
||||||
|
|
||||||
1. Phase execution completes, SUMMARY.md produced
|
|
||||||
2. Verifier agent compares output against VERIFICATION.md (user-defined success criteria)
|
|
||||||
3. If gaps detected: Trigger `/gsd:plan-phase --gaps` for remedial planning
|
|
||||||
4. Remedial PLAN.md created with gap-closure tasks
|
|
||||||
5. Execute → Verify loop continues until no gaps
|
|
||||||
|
|
||||||
**State Management Flow:**
|
|
||||||
|
|
||||||
1. STATE.md maintains global position: current_phase, current_milestone, workstream, blockers
|
|
||||||
2. Phase directories contain CONTEXT.md (decisions), PLAN.md (tasks), SUMMARY.md (results), VERIFICATION.md (success gates)
|
|
||||||
3. ROADMAP.md holds master scope: all phases with descriptions
|
|
||||||
4. .planning/config.json: branching strategy, model profiles, search behavior
|
|
||||||
5. Each operation updates relevant state file, committed atomically
|
|
||||||
|
|
||||||
## Key Abstractions
|
|
||||||
|
|
||||||
**Phase:**
|
|
||||||
- Purpose: Logical unit of work scoped by user, contains multiple Plans
|
|
||||||
- Examples: `1`, `1.1`, `1.2` (decimal numbering for sub-phases)
|
|
||||||
- Pattern: Each phase has directory `.planning/phases/N/` with CONTEXT.md, PLAN.md, SUMMARY.md, VERIFICATION.md
|
|
||||||
|
|
||||||
**Plan:**
|
|
||||||
- Purpose: Executable task breakdown derived from phase scope (typically 2-3 tasks per plan)
|
|
||||||
- Examples: `PLAN-01.md`, `PLAN-02.md` within a phase directory
|
|
||||||
- Pattern: PLAN.md contains frontmatter (metadata), objective, context references, tasks array, success criteria
|
|
||||||
|
|
||||||
**Task:**
|
|
||||||
- Purpose: Atomic unit of work within a plan (code change, test, commit)
|
|
||||||
- Types: `type="auto"` (fully autonomous), `type="checkpoint"` (pause before), `type="review"` (requires human sign-off)
|
|
||||||
- Pattern: Task has action (what to do), verification criteria, expected artifacts
|
|
||||||
|
|
||||||
**Workflow:**
|
|
||||||
- Purpose: Multi-step process coordinating user input and agent actions
|
|
||||||
- Examples: `plan-phase.md`, `execute-phase.md`, `discuss-phase.md`
|
|
||||||
- Pattern: Workflows define steps with conditional branching, agent spawning, state updates
|
|
||||||
|
|
||||||
**Agent Profiles:**
|
|
||||||
- Purpose: Model selection strategy for agent execution (quality vs cost)
|
|
||||||
- Profiles: `quality` (Opus for all), `balanced` (Opus planning, Sonnet execution), `budget` (minimal Opus)
|
|
||||||
- Pattern: Resolved via gsd-tools.cjs based on `.planning/config.json` model_profile setting
|
|
||||||
|
|
||||||
## Entry Points
|
|
||||||
|
|
||||||
**Command Entry Point:**
|
|
||||||
- Location: `.claude/get-shit-done/workflows/` (any `.md` file)
|
|
||||||
- Triggers: User invokes `/gsd:WORKFLOW_NAME [args]`
|
|
||||||
- Responsibilities: Parse arguments, call gsd-tools.cjs init command, route to agents or inline execution
|
|
||||||
|
|
||||||
**Agent Entry Point:**
|
|
||||||
- Location: `.claude/agents/AGENT_NAME.md`
|
|
||||||
- Triggers: Spawned by orchestrator via `@AGENT_NAME` mention or Task() call
|
|
||||||
- Responsibilities: Load context, read mandatory files, execute role-specific logic, return results
|
|
||||||
|
|
||||||
**Tool Entry Point:**
|
|
||||||
- Location: `.claude/get-shit-done/bin/gsd-tools.cjs`
|
|
||||||
- Triggers: `node gsd-tools.cjs <command> [args]`
|
|
||||||
- Responsibilities: Parse subcommands, perform atomic state/git operations, output JSON
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
**Strategy:** Graceful degradation with checkpoint pauses
|
|
||||||
|
|
||||||
**Patterns:**
|
|
||||||
- **Auth Errors:** Executor pauses at checkpoint, waits for user to provide credentials
|
|
||||||
- **Verification Failures:** Verifier identifies gaps, planner creates remedial PLAN.md, executor runs gap-closure phase
|
|
||||||
- **Invalid State:** gsd-tools.cjs validate commands detect inconsistencies, suggest repairs
|
|
||||||
- **Parse Failures:** Frontmatter validation catches schema violations early
|
|
||||||
- **Git Conflicts:** Executor respects branching strategy, creates feature branches per config
|
|
||||||
|
|
||||||
## Cross-Cutting Concerns
|
|
||||||
|
|
||||||
**Logging:** Shell output captured from gsd-tools.cjs, bash task execution. Errors logged to console, summary tracked in SUMMARY.md.
|
|
||||||
|
|
||||||
**Validation:** gsd-tools.cjs provides `validate` commands for:
|
|
||||||
- Phase numbering consistency
|
|
||||||
- Plan structure (required fields, task arrays)
|
|
||||||
- References (@ paths resolve to existing files)
|
|
||||||
- Artifacts (must_haves tracked in PLAN.md)
|
|
||||||
|
|
||||||
**Authentication:** Config stored in `~/.gsd/` (outside repo), environment variable overrides for CI. Secrets never committed to git.
|
|
||||||
|
|
||||||
**Atomicity:** gsd-tools.cjs commit command handles commit_docs check and .gitignore detection. Each task commits separately for rollback safety.
|
|
||||||
|
|
||||||
**Determinism:** Model profiles ensure same agent type always gets same model (unless inherit mode). Frontmatter schema validation prevents parsing ambiguity.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Architecture analysis: 2026-03-27*
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
# Codebase Concerns
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Silent Error Handling (Critical)
|
|
||||||
|
|
||||||
**Widespread use of empty catch blocks:**
|
|
||||||
- Files: `/.claude/hooks/gsd-prompt-guard.js`, `/.claude/hooks/gsd-context-monitor.js`, `/.claude/hooks/gsd-workflow-guard.js`, `/.claude/hooks/gsd-statusline.js`, `/.claude/hooks/gsd-check-update.js`
|
|
||||||
- Issue: 14+ catch blocks across hooks swallow errors silently with `catch (e) {}` or `catch (e) { // comment }` without logging
|
|
||||||
- Impact: Failures in hook execution go undetected, making debugging difficult. When files are corrupted, config fails to parse, or file system errors occur, no indication is provided to users or developers
|
|
||||||
- Current mitigation: Comments explain intent (e.g., "Silent fail -- bridge is best-effort", "don't break statusline on parse errors"), but no logging mechanism exists
|
|
||||||
- Recommendation: Implement optional debug logging to stderr (only when hooks are invoked in debug mode) without breaking normal operation. Use environment variable like `GSD_HOOK_DEBUG=1` to enable error logging
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fragile Hook Timeout Architecture
|
|
||||||
|
|
||||||
**Context-Monitor Hook (`.claude/hooks/gsd-context-monitor.js`)**:
|
|
||||||
- Issue: 10-second stdin timeout (line 35) is tight for slow systems. On Windows/Git Bash, pipe delays can exceed this
|
|
||||||
- Files: `/.claude/hooks/gsd-context-monitor.js` (line 35: `setTimeout(() => process.exit(0), 10000)`)
|
|
||||||
- Current mitigation: Comments reference issues #775, #1162 indicating this was a known problem
|
|
||||||
- Risk: On systems with slow I/O, the hook exits prematurely without reading input, potentially leaving metrics unprocessed
|
|
||||||
- Safe modification: Consider detecting platform and adjusting timeout accordingly, or make timeout configurable via environment variable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hardcoded Buffer Percentage
|
|
||||||
|
|
||||||
**Status Line Hook Context Normalization**:
|
|
||||||
- Issue: 16.5% buffer hardcoded for Claude Code autocompact (line 29)
|
|
||||||
- Files: `/.claude/hooks/gsd-statusline.js` (line 29: `const AUTO_COMPACT_BUFFER_PCT = 16.5`)
|
|
||||||
- Impact: If Claude Code changes autocompact behavior or other models have different buffers, context calculations become inaccurate
|
|
||||||
- Recommendation: Make this configurable via `.planning/config.json` with sensible defaults per model (Claude, Gemini, OpenCode)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Stale Hook Version Tracking
|
|
||||||
|
|
||||||
**Hook Version Header Validation**:
|
|
||||||
- Issue: Version detection depends on comment headers in JavaScript files (line 78 in `gsd-check-update.js`)
|
|
||||||
- Files: `/.claude/hooks/gsd-check-update.js` (lines 73-92)
|
|
||||||
- Risk: If hook files are minified, reformatted, or copied incorrectly, version headers can be lost, making stale hook detection fail silently
|
|
||||||
- Current state: Detects "unknown" version if header is missing, but this doesn't prevent the hook from running with wrong version
|
|
||||||
- Recommendation: Add a checksum or content hash validation in addition to version headers to detect unintended hook modifications
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Process Detachment on Windows
|
|
||||||
|
|
||||||
**Check-Update Hook Background Process**:
|
|
||||||
- Issue: Uses `spawn()` with `detached: true` (line 111 in `gsd-check-update.js`)
|
|
||||||
- Files: `/.claude/hooks/gsd-check-update.js` (lines 108-114)
|
|
||||||
- Impact: Background npm process can hang on Windows if not properly detached. Blocks are prevented by `unref()` (line 114), but edge cases remain if process fails
|
|
||||||
- Current state: `windowsHide: true` flag added, `stdio: 'ignore'` set
|
|
||||||
- Recommendation: Add timeout to background spawn, return immediately after unref, and consider writing to log file instead of silently ignoring errors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Config File Parsing Without Validation
|
|
||||||
|
|
||||||
**Multiple Hooks Read `.planning/config.json`**:
|
|
||||||
- Issue: Config files are parsed with `JSON.parse()` but no schema validation exists
|
|
||||||
- Files: `/.claude/hooks/gsd-context-monitor.js` (line 53), `/.claude/hooks/gsd-workflow-guard.js` (line 65)
|
|
||||||
- Risk: Malformed JSON or missing expected fields cause silent failures. If config structure changes, old configs won't error but silently ignore new fields
|
|
||||||
- Recommendation: Implement a simple schema validation utility (even lightweight: check for required keys and types) and log warnings when config doesn't match expected shape
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Race Conditions in Metrics Bridge
|
|
||||||
|
|
||||||
**Context Metrics Between Statusline and Monitor Hooks**:
|
|
||||||
- Issue: Two separate hooks read/write to `/tmp/claude-ctx-{sessionId}.json` without file locking
|
|
||||||
- Files: `/.claude/hooks/gsd-statusline.js` (line 40-47 writes), `/.claude/hooks/gsd-context-monitor.js` (line 63-70 reads)
|
|
||||||
- Risk: On high-frequency tool use, write and read can race, causing monitor hook to read stale metrics or corrupted JSON
|
|
||||||
- Current mitigation: Both have try-catch around JSON.parse(), stale metrics are detected by timestamp (line 74)
|
|
||||||
- Recommendation: Use atomic writes (write-to-temp-then-rename pattern) for metrics file, or switch to a simpler shared state mechanism
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Missing Logging Infrastructure
|
|
||||||
|
|
||||||
**No Structured Logging**:
|
|
||||||
- Issue: Hooks only log to stdout via process.stdout.write() for hook outputs. No persistent logs for troubleshooting
|
|
||||||
- Files: All hook files (`/.claude/hooks/*.js`)
|
|
||||||
- Impact: When hooks fail silently, no audit trail exists to debug issues. Users cannot see what hooks detected or why they exited
|
|
||||||
- Recommendation: Create optional debug logs in `.planning/hooks-debug.log` (only when env var `GSD_DEBUG=1`), append structured JSON records with timestamp, hook name, and outcome
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Unbounded Memory in Warning State File
|
|
||||||
|
|
||||||
**Context Monitor Warning Debounce**:
|
|
||||||
- Issue: Warning state tracked in `/tmp/claude-ctx-{sessionId}-warned.json` accumulates indefinitely
|
|
||||||
- Files: `/.claude/hooks/gsd-context-monitor.js` (lines 87-117)
|
|
||||||
- Risk: Temporary directory might not clean this up, causing stale state from previous sessions to affect new sessions
|
|
||||||
- Current mitigation: State is per-session (uses sessionId), so scope is limited
|
|
||||||
- Recommendation: Add timestamp to warning state and treat state older than 1 hour as stale (auto-reset)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Regex Pattern Complexity in Prompt Guard
|
|
||||||
|
|
||||||
**Prompt Injection Detection Patterns**:
|
|
||||||
- Issue: 12 regex patterns for injection detection (lines 18-32 in `gsd-prompt-guard.js`) are not anchored and may be fragile
|
|
||||||
- Files: `/.claude/hooks/gsd-prompt-guard.js` (lines 18-32)
|
|
||||||
- Risk: Some patterns (e.g., `/you\s+are\s+now\s+/i`) could match benign documentation text about "You are now ready to..." and trigger false warnings
|
|
||||||
- Impact: While warnings are advisory-only, frequent false positives degrade user experience
|
|
||||||
- Recommendation: Review patterns for false positive risk, consider context (e.g., only in code blocks, not in comments), or add allowlist for common false positives
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Inconsistent Hook Version Across Environments
|
|
||||||
|
|
||||||
**Multi-Environment Hook Duplication**:
|
|
||||||
- Issue: Identical hooks duplicated across `.claude/`, `.agent/`, `.gemini/`, `.opencode/` directories
|
|
||||||
- Files: 4x each of `gsd-prompt-guard.js`, `gsd-context-monitor.js`, `gsd-workflow-guard.js`, `gsd-statusline.js`, `gsd-check-update.js`
|
|
||||||
- Risk: If one environment's hooks are updated, others can drift out of sync. Version check in `gsd-check-update.js` detects this (line 81-82) but doesn't auto-fix
|
|
||||||
- Impact: Users with multiple agent environments may see inconsistent behavior
|
|
||||||
- Recommendation: Consolidate hooks to a shared location or implement auto-sync mechanism in installer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resource Leaks in Timeouts
|
|
||||||
|
|
||||||
**Stdin Timeout Cleanup**:
|
|
||||||
- Issue: `setTimeout()` created for stdin timeout in all hooks, relies on `clearTimeout()` to clean up
|
|
||||||
- Files: `/.claude/hooks/gsd-prompt-guard.js`, `/.claude/hooks/gsd-context-monitor.js`, `/.claude/hooks/gsd-workflow-guard.js`, `/.claude/hooks/gsd-statusline.js`
|
|
||||||
- Risk: If process exits before reaching `process.stdin.on('end')`, timeout continues running until expiry (3-10 seconds)
|
|
||||||
- Current state: Process exits on timeout with code 0, but spawned processes inherit this timeout
|
|
||||||
- Recommendation: Add `unref()` call on timeout timers so they don't keep process alive
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Platform-Specific Path Separator
|
|
||||||
|
|
||||||
**Windows Path Handling in Guards**:
|
|
||||||
- Issue: Guards check for both `/` and `\\` in file paths (e.g., line 52 in `gsd-prompt-guard.js`)
|
|
||||||
- Files: `/.claude/hooks/gsd-prompt-guard.js` (line 52), `/.claude/hooks/gsd-workflow-guard.js` (line 43)
|
|
||||||
- Risk: Mixing separators when checking `.planning/` paths may not catch all variations on Windows (e.g., mixed slashes)
|
|
||||||
- Recommendation: Normalize paths using `path.normalize()` before comparison, or use `path.sep` for platform detection
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Missing Config Directory Override Documentation
|
|
||||||
|
|
||||||
**CLAUDE_CONFIG_DIR Environment Variable**:
|
|
||||||
- Issue: Support for `CLAUDE_CONFIG_DIR` environment variable is implemented (line 18 in `gsd-check-update.js`, line 73 in `gsd-statusline.js`) but not documented
|
|
||||||
- Files: `/.claude/hooks/gsd-check-update.js` (line 18), `/.claude/hooks/gsd-statusline.js` (line 73)
|
|
||||||
- Impact: Users with custom config directories won't know this variable exists, potentially breaking hook functionality
|
|
||||||
- Recommendation: Document in copilot-instructions.md or add reference in hook comments
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Temporary File Cleanup Strategy
|
|
||||||
|
|
||||||
**Metric Files Never Cleaned**:
|
|
||||||
- Issue: `/tmp/claude-ctx-{sessionId}.json` and `/tmp/claude-ctx-{sessionId}-warned.json` are never deleted
|
|
||||||
- Files: `/.claude/hooks/gsd-statusline.js` (writes), `/.claude/hooks/gsd-context-monitor.js` (reads)
|
|
||||||
- Risk: On long-running systems, /tmp fills up with stale session files
|
|
||||||
- Recommendation: Implement cleanup logic to delete temp files older than 7 days, or hook into session cleanup to delete files on logout
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Package.json Missing Dependencies
|
|
||||||
|
|
||||||
**Minimal Configuration**:
|
|
||||||
- Issue: `.claude/package.json` only contains `{"type":"commonjs"}`, no dependencies listed
|
|
||||||
- Files: `/.claude/package.json`
|
|
||||||
- Risk: Hooks use built-in Node modules (fs, path, os, child_process), so this is correct, but appears incomplete
|
|
||||||
- Current state: This is intentional (all hooks use only Node builtins), but unclear to maintainers
|
|
||||||
- Recommendation: Add comment in package.json explaining that hooks intentionally use no external dependencies for portability
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Potential Memory Bloat in Large Outputs
|
|
||||||
|
|
||||||
**No Stream Processing in Hooks**:
|
|
||||||
- Issue: All hooks read entire stdin into memory before processing (`let input = ''; process.stdin.on('data', chunk => input += chunk)`)
|
|
||||||
- Files: All hook files
|
|
||||||
- Risk: If a tool call writes gigabytes of content (e.g., massive file read), the hook process could run out of memory
|
|
||||||
- Current mitigation: Only tool writes/edits are inspected, and these are typically small
|
|
||||||
- Recommendation: For hooks processing large content (especially prompt injection guard), implement streaming regex matching or line-by-line processing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Test Coverage Gaps
|
|
||||||
|
|
||||||
**No Unit Tests for Hooks**:
|
|
||||||
- What's not tested: None of the 5 hook files have automated tests
|
|
||||||
- Files: `/.claude/hooks/*.js` (all of them)
|
|
||||||
- Risk: Changes to hook logic have no regression protection. Timeout tweaks, path logic, or regex patterns could break without detection
|
|
||||||
- Impact: Hook failures are silent (by design), so broken hooks go unnoticed until users report issues
|
|
||||||
- Priority: High — hooks are critical path for agent context management
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Missing Error Recovery in Executor
|
|
||||||
|
|
||||||
**Deviation Rule Priority Conflicts**:
|
|
||||||
- Issue: Executor auto-applies Rules 1-3 without clear priority when multiple rules could apply
|
|
||||||
- Files: `/.github/agents/gsd-executor.agent.md` (line 150 mentions "RULE PRIORITY:")
|
|
||||||
- Risk: When a task has multiple issues (e.g., bug AND missing critical functionality), executor might fix bug first, missing that missing-function blocking task completion
|
|
||||||
- Recommendation: Clarify priority order: blocking issues (Rule 3) > missing critical functionality (Rule 2) > bugs (Rule 1)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Subprocess Error Propagation in Background Checks
|
|
||||||
|
|
||||||
**npm view Command Failure Silent**:
|
|
||||||
- Issue: `execSync('npm view get-shit-done-cc version')` wrapped in try-catch, timeout 10s (line 96 in `gsd-check-update.js`)
|
|
||||||
- Files: `/.claude/hooks/gsd-check-update.js` (lines 95-97)
|
|
||||||
- Risk: If npm is not installed, offline, or slow, fails silently. Users won't know update check failed
|
|
||||||
- Impact: Latest version is set to "unknown", update check becomes unreliable
|
|
||||||
- Recommendation: Log to file when update check fails (optional debug log), or retry with exponential backoff
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Missing Instrumentation for Decision Points
|
|
||||||
|
|
||||||
**No Audit Trail for Hook Decisions**:
|
|
||||||
- Issue: When context monitor injects critical warning or workflow guard injects advisory, no record of decision
|
|
||||||
- Files: `/.claude/hooks/gsd-context-monitor.js` (lines 125-142), `/.claude/hooks/gsd-workflow-guard.js` (lines 78-87)
|
|
||||||
- Risk: If user behavior doesn't match warning, no way to audit what warning was shown or when
|
|
||||||
- Recommendation: Write decision logs to `.planning/hooks-audit.log` with timestamp, hook name, decision, and reason (only in GSD active mode)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scalability of Hook Frequency
|
|
||||||
|
|
||||||
**Hooks Run on Every Tool Use**:
|
|
||||||
- Issue: Context monitor (PostToolUse) runs after EVERY tool call, reads file, parses JSON, checks thresholds
|
|
||||||
- Files: `/.claude/hooks/gsd-context-monitor.js`
|
|
||||||
- Risk: On high-frequency tool sessions (100+ tools), accumulated overhead could impact performance
|
|
||||||
- Current mitigation: Debounce warnings (line 108), stale metric detection (line 74)
|
|
||||||
- Recommendation: Profile hook execution time under load, consider batching metrics or reducing check frequency
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Unclear Behavior on Missing Files
|
|
||||||
|
|
||||||
**Graceful Degradation Assumptions**:
|
|
||||||
- Issue: When `.planning/STATE.md`, `.planning/config.json`, or todo files don't exist, hooks silently skip functionality
|
|
||||||
- Files: All hooks that check for optional files
|
|
||||||
- Risk: Unclear to users whether missing files are "normal" or indicate misconfiguration
|
|
||||||
- Example: Statusline shows "no task" if todos not found, but user might think todos are lost
|
|
||||||
- Recommendation: Distinguish between "feature not enabled" (e.g., no .planning/ = not a GSD project) vs. "feature broken" (e.g., config.json exists but unparseable)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Concerns audit: 2026-03-27*
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
# Coding Conventions
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Language & Runtime
|
|
||||||
|
|
||||||
**Primary Language:** JavaScript (CommonJS)
|
|
||||||
- Node.js environment (no TypeScript transpilation)
|
|
||||||
- All hooks use `.js` extension with shebang: `#!/usr/bin/env node`
|
|
||||||
|
|
||||||
**Module System:**
|
|
||||||
- CommonJS (`require()` only, no ES6 imports)
|
|
||||||
- Node.js built-in modules: `fs`, `path`, `os`, `child_process`
|
|
||||||
- No external npm dependencies in hook files
|
|
||||||
|
|
||||||
## Naming Patterns
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Kebab-case: `gsd-hook-name.js`
|
|
||||||
- Pattern: `gsd-` prefix + feature name + `.js` extension
|
|
||||||
- Examples: `gsd-prompt-guard.js`, `gsd-context-monitor.js`, `gsd-statusline.js`
|
|
||||||
|
|
||||||
**Functions:**
|
|
||||||
- camelCase: `detectConfigDir()`, `clearTimeout()`, `writeFileSync()`
|
|
||||||
- Single-letter shorthand acceptable for simple operations: `f` for file in loops
|
|
||||||
|
|
||||||
**Constants:**
|
|
||||||
- UPPER_SNAKE_CASE for immutable values: `WARNING_THRESHOLD`, `CRITICAL_THRESHOLD`, `STALE_SECONDS`, `DEBOUNCE_CALLS`
|
|
||||||
- Inline comments after constants for clarity: `const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%`
|
|
||||||
|
|
||||||
**Variables:**
|
|
||||||
- camelCase: `input`, `stdinTimeout`, `data`, `filePath`, `findings`, `configPath`
|
|
||||||
- Descriptive names: `remaining_percentage`, `callsSinceWarn`, `hookEventName`
|
|
||||||
- Single-letter loop variables: `f`, `e` (for errors), `p` (for patterns)
|
|
||||||
|
|
||||||
**Types/Objects:**
|
|
||||||
- Object keys use snake_case: `tool_name`, `tool_input`, `file_path`, `hook_version`, `installed_version`
|
|
||||||
- Array items plural: `staleHooks`, `findings`, `allowedPatterns`, `files`, `hookFiles`
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
**Formatting:**
|
|
||||||
- No linter configured (no eslintrc, prettier, or biome config files found)
|
|
||||||
- Line length: typically 80-120 characters, no strict enforced limit observed
|
|
||||||
- Indentation: 2 spaces consistently across all files
|
|
||||||
|
|
||||||
**Comments:**
|
|
||||||
- Single-line comments: `// comment`
|
|
||||||
- Multi-line blocks: Multiple `//` lines stacked (no `/* */` blocks found)
|
|
||||||
- Header comments with metadata: Version, purpose, triggers, behavior
|
|
||||||
- Pattern: `// gsd-hook-version: X.Y.Z` as first comment after shebang
|
|
||||||
- Purpose explanation follows immediately
|
|
||||||
|
|
||||||
**String Formatting:**
|
|
||||||
- Single quotes for regular strings: `'utf8'`, `'end'`
|
|
||||||
- Template literals for interpolation: `` `${variable}` ``
|
|
||||||
- Backticks for paths and code examples in comments: `` `${filePath}` ``
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
**Try-Catch Pattern:**
|
|
||||||
- All file I/O and JSON parsing wrapped in try-catch
|
|
||||||
- Silent failures preferred: catch blocks often empty or call `process.exit(0)`
|
|
||||||
- Examples from codebase:
|
|
||||||
- `try { const data = JSON.parse(input); } catch { process.exit(0); }`
|
|
||||||
- `try { ... } catch (e) { // Silent fail on parse errors }`
|
|
||||||
|
|
||||||
**Graceful Degradation:**
|
|
||||||
- Never block operations with errors
|
|
||||||
- Timeout guards prevent hanging: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);`
|
|
||||||
- File existence checks before operations: `if (fs.existsSync(configPath)) { ... }`
|
|
||||||
- Return early on missing critical data: `if (!sessionId) { process.exit(0); }`
|
|
||||||
|
|
||||||
**Error Recovery:**
|
|
||||||
- Corrupted files reset to defaults: `catch (e) { warnData = { callsSinceWarn: 0, lastLevel: null }; }`
|
|
||||||
- Optional chain operators for safe access: `data.tool_input?.file_path || ''`
|
|
||||||
|
|
||||||
## Type Coercion & Checks
|
|
||||||
|
|
||||||
**Null/Undefined Handling:**
|
|
||||||
- Null coalescing default values: `data.session_id || ''`, `data.cwd || process.cwd()`
|
|
||||||
- Explicit null checks: `if (remaining != null)` (distinguishes null from undefined)
|
|
||||||
- Undefined/null fallbacks: `|| 'unknown'`, `|| []`, `|| {}`
|
|
||||||
|
|
||||||
**Truthiness Checks:**
|
|
||||||
- Explicit boolean comparisons: `if (config.hooks?.workflow_guard) { ... }`
|
|
||||||
- Array length checks: `if (files.length > 0) { ... }`, `if (findings.length === 0) { ... }`
|
|
||||||
|
|
||||||
**Number Conversions:**
|
|
||||||
- Explicit parsing: `Math.floor(Date.now() / 1000)`, `Math.round(100 - usableRemaining)`
|
|
||||||
- Clamping with Math: `Math.max(0, value)`, `Math.min(100, value)`
|
|
||||||
|
|
||||||
## Imports & Requires
|
|
||||||
|
|
||||||
**Order (when present):**
|
|
||||||
1. Node.js built-in modules: `fs`, `path`, `os`, `child_process`
|
|
||||||
2. Destructured imports grouped: `const { spawn } = require('child_process');`
|
|
||||||
3. No external package requires in hooks
|
|
||||||
|
|
||||||
**Require Pattern:**
|
|
||||||
```javascript
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const os = require('os');
|
|
||||||
const { spawn } = require('child_process');
|
|
||||||
```
|
|
||||||
|
|
||||||
## Object & Array Patterns
|
|
||||||
|
|
||||||
**Object Creation:**
|
|
||||||
- Literal syntax: `{ hookEventName: 'PreToolUse', additionalContext: message }`
|
|
||||||
- Computed properties from template literals: `` { [key]: value } `` (not observed, uses direct literals)
|
|
||||||
- Spread operators: Not used in codebase
|
|
||||||
|
|
||||||
**Array Operations:**
|
|
||||||
- `.filter()`: `files.filter(f => f.startsWith(session))`
|
|
||||||
- `.map()`: `files.map(f => ({ name: f, mtime: fs.statSync(...) }))`
|
|
||||||
- `.find()`: `todos.find(t => t.status === 'in_progress')`
|
|
||||||
- `.some()`: `allowedPatterns.some(p => p.test(filePath))`
|
|
||||||
- Arrow functions for callbacks: `chunk => input += chunk`
|
|
||||||
|
|
||||||
**Sorting & Ordering:**
|
|
||||||
- Reverse chronological: `files.sort((a, b) => b.mtime - a.mtime)`
|
|
||||||
- File system operations ordered first, then filtering
|
|
||||||
|
|
||||||
## Output Patterns
|
|
||||||
|
|
||||||
**JSON Output:**
|
|
||||||
- All hook output is JSON: `process.stdout.write(JSON.stringify(output));`
|
|
||||||
- Output structure includes `hookSpecificOutput` wrapper:
|
|
||||||
```javascript
|
|
||||||
const output = {
|
|
||||||
hookSpecificOutput: {
|
|
||||||
hookEventName: 'PreToolUse',
|
|
||||||
additionalContext: message
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Process Exit:**
|
|
||||||
- Success: `process.exit(0)` or implicit exit after writing output
|
|
||||||
- Error: `process.exit(0)` (never `process.exit(1)` to avoid breaking workflows)
|
|
||||||
- Cleanup: `clearTimeout()` called before operations
|
|
||||||
|
|
||||||
## File I/O Patterns
|
|
||||||
|
|
||||||
**Synchronous Operations:**
|
|
||||||
- `fs.readFileSync(path, 'utf8')` for reads
|
|
||||||
- `fs.writeFileSync(path, JSON.stringify(data))` for writes
|
|
||||||
- `fs.existsSync(path)` for checks
|
|
||||||
- `fs.readdirSync(path)` for directory listing
|
|
||||||
- `fs.statSync(path).mtime` for file metadata
|
|
||||||
|
|
||||||
**Path Operations:**
|
|
||||||
- `path.join()` for path concatenation: `path.join(cwd, '.planning', 'config.json')`
|
|
||||||
- `path.basename(dir)` for extracting final component
|
|
||||||
- `path.dirname()` for parent directory
|
|
||||||
|
|
||||||
## Regular Expressions
|
|
||||||
|
|
||||||
**Pattern Definition:**
|
|
||||||
- Captured in constants array at file top: `const INJECTION_PATTERNS = [ /pattern1/i, /pattern2/i ]`
|
|
||||||
- Case-insensitive flag commonly used: `/pattern/i`
|
|
||||||
- Multiline patterns use raw strings: `/[\u200B-\u200F]/` for Unicode detection
|
|
||||||
|
|
||||||
**Pattern Testing:**
|
|
||||||
- `.test()` method for boolean check: `if (pattern.test(content))`
|
|
||||||
- `.match()` for capture groups: `const versionMatch = content.match(/\\/\\/ gsd-hook-version:\\s*(.+)/)`
|
|
||||||
- `.source` property for pattern string: `findings.push(pattern.source)`
|
|
||||||
|
|
||||||
## No Additional Frameworks
|
|
||||||
|
|
||||||
**Testing:** Not detected - no test files exist
|
|
||||||
**Build Tools:** Not detected - runs directly as Node.js scripts
|
|
||||||
**Linting/Formatting:** Not detected - no .eslintrc, .prettierrc, or similar configs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Convention analysis: 2026-03-27*
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# External Integrations
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## APIs & External Services
|
|
||||||
|
|
||||||
**Web Search:**
|
|
||||||
- Brave Search - Web search integration for research and discovery phases
|
|
||||||
- SDK/Client: Native `fetch` API (Node.js built-in)
|
|
||||||
- Auth: `BRAVE_API_KEY` environment variable or `~/.gsd/brave_api_key` file
|
|
||||||
- Endpoint: `https://api.search.brave.com/res/v1/web/search`
|
|
||||||
- Implementation: `cmdWebSearch()` in `/home/ys/family-repo/AgenticCode/.claude/get-shit-done/bin/lib/commands.cjs`
|
|
||||||
|
|
||||||
**Web Scraping (Optional):**
|
|
||||||
- Firecrawl - Web content extraction
|
|
||||||
- Auth: `FIRECRAWL_API_KEY` environment variable or `~/.gsd/firecrawl_api_key` file
|
|
||||||
- Status: Configuration detected, not actively used in codebase
|
|
||||||
|
|
||||||
**Search Alternative (Optional):**
|
|
||||||
- Exa Search - Alternative search API
|
|
||||||
- Auth: `~/.gsd/exa_api_key` file
|
|
||||||
- Status: Configuration detected, not actively used in codebase
|
|
||||||
|
|
||||||
## Data Storage
|
|
||||||
|
|
||||||
**Repositories:**
|
|
||||||
- Git - Primary version control system for all planning documents
|
|
||||||
- Client: Git CLI via `child_process.execSync()` and `execFileSync()`
|
|
||||||
- Operations: Commit, status, log, diff, tag management
|
|
||||||
- Integration points: `execGit()` in `core.cjs`, phase operations in `phase.cjs`
|
|
||||||
|
|
||||||
**File Storage:**
|
|
||||||
- Local filesystem only
|
|
||||||
- `.planning/` directory tree - All state, roadmaps, phases, requirements
|
|
||||||
- `~/.gsd/` directory - User-level API key storage
|
|
||||||
- Multi-workspace support via `.planning/config.json` sub_repos configuration
|
|
||||||
|
|
||||||
**Caching:**
|
|
||||||
- None detected
|
|
||||||
|
|
||||||
## Authentication & Identity
|
|
||||||
|
|
||||||
**Auth Provider:**
|
|
||||||
- Custom/API Key based
|
|
||||||
- No centralized identity provider
|
|
||||||
- Individual API keys per external service stored in files or environment variables
|
|
||||||
- Git authentication via system SSH/credentials configured externally
|
|
||||||
|
|
||||||
## Monitoring & Observability
|
|
||||||
|
|
||||||
**Error Tracking:**
|
|
||||||
- None detected - errors handled via console output
|
|
||||||
|
|
||||||
**Logs:**
|
|
||||||
- Console output via `output()` and `error()` functions in `/home/ys/family-repo/AgenticCode/.claude/get-shit-done/bin/lib/core.cjs`
|
|
||||||
- Support for structured JSON output via `--raw` flag
|
|
||||||
- Context monitoring hooks in `.claude/hooks/gsd-context-monitor.js`
|
|
||||||
|
|
||||||
## CI/CD & Deployment
|
|
||||||
|
|
||||||
**Hosting:**
|
|
||||||
- Self-hosted Node.js CLI
|
|
||||||
- IDE integration via Claude, Gemini, Agent, Cursor, OpenCode, Windsurf editors
|
|
||||||
- Editor-specific hooks and commands in `./.[editor]/.claude/hooks/`
|
|
||||||
|
|
||||||
**CI Pipeline:**
|
|
||||||
- Git hooks integration via Claude settings
|
|
||||||
- Post-tool-use monitoring: `gsd-context-monitor.js`
|
|
||||||
- Pre-write guards: `gsd-prompt-guard.js`
|
|
||||||
- Update checking: `gsd-check-update.js`
|
|
||||||
|
|
||||||
## Environment Configuration
|
|
||||||
|
|
||||||
**Required env vars:**
|
|
||||||
- `HOME` - User home directory (for API key storage and config)
|
|
||||||
- `BRAVE_API_KEY` - (optional) Brave Search API key for web search functionality
|
|
||||||
|
|
||||||
**Optional env vars:**
|
|
||||||
- `FIRECRAWL_API_KEY` - Web scraping capability
|
|
||||||
- `EXA_API_KEY` - Alternative search provider
|
|
||||||
|
|
||||||
**Secrets location:**
|
|
||||||
- Environment variables: `.env` or shell environment
|
|
||||||
- File-based: `~/.gsd/brave_api_key`, `~/.gsd/firecrawl_api_key`, `~/.gsd/exa_api_key`
|
|
||||||
- Git-ignored configuration: Not applicable (no local .env files in codebase)
|
|
||||||
|
|
||||||
## Webhooks & Callbacks
|
|
||||||
|
|
||||||
**Incoming:**
|
|
||||||
- None detected
|
|
||||||
|
|
||||||
**Outgoing:**
|
|
||||||
- Git commits via `execGit()` - Triggers CI/CD if configured
|
|
||||||
- Status updates via stdio - Display progress to Claude/editor interface
|
|
||||||
|
|
||||||
## Git Integration
|
|
||||||
|
|
||||||
**Operations Supported:**
|
|
||||||
- `git status` - Check working tree state
|
|
||||||
- `git add` - Stage files
|
|
||||||
- `git commit` - Create planning commits with auto-generated messages
|
|
||||||
- `git log` - Query commit history
|
|
||||||
- `git diff` - Compare changes
|
|
||||||
- `git tag` - Version marking for phases and milestones
|
|
||||||
- Commit routing for multi-repo workspaces
|
|
||||||
|
|
||||||
**Key Implementation:**
|
|
||||||
- Location: `execGit()` in `/home/ys/family-repo/AgenticCode/.claude/get-shit-done/bin/lib/core.cjs`
|
|
||||||
- Supports sub-repo commit routing via `commit-to-subrepo` command
|
|
||||||
- Automatic message generation with structured frontmatter
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Integration audit: 2026-03-27*
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Technology Stack
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Languages
|
|
||||||
|
|
||||||
**Primary:**
|
|
||||||
- JavaScript (Node.js) - Core framework and tooling
|
|
||||||
- CommonJS (`.cjs`) - Module format for all library files
|
|
||||||
- Markdown - Configuration, documentation, and state files
|
|
||||||
|
|
||||||
## Runtime
|
|
||||||
|
|
||||||
**Environment:**
|
|
||||||
- Node.js (version not explicitly specified, uses `#!/usr/bin/env node` shebang)
|
|
||||||
|
|
||||||
**Package Manager:**
|
|
||||||
- npm (inferred from package.json presence)
|
|
||||||
- Lockfile: Not detected in codebase
|
|
||||||
|
|
||||||
## Frameworks
|
|
||||||
|
|
||||||
**Core:**
|
|
||||||
- Get Shit Done (GSD) Framework v1.29.0 - Agentic code orchestration and planning
|
|
||||||
- Native Node.js APIs - `child_process`, `fs`, `path`
|
|
||||||
|
|
||||||
**Search & Web:**
|
|
||||||
- Brave Search API - Web search integration via HTTP fetch
|
|
||||||
|
|
||||||
**Build/Dev:**
|
|
||||||
- Node.js built-in utilities - No external build tool detected
|
|
||||||
|
|
||||||
## Key Dependencies
|
|
||||||
|
|
||||||
**Critical:**
|
|
||||||
- `child_process` - Subprocess execution for git commands and CLI operations
|
|
||||||
- `fs` - File system operations for planning document management
|
|
||||||
- `path` - Cross-platform path handling
|
|
||||||
- `os` - Environment detection (home directory, temp directories)
|
|
||||||
|
|
||||||
**Infrastructure:**
|
|
||||||
- `fetch` API (native Node.js) - HTTP requests for Brave Search API
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
**Environment:**
|
|
||||||
- Stored in `~/.gsd/` directory with separate API key files
|
|
||||||
- Environment variable overrides: `BRAVE_API_KEY`, `FIRECRAWL_API_KEY`, `EXA_API_KEY`
|
|
||||||
|
|
||||||
**Key Configuration Files:**
|
|
||||||
- `.planning/config.json` - Project-wide configuration and feature flags
|
|
||||||
- `settings.json` - Agent-specific settings (per `./.agent/`, `./.claude/`, `./.gemini/`, etc.)
|
|
||||||
- `.gsd-file-manifest.json` - File integrity tracking with SHA256 hashes
|
|
||||||
|
|
||||||
**Configuration Features:**
|
|
||||||
- Multi-repository workspace support
|
|
||||||
- Model profile resolution
|
|
||||||
- Milestone and workstream management
|
|
||||||
- Phase numbering strategies (decimal or custom)
|
|
||||||
|
|
||||||
## Platform Requirements
|
|
||||||
|
|
||||||
**Development:**
|
|
||||||
- Node.js with built-in ES2020+ support
|
|
||||||
- Git (for repository operations and commits)
|
|
||||||
- Bash/Shell environment for command execution
|
|
||||||
- File system with support for symlinks and deep directory structures
|
|
||||||
|
|
||||||
**Production:**
|
|
||||||
- Self-contained Node.js CLI tool
|
|
||||||
- No external runtime required beyond Node.js
|
|
||||||
- Execution via `node gsd-tools.cjs` or direct command invocation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Stack analysis: 2026-03-27*
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
# Codebase Structure
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Directory Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
/home/ys/family-repo/AgenticCode/
|
|
||||||
├── .agent/ # Anthropic Agent-specific GSD setup
|
|
||||||
│ ├── agents/ # Agent definitions (symlinks or copies)
|
|
||||||
│ ├── get-shit-done/ # Workflows, templates, references
|
|
||||||
│ ├── hooks/ # Post-tool integration hooks
|
|
||||||
│ ├── skills/ # Project skills (agent instructions)
|
|
||||||
│ └── package.json # CommonJS marker
|
|
||||||
├── .claude/ # Claude Code / Claude Web setup
|
|
||||||
│ ├── agents/ # 18 agent definition files (master)
|
|
||||||
│ ├── get-shit-done/ # Master workflows, references, commands
|
|
||||||
│ │ ├── bin/ # gsd-tools.cjs CLI (~1000 lines)
|
|
||||||
│ │ ├── commands/gsd/ # Command metadata (workstreams.md)
|
|
||||||
│ │ ├── references/ # Documentation and config specs
|
|
||||||
│ │ ├── templates/ # Scaffold templates for PLAN.md, SUMMARY.md
|
|
||||||
│ │ └── workflows/ # 56 orchestration workflows
|
|
||||||
│ ├── commands/gsd/ # Claude Code command overrides
|
|
||||||
│ ├── hooks/ # Post-tool hooks for linting/formatting
|
|
||||||
│ ├── package.json # CommonJS marker
|
|
||||||
│ └── settings.json # Per-runtime settings
|
|
||||||
├── .codex/ # Codeium Codex setup (mirrors .claude/)
|
|
||||||
├── .cursor/ # Cursor IDE setup (mirrors .claude/)
|
|
||||||
├── .gemini/ # Google Gemini setup (mirrors .claude/)
|
|
||||||
├── .opencode/ # OpenCode setup (mirrors .claude/)
|
|
||||||
├── .windsurf/ # Codeium Windsurf setup (mirrors .claude/)
|
|
||||||
├── .github/ # GitHub integration
|
|
||||||
│ ├── gsd-file-manifest.json # File integrity checksums
|
|
||||||
│ └── get-shit-done/ # GitHub action templates
|
|
||||||
├── .planning/ # Project state and planning documents
|
|
||||||
│ ├── codebase/ # Analysis documents (STACK.md, ARCHITECTURE.md, etc.)
|
|
||||||
│ ├── config.json # Project configuration (branching, model profiles)
|
|
||||||
│ ├── STATE.md # Global project state (current phase, milestone, blockers)
|
|
||||||
│ ├── ROADMAP.md # Full scope and phase definitions
|
|
||||||
│ ├── REQUIREMENTS.md # Traceability for feature requirements
|
|
||||||
│ └── phases/ # Phase execution directories
|
|
||||||
│ ├── 1/ # Phase 1
|
|
||||||
│ │ ├── CONTEXT.md # User decisions for this phase
|
|
||||||
│ │ ├── PLAN-01.md # Task breakdown (plan 1 of N)
|
|
||||||
│ │ ├── PLAN-02.md # Task breakdown (plan 2 of N)
|
|
||||||
│ │ ├── SUMMARY-01.md # Execution results (for PLAN-01)
|
|
||||||
│ │ ├── VERIFICATION.md # Success criteria and quality gates
|
|
||||||
│ │ └── WAITING.json # Optional: pause signal for checkpoints
|
|
||||||
│ ├── 1.1/ # Sub-phase 1.1
|
|
||||||
│ └── 2/ # Phase 2
|
|
||||||
├── .git/ # Git repository
|
|
||||||
├── .gitignore # Standard: excludes node_modules, .env, .planning (optional)
|
|
||||||
├── README.md # Project descriptor
|
|
||||||
└── .gsd-file-manifest.json # Root-level file integrity tracking
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory Purposes
|
|
||||||
|
|
||||||
**`.claude/` (Master Directory):**
|
|
||||||
- Purpose: Contains the canonical GSD system — agents, workflows, CLI tool, references
|
|
||||||
- Contains: Agent role definitions (18 files), 56 workflows, gsd-tools.cjs, documentation
|
|
||||||
- Key files: `agents/gsd-planner.md`, `agents/gsd-executor.md`, `agents/gsd-codebase-mapper.md`, `get-shit-done/bin/gsd-tools.cjs`
|
|
||||||
|
|
||||||
**`.claude/agents/`:**
|
|
||||||
- Purpose: Agent role definitions — instructions, tool access, execution patterns
|
|
||||||
- Contains: 18 agent files (one per agent type)
|
|
||||||
- File pattern: `gsd-{role-name}.md` (e.g., `gsd-planner.md`)
|
|
||||||
- Size: Large files (10KB-45KB), front-loaded with role definition, process steps
|
|
||||||
|
|
||||||
**`.claude/get-shit-done/workflows/`:**
|
|
||||||
- Purpose: Orchestration logic — multi-step processes coordinating agents and user input
|
|
||||||
- Contains: 56 markdown workflow files
|
|
||||||
- Naming: workflow names map to commands (e.g., `execute-phase.md` → `/gsd:execute-phase`)
|
|
||||||
- Pattern: Each workflow defines steps with conditional branching, gsd-tools.cjs calls, agent spawning
|
|
||||||
|
|
||||||
**`.claude/get-shit-done/bin/`:**
|
|
||||||
- Purpose: CLI tool providing centralized state/git operations
|
|
||||||
- Contains: `gsd-tools.cjs` (main tool, ~1000 lines), `lib/` (supporting utilities)
|
|
||||||
- Commands: 100+ subcommands for state, phases, roadmaps, validation, commits
|
|
||||||
- Called from: Every workflow and agent via `node gsd-tools.cjs <command>`
|
|
||||||
|
|
||||||
**`.claude/get-shit-done/references/`:**
|
|
||||||
- Purpose: Documentation and configuration specifications
|
|
||||||
- Contains: 15 reference files (model-profiles.md, planning-config.md, checkpoints.md, etc.)
|
|
||||||
- Usage: Read by agents to understand patterns, by implementers to understand system
|
|
||||||
|
|
||||||
**`.claude/get-shit-done/templates/`:**
|
|
||||||
- Purpose: Scaffold templates for plan and summary generation
|
|
||||||
- Contains: PLAN.md template, SUMMARY.md template, CONTEXT.md template
|
|
||||||
- Usage: Developers fill templates when creating new phases
|
|
||||||
|
|
||||||
**`.agent/`, `.gemini/`, `.codex/`, `.cursor/`, `.windsurf/`, `.opencode/`:**
|
|
||||||
- Purpose: Per-IDE setup directories (mirrors of `.claude/`)
|
|
||||||
- Contains: Symlinks or copies of agents/, workflows/, hooks/, settings.json
|
|
||||||
- Why separate: Each IDE has own credential storage, hook integration points, settings
|
|
||||||
|
|
||||||
**`.planning/`:**
|
|
||||||
- Purpose: Global project state and phase-local planning documents
|
|
||||||
- Contains: STATE.md (global), ROADMAP.md (full scope), config.json, phases/ directory tree
|
|
||||||
- Key files: `STATE.md` (current position), `ROADMAP.md` (phase definitions), `config.json` (branching strategy, model profiles)
|
|
||||||
|
|
||||||
**`.planning/phases/N/`:**
|
|
||||||
- Purpose: Per-phase execution directory — holds decisions, plans, summaries
|
|
||||||
- Contains: CONTEXT.md (user decisions), PLAN-*.md (task breakdowns), SUMMARY-*.md (results), VERIFICATION.md (success gates)
|
|
||||||
- Naming: Phase directories use number (1, 1.1, 1.2, 2) corresponding to ROADMAP.md phases
|
|
||||||
|
|
||||||
## Key File Locations
|
|
||||||
|
|
||||||
**Entry Points:**
|
|
||||||
- `.claude/get-shit-done/workflows/*.md` - User command entry points
|
|
||||||
- `.claude/agents/*.md` - Agent entry points (invoked by workflows)
|
|
||||||
- `.claude/get-shit-done/bin/gsd-tools.cjs` - CLI tool entry point
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
- `.planning/config.json` - Project-wide configuration (branching, model profiles, search behavior)
|
|
||||||
- `.claude/settings.json` - Per-runtime agent settings (model overrides, parallelization)
|
|
||||||
- `~/.gsd/defaults.json` - User-global GSD defaults (read-only in repo)
|
|
||||||
|
|
||||||
**Core Logic:**
|
|
||||||
- `.claude/agents/gsd-planner.md` - Phase planning logic (45KB, ~700 lines)
|
|
||||||
- `.claude/agents/gsd-executor.md` - Plan execution logic (21KB, ~450 lines)
|
|
||||||
- `.claude/agents/gsd-codebase-mapper.md` - Project analysis for architecture/tech docs
|
|
||||||
- `.claude/get-shit-done/bin/gsd-tools.cjs` - Centralized state/git operations
|
|
||||||
|
|
||||||
**State & Decisions:**
|
|
||||||
- `.planning/STATE.md` - Global state (current phase, milestone, workstream, blockers)
|
|
||||||
- `.planning/ROADMAP.md` - Master scope (phase numbers, descriptions, order)
|
|
||||||
- `.planning/phases/N/CONTEXT.md` - User decisions for phase N
|
|
||||||
- `.planning/phases/N/PLAN-*.md` - Task breakdowns (executable prompts)
|
|
||||||
|
|
||||||
**Execution & Verification:**
|
|
||||||
- `.planning/phases/N/SUMMARY-*.md` - Execution results, commit hashes, artifacts
|
|
||||||
- `.planning/phases/N/VERIFICATION.md` - Success criteria, quality gates, test requirements
|
|
||||||
- `.planning/REQUIREMENTS.md` - Requirement IDs (REQ-01, etc.) for traceability
|
|
||||||
|
|
||||||
**Documentation & References:**
|
|
||||||
- `.claude/get-shit-done/references/model-profiles.md` - Agent model selection strategies
|
|
||||||
- `.claude/get-shit-done/references/planning-config.md` - Configuration options spec
|
|
||||||
- `.claude/get-shit-done/references/checkpoints.md` - Pause point definition and usage
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Workflows: kebab-case (e.g., `execute-phase.md`, `plan-phase.md`)
|
|
||||||
- Agents: kebab-case with gsd- prefix (e.g., `gsd-planner.md`, `gsd-executor.md`)
|
|
||||||
- Phase documents: UPPERCASE with phase number (e.g., `PLAN-01.md`, `SUMMARY-02.md`)
|
|
||||||
- Config files: lowercase (e.g., `config.json`, `settings.json`)
|
|
||||||
- State files: UPPERCASE (e.g., `STATE.md`, `ROADMAP.md`)
|
|
||||||
|
|
||||||
**Directories:**
|
|
||||||
- Hidden IDE-specific: dot-prefixed (`.claude`, `.agent`, `.gemini`)
|
|
||||||
- Planning: `.planning` with phase structure (phases/1/, phases/1.1/, etc.)
|
|
||||||
- Tools/resources: lowercase (agents, workflows, bin, lib, references, templates)
|
|
||||||
|
|
||||||
**Phase Numbering:**
|
|
||||||
- Integer phases: 1, 2, 3 (major phases)
|
|
||||||
- Decimal sub-phases: 1.1, 1.2, 1.3 (sub-divisions of phase 1)
|
|
||||||
- Directory structure mirrors numbering: `.planning/phases/1/`, `.planning/phases/1.1/`
|
|
||||||
|
|
||||||
## Where to Add New Code
|
|
||||||
|
|
||||||
**New Agent:**
|
|
||||||
1. Create ``.claude/agents/gsd-{agent-name}.md`` with role definition, process steps, tools
|
|
||||||
2. Size: Keep under 50KB (agents are consumed whole as context)
|
|
||||||
3. Register: List in agent registry within workflow files and orchestrator
|
|
||||||
4. Mirror: Copy to `.agent/agents/`, `.gemini/agents/`, etc. after validation
|
|
||||||
|
|
||||||
**New Workflow:**
|
|
||||||
1. Create `.claude/get-shit-done/workflows/{workflow-name}.md`
|
|
||||||
2. Define: `<purpose>`, `<process>` with numbered steps, subprocess calls to gsd-tools.cjs
|
|
||||||
3. Commands: If user-facing, add metadata entry in `.claude/commands/gsd/`
|
|
||||||
4. Routing: Document how workflow invokes agents (inline vs spawned via Task())
|
|
||||||
|
|
||||||
**New Reference Documentation:**
|
|
||||||
1. Create `.claude/get-shit-done/references/{topic}.md`
|
|
||||||
2. Purpose: Explain system patterns, configuration options, validation rules
|
|
||||||
3. Read by: Agents when implementing features, developers understanding system
|
|
||||||
4. Update: Link from agent files or workflow documentation
|
|
||||||
|
|
||||||
**New Phase (during project execution):**
|
|
||||||
1. Create phase directory: `.planning/phases/{N}/` (where N is next phase number)
|
|
||||||
2. Create CONTEXT.md (template in references/)
|
|
||||||
3. Planner creates PLAN-*.md files
|
|
||||||
4. Add phase to ROADMAP.md with description
|
|
||||||
5. Update STATE.md current_phase field
|
|
||||||
|
|
||||||
**Phase Documents (during planning):**
|
|
||||||
- PLAN.md: Created by gsd-planner agent (follows template in templates/)
|
|
||||||
- VERIFICATION.md: User creates to define success criteria (template available)
|
|
||||||
- CONTEXT.md: Created by gsd-discuss-phase agent from user decisions
|
|
||||||
|
|
||||||
## Special Directories
|
|
||||||
|
|
||||||
**`.planning/codebase/`:**
|
|
||||||
- Purpose: Analysis documents for executor reference (STACK.md, ARCHITECTURE.md, CONVENTIONS.md, TESTING.md, CONCERNS.md)
|
|
||||||
- Generated: By gsd-codebase-mapper agent via `/gsd:map-codebase`
|
|
||||||
- Committed: Yes, documents tracked in git for future reference
|
|
||||||
|
|
||||||
**`.planning/phases/N/`:**
|
|
||||||
- Purpose: Execution workspace for phase N (isolated state)
|
|
||||||
- Generated: Directories created by gsd-tools.cjs during phase setup
|
|
||||||
- Committed: Yes, all planning docs committed (unless .planning/ in .gitignore)
|
|
||||||
|
|
||||||
**`.claude/get-shit-done/bin/lib/`:**
|
|
||||||
- Purpose: Supporting utilities for gsd-tools.cjs
|
|
||||||
- Contents: Shared functions for JSON parsing, git operations, file I/O
|
|
||||||
- Generated: No, part of GSD system
|
|
||||||
- Committed: Yes, part of codebase
|
|
||||||
|
|
||||||
**`node_modules/`:**
|
|
||||||
- Purpose: NPM dependencies (if any)
|
|
||||||
- Generated: Yes, by `npm install`
|
|
||||||
- Committed: No, excluded via .gitignore
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Structure analysis: 2026-03-27*
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
# Testing Patterns
|
|
||||||
|
|
||||||
**Analysis Date:** 2026-03-27
|
|
||||||
|
|
||||||
## Test Framework
|
|
||||||
|
|
||||||
**Status:** Not detected
|
|
||||||
- No test files (*.test.js, *.spec.js) found in codebase
|
|
||||||
- No test runner configured (no jest.config.js, vitest.config.js, or mocha configuration)
|
|
||||||
- No test dependencies listed in package.json files
|
|
||||||
|
|
||||||
**Code Context:**
|
|
||||||
- The codebase consists of 5 Node.js hook scripts (579 total lines across `.claude/hooks/`)
|
|
||||||
- Each hook is a standalone CLI tool that reads JSON from stdin and outputs JSON to stdout
|
|
||||||
- Hooks are event-driven (SessionStart, PreToolUse, PostToolUse, AfterTool lifecycle events)
|
|
||||||
- No application code beyond these hooks exists in the repository
|
|
||||||
|
|
||||||
## Script Type & Testing Approach
|
|
||||||
|
|
||||||
**Current Architecture:**
|
|
||||||
Each hook file (`gsd-*.js` in `/home/ys/family-repo/AgenticCode/.claude/hooks/`) follows the same structural pattern:
|
|
||||||
- Shebang: `#!/usr/bin/env node`
|
|
||||||
- Node.js built-in modules only (fs, path, os, child_process)
|
|
||||||
- JSON stdin → processing → JSON stdout
|
|
||||||
- Silent failure on errors (timeout guards, try-catch with exit(0))
|
|
||||||
|
|
||||||
**Hook Files:**
|
|
||||||
- `gsd-prompt-guard.js` (96 lines) - Detects prompt injection patterns in written files
|
|
||||||
- `gsd-statusline.js` (119 lines) - Renders context usage and task status
|
|
||||||
- `gsd-context-monitor.js` (156 lines) - Warns when context window is low
|
|
||||||
- `gsd-workflow-guard.js` (94 lines) - Advises on workflow compliance
|
|
||||||
- `gsd-check-update.js` (114 lines) - Checks for GSD updates in background
|
|
||||||
|
|
||||||
## Manual Testing Indicators
|
|
||||||
|
|
||||||
**Integration Points (evidence of real-world testing):**
|
|
||||||
- Comments referencing specific issues: `See #775`, `See #1162`, `See #870`, `See #884`
|
|
||||||
- Platform-specific workarounds: `windowsHide: true` for child_process to prevent console flash on Windows
|
|
||||||
- Timeout guards: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);` prevents hanging on pipe issues
|
|
||||||
- Git Bash compatibility: explicit handling of stdin timeout on Windows Git Bash
|
|
||||||
|
|
||||||
**Behavioral Validation Patterns:**
|
|
||||||
- Config file validation: reads `.planning/config.json`, catches parse errors gracefully
|
|
||||||
- File existence checks before operations: `if (fs.existsSync(filePath))`
|
|
||||||
- Stale data detection: `if ((now - metrics.timestamp) > STALE_SECONDS)`
|
|
||||||
- Severity escalation tracking: debounce counter resets on warning level change
|
|
||||||
|
|
||||||
## Input Validation
|
|
||||||
|
|
||||||
**JSON Parsing with Error Handling:**
|
|
||||||
All hooks follow this pattern (example from `gsd-prompt-guard.js`):
|
|
||||||
```javascript
|
|
||||||
let input = '';
|
|
||||||
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
||||||
process.stdin.setEncoding('utf8');
|
|
||||||
process.stdin.on('data', chunk => input += chunk);
|
|
||||||
process.stdin.on('end', () => {
|
|
||||||
clearTimeout(stdinTimeout);
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(input);
|
|
||||||
// Process data
|
|
||||||
} catch {
|
|
||||||
// Silent fail — never block tool execution
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Defensive Checks:**
|
|
||||||
- Field existence: `const toolName = data.tool_name;` then `if (toolName !== 'Write' && toolName !== 'Edit')`
|
|
||||||
- Optional chaining: `data.tool_input?.file_path || ''`
|
|
||||||
- Null checks: `if (!sessionId) { process.exit(0); }`
|
|
||||||
- Default values: `data.cwd || process.cwd()`, `data.model?.display_name || 'Claude'`
|
|
||||||
|
|
||||||
## File I/O Testing
|
|
||||||
|
|
||||||
**Patterns for Reliability:**
|
|
||||||
- Synchronous I/O ensures order: `fs.readFileSync()` → process → `fs.writeFileSync()`
|
|
||||||
- Directory existence checked before writing: `if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); }`
|
|
||||||
- Try-catch wraps all file operations that could fail:
|
|
||||||
```javascript
|
|
||||||
try {
|
|
||||||
const bridgeData = JSON.stringify({ ... });
|
|
||||||
fs.writeFileSync(bridgePath, bridgeData);
|
|
||||||
} catch (e) {
|
|
||||||
// Silent fail -- bridge is best-effort, don't break statusline
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Machine / Behavior Testing
|
|
||||||
|
|
||||||
**Context Monitor Debounce Logic** (`gsd-context-monitor.js`):
|
|
||||||
- Tracks warning state in file: `/tmp/claude-ctx-{session_id}-warned.json`
|
|
||||||
- Debounce counter incremented: `warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1`
|
|
||||||
- Severity escalation bypasses debounce: `if (severityEscalated) { // emit immediately }`
|
|
||||||
- State reset on warn: `warnData.callsSinceWarn = 0`
|
|
||||||
|
|
||||||
This pattern validates behavior without formal tests:
|
|
||||||
```javascript
|
|
||||||
let warnData = { callsSinceWarn: 0, lastLevel: null };
|
|
||||||
const isCritical = remaining <= CRITICAL_THRESHOLD;
|
|
||||||
const currentLevel = isCritical ? 'critical' : 'warning';
|
|
||||||
const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
|
|
||||||
if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
|
|
||||||
process.exit(0); // Suppress warning
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Regex Pattern Testing
|
|
||||||
|
|
||||||
**Prompt Injection Detection** (`gsd-prompt-guard.js`):
|
|
||||||
Patterns tested against content without formal unit tests:
|
|
||||||
```javascript
|
|
||||||
const INJECTION_PATTERNS = [
|
|
||||||
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
||||||
/override\s+(system|previous)\s+(prompt|instructions)/i,
|
|
||||||
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
||||||
/(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
|
|
||||||
/\[SYSTEM\]/i,
|
|
||||||
/<<\s*SYS\s*>>/i,
|
|
||||||
];
|
|
||||||
for (const pattern of INJECTION_PATTERNS) {
|
|
||||||
if (pattern.test(content)) {
|
|
||||||
findings.push(pattern.source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Unicode detection without regex: `if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD]/.test(content))`
|
|
||||||
|
|
||||||
## Environment & Configuration Testing
|
|
||||||
|
|
||||||
**Configuration Loading Safety** (all hooks):
|
|
||||||
- Try-catch with silent fail:
|
|
||||||
```javascript
|
|
||||||
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
||||||
if (fs.existsSync(configPath)) {
|
|
||||||
try {
|
|
||||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
||||||
if (config.hooks?.context_warnings === false) {
|
|
||||||
process.exit(0); // Feature disabled
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore config parse errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Optional chaining for nested config: `config.hooks?.workflow_guard`, `config.hooks?.context_warnings`
|
|
||||||
|
|
||||||
**Environment Variable Access:**
|
|
||||||
```javascript
|
|
||||||
const envDir = process.env.CLAUDE_CONFIG_DIR;
|
|
||||||
if (envDir && fs.existsSync(path.join(envDir, 'get-shit-done', 'VERSION'))) {
|
|
||||||
return envDir; // Custom config dir detected
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance & Resource Management
|
|
||||||
|
|
||||||
**Timeout Guards (prevent resource leaks):**
|
|
||||||
- All hooks implement stdin timeout: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);`
|
|
||||||
- Longer timeout for high-volume operations: `const stdinTimeout = setTimeout(() => process.exit(0), 10000);` in context-monitor
|
|
||||||
- Always cleared before processing: `clearTimeout(stdinTimeout);`
|
|
||||||
|
|
||||||
**Background Process Management** (`gsd-check-update.js`):
|
|
||||||
- Child process spawned with `stdio: 'ignore'`: doesn't inherit parent's stdio
|
|
||||||
- Process detached on Windows: `detached: true` (required for proper cleanup)
|
|
||||||
- Parent calls `child.unref()`: parent doesn't wait for child to exit
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const child = spawn(process.execPath, ['-e', `...inline script...`], {
|
|
||||||
stdio: 'ignore',
|
|
||||||
windowsHide: true,
|
|
||||||
detached: true
|
|
||||||
});
|
|
||||||
child.unref();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Coverage Gaps
|
|
||||||
|
|
||||||
**Areas Without Formal Testing:**
|
|
||||||
1. **Regex Pattern Accuracy** - Injection patterns untested against false positives/negatives
|
|
||||||
2. **Debounce Counter Edge Cases** - Corruption recovery, counter reset logic
|
|
||||||
3. **Platform-Specific Behavior** - Windows vs Linux path handling, process detachment
|
|
||||||
4. **Concurrent Access** - Multiple hooks writing to same state files simultaneously
|
|
||||||
5. **Large Input Handling** - No tests for multi-megabyte JSON on stdin
|
|
||||||
6. **Stale File Cleanup** - No validation that temp files are properly removed
|
|
||||||
7. **Spawn Child Behavior** - Background update check success/failure not validated
|
|
||||||
|
|
||||||
## Recommendation for Testing
|
|
||||||
|
|
||||||
**Given the architecture (CLI hooks, not library code):**
|
|
||||||
- Integration testing more valuable than unit tests
|
|
||||||
- Manual testing via real Claude Code sessions is current primary validation
|
|
||||||
- Would recommend:
|
|
||||||
1. Snapshot tests for JSON output structure
|
|
||||||
2. Mock file system for configuration loading paths
|
|
||||||
3. Integration tests simulating tool use hook flow
|
|
||||||
4. Platform-specific testing (Windows, macOS, Linux) for path handling
|
|
||||||
|
|
||||||
**Lack of tests is acceptable for:**
|
|
||||||
- Simple stdin/stdout data transformation scripts
|
|
||||||
- Hooks deployed once per installation (not on every tool call)
|
|
||||||
- Silent-fail-safe design (errors don't break workflows)
|
|
||||||
- Real-world testing via 50+ GitHub issues and fixes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Testing analysis: 2026-03-27*
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"model_profile": "balanced",
|
|
||||||
"commit_docs": true,
|
|
||||||
"parallelization": true,
|
|
||||||
"search_gitignored": false,
|
|
||||||
"brave_search": false,
|
|
||||||
"firecrawl": false,
|
|
||||||
"exa_search": false,
|
|
||||||
"git": {
|
|
||||||
"branching_strategy": "none",
|
|
||||||
"phase_branch_template": "gsd/phase-{phase}-{slug}",
|
|
||||||
"milestone_branch_template": "gsd/{milestone}-{slug}",
|
|
||||||
"quick_branch_template": null
|
|
||||||
},
|
|
||||||
"workflow": {
|
|
||||||
"research": true,
|
|
||||||
"plan_check": true,
|
|
||||||
"verifier": true,
|
|
||||||
"nyquist_validation": true,
|
|
||||||
"auto_advance": false,
|
|
||||||
"node_repair": true,
|
|
||||||
"node_repair_budget": 2,
|
|
||||||
"ui_phase": true,
|
|
||||||
"ui_safety_gate": true,
|
|
||||||
"text_mode": false,
|
|
||||||
"research_before_questions": false,
|
|
||||||
"discuss_mode": "discuss",
|
|
||||||
"skip_discuss": false,
|
|
||||||
"_auto_chain_active": false
|
|
||||||
},
|
|
||||||
"hooks": {
|
|
||||||
"context_warnings": true
|
|
||||||
},
|
|
||||||
"agent_skills": {},
|
|
||||||
"resolve_model_ids": "omit",
|
|
||||||
"mode": "interactive",
|
|
||||||
"granularity": "standard"
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
---
|
|
||||||
phase: 01-architecture-foundation
|
|
||||||
plan: 01
|
|
||||||
type: execute
|
|
||||||
wave: 1
|
|
||||||
depends_on: []
|
|
||||||
files_modified:
|
|
||||||
- ChatAgent.sln
|
|
||||||
- src/ChatAgent.Client/ChatAgent.Client.csproj
|
|
||||||
- src/ChatAgent.Client/Program.cs
|
|
||||||
- src/ChatAgent.Client/App.razor
|
|
||||||
- src/ChatAgent.Client/_Imports.razor
|
|
||||||
- src/ChatAgent.Client/wwwroot/index.html
|
|
||||||
- src/ChatAgent.Client/Properties/launchSettings.json
|
|
||||||
- src/ChatAgent.Api/ChatAgent.Api.csproj
|
|
||||||
- src/ChatAgent.Api/Program.cs
|
|
||||||
- src/ChatAgent.Api/Properties/launchSettings.json
|
|
||||||
- src/ChatAgent.Api/appsettings.json
|
|
||||||
- src/ChatAgent.Api/appsettings.Development.json
|
|
||||||
- src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
- .gitignore
|
|
||||||
autonomous: true
|
|
||||||
requirements:
|
|
||||||
- CODE-02
|
|
||||||
|
|
||||||
must_haves:
|
|
||||||
truths:
|
|
||||||
- "Running `dotnet build ChatAgent.sln` from repo root succeeds with zero errors"
|
|
||||||
- "Solution contains exactly three projects: ChatAgent.Client, ChatAgent.Api, ChatAgent.Shared"
|
|
||||||
- "Both Client and Api projects reference ChatAgent.Shared"
|
|
||||||
- "Client runs on https://localhost:5200, API runs on https://localhost:7100"
|
|
||||||
artifacts:
|
|
||||||
- path: "ChatAgent.sln"
|
|
||||||
provides: "Solution file at repo root"
|
|
||||||
contains: "ChatAgent.Client"
|
|
||||||
- path: "src/ChatAgent.Client/ChatAgent.Client.csproj"
|
|
||||||
provides: "Blazor WASM client project"
|
|
||||||
contains: "ChatAgent.Shared"
|
|
||||||
- path: "src/ChatAgent.Api/ChatAgent.Api.csproj"
|
|
||||||
provides: "ASP.NET Core Web API project"
|
|
||||||
contains: "ChatAgent.Shared"
|
|
||||||
- path: "src/ChatAgent.Shared/ChatAgent.Shared.csproj"
|
|
||||||
provides: "Shared class library"
|
|
||||||
contains: "net9.0"
|
|
||||||
key_links:
|
|
||||||
- from: "src/ChatAgent.Client/ChatAgent.Client.csproj"
|
|
||||||
to: "src/ChatAgent.Shared/ChatAgent.Shared.csproj"
|
|
||||||
via: "ProjectReference"
|
|
||||||
pattern: "ProjectReference.*ChatAgent\\.Shared"
|
|
||||||
- from: "src/ChatAgent.Api/ChatAgent.Api.csproj"
|
|
||||||
to: "src/ChatAgent.Shared/ChatAgent.Shared.csproj"
|
|
||||||
via: "ProjectReference"
|
|
||||||
pattern: "ProjectReference.*ChatAgent\\.Shared"
|
|
||||||
---
|
|
||||||
|
|
||||||
<objective>
|
|
||||||
Create the three-project .NET 9 solution scaffold with Blazor WASM client, ASP.NET Core Web API, and shared class library. Configure predictable development ports and project references.
|
|
||||||
|
|
||||||
Purpose: Establish the architectural skeleton that all subsequent phases build on. Phase 1's concept is "solution structure and project boundaries" (per CODE-02). No feature code -- just the verified scaffold.
|
|
||||||
Output: A buildable solution with three projects, shared references, and aligned port configuration.
|
|
||||||
</objective>
|
|
||||||
|
|
||||||
<execution_context>
|
|
||||||
@/home/ys/family-repo/AgenticCode/.claude/get-shit-done/workflows/execute-plan.md
|
|
||||||
@/home/ys/family-repo/AgenticCode/.claude/get-shit-done/templates/summary.md
|
|
||||||
</execution_context>
|
|
||||||
|
|
||||||
<context>
|
|
||||||
@.planning/PROJECT.md
|
|
||||||
@.planning/ROADMAP.md
|
|
||||||
@.planning/STATE.md
|
|
||||||
@.planning/phases/01-architecture-foundation/01-CONTEXT.md
|
|
||||||
@.planning/phases/01-architecture-foundation/01-RESEARCH.md
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<tasks>
|
|
||||||
|
|
||||||
<task type="auto">
|
|
||||||
<name>Task 1: Create solution and three projects with references</name>
|
|
||||||
<files>
|
|
||||||
ChatAgent.sln,
|
|
||||||
src/ChatAgent.Client/ChatAgent.Client.csproj,
|
|
||||||
src/ChatAgent.Api/ChatAgent.Api.csproj,
|
|
||||||
src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
</files>
|
|
||||||
<read_first>
|
|
||||||
.planning/phases/01-architecture-foundation/01-RESEARCH.md (Standard Stack and Installation sections),
|
|
||||||
.planning/phases/01-architecture-foundation/01-CONTEXT.md (D-01 through D-03 decisions),
|
|
||||||
CLAUDE.md
|
|
||||||
</read_first>
|
|
||||||
<action>
|
|
||||||
Run the following commands from the repo root (`/home/ys/family-repo/AgenticCode`). Per D-01, D-02, D-03:
|
|
||||||
|
|
||||||
1. Create the solution file at repo root:
|
|
||||||
```
|
|
||||||
dotnet new sln -n ChatAgent
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Create the three projects targeting net9.0:
|
|
||||||
```
|
|
||||||
dotnet new blazorwasm -n ChatAgent.Client --framework net9.0 -o src/ChatAgent.Client
|
|
||||||
dotnet new webapi -n ChatAgent.Api --framework net9.0 --use-controllers -o src/ChatAgent.Api
|
|
||||||
dotnet new classlib -n ChatAgent.Shared --framework net9.0 -o src/ChatAgent.Shared
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Add all three projects to the solution:
|
|
||||||
```
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Client/ChatAgent.Client.csproj
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Api/ChatAgent.Api.csproj
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Add Shared reference to both Client and Api:
|
|
||||||
```
|
|
||||||
dotnet add src/ChatAgent.Client/ChatAgent.Client.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
dotnet add src/ChatAgent.Api/ChatAgent.Api.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Delete the template-generated placeholder files that are not needed:
|
|
||||||
- `src/ChatAgent.Shared/Class1.cs` (will be replaced by Models/ directory in Plan 02)
|
|
||||||
- `src/ChatAgent.Api/Controllers/WeatherForecastController.cs` (template default, replaced by HealthController in Plan 02)
|
|
||||||
- `src/ChatAgent.Api/WeatherForecast.cs` (template default model)
|
|
||||||
|
|
||||||
6. Create a `.gitignore` at repo root if one does not already exist. Include standard .NET ignores:
|
|
||||||
```
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
*.user
|
|
||||||
*.suo
|
|
||||||
.vs/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
**/wwwroot/_framework/
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Run `dotnet build ChatAgent.sln` to verify the scaffold compiles.
|
|
||||||
</action>
|
|
||||||
<verify>
|
|
||||||
<automated>cd /home/ys/family-repo/AgenticCode && dotnet build ChatAgent.sln --nologo 2>&1 | tail -5</automated>
|
|
||||||
</verify>
|
|
||||||
<acceptance_criteria>
|
|
||||||
- `ChatAgent.sln` exists at `/home/ys/family-repo/AgenticCode/ChatAgent.sln`
|
|
||||||
- `ChatAgent.sln` contains the string `ChatAgent.Client`
|
|
||||||
- `ChatAgent.sln` contains the string `ChatAgent.Api`
|
|
||||||
- `ChatAgent.sln` contains the string `ChatAgent.Shared`
|
|
||||||
- `src/ChatAgent.Client/ChatAgent.Client.csproj` contains `ProjectReference` with `ChatAgent.Shared`
|
|
||||||
- `src/ChatAgent.Api/ChatAgent.Api.csproj` contains `ProjectReference` with `ChatAgent.Shared`
|
|
||||||
- All three `.csproj` files contain `net9.0` as TargetFramework
|
|
||||||
- `dotnet build ChatAgent.sln` exits with code 0
|
|
||||||
- `src/ChatAgent.Shared/Class1.cs` does NOT exist
|
|
||||||
- `src/ChatAgent.Api/Controllers/WeatherForecastController.cs` does NOT exist
|
|
||||||
- `.gitignore` exists at repo root and contains `bin/` and `obj/`
|
|
||||||
</acceptance_criteria>
|
|
||||||
<done>Three-project solution builds successfully from repo root with shared references wired in both directions.</done>
|
|
||||||
</task>
|
|
||||||
|
|
||||||
<task type="auto">
|
|
||||||
<name>Task 2: Configure predictable dev ports and clean up template defaults</name>
|
|
||||||
<files>
|
|
||||||
src/ChatAgent.Client/Properties/launchSettings.json,
|
|
||||||
src/ChatAgent.Api/Properties/launchSettings.json,
|
|
||||||
src/ChatAgent.Api/appsettings.json,
|
|
||||||
src/ChatAgent.Api/appsettings.Development.json,
|
|
||||||
src/ChatAgent.Client/wwwroot/appsettings.json
|
|
||||||
</files>
|
|
||||||
<read_first>
|
|
||||||
src/ChatAgent.Client/Properties/launchSettings.json,
|
|
||||||
src/ChatAgent.Api/Properties/launchSettings.json,
|
|
||||||
src/ChatAgent.Api/appsettings.json,
|
|
||||||
.planning/phases/01-architecture-foundation/01-RESEARCH.md (Pitfall 1: port alignment, Pattern 4: base URL config)
|
|
||||||
</read_first>
|
|
||||||
<action>
|
|
||||||
Per the research (Pitfall 1), template-generated ports are random and cause CORS mismatches. Set predictable ports:
|
|
||||||
|
|
||||||
1. Edit `src/ChatAgent.Client/Properties/launchSettings.json`:
|
|
||||||
- Set the HTTPS URL to `https://localhost:5200`
|
|
||||||
- Set the HTTP URL to `http://localhost:5100`
|
|
||||||
- Apply to both the profile used by `dotnet run` and any IIS Express profile
|
|
||||||
- Keep the `"inspectUri"` setting if present (needed for Blazor WASM debugging)
|
|
||||||
|
|
||||||
2. Edit `src/ChatAgent.Api/Properties/launchSettings.json`:
|
|
||||||
- Set the HTTPS URL to `https://localhost:7100`
|
|
||||||
- Set the HTTP URL to `http://localhost:7000`
|
|
||||||
- Remove the `"launchBrowser": true` setting if present (API has no browser UI)
|
|
||||||
|
|
||||||
3. Create `src/ChatAgent.Client/wwwroot/appsettings.json` with the API base URL:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ApiBaseUrl": "https://localhost:7100"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
This file is PUBLIC (served to the browser). Never put secrets here.
|
|
||||||
|
|
||||||
4. Verify `src/ChatAgent.Api/appsettings.json` exists (template-generated). Remove any Swagger/OpenAPI configuration if present (not needed for this project). Ensure it has a clean structure:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Ensure `src/ChatAgent.Api/appsettings.Development.json` exists with dev logging:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Run `dotnet build ChatAgent.sln` again to confirm nothing broke.
|
|
||||||
</action>
|
|
||||||
<verify>
|
|
||||||
<automated>cd /home/ys/family-repo/AgenticCode && grep -c "5200" src/ChatAgent.Client/Properties/launchSettings.json && grep -c "7100" src/ChatAgent.Api/Properties/launchSettings.json && grep -c "ApiBaseUrl" src/ChatAgent.Client/wwwroot/appsettings.json && dotnet build ChatAgent.sln --nologo 2>&1 | tail -3</automated>
|
|
||||||
</verify>
|
|
||||||
<acceptance_criteria>
|
|
||||||
- `src/ChatAgent.Client/Properties/launchSettings.json` contains `5200`
|
|
||||||
- `src/ChatAgent.Api/Properties/launchSettings.json` contains `7100`
|
|
||||||
- `src/ChatAgent.Client/wwwroot/appsettings.json` contains `"ApiBaseUrl": "https://localhost:7100"`
|
|
||||||
- `src/ChatAgent.Api/appsettings.json` does NOT contain `swagger` or `Swagger` (cleaned up)
|
|
||||||
- `dotnet build ChatAgent.sln` exits with code 0
|
|
||||||
</acceptance_criteria>
|
|
||||||
<done>Both projects configured with predictable ports (Client: 5200, API: 7100), client has API base URL in public config, build still passes.</done>
|
|
||||||
</task>
|
|
||||||
|
|
||||||
</tasks>
|
|
||||||
|
|
||||||
<verification>
|
|
||||||
From repo root:
|
|
||||||
1. `dotnet build ChatAgent.sln` completes with 0 errors
|
|
||||||
2. Solution has exactly 3 projects (grep for `.csproj` in sln file)
|
|
||||||
3. Both Client and Api reference Shared (grep for ProjectReference in both .csproj files)
|
|
||||||
4. Port 5200 appears in Client launchSettings, port 7100 in API launchSettings
|
|
||||||
5. Client wwwroot/appsettings.json has ApiBaseUrl pointing to API port
|
|
||||||
</verification>
|
|
||||||
|
|
||||||
<success_criteria>
|
|
||||||
- Three-project solution builds from `dotnet build ChatAgent.sln` with zero errors
|
|
||||||
- Projects are in `src/` subdirectories per D-02
|
|
||||||
- Solution file is at repo root per D-03
|
|
||||||
- Shared library is referenced by both Client and Api
|
|
||||||
- Ports are predictable and aligned (Client: 5200, API: 7100)
|
|
||||||
- Template boilerplate (WeatherForecast) is removed
|
|
||||||
</success_criteria>
|
|
||||||
|
|
||||||
<output>
|
|
||||||
After completion, create `.planning/phases/01-architecture-foundation/01-01-SUMMARY.md`
|
|
||||||
</output>
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
---
|
|
||||||
phase: 01-architecture-foundation
|
|
||||||
plan: 01
|
|
||||||
subsystem: infra
|
|
||||||
tags: [dotnet, blazor-wasm, webapi, solution-scaffold, net9]
|
|
||||||
|
|
||||||
# Dependency graph
|
|
||||||
requires: []
|
|
||||||
provides:
|
|
||||||
- Three-project .NET 9 solution (Client, Api, Shared)
|
|
||||||
- Predictable dev ports (Client: 5200, API: 7100)
|
|
||||||
- Shared project reference wiring
|
|
||||||
- API base URL configuration in client
|
|
||||||
affects: [01-02, 02, 03, 04, 05]
|
|
||||||
|
|
||||||
# Tech tracking
|
|
||||||
tech-stack:
|
|
||||||
added: [".NET 9 SDK 9.0.312", "Blazor WebAssembly Standalone", "ASP.NET Core Web API", "Class Library"]
|
|
||||||
patterns: ["Three-project solution with shared library", "Predictable port assignment", "global.json SDK pinning"]
|
|
||||||
|
|
||||||
key-files:
|
|
||||||
created:
|
|
||||||
- ChatAgent.sln
|
|
||||||
- global.json
|
|
||||||
- src/ChatAgent.Client/ChatAgent.Client.csproj
|
|
||||||
- src/ChatAgent.Api/ChatAgent.Api.csproj
|
|
||||||
- src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
- src/ChatAgent.Client/wwwroot/appsettings.json
|
|
||||||
modified:
|
|
||||||
- .gitignore
|
|
||||||
- src/ChatAgent.Client/Properties/launchSettings.json
|
|
||||||
- src/ChatAgent.Api/Properties/launchSettings.json
|
|
||||||
|
|
||||||
key-decisions:
|
|
||||||
- "Pinned .NET 9 via global.json since .NET 10 is installed as default"
|
|
||||||
- "Kept inspectUri in Client launchSettings for Blazor WASM debugging support"
|
|
||||||
|
|
||||||
patterns-established:
|
|
||||||
- "SDK pinning: global.json at repo root locks .NET version"
|
|
||||||
- "Port convention: Client HTTPS=5200, API HTTPS=7100"
|
|
||||||
- "Client config: wwwroot/appsettings.json for public settings (ApiBaseUrl)"
|
|
||||||
|
|
||||||
requirements-completed: [CODE-02]
|
|
||||||
|
|
||||||
# Metrics
|
|
||||||
duration: 2min
|
|
||||||
completed: 2026-03-27
|
|
||||||
---
|
|
||||||
|
|
||||||
# Phase 01 Plan 01: Solution Scaffold Summary
|
|
||||||
|
|
||||||
**Three-project .NET 9 solution with Blazor WASM client, ASP.NET Core Web API, and shared class library at predictable dev ports**
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
- **Duration:** 2 min
|
|
||||||
- **Started:** 2026-03-27T22:50:21Z
|
|
||||||
- **Completed:** 2026-03-27T22:52:42Z
|
|
||||||
- **Tasks:** 2
|
|
||||||
- **Files modified:** 26
|
|
||||||
|
|
||||||
## Accomplishments
|
|
||||||
- Created ChatAgent.sln with three projects targeting net9.0
|
|
||||||
- Wired ChatAgent.Shared as ProjectReference in both Client and Api
|
|
||||||
- Configured predictable ports (Client: 5200, API: 7100) and API base URL
|
|
||||||
- Removed template boilerplate (WeatherForecast, Class1)
|
|
||||||
- Pinned .NET 9 SDK via global.json
|
|
||||||
|
|
||||||
## Task Commits
|
|
||||||
|
|
||||||
Each task was committed atomically:
|
|
||||||
|
|
||||||
1. **Task 1: Create solution and three projects with references** - `eeaa9de` (feat)
|
|
||||||
2. **Task 2: Configure predictable dev ports and clean up template defaults** - `c6f1225` (feat)
|
|
||||||
|
|
||||||
## Files Created/Modified
|
|
||||||
- `ChatAgent.sln` - Solution file at repo root with 3 projects
|
|
||||||
- `global.json` - Pins .NET SDK to 9.0.312
|
|
||||||
- `src/ChatAgent.Client/ChatAgent.Client.csproj` - Blazor WASM client with Shared reference
|
|
||||||
- `src/ChatAgent.Api/ChatAgent.Api.csproj` - Web API with Shared reference
|
|
||||||
- `src/ChatAgent.Shared/ChatAgent.Shared.csproj` - Shared class library
|
|
||||||
- `src/ChatAgent.Client/Properties/launchSettings.json` - Client ports 5200/5100
|
|
||||||
- `src/ChatAgent.Api/Properties/launchSettings.json` - API ports 7100/7000
|
|
||||||
- `src/ChatAgent.Client/wwwroot/appsettings.json` - ApiBaseUrl pointing to API
|
|
||||||
- `.gitignore` - Added .NET-specific ignore patterns
|
|
||||||
|
|
||||||
## Decisions Made
|
|
||||||
- Pinned .NET 9 via global.json since machine default is .NET 10 (10.0.201) -- required to match CLAUDE.md constraint
|
|
||||||
- Kept inspectUri in Client launchSettings profiles for Blazor WASM debugging support
|
|
||||||
|
|
||||||
## Deviations from Plan
|
|
||||||
|
|
||||||
### Auto-fixed Issues
|
|
||||||
|
|
||||||
**1. [Rule 3 - Blocking] Added global.json to pin .NET 9 SDK**
|
|
||||||
- **Found during:** Task 1 (before project creation)
|
|
||||||
- **Issue:** Machine has .NET 10 as default SDK; `dotnet new` would target net10.0 without intervention
|
|
||||||
- **Fix:** Created global.json with version 9.0.312 and rollForward: latestPatch
|
|
||||||
- **Files modified:** global.json
|
|
||||||
- **Verification:** `dotnet --version` returns 9.0.312
|
|
||||||
- **Committed in:** eeaa9de (Task 1 commit)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Total deviations:** 1 auto-fixed (1 blocking)
|
|
||||||
**Impact on plan:** Essential for correctness -- without SDK pinning, all projects would target wrong framework version.
|
|
||||||
|
|
||||||
## Issues Encountered
|
|
||||||
None
|
|
||||||
|
|
||||||
## User Setup Required
|
|
||||||
None - no external service configuration required.
|
|
||||||
|
|
||||||
## Next Phase Readiness
|
|
||||||
- Solution scaffold is complete and builds with zero errors
|
|
||||||
- Ready for Plan 02: CORS configuration, health endpoint, HttpClient setup, and tutorial comments
|
|
||||||
- All three projects have proper references and port alignment
|
|
||||||
|
|
||||||
---
|
|
||||||
*Phase: 01-architecture-foundation*
|
|
||||||
*Completed: 2026-03-27*
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
---
|
|
||||||
phase: 01-architecture-foundation
|
|
||||||
plan: 02
|
|
||||||
type: execute
|
|
||||||
wave: 2
|
|
||||||
depends_on:
|
|
||||||
- 01-01
|
|
||||||
files_modified:
|
|
||||||
- src/ChatAgent.Shared/Models/HealthResponse.cs
|
|
||||||
- src/ChatAgent.Api/Program.cs
|
|
||||||
- src/ChatAgent.Api/Controllers/HealthController.cs
|
|
||||||
- src/ChatAgent.Client/Program.cs
|
|
||||||
- src/ChatAgent.Client/Services/ChatApiClient.cs
|
|
||||||
- src/ChatAgent.Client/Pages/Home.razor
|
|
||||||
- src/ChatAgent.Client/Layout/MainLayout.razor
|
|
||||||
- src/ChatAgent.Client/_Imports.razor
|
|
||||||
- src/ChatAgent.Client/wwwroot/css/app.css
|
|
||||||
autonomous: false
|
|
||||||
requirements:
|
|
||||||
- CODE-01
|
|
||||||
- CODE-02
|
|
||||||
|
|
||||||
must_haves:
|
|
||||||
truths:
|
|
||||||
- "A GET request from the WASM client to /api/health on the API server returns a HealthResponse with status 'healthy'"
|
|
||||||
- "CORS does not block the cross-origin request from localhost:5200 to localhost:7100"
|
|
||||||
- "The Home page displays the health check result (status and timestamp) fetched from the API"
|
|
||||||
- "Every .cs and .razor file contains inline comments explaining the Blazor/ASP.NET Core concept it demonstrates"
|
|
||||||
- "dotnet publish on the WASM project completes with no IL trimmer warnings"
|
|
||||||
artifacts:
|
|
||||||
- path: "src/ChatAgent.Shared/Models/HealthResponse.cs"
|
|
||||||
provides: "Shared DTO for health check"
|
|
||||||
contains: "public class HealthResponse"
|
|
||||||
- path: "src/ChatAgent.Api/Controllers/HealthController.cs"
|
|
||||||
provides: "Health check API endpoint"
|
|
||||||
contains: "[HttpGet]"
|
|
||||||
- path: "src/ChatAgent.Api/Program.cs"
|
|
||||||
provides: "API entry point with CORS and controller mapping"
|
|
||||||
contains: "AllowBlazorClient"
|
|
||||||
- path: "src/ChatAgent.Client/Services/ChatApiClient.cs"
|
|
||||||
provides: "Typed HttpClient wrapper"
|
|
||||||
contains: "GetHealthAsync"
|
|
||||||
- path: "src/ChatAgent.Client/Program.cs"
|
|
||||||
provides: "WASM entry point with DI registration"
|
|
||||||
contains: "AddHttpClient<ChatApiClient>"
|
|
||||||
- path: "src/ChatAgent.Client/Pages/Home.razor"
|
|
||||||
provides: "Landing page showing health check result"
|
|
||||||
contains: "@inject"
|
|
||||||
key_links:
|
|
||||||
- from: "src/ChatAgent.Client/Pages/Home.razor"
|
|
||||||
to: "src/ChatAgent.Client/Services/ChatApiClient.cs"
|
|
||||||
via: "@inject ChatApiClient"
|
|
||||||
pattern: "@inject.*ChatApiClient"
|
|
||||||
- from: "src/ChatAgent.Client/Services/ChatApiClient.cs"
|
|
||||||
to: "src/ChatAgent.Api/Controllers/HealthController.cs"
|
|
||||||
via: "HTTP GET to api/health"
|
|
||||||
pattern: "api/health"
|
|
||||||
- from: "src/ChatAgent.Api/Controllers/HealthController.cs"
|
|
||||||
to: "src/ChatAgent.Shared/Models/HealthResponse.cs"
|
|
||||||
via: "returns HealthResponse object"
|
|
||||||
pattern: "HealthResponse"
|
|
||||||
- from: "src/ChatAgent.Api/Program.cs"
|
|
||||||
to: "CORS middleware"
|
|
||||||
via: "UseCors before MapControllers"
|
|
||||||
pattern: "UseCors.*AllowBlazorClient"
|
|
||||||
---
|
|
||||||
|
|
||||||
<objective>
|
|
||||||
Implement the health check round-trip: shared DTO, API controller with CORS, typed HttpClient in the WASM client, and a home page that displays the result. Every file gets full tutorial-style inline comments per CODE-01.
|
|
||||||
|
|
||||||
Purpose: Prove the WASM-to-API communication path works end-to-end with CORS, establishing the pattern all future API calls will follow. This is Phase 1's single concept: "solution structure and project boundaries" (CODE-02).
|
|
||||||
Output: A running app where the WASM client calls the API health endpoint and displays the response.
|
|
||||||
</objective>
|
|
||||||
|
|
||||||
<execution_context>
|
|
||||||
@/home/ys/family-repo/AgenticCode/.claude/get-shit-done/workflows/execute-plan.md
|
|
||||||
@/home/ys/family-repo/AgenticCode/.claude/get-shit-done/templates/summary.md
|
|
||||||
</execution_context>
|
|
||||||
|
|
||||||
<context>
|
|
||||||
@.planning/PROJECT.md
|
|
||||||
@.planning/ROADMAP.md
|
|
||||||
@.planning/STATE.md
|
|
||||||
@.planning/phases/01-architecture-foundation/01-CONTEXT.md
|
|
||||||
@.planning/phases/01-architecture-foundation/01-RESEARCH.md
|
|
||||||
@.planning/phases/01-architecture-foundation/01-01-SUMMARY.md
|
|
||||||
|
|
||||||
<interfaces>
|
|
||||||
<!-- From Plan 01 outputs -- executor needs these project paths and port config -->
|
|
||||||
|
|
||||||
Projects created by Plan 01:
|
|
||||||
- src/ChatAgent.Client/ (Blazor WASM, port 5200)
|
|
||||||
- src/ChatAgent.Api/ (ASP.NET Core Web API, port 7100)
|
|
||||||
- src/ChatAgent.Shared/ (class library, referenced by both)
|
|
||||||
|
|
||||||
Client config (src/ChatAgent.Client/wwwroot/appsettings.json):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ApiBaseUrl": "https://localhost:7100"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
All projects target net9.0. Solution file at repo root: ChatAgent.sln
|
|
||||||
</interfaces>
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<tasks>
|
|
||||||
|
|
||||||
<task type="auto">
|
|
||||||
<name>Task 1: Implement shared DTO, API health endpoint with CORS, and client services</name>
|
|
||||||
<files>
|
|
||||||
src/ChatAgent.Shared/Models/HealthResponse.cs,
|
|
||||||
src/ChatAgent.Api/Program.cs,
|
|
||||||
src/ChatAgent.Api/Controllers/HealthController.cs,
|
|
||||||
src/ChatAgent.Client/Program.cs,
|
|
||||||
src/ChatAgent.Client/Services/ChatApiClient.cs,
|
|
||||||
src/ChatAgent.Client/Pages/Home.razor,
|
|
||||||
src/ChatAgent.Client/Layout/MainLayout.razor,
|
|
||||||
src/ChatAgent.Client/_Imports.razor,
|
|
||||||
src/ChatAgent.Client/wwwroot/css/app.css
|
|
||||||
</files>
|
|
||||||
<read_first>
|
|
||||||
src/ChatAgent.Shared/ChatAgent.Shared.csproj,
|
|
||||||
src/ChatAgent.Api/Program.cs,
|
|
||||||
src/ChatAgent.Api/ChatAgent.Api.csproj,
|
|
||||||
src/ChatAgent.Client/Program.cs,
|
|
||||||
src/ChatAgent.Client/ChatAgent.Client.csproj,
|
|
||||||
src/ChatAgent.Client/_Imports.razor,
|
|
||||||
src/ChatAgent.Client/Layout/MainLayout.razor,
|
|
||||||
src/ChatAgent.Client/Pages/Home.razor,
|
|
||||||
src/ChatAgent.Client/wwwroot/css/app.css,
|
|
||||||
.planning/phases/01-architecture-foundation/01-RESEARCH.md,
|
|
||||||
.planning/phases/01-architecture-foundation/01-CONTEXT.md
|
|
||||||
</read_first>
|
|
||||||
<action>
|
|
||||||
Create/modify the following files. Per D-07/D-08/D-09 (CODE-01), EVERY file must have tutorial-style inline comments explaining WHAT each Blazor/ASP.NET Core concept is and WHY it is used. Comments go inline as XML doc comments and `//` comments right next to the code.
|
|
||||||
|
|
||||||
**A. Shared DTO** -- Create `src/ChatAgent.Shared/Models/HealthResponse.cs`:
|
|
||||||
|
|
||||||
Create the `Models/` directory under `src/ChatAgent.Shared/`. Write the HealthResponse class in namespace `ChatAgent.Shared.Models` with two properties:
|
|
||||||
- `public string Status { get; set; } = string.Empty;` -- server health status (e.g., "healthy")
|
|
||||||
- `public DateTime Timestamp { get; set; }` -- server timestamp proving live response
|
|
||||||
|
|
||||||
Include XML doc comments on the class explaining it is a shared DTO (Data Transfer Object) that lives in the Shared project so both Client and Api use the same type without duplication. When the API returns this object, the Client deserializes it into the same class.
|
|
||||||
|
|
||||||
**B. API Program.cs** -- Rewrite `src/ChatAgent.Api/Program.cs`:
|
|
||||||
|
|
||||||
The file must contain, in order:
|
|
||||||
1. Top comment block explaining this is the ASP.NET Core Web API entry point, that it will eventually proxy OpenAI calls and manage JSON storage, but in Phase 1 only serves a health check
|
|
||||||
2. `builder.Services.AddControllers();` with comment: "We use Controllers (not Minimal API) for explicit structure (D-05)"
|
|
||||||
3. CORS configuration using `builder.Services.AddCors()` with a named policy `"AllowBlazorClient"` that allows origin `"https://localhost:5200"`, `AllowAnyHeader()`, `AllowAnyMethod()`. Include comments explaining: CORS is required because the WASM client runs on a different origin (different port), browsers block cross-origin requests by default for security
|
|
||||||
4. `var app = builder.Build();`
|
|
||||||
5. Middleware in correct order with comment "Middleware order matters in ASP.NET Core -- CORS must be applied before routing and authorization":
|
|
||||||
- `app.UseCors("AllowBlazorClient");`
|
|
||||||
- `app.UseAuthorization();`
|
|
||||||
- `app.MapControllers();` with comment explaining it discovers all [ApiController] classes
|
|
||||||
6. `app.Run();`
|
|
||||||
|
|
||||||
Do NOT include Swagger/OpenAPI middleware. Do NOT include `app.UseHttpsRedirection()` (it causes issues with local dev).
|
|
||||||
|
|
||||||
**C. Health Controller** -- Create `src/ChatAgent.Api/Controllers/HealthController.cs`:
|
|
||||||
|
|
||||||
Create the `Controllers/` directory if it does not exist (template may have one already). Write:
|
|
||||||
- Namespace: `ChatAgent.Api.Controllers`
|
|
||||||
- Class: `HealthController : ControllerBase`
|
|
||||||
- Attributes: `[ApiController]` and `[Route("api/[controller]")]`
|
|
||||||
- Method: `[HttpGet] public IActionResult Get()` returning `Ok(new HealthResponse { Status = "healthy", Timestamp = DateTime.UtcNow })`
|
|
||||||
- Using: `ChatAgent.Shared.Models`
|
|
||||||
- Comments explaining: [ApiController] enables automatic model validation, [Route] maps the class to a URL path, [HttpGet] maps this method to GET requests. This endpoint proves CORS works between WASM and API.
|
|
||||||
|
|
||||||
**D. Client Program.cs** -- Rewrite `src/ChatAgent.Client/Program.cs`:
|
|
||||||
|
|
||||||
The file must contain:
|
|
||||||
1. Top comment block explaining this is the Blazor WASM application entry point, that WebAssemblyHostBuilder configures root components, services (DI), and configuration
|
|
||||||
2. Using statements: `Microsoft.AspNetCore.Components.Web`, `Microsoft.AspNetCore.Components.WebAssembly.Hosting`, `ChatAgent.Client`, `ChatAgent.Client.Services`
|
|
||||||
3. `var builder = WebAssemblyHostBuilder.CreateDefault(args);`
|
|
||||||
4. `builder.RootComponents.Add<App>("#app");` with comment explaining it renders the App component inside the `<div id="app">` element in index.html
|
|
||||||
5. `builder.RootComponents.Add<HeadOutlet>("head::after");` with comment explaining HeadOutlet manages head elements from Razor components
|
|
||||||
6. Read API base URL from config: `var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? "https://localhost:7100";` with comment explaining wwwroot/appsettings.json is PUBLIC, never put secrets there
|
|
||||||
7. Register typed HttpClient: `builder.Services.AddHttpClient<ChatApiClient>(client => { client.BaseAddress = new Uri(apiBaseUrl); });` with comments explaining AddHttpClient uses IHttpClientFactory internally for proper socket management, per-client configuration, and constructor DI
|
|
||||||
8. `await builder.Build().RunAsync();`
|
|
||||||
|
|
||||||
**E. Typed HttpClient** -- Create `src/ChatAgent.Client/Services/ChatApiClient.cs`:
|
|
||||||
|
|
||||||
Create the `Services/` directory under `src/ChatAgent.Client/`. Write:
|
|
||||||
- Namespace: `ChatAgent.Client.Services`
|
|
||||||
- Class: `ChatApiClient` with constructor injection of `HttpClient`
|
|
||||||
- Method: `public async Task<HealthResponse?> GetHealthAsync()` calling `_httpClient.GetFromJsonAsync<HealthResponse>("api/health")`
|
|
||||||
- Using: `ChatAgent.Shared.Models`, `System.Net.Http.Json`
|
|
||||||
- Comments explaining: In Blazor WASM, HttpClient is backed by the browser's Fetch API. We wrap it in a typed client so components don't depend on HttpClient directly (D-04). This makes testing easier and centralizes API URL management.
|
|
||||||
|
|
||||||
**F. Home Page** -- Rewrite `src/ChatAgent.Client/Pages/Home.razor`:
|
|
||||||
|
|
||||||
The file must contain:
|
|
||||||
1. `@page "/"` directive with comment explaining this maps the component to the root URL
|
|
||||||
2. `@using ChatAgent.Client.Services`
|
|
||||||
3. `@using ChatAgent.Shared.Models`
|
|
||||||
4. `@inject ChatApiClient ApiClient` with comment explaining @inject requests a service from the DI container
|
|
||||||
5. `<PageTitle>Chat Agent</PageTitle>`
|
|
||||||
6. An `<h1>Chat Agent</h1>` heading
|
|
||||||
7. A section that displays health check results:
|
|
||||||
- If `_healthResponse` is null and `_error` is null, show "Checking API connection..." text with class="loading"
|
|
||||||
- If `_healthResponse` is not null, show "API Status: {Status}" and "Server Time: {Timestamp}" in a div with class="health-status"
|
|
||||||
- If `_error` is not null, show the error message in a div with class="error-message"
|
|
||||||
8. An `@code` block with:
|
|
||||||
- `private HealthResponse? _healthResponse;`
|
|
||||||
- `private string? _error;`
|
|
||||||
- `protected override async Task OnInitializedAsync()` that calls `ApiClient.GetHealthAsync()` in a try/catch, setting `_error` on failure
|
|
||||||
- Comment explaining `OnInitializedAsync` is a Blazor lifecycle method called once when the component is first rendered, and that `StateHasChanged()` is called automatically after it completes
|
|
||||||
|
|
||||||
Use plain HTML/CSS per D-10 (no MudBlazor). Light theme per D-11.
|
|
||||||
|
|
||||||
**G. MainLayout** -- Update `src/ChatAgent.Client/Layout/MainLayout.razor`:
|
|
||||||
|
|
||||||
Simplify to a clean layout with:
|
|
||||||
- `@inherits LayoutComponentBase` with comment explaining LayoutComponentBase is the base class for layout components
|
|
||||||
- A `<main>` element wrapping `@Body` with comment explaining @Body is where the routed page content renders
|
|
||||||
- Remove any template navigation or sidebar (not needed in Phase 1)
|
|
||||||
- Basic styling: centered content with padding
|
|
||||||
|
|
||||||
**H. _Imports.razor** -- Update `src/ChatAgent.Client/_Imports.razor`:
|
|
||||||
|
|
||||||
Ensure these using directives are present (keep existing ones, add missing):
|
|
||||||
- `@using System.Net.Http`
|
|
||||||
- `@using System.Net.Http.Json`
|
|
||||||
- `@using Microsoft.AspNetCore.Components.Forms`
|
|
||||||
- `@using Microsoft.AspNetCore.Components.Routing`
|
|
||||||
- `@using Microsoft.AspNetCore.Components.Web`
|
|
||||||
- `@using Microsoft.AspNetCore.Components.Web.Virtualization`
|
|
||||||
- `@using Microsoft.AspNetCore.Components.WebAssembly.Http`
|
|
||||||
- `@using Microsoft.JSInterop`
|
|
||||||
- `@using ChatAgent.Client`
|
|
||||||
- `@using ChatAgent.Client.Layout`
|
|
||||||
- `@using ChatAgent.Client.Services`
|
|
||||||
- `@using ChatAgent.Shared.Models`
|
|
||||||
|
|
||||||
Add a comment at the top explaining _Imports.razor provides global using directives for all .razor files in the project.
|
|
||||||
|
|
||||||
**I. App CSS** -- Update `src/ChatAgent.Client/wwwroot/css/app.css`:
|
|
||||||
|
|
||||||
Replace with minimal clean CSS for Phase 1 (D-10 plain HTML/CSS, D-11 light theme):
|
|
||||||
```css
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
After all files are created, run:
|
|
||||||
1. `dotnet build ChatAgent.sln` -- must succeed
|
|
||||||
2. `dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo 2>&1 | grep -i "warning IL"` -- must produce no output (no IL trimmer warnings)
|
|
||||||
</action>
|
|
||||||
<verify>
|
|
||||||
<automated>cd /home/ys/family-repo/AgenticCode && dotnet build ChatAgent.sln --nologo 2>&1 | tail -5 && echo "---PUBLISH---" && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo 2>&1 | grep -ci "warning IL" || echo "0 IL warnings"</automated>
|
|
||||||
</verify>
|
|
||||||
<acceptance_criteria>
|
|
||||||
- `src/ChatAgent.Shared/Models/HealthResponse.cs` contains `public class HealthResponse`
|
|
||||||
- `src/ChatAgent.Shared/Models/HealthResponse.cs` contains `public string Status`
|
|
||||||
- `src/ChatAgent.Shared/Models/HealthResponse.cs` contains `public DateTime Timestamp`
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains `AddCors`
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains `AllowBlazorClient`
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains `WithOrigins("https://localhost:5200")`
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains `UseCors` BEFORE `MapControllers` (line number of UseCors < line number of MapControllers)
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains `AddControllers`
|
|
||||||
- `src/ChatAgent.Api/Program.cs` does NOT contain `swagger` or `Swagger` or `UseHttpsRedirection`
|
|
||||||
- `src/ChatAgent.Api/Controllers/HealthController.cs` contains `[ApiController]`
|
|
||||||
- `src/ChatAgent.Api/Controllers/HealthController.cs` contains `[HttpGet]`
|
|
||||||
- `src/ChatAgent.Api/Controllers/HealthController.cs` contains `HealthResponse`
|
|
||||||
- `src/ChatAgent.Client/Program.cs` contains `AddHttpClient<ChatApiClient>`
|
|
||||||
- `src/ChatAgent.Client/Program.cs` contains `ApiBaseUrl`
|
|
||||||
- `src/ChatAgent.Client/Services/ChatApiClient.cs` contains `GetHealthAsync`
|
|
||||||
- `src/ChatAgent.Client/Services/ChatApiClient.cs` contains `api/health`
|
|
||||||
- `src/ChatAgent.Client/Pages/Home.razor` contains `@inject ChatApiClient`
|
|
||||||
- `src/ChatAgent.Client/Pages/Home.razor` contains `OnInitializedAsync`
|
|
||||||
- `src/ChatAgent.Client/_Imports.razor` contains `ChatAgent.Shared.Models`
|
|
||||||
- `dotnet build ChatAgent.sln` exits with code 0
|
|
||||||
- `dotnet publish` produces 0 IL trimmer warnings
|
|
||||||
- Every `.cs` file created contains at least 3 comment lines (`//` or `///`)
|
|
||||||
- `src/ChatAgent.Api/Program.cs` contains at least 5 comment lines
|
|
||||||
- `src/ChatAgent.Client/Program.cs` contains at least 5 comment lines
|
|
||||||
</acceptance_criteria>
|
|
||||||
<done>All source files created with full tutorial comments. Solution builds. WASM publishes with no IL trim warnings. Health check round-trip code is in place.</done>
|
|
||||||
</task>
|
|
||||||
|
|
||||||
<task type="checkpoint:human-verify" gate="blocking">
|
|
||||||
<name>Task 2: Verify CORS health check works end-to-end</name>
|
|
||||||
<files>
|
|
||||||
src/ChatAgent.Client/Pages/Home.razor,
|
|
||||||
src/ChatAgent.Api/Controllers/HealthController.cs
|
|
||||||
</files>
|
|
||||||
<action>
|
|
||||||
Start both projects and verify the WASM client successfully calls the API health endpoint across origins.
|
|
||||||
|
|
||||||
1. In terminal 1: `cd /home/ys/family-repo/AgenticCode && dotnet run --project src/ChatAgent.Api`
|
|
||||||
2. In terminal 2: `cd /home/ys/family-repo/AgenticCode && dotnet run --project src/ChatAgent.Client`
|
|
||||||
3. Open browser to https://localhost:5200
|
|
||||||
4. Verify the page shows "Chat Agent" heading with "API Status: healthy" and a server timestamp
|
|
||||||
5. Check browser console (F12) for absence of CORS errors
|
|
||||||
6. Optionally test API directly: `curl -k https://localhost:7100/api/health`
|
|
||||||
</action>
|
|
||||||
<verify>
|
|
||||||
<automated>cd /home/ys/family-repo/AgenticCode && dotnet build ChatAgent.sln --nologo 2>&1 | tail -3</automated>
|
|
||||||
</verify>
|
|
||||||
<done>User confirms health check displays correctly in browser with no CORS errors.</done>
|
|
||||||
</task>
|
|
||||||
|
|
||||||
</tasks>
|
|
||||||
|
|
||||||
<verification>
|
|
||||||
Phase 1 success criteria from ROADMAP.md:
|
|
||||||
1. `dotnet run` on both projects starts without errors -- verified in checkpoint
|
|
||||||
2. WASM client reaches API server and gets response (CORS working) -- verified in checkpoint
|
|
||||||
3. Shared models library referenced by both projects -- verified by build success + ProjectReference in .csproj
|
|
||||||
4. `dotnet publish` on WASM completes with no IL trim warnings -- verified in Task 1 automated check
|
|
||||||
5. Every file contains inline comments explaining the Blazor concept -- verified by acceptance criteria comment line counts
|
|
||||||
</verification>
|
|
||||||
|
|
||||||
<success_criteria>
|
|
||||||
- Health check response visible in browser at https://localhost:5200 showing "healthy" status and timestamp
|
|
||||||
- No CORS errors in browser console
|
|
||||||
- All .cs and .razor files have tutorial-style inline comments (CODE-01)
|
|
||||||
- Phase introduces exactly one concept: solution structure and project boundaries (CODE-02)
|
|
||||||
- Build and publish both succeed cleanly
|
|
||||||
</success_criteria>
|
|
||||||
|
|
||||||
<output>
|
|
||||||
After completion, create `.planning/phases/01-architecture-foundation/01-02-SUMMARY.md`
|
|
||||||
</output>
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# Phase 1: Architecture Foundation - Context
|
|
||||||
|
|
||||||
**Gathered:** 2026-03-27
|
|
||||||
**Status:** Ready for planning
|
|
||||||
|
|
||||||
<domain>
|
|
||||||
## Phase Boundary
|
|
||||||
|
|
||||||
Two-project solution scaffold with WASM/API split and shared models. Establishes the tutorial commenting convention. The critical boundaries (no API key in WASM, no file I/O in WASM, no direct OpenAI calls from WASM) are architecturally enforced before any feature code is written.
|
|
||||||
|
|
||||||
</domain>
|
|
||||||
|
|
||||||
<decisions>
|
|
||||||
## Implementation Decisions
|
|
||||||
|
|
||||||
### Solution Structure
|
|
||||||
- **D-01:** Solution named `ChatAgent.sln` at repo root with three projects: `ChatAgent.Client` (Blazor WASM), `ChatAgent.Api` (ASP.NET Core backend), `ChatAgent.Shared` (shared models/DTOs)
|
|
||||||
- **D-02:** Projects live in `src/` subfolders: `src/ChatAgent.Client/`, `src/ChatAgent.Api/`, `src/ChatAgent.Shared/`
|
|
||||||
- **D-03:** Solution file at repo root for easy `dotnet build` from project root
|
|
||||||
|
|
||||||
### API Communication
|
|
||||||
- **D-04:** Typed HttpClient pattern — a `ChatApiClient` class in the Client project wraps all backend API calls, registered via DI
|
|
||||||
- **D-05:** Backend uses traditional MVC Controllers (not Minimal API) — more structure, familiar pattern for tutorial
|
|
||||||
- **D-06:** CORS configured on the API to allow the WASM client origin during development
|
|
||||||
|
|
||||||
### Tutorial Comments
|
|
||||||
- **D-07:** Full tutorial-style inline comments — explain everything including basic patterns, treat every file as a teaching moment
|
|
||||||
- **D-08:** Comments go inline (XML doc comments and `//` comments right next to the code), no separate companion docs
|
|
||||||
- **D-09:** Every Blazor concept introduced must have a comment explaining WHAT it is and WHY it's used
|
|
||||||
|
|
||||||
### UI Framework
|
|
||||||
- **D-10:** Start with plain HTML/CSS in Phase 1 — no MudBlazor yet. Learn raw Blazor rendering first, add component library later
|
|
||||||
- **D-11:** Light theme as default look and feel
|
|
||||||
|
|
||||||
### Claude's Discretion
|
|
||||||
- CORS configuration details (origins, headers, methods)
|
|
||||||
- Base URL configuration approach (appsettings.json vs environment variables)
|
|
||||||
- Project file (.csproj) configuration details
|
|
||||||
- Test project structure (if any placeholder tests needed)
|
|
||||||
- .NET version targeting (9 vs 10 based on current stability)
|
|
||||||
|
|
||||||
</decisions>
|
|
||||||
|
|
||||||
<canonical_refs>
|
|
||||||
## Canonical References
|
|
||||||
|
|
||||||
**Downstream agents MUST read these before planning or implementing.**
|
|
||||||
|
|
||||||
### Project context
|
|
||||||
- `.planning/PROJECT.md` — Project vision, constraints, core value (tutorial-style build)
|
|
||||||
- `.planning/REQUIREMENTS.md` — CODE-01 and CODE-02 requirements for this phase
|
|
||||||
- `.planning/ROADMAP.md` — Phase 1 success criteria and dependencies
|
|
||||||
|
|
||||||
### Research
|
|
||||||
- `.planning/research/STACK.md` — Recommended .NET 9 stack, OpenAI SDK, Markdig, version compatibility
|
|
||||||
- `.planning/research/ARCHITECTURE.md` — WASM/API split architecture, component boundaries, data flow
|
|
||||||
- `.planning/research/PITFALLS.md` — Critical pitfalls: streaming transport, API key exposure, DI lifetimes, IL trimming
|
|
||||||
|
|
||||||
</canonical_refs>
|
|
||||||
|
|
||||||
<code_context>
|
|
||||||
## Existing Code Insights
|
|
||||||
|
|
||||||
### Reusable Assets
|
|
||||||
- No existing application code — greenfield project
|
|
||||||
|
|
||||||
### Established Patterns
|
|
||||||
- No patterns yet — this phase establishes the foundational patterns all subsequent phases will follow
|
|
||||||
|
|
||||||
### Integration Points
|
|
||||||
- `.planning/` directory exists with research and project docs
|
|
||||||
- `.claude/` directory has GSD workflow tooling (do not modify)
|
|
||||||
- Git repo already initialized with planning commits
|
|
||||||
|
|
||||||
</code_context>
|
|
||||||
|
|
||||||
<specifics>
|
|
||||||
## Specific Ideas
|
|
||||||
|
|
||||||
- ChatAgent.* namespace convention for all projects
|
|
||||||
- Controllers over Minimal API for more structured, tutorial-friendly code
|
|
||||||
- Plain HTML/CSS first to understand raw Blazor before adding component libraries
|
|
||||||
- Full verbose comments — this is a learning project, not a production codebase
|
|
||||||
|
|
||||||
</specifics>
|
|
||||||
|
|
||||||
<deferred>
|
|
||||||
## Deferred Ideas
|
|
||||||
|
|
||||||
None — discussion stayed within phase scope
|
|
||||||
|
|
||||||
</deferred>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Phase: 01-architecture-foundation*
|
|
||||||
*Context gathered: 2026-03-27*
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
# Phase 1: Architecture Foundation - Discussion Log
|
|
||||||
|
|
||||||
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
|
||||||
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
|
||||||
|
|
||||||
**Date:** 2026-03-27
|
|
||||||
**Phase:** 01-Architecture Foundation
|
|
||||||
**Areas discussed:** Solution structure, API communication, Tutorial comments, UI framework
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Solution Structure
|
|
||||||
|
|
||||||
### Naming
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| ChatAgent.* | ChatAgent.Client, ChatAgent.Api, ChatAgent.Shared — clean namespace | ✓ |
|
|
||||||
| Custom name | You have a specific name in mind | |
|
|
||||||
| You decide | Claude picks a sensible naming convention | |
|
|
||||||
|
|
||||||
**User's choice:** ChatAgent.* namespace
|
|
||||||
|
|
||||||
### Location
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Repo root | Solution file at repo root, projects in src/ subfolders | ✓ |
|
|
||||||
| Nested src/ folder | Everything under src/ — keeps planning/config separate from code | |
|
|
||||||
| You decide | Claude picks based on .NET conventions | |
|
|
||||||
|
|
||||||
**User's choice:** Repo root with src/ subfolders
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Communication
|
|
||||||
|
|
||||||
### HTTP Client Pattern
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Typed HttpClient | Named/typed HttpClient via DI — ChatApiClient class wraps all API calls | ✓ |
|
|
||||||
| Raw HttpClient | Inject HttpClient directly into components — simpler but less organized | |
|
|
||||||
| You decide | Claude picks the best pattern for a tutorial project | |
|
|
||||||
|
|
||||||
**User's choice:** Typed HttpClient (ChatApiClient)
|
|
||||||
|
|
||||||
### API Style
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Minimal API | app.MapGet/MapPost — modern .NET, less boilerplate | |
|
|
||||||
| Controllers | Traditional MVC controllers — more structure, more files | ✓ |
|
|
||||||
| You decide | Claude picks based on .NET 9 best practices | |
|
|
||||||
|
|
||||||
**User's choice:** Controllers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tutorial Comments
|
|
||||||
|
|
||||||
### Comment Depth
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Explain Blazor concepts | Comment on Blazor-specific things, skip basic C# | |
|
|
||||||
| Full tutorial | Explain everything including basic patterns — treat every file as a teaching moment | ✓ |
|
|
||||||
| Minimal + README | Light inline comments, but a per-phase README explaining what was built and why | |
|
|
||||||
|
|
||||||
**User's choice:** Full tutorial — explain everything
|
|
||||||
|
|
||||||
### Location
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Inline comments | XML doc comments and // comments right next to the code | ✓ |
|
|
||||||
| Companion docs | Separate .md file per phase explaining the concepts introduced | |
|
|
||||||
| Both | Inline comments + companion docs for deeper explanation | |
|
|
||||||
|
|
||||||
**User's choice:** Inline comments only
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI Framework
|
|
||||||
|
|
||||||
### Framework Choice
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| MudBlazor now | Install MudBlazor 9.2.0 in Phase 1 — ready for later phases | |
|
|
||||||
| Plain HTML/CSS first | Start minimal, add MudBlazor later — learn raw Blazor rendering first | ✓ |
|
|
||||||
| You decide | Claude picks what makes sense for a tutorial progression | |
|
|
||||||
|
|
||||||
**User's choice:** Plain HTML/CSS first
|
|
||||||
|
|
||||||
### Look & Feel
|
|
||||||
| Option | Description | Selected |
|
|
||||||
|--------|-------------|----------|
|
|
||||||
| Dark theme | Dark background, light text — like ChatGPT/Claude | |
|
|
||||||
| Light theme | Light background, dark text — clean and bright | ✓ |
|
|
||||||
| System default | Follow OS preference | |
|
|
||||||
| You decide | Claude picks a sensible default | |
|
|
||||||
|
|
||||||
**User's choice:** Light theme
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Claude's Discretion
|
|
||||||
|
|
||||||
- CORS configuration details
|
|
||||||
- Base URL configuration approach
|
|
||||||
- .csproj configuration details
|
|
||||||
- .NET version targeting
|
|
||||||
- Test project structure
|
|
||||||
|
|
||||||
## Deferred Ideas
|
|
||||||
|
|
||||||
None — discussion stayed within phase scope
|
|
||||||
@@ -1,500 +0,0 @@
|
|||||||
# Phase 1: Architecture Foundation - Research
|
|
||||||
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Domain:** .NET 9 Blazor WASM + ASP.NET Core Web API solution scaffolding
|
|
||||||
**Confidence:** HIGH
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Phase 1 creates the three-project solution structure (`ChatAgent.Client`, `ChatAgent.Api`, `ChatAgent.Shared`) with working CORS communication between WASM client and API server, and establishes the tutorial commenting convention that applies to all subsequent phases. No feature code is written -- this phase locks in the architectural boundaries (no API key in WASM, no file I/O in WASM, no direct OpenAI calls from WASM) so that later phases build on a verified foundation.
|
|
||||||
|
|
||||||
The .NET 9 SDK is available on this machine (9.0.312), alongside .NET 10 (10.0.201). Per CLAUDE.md the project targets .NET 9. The `blazorwasm`, `webapi`, and `classlib` templates are all available. The `webapi` template supports `--use-controllers` for the MVC Controller pattern the user chose (D-05). The hosted Blazor WASM template was removed in .NET 8, so the three projects must be created separately and added to a solution manually.
|
|
||||||
|
|
||||||
**Primary recommendation:** Create the solution using `dotnet new` templates targeting `net9.0`, add a shared class library for DTOs, wire up a minimal health-check endpoint, confirm CORS works from WASM to API, and verify `dotnet publish` completes cleanly.
|
|
||||||
|
|
||||||
<user_constraints>
|
|
||||||
## User Constraints (from CONTEXT.md)
|
|
||||||
|
|
||||||
### Locked Decisions
|
|
||||||
- **D-01:** Solution named `ChatAgent.sln` at repo root with three projects: `ChatAgent.Client` (Blazor WASM), `ChatAgent.Api` (ASP.NET Core backend), `ChatAgent.Shared` (shared models/DTOs)
|
|
||||||
- **D-02:** Projects live in `src/` subfolders: `src/ChatAgent.Client/`, `src/ChatAgent.Api/`, `src/ChatAgent.Shared/`
|
|
||||||
- **D-03:** Solution file at repo root for easy `dotnet build` from project root
|
|
||||||
- **D-04:** Typed HttpClient pattern -- a `ChatApiClient` class in the Client project wraps all backend API calls, registered via DI
|
|
||||||
- **D-05:** Backend uses traditional MVC Controllers (not Minimal API) -- more structure, familiar pattern for tutorial
|
|
||||||
- **D-06:** CORS configured on the API to allow the WASM client origin during development
|
|
||||||
- **D-07:** Full tutorial-style inline comments -- explain everything including basic patterns, treat every file as a teaching moment
|
|
||||||
- **D-08:** Comments go inline (XML doc comments and `//` comments right next to the code), no separate companion docs
|
|
||||||
- **D-09:** Every Blazor concept introduced must have a comment explaining WHAT it is and WHY it's used
|
|
||||||
- **D-10:** Start with plain HTML/CSS in Phase 1 -- no MudBlazor yet. Learn raw Blazor rendering first, add component library later
|
|
||||||
- **D-11:** Light theme as default look and feel
|
|
||||||
|
|
||||||
### Claude's Discretion
|
|
||||||
- CORS configuration details (origins, headers, methods)
|
|
||||||
- Base URL configuration approach (appsettings.json vs environment variables)
|
|
||||||
- Project file (.csproj) configuration details
|
|
||||||
- Test project structure (if any placeholder tests needed)
|
|
||||||
- .NET version targeting (9 vs 10 based on current stability)
|
|
||||||
|
|
||||||
### Deferred Ideas (OUT OF SCOPE)
|
|
||||||
None -- discussion stayed within phase scope
|
|
||||||
</user_constraints>
|
|
||||||
|
|
||||||
<phase_requirements>
|
|
||||||
## Phase Requirements
|
|
||||||
|
|
||||||
| ID | Description | Research Support |
|
|
||||||
|----|-------------|------------------|
|
|
||||||
| CODE-01 | Every Blazor concept introduced has inline comments explaining what and why | Tutorial commenting convention (D-07/D-08/D-09); every `.cs`, `.razor`, and `.csproj` file gets comments explaining the Blazor/ASP.NET Core concept it demonstrates |
|
|
||||||
| CODE-02 | Each phase introduces one concept incrementally (tutorial-style progression) | Phase 1's concept is "solution structure and project boundaries" -- no feature code, no OpenAI calls, no persistence, no streaming. Just the scaffold + one health-check round-trip to prove communication works |
|
|
||||||
</phase_requirements>
|
|
||||||
|
|
||||||
## Standard Stack
|
|
||||||
|
|
||||||
### Core (Phase 1 Only)
|
|
||||||
| Library | Version | Purpose | Why Standard |
|
|
||||||
|---------|---------|---------|--------------|
|
|
||||||
| .NET 9 SDK | 9.0.312 | Runtime and tooling | Installed on machine; stable; CLAUDE.md specifies .NET 9 |
|
|
||||||
| Blazor WebAssembly Standalone | net9.0 | Client SPA | Non-negotiable per constraints |
|
|
||||||
| ASP.NET Core Web API | net9.0 | Backend API server | Required for API key isolation |
|
|
||||||
| Class Library | net9.0 | Shared models/DTOs | Referenced by both Client and Api projects |
|
|
||||||
|
|
||||||
### Not Needed in Phase 1
|
|
||||||
| Library | Why Deferred |
|
|
||||||
|---------|-------------|
|
|
||||||
| `OpenAI` NuGet | No AI calls in Phase 1 -- introduced in Phase 3 |
|
|
||||||
| `Markdig` | No markdown rendering in Phase 1 -- introduced in Phase 4 |
|
|
||||||
| `MudBlazor` | D-10 explicitly defers this; plain HTML/CSS first |
|
|
||||||
| Any test framework | No business logic to test yet; placeholder tests are optional (Claude's discretion -- recommend skipping to keep Phase 1 focused) |
|
|
||||||
|
|
||||||
**Installation:**
|
|
||||||
```bash
|
|
||||||
# From repo root
|
|
||||||
dotnet new sln -n ChatAgent
|
|
||||||
|
|
||||||
# Create projects targeting .NET 9
|
|
||||||
dotnet new blazorwasm -n ChatAgent.Client --framework net9.0 -o src/ChatAgent.Client
|
|
||||||
dotnet new webapi -n ChatAgent.Api --framework net9.0 --use-controllers -o src/ChatAgent.Api
|
|
||||||
dotnet new classlib -n ChatAgent.Shared --framework net9.0 -o src/ChatAgent.Shared
|
|
||||||
|
|
||||||
# Add projects to solution
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Client/ChatAgent.Client.csproj
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Api/ChatAgent.Api.csproj
|
|
||||||
dotnet sln ChatAgent.sln add src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
|
|
||||||
# Add Shared reference to both projects
|
|
||||||
dotnet add src/ChatAgent.Client/ChatAgent.Client.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
dotnet add src/ChatAgent.Api/ChatAgent.Api.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture Patterns
|
|
||||||
|
|
||||||
### Recommended Project Structure (Phase 1)
|
|
||||||
```
|
|
||||||
ChatAgent.sln # Solution file at repo root (D-03)
|
|
||||||
src/
|
|
||||||
ChatAgent.Client/ # Blazor WASM standalone app
|
|
||||||
ChatAgent.Client.csproj
|
|
||||||
Program.cs # DI registration, HttpClient base URL config
|
|
||||||
App.razor # Root component
|
|
||||||
_Imports.razor # Global using directives
|
|
||||||
Layout/
|
|
||||||
MainLayout.razor # App shell layout
|
|
||||||
Pages/
|
|
||||||
Home.razor # Landing page (placeholder in Phase 1)
|
|
||||||
Services/
|
|
||||||
ChatApiClient.cs # Typed HttpClient wrapper (D-04)
|
|
||||||
wwwroot/
|
|
||||||
index.html # HTML host page
|
|
||||||
css/app.css # Basic styling (plain CSS, D-10)
|
|
||||||
|
|
||||||
ChatAgent.Api/ # ASP.NET Core Web API
|
|
||||||
ChatAgent.Api.csproj
|
|
||||||
Program.cs # DI, CORS, controller mapping
|
|
||||||
Controllers/
|
|
||||||
HealthController.cs # GET /api/health -- proves CORS works
|
|
||||||
appsettings.json # API config (base URLs, future API key ref)
|
|
||||||
appsettings.Development.json # Dev-specific config
|
|
||||||
|
|
||||||
ChatAgent.Shared/ # Shared class library
|
|
||||||
ChatAgent.Shared.csproj
|
|
||||||
Models/
|
|
||||||
HealthResponse.cs # Simple DTO for health-check response
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 1: Typed HttpClient in Blazor WASM (D-04)
|
|
||||||
**What:** A `ChatApiClient` class wraps `HttpClient` for all API calls. Registered in DI as a typed client.
|
|
||||||
**When to use:** Always -- components never use `HttpClient` directly.
|
|
||||||
**Example:**
|
|
||||||
```csharp
|
|
||||||
// Source: ASP.NET Core docs + CONTEXT.md D-04
|
|
||||||
// ChatApiClient.cs -- Typed HttpClient wrapper
|
|
||||||
// In Blazor WASM, HttpClient is backed by the browser's Fetch API.
|
|
||||||
// We wrap it in a typed client so components don't depend on HttpClient directly.
|
|
||||||
// This makes testing easier and centralizes API URL management.
|
|
||||||
public class ChatApiClient
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public ChatApiClient(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calls the health endpoint to verify the API server is reachable.
|
|
||||||
/// This is the first HTTP call from WASM to the backend -- proves CORS works.
|
|
||||||
/// </summary>
|
|
||||||
public async Task<HealthResponse?> GetHealthAsync()
|
|
||||||
{
|
|
||||||
return await _httpClient.GetFromJsonAsync<HealthResponse>("api/health");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registration in Program.cs:
|
|
||||||
// AddHttpClient<T> registers a typed HttpClient with its own configuration.
|
|
||||||
// The base address points to the API server (different port during local dev).
|
|
||||||
builder.Services.AddHttpClient<ChatApiClient>(client =>
|
|
||||||
{
|
|
||||||
client.BaseAddress = new Uri("https://localhost:7100");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 2: MVC Controller on API (D-05)
|
|
||||||
**What:** Traditional `[ApiController]` with `[Route("api/[controller]")]` attributes instead of Minimal API `app.MapGet()`.
|
|
||||||
**When to use:** All API endpoints in this project.
|
|
||||||
**Example:**
|
|
||||||
```csharp
|
|
||||||
// HealthController.cs -- ASP.NET Core MVC Controller
|
|
||||||
// We use Controllers (not Minimal API) for more explicit structure (D-05).
|
|
||||||
// Each controller is a class with methods for each HTTP verb.
|
|
||||||
// [ApiController] enables automatic model validation and 400 responses.
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
public class HealthController : ControllerBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Health check endpoint. Returns server status and timestamp.
|
|
||||||
/// Used by the WASM client to verify the API is reachable and CORS is working.
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet]
|
|
||||||
public IActionResult Get()
|
|
||||||
{
|
|
||||||
return Ok(new HealthResponse
|
|
||||||
{
|
|
||||||
Status = "healthy",
|
|
||||||
Timestamp = DateTime.UtcNow
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 3: CORS Configuration (D-06)
|
|
||||||
**What:** Explicit CORS policy on the API allowing the WASM client origin.
|
|
||||||
**When to use:** Required for any cross-origin HTTP call from WASM to API during development.
|
|
||||||
**Example:**
|
|
||||||
```csharp
|
|
||||||
// Program.cs (API project) -- CORS setup
|
|
||||||
// CORS (Cross-Origin Resource Sharing) is required because the Blazor WASM client
|
|
||||||
// runs on a different port (e.g., localhost:5200) than the API (e.g., localhost:7100).
|
|
||||||
// Browsers block cross-origin requests by default for security.
|
|
||||||
// We define a named policy that allows only our client origin.
|
|
||||||
builder.Services.AddCors(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy("AllowBlazorClient", policy =>
|
|
||||||
{
|
|
||||||
policy.WithOrigins("https://localhost:5200") // WASM client origin
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowAnyMethod();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ... after builder.Build()
|
|
||||||
app.UseCors("AllowBlazorClient");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 4: Base URL Configuration (Claude's Discretion)
|
|
||||||
**Recommendation:** Use `appsettings.json` in the WASM client for the API base URL. This is standard for Blazor WASM and the file is in `wwwroot/` (public, no secrets). Environment variables are harder to configure for WASM since it runs in the browser.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// wwwroot/appsettings.json (Client project) -- PUBLIC config only
|
|
||||||
// In Blazor WASM, appsettings.json is a static file served to the browser.
|
|
||||||
// NEVER put secrets here. Only public configuration like API URLs.
|
|
||||||
{
|
|
||||||
"ApiBaseUrl": "https://localhost:7100"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Program.cs -- reading the config
|
|
||||||
var apiBaseUrl = builder.Configuration["ApiBaseUrl"]
|
|
||||||
?? throw new InvalidOperationException("ApiBaseUrl not configured");
|
|
||||||
builder.Services.AddHttpClient<ChatApiClient>(client =>
|
|
||||||
{
|
|
||||||
client.BaseAddress = new Uri(apiBaseUrl);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Anti-Patterns to Avoid
|
|
||||||
- **Any `System.IO.File` usage in the Client project:** WASM runs in a browser sandbox; file I/O does not persist. All persistence is server-side.
|
|
||||||
- **Any API key or secret in `wwwroot/appsettings.json`:** This file is publicly accessible. Secrets belong in the API project only.
|
|
||||||
- **OpenAI SDK registration in the Client `Program.cs`:** All OpenAI calls go through the backend API. The Client never talks to OpenAI directly.
|
|
||||||
- **CORS wildcard (`AllowAnyOrigin`):** Even for local dev, scope to the actual client origin. Prevents accidental exposure.
|
|
||||||
- **Logic in `.razor` code blocks:** Components call services; services own logic (D-04 enforces this via typed client pattern).
|
|
||||||
|
|
||||||
## Don't Hand-Roll
|
|
||||||
|
|
||||||
| Problem | Don't Build | Use Instead | Why |
|
|
||||||
|---------|-------------|-------------|-----|
|
|
||||||
| HTTP client management | Manual `HttpClient` instantiation in components | `IHttpClientFactory` via `AddHttpClient<T>` | Proper socket management, DI lifecycle, testability |
|
|
||||||
| JSON serialization | Manual string building | `System.Text.Json` (built-in) with `GetFromJsonAsync`/`PostAsJsonAsync` | Type-safe, trim-compatible, zero dependencies |
|
|
||||||
| CORS handling | Custom middleware or response headers | `builder.Services.AddCors()` + `app.UseCors()` | Handles preflight OPTIONS requests, header management automatically |
|
|
||||||
| Project references | Copy-paste DTOs between projects | `dotnet add reference` to Shared project | Single source of truth; compile-time verification |
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
### Pitfall 1: Wrong Port in CORS or Client Config
|
|
||||||
**What goes wrong:** WASM client gets CORS errors because the API CORS policy specifies a different port than the client actually runs on.
|
|
||||||
**Why it happens:** `dotnet new blazorwasm` and `dotnet new webapi` assign random ports in `launchSettings.json`. Developer hardcodes one set of ports but the templates use different ones.
|
|
||||||
**How to avoid:** After project creation, check `Properties/launchSettings.json` in BOTH projects. Align the CORS origin in the API with the actual client URL. Or set explicit ports in `launchSettings.json`.
|
|
||||||
**Warning signs:** Browser console shows `Access-Control-Allow-Origin` errors.
|
|
||||||
|
|
||||||
### Pitfall 2: Missing `UseCors()` Middleware Order
|
|
||||||
**What goes wrong:** CORS policy is registered in DI but `app.UseCors()` is called after `app.MapControllers()`, causing CORS headers to not be applied.
|
|
||||||
**Why it happens:** ASP.NET Core middleware order matters. CORS must run before routing/endpoints.
|
|
||||||
**How to avoid:** Call `app.UseCors("PolicyName")` before `app.MapControllers()` and before `app.UseAuthorization()`.
|
|
||||||
**Warning signs:** CORS errors despite correct policy configuration.
|
|
||||||
|
|
||||||
### Pitfall 3: IL Trimming Warnings on Publish
|
|
||||||
**What goes wrong:** `dotnet publish` for the WASM project produces IL trimmer warnings about types that may be removed.
|
|
||||||
**Why it happens:** Blazor WASM uses IL trimming in Release mode. Types used only via reflection (JSON serialization) can be trimmed away.
|
|
||||||
**How to avoid:** For Phase 1, the surface area is tiny (one DTO). Verify `dotnet publish` completes without warnings. In later phases, use `[JsonSerializable]` source generators.
|
|
||||||
**Warning signs:** `dotnet publish` output contains lines with `IL2xxx` warning codes.
|
|
||||||
|
|
||||||
### Pitfall 4: Solution File Path Issues
|
|
||||||
**What goes wrong:** `dotnet build` from repo root fails because `ChatAgent.sln` references projects with wrong relative paths.
|
|
||||||
**Why it happens:** Solution was created in a subdirectory, or projects were moved after being added.
|
|
||||||
**How to avoid:** Create the solution at repo root, add projects using their paths relative to the sln file location.
|
|
||||||
**Warning signs:** `The project file could not be found` errors.
|
|
||||||
|
|
||||||
### Pitfall 5: WASM Project Serves on HTTPS But Certificate Not Trusted
|
|
||||||
**What goes wrong:** Browser shows security warning when accessing WASM app, blocking API calls.
|
|
||||||
**Why it happens:** Dev HTTPS certificate not installed or not trusted.
|
|
||||||
**How to avoid:** Run `dotnet dev-certs https --trust` once before starting development. Alternatively, use `--no-https` flag during project creation for Phase 1 simplicity and add HTTPS later.
|
|
||||||
**Warning signs:** Browser shows "Your connection is not private" page.
|
|
||||||
|
|
||||||
## Code Examples
|
|
||||||
|
|
||||||
### Shared DTO (ChatAgent.Shared)
|
|
||||||
```csharp
|
|
||||||
// Models/HealthResponse.cs
|
|
||||||
// This is a shared model (DTO - Data Transfer Object).
|
|
||||||
// It lives in the Shared project so both the Client and Api can use it
|
|
||||||
// without duplicating the class definition. When the API returns this object,
|
|
||||||
// the Client can deserialize it into the same type.
|
|
||||||
namespace ChatAgent.Shared.Models;
|
|
||||||
|
|
||||||
public class HealthResponse
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Server health status (e.g., "healthy").
|
|
||||||
/// </summary>
|
|
||||||
public string Status { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Server timestamp -- proves we're getting a live response, not a cached one.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### WASM Program.cs (Client)
|
|
||||||
```csharp
|
|
||||||
// Program.cs -- Blazor WASM application entry point
|
|
||||||
// This is where the WebAssembly application starts.
|
|
||||||
// WebAssemblyHostBuilder configures:
|
|
||||||
// 1. Root components (what Razor components to render)
|
|
||||||
// 2. Services (dependency injection container)
|
|
||||||
// 3. Configuration (reads from wwwroot/appsettings.json)
|
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
using ChatAgent.Client;
|
|
||||||
using ChatAgent.Client.Services;
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
|
||||||
|
|
||||||
// Add<App>("#app") tells Blazor to render the App component
|
|
||||||
// inside the <div id="app"> element in wwwroot/index.html.
|
|
||||||
builder.RootComponents.Add<App>("#app");
|
|
||||||
|
|
||||||
// HeadOutlet manages <head> elements (title, meta tags) from Razor components.
|
|
||||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
|
||||||
|
|
||||||
// Read the API base URL from configuration (wwwroot/appsettings.json).
|
|
||||||
// This file is PUBLIC -- never put secrets here.
|
|
||||||
var apiBaseUrl = builder.Configuration["ApiBaseUrl"]
|
|
||||||
?? "https://localhost:7100";
|
|
||||||
|
|
||||||
// Register ChatApiClient as a typed HttpClient.
|
|
||||||
// AddHttpClient<T> uses IHttpClientFactory internally, which:
|
|
||||||
// - Manages HttpClient lifetimes properly (avoids socket exhaustion)
|
|
||||||
// - Allows per-client configuration (base address, headers)
|
|
||||||
// - Makes the service injectable via constructor DI
|
|
||||||
builder.Services.AddHttpClient<ChatApiClient>(client =>
|
|
||||||
{
|
|
||||||
client.BaseAddress = new Uri(apiBaseUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Program.cs (Server)
|
|
||||||
```csharp
|
|
||||||
// Program.cs -- ASP.NET Core Web API entry point
|
|
||||||
// This is the backend server that the Blazor WASM client talks to.
|
|
||||||
// It will eventually proxy OpenAI calls and manage JSON file storage.
|
|
||||||
// In Phase 1, it only serves a health check endpoint.
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
// AddControllers() registers MVC controller services.
|
|
||||||
// We use Controllers (not Minimal API) for explicit structure (D-05).
|
|
||||||
builder.Services.AddControllers();
|
|
||||||
|
|
||||||
// CORS (Cross-Origin Resource Sharing) configuration.
|
|
||||||
// The WASM client runs on a different origin (different port),
|
|
||||||
// so the browser blocks requests unless the server explicitly allows them.
|
|
||||||
builder.Services.AddCors(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy("AllowBlazorClient", policy =>
|
|
||||||
{
|
|
||||||
// TODO: Read from configuration instead of hardcoding
|
|
||||||
policy.WithOrigins("https://localhost:5200")
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowAnyMethod();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Middleware order matters in ASP.NET Core.
|
|
||||||
// CORS must be applied before routing and authorization.
|
|
||||||
app.UseCors("AllowBlazorClient");
|
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
// MapControllers() discovers all [ApiController] classes
|
|
||||||
// and maps their [HttpGet], [HttpPost], etc. attributes to routes.
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
```
|
|
||||||
|
|
||||||
## State of the Art
|
|
||||||
|
|
||||||
| Old Approach | Current Approach | When Changed | Impact |
|
|
||||||
|--------------|------------------|--------------|--------|
|
|
||||||
| Hosted Blazor WASM template (Client+Server+Shared in one template) | Separate projects added to a manual solution | .NET 8 (Nov 2023) | Must create three projects manually; no `--hosted` flag |
|
|
||||||
| `Newtonsoft.Json` for serialization | `System.Text.Json` built into .NET | .NET Core 3.0+ | No extra dependency; source generators for trim safety |
|
|
||||||
| `HttpClient` registered directly as Singleton/Scoped | `IHttpClientFactory` via `AddHttpClient<T>` | .NET Core 2.1+ | Proper socket management; typed clients for clean architecture |
|
|
||||||
|
|
||||||
## .NET Version Decision (Claude's Discretion)
|
|
||||||
|
|
||||||
**Recommendation: Target .NET 9 (`net9.0`)**
|
|
||||||
|
|
||||||
Rationale:
|
|
||||||
- CLAUDE.md explicitly specifies .NET 9 as the target
|
|
||||||
- .NET 9.0.312 SDK is installed on this machine
|
|
||||||
- .NET 10 SDK (10.0.201) is also available but is not yet at LTS; using it in a tutorial project risks encountering preview-stage breaking changes
|
|
||||||
- All recommended packages (OpenAI 2.9.1, Markdig 1.1.1, MudBlazor 9.2.0) are confirmed compatible with .NET 9
|
|
||||||
- The `blazorwasm` template with `--framework net9.0` is verified working
|
|
||||||
|
|
||||||
## Validation Architecture
|
|
||||||
|
|
||||||
### Test Framework
|
|
||||||
| Property | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| Framework | None yet (greenfield) |
|
|
||||||
| Config file | None -- see Wave 0 |
|
|
||||||
| Quick run command | `dotnet build ChatAgent.sln` |
|
|
||||||
| Full suite command | `dotnet build ChatAgent.sln && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo` |
|
|
||||||
|
|
||||||
### Phase Requirements -> Test Map
|
|
||||||
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
|
||||||
|--------|----------|-----------|-------------------|-------------|
|
|
||||||
| CODE-01 | Every file has inline comments explaining Blazor concepts | manual | Manual review of all `.cs` and `.razor` files | N/A |
|
|
||||||
| CODE-02 | Phase introduces one concept incrementally | manual | Manual review -- Phase 1 concept = "solution structure and project boundaries" | N/A |
|
|
||||||
| (SC-1) | `dotnet run` starts both projects without errors | smoke | `dotnet build ChatAgent.sln` (build verification) | N/A -- Wave 0 |
|
|
||||||
| (SC-2) | WASM client reaches API server (CORS working) | integration | Manual -- start both projects, verify health endpoint from client UI | N/A |
|
|
||||||
| (SC-3) | Shared models referenced by both projects | build | `dotnet build ChatAgent.sln` (compile-time check) | N/A -- Wave 0 |
|
|
||||||
| (SC-4) | `dotnet publish` completes with no IL trim warnings | smoke | `dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo 2>&1 \| grep -c "warning IL"` | N/A -- Wave 0 |
|
|
||||||
| (SC-5) | Every file contains inline comments | manual | Manual review | N/A |
|
|
||||||
|
|
||||||
### Sampling Rate
|
|
||||||
- **Per task commit:** `dotnet build ChatAgent.sln`
|
|
||||||
- **Per wave merge:** `dotnet build ChatAgent.sln && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo`
|
|
||||||
- **Phase gate:** Full build + publish clean + manual CORS verification
|
|
||||||
|
|
||||||
### Wave 0 Gaps
|
|
||||||
- No formal test project needed in Phase 1 -- the verification is build success + publish success + manual CORS check
|
|
||||||
- The `dotnet build` and `dotnet publish` commands serve as the automated validation
|
|
||||||
- CODE-01 and CODE-02 are inherently manual-review requirements
|
|
||||||
|
|
||||||
## Environment Availability
|
|
||||||
|
|
||||||
| Dependency | Required By | Available | Version | Fallback |
|
|
||||||
|------------|------------|-----------|---------|----------|
|
|
||||||
| .NET 9 SDK | All projects | Yes | 9.0.312 | -- |
|
|
||||||
| .NET 10 SDK | Not required | Yes | 10.0.201 | -- |
|
|
||||||
| `dotnet new blazorwasm` template | Client project | Yes | Included in SDK | -- |
|
|
||||||
| `dotnet new webapi` template | API project | Yes | Included in SDK | -- |
|
|
||||||
| `dotnet new classlib` template | Shared project | Yes | Included in SDK | -- |
|
|
||||||
| HTTPS dev certificate | Local HTTPS | Unknown | -- | Use `--no-https` flag or run `dotnet dev-certs https --trust` |
|
|
||||||
|
|
||||||
**Missing dependencies with no fallback:** None.
|
|
||||||
|
|
||||||
**Missing dependencies with fallback:**
|
|
||||||
- HTTPS dev certificate status is unknown -- if untrusted, can use HTTP for Phase 1 local dev or run the trust command.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
1. **Port numbers for local development**
|
|
||||||
- What we know: Templates assign ports in `launchSettings.json` which vary per creation
|
|
||||||
- What's unclear: Exact ports until projects are actually created
|
|
||||||
- Recommendation: After project creation, inspect both `launchSettings.json` files and align CORS config with actual client URL. Consider setting explicit predictable ports (e.g., Client: 5200, API: 7100).
|
|
||||||
|
|
||||||
2. **HTTPS vs HTTP for Phase 1 local dev**
|
|
||||||
- What we know: HTTPS requires a trusted dev certificate
|
|
||||||
- What's unclear: Whether the dev cert is already trusted on this machine
|
|
||||||
- Recommendation: Attempt HTTPS first. If certificate issues arise, fall back to HTTP for Phase 1 (add `--no-https` and use `http://` URLs). HTTPS can be re-enabled in later phases.
|
|
||||||
|
|
||||||
## Project Constraints (from CLAUDE.md)
|
|
||||||
|
|
||||||
- **Tech stack**: .NET / C# / Blazor WebAssembly -- non-negotiable
|
|
||||||
- **LLM provider**: OpenAI GPT API (not used in Phase 1, but architecture must support it)
|
|
||||||
- **Storage**: JSON files on local disk (not used in Phase 1, but architecture must support it)
|
|
||||||
- **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
|
|
||||||
- **GSD Workflow**: Use GSD entry points for all file changes
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
### Primary (HIGH confidence)
|
|
||||||
- .NET 9 SDK verified installed: `dotnet --list-sdks` shows 9.0.312
|
|
||||||
- `blazorwasm` template verified: `dotnet new list blazorwasm` confirms availability
|
|
||||||
- `webapi --use-controllers` flag verified: `dotnet new webapi --help` confirms option exists
|
|
||||||
- Template output verified: Created temporary projects to confirm `Program.cs` structure, `.csproj` contents, and default file layout
|
|
||||||
|
|
||||||
### Secondary (MEDIUM confidence)
|
|
||||||
- `.planning/research/ARCHITECTURE.md` -- pre-existing project research on WASM/API split patterns
|
|
||||||
- `.planning/research/STACK.md` -- pre-existing stack research with NuGet-verified versions
|
|
||||||
- `.planning/research/PITFALLS.md` -- pre-existing pitfalls research from official GitHub issues
|
|
||||||
|
|
||||||
### Tertiary (LOW confidence)
|
|
||||||
- None -- all findings verified against installed SDK and official templates
|
|
||||||
|
|
||||||
## Metadata
|
|
||||||
|
|
||||||
**Confidence breakdown:**
|
|
||||||
- Standard stack: HIGH -- verified against installed SDK and templates
|
|
||||||
- Architecture: HIGH -- three-project structure is well-documented Microsoft pattern; template output confirmed
|
|
||||||
- Pitfalls: HIGH -- CORS ordering, port mismatch, and IL trimming are well-known ASP.NET Core issues documented in official sources
|
|
||||||
|
|
||||||
**Research date:** 2026-03-27
|
|
||||||
**Valid until:** 2026-04-27 (stable -- .NET 9 is mature, templates are not changing)
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
phase: 1
|
|
||||||
slug: architecture-foundation
|
|
||||||
status: draft
|
|
||||||
nyquist_compliant: false
|
|
||||||
wave_0_complete: false
|
|
||||||
created: 2026-03-27
|
|
||||||
---
|
|
||||||
|
|
||||||
# Phase 1 — Validation Strategy
|
|
||||||
|
|
||||||
> Per-phase validation contract for feedback sampling during execution.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Test Infrastructure
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| **Framework** | dotnet test (built-in) |
|
|
||||||
| **Config file** | none — Wave 0 installs |
|
|
||||||
| **Quick run command** | `dotnet build` |
|
|
||||||
| **Full suite command** | `dotnet build && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj` |
|
|
||||||
| **Estimated runtime** | ~15 seconds |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sampling Rate
|
|
||||||
|
|
||||||
- **After every task commit:** Run `dotnet build`
|
|
||||||
- **After every plan wave:** Run `dotnet build && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj`
|
|
||||||
- **Before `/gsd:verify-work`:** Full suite must be green
|
|
||||||
- **Max feedback latency:** 15 seconds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Per-Task Verification Map
|
|
||||||
|
|
||||||
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|
|
||||||
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
|
|
||||||
| 01-01-01 | 01 | 1 | CODE-01 | build | `dotnet build` | ❌ W0 | ⬜ pending |
|
|
||||||
| 01-01-02 | 01 | 1 | CODE-01 | build | `dotnet build` | ❌ W0 | ⬜ pending |
|
|
||||||
| 01-02-01 | 02 | 1 | CODE-02 | build+run | `dotnet build && dotnet run --project src/ChatAgent.Api` | ❌ W0 | ⬜ pending |
|
|
||||||
| 01-02-02 | 02 | 2 | CODE-02 | publish | `dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj` | ❌ W0 | ⬜ pending |
|
|
||||||
|
|
||||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Wave 0 Requirements
|
|
||||||
|
|
||||||
- [ ] Solution and projects created via `dotnet new` commands
|
|
||||||
- [ ] All three projects buildable with `dotnet build`
|
|
||||||
|
|
||||||
*If none: "Existing infrastructure covers all phase requirements."*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Manual-Only Verifications
|
|
||||||
|
|
||||||
| Behavior | Requirement | Why Manual | Test Instructions |
|
|
||||||
|----------|-------------|------------|-------------------|
|
|
||||||
| CORS working end-to-end | CODE-01 | Requires both projects running simultaneously | Start API, start Client, verify client can reach API endpoint |
|
|
||||||
| Tutorial comments present | CODE-02 | Content quality check | Review each file for inline Blazor concept explanations |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Validation Sign-Off
|
|
||||||
|
|
||||||
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
|
|
||||||
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
|
|
||||||
- [ ] Wave 0 covers all MISSING references
|
|
||||||
- [ ] No watch-mode flags
|
|
||||||
- [ ] Feedback latency < 15s
|
|
||||||
- [ ] `nyquist_compliant: true` set in frontmatter
|
|
||||||
|
|
||||||
**Approval:** pending
|
|
||||||
Binary file not shown.
@@ -1,389 +0,0 @@
|
|||||||
# Architecture Research
|
|
||||||
|
|
||||||
**Domain:** Blazor WebAssembly AI Chat Application
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Confidence:** HIGH (Microsoft official docs + verified community patterns)
|
|
||||||
|
|
||||||
## Standard Architecture
|
|
||||||
|
|
||||||
### System Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ BROWSER (Blazor WASM) │
|
|
||||||
├──────────────────────────────────────────────────────────────────────┤
|
|
||||||
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
|
|
||||||
│ │ ChatPage │ │ ConvList │ │ MessageBubble │ │
|
|
||||||
│ │ (container) │ │ (sidebar) │ │ (leaf component) │ │
|
|
||||||
│ └───────┬────────┘ └───────┬────────┘ └────────────────────────┘ │
|
|
||||||
│ │ │ │
|
|
||||||
│ ┌───────▼──────────────────▼─────────────────────────────────────┐ │
|
|
||||||
│ │ ConversationStateService (Singleton DI) │ │
|
|
||||||
│ │ Holds: active conversation, message list, loading flag │ │
|
|
||||||
│ └───────┬────────────────────────────────────────────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌───────▼────────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ ChatApiClient (HttpClient wrapper) │ │
|
|
||||||
│ │ POST /api/conversations, GET /api/stream?…, DELETE … │ │
|
|
||||||
│ └───────┬────────────────────────────────────────────────────────┘ │
|
|
||||||
└──────────┼─────────────────────────────────────────────────────────┘
|
|
||||||
│ HTTP / SSE (text/event-stream)
|
|
||||||
│
|
|
||||||
┌──────────▼─────────────────────────────────────────────────────────┐
|
|
||||||
│ ASP.NET Core Minimal API (Server) │
|
|
||||||
├─────────────────────────────────────────────────────────────────────┤
|
|
||||||
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
|
|
||||||
│ │ ChatEndpoints │ │ ConversationEndpoints │ │
|
|
||||||
│ │ POST /api/chat │ │ GET/POST/DELETE /api/… │ │
|
|
||||||
│ │ GET /api/chat/… │ │ │ │
|
|
||||||
│ └──────────┬───────────┘ └───────────────┬──────────────────┘ │
|
|
||||||
│ │ │ │
|
|
||||||
│ ┌──────────▼───────────┐ ┌───────────────▼──────────────────┐ │
|
|
||||||
│ │ OpenAiService │ │ ConversationRepository │ │
|
|
||||||
│ │ (streams tokens via │ │ (reads/writes JSON files) │ │
|
|
||||||
│ │ openai-dotnet SDK) │ │ │ │
|
|
||||||
│ └──────────┬───────────┘ └───────────────┬──────────────────┘ │
|
|
||||||
│ │ │ │
|
|
||||||
├─────────────┼───────────────────────────────┼────────────────────────┤
|
|
||||||
│ │ HTTPS │ local disk │
|
|
||||||
│ ┌─────▼──────┐ ┌─────────▼──────────────────┐ │
|
|
||||||
│ │ OpenAI API │ │ ~/chat-data/ │ │
|
|
||||||
│ │ (GPT-4o) │ │ conversations/{id}.json │ │
|
|
||||||
│ └────────────┘ └────────────────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component Responsibilities
|
|
||||||
|
|
||||||
| Component | Responsibility | Typical Implementation |
|
|
||||||
|-----------|----------------|------------------------|
|
|
||||||
| ChatPage | Top-level container — composes sidebar + chat panel, owns route | Blazor page component (`@page "/chat/{id?}"`) |
|
|
||||||
| ConversationList | Lists saved conversations, triggers create/delete/switch | Child component with EventCallback to parent |
|
|
||||||
| MessageList | Renders all messages in active conversation | Child component, iterates message model |
|
|
||||||
| MessageBubble | Renders single message — user vs AI, markdown for AI | Leaf component, uses Markdig or similar |
|
|
||||||
| ChatInput | Text area + send button, raises OnSend event | Child component with EventCallback |
|
|
||||||
| ConversationStateService | Singleton in-memory state — active conversation, messages, streaming flag | C# service registered `AddSingleton`, raises `OnChange` events |
|
|
||||||
| ChatApiClient | Wraps HttpClient, handles streaming plumbing | Scoped service, uses `SetBrowserResponseStreamingEnabled(true)` |
|
|
||||||
| ChatEndpoints | Minimal API: accepts message, streams SSE response from OpenAI | Static endpoint methods wired in `Program.cs` |
|
|
||||||
| ConversationEndpoints | Minimal API: CRUD for conversations | Static endpoint methods |
|
|
||||||
| OpenAiService | Calls OpenAI SDK, returns `IAsyncEnumerable<string>` of tokens | Scoped service on server |
|
|
||||||
| ConversationRepository | Read/write JSON files on disk | Singleton or Scoped service on server |
|
|
||||||
|
|
||||||
## Recommended Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
ChatAgentWebApp/
|
|
||||||
├── ChatAgentWebApp.Client/ # Blazor WASM project
|
|
||||||
│ ├── Components/
|
|
||||||
│ │ ├── Chat/
|
|
||||||
│ │ │ ├── ChatPage.razor # Page — route entry point
|
|
||||||
│ │ │ ├── MessageList.razor # Renders message history
|
|
||||||
│ │ │ ├── MessageBubble.razor # Single message (user/AI)
|
|
||||||
│ │ │ └── ChatInput.razor # Text input + send
|
|
||||||
│ │ └── Conversations/
|
|
||||||
│ │ └── ConversationList.razor # Sidebar conversation switcher
|
|
||||||
│ ├── Services/
|
|
||||||
│ │ ├── ConversationStateService.cs # In-memory singleton state
|
|
||||||
│ │ └── ChatApiClient.cs # HttpClient wrapper + SSE reading
|
|
||||||
│ └── Program.cs # DI registration, HttpClient base URL
|
|
||||||
│
|
|
||||||
├── ChatAgentWebApp.Server/ # ASP.NET Core Minimal API project
|
|
||||||
│ ├── Endpoints/
|
|
||||||
│ │ ├── ChatEndpoints.cs # POST /api/chat/stream (SSE)
|
|
||||||
│ │ └── ConversationEndpoints.cs # GET/POST/DELETE /api/conversations
|
|
||||||
│ ├── Services/
|
|
||||||
│ │ ├── OpenAiService.cs # Wraps openai-dotnet SDK, yields tokens
|
|
||||||
│ │ └── ConversationRepository.cs # JSON file read/write
|
|
||||||
│ ├── Models/ # Server-only models (request/response)
|
|
||||||
│ └── Program.cs # Minimal API wiring, CORS, DI
|
|
||||||
│
|
|
||||||
└── ChatAgentWebApp.Shared/ # Shared library (both projects reference)
|
|
||||||
└── Models/
|
|
||||||
├── Conversation.cs # Shared model — id, title, createdAt
|
|
||||||
└── ChatMessage.cs # Shared model — role, content, timestamp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Structure Rationale
|
|
||||||
|
|
||||||
- **Client/Services/:** All HttpClient wiring and streaming logic lives here, not in components. Components stay dumb (data in via parameters, actions out via EventCallback).
|
|
||||||
- **Shared/Models/:** Models used by both Client (display) and Server (serialization) live here. Eliminates duplicate DTOs.
|
|
||||||
- **Server/Endpoints/:** Minimal API endpoint registration separated by concern (chat streaming vs conversation CRUD). Keeps Program.cs clean.
|
|
||||||
- **Server/Services/:** OpenAI SDK calls and file I/O isolated from HTTP concerns. Enables testing without HTTP context.
|
|
||||||
|
|
||||||
## Architectural Patterns
|
|
||||||
|
|
||||||
### Pattern 1: SSE Streaming from Minimal API to WASM Client
|
|
||||||
|
|
||||||
**What:** The server endpoint writes `text/event-stream` frames to the response as OpenAI tokens arrive. The WASM client reads the response as a stream using `SetBrowserResponseStreamingEnabled(true)` and processes tokens without waiting for the full response.
|
|
||||||
|
|
||||||
**When to use:** Any time you need token-by-token streaming from an LLM. The alternative (wait for full response, then display) is noticeably worse UX for long answers.
|
|
||||||
|
|
||||||
**Trade-offs:** Slightly more plumbing than a simple JSON response. SSE is one-directional (server to client), which is fine here — the client sends the initial message as a POST, and the stream returns the reply.
|
|
||||||
|
|
||||||
**Server endpoint pattern:**
|
|
||||||
```csharp
|
|
||||||
// Server: ChatEndpoints.cs
|
|
||||||
app.MapPost("/api/chat/stream", async (ChatRequest request, OpenAiService ai,
|
|
||||||
ConversationRepository repo, HttpContext http) =>
|
|
||||||
{
|
|
||||||
http.Response.Headers.ContentType = "text/event-stream";
|
|
||||||
http.Response.Headers.CacheControl = "no-cache";
|
|
||||||
|
|
||||||
await foreach (var token in ai.StreamResponseAsync(request))
|
|
||||||
{
|
|
||||||
await http.Response.WriteAsync($"data: {token}\n\n");
|
|
||||||
await http.Response.Body.FlushAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
await http.Response.WriteAsync("event: done\ndata: end\n\n");
|
|
||||||
await repo.AppendMessageAsync(request.ConversationId, assistantMessage);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Client consumption pattern:**
|
|
||||||
```csharp
|
|
||||||
// Client: ChatApiClient.cs
|
|
||||||
var req = new HttpRequestMessage(HttpMethod.Post, "/api/chat/stream");
|
|
||||||
req.SetBrowserResponseStreamingEnabled(true); // Critical for WASM
|
|
||||||
req.Content = JsonContent.Create(chatRequest);
|
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(req,
|
|
||||||
HttpCompletionOption.ResponseHeadersRead); // Don't buffer full body
|
|
||||||
|
|
||||||
using var stream = await response.Content.ReadAsStreamAsync();
|
|
||||||
using var reader = new StreamReader(stream);
|
|
||||||
|
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
|
||||||
var line = await reader.ReadLineAsync();
|
|
||||||
if (line?.StartsWith("data: ") == true)
|
|
||||||
{
|
|
||||||
var token = line[6..];
|
|
||||||
_stateService.AppendToken(token);
|
|
||||||
await InvokeAsync(StateHasChanged); // Update UI per token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 2: Singleton State Service as Shared State
|
|
||||||
|
|
||||||
**What:** A `ConversationStateService` registered as a singleton (in WASM, scoped = singleton anyway) holds the active conversation, message list, and streaming state. Components subscribe to an `OnChange` event to re-render when state updates.
|
|
||||||
|
|
||||||
**When to use:** When multiple components need to reflect the same data — the sidebar list, the message pane, and the input box all depend on the same active conversation.
|
|
||||||
|
|
||||||
**Trade-offs:** Simple and explicit. Not as formal as Redux/Flux but appropriate for single-user personal tool. Avoids prop-drilling through component hierarchies.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```csharp
|
|
||||||
// ConversationStateService.cs
|
|
||||||
public class ConversationStateService
|
|
||||||
{
|
|
||||||
public Conversation? ActiveConversation { get; private set; }
|
|
||||||
public List<ChatMessage> Messages { get; } = new();
|
|
||||||
public bool IsStreaming { get; private set; }
|
|
||||||
|
|
||||||
public event Action? OnChange; // Components subscribe to this
|
|
||||||
|
|
||||||
public void SetActive(Conversation conv)
|
|
||||||
{
|
|
||||||
ActiveConversation = conv;
|
|
||||||
Messages.Clear();
|
|
||||||
NotifyStateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AppendToken(string token)
|
|
||||||
{
|
|
||||||
// Append to last message (AI response being streamed)
|
|
||||||
Messages.Last().Content += token;
|
|
||||||
NotifyStateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 3: Repository for JSON File Persistence
|
|
||||||
|
|
||||||
**What:** `ConversationRepository` encapsulates all file I/O. Each conversation is stored as `{id}.json` in a configured data directory. The repository loads/saves these files and maintains an in-memory index for listing.
|
|
||||||
|
|
||||||
**When to use:** Always — never write `File.ReadAll...` directly in endpoint handlers. Even for JSON files, the repository pattern keeps concerns separate and makes the storage medium swappable.
|
|
||||||
|
|
||||||
**Trade-offs:** Slight overhead vs inline file I/O. But it cleanly separates persistence from HTTP handling and makes unit testing possible without touching the filesystem.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// ConversationRepository.cs
|
|
||||||
public class ConversationRepository
|
|
||||||
{
|
|
||||||
private readonly string _dataDir;
|
|
||||||
|
|
||||||
public async Task<List<Conversation>> GetAllAsync() { ... }
|
|
||||||
public async Task<Conversation?> GetByIdAsync(string id) { ... }
|
|
||||||
public async Task SaveAsync(Conversation conv) { ... }
|
|
||||||
public async Task DeleteAsync(string id) { ... }
|
|
||||||
public async Task AppendMessageAsync(string id, ChatMessage msg) { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
### Sending a Message and Receiving a Streaming Response
|
|
||||||
|
|
||||||
```
|
|
||||||
[User types message + clicks Send]
|
|
||||||
↓
|
|
||||||
[ChatInput.razor] → OnSend EventCallback
|
|
||||||
↓
|
|
||||||
[ChatPage.razor] calls ConversationStateService.BeginStreaming()
|
|
||||||
↓
|
|
||||||
[ChatApiClient.PostMessageStreamAsync()] — POST /api/chat/stream
|
|
||||||
↓
|
|
||||||
[Server: ChatEndpoints] receives request
|
|
||||||
↓
|
|
||||||
[OpenAiService.StreamResponseAsync()] → calls OpenAI GPT API
|
|
||||||
↓
|
|
||||||
[OpenAI returns streaming response] — tokens arrive incrementally
|
|
||||||
↓
|
|
||||||
[Server writes SSE frames] → "data: Hello\n\n", "data: world\n\n", ...
|
|
||||||
↓
|
|
||||||
[Client StreamReader] reads lines as they arrive (ResponseHeadersRead)
|
|
||||||
↓
|
|
||||||
[ConversationStateService.AppendToken()] mutates last message
|
|
||||||
↓
|
|
||||||
[OnChange event fires] → all subscribed components call StateHasChanged()
|
|
||||||
↓
|
|
||||||
[MessageList re-renders] — user sees tokens appearing in real time
|
|
||||||
↓
|
|
||||||
[Server sends "event: done"] → client marks IsStreaming = false
|
|
||||||
↓
|
|
||||||
[Server persists full response] → ConversationRepository.AppendMessageAsync()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conversation Management Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
[User clicks "New Conversation"]
|
|
||||||
↓
|
|
||||||
[ConversationList.razor] → EventCallback to ChatPage
|
|
||||||
↓
|
|
||||||
[ChatApiClient.CreateConversationAsync()] → POST /api/conversations
|
|
||||||
↓
|
|
||||||
[Server: ConversationEndpoints] → ConversationRepository.SaveAsync()
|
|
||||||
↓
|
|
||||||
[Returns new Conversation object]
|
|
||||||
↓
|
|
||||||
[ConversationStateService.SetActive(newConv)] → clears message list
|
|
||||||
↓
|
|
||||||
[All components re-render] — empty chat ready for input
|
|
||||||
```
|
|
||||||
|
|
||||||
### State Management Summary
|
|
||||||
|
|
||||||
```
|
|
||||||
ConversationStateService (Singleton in WASM)
|
|
||||||
OnChange event
|
|
||||||
↓ (subscribed in OnInitialized, unsubscribed in Dispose)
|
|
||||||
[ChatPage] [MessageList] [ConversationList] [ChatInput]
|
|
||||||
↑
|
|
||||||
Mutations via method calls (SetActive, AppendToken, SetStreaming)
|
|
||||||
↑
|
|
||||||
Triggered by ChatApiClient responses
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build Order Implications
|
|
||||||
|
|
||||||
Components have clear dependency layers. Build from the bottom up:
|
|
||||||
|
|
||||||
1. **Shared Models** — `Conversation`, `ChatMessage` (no deps, both projects need these)
|
|
||||||
2. **ConversationRepository** — file I/O, no HTTP (testable in isolation)
|
|
||||||
3. **OpenAiService** — OpenAI SDK calls, yields `IAsyncEnumerable<string>`
|
|
||||||
4. **Server Endpoints** — wires services to HTTP (depends on 2 and 3)
|
|
||||||
5. **ChatApiClient** — WASM HTTP client + SSE consumer (depends on server being up)
|
|
||||||
6. **ConversationStateService** — in-memory state (depends on models, no HTTP)
|
|
||||||
7. **Leaf UI components** — `MessageBubble`, `ChatInput`, `ConversationList` (pure display)
|
|
||||||
8. **Container components** — `MessageList`, `ChatPage` (compose leaves, use state service)
|
|
||||||
|
|
||||||
This order maps naturally to build phases: backend first (phases 1-3), then state layer, then UI.
|
|
||||||
|
|
||||||
## Scaling Considerations
|
|
||||||
|
|
||||||
This is a single-user personal tool. Scaling is not a concern for v1.
|
|
||||||
|
|
||||||
| Scale | Architecture Adjustment |
|
|
||||||
|-------|--------------------------|
|
|
||||||
| 1 user (current) | Monolith fine, JSON files fine, no auth needed |
|
|
||||||
| Multi-user | Add auth, move to SQLite or Postgres, scope state service per user |
|
|
||||||
| Cloud deploy | Externalize API key via Azure Key Vault, containerize server |
|
|
||||||
|
|
||||||
### Scaling Priorities (if needed in v2+)
|
|
||||||
|
|
||||||
1. **First bottleneck:** JSON files don't support concurrent writes — add SQLite (EF Core migration is straightforward)
|
|
||||||
2. **Second bottleneck:** Single server process — Blazor WASM + separate API already decoupled; scale API independently
|
|
||||||
|
|
||||||
## Anti-Patterns
|
|
||||||
|
|
||||||
### Anti-Pattern 1: Calling OpenAI from the WASM Client
|
|
||||||
|
|
||||||
**What people do:** Register `HttpClient` in the Blazor WASM project and call `api.openai.com` directly.
|
|
||||||
|
|
||||||
**Why it's wrong:** The OpenAI API key is visible to anyone who opens browser DevTools. The key is in the `Authorization` header of every request.
|
|
||||||
|
|
||||||
**Do this instead:** All OpenAI calls go through the backend API. The server reads the key from `appsettings.json` or environment variables (server-side, never shipped to browser).
|
|
||||||
|
|
||||||
### Anti-Pattern 2: Buffering the Streaming Response
|
|
||||||
|
|
||||||
**What people do:** Call `await response.Content.ReadAsStringAsync()` after `SendAsync`, then parse the complete response.
|
|
||||||
|
|
||||||
**Why it's wrong:** In Blazor WASM, without `SetBrowserResponseStreamingEnabled(true)` and `ResponseHeadersRead`, the browser buffers the entire response before making any of it available. The UI shows nothing until the full AI response is complete — defeating the entire point of streaming.
|
|
||||||
|
|
||||||
**Do this instead:** Set `SetBrowserResponseStreamingEnabled(true)` on the `HttpRequestMessage` and use `HttpCompletionOption.ResponseHeadersRead` in `SendAsync`. Read the response as a stream line by line.
|
|
||||||
|
|
||||||
### Anti-Pattern 3: Calling StateHasChanged from a Background Thread
|
|
||||||
|
|
||||||
**What people do:** Mutate state and call `StateHasChanged()` directly inside async token-reading loops.
|
|
||||||
|
|
||||||
**Why it's wrong:** In Blazor WASM, this is mostly harmless today because WASM is single-threaded — but in .NET 10+, multi-threaded WASM is becoming a reality. The correct pattern also reads more clearly.
|
|
||||||
|
|
||||||
**Do this instead:** Use `await InvokeAsync(StateHasChanged)` when updating UI from within async callbacks or loops. This schedules re-render on the correct synchronization context and is safe across all hosting models.
|
|
||||||
|
|
||||||
### Anti-Pattern 4: Fat Components (Logic in Razor Files)
|
|
||||||
|
|
||||||
**What people do:** Put API call logic, JSON deserialization, and state mutation directly in `.razor` component code blocks.
|
|
||||||
|
|
||||||
**Why it's wrong:** The tutorial nature of this project means code must be readable. Logic buried in components is hard to explain, hard to test, and violates single responsibility. The builder is also learning Blazor patterns — fat components teach bad habits.
|
|
||||||
|
|
||||||
**Do this instead:** Components only call services. Services own all logic. This also demonstrates the Blazor DI pattern explicitly, which is a key learning objective.
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
### External Services
|
|
||||||
|
|
||||||
| Service | Integration Pattern | Notes |
|
|
||||||
|---------|---------------------|-------|
|
|
||||||
| OpenAI GPT API | `openai-dotnet` SDK on server, `IAsyncEnumerable<string>` returned | Never from WASM client. Key in `appsettings.json` on server. |
|
|
||||||
| Browser FileSystem | None — all persistence is server-side JSON files | WASM cannot write to local disk; server has full file access |
|
|
||||||
|
|
||||||
### Internal Boundaries
|
|
||||||
|
|
||||||
| Boundary | Communication | Notes |
|
|
||||||
|----------|---------------|-------|
|
|
||||||
| WASM Client ↔ API Server | HTTP / SSE via `HttpClient` | Configure base URL + CORS. During development, server serves client static files (hosted model). |
|
|
||||||
| ChatPage ↔ Child Components | Blazor parameters + EventCallback | Downward via `[Parameter]`, upward via `EventCallback<T>` |
|
|
||||||
| Components ↔ State Service | Injected singleton, `OnChange` event subscription | Components subscribe in `OnInitialized`, unsubscribe in `IDisposable.Dispose` |
|
|
||||||
| Endpoints ↔ Services | Constructor DI | Both `OpenAiService` and `ConversationRepository` injected into endpoint handlers |
|
|
||||||
| OpenAiService ↔ ConversationRepository | No direct coupling | Endpoint coordinates both — calls AI service, then persists to repo |
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- [Microsoft Docs: Call a web API from Blazor (aspnetcore-10.0)](https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-10.0)
|
|
||||||
- [Meziantou: Streaming an HTTP response in Blazor WebAssembly](https://www.meziantou.net/streaming-an-http-response-in-blazor-webassembly.htm)
|
|
||||||
- [Strathweb: Built-in support for Server Sent Events in .NET 9](https://www.strathweb.com/2024/07/built-in-support-for-server-sent-events-in-net-9/)
|
|
||||||
- [Petkir: Stream chat to your frontend with SSE in ASP.NET Core (.NET 10)](https://www.petkir.at/blog/semantic-kernel/01_chat_03_sse)
|
|
||||||
- [openai/openai-dotnet issue #65: Streaming doesn't work properly in Blazor WASM](https://github.com/openai/openai-dotnet/issues/65)
|
|
||||||
- [Microsoft Docs: Blazor project structure](https://learn.microsoft.com/en-us/aspnet/core/blazor/project-structure?view=aspnetcore-10.0)
|
|
||||||
- [Microsoft Docs: Blazor state management](https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management/?view=aspnetcore-10.0)
|
|
||||||
- [Syncfusion: MVVM Pattern in Blazor For State Management](https://www.syncfusion.com/blogs/post/mvvm-pattern-blazor-state-management)
|
|
||||||
- [PalmHill.BlazorChat — reference implementation (WASM + WebAPI + real-time LLM)](https://github.com/edgett/PalmHill.BlazorChat)
|
|
||||||
|
|
||||||
---
|
|
||||||
*Architecture research for: Blazor WebAssembly AI Chat Application*
|
|
||||||
*Researched: 2026-03-27*
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
# Feature Research
|
|
||||||
|
|
||||||
**Domain:** Personal AI chat web application (single-user, OpenAI GPT backend, Blazor WebAssembly)
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Confidence:** HIGH (core features verified against live ChatGPT/Claude, OpenAI API docs, and Blazor ecosystem)
|
|
||||||
|
|
||||||
## Feature Landscape
|
|
||||||
|
|
||||||
### Table Stakes (Users Expect These)
|
|
||||||
|
|
||||||
Features users assume exist. Missing these = product feels incomplete.
|
|
||||||
|
|
||||||
| Feature | Why Expected | Complexity | Notes |
|
|
||||||
|---------|--------------|------------|-------|
|
|
||||||
| Send message and receive response | Core function of any AI chat app | LOW | POST to backend API; backend calls OpenAI chat completions endpoint |
|
|
||||||
| Streaming token-by-token responses | ChatGPT normalized this; blocking responses feel broken | MEDIUM | Server-Sent Events (SSE) from backend; HttpClient streaming or SignalR on WASM client; creates "typewriter" effect |
|
|
||||||
| Markdown rendering in AI responses | GPT always responds with markdown; raw markdown is unreadable | MEDIUM | Markdig library on backend or client; MarkupString in Blazor to render HTML; Markdown.ColorCode for syntax highlighting |
|
|
||||||
| Syntax-highlighted code blocks | Code responses are a primary GPT use-case; unformatted code is unusable | MEDIUM | Markdown.ColorCode NuGet package; note: CsharpToColouredHTML has WASM compatibility issues — use base ColorCode package |
|
|
||||||
| Copy-to-clipboard on code blocks | Standard expectation from ChatGPT/Claude; users paste code constantly | LOW | JavaScript interop in Blazor (`navigator.clipboard.writeText`); small JS interop call |
|
|
||||||
| Multiple named conversations | Users need to separate topics; single-thread apps feel like a toy | MEDIUM | Conversation list in sidebar; each conversation has ID, title, message list; JSON file per conversation |
|
|
||||||
| Create and switch between conversations | Navigation between conversations is core workflow | LOW | Once multi-conversation storage exists, switching is just loading by ID |
|
|
||||||
| Delete conversations | Users need to clean up; no delete = clutter accumulates | LOW | Remove JSON file; update conversation list |
|
|
||||||
| Persist conversation history across sessions | Without persistence, app is useless after refresh | MEDIUM | JSON file storage on disk; load on startup; save on every message |
|
|
||||||
| Auto-scroll to latest message | Standard chat behavior; missing it feels broken | LOW | JavaScript interop to scroll div; or CSS scroll-behavior |
|
|
||||||
| Loading/thinking indicator | Users need feedback that a request is in-flight | LOW | Show spinner or "..." while awaiting first token; hide once streaming starts |
|
|
||||||
| Input disabled during response | Prevent double-submit while response is streaming | LOW | Boolean state flag; disable textarea and button while `isStreaming = true` |
|
|
||||||
| Send on Enter key | Standard text input convention for chat | LOW | `@onkeydown` handler; Shift+Enter for newline |
|
|
||||||
| Responsive layout | Mobile-friendly is expected even for personal tools | LOW | CSS flexbox/grid; sidebar collapses on small screens |
|
|
||||||
|
|
||||||
### Differentiators (Competitive Advantage)
|
|
||||||
|
|
||||||
Features that set the product apart. Not required, but valuable.
|
|
||||||
|
|
||||||
| Feature | Value Proposition | Complexity | Notes |
|
|
||||||
|---------|-------------------|------------|-------|
|
|
||||||
| Auto-generated conversation titles | Reduces naming friction; GPT can summarize first message as title | LOW | Call GPT with "Summarize this in 5 words" after first exchange; update conversation title |
|
|
||||||
| System prompt / persona configuration | Power users want to customize GPT behavior per conversation | MEDIUM | Add `systemPrompt` field to conversation model; include as first message in API payload |
|
|
||||||
| Message edit and regenerate | Fix typos without starting over; common in ChatGPT | MEDIUM | Truncate conversation at edited message; resend; requires re-streaming |
|
|
||||||
| Token usage display | Helps users understand context window consumption; teaches GPT behavior | LOW | OpenAI API returns usage in response; display in footer or message metadata |
|
|
||||||
| Conversation search | Find a past conversation by keyword | MEDIUM | Client-side search over loaded conversation titles; full-text needs indexing |
|
|
||||||
| Export conversation | Save as markdown or text file | LOW | Serialize messages to markdown string; trigger browser download via JS interop |
|
|
||||||
| Model selector (GPT-4o vs GPT-4o-mini) | Cost vs quality tradeoff is real; power users want control | LOW | Dropdown stored in app settings or per-conversation; passed as `model` parameter in API call |
|
|
||||||
| Well-commented tutorial-style code | The project doubles as a Blazor learning resource | LOW (implementation cost) | Inline `// Blazor:` comments on lifecycle hooks, DI, component patterns — this is a core differentiator for this specific project's purpose |
|
|
||||||
|
|
||||||
### Anti-Features (Commonly Requested, Often Problematic)
|
|
||||||
|
|
||||||
Features that seem good but create problems.
|
|
||||||
|
|
||||||
| Feature | Why Requested | Why Problematic | Alternative |
|
|
||||||
|---------|---------------|-----------------|-------------|
|
|
||||||
| Authentication / login | "What if someone else uses it?" | Single-user personal tool; adds OAuth complexity with zero value | Leave open; document that it's intentionally single-user |
|
|
||||||
| Database (SQL/SQLite) | "JSON doesn't scale" | Premature optimization for a personal tool; adds EF Core migration complexity | JSON files are fast, human-readable, and zero-setup — perfect for this scope |
|
|
||||||
| Real-time sync across tabs | "What if I have two windows open?" | SignalR state sync complexity; no real use case for single user | Reload on focus; acceptable for personal tool |
|
|
||||||
| Plugin / tool calling system | "GPT can call functions!" | That's LangChain/MCP territory; v2 scope — building it now adds architecture complexity before core chat works | Defer to v2 milestone with LangChain and MCP servers |
|
|
||||||
| Voice input / output | ChatGPT has it | OpenAI Realtime API is being deprecated May 2026; adds Web Speech API complexity; out of scope | Text-only for v1 |
|
|
||||||
| Image uploads / multimodal | GPT-4o supports it | WASM file upload + base64 encoding + vision API adds significant complexity | Text chat first; defer multimodal to v2 |
|
|
||||||
| Conversation branching | "What if I want to explore different answers?" | Complex tree data structure; confusing UX; rare real-world use | Regenerate last response is sufficient for 95% of cases |
|
|
||||||
| Infinite scroll / lazy loading | "What about long conversations?" | Adds virtual scrolling complexity; JSON load is fine at personal scale | Load full conversation on select; revisit if performance suffers |
|
|
||||||
| PWA / offline support | "Make it installable" | Service worker complexity; AI chat requires internet anyway | Responsive web design is sufficient |
|
|
||||||
|
|
||||||
## Feature Dependencies
|
|
||||||
|
|
||||||
```
|
|
||||||
[JSON File Storage]
|
|
||||||
└──required by──> [Multiple Conversations]
|
|
||||||
└──required by──> [Create / Switch / Delete Conversations]
|
|
||||||
└──required by──> [Persist History Across Sessions]
|
|
||||||
|
|
||||||
[OpenAI API Call (blocking)]
|
|
||||||
└──required by──> [Streaming Responses]
|
|
||||||
└──required by──> [Loading Indicator]
|
|
||||||
└──required by──> [Input Disabled During Streaming]
|
|
||||||
|
|
||||||
[Markdown Rendering]
|
|
||||||
└──required by──> [Syntax-Highlighted Code Blocks]
|
|
||||||
└──required by──> [Copy-to-Clipboard on Code Blocks]
|
|
||||||
|
|
||||||
[Streaming Responses] ──enhances──> [Auto-Scroll to Latest Message]
|
|
||||||
|
|
||||||
[Multiple Conversations] ──enables──> [Auto-Generated Titles]
|
|
||||||
[Multiple Conversations] ──enables──> [Conversation Search]
|
|
||||||
[Multiple Conversations] ──enables──> [Export Conversation]
|
|
||||||
|
|
||||||
[System Prompt Config] ──enhances──> [Multiple Conversations]
|
|
||||||
(each conversation can have its own persona)
|
|
||||||
|
|
||||||
[Token Usage Display] ──conflicts with [Streaming]
|
|
||||||
(usage metadata only available when stream=false or in the final chunk)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependency Notes
|
|
||||||
|
|
||||||
- **JSON File Storage required by Multiple Conversations:** Conversations need somewhere to live before the list/switch UI can be built. Storage phase must precede conversation management phase.
|
|
||||||
- **Blocking API call required by Streaming:** Must implement the non-streaming call first to understand the request/response shape, then layer SSE streaming on top.
|
|
||||||
- **Markdown Rendering required by Syntax Highlighting:** ColorCode is a Markdig pipeline extension — Markdig must be wired in before syntax highlighting can be added.
|
|
||||||
- **Token Usage Display conflicts with Streaming:** OpenAI streams individual content chunks; the `usage` field only appears in the final chunk (`finish_reason: stop`). Implementation must capture the last chunk separately.
|
|
||||||
|
|
||||||
## MVP Definition
|
|
||||||
|
|
||||||
### Launch With (v1)
|
|
||||||
|
|
||||||
Minimum viable product — what's needed to validate the concept.
|
|
||||||
|
|
||||||
- [ ] Send message, receive non-streaming GPT response — validates API connectivity and basic loop
|
|
||||||
- [ ] Streaming responses — core UX differentiator; blocking responses feel unacceptable
|
|
||||||
- [ ] Markdown rendering with syntax highlighting — GPT responses are markdown; unrendered output is unusable
|
|
||||||
- [ ] Create / switch / delete multiple conversations — without this, the app is a single disposable thread
|
|
||||||
- [ ] JSON file persistence — conversations must survive page refresh to be useful
|
|
||||||
- [ ] Auto-scroll and loading indicator — baseline polish that makes the app feel complete
|
|
||||||
- [ ] Copy-to-clipboard on code blocks — high-frequency action for developer-focused use
|
|
||||||
- [ ] Tutorial-style inline code comments — this project's defining purpose as a learning resource
|
|
||||||
|
|
||||||
### Add After Validation (v1.x)
|
|
||||||
|
|
||||||
Features to add once core is working.
|
|
||||||
|
|
||||||
- [ ] Auto-generated conversation titles — reduces friction once the core loop is validated
|
|
||||||
- [ ] System prompt / persona configuration — natural extension once multi-conversation is stable
|
|
||||||
- [ ] Model selector — easy add once API layer is clean; real value for cost control
|
|
||||||
- [ ] Export conversation — low complexity, high occasional value
|
|
||||||
|
|
||||||
### Future Consideration (v2+)
|
|
||||||
|
|
||||||
Features to defer until product-market fit is established.
|
|
||||||
|
|
||||||
- [ ] Message edit and regenerate — medium complexity; wait until core loop is solid
|
|
||||||
- [ ] Token usage display — useful but not blocking; needs streaming completion handling
|
|
||||||
- [ ] Conversation search — only valuable when there are many conversations to search
|
|
||||||
- [ ] LangChain / agentic workflows — explicitly v2 scope per PROJECT.md
|
|
||||||
- [ ] RAG document retrieval — v2 scope
|
|
||||||
- [ ] MCP server integration — v2 scope
|
|
||||||
|
|
||||||
## Feature Prioritization Matrix
|
|
||||||
|
|
||||||
| Feature | User Value | Implementation Cost | Priority |
|
|
||||||
|---------|------------|---------------------|----------|
|
|
||||||
| Send message / receive response | HIGH | LOW | P1 |
|
|
||||||
| Streaming responses | HIGH | MEDIUM | P1 |
|
|
||||||
| Markdown + syntax highlighting | HIGH | MEDIUM | P1 |
|
|
||||||
| Multiple conversations + persistence | HIGH | MEDIUM | P1 |
|
|
||||||
| Auto-scroll + loading indicator | HIGH | LOW | P1 |
|
|
||||||
| Copy code to clipboard | HIGH | LOW | P1 |
|
|
||||||
| Tutorial-style code comments | HIGH (for this project) | LOW | P1 |
|
|
||||||
| Auto-generated conversation titles | MEDIUM | LOW | P2 |
|
|
||||||
| System prompt configuration | MEDIUM | MEDIUM | P2 |
|
|
||||||
| Model selector | MEDIUM | LOW | P2 |
|
|
||||||
| Export conversation | LOW | LOW | P2 |
|
|
||||||
| Token usage display | LOW | LOW | P2 |
|
|
||||||
| Message edit and regenerate | MEDIUM | MEDIUM | P3 |
|
|
||||||
| Conversation search | LOW | MEDIUM | P3 |
|
|
||||||
|
|
||||||
**Priority key:**
|
|
||||||
- P1: Must have for launch
|
|
||||||
- P2: Should have, add when possible
|
|
||||||
- P3: Nice to have, future consideration
|
|
||||||
|
|
||||||
## Competitor Feature Analysis
|
|
||||||
|
|
||||||
| Feature | ChatGPT (OpenAI) | Claude (Anthropic) | This Project |
|
|
||||||
|---------|------------------|--------------------|--------------|
|
|
||||||
| Streaming responses | Yes, token-by-token | Yes, token-by-token | Yes — SSE via backend API |
|
|
||||||
| Markdown rendering | Yes | Yes | Yes — Markdig + MarkupString |
|
|
||||||
| Syntax highlighted code | Yes + copy button | Yes + copy button | Yes — Markdown.ColorCode |
|
|
||||||
| Multiple conversations (sidebar) | Yes | Yes (Projects) | Yes — JSON file per conversation |
|
|
||||||
| Conversation persistence | Yes (cloud) | Yes (cloud) | Yes — local JSON files |
|
|
||||||
| Auto-generated titles | Yes | Yes | v1.x — GPT summarization call |
|
|
||||||
| System prompt | Via custom instructions | Via system prompt | v1.x — per-conversation field |
|
|
||||||
| Model selector | Yes (GPT-5.4 variants) | Yes (Opus/Sonnet/Haiku) | v1.x — GPT-4o vs GPT-4o-mini |
|
|
||||||
| Voice input/output | Yes (Advanced Voice Mode) | No | Deliberately excluded from v1 |
|
|
||||||
| Image uploads | Yes (multimodal) | Yes (multimodal) | Deliberately excluded from v1 |
|
|
||||||
| Plugin / tool calling | Yes (via GPT Actions) | Yes (via tool use) | v2 — MCP servers |
|
|
||||||
| RAG / document search | Yes (file attachments) | Yes (Projects + files) | v2 — RAG milestone |
|
|
||||||
| Auth / multi-user | Yes | Yes | Deliberately excluded (single user) |
|
|
||||||
|
|
||||||
## Blazor-Specific Implementation Notes
|
|
||||||
|
|
||||||
These are not features per se, but implementation constraints that affect feature complexity in Blazor WebAssembly:
|
|
||||||
|
|
||||||
- **JavaScript Interop is required for:** clipboard access, scroll-to-bottom, syntax highlighting via client-side JS libraries (Highlight.js alternative to server-side ColorCode)
|
|
||||||
- **API key must never reach WASM client:** all OpenAI calls must go through the ASP.NET Core backend API — the WASM client calls the backend, the backend calls OpenAI
|
|
||||||
- **Streaming from backend to WASM client:** options are SSE (Server-Sent Events via `HttpClient` streaming) or SignalR HubConnection; SSE is simpler for one-way server-to-client streaming; SignalR is better if bidirectional messaging is needed later
|
|
||||||
- **MarkupString in Blazor:** required to render HTML from Markdig; must be used intentionally as it bypasses Blazor's XSS protections — only render trusted content (GPT output is untrusted; sanitize or accept risk as single-user personal tool)
|
|
||||||
- **Markdown.ColorCode WASM note:** base `Markdown.ColorCode` package works in WASM; `Markdown.ColorCode.CSharpToColoredHtml` does NOT — avoid the latter
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- [ChatGPT vs Claude feature comparison 2026 — LogicWeb](https://www.logicweb.com/chatgpt-vs-claude-ultimate-ai-comparison-in-2026/)
|
|
||||||
- [OpenAI Streaming API documentation](https://platform.openai.com/docs/api-reference/chat/streaming)
|
|
||||||
- [OpenAI Streaming Responses Guide](https://developers.openai.com/api/docs/guides/streaming-responses)
|
|
||||||
- [OpenAI Conversation State Guide](https://platform.openai.com/docs/guides/conversation-state)
|
|
||||||
- [Best practices for OpenAI Chat streaming UI — Pamela Fox](http://blog.pamelafox.org/2023/09/best-practices-for-openai-chat-apps_16.html)
|
|
||||||
- [PalmHill.BlazorChat — Blazor WASM + LLM reference implementation](https://github.com/edgett/PalmHill.BlazorChat)
|
|
||||||
- [Blazor Live Preview Markdown with Markdig — Syncfusion](https://www.syncfusion.com/blogs/post/blazor-live-preview-markdown-editors-content-using-markdig-library)
|
|
||||||
- [Markdown.ColorCode NuGet package](https://www.nuget.org/packages/Markdown.ColorCode)
|
|
||||||
- [16 Chat UI Design Patterns 2025](https://bricxlabs.com/articles/message-screen-ui-deisgn)
|
|
||||||
- [AI Chat Interface UX Patterns — UXPatterns.dev](https://uxpatterns.dev/patterns/ai-intelligence/ai-chat)
|
|
||||||
- [Conversational AI UI Comparison 2025 — IntuitionLabs](https://intuitionlabs.ai/articles/conversational-ai-ui-comparison-2025)
|
|
||||||
- [Token management best practices — OpenAI Community](https://community.openai.com/t/best-practices-for-cost-efficient-high-quality-context-management-in-long-ai-chats/1373996)
|
|
||||||
|
|
||||||
---
|
|
||||||
*Feature research for: Personal AI Chat WebApp (Blazor WebAssembly + OpenAI GPT)*
|
|
||||||
*Researched: 2026-03-27*
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
# Pitfalls Research
|
|
||||||
|
|
||||||
**Domain:** Blazor WebAssembly AI Chat Application (OpenAI GPT, JSON storage, streaming)
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Confidence:** HIGH (multiple authoritative sources: official GitHub issues, Microsoft docs, verified community findings)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Critical Pitfalls
|
|
||||||
|
|
||||||
### Pitfall 1: Streaming Silently Broken in Blazor WASM Without Custom Transport
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
OpenAI's .NET SDK streaming (`CompleteChatStreamingAsync`, returning `IAsyncEnumerable`) does not stream token-by-token in Blazor WASM. The entire response arrives at once after generation completes, making it appear like a non-streaming call. There is no error thrown — it just does not stream. This is confirmed in the official `openai-dotnet` GitHub issue tracker (#65).
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
Blazor WASM uses a browser-based `HttpClient` backed by the Fetch API via JS interop. Response streaming requires explicitly calling `SetBrowserResponseStreamingEnabled(true)` on the underlying `HttpRequestMessage`. The OpenAI .NET SDK does not set this flag by default. Without it, the browser buffers the entire response body before exposing it to the .NET layer.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
Create a custom `HttpClientPipelineTransport` that overrides `OnSendingRequest` to enable browser streaming:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class BlazorHttpClientTransport : HttpClientPipelineTransport
|
|
||||||
{
|
|
||||||
protected override void OnSendingRequest(
|
|
||||||
PipelineMessage message,
|
|
||||||
HttpRequestMessage httpRequest)
|
|
||||||
{
|
|
||||||
httpRequest.SetBrowserResponseStreamingEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wire up at client construction:
|
|
||||||
var options = new OpenAIClientOptions();
|
|
||||||
options.Transport = new BlazorHttpClientTransport();
|
|
||||||
var chatClient = new ChatClient(model: "gpt-4o", apiKey, options);
|
|
||||||
```
|
|
||||||
|
|
||||||
This must be done server-side (in the backend API that proxies to OpenAI), not in the WASM client directly.
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- Tokens appear all at once after a delay instead of progressively
|
|
||||||
- No console errors — it looks like it is working but is not streaming
|
|
||||||
- Local dev works "fine" because the full response still arrives, just not incrementally
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Phase that introduces streaming (SSE/token-by-token rendering). Must be addressed before any streaming demo is built.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 2: OpenAI API Key Exposed in Blazor WASM Client
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
A developer puts the OpenAI API key in `wwwroot/appsettings.json` or reads it from `IConfiguration` in a WASM component. The key is then downloadable by any browser visitor by requesting `/_framework/blazor.boot.json` or simply navigating to `wwwroot/appsettings.json` directly. The key is burned.
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
Experienced C# developers coming from ASP.NET Core or console apps expect `appsettings.json` and `IConfiguration` to be server-side. In Blazor WASM, `wwwroot/appsettings.json` is a static file served to the browser — it is not protected in any way. User Secrets also do not help: they are embedded into the published bundle in plaintext.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
The OpenAI API key must live exclusively in the backend API (ASP.NET Core Minimal API or Web API project). The WASM client calls a backend endpoint (e.g., `/api/chat`) that holds the key and proxies requests to OpenAI. The key never appears in any client-side file or JS bundle. Use `dotnet user-secrets` on the server project only.
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- Any `IConfiguration["OpenAI:ApiKey"]` usage in a `.razor` file or service registered in the WASM `Program.cs`
|
|
||||||
- `appsettings.json` in `wwwroot/` containing any token, key, or secret
|
|
||||||
- The word `sk-` visible in browser DevTools → Network → response body for any `.json` file
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Phase 1 (project setup / architecture foundation). This must be locked in before any OpenAI code is written.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 3: Scoped DI Services Act as Singletons in WASM — State Leaks Across Conversations
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
A developer registers a `ConversationService` or `ChatStateService` as `Scoped`, expecting it to reset between logical "sessions" like it would in ASP.NET Core (per-request). In Blazor WASM there is exactly one DI scope for the lifetime of the browser tab. The service never resets. All conversations accumulate state in a single object, producing corrupted cross-conversation history.
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
In ASP.NET Core, Scoped = per HTTP request. In Blazor WASM, Scoped = per application lifetime (equivalent to Singleton). There is no shorter-lived scope unless you use `OwningComponentBase`. Developers familiar with server-side DI expect different behavior.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
- Understand that in WASM, `Scoped` and `Singleton` are functionally identical
|
|
||||||
- For services that manage per-conversation state, design them to hold a collection keyed by conversation ID rather than holding mutable "current conversation" state
|
|
||||||
- If a service must be component-scoped, inherit from `OwningComponentBase` which creates a DI scope tied to the component's lifetime
|
|
||||||
- Never store mutable "active session" state in a scoped/singleton service; store a dictionary of `ConversationId → ConversationState`
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- Switching conversations causes the wrong history to appear
|
|
||||||
- Deleting a conversation does not fully clear its state from memory
|
|
||||||
- Services have fields like `CurrentConversation` or `ActiveMessages` rather than `Dictionary<Guid, Conversation>`
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Phase introducing conversation state management and multi-conversation switching.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 4: `StateHasChanged` Not Called During Token Streaming — UI Freezes Until Completion
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
The developer wires up streaming correctly (transport fixed, backend proxying works) but the UI does not update token-by-token. The message bubble stays empty until all tokens arrive, then the entire response appears at once. This is indistinguishable from the streaming transport bug (Pitfall 1) if not diagnosed carefully.
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
Blazor does not automatically re-render after every `await` inside an `async` event handler or lifecycle method. When consuming an `IAsyncEnumerable<string>` (streaming tokens), the component must explicitly call `StateHasChanged()` after appending each token. Without this call, Blazor batches rendering and only repaints when the entire method completes.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
```csharp
|
|
||||||
await foreach (var token in streamingResponse)
|
|
||||||
{
|
|
||||||
currentMessage += token;
|
|
||||||
StateHasChanged(); // required — Blazor will not re-render otherwise
|
|
||||||
await Task.Yield(); // prevents UI thread starvation on rapid token delivery
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, consider throttling `StateHasChanged` calls (e.g., every 50ms or every N tokens) to avoid excessive rendering if token delivery is very fast.
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- Streaming transport is confirmed working (via backend logs) but UI still shows nothing until complete
|
|
||||||
- Token-by-token updates visible in server logs but not in the browser
|
|
||||||
- Removing `await Task.Yield()` causes the browser tab to become unresponsive during streaming
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Streaming UI rendering phase. Document this explicitly inline in the component code.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 5: JSON File Storage Architecture Assumes Server Filesystem — Not Viable Pure Client-Side
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
A developer writes file I/O code (`File.ReadAllText`, `File.WriteAllText`) directly in the WASM project. The code compiles without error but throws at runtime because Blazor WASM runs in a browser sandbox with no access to the host filesystem. The virtual WASM filesystem resets on every page refresh.
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
C# file APIs exist in the WASM .NET runtime but map to an in-memory virtual filesystem, not the OS disk. Developers coming from console or desktop C# assume `File.WriteAllText("conversations.json", json)` writes to disk. It does not — the data vanishes on refresh.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
JSON file storage must live in the backend API (server-side), not in the WASM client. The correct architecture:
|
|
||||||
- WASM client calls `POST /api/conversations` → backend writes JSON to disk
|
|
||||||
- WASM client calls `GET /api/conversations` → backend reads JSON from disk and returns it
|
|
||||||
- Backend stores files in a configurable local path (e.g., `~/chat-data/`)
|
|
||||||
|
|
||||||
This reinforces the same architectural boundary required for API key protection (Pitfall 2).
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- Any `System.IO.File` or `System.IO.Directory` usage inside the WASM project (`Client/`)
|
|
||||||
- Conversations persist during a session but disappear on browser refresh
|
|
||||||
- Data is present in the WASM virtual FS (`MemoryFileSystem`) but absent from the OS
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Phase 1 (architecture setup). The WASM/backend split must be established before any persistence code is written.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 6: IL Trimming Silently Breaks Code in Release Builds
|
|
||||||
|
|
||||||
**What goes wrong:**
|
|
||||||
The app works perfectly in `dotnet run` (Debug) but breaks in `dotnet publish` (Release). JSON serialization loses properties, services cannot be resolved, or features silently stop working. No exceptions in development, cryptic failures in production.
|
|
||||||
|
|
||||||
**Why it happens:**
|
|
||||||
Blazor WASM uses aggressive IL trimming (ILLink) during publish to reduce bundle size. The trimmer performs static analysis and removes types/methods that appear unreachable — including types used only via reflection (JSON serialization, DI, JSInterop callbacks). Debug builds do not trim.
|
|
||||||
|
|
||||||
**How to avoid:**
|
|
||||||
- Use `[JsonSerializable]` with `System.Text.Json` source generation for all DTO types
|
|
||||||
- Apply `[DynamicDependency]` to methods called via reflection
|
|
||||||
- Apply `[JSInvokable]` to all methods callable from JavaScript
|
|
||||||
- Run `dotnet publish` early in the project (Phase 1 or 2) to detect trim warnings while the surface is small
|
|
||||||
- Treat `<TrimmerRootDescriptor>` as a last resort, not a first step
|
|
||||||
|
|
||||||
**Warning signs:**
|
|
||||||
- App works in `dotnet run` but throws `NullReferenceException` or loses data after `dotnet publish`
|
|
||||||
- JSON responses missing properties that were present in debug
|
|
||||||
- IL trimmer warnings during publish that were ignored
|
|
||||||
|
|
||||||
**Phase to address:**
|
|
||||||
Phase 1 (publish pipeline verification). Also Phase covering JSON data models for conversations.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Debt Patterns
|
|
||||||
|
|
||||||
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|
|
||||||
|----------|-------------------|----------------|-----------------|
|
|
||||||
| Put API key in WASM `appsettings.json` | Faster to get OpenAI call working | Key is permanently burned; must rotate | Never |
|
|
||||||
| Call OpenAI directly from WASM HttpClient | Eliminates backend project | API key exposed; no server-side rate limiting; blocks v2 RAG/LangChain which needs server | Never |
|
|
||||||
| Write file I/O in WASM project | Familiar C# patterns | Silent data loss on refresh; hard to migrate later | Never |
|
|
||||||
| All logic in `.razor` files | Faster iteration in early phases | Untestable; components become unmaintainable; hard to add v2 agent layer | Phase 1 only, refactor before Phase 3 |
|
|
||||||
| Single `ChatService` singleton holding all state | Simple to start | State leaks across conversations; breaks multi-conversation feature | Never in this project — multi-conversation is a core requirement |
|
|
||||||
| Skip `StateHasChanged` calls during streaming | Code is simpler | UI appears broken; streaming appears non-functional | Never |
|
|
||||||
| Skip IL trim testing until "done" | Saves time during early phases | Trim bugs compound as codebase grows | Acceptable in Phase 1-2 if `dotnet publish` test is added to Phase 3 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration Gotchas
|
|
||||||
|
|
||||||
| Integration | Common Mistake | Correct Approach |
|
|
||||||
|-------------|----------------|-----------------|
|
|
||||||
| OpenAI .NET SDK + Blazor WASM | Using SDK directly in WASM project without custom transport — streaming silently broken | Custom `BlazorHttpClientTransport` with `SetBrowserResponseStreamingEnabled(true)` on backend; or server-side only call |
|
|
||||||
| OpenAI streaming via backend proxy | Backend returns full `StreamingChatCompletionUpdate` objects — client gets batched response | Backend uses `IAsyncEnumerable` with `[EnumeratorCancellation]`; streams SSE or NDJSON to client |
|
|
||||||
| CORS between WASM client and backend API | Forgetting `AddCors` + `UseCors` on backend; 403/CORS errors during local dev | Configure CORS policy explicitly in backend `Program.cs`; scope to localhost dev origins |
|
|
||||||
| JSON serialization of conversation models | Properties stripped by trimmer in Release; conversation history loses fields | Use `System.Text.Json` source generators with `[JsonSerializable]` for all model types |
|
|
||||||
| Markdown rendering (AI responses) | Using `MarkupString` with raw AI output — XSS risk if AI returns script tags | Use a library like `Markdig` server-side, or a client-side library with HTML sanitization enabled |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Traps
|
|
||||||
|
|
||||||
| Trap | Symptoms | Prevention | When It Breaks |
|
|
||||||
|------|----------|------------|----------------|
|
|
||||||
| Calling `StateHasChanged` on every token without throttling | Browser tab becomes unresponsive during fast streaming; CPU spikes | Throttle to every 50ms or every N tokens using a timer or counter | At ~20+ tokens/second (typical GPT-4o speed) |
|
|
||||||
| Re-rendering entire conversation list on every message append | Visible flicker; full list DOM re-created on each token | Use `@key` directive on conversation list items; isolate streaming component from sidebar | At 5+ conversations in the list |
|
|
||||||
| Loading all conversation history on app start | Slow initial load for users with many old conversations | Lazy-load conversation content; sidebar shows metadata only; load full messages on selection | At 50+ conversations stored in JSON |
|
|
||||||
| Excessive component nesting for chat messages | Sluggish scroll performance with many messages | Keep message list in a single component with virtualization (`Virtualize`) for long histories | At 200+ messages in a single conversation |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Mistakes
|
|
||||||
|
|
||||||
| Mistake | Risk | Prevention |
|
|
||||||
|---------|------|------------|
|
|
||||||
| OpenAI API key in `wwwroot/appsettings.json` | Key visible to any browser user; unlimited API charges | Key lives only in backend server project; accessed via `dotnet user-secrets` or environment variable |
|
|
||||||
| Calling OpenAI API directly from WASM (no backend) | Same as above; also bypasses rate limiting and logging | Mandatory backend proxy — this is enforced by the architecture from Phase 1 |
|
|
||||||
| Rendering AI response HTML without sanitization | AI model could produce `<script>` tags; XSS attack on self | Use Markdown-to-HTML library with HTML sanitization; never use raw `MarkupString` on LLM output |
|
|
||||||
| Storing secrets in WASM `IConfiguration` | Even runtime config values in WASM are readable from browser | All secrets in server-side `IConfiguration` only; WASM config contains only public values (API endpoint URLs) |
|
|
||||||
| CORS wildcard (`AllowAnyOrigin`) in backend | Allows any site to call the local chat backend | Restrict CORS to `localhost` origins during development; lock down on deploy |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UX Pitfalls
|
|
||||||
|
|
||||||
| Pitfall | User Impact | Better Approach |
|
|
||||||
|---------|-------------|-----------------|
|
|
||||||
| No loading indicator while waiting for first streaming token | App appears frozen; user thinks it broke | Show typing indicator / spinner immediately on message send; hide when first token arrives |
|
|
||||||
| No way to cancel an in-progress stream | User must wait for full response even if they sent the wrong message | Pass `CancellationToken` through to streaming call; expose cancel button that calls `cts.Cancel()` |
|
|
||||||
| Auto-scroll that fights user scroll position | User scrolls up to read history; app violently scrolls back to bottom on each token | Only auto-scroll if the user is already at the bottom; detect scroll position before each `StateHasChanged` |
|
|
||||||
| No error message when OpenAI API call fails | Empty response bubble; user does not know what happened | `try/catch` around all API calls; display inline error in message bubble with retry option |
|
|
||||||
| Conversation list has no visual indication of active conversation | User loses track of which conversation is displayed | Highlight active conversation item; update `document.title` with conversation name |
|
|
||||||
| Markdown rendered as raw text | AI responses with code blocks and lists look like symbol-laden garbage | Wire up Markdown renderer before any AI content is displayed — do not defer this |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## "Looks Done But Isn't" Checklist
|
|
||||||
|
|
||||||
- [ ] **Streaming:** Tokens appear progressively in the UI — verify this is actual token-by-token delivery, not a batched response that arrives quickly. Check with a slow prompt.
|
|
||||||
- [ ] **API key security:** Open DevTools → Network → find any `.json` request → confirm no `sk-` prefixed values appear in any response body.
|
|
||||||
- [ ] **Conversation persistence:** Close and reopen the browser tab (not just refresh). Confirm conversations are still present.
|
|
||||||
- [ ] **Multi-conversation isolation:** Open conversation A, send a message. Switch to conversation B. Verify conversation A's messages do not bleed into B.
|
|
||||||
- [ ] **Stream cancellation:** Start a long generation. Click cancel or navigate away. Confirm the backend stops consuming tokens (check backend logs).
|
|
||||||
- [ ] **Release build:** Run `dotnet publish` at least once before calling any phase "done." Confirm the published app loads and all features work.
|
|
||||||
- [ ] **Error handling:** Temporarily set an invalid API key. Confirm the UI shows a user-friendly error rather than a blank component or silent failure.
|
|
||||||
- [ ] **Markdown rendering:** Ask the AI for a code snippet. Confirm it renders in a code block, not as raw backtick-surrounded text.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recovery Strategies
|
|
||||||
|
|
||||||
| Pitfall | Recovery Cost | Recovery Steps |
|
|
||||||
|---------|---------------|----------------|
|
|
||||||
| API key exposed in WASM | HIGH | Immediately rotate key in OpenAI dashboard; add `Client/` project scan to CI to block any future secret patterns |
|
|
||||||
| All logic in `.razor` files (discovered late) | MEDIUM | Extract services incrementally — one component per PR; do not refactor all at once |
|
|
||||||
| File I/O written in WASM project | MEDIUM | Move all persistence calls to backend API; update WASM to use `HttpClient` calls to new endpoints |
|
|
||||||
| Streaming not working (transport not set) | LOW | Add `BlazorHttpClientTransport` wrapper — 10-line fix once identified; the hard part is diagnosing it |
|
|
||||||
| Scoped service holding mutable conversation state | MEDIUM | Redesign service to hold `Dictionary<Guid, ConversationState>`; update all call sites |
|
|
||||||
| IL trim breaks release build | MEDIUM–HIGH (depends on when discovered) | Add source generators for all model types; treat every trim warning as a compile error |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pitfall-to-Phase Mapping
|
|
||||||
|
|
||||||
| Pitfall | Prevention Phase | Verification |
|
|
||||||
|---------|------------------|--------------|
|
|
||||||
| API key in WASM client | Phase 1: Project setup and architecture | Grep WASM project for `sk-`; confirm key only in backend `user-secrets` |
|
|
||||||
| File I/O in WASM project | Phase 1: Project setup and architecture | Grep WASM project for `System.IO.File`; confirm persistence is backend-only |
|
|
||||||
| Direct OpenAI call from WASM | Phase 1: Project setup and architecture | Confirm no OpenAI SDK registration in WASM `Program.cs` |
|
|
||||||
| CORS misconfiguration | Phase 1: First HTTP call from WASM to backend | Verify browser console shows no CORS errors during local dev |
|
|
||||||
| Scoped DI lifetime confusion | Phase covering conversation state management | Test: create two conversations; switch between them; verify history isolation |
|
|
||||||
| Streaming transport not set | Phase introducing token-by-token streaming | Observe: tokens must appear incrementally; verify with slow prompt or network throttle |
|
|
||||||
| StateHasChanged missing in stream loop | Phase introducing token-by-token streaming | Same as above — visible as UI not updating mid-stream |
|
|
||||||
| UI freeze without streaming throttle | Phase polishing streaming UI | CPU profiler during active stream; verify no jank |
|
|
||||||
| IL trimming breaks release | Phase 1 (publish test) + any phase adding new model types | Run `dotnet publish` as part of each phase completion check |
|
|
||||||
| Markdown XSS via raw MarkupString | Phase introducing Markdown rendering | Code review: confirm no `new MarkupString(aiResponse)` without prior sanitization |
|
|
||||||
| Auto-scroll fighting user scroll | Phase building chat message UI | Manual test: scroll up mid-stream; verify auto-scroll does not override |
|
|
||||||
| No cancel button | Phase building streaming UI | Test: start stream, navigate away; check backend logs confirm stream terminated |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- [openai/openai-dotnet Issue #65 — Streaming doesn't work properly in Blazor WASM](https://github.com/openai/openai-dotnet/issues/65) — confirmed fix with `BlazorHttpClientTransport`
|
|
||||||
- [Microsoft Q&A — Streaming Issue with Blazor WebAssembly and Semantic Kernel and OpenAI](https://learn.microsoft.com/en-sg/answers/questions/2242618/streaming-issue-with-blazor-webassembly-and-semati)
|
|
||||||
- [DEV Community — Real Blazor WebAssembly Production Pitfalls](https://dev.to/janhjordie/real-blazor-webassembly-production-pitfalls-3hmf) — IL trimming, JS interop, release-only failures
|
|
||||||
- [Chandradev Blog — 10 Blazor Coding Mistakes](https://chandradev819.wordpress.com/2025/12/17/10-blazor-coding-mistakes-i-see-in-real-projects-and-how-to-avoid-them/) — logic in components, DI misuse, naming
|
|
||||||
- [Thinktecture — Dependency Injection Scopes in Blazor](https://www.thinktecture.com/en/blazor/dependency-injection-scopes-in-blazor/) — Scoped = Singleton in WASM
|
|
||||||
- [ASP.NET Core Blazor DI docs](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection) — official lifetime guidance
|
|
||||||
- [ASP.NET Core Blazor rendering performance best practices](https://learn.microsoft.com/en-us/aspnet/core/blazor/performance/rendering) — StateHasChanged, re-render control
|
|
||||||
- [dotnet/aspnetcore Issue #43098 — StateHasChanged not firing with IAsyncEnumerable](https://github.com/dotnet/aspnetcore/issues/43098)
|
|
||||||
- [Microsoft — Secure ASP.NET Core Blazor WebAssembly](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/) — API key security, no secrets in WASM
|
|
||||||
- [DEV Community — The Missing Third Config Layer: User Secrets in Blazor WASM](https://dev.to/j_sakamoto/the-missing-third-config-layer-adding-user-secrets-to-blazor-webassembly-2a5a) — confirms user secrets are NOT secret in WASM
|
|
||||||
- [Microsoft — Blazor WebAssembly file access Q&A](https://learn.microsoft.com/en-us/answers/questions/1290337/blazor-webassembly-get-file-access) — browser sandbox, no local disk
|
|
||||||
- [tpeczek.com — ASP.NET Core 9 and IAsyncEnumerable — Async Streaming from Blazor WASM](https://www.tpeczek.com/2024/09/aspnet-core-9-and-iasyncenumerable.html) — correct streaming patterns
|
|
||||||
- [dotnet/aspnetcore Issue #55982 — network error with IAsyncEnumerable streaming in WASM](https://github.com/dotnet/aspnetcore/issues/55982)
|
|
||||||
|
|
||||||
---
|
|
||||||
*Pitfalls research for: Blazor WebAssembly AI Chat Application*
|
|
||||||
*Researched: 2026-03-27*
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
# Stack Research
|
|
||||||
|
|
||||||
**Domain:** Blazor WebAssembly AI Chat Application (.NET / C#)
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Confidence:** HIGH (core stack verified via NuGet and official Microsoft docs; version numbers confirmed via nuget.org)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommended Stack
|
|
||||||
|
|
||||||
### Core Technologies
|
|
||||||
|
|
||||||
| Technology | Version | Purpose | Why Recommended |
|
|
||||||
|------------|---------|---------|-----------------|
|
|
||||||
| .NET 9 SDK | 9.x (latest patch) | Runtime, tooling, SDK | LTS-adjacent, stable, .NET 10 is in preview — stay on 9 for a tutorial project targeting a stable foundation |
|
|
||||||
| Blazor WebAssembly Standalone | .NET 9 | Client SPA running in-browser | Non-negotiable per project constraints; client-side execution with no server round-trip for UI |
|
|
||||||
| ASP.NET Core Web API | .NET 9 | Backend proxy for OpenAI calls | Required to keep the OpenAI API key server-side; WASM cannot access secrets directly |
|
|
||||||
| C# 13 | Included with .NET 9 | Application language | Included in .NET 9 SDK; no separate install needed |
|
|
||||||
|
|
||||||
**Critical architecture note:** The "hosted Blazor WebAssembly" template (single `.sln` with Client + Server + Shared projects) was removed in .NET 8. In .NET 9, you create two separate projects manually: a `dotnet new blazorwasm` standalone client and a `dotnet new webapi` backend, then add them to a solution. This is the correct approach for this project.
|
|
||||||
|
|
||||||
### OpenAI Integration
|
|
||||||
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `OpenAI` (official) | 2.9.1 | OpenAI API client with streaming | The official OpenAI-published .NET library; supports `CompleteChatStreamingAsync()` returning `AsyncCollectionResult<StreamingChatCompletionUpdate>` via `await foreach`; stable release as of 2026-03-02 |
|
|
||||||
|
|
||||||
**Do not use** `OpenAI-DotNet` (version 8.8.8) — this is an unofficial community package with a different API surface. The official `OpenAI` package is published directly by OpenAI and is the correct choice.
|
|
||||||
|
|
||||||
**Streaming mechanism:** The backend Web API endpoint calls `CompleteChatStreamingAsync()` and proxies chunks to the client. The WASM client uses `HttpCompletionOption.ResponseHeadersRead` with `SetBrowserResponseStreamingEnabled(true)` on the `HttpRequestMessage` to consume the streamed response. In .NET 10 streaming is enabled by default; in .NET 9 it must be explicitly opted in per-request.
|
|
||||||
|
|
||||||
### Markdown Rendering
|
|
||||||
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `Markdig` | 1.1.1 | Parse markdown text to HTML | The de facto standard markdown processor for .NET; CommonMark-compliant, fast, extensible, targets .NET Standard 2.0 so works in WASM; used by Microsoft and Syncfusion as the underlying engine |
|
|
||||||
|
|
||||||
**How it integrates in Blazor:** Call `Markdig.Markdown.ToHtml(content)` on the client, render the result with `@((MarkupString)htmlContent)` in a Razor component. No JS interop needed.
|
|
||||||
|
|
||||||
### UI Component Library
|
|
||||||
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `MudBlazor` | 9.2.0 | Material Design component library | Full .NET 9 support confirmed; pure C# with minimal JavaScript; comprehensive chat-friendly components (MudTextField, MudPaper, MudScrollToBottom, MudList); large community; no per-seat licensing |
|
|
||||||
|
|
||||||
**Alternative considered:** Radzen Blazor (free, good) and Telerik UI for Blazor (licensed). MudBlazor wins for a tutorial/personal project because it is free, has zero JS dependencies, and has excellent documentation for learners.
|
|
||||||
|
|
||||||
### JSON Storage (Server-side)
|
|
||||||
|
|
||||||
| Technology | Version | Purpose | Why Recommended |
|
|
||||||
|------------|---------|---------|-----------------|
|
|
||||||
| `System.Text.Json` | Built into .NET 9 | Serialize/deserialize conversation history | Built-in, no extra dependency; `JsonSerializerOptions` with `WriteIndented = true` for human-readable files; async file I/O via `File.ReadAllTextAsync` / `File.WriteAllTextAsync` |
|
|
||||||
|
|
||||||
Storage lives entirely on the **backend** (Web API project). The WASM client cannot access the local filesystem — only the server can. API endpoints expose CRUD operations over conversations, with JSON files persisted in a configurable directory on the server host.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supporting Libraries
|
|
||||||
|
|
||||||
| Library | Version | Purpose | When to Use |
|
|
||||||
|---------|---------|---------|-------------|
|
|
||||||
| `Microsoft.Extensions.AI` (abstractions) | 9.x preview | Optional AI abstraction layer | Skip for v1 — adds indirection before the core chat pattern is understood. Relevant for v2 when adding multi-provider support |
|
|
||||||
| `Blazored.LocalStorage` | latest | Browser local storage | Not needed for this project — persistence is on the server via JSON files, not the browser |
|
|
||||||
| `System.Net.ServerSentEvents` | Built into .NET 9 | SSE parser for streaming | Used automatically by the `OpenAI` library on the server; no direct usage needed |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Tools
|
|
||||||
|
|
||||||
| Tool | Purpose | Notes |
|
|
||||||
|------|---------|-------|
|
|
||||||
| Visual Studio 2022 (v17.12+) | IDE with Blazor hot reload | Recommended for tutorial builder; full Blazor debugging, component preview, and hot reload support |
|
|
||||||
| VS Code + C# Dev Kit | Lighter-weight alternative | Works well; use `dotnet watch` for hot reload |
|
|
||||||
| `dotnet watch run` | Hot reload during development | Run in both Client and Server project directories simultaneously |
|
|
||||||
| `dotnet-dev-certs` | HTTPS dev certificate | Required for local HTTPS; run `dotnet dev-certs https --trust` once |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create solution
|
|
||||||
mkdir ChatAgentApp && cd ChatAgentApp
|
|
||||||
dotnet new sln -n ChatAgentApp
|
|
||||||
|
|
||||||
# Create Blazor WASM client (standalone)
|
|
||||||
dotnet new blazorwasm -n ChatAgentApp.Client --framework net9.0
|
|
||||||
dotnet sln add ChatAgentApp.Client/ChatAgentApp.Client.csproj
|
|
||||||
|
|
||||||
# Create ASP.NET Core Web API backend
|
|
||||||
dotnet new webapi -n ChatAgentApp.Api --framework net9.0
|
|
||||||
dotnet sln add ChatAgentApp.Api/ChatAgentApp.Api.csproj
|
|
||||||
|
|
||||||
# Install OpenAI SDK in the API project
|
|
||||||
cd ChatAgentApp.Api
|
|
||||||
dotnet add package OpenAI --version 2.9.1
|
|
||||||
|
|
||||||
# Install Markdig in the Client project
|
|
||||||
cd ../ChatAgentApp.Client
|
|
||||||
dotnet add package Markdig --version 1.1.1
|
|
||||||
dotnet add package MudBlazor --version 9.2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Alternatives Considered
|
|
||||||
|
|
||||||
| Recommended | Alternative | When to Use Alternative |
|
|
||||||
|-------------|-------------|-------------------------|
|
|
||||||
| `OpenAI` 2.9.1 (official) | `OpenAI-DotNet` 8.8.8 (unofficial) | Never — the official package is now stable and maintained by OpenAI directly |
|
|
||||||
| `OpenAI` 2.9.1 (official) | `Azure.AI.OpenAI` 2.1.0 | When targeting Azure OpenAI Service specifically (e.g., enterprise, EU data residency, private endpoints) — overkill for this project |
|
|
||||||
| `Markdig` | `CommonMark.NET` | Only if strict CommonMark compliance matters more than extensions; Markdig is a superset and the ecosystem standard |
|
|
||||||
| `MudBlazor` | Radzen Blazor | Radzen is fine; choose it if you already know it; MudBlazor has more learning resources |
|
|
||||||
| `MudBlazor` | Telerik UI for Blazor | Telerik requires a paid license; not appropriate for a personal tool |
|
|
||||||
| Standalone WASM + separate Web API | Blazor Web App template (unified) | Use the unified Blazor Web App template when you want mixed Server+WASM render modes on a single project; overkill for this project and obscures the WASM-specific patterns the tutorial aims to teach |
|
|
||||||
| JSON flat files (server-side) | SQLite via EF Core | SQLite is a better choice at scale; JSON is simpler for single-user personal tools and avoids introducing a migration workflow |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What NOT to Use
|
|
||||||
|
|
||||||
| Avoid | Why | Use Instead |
|
|
||||||
|-------|-----|-------------|
|
|
||||||
| `OpenAI-DotNet` (unofficial) | Different API surface, not maintained by OpenAI, version numbers create confusion | Official `OpenAI` NuGet package |
|
|
||||||
| `Microsoft.SemanticKernel` | Adds significant abstraction and dependency weight for a tutorial; streaming works but is complex to explain | Direct `OpenAI` SDK calls; add SK in v2 when orchestration is needed |
|
|
||||||
| JavaScript `EventSource` API via JSInterop for streaming | Blazor WASM has `SetBrowserResponseStreamingEnabled` which avoids JS interop; adding JSInterop for streaming increases complexity significantly | `HttpCompletionOption.ResponseHeadersRead` + `SetBrowserResponseStreamingEnabled(true)` in the HTTP handler |
|
|
||||||
| `Newtonsoft.Json` | Unnecessary dependency; `System.Text.Json` is built into .NET 9 and is faster; Newtonsoft was the pre-.NET Core standard | `System.Text.Json` (built-in) |
|
|
||||||
| `Blazored.LocalStorage` for persistence | Browser storage is limited (~5MB), cleared by users, and not suitable for chat history of any meaningful length; also exposes all data client-side | Server-side JSON file storage via the Web API |
|
|
||||||
| AOT compilation during learning phase | Dramatically increases build times; not needed until production optimization is a concern; confusing to introduce in a tutorial | Default IL interpretation; add AOT opt-in note in the final phase |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Stack Patterns by Variant
|
|
||||||
|
|
||||||
**For streaming responses from the API backend to the WASM client:**
|
|
||||||
- Backend streams OpenAI tokens as `text/event-stream` (SSE) or `application/x-ndjson`
|
|
||||||
- Client uses `SetBrowserResponseStreamingEnabled(true)` on `HttpRequestMessage`
|
|
||||||
- Client reads with `HttpCompletionOption.ResponseHeadersRead` and iterates the stream
|
|
||||||
- Trigger `StateHasChanged()` in the component after each token to update the UI
|
|
||||||
|
|
||||||
**For local JSON file storage on the server:**
|
|
||||||
- Define a `ConversationRepository` service on the API that reads/writes from a configurable base path
|
|
||||||
- Register as `Singleton` (not `Scoped`) since there is only one user and file access must be serialized
|
|
||||||
- Use `SemaphoreSlim(1,1)` to prevent concurrent write conflicts even in single-user mode
|
|
||||||
|
|
||||||
**For markdown rendering in the client:**
|
|
||||||
- Use `Markdig.Markdown.ToHtml(text, pipeline)` where `pipeline` is built with `MarkdownPipelineBuilder` enabling extensions (e.g., `UseAutoLinks()`, `UseEmojiAndSmiley()`)
|
|
||||||
- Render the HTML string using `@((MarkupString)html)` inside a `<div class="markdown-body">` element
|
|
||||||
- Apply CSS (GitHub Markdown CSS or custom) scoped to `.markdown-body` for code blocks and tables
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Version Compatibility
|
|
||||||
|
|
||||||
| Package | Compatible With | Notes |
|
|
||||||
|---------|-----------------|-------|
|
|
||||||
| `OpenAI` 2.9.1 | .NET Standard 2.0+ (.NET 9 confirmed) | Published 2026-03-02; requires `System.Net.ServerSentEvents` (built into .NET 9) |
|
|
||||||
| `Markdig` 1.1.1 | .NET 8.0, .NET Standard 2.0, .NET Framework 4.6.2 | .NET 9 compatible via .NET 8 TFM; published 2026-03-04 |
|
|
||||||
| `MudBlazor` 9.2.0 | .NET 8.0, .NET 9.0, .NET 10.0 | Published 2026-03-18; version 9.x = full support for .NET 9 |
|
|
||||||
| .NET 9 SDK | Blazor WASM + Web API in same solution | Both project types target `net9.0`; no cross-framework issues |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- https://www.nuget.org/packages/OpenAI — Official OpenAI NuGet package; version 2.9.1 confirmed (2026-03-02)
|
|
||||||
- https://github.com/openai/openai-dotnet — Official OpenAI .NET SDK; streaming API verified (`CompleteChatStreamingAsync`, `await foreach`)
|
|
||||||
- https://www.nuget.org/packages/Markdig — Markdig version 1.1.1 confirmed (2026-03-04)
|
|
||||||
- https://www.nuget.org/packages/MudBlazor — MudBlazor 9.2.0 confirmed; .NET 8/9/10 full support (2026-03-18)
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-9.0 — Official Blazor hosting model docs; standalone WASM vs Blazor Web App distinction verified
|
|
||||||
- https://learn.microsoft.com/en-us/dotnet/core/compatibility/networking/10.0/default-http-streaming — Breaking change: WASM streaming opt-in (.NET 9) vs default (.NET 10)
|
|
||||||
- https://www.strathweb.com/2024/07/built-in-support-for-server-sent-events-in-net-9/ — SSE native support in .NET 9 via `System.Net.ServerSentEvents`; used internally by OpenAI SDK (MEDIUM confidence, single source)
|
|
||||||
- https://github.com/openai/openai-dotnet/issues/65 — Confirmed streaming issue in Blazor WASM requires `SetBrowserResponseStreamingEnabled(true)` (MEDIUM confidence, GitHub issue thread)
|
|
||||||
- https://devblogs.microsoft.com/dotnet/openai-dotnet-library/ — Official .NET Blog announcement of the OpenAI library
|
|
||||||
- https://dev.to/kazinix/blazor-web-app-webassembly-hosted-in-net8-and-net9-1k6g — Hosted template removal in .NET 8+, manual solution structure (MEDIUM confidence)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Stack research for: Blazor WebAssembly AI Chat Application*
|
|
||||||
*Researched: 2026-03-27*
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
# Project Research Summary
|
|
||||||
|
|
||||||
**Project:** Blazor WebAssembly AI Chat Application
|
|
||||||
**Domain:** Single-user personal AI chat web app (.NET / C# / OpenAI GPT)
|
|
||||||
**Researched:** 2026-03-27
|
|
||||||
**Confidence:** HIGH
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
This is a single-user personal AI chat application built on Blazor WebAssembly with an ASP.NET Core backend. The project has a dual purpose: functioning as a useful personal tool and serving as a tutorial-quality reference implementation for Blazor WASM patterns. The recommended architecture is a strict two-project split — a standalone Blazor WASM client and a separate ASP.NET Core Minimal API server — reflecting a breaking change in .NET 8+ that removed the hosted Blazor WASM template. The client runs entirely in the browser; the server holds secrets, calls OpenAI, and manages disk persistence. This boundary is non-negotiable and must be established before any feature code is written.
|
|
||||||
|
|
||||||
The core technical challenge is streaming. OpenAI's token-by-token streaming requires explicit opt-in in Blazor WASM (`SetBrowserResponseStreamingEnabled(true)`) that the SDK does not set by default — the stream silently falls back to buffered delivery with no error. Combined with the need to call `StateHasChanged()` on every token to update the UI, streaming is the highest-risk implementation step and must be validated early. All other features — conversation management, markdown rendering, copy-to-clipboard — are well-understood patterns with clear .NET implementations.
|
|
||||||
|
|
||||||
The key risk profile is concentrated in Phase 1 (architecture foundation) and the streaming phase. Three "never" mistakes — putting the API key in WASM, writing file I/O in the WASM project, and calling OpenAI directly from WASM — must be locked out architecturally before feature development begins. Once those boundaries are established, the remainder of the v1 feature set follows a clear dependency chain from storage to conversations to streaming to UI polish.
|
|
||||||
|
|
||||||
## Key Findings
|
|
||||||
|
|
||||||
### Recommended Stack
|
|
||||||
|
|
||||||
The stack is .NET 9 throughout: a `blazorwasm` standalone client and a `webapi` backend in a single solution, connected by HTTP and SSE. There are no exotic dependencies — the official `OpenAI` NuGet package (2.9.1, published by OpenAI directly) handles AI calls, `Markdig` (1.1.1) handles markdown-to-HTML conversion, `MudBlazor` (9.2.0) provides Material Design UI components with zero JavaScript dependencies, and `System.Text.Json` (built-in) handles JSON serialization and file storage. All versions are confirmed compatible with .NET 9.
|
|
||||||
|
|
||||||
The most important stack decision is what to exclude: do not use `OpenAI-DotNet` (unofficial community package), `Microsoft.SemanticKernel` (excessive abstraction for v1), `Newtonsoft.Json` (superseded by System.Text.Json), `Blazored.LocalStorage` (wrong persistence layer for this architecture), or JSInterop for streaming (WASM has a native streaming opt-in that avoids it).
|
|
||||||
|
|
||||||
**Core technologies:**
|
|
||||||
- `.NET 9 SDK + C# 13`: Runtime and language — stable, LTS-adjacent, both project types target `net9.0`
|
|
||||||
- `Blazor WebAssembly (standalone)`: Client SPA — non-negotiable per project constraints; runs in-browser with no server round-trip for UI
|
|
||||||
- `ASP.NET Core Minimal API`: Backend proxy — required to keep the OpenAI key server-side and to handle disk I/O that WASM cannot perform
|
|
||||||
- `OpenAI` 2.9.1 (official): AI calls — `CompleteChatStreamingAsync()` with `await foreach`; the only correct .NET SDK choice
|
|
||||||
- `Markdig` 1.1.1: Markdown rendering — de facto .NET standard; CommonMark-compliant; renders via `@((MarkupString)html)` with no JS interop
|
|
||||||
- `MudBlazor` 9.2.0: UI components — pure C#, zero JS dependencies, comprehensive chat-friendly components, free license
|
|
||||||
- `System.Text.Json` (built-in): Persistence — serialize conversations to JSON files on the server; no extra dependency
|
|
||||||
|
|
||||||
### Expected Features
|
|
||||||
|
|
||||||
The feature set is well-defined by comparison to ChatGPT and Claude as reference products. The v1 scope is intentionally constrained to what makes the app genuinely usable, with explicit anti-features documented to prevent scope creep during implementation.
|
|
||||||
|
|
||||||
**Must have (table stakes):**
|
|
||||||
- Send message / receive streaming response — core loop; blocking responses are unacceptable by 2026 standards
|
|
||||||
- Markdown rendering with syntax-highlighted code blocks — GPT always responds with markdown; raw output is unusable
|
|
||||||
- Multiple named conversations with create / switch / delete — without this, the app is a single disposable thread
|
|
||||||
- JSON file persistence across sessions — conversations must survive page refresh to be useful
|
|
||||||
- Auto-scroll to latest message and loading indicator — baseline polish that makes the app feel complete
|
|
||||||
- Copy-to-clipboard on code blocks — high-frequency action for the developer-focused target user
|
|
||||||
- Input disabled during streaming and send-on-Enter — prevents double-submit and matches chat conventions
|
|
||||||
- Tutorial-style inline code comments — the project's defining purpose as a learning resource
|
|
||||||
|
|
||||||
**Should have (competitive, v1.x):**
|
|
||||||
- Auto-generated conversation titles — reduces naming friction; single GPT summarization call
|
|
||||||
- System prompt / persona configuration — power-user feature; natural extension once multi-conversation works
|
|
||||||
- Model selector (GPT-4o vs GPT-4o-mini) — cost/quality tradeoff; low implementation cost
|
|
||||||
- Export conversation to markdown/text — low complexity, occasional high value
|
|
||||||
|
|
||||||
**Defer (v2+):**
|
|
||||||
- Message edit and regenerate — medium complexity; wait until core loop is solid
|
|
||||||
- Token usage display — streaming completion handling required; not blocking
|
|
||||||
- LangChain / agentic workflows, RAG, MCP server integration — explicitly v2 per project intent
|
|
||||||
- Voice input/output, image uploads, multi-user auth, PWA — all documented anti-features with clear rationale
|
|
||||||
|
|
||||||
Feature dependencies are explicit: JSON storage must precede conversation management, which must precede conversation switching. A basic blocking API call must precede streaming. Markdown must precede syntax highlighting, which must precede copy-to-clipboard.
|
|
||||||
|
|
||||||
### Architecture Approach
|
|
||||||
|
|
||||||
The architecture is a strict two-tier system: a Blazor WASM SPA in the browser communicating with an ASP.NET Core Minimal API via HTTP and SSE. State on the client is managed by a singleton `ConversationStateService` that raises `OnChange` events — components subscribe in `OnInitialized` and unsubscribe in `Dispose`. There is a `Shared` library project that holds `Conversation` and `ChatMessage` models used by both tiers, eliminating duplicate DTOs.
|
|
||||||
|
|
||||||
Components are kept intentionally thin (data in via `[Parameter]`, actions out via `EventCallback<T>`). All logic lives in services. This is explicitly stated as a tutorial goal — fat components teach bad habits and are hard to explain.
|
|
||||||
|
|
||||||
**Major components:**
|
|
||||||
1. `ConversationStateService` (WASM singleton) — active conversation, message list, streaming flag; raises `OnChange` for all subscribed components
|
|
||||||
2. `ChatApiClient` (WASM scoped service) — wraps `HttpClient`, handles SSE stream reading with `SetBrowserResponseStreamingEnabled(true)` and `ResponseHeadersRead`
|
|
||||||
3. `OpenAiService` (server scoped) — wraps official OpenAI SDK, returns `IAsyncEnumerable<string>` of tokens to endpoint handlers
|
|
||||||
4. `ConversationRepository` (server singleton) — reads/writes JSON files under a configurable data directory; uses `SemaphoreSlim(1,1)` for write serialization
|
|
||||||
5. `ChatEndpoints` + `ConversationEndpoints` (server Minimal API) — thin HTTP layer wiring services to routes; SSE streaming endpoint proxies tokens to client
|
|
||||||
6. Leaf UI components: `MessageBubble`, `ChatInput`, `ConversationList`, `MessageList` — pure display, no service calls
|
|
||||||
7. Container component: `ChatPage` — composes all child components, owns the route (`@page "/chat/{id?}"`)
|
|
||||||
|
|
||||||
**Build order:** Shared models → `ConversationRepository` → `OpenAiService` → server endpoints → `ChatApiClient` → `ConversationStateService` → leaf UI → container UI. This maps directly to implementation phases.
|
|
||||||
|
|
||||||
### Critical Pitfalls
|
|
||||||
|
|
||||||
1. **Streaming silently broken in WASM (Pitfall 1 + 4)** — Two distinct failure modes that appear identical: (a) the OpenAI SDK does not set `SetBrowserResponseStreamingEnabled(true)` so the browser buffers the entire response; (b) `StateHasChanged()` is not called per-token so Blazor batches all renders until the stream completes. Both produce the same symptom — tokens appear all at once. Fix: custom `BlazorHttpClientTransport` on the backend, and explicit `StateHasChanged()` + `await Task.Yield()` inside the `await foreach` token loop. Throttle to ~50ms intervals to prevent UI thread starvation at GPT-4o token speeds.
|
|
||||||
|
|
||||||
2. **API key exposure in WASM (Pitfall 2)** — `wwwroot/appsettings.json` is a static file served to any browser visitor. `dotnet user-secrets` in WASM projects are embedded in the published bundle in plaintext. The key must live exclusively in the server project, accessed via server-side `user-secrets` or environment variables. This boundary must be established in Phase 1 and never crossed.
|
|
||||||
|
|
||||||
3. **File I/O in WASM project (Pitfall 5)** — `System.IO.File` compiles in WASM but writes to an in-memory virtual filesystem that resets on every page refresh. All persistence must go through backend API endpoints. Reinforce the same architectural boundary as the API key rule.
|
|
||||||
|
|
||||||
4. **Scoped DI = Singleton in WASM (Pitfall 3)** — In Blazor WASM there is exactly one DI scope for the tab lifetime. A service registered as `Scoped` never resets. Design `ConversationStateService` to hold a collection keyed by conversation ID, not mutable "current conversation" fields.
|
|
||||||
|
|
||||||
5. **IL trimming breaks Release builds (Pitfall 6)** — Debug builds do not trim; published builds do. JSON serialization properties, DI-resolved types, and JSInterop callbacks can be silently stripped. Use `[JsonSerializable]` source generators on all model types and run `dotnet publish` once in Phase 1 to catch trim warnings while the surface is small.
|
|
||||||
|
|
||||||
## Implications for Roadmap
|
|
||||||
|
|
||||||
Based on combined research, the architecture dependency chain and pitfall prevention requirements suggest five phases:
|
|
||||||
|
|
||||||
### Phase 1: Architecture Foundation
|
|
||||||
**Rationale:** Three critical "never" mistakes (API key in WASM, file I/O in WASM, direct OpenAI call from WASM) must be architecturally locked before any feature code is written. The WASM/backend split is the load-bearing constraint everything else depends on. This phase also establishes the `Shared` models library which both tiers need immediately.
|
|
||||||
**Delivers:** Working solution structure with two projects + shared library; CORS configured; basic HTTP connectivity verified WASM-to-server; `dotnet publish` tested once to catch IL trim warnings early; placeholder endpoints in place; no OpenAI calls yet.
|
|
||||||
**Addresses:** Project scaffolding, solution structure (FEATURES.md scaffolding prerequisite)
|
|
||||||
**Avoids:** API key exposure (Pitfall 2), file I/O in WASM (Pitfall 5), direct OpenAI calls from WASM (Architecture anti-pattern 1), IL trimming surprises (Pitfall 6)
|
|
||||||
|
|
||||||
### Phase 2: Conversation Storage and Management
|
|
||||||
**Rationale:** JSON file storage is the prerequisite for every conversation-related feature. Per the feature dependency graph: `[JSON File Storage] → [Multiple Conversations] → [Create/Switch/Delete/Persist]`. This phase must come before any AI integration because the persistence layer needs to exist before we can store AI responses.
|
|
||||||
**Delivers:** `ConversationRepository` with full CRUD, `ConversationEndpoints` wired to HTTP routes, `ConversationList` sidebar component, create/switch/delete conversations working, conversation history persisted to disk and loaded on startup. The app has no AI yet but has a working conversation management UI.
|
|
||||||
**Uses:** `System.Text.Json` built-in, `SemaphoreSlim(1,1)` for write serialization, `MudBlazor` for sidebar components
|
|
||||||
**Implements:** `ConversationRepository`, `ConversationEndpoints`, `ConversationStateService` (initial version), `ConversationList.razor`
|
|
||||||
**Avoids:** Scoped DI state leaks (Pitfall 3) — design `ConversationStateService` with `Dictionary<Guid, ConversationState>` from the start
|
|
||||||
|
|
||||||
### Phase 3: Basic AI Chat (Non-Streaming)
|
|
||||||
**Rationale:** Per the feature dependency chain, a working blocking API call must be established before streaming is layered on top. Building non-streaming first validates the full request/response shape, CORS, error handling, and conversation history construction without the added complexity of SSE. This is the correct learning sequence for a tutorial project.
|
|
||||||
**Delivers:** Full chat loop working end-to-end: user sends message → backend calls OpenAI → response appended to conversation → conversation saved to disk. All without streaming. Markdown rendering added here because GPT responses with raw markdown are effectively unusable and would make all testing painful.
|
|
||||||
**Uses:** `OpenAI` 2.9.1 SDK, `Markdig` 1.1.1, `MudBlazor` chat components
|
|
||||||
**Implements:** `OpenAiService`, `ChatEndpoints` (non-streaming POST), `ChatApiClient` (basic POST), `MessageBubble.razor` with `@((MarkupString)html)` rendering, `ChatInput.razor`
|
|
||||||
**Avoids:** Markdown XSS via raw `MarkupString` (PITFALLS integration gotchas) — sanitize or accept risk explicitly in code comments
|
|
||||||
|
|
||||||
### Phase 4: Streaming Responses
|
|
||||||
**Rationale:** Streaming is the highest-risk implementation step. Research identified two independent failure modes (transport not set, `StateHasChanged` not called) that produce identical symptoms. Addressing this in its own phase means streaming can be diagnosed and debugged in isolation, without other variables. All streaming-specific patterns — `BlazorHttpClientTransport`, SSE endpoint, `ResponseHeadersRead`, per-token `StateHasChanged` with throttling — are introduced and documented here.
|
|
||||||
**Delivers:** Token-by-token streaming from OpenAI through the backend SSE endpoint to the WASM UI. Loading indicator shown immediately on send, hidden on first token. Auto-scroll to latest message. Input disabled during streaming. Cancel button wired to `CancellationToken`. Stream throttling (~50ms) to prevent UI thread starvation.
|
|
||||||
**Uses:** `SetBrowserResponseStreamingEnabled(true)`, `HttpCompletionOption.ResponseHeadersRead`, `text/event-stream` SSE frames, `await Task.Yield()` in token loop
|
|
||||||
**Implements:** Streaming `ChatEndpoints`, updated `ChatApiClient` with stream reader, updated `MessageList` and `ChatPage` with streaming state
|
|
||||||
**Avoids:** Streaming silently broken (Pitfall 1), UI freeze without `StateHasChanged` (Pitfall 4), UI thread starvation from unthrottled renders (PITFALLS performance traps)
|
|
||||||
|
|
||||||
### Phase 5: Polish and v1.x Features
|
|
||||||
**Rationale:** Once the core loop (storage + AI + streaming) is solid, the remaining v1.x features are all low-to-medium complexity additions that build on the established foundation. Grouping them together allows the tutorial narrative to focus on "extending a working app" rather than "getting the basics right."
|
|
||||||
**Delivers:** Auto-generated conversation titles (GPT summarization call after first exchange), syntax-highlighted code blocks (`Markdown.ColorCode` Markdig pipeline extension), copy-to-clipboard on code blocks (JS interop via `navigator.clipboard.writeText`), responsive layout for mobile, error handling with user-visible messages, model selector dropdown (GPT-4o vs GPT-4o-mini). Optional v1.x additions: system prompt configuration, export conversation.
|
|
||||||
**Uses:** `Markdown.ColorCode` NuGet package (base package, NOT `CSharpToColoredHtml` which breaks WASM), `navigator.clipboard` JS interop
|
|
||||||
**Implements:** Updated `MarkdownPipeline` with ColorCode extension, `ClipboardService.cs` JS interop wrapper, settings model for model selection
|
|
||||||
|
|
||||||
### Phase Ordering Rationale
|
|
||||||
|
|
||||||
- **Architecture before features** prevents the three hardest-to-recover-from mistakes (API key exposure, WASM file I/O, wrong project boundaries) from being baked in.
|
|
||||||
- **Storage before AI** follows the feature dependency graph exactly: conversations need a home before AI responses can be stored in them.
|
|
||||||
- **Non-streaming before streaming** validates the full request/response shape with simpler code, making streaming easier to debug when it is introduced.
|
|
||||||
- **Streaming as its own phase** isolates the highest-risk technical challenge. Combined with the tutorial purpose, this also makes for a clear "here is how streaming actually works in Blazor WASM" chapter.
|
|
||||||
- **Polish last** respects the single-responsibility of each phase and avoids complexity interleaving.
|
|
||||||
|
|
||||||
### Research Flags
|
|
||||||
|
|
||||||
Phases likely needing deeper research during planning (i.e., run `/gsd:research-phase`):
|
|
||||||
- **Phase 4 (Streaming):** The `BlazorHttpClientTransport` workaround and SSE frame format have multiple interacting constraints. Phase planning should re-verify the current state of `openai-dotnet` issue #65 and confirm whether .NET 9.x patch releases have changed the default behavior. Token throttling strategy (timer vs counter) also warrants a concrete recommendation.
|
|
||||||
- **Phase 5 (Markdown.ColorCode + JS Interop):** The WASM compatibility note (base `Markdown.ColorCode` works; `CSharpToColoredHtml` does not) was sourced from community reports. Verify against the current NuGet package version before implementing.
|
|
||||||
|
|
||||||
Phases with standard patterns (skip research-phase):
|
|
||||||
- **Phase 1 (Architecture Foundation):** The two-project solution structure and CORS setup are fully documented in official Microsoft docs. No novel patterns.
|
|
||||||
- **Phase 2 (Conversation Storage):** Repository pattern with JSON file I/O is a standard .NET pattern. `SemaphoreSlim` for single-writer serialization is well-documented.
|
|
||||||
- **Phase 3 (Basic AI Chat):** OpenAI SDK usage for non-streaming chat completions is documented in the official SDK repo with examples. Markdig integration in Blazor has multiple tutorial references.
|
|
||||||
|
|
||||||
## Confidence Assessment
|
|
||||||
|
|
||||||
| Area | Confidence | Notes |
|
|
||||||
|------|------------|-------|
|
|
||||||
| Stack | HIGH | All package versions verified on nuget.org; official SDK confirmed by OpenAI .NET Blog post; version compatibility table verified against published TFM support |
|
|
||||||
| Features | HIGH | Feature set cross-referenced against live ChatGPT and Claude UX; OpenAI streaming API docs consulted; Blazor-specific constraints verified |
|
|
||||||
| Architecture | HIGH | Microsoft official Blazor docs + verified community implementations (PalmHill.BlazorChat reference); all patterns confirmed with working code samples |
|
|
||||||
| Pitfalls | HIGH | Critical pitfalls sourced from official GitHub issue tracker (`openai-dotnet` #65, `aspnetcore` #43098), Microsoft Q&A, and documented production experience |
|
|
||||||
|
|
||||||
**Overall confidence:** HIGH
|
|
||||||
|
|
||||||
### Gaps to Address
|
|
||||||
|
|
||||||
- **Streaming transport behavior in .NET 9 patch releases:** The `SetBrowserResponseStreamingEnabled(true)` workaround is confirmed required in .NET 9 and becomes default in .NET 10. There is a possibility a .NET 9.x patch release may have changed this behavior. Verify at the start of Phase 4 by checking the official .NET 9 breaking change notes.
|
|
||||||
- **StateHasChanged throttling threshold:** Research recommends ~50ms or every N tokens, but the optimal value depends on GPT-4o's actual token delivery rate and the target device's rendering performance. Treat as a tunable constant in code rather than a magic number.
|
|
||||||
- **XSS risk of rendering GPT output as MarkupString:** This is a known accepted risk for a single-user personal tool. Document the decision explicitly in the code (tutorial purpose) rather than leaving it as a silent assumption. Consider adding `Markdig`'s `DisableHtml()` pipeline option as a low-friction mitigation.
|
|
||||||
- **CORS configuration for deployment:** Research covered localhost development CORS. If the app is ever deployed (even to a home server), the CORS origin list needs updating. Document this as a deployment note in Phase 1.
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
### Primary (HIGH confidence)
|
|
||||||
- https://www.nuget.org/packages/OpenAI — OpenAI 2.9.1 version and publish date confirmed
|
|
||||||
- https://github.com/openai/openai-dotnet — Streaming API (`CompleteChatStreamingAsync`, `await foreach`) verified
|
|
||||||
- https://www.nuget.org/packages/Markdig — Markdig 1.1.1 confirmed; .NET 8 TFM confirmed .NET 9 compatible
|
|
||||||
- https://www.nuget.org/packages/MudBlazor — MudBlazor 9.2.0 confirmed; .NET 8/9/10 support listed
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-9.0 — Standalone WASM vs Blazor Web App distinction; hosted template removal confirmed
|
|
||||||
- https://learn.microsoft.com/en-us/dotnet/core/compatibility/networking/10.0/default-http-streaming — WASM streaming opt-in (.NET 9) vs default (.NET 10) breaking change
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-10.0 — HttpClient streaming patterns for Blazor
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection — Official DI lifetime guidance; Scoped = Singleton in WASM
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/ — API key security; no secrets in WASM bundle
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/performance/rendering — StateHasChanged and re-render control
|
|
||||||
- https://devblogs.microsoft.com/dotnet/openai-dotnet-library/ — Official .NET Blog announcement of the OpenAI SDK
|
|
||||||
|
|
||||||
### Secondary (MEDIUM confidence)
|
|
||||||
- https://github.com/openai/openai-dotnet/issues/65 — Confirmed `SetBrowserResponseStreamingEnabled(true)` fix with `BlazorHttpClientTransport`; community-verified workaround
|
|
||||||
- https://www.meziantou.net/streaming-an-http-response-in-blazor-webassembly.htm — WASM streaming HttpClient patterns
|
|
||||||
- https://www.thinktecture.com/en/blazor/dependency-injection-scopes-in-blazor/ — Scoped = Singleton in WASM; verified against official docs
|
|
||||||
- https://www.strathweb.com/2024/07/built-in-support-for-server-sent-events-in-net-9/ — SSE native support in .NET 9
|
|
||||||
- https://github.com/edgett/PalmHill.BlazorChat — Reference implementation; WASM + WebAPI + real-time LLM
|
|
||||||
- https://dev.to/janhjordie/real-blazor-webassembly-production-pitfalls-3hmf — IL trimming, JS interop, release-only failures
|
|
||||||
- https://github.com/dotnet/aspnetcore/issues/43098 — StateHasChanged not firing with IAsyncEnumerable
|
|
||||||
|
|
||||||
### Tertiary (MEDIUM-LOW confidence, validate before use)
|
|
||||||
- https://dev.to/kazinix/blazor-web-app-webassembly-hosted-in-net8-and-net9-1k6g — Hosted template removal in .NET 8+ (single community source; cross-checked against official docs)
|
|
||||||
- https://chandradev819.wordpress.com/2025/12/17/10-blazor-coding-mistakes-i-see-in-real-projects-and-how-to-avoid-them/ — Fat component patterns, DI misuse
|
|
||||||
- https://www.nuget.org/packages/Markdown.ColorCode — WASM base package compatibility note (community-reported; verify during Phase 5 implementation)
|
|
||||||
|
|
||||||
---
|
|
||||||
*Research completed: 2026-03-27*
|
|
||||||
*Ready for roadmap: yes*
|
|
||||||
146
CLAUDE.md
146
CLAUDE.md
@@ -1,147 +1,5 @@
|
|||||||
<!-- GSD:project-start source:PROJECT.md -->
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
**Chat Agent WebApp**
|
Chat Agent WebApp — a personal AI chat app built with Blazor WebAssembly and the OpenAI GPT API that doubles as an incremental Blazor tutorial.
|
||||||
|
|
||||||
A personal AI chat web application built with Blazor WebAssembly and the OpenAI GPT API. Users send messages, receive streaming AI responses rendered as markdown, and manage multiple persistent conversations. The project doubles as an incremental learning journey — each phase introduces one concept with well-documented, explained code, making it suitable as a Blazor tutorial for a developer experienced in C# but new to the framework.
|
For full project details, constraints, and technology stack, see `openspec/specs/`.
|
||||||
|
|
||||||
**Core Value:** A working, well-understood AI chat interface — every line of code is intentional and explained, so the builder learns Blazor patterns while shipping a real product.
|
|
||||||
|
|
||||||
### Constraints
|
|
||||||
|
|
||||||
- **Tech stack**: .NET / C# / Blazor WebAssembly — non-negotiable
|
|
||||||
- **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
|
|
||||||
<!-- GSD:project-end -->
|
|
||||||
|
|
||||||
<!-- GSD:stack-start source:research/STACK.md -->
|
|
||||||
## Technology Stack
|
|
||||||
|
|
||||||
## Recommended Stack
|
|
||||||
### Core Technologies
|
|
||||||
| Technology | Version | Purpose | Why Recommended |
|
|
||||||
|------------|---------|---------|-----------------|
|
|
||||||
| .NET 9 SDK | 9.x (latest patch) | Runtime, tooling, SDK | LTS-adjacent, stable, .NET 10 is in preview — stay on 9 for a tutorial project targeting a stable foundation |
|
|
||||||
| Blazor WebAssembly Standalone | .NET 9 | Client SPA running in-browser | Non-negotiable per project constraints; client-side execution with no server round-trip for UI |
|
|
||||||
| ASP.NET Core Web API | .NET 9 | Backend proxy for OpenAI calls | Required to keep the OpenAI API key server-side; WASM cannot access secrets directly |
|
|
||||||
| C# 13 | Included with .NET 9 | Application language | Included in .NET 9 SDK; no separate install needed |
|
|
||||||
### OpenAI Integration
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `OpenAI` (official) | 2.9.1 | OpenAI API client with streaming | The official OpenAI-published .NET library; supports `CompleteChatStreamingAsync()` returning `AsyncCollectionResult<StreamingChatCompletionUpdate>` via `await foreach`; stable release as of 2026-03-02 |
|
|
||||||
### Markdown Rendering
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `Markdig` | 1.1.1 | Parse markdown text to HTML | The de facto standard markdown processor for .NET; CommonMark-compliant, fast, extensible, targets .NET Standard 2.0 so works in WASM; used by Microsoft and Syncfusion as the underlying engine |
|
|
||||||
### UI Component Library
|
|
||||||
| Library | Version | Purpose | Why Recommended |
|
|
||||||
|---------|---------|---------|-----------------|
|
|
||||||
| `MudBlazor` | 9.2.0 | Material Design component library | Full .NET 9 support confirmed; pure C# with minimal JavaScript; comprehensive chat-friendly components (MudTextField, MudPaper, MudScrollToBottom, MudList); large community; no per-seat licensing |
|
|
||||||
### JSON Storage (Server-side)
|
|
||||||
| Technology | Version | Purpose | Why Recommended |
|
|
||||||
|------------|---------|---------|-----------------|
|
|
||||||
| `System.Text.Json` | Built into .NET 9 | Serialize/deserialize conversation history | Built-in, no extra dependency; `JsonSerializerOptions` with `WriteIndented = true` for human-readable files; async file I/O via `File.ReadAllTextAsync` / `File.WriteAllTextAsync` |
|
|
||||||
## Supporting Libraries
|
|
||||||
| Library | Version | Purpose | When to Use |
|
|
||||||
|---------|---------|---------|-------------|
|
|
||||||
| `Microsoft.Extensions.AI` (abstractions) | 9.x preview | Optional AI abstraction layer | Skip for v1 — adds indirection before the core chat pattern is understood. Relevant for v2 when adding multi-provider support |
|
|
||||||
| `Blazored.LocalStorage` | latest | Browser local storage | Not needed for this project — persistence is on the server via JSON files, not the browser |
|
|
||||||
| `System.Net.ServerSentEvents` | Built into .NET 9 | SSE parser for streaming | Used automatically by the `OpenAI` library on the server; no direct usage needed |
|
|
||||||
## Development Tools
|
|
||||||
| Tool | Purpose | Notes |
|
|
||||||
|------|---------|-------|
|
|
||||||
| Visual Studio 2022 (v17.12+) | IDE with Blazor hot reload | Recommended for tutorial builder; full Blazor debugging, component preview, and hot reload support |
|
|
||||||
| VS Code + C# Dev Kit | Lighter-weight alternative | Works well; use `dotnet watch` for hot reload |
|
|
||||||
| `dotnet watch run` | Hot reload during development | Run in both Client and Server project directories simultaneously |
|
|
||||||
| `dotnet-dev-certs` | HTTPS dev certificate | Required for local HTTPS; run `dotnet dev-certs https --trust` once |
|
|
||||||
## Installation
|
|
||||||
# Create solution
|
|
||||||
# Create Blazor WASM client (standalone)
|
|
||||||
# Create ASP.NET Core Web API backend
|
|
||||||
# Install OpenAI SDK in the API project
|
|
||||||
# Install Markdig in the Client project
|
|
||||||
## Alternatives Considered
|
|
||||||
| Recommended | Alternative | When to Use Alternative |
|
|
||||||
|-------------|-------------|-------------------------|
|
|
||||||
| `OpenAI` 2.9.1 (official) | `OpenAI-DotNet` 8.8.8 (unofficial) | Never — the official package is now stable and maintained by OpenAI directly |
|
|
||||||
| `OpenAI` 2.9.1 (official) | `Azure.AI.OpenAI` 2.1.0 | When targeting Azure OpenAI Service specifically (e.g., enterprise, EU data residency, private endpoints) — overkill for this project |
|
|
||||||
| `Markdig` | `CommonMark.NET` | Only if strict CommonMark compliance matters more than extensions; Markdig is a superset and the ecosystem standard |
|
|
||||||
| `MudBlazor` | Radzen Blazor | Radzen is fine; choose it if you already know it; MudBlazor has more learning resources |
|
|
||||||
| `MudBlazor` | Telerik UI for Blazor | Telerik requires a paid license; not appropriate for a personal tool |
|
|
||||||
| Standalone WASM + separate Web API | Blazor Web App template (unified) | Use the unified Blazor Web App template when you want mixed Server+WASM render modes on a single project; overkill for this project and obscures the WASM-specific patterns the tutorial aims to teach |
|
|
||||||
| JSON flat files (server-side) | SQLite via EF Core | SQLite is a better choice at scale; JSON is simpler for single-user personal tools and avoids introducing a migration workflow |
|
|
||||||
## What NOT to Use
|
|
||||||
| Avoid | Why | Use Instead |
|
|
||||||
|-------|-----|-------------|
|
|
||||||
| `OpenAI-DotNet` (unofficial) | Different API surface, not maintained by OpenAI, version numbers create confusion | Official `OpenAI` NuGet package |
|
|
||||||
| `Microsoft.SemanticKernel` | Adds significant abstraction and dependency weight for a tutorial; streaming works but is complex to explain | Direct `OpenAI` SDK calls; add SK in v2 when orchestration is needed |
|
|
||||||
| JavaScript `EventSource` API via JSInterop for streaming | Blazor WASM has `SetBrowserResponseStreamingEnabled` which avoids JS interop; adding JSInterop for streaming increases complexity significantly | `HttpCompletionOption.ResponseHeadersRead` + `SetBrowserResponseStreamingEnabled(true)` in the HTTP handler |
|
|
||||||
| `Newtonsoft.Json` | Unnecessary dependency; `System.Text.Json` is built into .NET 9 and is faster; Newtonsoft was the pre-.NET Core standard | `System.Text.Json` (built-in) |
|
|
||||||
| `Blazored.LocalStorage` for persistence | Browser storage is limited (~5MB), cleared by users, and not suitable for chat history of any meaningful length; also exposes all data client-side | Server-side JSON file storage via the Web API |
|
|
||||||
| AOT compilation during learning phase | Dramatically increases build times; not needed until production optimization is a concern; confusing to introduce in a tutorial | Default IL interpretation; add AOT opt-in note in the final phase |
|
|
||||||
## Stack Patterns by Variant
|
|
||||||
- Backend streams OpenAI tokens as `text/event-stream` (SSE) or `application/x-ndjson`
|
|
||||||
- Client uses `SetBrowserResponseStreamingEnabled(true)` on `HttpRequestMessage`
|
|
||||||
- Client reads with `HttpCompletionOption.ResponseHeadersRead` and iterates the stream
|
|
||||||
- Trigger `StateHasChanged()` in the component after each token to update the UI
|
|
||||||
- Define a `ConversationRepository` service on the API that reads/writes from a configurable base path
|
|
||||||
- Register as `Singleton` (not `Scoped`) since there is only one user and file access must be serialized
|
|
||||||
- Use `SemaphoreSlim(1,1)` to prevent concurrent write conflicts even in single-user mode
|
|
||||||
- Use `Markdig.Markdown.ToHtml(text, pipeline)` where `pipeline` is built with `MarkdownPipelineBuilder` enabling extensions (e.g., `UseAutoLinks()`, `UseEmojiAndSmiley()`)
|
|
||||||
- Render the HTML string using `@((MarkupString)html)` inside a `<div class="markdown-body">` element
|
|
||||||
- Apply CSS (GitHub Markdown CSS or custom) scoped to `.markdown-body` for code blocks and tables
|
|
||||||
## Version Compatibility
|
|
||||||
| Package | Compatible With | Notes |
|
|
||||||
|---------|-----------------|-------|
|
|
||||||
| `OpenAI` 2.9.1 | .NET Standard 2.0+ (.NET 9 confirmed) | Published 2026-03-02; requires `System.Net.ServerSentEvents` (built into .NET 9) |
|
|
||||||
| `Markdig` 1.1.1 | .NET 8.0, .NET Standard 2.0, .NET Framework 4.6.2 | .NET 9 compatible via .NET 8 TFM; published 2026-03-04 |
|
|
||||||
| `MudBlazor` 9.2.0 | .NET 8.0, .NET 9.0, .NET 10.0 | Published 2026-03-18; version 9.x = full support for .NET 9 |
|
|
||||||
| .NET 9 SDK | Blazor WASM + Web API in same solution | Both project types target `net9.0`; no cross-framework issues |
|
|
||||||
## Sources
|
|
||||||
- https://www.nuget.org/packages/OpenAI — Official OpenAI NuGet package; version 2.9.1 confirmed (2026-03-02)
|
|
||||||
- https://github.com/openai/openai-dotnet — Official OpenAI .NET SDK; streaming API verified (`CompleteChatStreamingAsync`, `await foreach`)
|
|
||||||
- https://www.nuget.org/packages/Markdig — Markdig version 1.1.1 confirmed (2026-03-04)
|
|
||||||
- https://www.nuget.org/packages/MudBlazor — MudBlazor 9.2.0 confirmed; .NET 8/9/10 full support (2026-03-18)
|
|
||||||
- https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-9.0 — Official Blazor hosting model docs; standalone WASM vs Blazor Web App distinction verified
|
|
||||||
- https://learn.microsoft.com/en-us/dotnet/core/compatibility/networking/10.0/default-http-streaming — Breaking change: WASM streaming opt-in (.NET 9) vs default (.NET 10)
|
|
||||||
- https://www.strathweb.com/2024/07/built-in-support-for-server-sent-events-in-net-9/ — SSE native support in .NET 9 via `System.Net.ServerSentEvents`; used internally by OpenAI SDK (MEDIUM confidence, single source)
|
|
||||||
- https://github.com/openai/openai-dotnet/issues/65 — Confirmed streaming issue in Blazor WASM requires `SetBrowserResponseStreamingEnabled(true)` (MEDIUM confidence, GitHub issue thread)
|
|
||||||
- https://devblogs.microsoft.com/dotnet/openai-dotnet-library/ — Official .NET Blog announcement of the OpenAI library
|
|
||||||
- https://dev.to/kazinix/blazor-web-app-webassembly-hosted-in-net8-and-net9-1k6g — Hosted template removal in .NET 8+, manual solution structure (MEDIUM confidence)
|
|
||||||
<!-- GSD:stack-end -->
|
|
||||||
|
|
||||||
<!-- GSD:conventions-start source:CONVENTIONS.md -->
|
|
||||||
## Conventions
|
|
||||||
|
|
||||||
Conventions not yet established. Will populate as patterns emerge during development.
|
|
||||||
<!-- GSD:conventions-end -->
|
|
||||||
|
|
||||||
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Architecture not yet mapped. Follow existing patterns found in the codebase.
|
|
||||||
<!-- GSD:architecture-end -->
|
|
||||||
|
|
||||||
<!-- GSD:workflow-start source:GSD defaults -->
|
|
||||||
## GSD Workflow Enforcement
|
|
||||||
|
|
||||||
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
|
|
||||||
|
|
||||||
Use these entry points:
|
|
||||||
- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks
|
|
||||||
- `/gsd:debug` for investigation and bug fixing
|
|
||||||
- `/gsd:execute-phase` for planned phase work
|
|
||||||
|
|
||||||
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
|
|
||||||
<!-- GSD:workflow-end -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- GSD:profile-start -->
|
|
||||||
## Developer Profile
|
|
||||||
|
|
||||||
> Profile not yet configured. Run `/gsd:profile-user` to generate your developer profile.
|
|
||||||
> This section is managed by `generate-claude-profile` -- do not edit manually.
|
|
||||||
<!-- GSD:profile-end -->
|
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAgent.Api", "src\ChatAg
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAgent.Shared", "src\ChatAgent.Shared\ChatAgent.Shared.csproj", "{06182E3F-BC78-449B-ADF6-D9EE49E48945}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAgent.Shared", "src\ChatAgent.Shared\ChatAgent.Shared.csproj", "{06182E3F-BC78-449B-ADF6-D9EE49E48945}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAgent.Api.Tests", "tests\ChatAgent.Api.Tests\ChatAgent.Api.Tests.csproj", "{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAgent.Client.Tests", "tests\ChatAgent.Client.Tests\ChatAgent.Client.Tests.csproj", "{DBB73B66-042A-4858-B813-23AEA84FC4C6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -57,6 +63,30 @@ Global
|
|||||||
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x64.Build.0 = Release|Any CPU
|
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x86.ActiveCfg = Release|Any CPU
|
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x86.Build.0 = Release|Any CPU
|
{06182E3F-BC78-449B-ADF6-D9EE49E48945}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -65,5 +95,7 @@ Global
|
|||||||
{600EA0C4-7CDE-4807-BE3C-30A6D2242392} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{600EA0C4-7CDE-4807-BE3C-30A6D2242392} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{467D4550-6F9A-456E-B99C-0ABE94070ECF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{467D4550-6F9A-456E-B99C-0ABE94070ECF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{06182E3F-BC78-449B-ADF6-D9EE49E48945} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{06182E3F-BC78-449B-ADF6-D9EE49E48945} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
{C70B5EEA-073A-4ED0-BA02-C4A92583ECE5} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
|
{DBB73B66-042A-4858-B813-23AEA84FC4C6} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
25
examples/extraction/few-shot/01/input.html
Normal file
25
examples/extraction/few-shot/01/input.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>Subject: RE: AG – Inflation swaps – CVA Request</p>
|
||||||
|
<p>Ovi,</p>
|
||||||
|
<p>Hope you are well.</p>
|
||||||
|
<p>Could you kindly share indicative CVA for the below two inflation swaps, assuming the counterparty is Assured Guaranty UK Limited (formerly Assured Guaranty (Europe) Ltd), which is rated AA- by S&P and A1 by Moody's please?</p>
|
||||||
|
<p>OB 27/11/2025</p>
|
||||||
|
<p><b>Swap 1 – please price standalone</b></p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><th></th><th>CSA</th><th>Murex</th><th>PV (£)</th></tr>
|
||||||
|
<tr><td>Coupon Leg</td><td>BTMU_JPY</td><td>79353083</td><td>4,562,456</td></tr>
|
||||||
|
<tr><td>APD leg</td><td>BTMU_JPY</td><td>79353084</td><td>76,985,170</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>Total PV: 81,547,626</p>
|
||||||
|
<p><b>Swap 2 – please price standalone</b></p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><th></th><th>CSA</th><th>Murex</th><th>PV (£)</th></tr>
|
||||||
|
<tr><td>Coupon Leg</td><td>BTMU_JPY</td><td>79353093</td><td>1,663,261</td></tr>
|
||||||
|
<tr><td>APD leg</td><td>BTMU_JPY</td><td>79353094</td><td>41,333,773</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>Total PV: 42,997,034</p>
|
||||||
|
<p>Many thanks.</p>
|
||||||
|
<p>Kind regards,</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
examples/extraction/few-shot/01/output.json
Normal file
36
examples/extraction/few-shot/01/output.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"valuedate": "27/11/2025",
|
||||||
|
"counterparty": "Assured Guaranty UK Limited (formerly Assured Guaranty (Europe) Ltd)",
|
||||||
|
"trade_id": 79353083,
|
||||||
|
"display_ccy": "GBP",
|
||||||
|
"pv": 4562456,
|
||||||
|
"breakclause": "N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valuedate": "27/11/2025",
|
||||||
|
"counterparty": "Assured Guaranty UK Limited (formerly Assured Guaranty (Europe) Ltd)",
|
||||||
|
"trade_id": 79353084,
|
||||||
|
"display_ccy": "GBP",
|
||||||
|
"pv": 76985170,
|
||||||
|
"breakclause": "N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valuedate": "27/11/2025",
|
||||||
|
"counterparty": "Assured Guaranty UK Limited (formerly Assured Guaranty (Europe) Ltd)",
|
||||||
|
"trade_id": 79353093,
|
||||||
|
"display_ccy": "GBP",
|
||||||
|
"pv": 1663261,
|
||||||
|
"breakclause": "N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valuedate": "27/11/2025",
|
||||||
|
"counterparty": "Assured Guaranty UK Limited (formerly Assured Guaranty (Europe) Ltd)",
|
||||||
|
"trade_id": 79353094,
|
||||||
|
"display_ccy": "GBP",
|
||||||
|
"pv": 41333773,
|
||||||
|
"breakclause": "N"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
examples/extraction/few-shot/02/input.html
Normal file
14
examples/extraction/few-shot/02/input.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>Subject: CVA quote – USD interest rate swap</p>
|
||||||
|
<p>Hi team,</p>
|
||||||
|
<p>Please provide CVA for the following single interest rate swap with Deutsche Bank AG, London Branch.</p>
|
||||||
|
<p>Value date: 15/03/2026</p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><th></th><th>CSA</th><th>Murex</th><th>PV ($)</th></tr>
|
||||||
|
<tr><td>Fixed Leg</td><td>DB_USD</td><td>81200451</td><td>12,750,000</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>Thanks,</p>
|
||||||
|
<p>Sarah</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
examples/extraction/few-shot/02/output.json
Normal file
12
examples/extraction/few-shot/02/output.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"valuedate": "15/03/2026",
|
||||||
|
"counterparty": "Deutsche Bank AG, London Branch",
|
||||||
|
"trade_id": 81200451,
|
||||||
|
"display_ccy": "USD",
|
||||||
|
"pv": 12750000,
|
||||||
|
"breakclause": "N"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
examples/extraction/few-shot/03/input.html
Normal file
15
examples/extraction/few-shot/03/input.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>Subject: RE: CVA – Cross-currency swap with break clause</p>
|
||||||
|
<p>Dear Ovi,</p>
|
||||||
|
<p>Could you provide indicative CVA for the below cross-currency swap with Barclays Bank PLC? Note: this trade has a break clause at the 5-year point.</p>
|
||||||
|
<p>OB 01/06/2025</p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><th></th><th>CSA</th><th>Murex</th><th>PV (€)</th></tr>
|
||||||
|
<tr><td>EUR Leg</td><td>BARC_EUR</td><td>77890112</td><td>8,421,300</td></tr>
|
||||||
|
<tr><td>GBP Leg</td><td>BARC_EUR</td><td>77890113</td><td>22,105,800</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>Thanks,</p>
|
||||||
|
<p>Mark</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
examples/extraction/few-shot/03/output.json
Normal file
20
examples/extraction/few-shot/03/output.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"valuedate": "01/06/2025",
|
||||||
|
"counterparty": "Barclays Bank PLC",
|
||||||
|
"trade_id": 77890112,
|
||||||
|
"display_ccy": "EUR",
|
||||||
|
"pv": 8421300,
|
||||||
|
"breakclause": "Y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valuedate": "01/06/2025",
|
||||||
|
"counterparty": "Barclays Bank PLC",
|
||||||
|
"trade_id": 77890113,
|
||||||
|
"display_ccy": "EUR",
|
||||||
|
"pv": 22105800,
|
||||||
|
"breakclause": "Y"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
examples/extraction/instruction-template.txt
Normal file
39
examples/extraction/instruction-template.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
You are a trade data extraction agent. Your task is to extract structured trade data from sales emails (typically CVA pricing requests) and return the result as JSON.
|
||||||
|
|
||||||
|
## Output Schema
|
||||||
|
|
||||||
|
Return a JSON object with an "items" array. Each item represents one trade leg and has these fields:
|
||||||
|
|
||||||
|
- valuedate (string): The value/observation date in dd/MM/yyyy format. Look for "OB", "Value date", or similar date references in the email.
|
||||||
|
- counterparty (string): The full legal name of the counterparty as stated in the email prose.
|
||||||
|
- trade_id (integer): The Murex trade identifier. Each trade leg has a unique Murex ID.
|
||||||
|
- display_ccy (string): The ISO currency code derived from the email. Map currency symbols: £ → GBP, $ → USD, € → EUR. If stated as an ISO code, use it directly.
|
||||||
|
- pv (number): The present value as a plain number. Remove commas and currency symbols. Do not round.
|
||||||
|
- breakclause (string): "Y" if the email mentions a break clause for the trade, "N" otherwise. Default to "N" if not mentioned.
|
||||||
|
|
||||||
|
The legal_entity field is NOT included in your output. It is populated later via a counterparty lookup tool.
|
||||||
|
|
||||||
|
## Mapping Rules
|
||||||
|
|
||||||
|
1. FLATTEN: Each swap leg with a unique Murex trade ID becomes a separate item. A swap with a Coupon Leg (Murex 123) and an APD leg (Murex 456) produces two items.
|
||||||
|
2. DATE: Parse the value date from context (e.g., "OB 27/11/2025" means valuedate is "27/11/2025"). Always output in dd/MM/yyyy format.
|
||||||
|
3. COUNTERPARTY: Use the full legal name exactly as written in the email, including any parenthetical former names.
|
||||||
|
4. CURRENCY: Derive from the PV column header or context. "PV (£)" means GBP. "PV ($)" means USD. "PV (€)" means EUR.
|
||||||
|
5. PV: Strip formatting (commas, spaces, currency symbols) and output as a plain number.
|
||||||
|
6. BREAKCLAUSE: Default to "N". Only set to "Y" if the email explicitly mentions a break clause for the trade.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Return ONLY valid JSON. Do not include markdown code fences or explanatory text. Example:
|
||||||
|
|
||||||
|
{"items":[{"valuedate":"27/11/2025","counterparty":"Example Corp","trade_id":12345,"display_ccy":"GBP","pv":1234567,"breakclause":"N"}]}
|
||||||
|
|
||||||
|
## After Extraction
|
||||||
|
|
||||||
|
After producing the initial extraction, use the available validation tools:
|
||||||
|
- lookup_counterparty: Look up the counterparty name to find matching legal entities.
|
||||||
|
- validate_trade: Verify each trade ID exists.
|
||||||
|
- validate_currency: Confirm each currency code is valid.
|
||||||
|
- validate_schema: Validate the complete extraction result.
|
||||||
|
|
||||||
|
If a tool returns multiple candidates (e.g., counterparty lookup), present them to the user as a numbered list and ask which one to use. If a tool indicates an error, attempt to fix the extraction or ask the user for clarification.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-03
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
## 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.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-04
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
No test projects exist. The solution has 3 projects (Client, Api, Shared) under `src/`. Tests need to cover the API controllers and the client's ChatApiClient service, both of which involve HTTP and SSE streaming.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Establish test infrastructure (xUnit + Moq)
|
||||||
|
- Test API controllers using WebApplicationFactory (integration-style)
|
||||||
|
- Test ChatApiClient using a mock HttpMessageHandler (unit-style)
|
||||||
|
- All tests runnable via `dotnet test` from solution root
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Blazor component tests (bUnit) — Chat.razor is UI-heavy, defer to a future phase
|
||||||
|
- End-to-end browser tests (Playwright/Selenium)
|
||||||
|
- Testing the upstream Responses API itself
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision 1: Test framework — xUnit + Moq
|
||||||
|
|
||||||
|
xUnit is the .NET standard. Moq for mocking HttpMessageHandler so we can control HTTP responses in tests without hitting real servers.
|
||||||
|
|
||||||
|
### Decision 2: Test project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── ChatAgent.Api.Tests/ → references Api project
|
||||||
|
└── ChatAgent.Client.Tests/ → references Client project + Shared
|
||||||
|
```
|
||||||
|
|
||||||
|
Both added to the `ChatAgent.sln` solution file under a `tests` solution folder.
|
||||||
|
|
||||||
|
### Decision 3: API tests use WebApplicationFactory
|
||||||
|
|
||||||
|
`Microsoft.AspNetCore.Mvc.Testing` provides `WebApplicationFactory<Program>` for integration-style tests. For ChatController, we inject a mock `IHttpClientFactory` that returns a handler with canned SSE responses — no real Responses API needed.
|
||||||
|
|
||||||
|
### Decision 4: Client tests mock HttpMessageHandler
|
||||||
|
|
||||||
|
ChatApiClient takes an HttpClient. Tests create an HttpClient with a custom `DelegatingHandler` that returns canned SSE response streams. This tests the SSE parsing logic in isolation.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [WebApplicationFactory requires Api's Program to be accessible] → Add `InternalsVisibleTo` or use the `public partial class Program {}` trick in Api's Program.cs
|
||||||
|
- [SSE stream mocking is verbose] → Create a small helper method that builds SSE response content from a list of events
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
The project has zero test coverage. There are two controllers, a typed HttpClient service, and shared models — all untested. Adding tests now establishes the pattern before the codebase grows, and catches regressions as new phases are added.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Create an xUnit test project for the API (`ChatAgent.Api.Tests`)
|
||||||
|
- Create an xUnit test project for the Client services (`ChatAgent.Client.Tests`)
|
||||||
|
- Add tests for HealthController (GET /api/health)
|
||||||
|
- Add tests for ChatController (POST /api/chat SSE streaming proxy)
|
||||||
|
- Add tests for ChatApiClient (GetHealthAsync, SendChatStreamingAsync)
|
||||||
|
- Add both test projects to the solution
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `test-infrastructure`: Test project setup, test framework choices, and shared test utilities
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
<!-- None — adding tests doesn't change existing specs -->
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- `tests/ChatAgent.Api.Tests/`: New xUnit test project
|
||||||
|
- `tests/ChatAgent.Client.Tests/`: New xUnit test project
|
||||||
|
- `ChatAgent.sln`: Add test projects
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: API test project exists
|
||||||
|
|
||||||
|
An xUnit test project SHALL exist at `tests/ChatAgent.Api.Tests/` targeting the API project, with xUnit, Moq, and Microsoft.AspNetCore.Mvc.Testing as dependencies.
|
||||||
|
|
||||||
|
#### Scenario: API tests run
|
||||||
|
|
||||||
|
- **WHEN** `dotnet test` is run from the solution root
|
||||||
|
- **THEN** API tests are discovered and executed
|
||||||
|
|
||||||
|
### Requirement: Client test project exists
|
||||||
|
|
||||||
|
An xUnit test project SHALL exist at `tests/ChatAgent.Client.Tests/` targeting the Client services, with xUnit and Moq as dependencies.
|
||||||
|
|
||||||
|
#### Scenario: Client tests run
|
||||||
|
|
||||||
|
- **WHEN** `dotnet test` is run from the solution root
|
||||||
|
- **THEN** Client service tests are discovered and executed
|
||||||
|
|
||||||
|
### Requirement: HealthController test coverage
|
||||||
|
|
||||||
|
Tests SHALL verify that GET /api/health returns HTTP 200 with a valid HealthResponse containing a non-empty Status and a recent Timestamp.
|
||||||
|
|
||||||
|
#### Scenario: Health endpoint returns 200
|
||||||
|
|
||||||
|
- **WHEN** a GET request is sent to /api/health
|
||||||
|
- **THEN** the response status is 200 and the body contains `status: "healthy"` and a UTC timestamp
|
||||||
|
|
||||||
|
### Requirement: ChatController test coverage
|
||||||
|
|
||||||
|
Tests SHALL verify that POST /api/chat returns a streaming SSE response containing text deltas and a [DONE] terminator. Tests SHALL mock the upstream Responses API.
|
||||||
|
|
||||||
|
#### Scenario: Chat streams text deltas
|
||||||
|
|
||||||
|
- **WHEN** a POST is sent to /api/chat with a valid ChatRequest
|
||||||
|
- **THEN** the response is `text/event-stream` containing `data: {"text":"..."}` events followed by `data: [DONE]`
|
||||||
|
|
||||||
|
#### Scenario: Chat handles upstream error
|
||||||
|
|
||||||
|
- **WHEN** the Responses API is unreachable or returns an error
|
||||||
|
- **THEN** the response contains a `data: {"error":"..."}` event followed by `data: [DONE]`
|
||||||
|
|
||||||
|
### Requirement: ChatApiClient test coverage
|
||||||
|
|
||||||
|
Tests SHALL verify that SendChatStreamingAsync correctly parses SSE events from the backend into text deltas, handles [DONE], and throws on error events.
|
||||||
|
|
||||||
|
#### Scenario: Client parses text deltas
|
||||||
|
|
||||||
|
- **WHEN** the backend returns SSE events with text deltas
|
||||||
|
- **THEN** SendChatStreamingAsync yields each text fragment in order
|
||||||
|
|
||||||
|
#### Scenario: Client handles error event
|
||||||
|
|
||||||
|
- **WHEN** the backend returns an error SSE event
|
||||||
|
- **THEN** SendChatStreamingAsync throws HttpRequestException with the error message
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. Test Project Setup
|
||||||
|
|
||||||
|
- [x] 1.1 Create xUnit test project at tests/ChatAgent.Api.Tests with xUnit, Moq, Microsoft.AspNetCore.Mvc.Testing
|
||||||
|
- [x] 1.2 Create xUnit test project at tests/ChatAgent.Client.Tests with xUnit, Moq
|
||||||
|
- [x] 1.3 Add both test projects to ChatAgent.sln under a tests solution folder
|
||||||
|
- [x] 1.4 Make Api's Program class accessible to tests (public partial class Program)
|
||||||
|
|
||||||
|
## 2. API Tests
|
||||||
|
|
||||||
|
- [x] 2.1 Test HealthController: GET /api/health returns 200 with valid HealthResponse
|
||||||
|
- [x] 2.2 Test ChatController: POST /api/chat streams SSE text deltas from mocked upstream
|
||||||
|
- [x] 2.3 Test ChatController: POST /api/chat handles upstream error gracefully
|
||||||
|
|
||||||
|
## 3. Client Service Tests
|
||||||
|
|
||||||
|
- [x] 3.1 Create SSE response helper for building mock SSE streams
|
||||||
|
- [x] 3.2 Test ChatApiClient.SendChatStreamingAsync: parses text deltas in order
|
||||||
|
- [x] 3.3 Test ChatApiClient.SendChatStreamingAsync: throws on error event
|
||||||
|
- [x] 3.4 Test ChatApiClient.GetHealthAsync: returns HealthResponse on success
|
||||||
|
|
||||||
|
## 4. Verify
|
||||||
|
|
||||||
|
- [x] 4.1 Run dotnet test from solution root — all tests pass
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-03
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
## 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`.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-04
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
The chat backend currently proxies requests to a local CLIProxyAPI instance (OpenAI-compatible API at `localhost:8317`) via manual `HttpClient` calls and SSE parsing in `ChatController`. The architecture works for simple chat completion but has no abstraction for tool calling, function invocation, or agentic loops. The goal is to adopt Semantic Kernel as the AI orchestration layer to enable structured extraction with autonomous validation.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Replace manual HTTP proxy logic with Semantic Kernel's chat completion service
|
||||||
|
- Enable tool/function calling via SK plugins
|
||||||
|
- Implement an agentic extraction loop: extract → validate → retry (up to 3 times) → escalate to user
|
||||||
|
- Preserve the existing SSE contract so the Blazor client requires no changes
|
||||||
|
- Maintain inline tutorial comments explaining SK concepts
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Multi-agent orchestration (future — when Agent Framework reaches GA)
|
||||||
|
- Changing the Blazor client or `ChatApiClient`
|
||||||
|
- Adding new UI for structured output display (future change)
|
||||||
|
- Replacing CLIProxyAPI — SK's OpenAI connector talks to it as-is
|
||||||
|
- Authentication or multi-user support
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### D1: Use SK's OpenAI chat completion connector pointed at CLIProxyAPI
|
||||||
|
|
||||||
|
**Choice:** `Microsoft.SemanticKernel.Connectors.OpenAI` with `OpenAIChatCompletionService` configured to use `localhost:8317` as the endpoint.
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
- SK Anthropic connector (talks to Anthropic API directly) — would bypass CLIProxyAPI and lose model-switching flexibility
|
||||||
|
- Keep manual HttpClient alongside SK — defeats the purpose of the migration
|
||||||
|
|
||||||
|
**Rationale:** CLIProxyAPI already provides an OpenAI-compatible interface. SK's OpenAI connector works with any OpenAI-compatible endpoint. No infrastructure change required.
|
||||||
|
|
||||||
|
### D2: Register Kernel and plugins in DI via `Program.cs`
|
||||||
|
|
||||||
|
**Choice:** Configure `Kernel` in `Program.cs` using `builder.Services.AddKernel()` and register plugins via DI. Inject `Kernel` into `ChatController`.
|
||||||
|
|
||||||
|
**Rationale:** Follows ASP.NET Core conventions. The kernel is a singleton service with plugins registered at startup. Controller receives it via constructor injection, consistent with the existing pattern of injecting `IHttpClientFactory` and `IConfiguration`.
|
||||||
|
|
||||||
|
### D3: Validation as a native SK plugin function
|
||||||
|
|
||||||
|
**Choice:** Create an `ExtractionPlugin` class with `[KernelFunction]` methods: one for validation of extracted fields. The agent auto-invokes this via `ToolCallBehavior.AutoInvokeKernelFunctions`.
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
- Manual tool call loop in controller code — loses SK's built-in retry/function-calling orchestration
|
||||||
|
- Separate validation service outside SK — requires manual plumbing between LLM and validator
|
||||||
|
|
||||||
|
**Rationale:** SK's auto-invocation handles the loop naturally. The LLM sees the validation function as a tool, calls it, reads the result, and decides whether to retry or escalate. This is the core value proposition of adopting SK.
|
||||||
|
|
||||||
|
### D4: Iteration cap with human-in-the-loop escalation
|
||||||
|
|
||||||
|
**Choice:** Configure `ToolCallBehavior.AutoInvokeKernelFunctions` with `MaximumAutoInvokeAttempts = 3`. If the agent exhausts retries without valid output, it returns a clarification request as a regular chat message to the user.
|
||||||
|
|
||||||
|
**Rationale:** The iteration cap prevents runaway loops. The escalation path uses the existing chat UI — the agent simply asks for clarification in natural language, and the user responds in the next message. No special UI needed.
|
||||||
|
|
||||||
|
### D5: Preserve SSE contract via streaming kernel invocation
|
||||||
|
|
||||||
|
**Choice:** Use `kernel.InvokeStreamingAsync<StreamingChatMessageContent>()` (or `IChatCompletionService.GetStreamingChatMessageContentsAsync()`) and re-emit tokens as the same SSE format the client expects: `data: {"text":"..."}\n\n` and `data: [DONE]\n\n`.
|
||||||
|
|
||||||
|
**Rationale:** The Blazor client's `ChatApiClient` parses this exact format. By keeping the SSE contract identical, the entire client codebase remains untouched.
|
||||||
|
|
||||||
|
### D6: Predefined extraction schema as a strongly-typed C# class
|
||||||
|
|
||||||
|
**Choice:** Define an `ExtractedFields` record/class in `ChatAgent.Shared.Models` with the fixed set of known fields. Validation logic checks for required fields and type correctness.
|
||||||
|
|
||||||
|
**Rationale:** Single output type with fixed keys. A strongly-typed class gives compile-time safety, works with `System.Text.Json` serialization, and can carry data annotations for validation rules.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[SK OpenAI connector compatibility with CLIProxyAPI]** → CLIProxyAPI aims for OpenAI API parity but may have edge cases with tool calling responses. Mitigation: test tool calling end-to-end early; fall back to direct Anthropic connector if needed.
|
||||||
|
- **[Streaming + tool calling interaction]** → When the agent calls a tool mid-stream, the streaming behavior may differ from pure chat completion. Mitigation: handle tool call chunks in the SSE bridge; may need to buffer during tool execution and resume streaming after.
|
||||||
|
- **[SK version churn]** → Semantic Kernel is actively developed; APIs may evolve. Mitigation: pin to a specific stable version, document the version in stack spec.
|
||||||
|
- **[Tutorial complexity increase]** → SK adds abstractions (kernel, plugins, functions) that need explaining. Mitigation: maintain inline comments for every SK concept, consistent with project convention.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- What are the exact field names and types for `ExtractedFields`? (Need user input for the real schema — can use a placeholder for initial implementation.)
|
||||||
|
- Should tool call status ("Validating output...") be surfaced to the client as a distinct SSE event type, or just as regular text tokens? (Current design: regular text, revisit in a future change if needed.)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
The chat backend currently proxies requests to an OpenAI-compatible API (CLIProxyAPI) via manual HttpClient calls and SSE parsing. As the agent evolves toward structured extraction with tool calling and autonomous validation loops, this manual plumbing becomes a liability. Semantic Kernel provides a production-ready abstraction for chat completion, tool/function calling, and auto-invocation — letting us focus on agent behavior rather than HTTP mechanics. Adopting it now establishes the foundation for the agentic workflow (natural language → structured extraction → tool-based validation → human-in-the-loop clarification).
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Replace manual HttpClient + SSE proxy in `ChatController` with Semantic Kernel's `OpenAIChatCompletionService` pointed at the existing CLIProxyAPI proxy
|
||||||
|
- Add a validation plugin that the agent can call as a tool to validate extracted key-value output against a predefined schema
|
||||||
|
- Introduce an agentic loop: the kernel autonomously retries extraction up to 2–3 times on validation failure, then escalates to the user for clarification
|
||||||
|
- Keep the existing SSE contract to the Blazor client unchanged — `ChatApiClient` and `Chat.razor` are not modified
|
||||||
|
- **BREAKING**: `ChatController` internals are rewritten; the manual Responses API proxy logic is removed entirely
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `agent-extraction`: Defines the structured field extraction behavior — predefined keys, validation rules, autonomous retry loop, and human-in-the-loop escalation
|
||||||
|
- `semantic-kernel-integration`: Defines how Semantic Kernel is configured, registered, and wired into the API — kernel setup, OpenAI connector config, plugin registration
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `chat-streaming`: The streaming requirement changes from "proxy SSE from upstream API" to "stream Semantic Kernel chat completion responses as SSE" — same client contract, different server implementation
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- **ChatAgent.Api**: New NuGet dependencies (`Microsoft.SemanticKernel`), `Program.cs` service registration changes, `ChatController` rewritten
|
||||||
|
- **ChatAgent.Api.Tests**: Existing `ChatControllerTests` need updating to mock Semantic Kernel services instead of upstream HTTP calls
|
||||||
|
- **Dependencies**: Adds `Microsoft.SemanticKernel` and `Microsoft.SemanticKernel.Connectors.OpenAI` packages
|
||||||
|
- **Infrastructure**: No change — still talks to CLIProxyAPI at `localhost:8317`
|
||||||
|
- **Client**: No change — SSE contract preserved
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Structured field extraction from natural language
|
||||||
|
|
||||||
|
The agent SHALL extract a predefined set of key-value pairs from user-provided natural language text (e.g., email content) and return them as a structured JSON object.
|
||||||
|
|
||||||
|
#### Scenario: All fields extracted successfully
|
||||||
|
|
||||||
|
- **WHEN** the user sends a message containing natural language with all required information
|
||||||
|
- **THEN** the agent returns a JSON object with all predefined fields populated from the text
|
||||||
|
|
||||||
|
#### Scenario: Partial extraction
|
||||||
|
|
||||||
|
- **WHEN** the user sends a message that contains some but not all required fields
|
||||||
|
- **THEN** the agent extracts available fields and leaves missing fields as null
|
||||||
|
|
||||||
|
### Requirement: Predefined extraction schema
|
||||||
|
|
||||||
|
The system SHALL define a fixed set of known field names and types as a strongly-typed C# class. All extraction output MUST conform to this schema.
|
||||||
|
|
||||||
|
#### Scenario: Output conforms to schema
|
||||||
|
|
||||||
|
- **WHEN** the agent produces extracted fields
|
||||||
|
- **THEN** every key in the output matches a field defined in the schema and values match expected types
|
||||||
|
|
||||||
|
### Requirement: Autonomous validation via tool calling
|
||||||
|
|
||||||
|
The agent SHALL validate extracted fields by calling a validation tool function. The validation tool checks that all required fields are present and correctly typed.
|
||||||
|
|
||||||
|
#### Scenario: Validation passes
|
||||||
|
|
||||||
|
- **WHEN** the agent calls the validation tool with a complete and correct extraction
|
||||||
|
- **THEN** the tool returns a success result and the agent returns the final output to the user
|
||||||
|
|
||||||
|
#### Scenario: Validation fails with fixable errors
|
||||||
|
|
||||||
|
- **WHEN** the validation tool returns errors for missing or malformed fields
|
||||||
|
- **THEN** the agent re-reads the source text and attempts to fix the extraction without user intervention
|
||||||
|
|
||||||
|
### Requirement: Autonomous retry with iteration cap
|
||||||
|
|
||||||
|
The agent SHALL retry extraction autonomously up to 3 times when validation fails. After exhausting retries, the agent MUST escalate to the user.
|
||||||
|
|
||||||
|
#### Scenario: Agent retries and succeeds
|
||||||
|
|
||||||
|
- **WHEN** validation fails on the first attempt but the error is recoverable
|
||||||
|
- **THEN** the agent retries extraction and calls validation again, up to 3 total attempts
|
||||||
|
|
||||||
|
#### Scenario: Agent exhausts retries and escalates
|
||||||
|
|
||||||
|
- **WHEN** validation fails after 3 attempts
|
||||||
|
- **THEN** the agent sends a natural language message to the user identifying the specific fields it could not resolve and asking for clarification
|
||||||
|
|
||||||
|
### Requirement: Human-in-the-loop clarification
|
||||||
|
|
||||||
|
When the agent escalates to the user, the user SHALL be able to provide the missing information in natural language, and the agent SHALL incorporate the clarification and re-attempt extraction.
|
||||||
|
|
||||||
|
#### Scenario: User provides clarification
|
||||||
|
|
||||||
|
- **WHEN** the agent asks for clarification about missing fields and the user responds
|
||||||
|
- **THEN** the agent incorporates the user's response into the conversation context and produces an updated extraction
|
||||||
|
|
||||||
|
#### Scenario: Clarification via normal chat
|
||||||
|
|
||||||
|
- **WHEN** the agent escalates for clarification
|
||||||
|
- **THEN** the clarification request appears as a regular assistant message in the chat UI, and the user responds via the normal chat input
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Chat endpoint proxies to Responses API
|
||||||
|
|
||||||
|
The API backend SHALL expose `POST /api/chat` that accepts a list of messages and processes them using a Semantic Kernel chat completion service. The kernel is configured with an OpenAI connector pointed at the existing CLIProxyAPI proxy.
|
||||||
|
|
||||||
|
#### Scenario: Successful chat request
|
||||||
|
|
||||||
|
- **WHEN** the client sends a POST to `/api/chat` with a message list
|
||||||
|
- **THEN** the API processes the messages through the Semantic Kernel and returns the response
|
||||||
|
|
||||||
|
### Requirement: Streaming response delivery
|
||||||
|
|
||||||
|
The API backend SHALL stream the Semantic Kernel's chat completion response back to the WASM client as `text/event-stream`, forwarding text content so the client can render tokens incrementally. The SSE event format MUST remain `data: {"text":"..."}\n\n` for text deltas and `data: [DONE]\n\n` for completion.
|
||||||
|
|
||||||
|
#### Scenario: Tokens stream to client
|
||||||
|
|
||||||
|
- **WHEN** the Semantic Kernel emits streaming chat message content
|
||||||
|
- **THEN** the backend forwards each content chunk as an SSE event to the client containing the text fragment
|
||||||
|
|
||||||
|
#### Scenario: Stream completes
|
||||||
|
|
||||||
|
- **WHEN** the Semantic Kernel streaming response completes
|
||||||
|
- **THEN** the backend signals stream completion to the client with `data: [DONE]\n\n`
|
||||||
|
|
||||||
|
### Requirement: Configurable proxy target
|
||||||
|
|
||||||
|
The CLIProxyAPI base URL and model name SHALL be configurable via `appsettings.json` in the API project, not hardcoded. These values are used to configure the Semantic Kernel OpenAI connector.
|
||||||
|
|
||||||
|
#### Scenario: Configuration read at startup
|
||||||
|
|
||||||
|
- **WHEN** the API starts
|
||||||
|
- **THEN** it reads `ResponsesApi:BaseUrl` and `ResponsesApi:Model` from configuration to configure the Semantic Kernel
|
||||||
|
|
||||||
|
### Requirement: Error propagation
|
||||||
|
|
||||||
|
If the LLM service returns an error or is unreachable, the API backend SHALL return an error SSE event and the client SHALL display the error to the user.
|
||||||
|
|
||||||
|
#### Scenario: LLM service unreachable
|
||||||
|
|
||||||
|
- **WHEN** the CLIProxyAPI proxy is not running
|
||||||
|
- **THEN** the client displays an error message instead of an assistant response
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Semantic Kernel service registration
|
||||||
|
|
||||||
|
The API backend SHALL register a Semantic Kernel `Kernel` instance in the ASP.NET Core DI container at startup, configured with an OpenAI chat completion connector.
|
||||||
|
|
||||||
|
#### Scenario: Kernel registered at startup
|
||||||
|
|
||||||
|
- **WHEN** the API application starts
|
||||||
|
- **THEN** a `Kernel` instance is available for injection into controllers
|
||||||
|
|
||||||
|
### Requirement: OpenAI connector targets CLIProxyAPI proxy
|
||||||
|
|
||||||
|
The Semantic Kernel OpenAI chat completion service SHALL be configured to use the existing CLIProxyAPI proxy endpoint as its base URL, reading the URL and model name from `appsettings.json`.
|
||||||
|
|
||||||
|
#### Scenario: Connector uses configured endpoint
|
||||||
|
|
||||||
|
- **WHEN** the kernel makes a chat completion request
|
||||||
|
- **THEN** it sends the request to the URL specified in `ResponsesApi:BaseUrl` configuration
|
||||||
|
|
||||||
|
#### Scenario: Model from configuration
|
||||||
|
|
||||||
|
- **WHEN** the kernel makes a chat completion request
|
||||||
|
- **THEN** it uses the model name specified in `ResponsesApi:Model` configuration
|
||||||
|
|
||||||
|
### Requirement: Plugin registration
|
||||||
|
|
||||||
|
The API backend SHALL register extraction and validation plugins with the Kernel so they are available as tools for the LLM to invoke.
|
||||||
|
|
||||||
|
#### Scenario: Plugins available as tools
|
||||||
|
|
||||||
|
- **WHEN** the kernel is constructed
|
||||||
|
- **THEN** all registered plugin functions appear in the tool list sent to the LLM
|
||||||
|
|
||||||
|
### Requirement: Auto function calling
|
||||||
|
|
||||||
|
The Kernel SHALL be configured with automatic function calling enabled, allowing the LLM to invoke registered plugin functions without manual dispatch code.
|
||||||
|
|
||||||
|
#### Scenario: LLM invokes tool automatically
|
||||||
|
|
||||||
|
- **WHEN** the LLM decides to call a registered function during chat completion
|
||||||
|
- **THEN** the kernel automatically executes the function and returns the result to the LLM
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
## 1. Add Semantic Kernel Dependencies
|
||||||
|
|
||||||
|
- [x] 1.1 Add `Microsoft.SemanticKernel` and `Microsoft.SemanticKernel.Connectors.OpenAI` NuGet packages to `ChatAgent.Api`
|
||||||
|
- [x] 1.2 Remove the `OpenAI` SDK package if no longer needed after migration
|
||||||
|
|
||||||
|
## 2. Define Extraction Schema
|
||||||
|
|
||||||
|
- [x] 2.1 Create `ExtractedFields` class in `ChatAgent.Shared/Models/` with the predefined set of key-value fields (placeholder fields until real schema is provided)
|
||||||
|
- [x] 2.2 Create `ValidationResult` class in `ChatAgent.Shared/Models/` with `IsValid`, `Errors` properties
|
||||||
|
|
||||||
|
## 3. Create Extraction Plugin
|
||||||
|
|
||||||
|
- [x] 3.1 Create `ExtractionPlugin` class in `ChatAgent.Api/Plugins/` with a `[KernelFunction]` validation method that checks `ExtractedFields` for required fields and type correctness
|
||||||
|
- [x] 3.2 Add inline tutorial comments explaining SK plugin concepts (`[KernelFunction]`, `[Description]`, auto-invocation)
|
||||||
|
|
||||||
|
## 4. Wire Semantic Kernel in Program.cs
|
||||||
|
|
||||||
|
- [x] 4.1 Register `OpenAIChatCompletionService` in DI using `ResponsesApi:BaseUrl` and `ResponsesApi:Model` from config
|
||||||
|
- [x] 4.2 Register `Kernel` with `AddKernel()` and import `ExtractionPlugin`
|
||||||
|
- [x] 4.3 Add inline tutorial comments explaining kernel setup, connectors, and plugin registration
|
||||||
|
|
||||||
|
## 5. Rewrite ChatController
|
||||||
|
|
||||||
|
- [x] 5.1 Replace `IHttpClientFactory` and `IConfiguration` injection with `Kernel` injection
|
||||||
|
- [x] 5.2 Replace manual HTTP proxy logic with `IChatCompletionService.GetStreamingChatMessageContentsAsync()` using the conversation history from the request
|
||||||
|
- [x] 5.3 Configure `OpenAIPromptExecutionSettings` with `FunctionChoiceBehavior.Auto()` and `autoInvokeMaxCallCount = 3`
|
||||||
|
- [x] 5.4 Re-emit streaming content as the existing SSE format (`data: {"text":"..."}\n\n` and `data: [DONE]\n\n`)
|
||||||
|
- [x] 5.5 Add inline tutorial comments explaining streaming chat completion, execution settings, and tool call behavior
|
||||||
|
|
||||||
|
## 6. Update Tests
|
||||||
|
|
||||||
|
- [x] 6.1 Update `ChatControllerTests` to mock `IChatCompletionService` instead of upstream HTTP calls
|
||||||
|
- [x] 6.2 Add tests for the validation plugin (`ExtractionPlugin` returns correct pass/fail results)
|
||||||
|
- [x] 6.3 Add a test verifying the agent escalates to the user after max retries
|
||||||
|
|
||||||
|
## 7. Verify
|
||||||
|
|
||||||
|
- [x] 7.1 Run `dotnet build` to confirm no errors
|
||||||
|
- [x] 7.2 Run `dotnet test` to confirm all tests pass
|
||||||
|
- [ ] 7.3 Manual smoke test: send a chat message and verify streaming still works end-to-end through SK
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-04
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Chat.razor currently constructs a `ChatRequest` with only the latest user message (line 161-164). The backend and Responses API already support multi-message input — no server changes needed. This is purely a client-side change.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Send full conversation history with each request for multi-turn context
|
||||||
|
- Add a "New Chat" button to reset the session
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Persistent conversation storage (explicitly deferred)
|
||||||
|
- Multiple conversation tabs/sidebar
|
||||||
|
- Token limit management or history truncation (small conversations for now)
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision 1: Send full _messages list minus the empty placeholder
|
||||||
|
|
||||||
|
When building the `ChatRequest`, include all messages from `_messages` except the last one (which is the empty assistant placeholder waiting for streaming). This gives the AI the full conversation context.
|
||||||
|
|
||||||
|
### Decision 2: New Chat button in the AppBar
|
||||||
|
|
||||||
|
Place a "New Chat" icon button in the `MudAppBar` (in MainLayout.razor or Chat.razor). Clicking it clears `_messages` and resets to the empty state. Disabled during streaming.
|
||||||
|
|
||||||
|
Alternative considered: putting it in the input area. Rejected — the AppBar is more natural and matches ChatGPT/Gemini placement.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [No token limit management] → For long conversations, the full history could exceed the model's context window. Acceptable for now; truncation can be added later.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Each chat request currently sends only the latest user message, so the AI has no memory of previous exchanges within the same session. Users expect the assistant to remember context from earlier in the conversation, like ChatGPT/Gemini do.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Send the full conversation history (all prior user and assistant messages) with each API request instead of just the latest user message
|
||||||
|
- The backend already forwards all messages in `ChatRequest.Messages` to the Responses API — no backend changes needed
|
||||||
|
- Add a "New Chat" button to clear the conversation and start fresh
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
<!-- None -->
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `chat-ui`: Send full message history with each request; add a "New Chat" button to reset the conversation
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- `src/ChatAgent.Client/Pages/Chat.razor`: Change request construction to include full history; add new chat button
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Streaming AI response
|
||||||
|
|
||||||
|
The assistant SHALL reply with a real AI response streamed from the backend API, using the full conversation history as context. 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
|
||||||
|
|
||||||
|
#### Scenario: Full history sent with each request
|
||||||
|
|
||||||
|
- **WHEN** the user sends a message after prior exchanges
|
||||||
|
- **THEN** all previous user and assistant messages are included in the API request so the AI has conversational context
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: New chat button
|
||||||
|
|
||||||
|
The chat page SHALL provide a button to clear the current conversation and start a new one.
|
||||||
|
|
||||||
|
#### Scenario: User starts a new chat
|
||||||
|
|
||||||
|
- **WHEN** the user clicks the "New Chat" button
|
||||||
|
- **THEN** all messages are cleared and the empty state is shown
|
||||||
|
|
||||||
|
#### Scenario: New chat button disabled during streaming
|
||||||
|
|
||||||
|
- **WHEN** the assistant is currently streaming a response
|
||||||
|
- **THEN** the "New Chat" button is disabled
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
## 1. Multi-turn History
|
||||||
|
|
||||||
|
- [x] 1.1 Change ChatRequest construction in Chat.razor to send all prior messages (excluding the empty assistant placeholder) instead of just the latest user message
|
||||||
|
|
||||||
|
## 2. New Chat Button
|
||||||
|
|
||||||
|
- [x] 2.1 Add a "New Chat" icon button to the AppBar or chat page that clears _messages
|
||||||
|
- [x] 2.2 Disable the "New Chat" button while streaming is in progress
|
||||||
|
|
||||||
|
## 3. Verify
|
||||||
|
|
||||||
|
- [x] 3.1 Run dotnet build to confirm no errors
|
||||||
|
- [x] 3.2 Run dotnet test to confirm existing tests still pass
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-04
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
## 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.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## 1. Shared Models
|
||||||
|
|
||||||
|
- [x] 1.1 Create ChatRequest.cs in ChatAgent.Shared/Models with a Messages list property
|
||||||
|
|
||||||
|
## 2. API Backend
|
||||||
|
|
||||||
|
- [x] 2.1 Add appsettings.json to ChatAgent.Api with ResponsesApi:BaseUrl and ResponsesApi:Model
|
||||||
|
- [x] 2.2 Register an HttpClient for the Responses API proxy in Api Program.cs
|
||||||
|
- [x] 2.3 Create ChatController with POST /api/chat that proxies to the Responses API with streaming
|
||||||
|
- [x] 2.4 Parse Responses API SSE stream, extract response.output_text.delta events, re-emit as simplified SSE to client
|
||||||
|
|
||||||
|
## 3. Client Streaming
|
||||||
|
|
||||||
|
- [x] 3.1 Add a streaming SendChatAsync method to ChatApiClient that uses SetBrowserResponseStreamingEnabled and HttpCompletionOption.ResponseHeadersRead
|
||||||
|
- [x] 3.2 Parse the simplified SSE stream line-by-line, yielding text deltas
|
||||||
|
|
||||||
|
## 4. Chat Page Updates
|
||||||
|
|
||||||
|
- [x] 4.1 Replace hardcoded response in Chat.razor with a call to ChatApiClient.SendChatAsync
|
||||||
|
- [x] 4.2 Append tokens to the assistant message incrementally with StateHasChanged after each delta
|
||||||
|
- [x] 4.3 Add a thinking indicator shown until the first token arrives
|
||||||
|
- [x] 4.4 Disable input field and send button while streaming is in progress
|
||||||
|
- [x] 4.5 Handle errors — display error message if API call fails
|
||||||
|
- [x] 4.6 Auto-scroll during streaming (not just at the end)
|
||||||
|
|
||||||
|
## 5. Verify
|
||||||
|
|
||||||
|
- [x] 5.1 Run dotnet build to confirm no errors
|
||||||
|
- [ ] 5.2 Manually verify: send a message, see streaming response from Claude
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-05
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Assistant messages from the LLM contain markdown formatting (bold, code blocks, lists, headings) but are currently rendered as plain text via `<MudText>@message.Content</MudText>`. Markdig 1.1.1 is already in the stack spec but not yet wired up. The Blazor WASM client needs a rendering pipeline that converts markdown to HTML and displays it safely inside chat bubbles.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Render assistant message markdown as formatted HTML (headings, bold, italic, code, lists, tables, links)
|
||||||
|
- Sanitize HTML output to prevent XSS from LLM-generated content
|
||||||
|
- Style rendered elements to look good inside MudBlazor chat bubbles
|
||||||
|
- Maintain streaming performance — re-render as tokens arrive without flicker
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Rendering user messages as markdown (they stay plain text)
|
||||||
|
- Syntax highlighting for code blocks (future enhancement)
|
||||||
|
- LaTeX/math rendering
|
||||||
|
- Image rendering from markdown
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### D1: Use Markdig for markdown-to-HTML conversion
|
||||||
|
|
||||||
|
Markdig is already in the stack spec. It's the standard .NET markdown library, runs in WASM, and supports GFM (GitHub Flavored Markdown) extensions out of the box.
|
||||||
|
|
||||||
|
**Alternative**: Custom regex-based parsing — rejected, fragile and incomplete.
|
||||||
|
|
||||||
|
### D2: Render via `MarkupString` in Blazor
|
||||||
|
|
||||||
|
Blazor's `MarkupString` struct renders raw HTML in the component tree. We wrap the Markdig HTML output in `MarkupString` to display it. This replaces `@message.Content` with `@((MarkupString)renderedHtml)` for assistant messages only.
|
||||||
|
|
||||||
|
**Alternative**: Use a third-party Blazor markdown component — rejected, adds a dependency when Markdig + MarkupString achieves the same result with less coupling.
|
||||||
|
|
||||||
|
### D3: HTML sanitization via allowlist approach
|
||||||
|
|
||||||
|
LLM output is untrusted. After Markdig converts markdown to HTML, we strip any tags/attributes not on an allowlist. Allowed: `p, h1-h6, strong, em, code, pre, ul, ol, li, a (href only), table, thead, tbody, tr, th, td, br, blockquote`. This prevents script injection without a heavy sanitizer dependency.
|
||||||
|
|
||||||
|
**Alternative**: Use a NuGet sanitizer package (e.g., HtmlSanitizer) — viable but adds a dependency for a simple allowlist. If the allowlist proves insufficient, revisit.
|
||||||
|
|
||||||
|
### D4: Create a MarkdownService for the conversion pipeline
|
||||||
|
|
||||||
|
A `MarkdownService` class encapsulates the Markdig pipeline configuration, HTML sanitization, and caching. Registered as singleton in DI. This keeps the rendering logic out of the Razor component and makes it testable.
|
||||||
|
|
||||||
|
### D5: Incremental rendering during streaming
|
||||||
|
|
||||||
|
During streaming, the assistant message content grows token by token. Each time `StateHasChanged()` is called, the full content string is re-converted through Markdig. This is acceptable because:
|
||||||
|
- Markdig is fast (sub-millisecond for typical message sizes)
|
||||||
|
- Messages rarely exceed a few KB
|
||||||
|
- No DOM diffing overhead — Blazor handles this efficiently
|
||||||
|
|
||||||
|
If performance becomes an issue, we can cache the last-known-good HTML prefix and only re-render the delta.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[XSS from LLM output]** → Mitigated by HTML sanitization allowlist. The allowlist is conservative — only structural tags, no script/style/event handlers.
|
||||||
|
- **[Streaming flicker]** → Low risk. Blazor's diffing minimizes DOM updates. If observed, add debouncing to StateHasChanged calls.
|
||||||
|
- **[Markdig WASM size]** → Markdig adds ~200KB to the WASM bundle. Already accepted in stack spec.
|
||||||
|
- **[Incomplete markdown support]** → Markdig supports GFM by default. Edge cases (nested tables, complex HTML blocks) may render imperfectly but are rare in LLM output.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Assistant messages currently render as plain text — markdown formatting (bold, code blocks, lists, headings) from the LLM appears as raw characters. With Semantic Kernel and tool calling now in place, responses are increasingly structured and harder to read without proper rendering. Markdig 1.1.1 is already in the stack but not wired up.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Render assistant message content as HTML by converting markdown via Markdig
|
||||||
|
- Sanitize rendered HTML to prevent XSS (the LLM output is untrusted content)
|
||||||
|
- Style rendered markdown elements (code blocks, lists, tables) to fit the chat bubble aesthetic
|
||||||
|
- Keep user messages as plain text (they are short inputs, not markdown)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `rich-text-display`: Markdown-to-HTML rendering pipeline for assistant messages, including sanitization and styling
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `chat-ui`: Assistant message display changes from plain text to rendered markdown
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- ChatAgent.Client: Chat.razor message rendering, new markdown service, CSS additions
|
||||||
|
- Dependencies: Markdig already in stack spec; may need an HTML sanitizer package
|
||||||
|
- No backend changes — this is purely client-side rendering
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user