Skill: utopiabenben/workspace-heartbeat-integration (version 1.0.0)

# Workspace Heartbeat Integration ## When to Use Use this skill when you want to: - Automatically record work成果 during heartbeat checks - Synchronize workspace HEARTBEAT.md with self-improving heartbeat-state.md - Reduce manual memory updates by automating the logging process - Maintain consistent heartbeat state across different memory systems **Triggers:** - During routine heartbeat checks (every 30min/1h/2h/daily) - When manually syncing work logs to MEMORY.md - When updating TASK_BOARD.md progress - When you want to automate self-tracking ## Architecture This skill integrates three systems: 1. **Workspace HEARTBEAT.md** - Task checklist and timing rules 2. **Self-improving heartbeat-state.md** - Per-skill heartbeat tracking 3. **Workspace memory/** - Daily learning logs and MEMORY.md ``` ~/.openclaw/workspace/ ├── HEARTBEAT.md # Workspace heartbeat rules ├── MEMORY.md # Long-term memory (curated) ├── memory/ │ ├── 2026-03-20.md # Daily logs (raw) │ └── heartbeat-state.json # Workspace heartbeat state └── skills/ └── workspace-heartbeat-integration/ ├── SKILL.md ├── skill.json └── source/ └── integration.py # Core logic ``` ## Actions ### Sync Heartbeat State ```bash workspace-heartbeat-integration --sync ``` Performs a full synchronization: 1. Reads current workspace heartbeat-state.json 2. Updates timestamps based on last check times 3. Scans memory/YYYY-MM-DD.md for today's entries 4. Generates a summary of today's work 5. Optionally updates MEMORY.md with new insights ### Auto-log Work Session ```bash workspace-heartbeat-integration --log "Completed skill X development" ``` Adds a work entry to today's memory file: - Date and timestamp - Work category (learning, development, debugging, etc.) - Detailed description - Links to related files (if any) ### Generate Heartbeat Report ```bash workspace-heartbeat-integration --report [--format text|markdown|json] ``` Generates a comprehensive heartbeat report: - Last check times for all intervals - Today's work summary - Pending tasks from TASK_BOARD.md - Skill development progress - Suggestions for next actions ## Configuration The skill reads configuration from: - `~/.config/workspace-heartbeat-integration/config.json` (optional) Example config: ```json { "auto_sync": true, "log_retention_days": 30, "memory_update_threshold": 3, "excluded_dirs": [".git", "node_modules", "__pycache__"] } ``` ## Integration with Self-Improving When installed alongside the `self-improving` skill: - Heartbeat logs become candidate patterns for self-improvement - Repeated work patterns can be promoted to HOT memory - Corrections from manual reviews feed into corrections.md ## Setup 1. Install the skill: ```bash clawhub install workspace-heartbeat-integration ``` 2. (Optional) Configure integration in HEARTBEAT.md: Add to your heartbeat check routine: ```markdown ### 每 30 分钟检查： 1. 📊 回顾当前进度，更新学习日志 2. 📋 查看任务看板，找下一个任务 3. 💡 同步心跳状态：workspace-heartbeat-integration --sync ``` 3. Test the integration: ```bash workspace-heartbeat-integration --sync ``` ## Example Workflow **Typical heartbeat check (30min interval):** ``` 1. 查看是否有紧急消息/邮件 2. 检查任务看板，选择下一个任务 3. 开始执行（例如：开发 skill-validator） 4. 完成后记录：workspace-heartbeat-integration --log "Finished parsing SKILL.md frontmatter" 5. Sync state: workspace-heartbeat-integration --sync ``` **Daily summary (end of day):** ```bash workspace-heartbeat-integration --report --format markdown > daily_summary.md ``` ## Benefits - **Automation**: No manual copying of timestamps and work entries - **Consistency**: Uniform format for all daily logs - **Traceability**: Every heartbeat check is logged with context - **Self-improvement**: Work patterns become visible for optimization - **Accountability**: Clear record of what was done each day ## Technical Details - **Language**: Python 3.8+ - **Dependencies**: None (uses only standard library) - **Python modules**: json, os, datetime, pathlib, glob, re - **Thread-safe**: Uses file locks to prevent concurrent modifications - **Idempotent**: Safe to run multiple times (won't duplicate entries) ## Limitations - Does not automatically update ClawHub download stats (browser tool required) - Requires write access to workspace memory/ directory - Does not parse git history for commit messages (future enhancement) ## Future Enhancements - Integration with browser tool for ClawHub stats - Git commit message parsing - Automatic TASK_BOARD.md progress updates - Weekly/Monthly trend analysis - Export to Notion/Google Sheets --- **Version**: 1.0.0 **Last Updated**: 2026-03-20 **Author**: 小叮当 (智脑)

