myxce/.claude/skills/xce-changes/SKILL.md
Rongsong Shen ccef561031
feat(skills): add xce-changes skill for component-grouped CHANGES.md
Generates/updates a rolling per-component change log from git history,
mirroring /weekly_report's date-window and continuity-check patterns but
organized by component (controller/worker/scheduler/etc.) instead of a
narrative weekly write-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 14:08:57 +00:00

282 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: xce-changes
description: Generate or update a component-grouped change log for XCE from the last week's commits, written to CHANGES.md. Use when the user asks for a changelog, change log, "what changed this week", or to update CHANGES.md.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
argument-hint: [--week N] [--since YYYY-MM-DD --until YYYY-MM-DD] [--component <name>] [--output <path>]
triggers:
- /xce-changes
- update changelog
- update changes.md
- generate change log
- component changelog
---
# xce-changes Skill
Generate/update `CHANGES.md` at the repo root: a rolling, component-grouped
change log. Each run captures one week of commits and writes (or refreshes)
that week's section, organized into subsections per XCE component
(`controller`, `worker`, `scheduler`, `autoscaler`, `frontend`, `xceadmin`,
`lib`, `include`, `schema`, `docs`, `specs`, `build`, `misc`). Older weeks are
preserved below, newest week on top — this is different from `/weekly_report`
(narrative status report, dated file per week); this is a mechanical,
per-component change ledger.
## Invocation
```
/xce-changes # last complete week -> CHANGES.md
/xce-changes --week 2 # two weeks back
/xce-changes --since 2026-08-20 --until 2026-08-27
/xce-changes --component scheduler # regenerate only the scheduler subsection for the window
/xce-changes --output docs/CHANGES.md # non-default location
```
### Arguments
| Arg | Default | Meaning |
|-----|---------|---------|
| `--week N` | `1` | Weeks back. `1` = last complete week (Mon–Sun), `0` = current partial week. |
| `--since` / `--until` | *(unset)* | Explicit `YYYY-MM-DD` range, overrides `--week`. Both required together. |
| `--component <name>` | *(all)* | Only (re)build the named component's subsection for the resolved window; leave every other component's existing entry in that section untouched. |
| `--output <path>` | `CHANGES.md` (repo root) | File to write/update. Created if missing. |
## Component mapping
Map each changed file's path to exactly one component bucket — do not reuse
the generic `awk -F/ '{print $1}'` bucketing from `/weekly_report`, since
`src/` here mixes real components with build/config directories that would
otherwise get miscategorized as fake "components":
| Path prefix | Component |
|---|---|
| `src/lib/` | `lib` |
| `src/controller/` | `controller` |
| `src/worker/` | `worker` |
| `src/scheduler/` | `scheduler` |
| `src/autoscaler/` | `autoscaler` |
| `src/frontend/` | `frontend` |
| `src/xceadmin/` | `xceadmin` |
| `src/include/` | `include` |
| `src/schema/` | `schema` |
| `src/docs/`, `docs/` | `docs` |
| `.kiro/` | `specs` |
| `src/meson.build`, `src/flake.*`, `src/tools/`, `src/configs/`, `src/nixSupport/`, `src/xce.build*`, `src/build*` | `build` |
| `.wolf/` | **exclude entirely** — bookkeeping, never appears in the changelog |
| anything else (root files, `scripts/`, `reports/`, `.claude/`) | `misc` |
A commit can land in multiple components if it touched multiple areas —
list it under every component subsection it touches, not just one.
## Procedure
### Step 1 — Resolve the window
Same reasoning as `/weekly_report`: use `python3` for date math, never
`date -d` with a relative expression (BusyBox `date` silently no-ops on
relative arithmetic and yields empty vars — see `.wolf/cerebrum.md`'s
bug-1628 entry).
```bash
WEEK_OFFSET=1 # from --week; ignored if --since/--until given
eval "$(python3 - "$WEEK_OFFSET" <<'PY'
import sys, datetime
off = int(sys.argv[1])
t = datetime.date.today()
start = t - datetime.timedelta(days=t.weekday()) - datetime.timedelta(weeks=off)
end = start + datetime.timedelta(days=6)
f = lambda d: d.strftime('%b %d, %Y')
print(f'START={start}\nEND={end}\nHEADER={f(start)} - {f(end)}')
PY
)"
echo "window: $START .. $END"
```
If `--since`/`--until` were passed, set `START`/`END` from them directly and
compute `HEADER` the same way. Sanity-check both are non-empty before
continuing.
### Step 2 — Continuity check against the existing file
```bash
OUT="${OUTPUT:-CHANGES.md}"
grep -n '^## ' "$OUT" 2>/dev/null | head -5
```
- If `$OUT` doesn't exist yet, this is a first run — skip to Step 3 and
create it fresh.
- If a section header for this **exact** window already exists (matched by
its `<!-- window: START..END -->` marker comment, not just the printed
date range), this is an **update**: rebuild that section's content from
git truth and replace it in place — do not append a duplicate. Announce
this to the user ("refreshing existing Aug 24–30 section").
- If `--component` was passed, only replace that one subsection inside the
existing window section, leaving sibling subsections as they are.
- If the most recent existing window's end date is `>= $START`, there's an
overlap — narrow `START` to `last_end + 1 day` so the same commit isn't
logged twice under two different headers. Tell the user about the
adjustment.
- Otherwise this is a new section — it will be **prepended** (newest window
first, right after the `# XCE Change Log` title line).
### Step 3 — Gather commits and bucket by component
```bash
git log --all --no-merges --since="$START 00:00:00" --until="$END 23:59:59" \
--date=format:'%Y-%m-%d' --pretty=format:'@@%H%x09%h%x09%ad%x09%s' --name-only \
-- . ':(exclude).wolf/*'
```
Feed that output to a small python3 pass (parsing name-only git log by hand
in bash is error-prone with filenames containing spaces — don't):
```python3
import re, subprocess, sys
def bucket(path):
rules = [
('src/lib/', 'lib'), ('src/controller/', 'controller'),
('src/worker/', 'worker'), ('src/scheduler/', 'scheduler'),
('src/autoscaler/', 'autoscaler'), ('src/frontend/', 'frontend'),
('src/xceadmin/', 'xceadmin'), ('src/include/', 'include'),
('src/schema/', 'schema'), ('src/docs/', 'docs'), ('docs/', 'docs'),
('.kiro/', 'specs'),
]
for prefix, name in rules:
if path.startswith(prefix):
return name
if path.startswith('.wolf/'):
return None
build_prefixes = ('src/meson.build', 'src/flake.', 'src/tools/',
'src/configs/', 'src/nixSupport/', 'src/xce.build',
'src/build')
if path.startswith(build_prefixes):
return 'build'
if path.startswith('src/'):
return 'build'
return 'misc'
BOOKKEEPING_SUBJECTS = re.compile(
r'^(chore\(wolf\)|save wolf|commit wolf|docs: sync OpenWolf state)')
commits = {} # sha -> {sha7, date, subject, components:set}
order = []
cur = None
for line in sys.stdin:
line = line.rstrip('\n')
if line.startswith('@@'):
sha, sha7, date, subject = line[2:].split('\t', 3)
if BOOKKEEPING_SUBJECTS.match(subject):
cur = None
continue
cur = sha
commits[sha] = {'sha7': sha7, 'date': date, 'subject': subject,
'components': set()}
order.append(sha)
elif line.strip() and cur:
c = bucket(line)
if c:
commits[cur]['components'].add(c)
by_component = {}
for sha in order:
c = commits[sha]
if not c['components']:
continue
for comp in c['components']:
by_component.setdefault(comp, []).append(c)
COMPONENT_ORDER = ['controller', 'worker', 'scheduler', 'autoscaler',
'frontend', 'xceadmin', 'lib', 'include', 'schema',
'docs', 'specs', 'build', 'misc']
for comp in COMPONENT_ORDER:
if comp not in by_component:
continue
print(f'### {comp}')
for c in by_component[comp]:
print(f"- `{c['sha7']}` {c['subject']} ({c['date']})")
print()
```
Run the `git log` piped directly into this script (heredoc or a temp `.py`
file — either is fine, this is a one-shot generation step, not a checked-in
script).
If `--component <name>` was passed, only emit that one component's block and
splice it into the existing section rather than regenerating all of them.
### Step 4 — Compose the section
```markdown
## <HEADER>
<!-- window: <START>..<END> -->
### controller
- `<sha7>` <subject> (<date>)
### scheduler
- `<sha7>` <subject> (<date>)
...
```
- Keep the raw conventional-commit subject line verbatim (it already encodes
type/scope, e.g. `fix(bug-1561): ...`) — don't reword or summarize it.
- Omit component subsections with zero commits in this window; don't print
empty headers.
- If the window has **no** non-bookkeeping commits at all across every
component, write a single line under the header: `_No changes recorded._`
— don't fabricate content, and don't silently skip writing the section.
- For `fix(bug-N)` subjects, optionally enrich with a 6–10 word gist from
`.wolf/buglog.json`'s matching entry's `error_message` if it adds real
information beyond the subject line — skip this enrichment if the subject
is already self-explanatory, don't pad every line uniformly.
### Step 5 — Write the file
If the file doesn't exist:
```markdown
# XCE Change Log
Auto-generated by /xce-changes — component-grouped, newest week first.
`.wolf/` bookkeeping commits are excluded.
<new section>
```
If it exists and this is a genuinely new window: insert the new section
immediately after the header block (title + intro lines), before the first
`## ` section, using Edit — preserve every existing section byte-for-byte.
If this is a refresh of an existing window's section (Step 2): replace only
that `## ... <!-- window: ... --> ... ` block (up to the next `## ` or EOF)
with the newly generated content, via Edit — do not touch any other
section.
### Step 6 — Report back
Tell the user: the window covered, the output path, and a one-line count
per component (e.g. "controller 4, scheduler 9, docs 2 — 15 commits total,
3 bookkeeping excluded"). If the window had zero real commits, say so
plainly and ask whether the offset/range was intended.
## Safety & Fallback
**Not a git repository** — report that git evidence is unavailable; this
skill has no non-git fallback (unlike `/weekly_report`, there's no narrative
substitute for a mechanical commit ledger).
**Existing `CHANGES.md` doesn't look auto-generated** (no `<!-- window: -->`
markers, or a title other than `# XCE Change Log`) — stop and ask before
editing; it may be a hand-maintained file this skill shouldn't clobber.
**`--component` with no matching commits** — still report zero for that
component rather than leaving the subsection stale/unmentioned.
## After Running
Per the OpenWolf protocol, append a one-line entry to `.wolf/memory.md`
recording the change-log update, and if `CHANGES.md` didn't previously
exist, add it to `.wolf/anatomy.md`.