diff --git a/gsd-openclaw/.planning/ROADMAP.md b/gsd-openclaw/.planning/ROADMAP.md index abf83e7..f6ab9c4 100644 --- a/gsd-openclaw/.planning/ROADMAP.md +++ b/gsd-openclaw/.planning/ROADMAP.md @@ -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* diff --git a/gsd-openclaw/.planning/phases/02-integration-layer/02-01-PLAN.md b/gsd-openclaw/.planning/phases/02-integration-layer/02-01-PLAN.md new file mode 100644 index 0000000..95a10ff --- /dev/null +++ b/gsd-openclaw/.planning/phases/02-integration-layer/02-01-PLAN.md @@ -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:" +--- + + +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 + + + +@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md +@/home/starmen1265/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + +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. +--- +``` + + + + + + + Task 1: Create test scaffold for SKILL.md validation + tests/test_skill_md.py + + - 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 + + + 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). + + + pytest tests/test_skill_md.py -v + + + - tests/test_skill_md.py exists with 5 passing tests + - All tests fail initially (no SKILL.md yet) + + + + + Task 2: Create SKILL.md with valid frontmatter + skill/SKILL.md + + - Test: All tests in test_skill_md.py pass + + + 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. + + + pytest tests/test_skill_md.py -v + + + - 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 + + + + + + +- 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 + + + +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 + + + +After completion, create `.planning/phases/02-integration-layer/02-01-SUMMARY.md` + diff --git a/gsd-openclaw/.planning/phases/02-integration-layer/02-02-PLAN.md b/gsd-openclaw/.planning/phases/02-integration-layer/02-02-PLAN.md new file mode 100644 index 0000000..9eece6b --- /dev/null +++ b/gsd-openclaw/.planning/phases/02-integration-layer/02-02-PLAN.md @@ -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"' +--- + + +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 + + + +@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md +@/home/starmen1265/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + +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). + + + + + + + Task 1: Create test scaffold for FeishuResponseFormatter + tests/test_response_formatter.py + + - 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 + + + 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. + + + pytest tests/test_response_formatter.py -v + + + - tests/test_response_formatter.py exists with 6 tests + - All tests fail initially (no implementation yet) + + + + + Task 2: Implement CardTheme enum and CardButton dataclass + src/gsd_openclaw/response_formatter.py + + - Test: CardButton can be created with text, value, button_type + - Test: CardTheme enum has SUCCESS, ERROR, INFO, WARNING values + + + 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. + + + 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)" + + + - CardTheme enum exists with SUCCESS, ERROR, INFO, WARNING + - CardButton dataclass exists with text, value, button_type + + + + + Task 3: Implement FeishuResponseFormatter class + src/gsd_openclaw/response_formatter.py + + - Test: All 6 tests in test_response_formatter.py pass + + + 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. + + + pytest tests/test_response_formatter.py -v + + + - FeishuResponseFormatter class implements all format methods + - All 6 tests pass + - Card JSON uses schema 2.0 with three-zone structure + + + + + + +- 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) + + + +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 + + + +After completion, create `.planning/phases/02-integration-layer/02-02-SUMMARY.md` + diff --git a/gsd-openclaw/.planning/phases/02-integration-layer/02-03-PLAN.md b/gsd-openclaw/.planning/phases/02-integration-layer/02-03-PLAN.md new file mode 100644 index 0000000..3b6beb0 --- /dev/null +++ b/gsd-openclaw/.planning/phases/02-integration-layer/02-03-PLAN.md @@ -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}}" +--- + + +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 + + + +@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md +@/home/starmen1265/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + +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. + + + + + + + Task 1: Create test scaffold for prompt templates + tests/test_prompt_templates.py + + - 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 + + + 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. + + + pytest tests/test_prompt_templates.py -v + + + - tests/test_prompt_templates.py exists with 7 tests + - Tests fail initially (no templates yet) + + + + + Task 2: Create prompt template 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 + + + - Test: All 4 tests in TestPromptTemplatesExist pass + + + 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. + + + pytest tests/test_prompt_templates.py::TestPromptTemplatesExist -v + + + - All 3 template files exist with {{variable}} placeholders + - 4 tests in TestPromptTemplatesExist pass + + + + + Task 3: Implement PromptLoader class + src/gsd_openclaw/prompt_loader.py + + - Test: All 7 tests in test_prompt_templates.py pass + + + 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. + + + pytest tests/test_prompt_templates.py -v + + + - PromptLoader class implements get() and render() methods + - All 7 tests pass + - Missing variables are left as {{variable}} (safe substitution) + + + + + + +- 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 + + + +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 + + + +After completion, create `.planning/phases/02-integration-layer/02-03-SUMMARY.md` +