path: install.sh
content: #!/bin/bash
set -e

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE="/root/.openclaw/workspace"

echo "🚀 Installing Workspace Heartbeat Integration..."

# 1. Verify Python 3.8+
if ! command -v python3 &> /dev/null; then
    echo "❌ Python 3 is required but not found"
    exit 1
fi

PY_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
REQUIRED="3.8"
if [ "$(printf '%s\n' "$REQUIRED" "$PY_VERSION" | sort -V | head -n1)" != "$REQUIRED" ]; then
    echo "❌ Python $REQUIRED+ required, found $PY_VERSION"
    exit 1
fi

echo "✅ Python $PY_VERSION detected"

# 2. Install skill to OpenClaw
echo "📦 Publishing skill to ClawHub..."
if command -v clawhub &> /dev/null; then
    cd "$SKILL_DIR"
    clawhub publish . --slug workspace-heartbeat-integration --name "Workspace Heartbeat Integration" --version 1.0.0 --changelog "自动同步 HEARTBEAT 和 self-improving 状态"
    echo "✅ Skill published successfully"
else
    echo "⚠️  clawhub CLI not found, skipping publish"
    echo "   Install with: npm install -g clawhub"
fi

# 3. Create symlink for CLI command (optional)
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"
ln -sf "$SKILL_DIR/source/integration.py" "$INSTALL_DIR/workspace-heartbeat-integration"
chmod +x "$SKILL_DIR/source/integration.py"

if [[ ":$PATH:" == *":$INSTALL_DIR:"* ]]; then
    echo "✅ CLI command installed to $INSTALL_DIR (already in PATH)"
else
    echo "⚠️  CLI installed to $INSTALL_DIR but not in PATH"
    echo "   Add to PATH: export PATH=\"$INSTALL_DIR:\$PATH\""
fi

# 4. Create default config
CONFIG_DIR="${HOME}/.config/workspace-heartbeat-integration"
mkdir -p "$CONFIG_DIR"
cat > "$CONFIG_DIR/config.json" << 'EOF'
{
  "auto_sync": true,
  "log_retention_days": 30,
  "memory_update_threshold": 3,
  "excluded_dirs": [".git", "node_modules", "__pycache__", ".venv"]
}
EOF
echo "✅ Default config created at $CONFIG_DIR/config.json"

# 5. Verify installation
echo ""
echo "🎉 Installation complete!"
echo ""
echo "Usage:"
echo "  workspace-heartbeat-integration --sync               # Sync state"
echo "  workspace-heartbeat-integration --log learning:desc # Log work"
echo "  workspace-heartbeat-integration --report --format markdown  # Generate report"
echo ""
echo "Next step: Add to your HEARTBEAT.md to automate:"
echo "  workspace-heartbeat-integration --sync"
echo ""

path: requirements.txt
content: # No external dependencies - uses Python standard library only

path: skill.json
content: {
  "name": "Workspace Heartbeat Integration",
  "slug": "workspace-heartbeat-integration",
  "version": "1.0.0",
  "description": "将 workspace HEARTBEAT.md 与 self-improving heartbeat-state 同步，自动记录工作成果并更新长期记忆",
  "homepage": "https://clawhub.ai/skills/workspace-heartbeat-integration",
  "dependencies": {
    "bins": [],
    "python": ["3.8+"]
  },
  "configPaths": ["~/workspace-heartbeat-integration/"]
}

path: source/integration.py
content: #!/usr/bin/env python3
"""
Workspace Heartbeat Integration
Synchronizes workspace heartbeat tracking with self-improving system.
"""

import json
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional

# Workspace paths
WORKSPACE = Path("/root/.openclaw/workspace")
MEMORY_DIR = WORKSPACE / "memory"
HEARTBEAT_STATE = MEMORY_DIR / "heartbeat-state.json"
TASK_BOARD = WORKSPACE / "TASK_BOARD.md"
MEMORY_MD = WORKSPACE / "MEMORY.md"


