docs(02): create phase 2 integration layer plans

This commit is contained in:
2026-03-11 12:29:22 +08:00
parent 0ab884bc2a
commit 637240c546
4 changed files with 844 additions and 3 deletions
+8 -3
View File
@@ -45,7 +45,12 @@ Plans:
1. SKILL.md has valid frontmatter (name, description, triggers) and OpenClaw loads the skill successfully 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 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) 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 ### Phase 3: Orchestration
**Goal**: Users can send GSD slash commands through chat and receive captured output with proper error handling **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 | | Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 1. Core Infrastructure | 3/4 | In Progress | - | | 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 | - | | 3. Orchestration | 0/TBD | Not started | - |
| 4. GSD Integration | 0/TBD | Not started | - | | 4. GSD Integration | 0/TBD | Not started | - |
--- ---
*Roadmap created: 2026-03-10* *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 &lt;name&gt; | Create a new GSD project |
| /gsd:use &lt;project&gt; | 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 &lt;name&gt; - Create a new project
- /gsd:use &lt;project&gt; - 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>