myxce/.claude/skills/gitea-sync/SKILL.md
Rongsong Shen d26ff8870d
feat(buglog): one Markdown record per bug, checked against a record standard
The buglog was one 27K-line JSON file whose fields covered only symptom,
root cause and fix, as free text: 2% of entries named the code version a
bug was found on, 12% the build profile, 36% a way to reproduce, and 58% of
the file was OpenWolf hook auto-detections ("Missing guard clause",
"Function not marked async" on .md files).

- .claude/skills/buglog/bug-record.md: the record standard. Eight
  sections (Summary as the title, Description, Environment, Reproduction;
  Root cause, Fix, Verification, References once fixed), each with the
  content it must contain -- e.g. Environment needs the OS, the libc the
  failing binary loads (from its ELF interpreter: glibc 2.42 in the nix
  shell vs 2.40 on the host), toolchain, build profile and commit.
- .wolf/bugs/<id>.md: 500 records migrated from buglog.json; every field
  verified to round-trip. They carry legacy: true and an explicit "not
  recorded" where the old log had no such field; component derived from
  file paths, severity from keywords (else unknown), fix_commits from
  hashes in status_note/fix that git resolves (91 of 463 fixed).
- .wolf/buglog-archive.json: the 710 hook auto-detections, verbatim.
- .wolf/buglog.json is now an index generated from the records, keeping
  the fields OpenWolf's hooks and `openwolf bug search` read.
- scripts/buglog.py rewritten: new (template + Environment collected from
  the machine and --binary), check, status (refuses a status the record
  doesn't support; fixed needs Root cause, Fix, Verification and
  --commit hashes git resolves), note, recur, set, show, list, triage,
  env, index. 29 unittest cases in scripts/tests/test_buglog.py.
- scripts/git-hooks/pre-commit runs `check --staged`: incomplete staged
  records, a legacy record hand-changed to fixed without the fix-time
  sections, a new record claiming legacy: true, or a stale index refuse
  the commit.
- scripts/gitea-sync.py reads the records: issue body = the record's
  sections plus linked fix commits (origin remote, GITEA_COMMIT_REPO to
  override), and a body hash so an issue's text is refreshed when the
  record changes.
- bug-1866 rewritten to the full standard as the worked example.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 03:40:11 +00:00

12 KiB