class HeartbeatIntegration:
    def __init__(self, config_path: Optional[Path] = None):
        self.workspace = WORKSPACE
        self.memory_dir = MEMORY_DIR
        self.state_file = HEARTBEAT_STATE
        self.config = self._load_config(config_path)

    def _load_config(self, config_path: Optional[Path]) -> Dict:
        default_config = {
            "auto_sync": True,
            "log_retention_days": 30,
            "memory_update_threshold": 3,
            "excluded_dirs": [".git", "node_modules", "__pycache__", ".venv"],
        }
        if config_path and config_path.exists():
            try:
                with open(config_path) as f:
                    user_config = json.load(f)
                    default_config.update(user_config)
            except Exception as e:
                print(f"Warning: failed to load config: {e}")
        return default_config

    def load_state(self) -> Dict:
        if not self.state_file.exists():
            return {"lastChecks": {}, "notes": ""}
        with open(self.state_file) as f:
            return json.load(f)

    def save_state(self, state: Dict) -> None:
        self.state_file.parent.mkdir(parents=True, exist_ok=True)
        with open(self.state_file, "w") as f:
            json.dump(state, f, indent=2, ensure_ascii=False)

    def get_today_date(self) -> str:
        return datetime.now().strftime("%Y-%m-%d")

    def get_today_log_path(self) -> Path:
        return self.memory_dir / self.get_today_date()

    def ensure_today_log(self) -> Path:
        log_path = self.get_today_log_path()
        if not log_path.exists():
            log_path.parent.mkdir(parents=True, exist_ok=True)
            with open(log_path, "w", encoding="utf-8") as f:
                f.write(f"# {log_path.name} 学习日志\n\n## 🕐 时间线\n\n**凌晨 (4:00-6:00)**\n- 00:00 心跳检查，开始新一天\n\n")
        return log_path

    def append_work_log(self, category: str, description: str, details: Optional[str] = None) -> Path:
        """Append a work log entry to today's memory file."""
        log_path = self.ensure_today_log()
        timestamp = datetime.now().strftime("%H:%M")
        category_icons = {
            "learning": "📚",
            "development": "🏗️",
            "testing": "🧪",
            "debugging": "🔧",
            "documentation": "📝",
            "release": "🚀",
            "research": "🔍",
            "optimization": "⚡",
            "meeting": "🤝",
            "other": "💡"
        }
        icon = category_icons.get(category.lower(), "•")

        entry = f"- {timestamp} {category.title()}: {description}\n"
        if details:
            entry += f"  Details: {details}\n"

        with open(log_path, "a", encoding="utf-8") as f:
            f.write(entry)

        print(f"✅ Logged work: {description}")
        return log_path

    def sync(self, dry_run: bool = False) -> Dict:
        """Perform full synchronization of heartbeat state."""
        print("🔄 Starting heartbeat sync...")

        state = self.load_state()
        now = int(datetime.now().timestamp())

        # Update last sync timestamp
        state.setdefault("lastSync", now)

        # Check which intervals need updating based on last check times
        intervals = {
            "hourly": 3600,
            "twoHourly": 7200,
            "fourHourly": 14400,
            "daily": 86400
        }

        for key, interval in intervals.items():
            last_check = state.get("lastChecks", {}).get(key, 0)
            if now - last_check >= interval:
                print(f"  • {key} check due")
                state.setdefault("lastChecks", {})[key] = now

        # Scan today's log and generate summary
        today = self.get_today_date()
        log_path = self.memory_dir / today
        if log_path.exists():
            entry_count = sum(1 for _ in open(log_path, encoding="utf-8") if "- " in _)
            state.setdefault("todayStats", {})[today] = {
                "entries": entry_count,
                "lastUpdated": now
            }
            print(f"  • Today's log: {entry_count} entries")

        # If dry_run, don't save
        if not dry_run:
            self.save_state(state)
            print(f"✅ Sync completed at {datetime.fromtimestamp(now).strftime('%H:%M:%S')}")
        else:
            print("ℹ️ Dry run mode, state not saved")

        return state

    def generate_report(self, output_format: str = "text") -> str:
        """Generate a comprehensive heartbeat report."""
        state = self.load_state()
        today = self.get_today_date()
        log_path = self.memory_dir / today

        report_lines = [
            "📊 Heartbeat Report",
            f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
            "",
            "## Last Checks",
        ]

        for check, ts in state.get("lastChecks", {}).items():
            dt = datetime.fromtimestamp(ts).strftime("%H:%M:%S") if ts else "Never"
            report_lines.append(f"- {check}: {dt}")

        report_lines.append("")
        report_lines.append("## Today's Work Summary")

        if log_path.exists():
            with open(log_path, encoding="utf-8") as f:
                content = f.read()
                # Extract actual work entries (skip header)
                lines = [l for l in content.splitlines() if l.strip().startswith("- ")]
                report_lines.extend(lines[:20])  # Limit to 20 entries
                if len(lines) > 20:
                    report_lines.append(f"... and {len(lines)-20} more entries")
        else:
            report_lines.append("No log entries for today yet.")

        # Task board summary
        if TASK_BOARD.exists():
            report_lines.append("")
            report_lines.append("## Pending High-Priority Tasks")
            with open(TASK_BOARD) as f:
                content = f.read()
                # Simple parsing: look for "🚧 开发中" section
                if "🚧 开发中" in content:
                    report_lines.append("(See TASK_BOARD.md for full list)")

        report = "\n".join(report_lines)

        if output_format == "json":
            import json
            report = json.dumps({
                "generated": datetime.now().isoformat(),
                "lastChecks": state.get("lastChecks", {}),
                "todayLog": str(log_path) if log_path.exists() else None,
                "summary": report
            }, indent=2, ensure_ascii=False)

        return report


