Skill: yj85814/spec-engine (version 3.0.0)

--- name: spec-engine description: | 项目规格自动生成与验证工具 — 从想法到任务清单的全流程自动化。 支持：(1) 智能生成 spec (2) 可配置验证评分 (3) 自动拆解子任务 (4) Web 仪表盘 (5) 版本对比 (6) 历史分析 version: 3.0.0 tags: spec, project, validation, generator, tasks, dashboard --- # spec-engine v3.0 > 项目规格自动生成与验证工具 — 从想法到任务清单的全流程自动化 ## 功能一览 | 命令 | 说明 | |------|------| | `generate` | 输入项目描述 → 智能提取 → 生成完整 spec | | `validate` | 验证 spec 完整性，100分制评分 + A/B/C/D 等级 | | `decompose` | 从 spec 中提取功能 → 自动拆解子任务 + 工时 + 依赖 + 负责人 | | `analyze` | 扫描目录，分析历史 spec 数据统计 | | `dashboard` | 生成 Web 仪表盘，可视化所有 spec 状态 | | `compare` | 对比两个 spec 版本的差异 | ## 快速开始 ### generate — 智能生成 spec ```bash python scripts/generate.py -i <项目描述文件> [-o spec.md] [--format brief|detailed] ``` 自动识别技术栈、推断文件结构、估算时间、识别风险。 ### validate — 可配置验证 ```bash python scripts/validate.py <spec文件> [--rules rules.json] [--strict] [--json] ``` 支持自定义规则、4维检查、100分评分制。 ### decompose — 任务拆解（v3.0 新增） ```bash python scripts/decompose.py -i <spec文件> [-o tasks.md] [--format table|list] [--json] ``` 从 spec 中提取功能需求，自动拆解为子任务清单，含工时估算、依赖关系、负责人建议、关键路径分析。 ### analyze — 历史分析 ```bash python scripts/analyze.py [--dir <目录>] [--output report.md] [--json] ``` ### dashboard — Web 仪表盘（v3.0 新增） ```bash python scripts/dashboard.py [-d <目录>] [-o dashboard.html] ``` 生成深色主题 HTML 仪表盘，展示所有 spec 的评分、技术栈分布、完整性状态。 ### compare — 版本对比 ```bash python scripts/compare.py <旧spec> <新spec> [--json] ``` ## 文件结构 ``` spec-engine/ ├── SKILL.md ├── scripts/ │ ├── generate.py # 智能 spec 生成 │ ├── validate.py # 可配置验证 │ ├── decompose.py # 任务拆解（v3.0） │ ├── analyze.py # 历史分析 │ ├── dashboard.py # Web 仪表盘（v3.0） │ └── compare.py # 版本对比 └── templates/ ├── spec-template.md # spec 模板 └── rules.json # 验证规则配置 ``` ## 特点 - 纯 Python 标准库，零外部依赖 - UTF-8 编码，跨平台兼容 - 完全向后兼容 v1/v2 ## 适用场景 - Agent 团队写项目 spec → generate - 提交前检查 → validate - spec 确认后拆任务 → decompose - 团队复盘 → analyze + dashboard

path: scripts/collectors/github_oc.py
content: """
GitHub OpenClaw 动态采集模块
搜索 GitHub 上 OpenClaw 相关的最新动态（issues/PRs/releases/新仓库）
"""

import json
from datetime import datetime, timedelta, timezone

try:
    import requests

    def _get(url, params=None, timeout=15):
        resp = requests.get(url, params=params, timeout=timeout, headers={
            "Accept": "application/vnd.github.v3+json",
            "User-Agent": "spec-engine-collector/1.0",
        })
        resp.raise_for_status()
        return resp.json()

except ImportError:
    import urllib.request
    import urllib.parse
    import urllib.error

    def _get(url, params=None, timeout=15):
        if params:
            url = url + "?" + urllib.parse.urlencode(params)
        req = urllib.request.Request(url, headers={
            "Accept": "application/vnd.github.v3+json",
            "User-Agent": "spec-engine-collector/1.0",
        })
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return json.loads(resp.read().decode("utf-8"))


API_BASE = "https://api.github.com"


def _trunc(text, max_len=200):
    """截断文本到指定长度"""
    if not text:
        return ""
    text = text.replace("\n", " ").replace("\r", " ").strip()
    return text[:max_len] + ("..." if len(text) > max_len else "")


def _parse_issue(item):
    """解析 issue/PR 搜索结果"""
    is_pr = "pull_request" in item
    repo_full = ""
    if item.get("repository_url"):
        repo_full = item["repository_url"].split("repos/")[-1]

    return {
        "title": item.get("title", ""),
        "url": item.get("html_url", ""),
        "author": item.get("user", {}).get("login", "unknown"),
        "type": "pr" if is_pr else "issue",
        "publish_time": item.get("created_at", ""),
        "description": _trunc(item.get("body", "")),
        "repo": repo_full,
    }


def _parse_repo(item):
    """解析仓库搜索结果"""
    return {
        "title": item.get("full_name", ""),
        "url": item.get("html_url", ""),
        "author": item.get("owner", {}).get("login", "unknown"),
        "type": "repo",
        "publish_time": item.get("created_at", ""),
        "description": _trunc(item.get("description", "")),
        "repo": item.get("full_name", ""),
    }


def collect(keyword="openclaw", hours=24):
    """
    搜索 GitHub 上指定关键词的最新动态

    Args:
        keyword: 搜索关键词，默认 "openclaw"
        hours: 回溯小时数，默认 24

    Returns:
        dict: 包含 source, items, total, query_time 的结果字典
    """
    now = datetime.now(timezone.utc)
    since = now - timedelta(hours=hours)
    since_str = since.strftime("%Y-%m-%d")
    query_time = now.strftime("%Y-%m-%d %H:%M")

    items = []
    errors = []

    # 1. 搜索 issues + PRs
    try:
        q = f"{keyword} created:>{since_str}"
        data = _get(f"{API_BASE}/search/issues", params={
            "q": q,
            "sort": "created",
            "order": "desc",
            "per_page": 30,
        })
        for item in data.get("items", []):
            items.append(_parse_issue(item))
    except Exception as e:
        errors.append(f"issues/PRs search failed: {e}")

    # 2. 搜索新仓库
    try:
        q = f"{keyword} created:>{since_str}"
        data = _get(f"{API_BASE}/search/repositories", params={
            "q": q,
            "sort": "created",
            "order": "desc",
            "per_page": 30,
        })
        for item in data.get("items", []):
            items.append(_parse_repo(item))
    except Exception as e:
        errors.append(f"repos search failed: {e}")

    # 按发布时间倒序排列
    items.sort(key=lambda x: x.get("publish_time", ""), reverse=True)

    result = {
        "source": "github",
        "items": items,
        "total": len(items),
        "query_time": query_time,
    }
    if errors:
        result["errors"] = errors

    return result


if __name__ == "__main__":
    import sys
    sys.stdout.reconfigure(encoding="utf-8")
    print("正在搜索 GitHub 上 OpenClaw 最近 24 小时的动态...\n")
    result = collect()
    print(json.dumps(result, indent=2, ensure_ascii=False))
    print(f"\n共找到 {result['total']} 条结果")
    if result.get("errors"):
        print("部分请求出错:", result["errors"])


path: scripts/collectors/__init__.py
content: """
OpenClaw 资讯采集器包
包含：bilibili, github_oc, clawhub_oc, xiaohongshu
"""


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