name: gitea-sync description: Sync cc-sdd specs and bug records (.wolf/bugs/*.md) to Gitea milestones/issues in nexa/xce argument-hint: [--dry-run] [--spec ] [--bugs] [--since ] [--ids ] triggers:

  • /gitea-sync
  • sync to gitea
  • gitea sync
  • synchronize specs to gitea

Gitea Sync Skill

Sync cc-sdd specs (.kiro/specs/) and bug records (.wolf/bugs/*.md) to Gitea issues in nexa/xce. Files are ground truth — Gitea is a read-only mirror. Sync state (issue numbers) is stored in source files for idempotency.

Invocation

/gitea-sync                           # sync all specs + all bugs
/gitea-sync --spec admin-config-tool  # sync one spec
/gitea-sync --bugs --since 2026-07-18 # sync recent bugs only
/gitea-sync --dry-run                  # preview without making changes

Preferred: run the script

scripts/gitea-sync.py implements every step below over the Gitea REST API and needs no MCP server (token from GITEA_TOKEN, or the gitea MCP entry in ~/.claude.json). Use it whenever it can reach Gitea; follow the manual MCP procedure only when it can't:

python3 scripts/gitea-sync.py --dry-run --bugs --since 2026-09-01   # preview
python3 scripts/gitea-sync.py --bugs --since 2026-09-01
python3 scripts/gitea-sync.py --spec <name>

Keep the script and this procedure in step when changing either.

Procedure

0. Parse arguments

  • --dry-run: only report what would change, don't call MCP write tools
  • --spec <name>: sync only one spec directory
  • --bugs: sync bugs only (skip specs)
  • --since <YYYY-MM-DD>: create issues only for bugs with last_seen >= this date; for already-synced bugs (gitea_issue set), also mirror those whose status_updated >= this date, so a status change on an old bug still reaches Gitea without flooding it with old never-synced bugs
  • --ids <bug-N,bug-M,...>: also sync these bugs regardless of dates (OR'd with --since). Needed because bulk triage has bumped last_seen / status_updated on most of the buglog, and for a never-synced bug --since only checks last_seen — a recently fixed older bug is otherwise unreachable. Always --dry-run first: --since alone can match hundreds of bugs.

1. Ensure labels exist

These labels must exist in nexa/xce. Use mcp__gitea__label_read to list existing labels, then mcp__gitea__label_write with method create_repo_label for any missing ones:

Name Color Description
phase:requirements 6C5CE7 Spec requirements phase
phase:design 6C5CE7 Spec design phase
phase:tasks 6C5CE7 Spec tasks phase
phase:implementation F39C12 Spec implementation phase
phase:complete 27AE60 Spec complete
spec 3498DB Spec-tracked issue
bug E74C3C Bug-tracked issue
bug:fixed 27AE60 Bug resolved
bug:partial F39C12 Partly fixed, remainder open
bug:investigating E67E22 Real failure, root cause not found yet
bug:cannot-reproduce 95A5A6 Repro attempted and failed
bug:wontfix 7F8C8D Not a bug / accepted limitation
bug:duplicate BDC3C7 Duplicate of another bug
bug:asan 8E44AD Sanitizer finding
bug:crash C0392B Crash (SEGV, double-free, UAF)
bug:race D35400 Race / TOCTOU
bug:ha 2980B9 HA / Raft / failover
bug:data-loss 922B21 Data loss
blocked E74C3C Blocked on dependency

2. Sync Specs

For each spec directory in .kiro/specs/ (or just --spec <name>):

  1. Read spec.json. If it has gitea.milestone_id and gitea.issues with all three keys (requirements_design, implementation, validation), skip creation — the spec is already synced. Check phase states and update issue open/closed state if needed (step 2d).

  2. If not synced: a. Create milestone: Use mcp__gitea__milestone_write method create. Title = spec name. Description = one-line summary + phase + blockers. b. Create three phase issues under that milestone via mcp__gitea__issue_write method create:

    Key Title Labels Initial State
    requirements_design [{spec}] Requirements & Design spec, phase:requirements, phase:design closed if both approved, else open
    implementation [{spec}] Implementation spec, phase:implementation open
    validation [{spec}] Validation & Closure spec, phase:complete open

    Body format for each issue (build from spec files):

    Requirements & Design body: Phase status, requirements summary table (extract ## Requirements Summary from requirements.md), blockers list, link to spec files.

    Implementation body: Tasks generated/ready status, task checklist (extract - [ ] / - [x] from tasks.md if it exists), blockers list.

    Validation body: Acceptance gate checklist, deferred future-spec items.

    c. Write sync state back to spec.json:

    "gitea": {
      "milestone_id": <id>,
      "issues": {
        "requirements_design": <number>,
        "implementation": <number>,
        "validation": <number>
      }
    }
    

    d. Phase state updates (for already-synced specs). spec.json approvals only covers requirements/design/tasks — there is no implementation approval — so use phase for the later issues:

    "Done" phases: implementation-complete, implemented, superseded (no tool distinguishes the first two).

    Condition Action
    approvals.requirements.approved AND approvals.design.approved, or phase is done close requirements_design
    phase is done close implementation, validation and the milestone
    phase is not done reopen implementation/validation/milestone if closed

    The same rules give the initial state when the issues are first created.

    Close issues via mcp__gitea__issue_write method update with state: "closed", the milestone via mcp__gitea__milestone_write method update.

3. Sync Bugs

Read the bug records, .wolf/bugs/<id>.md (format and rules: .claude/skills/buglog/bug-record.md). Each record's front-matter status is the source of truth for open/closed — never infer it from the Fix text or tags: migrated records often quote "NOT FIXED" or "PENDING" from earlier stages. .wolf/buglog-archive.json (retired hook auto-detections) is never synced.

Status → Gitea state / label (status values are defined in .claude/rules/buglog-status.md):

status Gitea state Status label
open open —
partial open bug:partial
investigating open bug:investigating
fixed closed bug:fixed
cannot-reproduce closed bug:cannot-reproduce
wontfix closed bug:wontfix
duplicate closed bug:duplicate
missing / unknown open — ; report it in the summary as "needs status"

An issue carries exactly one status label. When changing it, remove only the other bug:<status> labels and add the new one; never replace the issue's whole label set, which would wipe labels a person added.

Shared issues: several records can point at one gitea_issue (e.g. a user-reported issue whose cause was split into several bugs, like #46). Such an issue follows its "most open" record, in this order: open, investigating, partial, (no status), cannot-reproduce, duplicate, wontfix, fixed.

Title: [<bug-id>] <summary> — the record's # title (max 120 chars).

Body:

**Status:** <status> — <status_note, if any>
**Component:** <component> · **Severity:** <severity>
**Fix commits:** [`<sha>`](<GITEA_URL>/<commit repo>/commit/<sha>), …   (if any)
**Duplicate of:** / **Repro attempts:**   (if set)

## <each record section that has content: Description, Environment,
    Reproduction, Root cause, Fix, Verification, References, Legacy notes>

## Affected Files
- `<file>` for each of `files`

## Tags
`tag1` `tag2` ...

---
*Synced from `.wolf/bugs/<id>.md`*

Commit links point at GITEA_COMMIT_REPO, default the origin remote's owner/repo (fixes land on a fork branch before they reach nexa/xce).

Labels: ["bug"] + the status label, bug:crash/bug:data-loss from severity, and known tags: asan/ubsan → bug:asan, SEGV/double-free/UAF → bug:crash, race/toctou → bug:race, ha/raft/election/failover → bug:ha, data-loss → bug:data-loss.

For each record (after the --since/--ids filter):

  1. Skip if it has gitea_issue, gitea_synced_status == status and gitea_synced_hash equals the hash of the title + body it would write now.

  2. Create issue for records without gitea_issue, then close it if the state is closed (state: "closed" may be ignored on create). No milestone.

  3. Update changed issues:

    • text changed (hash differs): update the issue's title and body, so an issue created while the bug was open gets its Root cause, Fix, Verification and fix commits once it is fixed.
    • status changed: update the state and swap the status label (aggregate status for shared issues), and post a one-line comment with the new status, status_note and fix commits. This also reopens bugs whose status went back to open/investigating.
  4. Write sync state back to the record's front matter immediately after each issue is created/updated (a failure part-way through must not lose an issue number, or the next run creates a duplicate issue):

    python3 scripts/buglog.py set <bug-id> gitea_issue=<n> \
        'gitea_synced_status="<status>"' 'gitea_synced_hash="<hash>"'
    

    For records without a valid status, record gitea_synced_status="unknown".

4. Report

Print summary: "Specs: N synced, M skipped | Bugs: X created, Y closed, R reopened, F refreshed, Z skipped" and list any records with no valid status so they can be triaged.

Important Rules

  • Files are ground truth. Never read Gitea state to update local files. The gitea_issue/milestone_id fields in local files only store sync mappings.
  • Idempotent. Items with existing gitea_issue or gitea state are skipped unless their phase changed (specs) or their status or text changed (bugs).
  • Labels: Check existing labels before creating. Use label IDs (not names) in issue create calls — fetch with mcp__gitea__label_read first.
  • Rate limiting: Batch issue creation — create issues sequentially (a few per turn) to stay within API limits.
  • ~400 records have never been synced (older bugs). A run without --since/--ids creates an issue for every one of them; use the filters unless that is the intent, and always --dry-run first.
  • Private repo: nexa/xce is private. MCP auth must have access.

Gitea MCP Tool Reference

Action Tool Method
List labels mcp__gitea__label_read list_repo_labels
Create label mcp__gitea__label_write create_repo_label
List milestones mcp__gitea__milestone_read list
Create milestone mcp__gitea__milestone_write create
List issues mcp__gitea__issue_read get (single) or use list
Create issue mcp__gitea__issue_write create
Update issue mcp__gitea__issue_write update

For listing issues by milestone, use mcp__gitea__issue_read with method: "get" and state: "all". The Gitea MCP may have limited filtering — for bulk listing, check the milestone's open_issues/closed_issues counters.

Note: issue state: "closed" may not work on create — if the issue is created as "open", immediately update it with method: "update" + state: "closed".