docs(02): create phase 2 integration layer plans
This commit is contained in:
@@ -45,7 +45,12 @@ Plans:
|
||||
1. SKILL.md has valid frontmatter (name, description, triggers) and OpenClaw loads the skill successfully
|
||||
2. Skill triggers on /gsd:* patterns and project management keywords
|
||||
3. Response formatting adapts to channel capabilities (markdown for rich channels, plain for constrained ones)
|
||||
**Plans**: TBD
|
||||
**Plans**: 3 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 02-01-PLAN.md — SKILL.md creation with /gsd:* trigger pattern (OCLW-01, OCLW-02)
|
||||
- [ ] 02-02-PLAN.md — FeishuResponseFormatter with Card JSON 2.0 support (OCLW-04)
|
||||
- [ ] 02-03-PLAN.md — Prompt templates for context recovery, command mapping, error recovery (OCLW-02)
|
||||
|
||||
### Phase 3: Orchestration
|
||||
**Goal**: Users can send GSD slash commands through chat and receive captured output with proper error handling
|
||||
@@ -76,10 +81,10 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Core Infrastructure | 3/4 | In Progress | - |
|
||||
| 2. Integration Layer | 0/TBD | Not started | - |
|
||||
| 2. Integration Layer | 0/3 | Ready | - |
|
||||
| 3. Orchestration | 0/TBD | Not started | - |
|
||||
| 4. GSD Integration | 0/TBD | Not started | - |
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-03-10*
|
||||
*Last updated: 2026-03-11 - Completed 01-03 tmux session lifecycle*
|
||||
*Last updated: 2026-03-11 - Planned Phase 2 integration layer*
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
phase: 02-integration-layer
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- skill/SKILL.md
|
||||
- tests/test_skill_md.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
- OCLW-01
|
||||
- OCLW-02
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "SKILL.md has valid YAML frontmatter with name and description fields"
|
||||
- "OpenClaw can load the skill successfully"
|
||||
- "Skill triggers on /gsd:* command patterns"
|
||||
artifacts:
|
||||
- path: "skill/SKILL.md"
|
||||
provides: "OpenClaw skill definition and trigger patterns"
|
||||
min_lines: 20
|
||||
contains: "---"
|
||||
- path: "tests/test_skill_md.py"
|
||||
provides: "Tests for SKILL.md validation"
|
||||
exports: ["TestSkillMd"]
|
||||
key_links:
|
||||
- from: "skill/SKILL.md"
|
||||
to: "OpenClaw Gateway"
|
||||
via: "skill loading"
|
||||
pattern: "name:.*description:"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create SKILL.md with valid frontmatter that OpenClaw can load and triggers on /gsd:* commands.
|
||||
|
||||
Purpose: Define the skill entry point for OpenClaw's skill registry. The SKILL.md file is the "face" of the gsd-openclaw skill that OpenClaw loads to understand when to invoke this skill.
|
||||
|
||||
Output:
|
||||
- skill/SKILL.md - OpenClaw skill definition
|
||||
- tests/test_skill_md.py - Validation tests
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-integration-layer/02-CONTEXT.md
|
||||
@.planning/phases/02-integration-layer/02-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Reference: dev-workflow SKILL.md pattern -->
|
||||
<!-- OpenClaw skills require only `name` and `description` fields -->
|
||||
<!-- The `description` field serves as the trigger condition -->
|
||||
|
||||
Example frontmatter format:
|
||||
```yaml
|
||||
---
|
||||
name: gsd-openclaw
|
||||
description: GSD project management via /gsd:* commands. Triggers on /gsd:new-project, /gsd:plan, /gsd:status, /gsd:use and other GSD slash commands.
|
||||
---
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create test scaffold for SKILL.md validation</name>
|
||||
<files>tests/test_skill_md.py</files>
|
||||
<behavior>
|
||||
- Test 1: SKILL.md file exists at skill/SKILL.md
|
||||
- Test 2: SKILL.md has valid YAML frontmatter between --- delimiters
|
||||
- Test 3: SKILL.md frontmatter contains `name` field
|
||||
- Test 4: SKILL.md frontmatter contains `description` field
|
||||
- Test 5: description field contains /gsd: pattern for triggering
|
||||
</behavior>
|
||||
<action>
|
||||
Create tests/test_skill_md.py with test class TestSkillMd containing:
|
||||
|
||||
1. test_skill_md_exists():
|
||||
- Assert skill/SKILL.md file exists
|
||||
- Use Path(__file__).parent.parent / "skill" / "SKILL.md"
|
||||
|
||||
2. test_skill_md_has_valid_frontmatter():
|
||||
- Read file content
|
||||
- Assert content starts with "---\n"
|
||||
- Assert second "---" delimiter exists
|
||||
- Use yaml.safe_load() to parse frontmatter (import yaml)
|
||||
|
||||
3. test_skill_md_has_name_field():
|
||||
- Parse frontmatter
|
||||
- Assert "name" key exists
|
||||
- Assert name is non-empty string
|
||||
|
||||
4. test_skill_md_has_description_field():
|
||||
- Parse frontmatter
|
||||
- Assert "description" key exists
|
||||
- Assert description is non-empty string
|
||||
|
||||
5. test_skill_md_description_contains_gsd_pattern():
|
||||
- Parse frontmatter
|
||||
- Assert "/gsd:" appears in description (for trigger matching)
|
||||
|
||||
Follow test pattern from tests/test_state_manager.py (class-based, clear docstrings).
|
||||
Add yaml to test dependencies if needed (should be available via pytest).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_skill_md.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- tests/test_skill_md.py exists with 5 passing tests
|
||||
- All tests fail initially (no SKILL.md yet)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create SKILL.md with valid frontmatter</name>
|
||||
<files>skill/SKILL.md</files>
|
||||
<behavior>
|
||||
- Test: All tests in test_skill_md.py pass
|
||||
</behavior>
|
||||
<action>
|
||||
Create skill/SKILL.md with:
|
||||
|
||||
1. YAML frontmatter (between --- delimiters):
|
||||
- name: gsd-openclaw
|
||||
- description: GSD project management via /gsd:* commands. Triggers on /gsd:new-project, /gsd:plan, /gsd:status, /gsd:use and other GSD slash commands.
|
||||
|
||||
2. Markdown content body (after frontmatter):
|
||||
- Title: # GSD-OpenClaw Skill
|
||||
- "## When to Use" section explaining trigger patterns
|
||||
- "## Commands" table with supported /gsd:* commands
|
||||
- "## Project Context" section explaining /gsd:use command
|
||||
|
||||
User decision (LOCKED): Only /gsd:* command triggers, no natural language.
|
||||
|
||||
Commands to document in table:
|
||||
| Command | Description |
|
||||
| /gsd:new-project <name> | Create a new GSD project |
|
||||
| /gsd:use <project> | Switch to a project context |
|
||||
| /gsd:status | Show current project status |
|
||||
| /gsd:plan | Run planning phase |
|
||||
| /gsd:ship | Ship the current phase |
|
||||
| /gsd:help | Show available commands |
|
||||
|
||||
Follow dev-workflow/SKILL.md structure as reference.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_skill_md.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- skill/SKILL.md exists with valid YAML frontmatter
|
||||
- All 5 tests in test_skill_md.py pass
|
||||
- SKILL.md has name, description fields with /gsd: trigger pattern
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Run pytest tests/test_skill_md.py -v (all tests pass)
|
||||
- Verify SKILL.md can be parsed as valid YAML
|
||||
- Verify description contains /gsd: pattern for OpenClaw trigger matching
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. skill/SKILL.md exists with valid YAML frontmatter
|
||||
2. Frontmatter contains `name: gsd-openclaw`
|
||||
3. Frontmatter contains `description` with /gsd:* pattern
|
||||
4. All 5 tests in tests/test_skill_md.py pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-integration-layer/02-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
phase: 02-integration-layer
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/gsd_openclaw/response_formatter.py
|
||||
- tests/test_response_formatter.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
- OCLW-04
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Response formatter creates valid Feishu Card JSON 2.0 structure"
|
||||
- "Success cards have green theme"
|
||||
- "Error cards have red theme with guidance buttons"
|
||||
- "Long messages are paginated with [N/M] markers"
|
||||
- "Cards support interactive buttons with callback values"
|
||||
artifacts:
|
||||
- path: "src/gsd_openclaw/response_formatter.py"
|
||||
provides: "Feishu Card message formatting"
|
||||
exports: ["FeishuResponseFormatter", "CardTheme", "CardButton"]
|
||||
- path: "tests/test_response_formatter.py"
|
||||
provides: "Tests for response formatting"
|
||||
exports: ["TestFeishuResponseFormatter"]
|
||||
key_links:
|
||||
- from: "FeishuResponseFormatter"
|
||||
to: "Feishu API"
|
||||
via: "card JSON structure"
|
||||
pattern: '"schema": "2.0"'
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create FeishuResponseFormatter class that formats responses as Feishu Card JSON 2.0 messages with three-zone structure (header/content/actions).
|
||||
|
||||
Purpose: Provide channel-adaptive response formatting for Feishu. The formatter converts GSD output into structured Feishu Card messages with proper theming, pagination, and interactive elements.
|
||||
|
||||
Output:
|
||||
- src/gsd_openclaw/response_formatter.py - Response formatter implementation
|
||||
- tests/test_response_formatter.py - Formatter tests
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-integration-layer/02-CONTEXT.md
|
||||
@.planning/phases/02-integration-layer/02-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Feishu Card JSON 2.0 Structure (from RESEARCH.md) -->
|
||||
|
||||
Three-zone layout:
|
||||
1. header: Title + status color (blue=info, green=success, red=error)
|
||||
2. elements: Main content area (div, markdown, note components)
|
||||
3. actions: Interactive buttons (at card bottom)
|
||||
|
||||
Card JSON structure:
|
||||
```json
|
||||
{
|
||||
"schema": "2.0",
|
||||
"config": {"wide_screen_mode": true},
|
||||
"header": {
|
||||
"title": {"tag": "plain_text", "content": "..."},
|
||||
"template": "green"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"tag": "lark_md", "content": "..."}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Button structure:
|
||||
```json
|
||||
{
|
||||
"tag": "action",
|
||||
"actions": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {"tag": "plain_text", "content": "Confirm"},
|
||||
"type": "primary",
|
||||
"value": {"action": "approve", "project": "my-auth"}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Use `lark_md` tag for markdown content (Feishu's built-in markdown support).
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create test scaffold for FeishuResponseFormatter</name>
|
||||
<files>tests/test_response_formatter.py</files>
|
||||
<behavior>
|
||||
- Test 1: format_success returns valid card with green theme
|
||||
- Test 2: format_error returns valid card with red theme
|
||||
- Test 3: format_info returns valid card with blue theme
|
||||
- Test 4: Card with buttons includes action element
|
||||
- Test 5: format_paginated adds [N/M] marker
|
||||
- Test 6: Card has schema 2.0 and wide_screen_mode config
|
||||
</behavior>
|
||||
<action>
|
||||
Create tests/test_response_formatter.py with test class TestFeishuResponseFormatter:
|
||||
|
||||
1. test_format_success_returns_valid_card():
|
||||
- Create FeishuResponseFormatter()
|
||||
- Call format_success("Test Title", "Test content")
|
||||
- Assert card["schema"] == "2.0"
|
||||
- Assert card["header"]["template"] == "green"
|
||||
- Assert card["header"]["title"]["content"] == "Test Title"
|
||||
|
||||
2. test_format_error_returns_red_theme():
|
||||
- Call format_error("Error", "Something went wrong")
|
||||
- Assert card["header"]["template"] == "red"
|
||||
|
||||
3. test_format_info_returns_blue_theme():
|
||||
- Call format_info("Info", "Status update")
|
||||
- Assert card["header"]["template"] == "blue"
|
||||
|
||||
4. test_format_with_buttons_includes_actions():
|
||||
- Create CardButton("OK", {"action": "ok"})
|
||||
- Call format_success("Test", "Content", buttons=[button])
|
||||
- Assert elements contains action with tag="action"
|
||||
- Assert button text is "OK"
|
||||
|
||||
5. test_format_paginated_adds_marker():
|
||||
- Call format_paginated("Title", "Content", page=1, total_pages=3)
|
||||
- Assert "[1/3]" appears in content
|
||||
|
||||
6. test_card_has_required_structure():
|
||||
- Call format_success("Test", "Content")
|
||||
- Assert "config" key exists
|
||||
- Assert config["wide_screen_mode"] == True
|
||||
- Assert elements[0]["tag"] == "div"
|
||||
|
||||
Import from gsd_openclaw.response_formatter: FeishuResponseFormatter, CardTheme, CardButton
|
||||
|
||||
Follow test pattern from tests/test_state_manager.py.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_response_formatter.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- tests/test_response_formatter.py exists with 6 tests
|
||||
- All tests fail initially (no implementation yet)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement CardTheme enum and CardButton dataclass</name>
|
||||
<files>src/gsd_openclaw/response_formatter.py</files>
|
||||
<behavior>
|
||||
- Test: CardButton can be created with text, value, button_type
|
||||
- Test: CardTheme enum has SUCCESS, ERROR, INFO, WARNING values
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/gsd_openclaw/response_formatter.py with:
|
||||
|
||||
1. Imports:
|
||||
- from dataclasses import dataclass
|
||||
- from typing import Dict, Any, List, Optional
|
||||
- from enum import Enum
|
||||
|
||||
2. CardTheme(Enum):
|
||||
- SUCCESS = "green"
|
||||
- ERROR = "red"
|
||||
- INFO = "blue"
|
||||
- WARNING = "orange"
|
||||
|
||||
3. @dataclass CardButton:
|
||||
- text: str
|
||||
- value: Dict[str, Any]
|
||||
- button_type: str = "primary" # primary, default, danger
|
||||
|
||||
Keep implementation minimal - just these data structures.
|
||||
Tests should now pass for CardTheme and CardButton.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_response_formatter.py::TestFeishuResponseFormatter::test_format_success_returns_valid_card -v 2>&1 | head -20 || echo "Expected: tests still fail (formatter not implemented)"</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- CardTheme enum exists with SUCCESS, ERROR, INFO, WARNING
|
||||
- CardButton dataclass exists with text, value, button_type
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Implement FeishuResponseFormatter class</name>
|
||||
<files>src/gsd_openclaw/response_formatter.py</files>
|
||||
<behavior>
|
||||
- Test: All 6 tests in test_response_formatter.py pass
|
||||
</behavior>
|
||||
<action>
|
||||
Add FeishuResponseFormatter class to response_formatter.py:
|
||||
|
||||
class FeishuResponseFormatter:
|
||||
"""Format responses as Feishu Card messages."""
|
||||
|
||||
def format_success(self, title: str, content: str,
|
||||
buttons: Optional[List[CardButton]] = None) -> dict:
|
||||
"""Format a success response card with green theme."""
|
||||
return self._build_card(title, content, CardTheme.SUCCESS, buttons)
|
||||
|
||||
def format_error(self, title: str, content: str,
|
||||
buttons: Optional[List[CardButton]] = None) -> dict:
|
||||
"""Format an error response card with red theme."""
|
||||
return self._build_card(title, content, CardTheme.ERROR, buttons)
|
||||
|
||||
def format_info(self, title: str, content: str,
|
||||
buttons: Optional[List[CardButton]] = None) -> dict:
|
||||
"""Format an info response card with blue theme."""
|
||||
return self._build_card(title, content, CardTheme.INFO, buttons)
|
||||
|
||||
def format_paginated(self, title: str, content: str,
|
||||
page: int, total_pages: int) -> dict:
|
||||
"""Format a paginated response with [page/total] marker."""
|
||||
footer = f"\n\n---\n[{page}/{total_pages}]"
|
||||
return self._build_card(title, content + footer, CardTheme.INFO)
|
||||
|
||||
def _build_card(self, title: str, content: str, theme: CardTheme,
|
||||
buttons: Optional[List[CardButton]] = None) -> dict:
|
||||
"""Build complete card JSON structure."""
|
||||
card = {
|
||||
"schema": "2.0",
|
||||
"config": {"wide_screen_mode": True},
|
||||
"header": {
|
||||
"title": {"tag": "plain_text", "content": title},
|
||||
"template": theme.value
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"tag": "lark_md", "content": content}}
|
||||
]
|
||||
}
|
||||
if buttons:
|
||||
card["elements"].append(self._build_actions(buttons))
|
||||
return card
|
||||
|
||||
def _build_actions(self, buttons: List[CardButton]) -> dict:
|
||||
"""Build actions element from button list."""
|
||||
return {
|
||||
"tag": "action",
|
||||
"actions": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {"tag": "plain_text", "content": btn.text},
|
||||
"type": btn.button_type,
|
||||
"value": btn.value
|
||||
}
|
||||
for btn in buttons
|
||||
]
|
||||
}
|
||||
|
||||
User decision (LOCKED):
|
||||
- Three-zone structure: header + elements + actions
|
||||
- Error cards use red theme
|
||||
- Success cards use green theme
|
||||
- Pagination uses [N/M] format
|
||||
- Interactive buttons support callback values
|
||||
|
||||
All 6 tests should now pass.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_response_formatter.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- FeishuResponseFormatter class implements all format methods
|
||||
- All 6 tests pass
|
||||
- Card JSON uses schema 2.0 with three-zone structure
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Run pytest tests/test_response_formatter.py -v (all tests pass)
|
||||
- Verify card structure matches Feishu Card JSON 2.0 specification
|
||||
- Verify theme colors are correct (green=success, red=error, blue=info)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. FeishuResponseFormatter class exists in src/gsd_openclaw/response_formatter.py
|
||||
2. format_success, format_error, format_info, format_paginated methods work
|
||||
3. Card JSON has schema "2.0" with proper header/elements structure
|
||||
4. Buttons are rendered as action elements with callback values
|
||||
5. All 6 tests in tests/test_response_formatter.py pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-integration-layer/02-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,354 @@
|
||||
---
|
||||
phase: 02-integration-layer
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/gsd_openclaw/prompts/__init__.py
|
||||
- src/gsd_openclaw/prompts/context_recovery.md
|
||||
- src/gsd_openclaw/prompts/command_mapping.md
|
||||
- src/gsd_openclaw/prompts/error_recovery.md
|
||||
- src/gsd_openclaw/prompt_loader.py
|
||||
- tests/test_prompt_templates.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
- OCLW-02
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Prompt templates exist as markdown files with {{variable}} placeholders"
|
||||
- "PromptLoader can load and render templates with variable substitution"
|
||||
- "context_recovery.md template helps resume interrupted sessions"
|
||||
- "command_mapping.md template maps user input to GSD commands"
|
||||
- "error_recovery.md template generates user-friendly error messages"
|
||||
artifacts:
|
||||
- path: "src/gsd_openclaw/prompts/context_recovery.md"
|
||||
provides: "Session context recovery prompt"
|
||||
contains: "{{project_name}}"
|
||||
- path: "src/gsd_openclaw/prompts/command_mapping.md"
|
||||
provides: "Command mapping prompt"
|
||||
contains: "{{user_input}}"
|
||||
- path: "src/gsd_openclaw/prompts/error_recovery.md"
|
||||
provides: "Error recovery prompt"
|
||||
contains: "{{error_message}}"
|
||||
- path: "src/gsd_openclaw/prompt_loader.py"
|
||||
provides: "Template loading and rendering"
|
||||
exports: ["PromptLoader"]
|
||||
- path: "tests/test_prompt_templates.py"
|
||||
provides: "Tests for prompt templates"
|
||||
exports: ["TestPromptLoader"]
|
||||
key_links:
|
||||
- from: "PromptLoader"
|
||||
to: "prompts/*.md"
|
||||
via: "string.Template.safe_substitute"
|
||||
pattern: "{{variable}}"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create prompt templates for context recovery, command mapping, and error recovery, plus a PromptLoader class for loading and rendering templates.
|
||||
|
||||
Purpose: Provide reusable prompt templates that Phase 3's orchestrator will use to generate context-aware prompts for Claude. Templates use simple {{variable}} syntax with string.Template for substitution.
|
||||
|
||||
Output:
|
||||
- src/gsd_openclaw/prompts/ directory with 3 template files
|
||||
- src/gsd_openclaw/prompt_loader.py - Template loader implementation
|
||||
- tests/test_prompt_templates.py - Template tests
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-integration-layer/02-CONTEXT.md
|
||||
@.planning/phases/02-integration-layer/02-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Template pattern from RESEARCH.md -->
|
||||
|
||||
Use string.Template with safe_substitute() for {{variable}} syntax:
|
||||
```python
|
||||
from string import Template
|
||||
from pathlib import Path
|
||||
|
||||
class PromptLoader:
|
||||
def __init__(self, template_dir: Path):
|
||||
self.template_dir = template_dir
|
||||
self._cache: Dict[str, Template] = {}
|
||||
|
||||
def render(self, name: str, **variables) -> str:
|
||||
path = self.template_dir / f"{name}.md"
|
||||
template = Template(path.read_text(encoding='utf-8'))
|
||||
return template.safe_substitute(**variables)
|
||||
```
|
||||
|
||||
Note: string.Template uses $variable syntax by default. For {{variable}} syntax,
|
||||
need to override the delimiter pattern or use simple string substitution.
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create test scaffold for prompt templates</name>
|
||||
<files>tests/test_prompt_templates.py</files>
|
||||
<behavior>
|
||||
- Test 1: context_recovery.md template exists
|
||||
- Test 2: command_mapping.md template exists
|
||||
- Test 3: error_recovery.md template exists
|
||||
- Test 4: PromptLoader.render substitutes variables
|
||||
- Test 5: PromptLoader handles missing variables gracefully (safe_substitute)
|
||||
- Test 6: PromptLoader raises error for missing template file
|
||||
</behavior>
|
||||
<action>
|
||||
Create tests/test_prompt_templates.py with test classes:
|
||||
|
||||
class TestPromptTemplatesExist:
|
||||
1. test_context_recovery_template_exists():
|
||||
- Assert prompts_dir / "context_recovery.md" exists
|
||||
|
||||
2. test_command_mapping_template_exists():
|
||||
- Assert prompts_dir / "command_mapping.md" exists
|
||||
|
||||
3. test_error_recovery_template_exists():
|
||||
- Assert prompts_dir / "error_recovery.md" exists
|
||||
|
||||
4. test_templates_contain_variable_placeholders():
|
||||
- Read each template
|
||||
- Assert {{variable}} pattern exists in each
|
||||
|
||||
class TestPromptLoader:
|
||||
5. test_render_substitutes_variables():
|
||||
- Create PromptLoader with prompts directory
|
||||
- Call render("context_recovery", project_name="test-project", last_active_at="2026-03-11")
|
||||
- Assert "test-project" appears in output
|
||||
- Assert "{{project_name}}" does NOT appear
|
||||
|
||||
6. test_render_handles_missing_variables():
|
||||
- Call render with partial variables
|
||||
- Assert output contains literal {{missing_var}} (safe_substitute behavior)
|
||||
|
||||
7. test_render_raises_for_missing_template():
|
||||
- Call render("nonexistent_template")
|
||||
- Assert FileNotFoundError is raised
|
||||
|
||||
Use Path(__file__).parent.parent / "src" / "gsd_openclaw" / "prompts" for prompts_dir.
|
||||
Follow test pattern from tests/test_state_manager.py.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_prompt_templates.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- tests/test_prompt_templates.py exists with 7 tests
|
||||
- Tests fail initially (no templates yet)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create prompt template files</name>
|
||||
<files>
|
||||
- src/gsd_openclaw/prompts/__init__.py
|
||||
- src/gsd_openclaw/prompts/context_recovery.md
|
||||
- src/gsd_openclaw/prompts/command_mapping.md
|
||||
- src/gsd_openclaw/prompts/error_recovery.md
|
||||
</files>
|
||||
<behavior>
|
||||
- Test: All 4 tests in TestPromptTemplatesExist pass
|
||||
</behavior>
|
||||
<action>
|
||||
Create prompts directory and template files:
|
||||
|
||||
1. src/gsd_openclaw/prompts/__init__.py:
|
||||
- Empty file (marks directory as package)
|
||||
|
||||
2. src/gsd_openclaw/prompts/context_recovery.md:
|
||||
```markdown
|
||||
# Context Recovery
|
||||
|
||||
You are resuming a GSD session that was interrupted.
|
||||
|
||||
## Session Information
|
||||
- **Project**: {{project_name}}
|
||||
- **Last Active**: {{last_active_at}}
|
||||
- **Current Phase**: {{current_phase}}
|
||||
- **Commands Executed**: {{command_count}}
|
||||
|
||||
## Last Known State
|
||||
{{last_output_snapshot}}
|
||||
|
||||
## Instructions
|
||||
Continue from where the session left off. Do not repeat completed steps.
|
||||
```
|
||||
|
||||
3. src/gsd_openclaw/prompts/command_mapping.md:
|
||||
```markdown
|
||||
# Command Mapping
|
||||
|
||||
Map the following user input to a GSD slash command.
|
||||
|
||||
## User Input
|
||||
{{user_input}}
|
||||
|
||||
## Available Commands
|
||||
- /gsd:new-project <name> - Create a new project
|
||||
- /gsd:use <project> - Switch to a project
|
||||
- /gsd:status - Show current status
|
||||
- /gsd:plan - Run planning phase
|
||||
- /gsd:ship - Ship current phase
|
||||
- /gsd:help - Show help
|
||||
|
||||
## Current Context
|
||||
- Project: {{current_project}}
|
||||
- Phase: {{current_phase}}
|
||||
|
||||
## Instructions
|
||||
Determine the most appropriate command based on the user input and context.
|
||||
```
|
||||
|
||||
4. src/gsd_openclaw/prompts/error_recovery.md:
|
||||
```markdown
|
||||
# Error Recovery
|
||||
|
||||
An error occurred while processing a GSD command.
|
||||
|
||||
## Error Details
|
||||
**Command**: {{command}}
|
||||
**Error**: {{error_message}}
|
||||
|
||||
## Context
|
||||
- Project: {{project_name}}
|
||||
- Phase: {{current_phase}}
|
||||
|
||||
## Instructions
|
||||
Generate a user-friendly error message in Chinese that:
|
||||
1. Explains what went wrong
|
||||
2. Suggests possible solutions
|
||||
3. Offers next steps the user can take
|
||||
```
|
||||
|
||||
User decision (LOCKED): Template format is Markdown with {{variable}} syntax.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_prompt_templates.py::TestPromptTemplatesExist -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- All 3 template files exist with {{variable}} placeholders
|
||||
- 4 tests in TestPromptTemplatesExist pass
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Implement PromptLoader class</name>
|
||||
<files>src/gsd_openclaw/prompt_loader.py</files>
|
||||
<behavior>
|
||||
- Test: All 7 tests in test_prompt_templates.py pass
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/gsd_openclaw/prompt_loader.py:
|
||||
|
||||
```python
|
||||
"""Prompt template loader for GSD-OpenClaw."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
class PromptLoader:
|
||||
"""Load and render prompt templates with {{variable}} substitution.
|
||||
|
||||
Uses simple regex-based substitution for {{variable}} syntax.
|
||||
Missing variables are left as-is (safe substitution behavior).
|
||||
"""
|
||||
|
||||
def __init__(self, template_dir: Path | None = None):
|
||||
"""Initialize PromptLoader.
|
||||
|
||||
Args:
|
||||
template_dir: Directory containing template files.
|
||||
Defaults to src/gsd_openclaw/prompts/.
|
||||
"""
|
||||
if template_dir is None:
|
||||
template_dir = Path(__file__).parent / "prompts"
|
||||
self.template_dir = Path(template_dir)
|
||||
self._cache: Dict[str, str] = {}
|
||||
|
||||
def get(self, name: str) -> str:
|
||||
"""Get raw template content by name (cached).
|
||||
|
||||
Args:
|
||||
name: Template name without .md extension.
|
||||
|
||||
Returns:
|
||||
Raw template content.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If template file doesn't exist.
|
||||
"""
|
||||
if name not in self._cache:
|
||||
path = self.template_dir / f"{name}.md"
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Template not found: {name}")
|
||||
self._cache[name] = path.read_text(encoding='utf-8')
|
||||
return self._cache[name]
|
||||
|
||||
def render(self, name: str, **variables: Any) -> str:
|
||||
"""Render a template with variable substitution.
|
||||
|
||||
Uses {{variable}} syntax. Missing variables are left as literal
|
||||
{{variable}} strings (safe substitution).
|
||||
|
||||
Args:
|
||||
name: Template name without .md extension.
|
||||
**variables: Variable values to substitute.
|
||||
|
||||
Returns:
|
||||
Rendered template content.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If template file doesn't exist.
|
||||
"""
|
||||
content = self.get(name)
|
||||
for key, value in variables.items():
|
||||
content = content.replace("{{" + key + "}}", str(value))
|
||||
return content
|
||||
```
|
||||
|
||||
Note: Using simple string.replace() for {{variable}} syntax instead of
|
||||
string.Template which uses $variable syntax. This matches the user's
|
||||
decision for {{variable}} format.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_prompt_templates.py -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- PromptLoader class implements get() and render() methods
|
||||
- All 7 tests pass
|
||||
- Missing variables are left as {{variable}} (safe substitution)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Run pytest tests/test_prompt_templates.py -v (all tests pass)
|
||||
- Verify all 3 template files have {{variable}} placeholders
|
||||
- Verify PromptLoader.render substitutes variables correctly
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. src/gsd_openclaw/prompts/ directory exists with __init__.py
|
||||
2. context_recovery.md, command_mapping.md, error_recovery.md templates exist
|
||||
3. PromptLoader class can load and render templates
|
||||
4. Variable substitution uses {{variable}} syntax
|
||||
5. All 7 tests in tests/test_prompt_templates.py pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-integration-layer/02-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user