docs(02): research integration layer domain

This commit is contained in:
2026-03-11 12:22:13 +08:00
parent 55dee6ef16
commit 6c9aa54eda
@@ -0,0 +1,633 @@
# Phase 2: Integration Layer - Research
**Researched:** 2026-03-11
**Domain:** OpenClaw skill integration, Feishu Card Message API, response formatting
**Confidence:** HIGH
## Summary
Phase 2 establishes the "face" of the gsd-openclaw skill - the SKILL.md definition that OpenClaw loads, the response formatting layer that adapts output for Feishu's Card Message format, and the prompt templates for context recovery, command mapping, and error handling. This phase focuses exclusively on Feishu integration as the primary channel.
**Primary recommendation:** Use Feishu Card JSON 2.0 structure with three-zone layout (header/content/actions). Keep prompt templates in pure Markdown with `{{variable}}` syntax for simple string replacement (no Jinja2 dependency needed).
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **触发模式**: Only `/gsd:*` commands, no natural language triggers
- **命令匹配**: Wildcard `/gsd:*` pattern, orchestrator.py validates command validity
- **项目上下文**: Explicit switch via `/gsd:use <project>` to set current project
- **支持频道**: Feishu only (Phase 2)
- **消息格式**: Feishu Card Message
- **交互元素**:
- Interactive buttons (for approval confirmation, operation triggers)
- Dropdown selectors (for project selection)
- Link buttons (jump to OpenClaw control panel)
- **卡片布局规范**: Three-zone structure (title + content + actions)
- **长消息处理**: Pagination with `[1/N]` markers at message end
- **错误消息格式**: Error cards with red theme + guidance buttons
- **GSD审批流程**: Feishu button interaction for approve/reject
- **Prompt模板类型**: `context_recovery.md`, `command_mapping.md`, `error_recovery.md`
- **模板格式**: Markdown with `{{variable}}` syntax
### Claude's Discretion
- Exact character limit per message (based on Feishu API docs)
- Code block rendering in cards
- Card color themes and icon selection
- Pagination break points (avoid truncating code blocks or tables)
### Deferred Ideas (OUT OF SCOPE)
- WeChat channel support (Phase 3+)
- Telegram/Discord channel support (Phase 3+)
- Multi-language support (Chinese/English switching)
- Custom card themes
</user_constraints>
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|-----------------|
| OCLW-01 | SKILL.md has proper frontmatter (name, description, triggers) | SKILL.md YAML frontmatter format documented below; use `name` and `description` fields |
| OCLW-02 | Skill triggers on /gsd:* patterns and project management keywords | OpenClaw matches skill based on `description` field content; use explicit `/gsd:` in description |
| OCLW-04 | Response formatting adapts to channel capabilities (markdown vs plain) | Feishu Card JSON 2.0 structure documented; response_formatter.py pattern established |
</phase_requirements>
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| Python | 3.9+ | Core language | Required by existing codebase |
| dataclasses | stdlib | Data structures | Used in Phase 1, consistent pattern |
| json | stdlib | JSON handling | Card message structure |
| pathlib | stdlib | Path handling | Template file loading |
| re | stdlib | Pattern matching | Command validation |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| string.Template | stdlib | Simple variable substitution | Prompt templates (no Jinja2 needed) |
| typing | stdlib | Type hints | API contracts |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| string.Template | Jinja2 | Jinja2 adds dependency for simple `{{var}}` replacement; Template is sufficient |
| Custom card builder | Feishu Card Builder API | Card Builder is visual tool; programmatic JSON is more maintainable |
**No new pip dependencies required** - all Phase 2 components use Python standard library.
## Architecture Patterns
### Recommended Project Structure
```
src/gsd_openclaw/
├── __init__.py
├── state_manager.py # Phase 1 - existing
├── tmux_session.py # Phase 1 - existing
├── workspace.py # Phase 1 - existing
├── cli.py # Phase 1 - existing
├── response_formatter.py # Phase 2 - NEW
└── prompts/ # Phase 2 - NEW
├── __init__.py
├── context_recovery.md
├── command_mapping.md
└── error_recovery.md
skill/ # Phase 2 - NEW (deployment package)
├── SKILL.md # OpenClaw skill definition
├── orchestrator.py # Phase 3 - placeholder
└── prompts/ # Symlink or copy from src/gsd_openclaw/prompts/
```
### Pattern 1: SKILL.md Frontmatter Format
**What:** YAML frontmatter between `---` delimiters defines skill metadata.
**When to use:** Always - this is required for OpenClaw to load the skill.
**Example:**
```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.
---
# GSD-OpenClaw Skill
## When to Use
This skill triggers when users send messages starting with `/gsd:` ...
## Commands
| Command | Description |
|---------|-------------|
| `/gsd:new-project <name>` | Create a new GSD project |
| `/gsd:use <project>` | Switch to a project context |
...
```
**Source:** OpenClaw skills pattern from [Creating Custom Skills](https://zread.ai/openclaw/openclaw/19-creating-custom-skills) - only `name` and `description` fields are required. The `description` field serves as the trigger condition.
### Pattern 2: Feishu Card JSON 2.0 Structure
**What:** Feishu Card messages use JSON 2.0 schema with three main sections.
**When to use:** Every response sent to Feishu.
**Example:**
```json
{
"schema": "2.0",
"config": {
"wide_screen_mode": true,
"update_multi": true
},
"header": {
"title": {
"tag": "plain_text",
"content": "GSD: new-project"
},
"template": "blue"
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "Project **my-auth** created successfully."
}
}
]
}
```
**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)
**Source:** [Feishu Card JSON 2.0 Structure](https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-json-v2-structure)
### Pattern 3: Interactive Button with Callback
**What:** Buttons trigger callbacks to the skill with custom value data.
**When to use:** Approval flows, action confirmations.
**Example:**
```json
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "Confirm"
},
"type": "primary",
"value": {
"action": "approve",
"project": "my-auth",
"phase": "3"
}
},
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "Cancel"
},
"type": "default",
"value": {
"action": "reject",
"project": "my-auth"
}
}
]
}
```
**Source:** [Feishu Button Component](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/interactive-components/button)
### Pattern 4: Prompt Template with Variable Substitution
**What:** Markdown templates with `{{variable}}` placeholders.
**When to use:** Context recovery, error messages, command mapping.
**Example (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.
```
**Implementation:**
```python
from string import Template
from pathlib import Path
def load_template(name: str) -> Template:
"""Load a prompt template by name."""
template_path = Path(__file__).parent / "prompts" / f"{name}.md"
return Template(template_path.read_text(encoding='utf-8'))
def render_template(name: str, **variables) -> str:
"""Render a template with variables."""
template = load_template(name)
return template.safe_substitute(**variables)
```
**Note:** Use `safe_substitute()` to leave unmatched variables as-is instead of raising KeyError.
### Pattern 5: Response Formatter Class
**What:** Centralized formatting for all Feishu responses.
**When to use:** Every response going to Feishu channel.
**Example:**
```python
from dataclasses import dataclass
from typing import List, Optional, Dict, Any
from enum import Enum
class CardTheme(Enum):
SUCCESS = "green"
ERROR = "red"
INFO = "blue"
WARNING = "orange"
@dataclass
class CardButton:
text: str
value: Dict[str, Any]
button_type: str = "primary" # primary, default, danger
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."""
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."""
return self._build_card(title, content, CardTheme.ERROR, buttons)
def format_paginated(self, title: str, content: str,
page: int, total_pages: int) -> dict:
"""Format a paginated response."""
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
]
}
```
### Anti-Patterns to Avoid
- **Hardcoding full card JSON in multiple places**: Use ResponseFormatter class consistently
- **Jinja2 for simple templates**: `string.Template` is sufficient for `{{var}}` syntax
- **Exceeding 30KB card size**: Paginate long outputs (card JSON body max 30KB)
- **Mixing card schema versions**: Always use `"schema": "2.0"` consistently
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Card JSON construction | String concatenation | ResponseFormatter class | Type safety, consistency |
| Template variable substitution | regex replace | string.Template | Edge cases (escaped braces) |
| Markdown to Feishu format | Custom converter | lark_md tag | Feishu has built-in markdown support |
**Key insight:** Feishu's `lark_md` tag supports standard markdown - no conversion needed. Just wrap markdown content in `{"tag": "lark_md", "content": "..."}`.
## Common Pitfalls
### Pitfall 1: Card Size Exceeded
**What goes wrong:** Feishu rejects messages > 30KB JSON body.
**Why it happens:** Long GSD outputs (especially with code blocks) can exceed limit.
**How to avoid:** Implement pagination in ResponseFormatter. Check content size before sending.
**Warning signs:** `The length of the message content reaches its limit` error.
### Pitfall 2: Missing Button Callback Value
**What goes wrong:** Button clicks don't trigger expected action.
**Why it happens:** `value` field missing or improperly formatted in button JSON.
**How to avoid:** Always include `value` dict with action metadata. Test button clicks manually.
**Warning signs:** Button renders but clicking produces no response.
### Pitfall 3: Template Variable Not Substituted
**What goes wrong:** `{{variable}}` appears literally in output.
**Why it happens:** Variable name mismatch or using `substitute()` instead of `safe_substitute()`.
**How to avoid:** Use `safe_substitute()`, validate template variables in tests.
**Warning signs:** Raw `{{...}}` strings in generated prompts.
### Pitfall 4: Wrong Card Schema Version
**What goes wrong:** Card components don't render correctly.
**Why it happens:** Using JSON 1.0 structure with 2.0 expectations.
**How to avoid:** Always include `"schema": "2.0"` at card root. Check client version >= 7.20.
**Warning signs:** Missing elements, broken layouts.
## Code Examples
### Complete Feishu Card Example (Success with Buttons)
```python
# Source: Based on Feishu Card JSON 2.0 documentation
# https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-json-v2-structure
{
"schema": "2.0",
"config": {
"wide_screen_mode": True,
"update_multi": True
},
"header": {
"title": {
"tag": "plain_text",
"content": "GSD: Phase 3 Ready for Review"
},
"template": "blue"
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**Project**: my-auth\n**Phase**: 3 - Orchestration\n**Status**: Planning complete, awaiting approval\n\n**Summary**:\n- 4 tasks defined\n- Estimated time: 2 hours\n- Dependencies: Phase 1 (complete)"
}
},
{
"tag": "hr"
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"tag": "plain_text", "content": "Approve"},
"type": "primary",
"value": {"action": "approve", "project": "my-auth", "phase": "3"}
},
{
"tag": "button",
"text": {"tag": "plain_text", "content": "View Details"},
"type": "default",
"value": {"action": "details", "project": "my-auth"}
}
]
}
]
}
```
### Error Card Example
```python
# Error card with guidance buttons
{
"schema": "2.0",
"config": {"wide_screen_mode": True},
"header": {
"title": {"tag": "plain_text", "content": "Error: Project Not Found"},
"template": "red"
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "Project `nonexistent-project` does not exist.\n\n**Suggestion**: Create a new project or select an existing one."
}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"tag": "plain_text", "content": "Create New"},
"type": "primary",
"value": {"action": "create_project", "name": "nonexistent-project"}
},
{
"tag": "button",
"text": {"tag": "plain_text", "content": "My Projects"},
"type": "default",
"value": {"action": "list_projects"}
}
]
}
]
}
```
### Template Loading and Rendering
```python
# Source: Python standard library pattern
from string import Template
from pathlib import Path
from typing import Dict, Any
class PromptTemplateLoader:
"""Load and render prompt templates."""
def __init__(self, template_dir: Path):
self.template_dir = template_dir
self._cache: Dict[str, Template] = {}
def get(self, name: str) -> Template:
"""Get a template by name (cached)."""
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] = Template(path.read_text(encoding='utf-8'))
return self._cache[name]
def render(self, name: str, **variables: Any) -> str:
"""Render a template with variables."""
template = self.get(name)
return template.safe_substitute(**variables)
# Usage
loader = PromptTemplateLoader(Path(__file__).parent / "prompts")
prompt = loader.render("context_recovery",
project_name="my-auth",
last_active_at="2026-03-11T10:00:00Z",
current_phase="3",
command_count="15",
last_output_snapshot="Last task completed: orchestrator.py scaffold"
)
```
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Feishu Card JSON 1.0 | Card JSON 2.0 | Client 7.20+ | Better structure, streaming updates |
| Hardcoded JSON strings | Card Builder + Templates | 2025+ | Maintainability, consistency |
| Plain text responses | Card messages | OpenClaw standard | Rich UI, interactivity |
**Deprecated/outdated:**
- Feishu Card JSON 1.0: Use 2.0 schema (`"schema": "2.0"` required)
- Raw text messages: Always use card format for structured responses
## Open Questions
1. **Pagination break point algorithm**
- What we know: Cards max 30KB, need to avoid mid-code-block splits
- What's unclear: Best algorithm for smart break points
- Recommendation: Split at paragraph boundaries; if code block > limit, split at line boundaries with continuation marker
2. **Button callback endpoint configuration**
- What we know: Feishu sends callbacks to configured URL
- What's unclear: How OpenClaw routes callbacks to skill (Phase 3 concern)
- Recommendation: Design button values to be self-contained (include all needed context)
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | pytest (existing from Phase 1) |
| Config file | None - conftest.py pattern established |
| Quick run command | `pytest tests/test_response_formatter.py -v` |
| Full suite command | `pytest tests/ -v` |
### Phase Requirements -> Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| OCLW-01 | SKILL.md has proper frontmatter | unit | `pytest tests/test_skill_md.py -v` | Wave 0 |
| OCLW-02 | Skill triggers on /gsd:* patterns | unit | `pytest tests/test_skill_md.py::test_trigger_pattern -v` | Wave 0 |
| OCLW-04 | Response formatting adapts to channel | unit | `pytest tests/test_response_formatter.py -v` | Wave 0 |
### Sampling Rate
- **Per task commit:** `pytest tests/test_response_formatter.py tests/test_skill_md.py -v`
- **Per wave merge:** `pytest tests/ -v`
- **Phase gate:** Full suite green before `/gsd:verify-work`
### Wave 0 Gaps
- [ ] `tests/test_response_formatter.py` - tests for FeishuResponseFormatter class
- [ ] `tests/test_skill_md.py` - tests for SKILL.md structure and trigger patterns
- [ ] `tests/test_prompt_templates.py` - tests for template loading and rendering
- [ ] `src/gsd_openclaw/prompts/` directory - template files need creation
### Test Patterns to Follow
```python
# tests/test_response_formatter.py - Pattern from test_state_manager.py
import pytest
from gsd_openclaw.response_formatter import (
FeishuResponseFormatter, CardTheme, CardButton
)
class TestFeishuResponseFormatter:
"""Test response formatting for Feishu cards."""
def test_format_success_returns_valid_card(self):
"""Success card has green theme."""
formatter = FeishuResponseFormatter()
card = formatter.format_success("Test", "Content")
assert card["schema"] == "2.0"
assert card["header"]["template"] == "green"
assert card["header"]["title"]["content"] == "Test"
def test_format_error_returns_red_theme(self):
"""Error card has red theme."""
formatter = FeishuResponseFormatter()
card = formatter.format_error("Error", "Something went wrong")
assert card["header"]["template"] == "red"
def test_format_with_buttons_includes_actions(self):
"""Cards with buttons include action element."""
formatter = FeishuResponseFormatter()
buttons = [CardButton("OK", {"action": "ok"})]
card = formatter.format_success("Test", "Content", buttons=buttons)
actions = [e for e in card["elements"] if e["tag"] == "action"]
assert len(actions) == 1
assert actions[0]["actions"][0]["text"]["content"] == "OK"
```
## Sources
### Primary (HIGH confidence)
- [Feishu Card JSON 2.0 Structure](https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-json-v2-structure) - official documentation
- [Feishu Button Component](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/interactive-components/button) - interactive element spec
- [Feishu Card Callback Communication](https://open.feishu.cn/document/feishu-cards/card-callback-communication) - callback structure
- [OpenClaw Creating Custom Skills](https://zread.ai/openclaw/openclaw/19-creating-custom-skills) - SKILL.md format
- Phase 1 codebase: `/home/starmen1265/work/kb-skills/gsd-openclaw/src/gsd_openclaw/` - established patterns
### Secondary (MEDIUM confidence)
- [Feishu Card Overview](https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/feishu-card-overview) - general card concepts
- [Feishu Card Builder Tool](https://open.feishu.cn/document/tools-and-resources/message-card-builder) - visual builder reference
- [KB Workflow SKILL.md](file:///home/starmen1265/work/kb-skills/kb-workflow/SKILL.md) - local skill example
- [KB Whisper SKILL.md](file:///home/starmen1265/work/kb-skills/kb-whisper/SKILL.md) - local skill example
### Tertiary (LOW confidence)
- [Feishu API - Message Limits](https://feishu.apifox.cn/api-9020992) - 30KB card limit (third-party docs)
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH - Python stdlib only, no new dependencies
- Architecture: HIGH - Based on official Feishu docs and existing Phase 1 patterns
- Pitfalls: HIGH - Documented from official API limits and common integration issues
**Research date:** 2026-03-11
**Valid until:** 30 days (stable APIs, but Feishu may update card schema)