From 45b78a68becc26f48ab0df842b5741b1ed163698 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:21:50 +0800 Subject: [PATCH 1/7] test: initialize Agents-Team L2 smoke trial --- AGENTS.md | 132 ++++------------------------------- docs/adr/README.md | 6 ++ docs/agents-team-l2-smoke.md | 32 +++++++++ 3 files changed, 53 insertions(+), 117 deletions(-) create mode 100644 docs/adr/README.md create mode 100644 docs/agents-team-l2-smoke.md diff --git a/AGENTS.md b/AGENTS.md index d45e4a8..1799aae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,117 +1,15 @@ -# Project Rules for Codex - -## Project Overview - -- Project name: `skill-usage` -- Project type: public AI skill package -- Canonical skill entry: `SKILL.md` -- Canonical package name: `skill-usage` -- Main deliverables: deep audit scanner, SKILL.md, README, changelog, plugin metadata, references, evals - -## Working Mode - -- Use lightweight edits for wording, documentation, and examples -- Use stricter review when changing analyzer logic, path patterns, or output schema - -## Maintenance Principles - -- Maintain one canonical package for all platforms (Codex, Claude Code, Hermes) -- Keep `skill-usage` as the only canonical name -- Preserve cross-platform compatibility (don't hardcode paths) -- Keep analyzer logic platform-agnostic -- Maintain real usage detection accuracy -- Preserve tier definitions and thresholds - -## Core Analyzer Requirements - -- Must scan Codex, Claude Code, Hermes, and supported agent/editor transcript roots. -- Must handle JSON escaping (single, double, quadruple backslashes) -- Must distinguish real usage from metadata noise -- Must deduplicate by (platform, session, turn, skill) -- Must calculate both raw and real usage metrics -- Must support all common skill path patterns - -## Path Patterns to Support - -Required patterns: -- `~/.agents/skills/` -- `~/.codex/skills/` -- `~/.claude/skills/` -- `~/.hermes/skills/` -- `~/.codex/plugins/cache/*/skills/` -- OpenAI bundled skills paths - -## Real Usage Signals - -Must detect: -1. Subfile access: `/skills/foo/SKILL.md`, `/skills/foo/reference.md`, etc. -2. Real operations: `Read`, `Bash`, `cat`, `Get-Content`, `grep`, `Glob`, etc. -3. Multiple accesses in same turn (indicates active use) - -## Output Requirements - -Must generate: -1. JSON file with full ranking -2. Markdown ranking report -3. CSV ranking report -4. Source audit report -5. Recommendations grouped by resident, external/archive, description rewrite, and alias normalization - -## Tier Definitions (Do Not Change) - -- ★★★ 主力: strictCalls ≥ 100 -- ★★ 常用: strictCalls 20-99 -- ★ 偶用: strictCalls 5-19 -- · 尝试: strictCalls 1-4 -- ○ 仅提及: strictCalls = 0 - -## Verification - -Before publishing, verify: -- Analyzer runs on Windows, macOS, Linux -- Handles missing log directories gracefully -- Handles malformed JSONL gracefully -- JSON output is valid -- Markdown, CSV, and source-audit outputs are created -- Tier counts add up correctly -- Real usage detection works for all platforms - -## Privacy and Security - -- All analysis must be local -- No data sent to external services -- No credentials or secrets in logs -- Safe to run on any machine - -## Testing - -Test with: -- Empty log directories -- Malformed JSONL files -- Mixed platform logs -- Large log files (1M+ lines) -- Skills with special characters in names -- Skills with Chinese names - -## Commit Guidelines - -- Keep commits focused and atomic -- Use conventional commit format -- Test analyzer before committing logic changes -- Update CHANGELOG.md for user-facing changes - -## Release Process - -1. Update version in plugin.json -2. Update CHANGELOG.md -3. Test analyzer on all platforms -4. Commit and tag release -5. Push to GitHub -6. Create GitHub release with notes - -Only commit files in this repository. Do not commit: -- Local install directories -- Generated reports (*.json, *.md, *.csv) -- Log files -- Caches -- User-specific paths + +## 团队协作机制 + +本项目已启用 `agents-team`。开始开发任务前,读取 `.codex/team-collaboration.json`。 + +- 所有正式任务必须先有清晰、可观察的 Goal。 +- L2/L3 必须使用 GitHub Issue;Issue 必须依次包含 Goal、必须完成、验收门禁、任务边界、风险等级、依赖与阻塞条件。 +- “必须完成”缺一项、验收门禁未通过或任务边界被突破时,不得宣布完成。 +- L1 可由主 Codex 直接处理;L2 必须独立 QA;L3 实施前必须获得用户确认,实施后必须独立复核与 QA。 +- 执行智能体只能声明代码级完成,不得声明 QA PASS、可上线或发布完成。 +- 指定测试失败时必须修复实现,不得删除测试或用无解释的 skip/xfail 规避。 +- GitHub Issue 管承诺,Pull Request 管事实,QA 管结论;不得维护第二套动态任务台账。 +- 真实数据、权限、密钥、付费 Provider、不可逆操作和生产发布必须暂停并请求用户确认。 +- 所有改动必须遵守项目级测试命令、高风险路径和保护文件配置。 + diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..ae539ed --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,6 @@ + +# Architecture Decision Records + +仅为跨版本、难以撤销、影响多个模块或改变核心数据/API/状态机/Provider/部署路线的决定创建 ADR。 + +每份 ADR 包含:状态、日期、关联 Issue、背景、候选方案、决策、原因、后果和替代关系。 diff --git a/docs/agents-team-l2-smoke.md b/docs/agents-team-l2-smoke.md new file mode 100644 index 0000000..b9cd676 --- /dev/null +++ b/docs/agents-team-l2-smoke.md @@ -0,0 +1,32 @@ +# Agents-Team L2 smoke trial + +This branch is an isolated trial of Agents-Team 0.3.0. It must not be merged +into `main` without a separate decision. + +## Scope + +- Generated the repository adapter, contracts, and Collaboration Gate. +- Did not modify the skill scanner, ranking rules, privacy behavior, or release. +- Used no real session data, credentials, paid providers, or production systems. + +## Initialization evidence + +- Command: `python3 plugins/agents-team/scripts/initialize_project.py --apply` +- Exit code: `0` +- Result: `11` adapter files created. +- Project profile: `generic`; the repository has no root Python project manifest. + +## Local validation evidence + +- Command: `python .codex/scripts/validate_team_collaboration.py .` +- Exit code: `0` +- Result: `team collaboration project adapter: valid` + +## GitHub gate evidence + +Pending. The trial first submits intentionally incomplete PR evidence to prove +that the gate fails closed, then replaces it with current-head evidence. + +## Rollback + +Close the trial pull request and delete `test/agents-team-l2-smoke`. From 31a9c1262a5bc2fb1b16bb3e35e10864fee1a2e8 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:22:20 +0800 Subject: [PATCH 2/7] test: add generated collaboration gate --- .codex/schemas/team-collaboration.schema.json | 6 + .codex/scripts/doctor_team_collaboration.py | 23 +++ .codex/scripts/validate_pr_contract.py | 144 ++++++++++++++++++ .codex/scripts/validate_team_collaboration.py | 68 +++++++++ .codex/team-collaboration.json | 71 +++++++++ .github/ISSUE_TEMPLATE/critical-goal.yml | 57 +++++++ .github/ISSUE_TEMPLATE/team-goal.yml | 55 +++++++ .github/pull_request_template.md | 62 ++++++++ .github/workflows/collaboration-gate.yml | 24 +++ 9 files changed, 510 insertions(+) create mode 100644 .codex/schemas/team-collaboration.schema.json create mode 100644 .codex/scripts/doctor_team_collaboration.py create mode 100644 .codex/scripts/validate_pr_contract.py create mode 100644 .codex/scripts/validate_team_collaboration.py create mode 100644 .codex/team-collaboration.json create mode 100644 .github/ISSUE_TEMPLATE/critical-goal.yml create mode 100644 .github/ISSUE_TEMPLATE/team-goal.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/collaboration-gate.yml diff --git a/.codex/schemas/team-collaboration.schema.json b/.codex/schemas/team-collaboration.schema.json new file mode 100644 index 0000000..0501b46 --- /dev/null +++ b/.codex/schemas/team-collaboration.schema.json @@ -0,0 +1,6 @@ +{ + "_generated": "Generated by agents-team", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": ["mechanism", "project", "commands", "paths", "risk", "overrides", "enforcement", "managedFiles"] +} diff --git a/.codex/scripts/doctor_team_collaboration.py b/.codex/scripts/doctor_team_collaboration.py new file mode 100644 index 0000000..f76e030 --- /dev/null +++ b/.codex/scripts/doctor_team_collaboration.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Generated by agents-team. Run a read-only adapter health check.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +def main() -> int: + root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve() + validator = Path(__file__).with_name("validate_team_collaboration.py") + result = subprocess.run([sys.executable, str(validator), str(root)], text=True) + if result.returncode == 0: + print("doctor: healthy") + else: + print("doctor: blocked") + return result.returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.codex/scripts/validate_pr_contract.py b/.codex/scripts/validate_pr_contract.py new file mode 100644 index 0000000..5f9169a --- /dev/null +++ b/.codex/scripts/validate_pr_contract.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Generated by agents-team. Validate a PR, its Goal Issue, and current-head evidence.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import urllib.error +import urllib.request +from datetime import datetime +from pathlib import Path + + +PR_SECTIONS = [ + "关联任务", "风险等级", "实际改动", "范围偏差", "必须完成项证据", + "测试门禁", "行为验收", "QA 独立性与结论", "剩余风险", "回滚方式", "失败记录", +] +ISSUE_SECTIONS = ["Goal", "必须完成", "验收门禁", "任务边界", "风险等级", "依赖与阻塞条件", "失败处理与回滚"] +HEADING = re.compile(r"^#{2,6}\s+(.+?)\s*$", re.MULTILINE) +ISSUE_LINK = re.compile(r"\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b", re.IGNORECASE) +PLACEHOLDER = re.compile(r"<[^>]+>|待验收|待补充|TODO", re.IGNORECASE) + + +def sections(body: str) -> dict[str, str]: + matches = list(HEADING.finditer(body or "")) + result: dict[str, str] = {} + for index, match in enumerate(matches): + end = matches[index + 1].start() if index + 1 < len(matches) else len(body) + result[match.group(1).strip()] = body[match.end():end].strip() + return result + + +def scalar(block: str, name: str) -> str: + match = re.search(rf"(?mi)^\s*{re.escape(name)}\s*:\s*(.+?)\s*$", block) + return match.group(1).strip() if match else "" + + +def valid_timestamp(value: str) -> bool: + try: + datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return False + return bool(value) + + +def validate(pr_body: str, issue_body: str, head_sha: str) -> list[str]: + errors: list[str] = [] + pr = sections(pr_body) + issue = sections(issue_body) + for name in PR_SECTIONS: + if not pr.get(name, "").strip(): + errors.append(f"PR missing section: {name}") + for name in ISSUE_SECTIONS: + if not issue.get(name, "").strip(): + errors.append(f"Issue missing section: {name}") + if not ISSUE_LINK.search(pr_body or ""): + errors.append("PR must close a numbered Issue") + if PLACEHOLDER.search(pr_body or ""): + errors.append("PR contains unresolved placeholders") + + tests = pr.get("测试门禁", "") + for name in ("gate", "command", "exitCode", "passed", "failed", "skipped", "timestamp", "commitSha", "artifact"): + if not scalar(tests, name): + errors.append(f"test evidence missing: {name}") + if scalar(tests, "exitCode") != "0" or scalar(tests, "failed") != "0": + errors.append("test evidence is not successful") + commit_sha = scalar(tests, "commitSha") + if commit_sha and commit_sha != head_sha: + errors.append(f"test evidence commitSha {commit_sha} does not match head.sha {head_sha}") + timestamp = scalar(tests, "timestamp") + if timestamp and not valid_timestamp(timestamp): + errors.append("test evidence timestamp is not ISO 8601") + artifact = scalar(tests, "artifact") + if artifact and not re.match(r"https://", artifact): + errors.append("test evidence artifact must be an HTTPS URL") + + risk = pr.get("风险等级", "") + "\n" + issue.get("风险等级", "") + if re.search(r"\bL[23]\b", risk, re.IGNORECASE): + qa = pr.get("QA 独立性与结论", "") + if not re.search(r"独立上下文\s*[::]\s*是", qa): + errors.append("L2/L3 QA must use an independent context") + if not re.search(r"结论\s*[::]\s*PASS\b", qa, re.IGNORECASE): + errors.append("L2/L3 QA verdict must be PASS") + return errors + + +def fetch_issue(repo: str, number: int, token: str) -> str: + request = urllib.request.Request( + f"https://api.github.com/repos/{repo}/issues/{number}", + headers={"Accept": "application/vnd.github+json", "Authorization": f"Bearer {token}", "User-Agent": "agents-team-gate"}, + ) + try: + with urllib.request.urlopen(request, timeout=20) as response: + return str(json.loads(response.read().decode("utf-8")).get("body") or "") + except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError) as exc: + raise RuntimeError(f"cannot read linked Issue: {exc}") from exc + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--event", type=Path, required=True) + parser.add_argument("--issue-body-file", type=Path) + args = parser.parse_args() + event = json.loads(args.event.read_text(encoding="utf-8")) + pull_request = event.get("pull_request") or {} + if pull_request.get("draft"): + print("draft PR: strict contract deferred until ready for review") + return 0 + + pr_body = str(pull_request.get("body") or "") + head_sha = str((pull_request.get("head") or {}).get("sha") or "") # head.sha + repo = str((event.get("repository") or {}).get("full_name") or os.environ.get("GITHUB_REPOSITORY") or "") + linked = ISSUE_LINK.search(pr_body) + if not head_sha or not repo or linked is None: + errors = ["event is missing repository, head.sha, or linked Issue"] + else: + if args.issue_body_file: + issue_body = args.issue_body_file.read_text(encoding="utf-8") + else: + token = os.environ.get("GITHUB_TOKEN", "") + if not token: + print("Agents-Team gate blocked: GITHUB_TOKEN is required") + return 1 + try: + issue_body = fetch_issue(repo, int(linked.group(1)), token) + except RuntimeError as exc: + print(f"Agents-Team gate blocked: {exc}") + return 1 + errors = validate(pr_body, issue_body, head_sha) + + if errors: + print("Agents-Team PR contract failed:") + for error in errors: + print(f"- {error}") + return 1 + print("Agents-Team PR contract passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.codex/scripts/validate_team_collaboration.py b/.codex/scripts/validate_team_collaboration.py new file mode 100644 index 0000000..9479563 --- /dev/null +++ b/.codex/scripts/validate_team_collaboration.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Generated by agents-team. Self-contained project validator.""" + +import json +import hashlib +import re +import sys +from pathlib import Path + + +REQUIRED = [ + "AGENTS.md", + ".codex/team-collaboration.json", + ".codex/scripts/validate_pr_contract.py", + ".codex/scripts/doctor_team_collaboration.py", + ".github/ISSUE_TEMPLATE/team-goal.yml", + ".github/ISSUE_TEMPLATE/critical-goal.yml", + ".github/pull_request_template.md", +] +BLOCK_RE = re.compile( + r"\n.*?\n\n?", + re.DOTALL, +) + + +def sha256(data: bytes) -> str: + return "sha256:" + hashlib.sha256(data).hexdigest() + + +def main() -> int: + root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve() + errors = [f"missing: {path}" for path in REQUIRED if not (root / path).is_file()] + config_path = root / ".codex/team-collaboration.json" + if config_path.is_file(): + try: + config = json.loads(config_path.read_text(encoding="utf-8")) + if config.get("mechanism", {}).get("id") != "agents-team": + errors.append("invalid mechanism.id") + for target, expected in config.get("managedFiles", {}).items(): + if target == "AGENTS.md#TEAM-COLLABORATION": + agents_path = root / "AGENTS.md" + matches = BLOCK_RE.findall(agents_path.read_text(encoding="utf-8")) if agents_path.is_file() else [] + if len(matches) != 1: + errors.append("missing or malformed AGENTS managed block") + continue + data = matches[0].encode("utf-8") + else: + managed_path = root / target + if not managed_path.is_file(): + errors.append(f"missing: {target}") + continue + data = managed_path.read_bytes() + if sha256(data) != expected: + errors.append(f"managed file drift: {target}") + except (OSError, json.JSONDecodeError) as exc: + errors.append(f"invalid config: {exc}") + agents = root / "AGENTS.md" + if agents.is_file() and "TEAM-COLLABORATION:START" not in agents.read_text(encoding="utf-8"): + errors.append("missing AGENTS managed block") + for error in errors: + print(error) + if not errors: + print("team collaboration project adapter: valid") + return 1 if errors else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.codex/team-collaboration.json b/.codex/team-collaboration.json new file mode 100644 index 0000000..46391a0 --- /dev/null +++ b/.codex/team-collaboration.json @@ -0,0 +1,71 @@ +{ + "$schema": "./schemas/team-collaboration.schema.json", + "mechanism": { + "id": "agents-team", + "pluginVersion": "0.3.0", + "protocolVersion": "2.0.0", + "configSchemaVersion": 2, + "initializedAt": "2026-06-29T16:21:03Z", + "lastUpgradedAt": null + }, + "project": { + "name": "agents-team-skill-usage-pZ4IrA", + "type": "generic", + "repository": "DOIT-Ben/skill-usage", + "defaultBranch": "main" + }, + "commands": { + "test": "", + "build": "", + "lint": "", + "typecheck": "", + "e2e": "" + }, + "paths": { + "frontend": [], + "backend": [], + "database": [], + "deployment": [], + "tests": [] + }, + "risk": { + "criticalPaths": [], + "protectedFiles": [], + "productionPaths": [], + "realProviderPaths": [] + }, + "overrides": { + "requireIssueForL1": false, + "requireRealSmokeForRelease": true + }, + "enforcement": { + "mode": "strict", + "failClosedRisks": [ + "L2", + "L3" + ], + "requireLinkedIssue": { + "L1": false, + "L2": true, + "L3": true + }, + "requireIndependentQA": { + "L1": false, + "L2": true, + "L3": true + }, + "requireFailureRecord": true + }, + "managedFiles": { + ".codex/schemas/team-collaboration.schema.json": "sha256:3df7800f6802146c496da6241dab8be0e97687c7fcc097a110e6ea9457de96e7", + ".codex/scripts/doctor_team_collaboration.py": "sha256:5026a93cc571e8ef3ee16ab83b1ab77e026d040d0894332d2ead5f08a203b084", + ".codex/scripts/validate_pr_contract.py": "sha256:de378f6eb075566d1f2ab86ebd6e3e8c0f51a3e157f29538dc0b8faa6bea58e2", + ".codex/scripts/validate_team_collaboration.py": "sha256:9527cab3b26c5c9111850e125d8fec2a7414017201a8b42feabc2bff15926f25", + ".github/ISSUE_TEMPLATE/critical-goal.yml": "sha256:816e620235b01037a5b4009de8b2ff1d30c1ab66beb6e1b31bff2999545bf9d9", + ".github/ISSUE_TEMPLATE/team-goal.yml": "sha256:5708e9c0bdff27fc2fa2e26d6d8c782cb3d4c2249aac9be772b32c66faef5a44", + ".github/pull_request_template.md": "sha256:96953d71fedc2939255e89f9baaf42c79f5c51a70bfd67badaf95f2ece660eac", + ".github/workflows/collaboration-gate.yml": "sha256:72d5d4911057e0ab30963e39b0dedc3664602a14da2cc4bb43f8055af5722e1e", + "docs/adr/README.md": "sha256:eac61dad3e66bc2adf9fd75f0bd43e30d55a77a6d4a3957df474bf7f723a64ff", + "AGENTS.md#TEAM-COLLABORATION": "sha256:c82f5150a2d2a8aa1bb41ff7cbefa9469ab12329e96ea02a342ce0b0bb61f8dd" + } +} diff --git a/.github/ISSUE_TEMPLATE/critical-goal.yml b/.github/ISSUE_TEMPLATE/critical-goal.yml new file mode 100644 index 0000000..3418c10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/critical-goal.yml @@ -0,0 +1,57 @@ +# Generated by agents-team +name: 关键团队目标 +description: 创建必须由用户确认方案的 L3 Goal +title: "[L3 Goal] " +labels: ["status:ready", "risk:L3", "decision-needed"] +body: + - type: textarea + id: goal + attributes: + label: Goal + validations: + required: true + - type: textarea + id: must_complete + attributes: + label: 必须完成 + validations: + required: true + - type: textarea + id: acceptance_gates + attributes: + label: 验收门禁 + validations: + required: true + - type: textarea + id: boundaries + attributes: + label: 任务边界 + validations: + required: true + - type: dropdown + id: risk + attributes: + label: 风险等级 + options: ["L3"] + validations: + required: true + - type: textarea + id: decision + attributes: + label: L3 方案与用户确认 + description: 记录方案、备选、成本、风险、回滚以及用户确认结论。 + validations: + required: true + - type: textarea + id: dependencies + attributes: + label: 依赖与阻塞条件 + validations: + required: true + - type: textarea + id: failure_rollback + attributes: + label: 失败处理与回滚 + description: 记录失败停止条件、证据保留、回滚步骤和回滚后的验证。 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/team-goal.yml b/.github/ISSUE_TEMPLATE/team-goal.yml new file mode 100644 index 0000000..6770704 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/team-goal.yml @@ -0,0 +1,55 @@ +# Generated by agents-team +name: 团队目标 +description: 创建需要实现与独立验收的标准 Goal +title: "[Goal] " +labels: ["status:ready", "risk:L2"] +body: + - type: textarea + id: goal + attributes: + label: Goal + description: 用一句话定义最终必须达到的可观察结果。 + validations: + required: true + - type: textarea + id: must_complete + attributes: + label: 必须完成 + description: 所有条目完成后才允许进入 QA,请使用任务清单。 + validations: + required: true + - type: textarea + id: acceptance_gates + attributes: + label: 验收门禁 + description: 写明必须通过的测试命令、行为路径和证据。 + validations: + required: true + - type: textarea + id: boundaries + attributes: + label: 任务边界 + description: 明确严禁修改、重构、读取、调用或扩展的内容。 + validations: + required: true + - type: dropdown + id: risk + attributes: + label: 风险等级 + options: ["L1", "L2", "L3"] + validations: + required: true + - type: textarea + id: dependencies + attributes: + label: 依赖与阻塞条件 + description: 写明环境、权限、数据和外部服务条件;没有外部依赖时说明可在何种本地条件下执行。 + validations: + required: true + - type: textarea + id: failure_rollback + attributes: + label: 失败处理与回滚 + description: 写明失败证据如何保留、何时停止以及如何恢复到安全状态。 + validations: + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8f37044 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,62 @@ + +## 关联任务 + +Closes # + +## 风险等级 + +L1 / L2 / L3 + +## 实际改动 + +列出最终行为变化和涉及文件。 + +## 范围偏差 + +说明与 Issue 任务边界的偏差及确认记录;没有偏差时写明“无偏差”。 + +## 必须完成项证据 + +- [ ] 必须完成项:可检查的测试、行为或产物证据 + +## 测试门禁 + +```yaml +gate: test:unit +command: +exitCode: +passed: +failed: +skipped: +skipReason: +timestamp: +commitSha: +artifact: +``` + +## 行为验收 + +- 场景: +- 入口与步骤: +- 预期: +- 实际: +- 证据: + +## QA 独立性与结论 + +- 独立上下文:是 / 否 +- 验收者: +- 结论:PASS / FAIL / BLOCKED +- 证据: + +## 剩余风险 + +列出仍存在的风险及负责人。 + +## 回滚方式 + +写明回滚命令、数据恢复方式和回滚后验证。 + +## 失败记录 + +记录本任务出现过的失败、根因和回归证据;确实没有失败时写“无失败记录”。 diff --git a/.github/workflows/collaboration-gate.yml b/.github/workflows/collaboration-gate.yml new file mode 100644 index 0000000..9288353 --- /dev/null +++ b/.github/workflows/collaboration-gate.yml @@ -0,0 +1,24 @@ +# Generated by agents-team +name: Collaboration Gate + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + issues: read + +jobs: + validate-collaboration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate team collaboration project adapter + run: python .codex/scripts/validate_team_collaboration.py . + - name: Validate PR, linked Issue, and current-head evidence + if: github.event_name == 'pull_request' + env: + GITHUB_TOKEN: ${{ github.token }} + run: python .codex/scripts/validate_pr_contract.py --event "$GITHUB_EVENT_PATH" From dbac8a9a634fba793c0f6a1ede38ed8883cb1431 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:23:28 +0800 Subject: [PATCH 3/7] fix: preserve existing project rules --- AGENTS.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 1799aae..1adbbcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,121 @@ +# Project Rules for Codex + +## Project Overview + +- Project name: `skill-usage` +- Project type: public AI skill package +- Canonical skill entry: `SKILL.md` +- Canonical package name: `skill-usage` +- Main deliverables: deep audit scanner, SKILL.md, README, changelog, plugin metadata, references, evals + +## Working Mode + +- Use lightweight edits for wording, documentation, and examples +- Use stricter review when changing analyzer logic, path patterns, or output schema + +## Maintenance Principles + +- Maintain one canonical package for all platforms (Codex, Claude Code, Hermes) +- Keep `skill-usage` as the only canonical name +- Preserve cross-platform compatibility (don't hardcode paths) +- Keep analyzer logic platform-agnostic +- Maintain real usage detection accuracy +- Preserve tier definitions and thresholds + +## Core Analyzer Requirements + +- Must scan Codex, Claude Code, Hermes, and supported agent/editor transcript roots. +- Must handle JSON escaping (single, double, quadruple backslashes) +- Must distinguish real usage from metadata noise +- Must deduplicate by (platform, session, turn, skill) +- Must calculate both raw and real usage metrics +- Must support all common skill path patterns + +## Path Patterns to Support + +Required patterns: +- `~/.agents/skills/` +- `~/.codex/skills/` +- `~/.claude/skills/` +- `~/.hermes/skills/` +- `~/.codex/plugins/cache/*/skills/` +- OpenAI bundled skills paths + +## Real Usage Signals + +Must detect: +1. Subfile access: `/skills/foo/SKILL.md`, `/skills/foo/reference.md`, etc. +2. Real operations: `Read`, `Bash`, `cat`, `Get-Content`, `grep`, `Glob`, etc. +3. Multiple accesses in same turn (indicates active use) + +## Output Requirements + +Must generate: +1. JSON file with full ranking +2. Markdown ranking report +3. CSV ranking report +4. Source audit report +5. Recommendations grouped by resident, external/archive, description rewrite, and alias normalization + +## Tier Definitions (Do Not Change) + +- ★★★ 主力: strictCalls ≥ 100 +- ★★ 常用: strictCalls 20-99 +- ★ 偶用: strictCalls 5-19 +- · 尝试: strictCalls 1-4 +- ○ 仅提及: strictCalls = 0 + +## Verification + +Before publishing, verify: +- Analyzer runs on Windows, macOS, Linux +- Handles missing log directories gracefully +- Handles malformed JSONL gracefully +- JSON output is valid +- Markdown, CSV, and source-audit outputs are created +- Tier counts add up correctly +- Real usage detection works for all platforms + +## Privacy and Security + +- All analysis must be local +- No data sent to external services +- No credentials or secrets in logs +- Safe to run on any machine + +## Testing + +Test with: +- Empty log directories +- Malformed JSONL files +- Mixed platform logs +- Large log files (1M+ lines) +- Skills with special characters in names +- Skills with Chinese names + +## Commit Guidelines + +- Keep commits focused and atomic +- Use conventional commit format +- Test analyzer before committing logic changes +- Update CHANGELOG.md for user-facing changes + +## Release Process + +1. Update version in plugin.json +2. Update CHANGELOG.md +3. Test analyzer on all platforms +4. Commit and tag release +5. Push to GitHub +6. Create GitHub release with notes + +Only commit files in this repository. Do not commit: +- Local install directories +- Generated reports (*.json, *.md, *.csv) +- Log files +- Caches +- User-specific paths + ## 团队协作机制 From da48b68efad410acbc9a35c7c37354d36a2ddb94 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:23:56 +0800 Subject: [PATCH 4/7] test: record initialization preflight failure --- docs/agents-team-l2-smoke.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/agents-team-l2-smoke.md b/docs/agents-team-l2-smoke.md index b9cd676..9f37985 100644 --- a/docs/agents-team-l2-smoke.md +++ b/docs/agents-team-l2-smoke.md @@ -22,6 +22,15 @@ into `main` without a separate decision. - Exit code: `0` - Result: `team collaboration project adapter: valid` +## Preflight failure + +- Symptom: the first projected adapter replaced the existing AGENTS.md and the + comparison showed 117 deleted lines. +- Root cause: initialization was run against an empty local projection rather + than a complete checkout, so existing project rules were absent. +- Correction: restored the complete existing AGENTS.md and appended only the + managed collaboration block. The final comparison has zero deletions. + ## GitHub gate evidence Pending. The trial first submits intentionally incomplete PR evidence to prove From c331e73120ff76f19d412de3371082a832d10908 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:28:21 +0800 Subject: [PATCH 5/7] test: exercise bootstrap push gate --- .codex/scripts/validate_pr_contract.py | 40 ++++++++++++++++++++++-- .codex/team-collaboration.json | 4 +-- .github/workflows/collaboration-gate.yml | 5 ++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.codex/scripts/validate_pr_contract.py b/.codex/scripts/validate_pr_contract.py index 5f9169a..34ba3f0 100644 --- a/.codex/scripts/validate_pr_contract.py +++ b/.codex/scripts/validate_pr_contract.py @@ -9,6 +9,7 @@ import re import sys import urllib.error +import urllib.parse import urllib.request from datetime import datetime from pathlib import Path @@ -99,13 +100,49 @@ def fetch_issue(repo: str, number: int, token: str) -> str: raise RuntimeError(f"cannot read linked Issue: {exc}") from exc +def fetch_pull_request(repo: str, branch: str, token: str) -> dict[str, object]: + owner = repo.split("/", 1)[0] + query = urllib.parse.urlencode({"state": "open", "head": f"{owner}:{branch}"}) + request = urllib.request.Request( + f"https://api.github.com/repos/{repo}/pulls?{query}", + headers={"Accept": "application/vnd.github+json", "Authorization": f"Bearer {token}", "User-Agent": "agents-team-gate"}, + ) + try: + with urllib.request.urlopen(request, timeout=20) as response: + pulls = json.loads(response.read().decode("utf-8")) + except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError) as exc: + raise RuntimeError(f"cannot find pull request for pushed branch: {exc}") from exc + if not isinstance(pulls, list) or len(pulls) != 1 or not isinstance(pulls[0], dict): + raise RuntimeError(f"expected exactly one open pull request for branch {branch}") + return pulls[0] + + +def pull_request_from_event(event: dict[str, object], token: str) -> dict[str, object]: + pull_request = event.get("pull_request") + if isinstance(pull_request, dict) and pull_request: + return pull_request + ref = str(event.get("ref") or "") + repo_data = event.get("repository") + repo = str(repo_data.get("full_name") or "") if isinstance(repo_data, dict) else "" + if not ref.startswith("refs/heads/") or not event.get("after") or not repo: + raise RuntimeError("event is neither a pull_request nor a branch push") + if not token: + raise RuntimeError("GITHUB_TOKEN is required to resolve a pushed branch to its open PR") + return fetch_pull_request(repo, ref.removeprefix("refs/heads/"), token) + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--event", type=Path, required=True) parser.add_argument("--issue-body-file", type=Path) args = parser.parse_args() event = json.loads(args.event.read_text(encoding="utf-8")) - pull_request = event.get("pull_request") or {} + token = os.environ.get("GITHUB_TOKEN", "") + try: + pull_request = pull_request_from_event(event, token) + except RuntimeError as exc: + print(f"Agents-Team gate blocked: {exc}") + return 1 if pull_request.get("draft"): print("draft PR: strict contract deferred until ready for review") return 0 @@ -120,7 +157,6 @@ def main() -> int: if args.issue_body_file: issue_body = args.issue_body_file.read_text(encoding="utf-8") else: - token = os.environ.get("GITHUB_TOKEN", "") if not token: print("Agents-Team gate blocked: GITHUB_TOKEN is required") return 1 diff --git a/.codex/team-collaboration.json b/.codex/team-collaboration.json index 46391a0..269d9fc 100644 --- a/.codex/team-collaboration.json +++ b/.codex/team-collaboration.json @@ -59,12 +59,12 @@ "managedFiles": { ".codex/schemas/team-collaboration.schema.json": "sha256:3df7800f6802146c496da6241dab8be0e97687c7fcc097a110e6ea9457de96e7", ".codex/scripts/doctor_team_collaboration.py": "sha256:5026a93cc571e8ef3ee16ab83b1ab77e026d040d0894332d2ead5f08a203b084", - ".codex/scripts/validate_pr_contract.py": "sha256:de378f6eb075566d1f2ab86ebd6e3e8c0f51a3e157f29538dc0b8faa6bea58e2", + ".codex/scripts/validate_pr_contract.py": "sha256:574fc8bd29073ec12514034721e376a7373d92cae6b78716995010b3dd106be5", ".codex/scripts/validate_team_collaboration.py": "sha256:9527cab3b26c5c9111850e125d8fec2a7414017201a8b42feabc2bff15926f25", ".github/ISSUE_TEMPLATE/critical-goal.yml": "sha256:816e620235b01037a5b4009de8b2ff1d30c1ab66beb6e1b31bff2999545bf9d9", ".github/ISSUE_TEMPLATE/team-goal.yml": "sha256:5708e9c0bdff27fc2fa2e26d6d8c782cb3d4c2249aac9be772b32c66faef5a44", ".github/pull_request_template.md": "sha256:96953d71fedc2939255e89f9baaf42c79f5c51a70bfd67badaf95f2ece660eac", - ".github/workflows/collaboration-gate.yml": "sha256:72d5d4911057e0ab30963e39b0dedc3664602a14da2cc4bb43f8055af5722e1e", + ".github/workflows/collaboration-gate.yml": "sha256:2dce0d69007e2f8fd50e55b06668ad1436633e0524f471dcfd197c8d1fffb604", "docs/adr/README.md": "sha256:eac61dad3e66bc2adf9fd75f0bd43e30d55a77a6d4a3957df474bf7f723a64ff", "AGENTS.md#TEAM-COLLABORATION": "sha256:c82f5150a2d2a8aa1bb41ff7cbefa9469ab12329e96ea02a342ce0b0bb61f8dd" } diff --git a/.github/workflows/collaboration-gate.yml b/.github/workflows/collaboration-gate.yml index 9288353..45a7ed1 100644 --- a/.github/workflows/collaboration-gate.yml +++ b/.github/workflows/collaboration-gate.yml @@ -2,6 +2,7 @@ name: Collaboration Gate on: + push: pull_request: workflow_dispatch: @@ -18,7 +19,9 @@ jobs: - name: Validate team collaboration project adapter run: python .codex/scripts/validate_team_collaboration.py . - name: Validate PR, linked Issue, and current-head evidence - if: github.event_name == 'pull_request' + if: >- + github.event_name == 'pull_request' || + (github.event_name == 'push' && github.ref_name != github.event.repository.default_branch) env: GITHUB_TOKEN: ${{ github.token }} run: python .codex/scripts/validate_pr_contract.py --event "$GITHUB_EVENT_PATH" From 853971d3e643f0ea3e3127e6f9176c798d822a21 Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:31:59 +0800 Subject: [PATCH 6/7] test: enable evidence edit revalidation --- .codex/team-collaboration.json | 2 +- .github/workflows/collaboration-gate.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.codex/team-collaboration.json b/.codex/team-collaboration.json index 269d9fc..b06da9b 100644 --- a/.codex/team-collaboration.json +++ b/.codex/team-collaboration.json @@ -64,7 +64,7 @@ ".github/ISSUE_TEMPLATE/critical-goal.yml": "sha256:816e620235b01037a5b4009de8b2ff1d30c1ab66beb6e1b31bff2999545bf9d9", ".github/ISSUE_TEMPLATE/team-goal.yml": "sha256:5708e9c0bdff27fc2fa2e26d6d8c782cb3d4c2249aac9be772b32c66faef5a44", ".github/pull_request_template.md": "sha256:96953d71fedc2939255e89f9baaf42c79f5c51a70bfd67badaf95f2ece660eac", - ".github/workflows/collaboration-gate.yml": "sha256:2dce0d69007e2f8fd50e55b06668ad1436633e0524f471dcfd197c8d1fffb604", + ".github/workflows/collaboration-gate.yml": "sha256:edb1be4889b2e458be6d50ddedc31c5c7e675744aa6bd5f6b1b15395c95d773b", "docs/adr/README.md": "sha256:eac61dad3e66bc2adf9fd75f0bd43e30d55a77a6d4a3957df474bf7f723a64ff", "AGENTS.md#TEAM-COLLABORATION": "sha256:c82f5150a2d2a8aa1bb41ff7cbefa9469ab12329e96ea02a342ce0b0bb61f8dd" } diff --git a/.github/workflows/collaboration-gate.yml b/.github/workflows/collaboration-gate.yml index 45a7ed1..489baf3 100644 --- a/.github/workflows/collaboration-gate.yml +++ b/.github/workflows/collaboration-gate.yml @@ -4,6 +4,7 @@ name: Collaboration Gate on: push: pull_request: + types: [opened, synchronize, reopened, ready_for_review, edited] workflow_dispatch: permissions: From 18f72870761b649162d10dec3c0a3fe4584ace7f Mon Sep 17 00:00:00 2001 From: Doit <124886741+DOIT-Ben@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:37:10 +0800 Subject: [PATCH 7/7] test: adopt final PR event gate --- .codex/scripts/validate_pr_contract.py | 38 +----------------------- .codex/team-collaboration.json | 4 +-- .github/workflows/collaboration-gate.yml | 5 +--- 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/.codex/scripts/validate_pr_contract.py b/.codex/scripts/validate_pr_contract.py index 34ba3f0..0647efc 100644 --- a/.codex/scripts/validate_pr_contract.py +++ b/.codex/scripts/validate_pr_contract.py @@ -9,7 +9,6 @@ import re import sys import urllib.error -import urllib.parse import urllib.request from datetime import datetime from pathlib import Path @@ -100,37 +99,6 @@ def fetch_issue(repo: str, number: int, token: str) -> str: raise RuntimeError(f"cannot read linked Issue: {exc}") from exc -def fetch_pull_request(repo: str, branch: str, token: str) -> dict[str, object]: - owner = repo.split("/", 1)[0] - query = urllib.parse.urlencode({"state": "open", "head": f"{owner}:{branch}"}) - request = urllib.request.Request( - f"https://api.github.com/repos/{repo}/pulls?{query}", - headers={"Accept": "application/vnd.github+json", "Authorization": f"Bearer {token}", "User-Agent": "agents-team-gate"}, - ) - try: - with urllib.request.urlopen(request, timeout=20) as response: - pulls = json.loads(response.read().decode("utf-8")) - except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError) as exc: - raise RuntimeError(f"cannot find pull request for pushed branch: {exc}") from exc - if not isinstance(pulls, list) or len(pulls) != 1 or not isinstance(pulls[0], dict): - raise RuntimeError(f"expected exactly one open pull request for branch {branch}") - return pulls[0] - - -def pull_request_from_event(event: dict[str, object], token: str) -> dict[str, object]: - pull_request = event.get("pull_request") - if isinstance(pull_request, dict) and pull_request: - return pull_request - ref = str(event.get("ref") or "") - repo_data = event.get("repository") - repo = str(repo_data.get("full_name") or "") if isinstance(repo_data, dict) else "" - if not ref.startswith("refs/heads/") or not event.get("after") or not repo: - raise RuntimeError("event is neither a pull_request nor a branch push") - if not token: - raise RuntimeError("GITHUB_TOKEN is required to resolve a pushed branch to its open PR") - return fetch_pull_request(repo, ref.removeprefix("refs/heads/"), token) - - def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--event", type=Path, required=True) @@ -138,11 +106,7 @@ def main() -> int: args = parser.parse_args() event = json.loads(args.event.read_text(encoding="utf-8")) token = os.environ.get("GITHUB_TOKEN", "") - try: - pull_request = pull_request_from_event(event, token) - except RuntimeError as exc: - print(f"Agents-Team gate blocked: {exc}") - return 1 + pull_request = event.get("pull_request") or {} if pull_request.get("draft"): print("draft PR: strict contract deferred until ready for review") return 0 diff --git a/.codex/team-collaboration.json b/.codex/team-collaboration.json index b06da9b..a1c9dd5 100644 --- a/.codex/team-collaboration.json +++ b/.codex/team-collaboration.json @@ -59,12 +59,12 @@ "managedFiles": { ".codex/schemas/team-collaboration.schema.json": "sha256:3df7800f6802146c496da6241dab8be0e97687c7fcc097a110e6ea9457de96e7", ".codex/scripts/doctor_team_collaboration.py": "sha256:5026a93cc571e8ef3ee16ab83b1ab77e026d040d0894332d2ead5f08a203b084", - ".codex/scripts/validate_pr_contract.py": "sha256:574fc8bd29073ec12514034721e376a7373d92cae6b78716995010b3dd106be5", + ".codex/scripts/validate_pr_contract.py": "sha256:8e58749e37725760174d5c1ecd64c9c240cb91aafe915fd279d89af4eafe3555", ".codex/scripts/validate_team_collaboration.py": "sha256:9527cab3b26c5c9111850e125d8fec2a7414017201a8b42feabc2bff15926f25", ".github/ISSUE_TEMPLATE/critical-goal.yml": "sha256:816e620235b01037a5b4009de8b2ff1d30c1ab66beb6e1b31bff2999545bf9d9", ".github/ISSUE_TEMPLATE/team-goal.yml": "sha256:5708e9c0bdff27fc2fa2e26d6d8c782cb3d4c2249aac9be772b32c66faef5a44", ".github/pull_request_template.md": "sha256:96953d71fedc2939255e89f9baaf42c79f5c51a70bfd67badaf95f2ece660eac", - ".github/workflows/collaboration-gate.yml": "sha256:edb1be4889b2e458be6d50ddedc31c5c7e675744aa6bd5f6b1b15395c95d773b", + ".github/workflows/collaboration-gate.yml": "sha256:a941f6c7094b4974133ddd2642d60be4f252edf2d534694a7db4bdf9f0e05e36", "docs/adr/README.md": "sha256:eac61dad3e66bc2adf9fd75f0bd43e30d55a77a6d4a3957df474bf7f723a64ff", "AGENTS.md#TEAM-COLLABORATION": "sha256:c82f5150a2d2a8aa1bb41ff7cbefa9469ab12329e96ea02a342ce0b0bb61f8dd" } diff --git a/.github/workflows/collaboration-gate.yml b/.github/workflows/collaboration-gate.yml index 489baf3..db64140 100644 --- a/.github/workflows/collaboration-gate.yml +++ b/.github/workflows/collaboration-gate.yml @@ -2,7 +2,6 @@ name: Collaboration Gate on: - push: pull_request: types: [opened, synchronize, reopened, ready_for_review, edited] workflow_dispatch: @@ -20,9 +19,7 @@ jobs: - name: Validate team collaboration project adapter run: python .codex/scripts/validate_team_collaboration.py . - name: Validate PR, linked Issue, and current-head evidence - if: >- - github.event_name == 'pull_request' || - (github.event_name == 'push' && github.ref_name != github.event.repository.default_branch) + if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} run: python .codex/scripts/validate_pr_contract.py --event "$GITHUB_EVENT_PATH"