def main():
    import argparse
    parser = argparse.ArgumentParser(description="Workspace Heartbeat Integration")
    parser.add_argument("--sync", action="store_true", help="Sync heartbeat state")
    parser.add_argument("--log", metavar="CATEGORY:DESCRIPTION", help="Log work session (format: 'category:description')")
    parser.add_argument("--report", action="store_true", help="Generate heartbeat report")
    parser.add_argument("--format", choices=["text", "markdown", "json"], default="text", help="Report format")
    parser.add_argument("--dry-run", action="store_true", help="Do not save changes")
    args = parser.parse_args()

    integration = HeartbeatIntegration()

    if args.log:
        try:
            category, description = args.log.split(":", 1)
            integration.append_work_log(category.strip(), description.strip())
        except ValueError:
            print("Error: --log requires format 'category:description'")
            sys.exit(1)

    if args.sync:
        integration.sync(dry_run=args.dry_run)

    if args.report:
        report = integration.generate_report(output_format=args.format)
        print(report)

    # If no arguments, show help
    if not any([args.sync, args.log, args.report]):
        parser.print_help()


if __name__ == "__main__":
    main()

path: tests/test_basic.py
content: """
Unit tests for workspace-heartbeat-integration
"""

import os
import sys
import json
import tempfile
from datetime import datetime
from pathlib import Path

# Add source to path
SKILL_DIR = Path(__file__).parent.parent
sys.path.insert(0, str(SKILL_DIR / "source"))

from integration import HeartbeatIntegration


def test_heartbeat_integration_basic():
    """Test basic initialization and config loading."""
    with tempfile.TemporaryDirectory() as tmpdir:
        os.environ["WORKSPACE"] = tmpdir  # Override workspace for testing
        integration = HeartbeatIntegration()
        assert integration.workspace is not None
        print("✅ Basic initialization OK")


def test_ensure_today_log():
    """Test today's log creation."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)
        memory_dir = tmp / "memory"
        memory_dir.mkdir()
        state_file = memory_dir / "heartbeat-state.json"
        state_file.write_text("{}")

        # Patch paths
        integration = HeartbeatIntegration()
        integration.workspace = tmp
        integration.memory_dir = memory_dir
        integration.state_file = state_file

        log_path = integration.ensure_today_log()
        assert log_path.exists()
        content = log_path.read_text()
        assert "学习日志" in content
        print("✅ Today log creation OK")


def test_append_work_log():
    """Test appending work log entries."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)
        memory_dir = tmp / "memory"
        memory_dir.mkdir()
        state_file = memory_dir / "heartbeat-state.json"
        state_file.write_text("{}")

        integration = HeartbeatIntegration()
        integration.workspace = tmp
        integration.memory_dir = memory_dir
        integration.state_file = state_file

        log_path = integration.append_work_log("learning", "Test skill development")
        content = log_path.read_text()
        assert "Test skill development" in content
        print("✅ Work log append OK")


def test_sync():
    """Test heartbeat sync."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)
        memory_dir = tmp / "memory"
        memory_dir.mkdir()
        state_file = memory_dir / "heartbeat-state.json"
        state_file.write_text('{"lastChecks": {}}')

        integration = HeartbeatIntegration()
        integration.workspace = tmp
        integration.memory_dir = memory_dir
        integration.state_file = state_file

        state = integration.sync(dry_run=True)
        assert "lastChecks" in state
        print("✅ Sync (dry run) OK")


def test_generate_report():
    """Test report generation."""
    import integration
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)
        memory_dir = tmp / "memory"
        memory_dir.mkdir()
        state_file = memory_dir / "heartbeat-state.json"
        state_file.write_text('{"lastChecks": {"hourly": 1234567890}, "notes": "Test"}')

        # Patch TASK_BOARD to a temp file with some content
        task_board = tmp / "TASK_BOARD.md"
        task_board.write_text("## 🚧 开发中\n... content ...\n")
        integration.TASK_BOARD = task_board

        integration_obj = integration.HeartbeatIntegration()
        integration_obj.workspace = tmp
        integration_obj.memory_dir = memory_dir
        integration_obj.state_file = state_file

        report = integration_obj.generate_report(output_format="text")
        assert "Heartbeat Report" in report
        assert "Last Checks" in report  # Section header
        print("✅ Report generation OK")


if __name__ == "__main__":
    print("🧪 Running workspace-heartbeat-integration tests...\n")
    test_heartbeat_integration_basic()
    test_ensure_today_log()
    test_append_work_log()
    test_sync()
    test_generate_report()
    print("\n✅ All tests passed!")

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)