关于 hill-climb
hill-climb 是一种通过逐轮提出候选改动、执行门槛检查并与当前版本比较来优化代码或界面的工作流,面向已有可运行改动或明确改进目标的开发者。它要求配合 council 技能,并依据预设标准决定保留或撤销候选版本。
主要功能
- 01逐轮提出并比较候选改动
- 02通过门槛检查后再评判候选
- 03按预设标准或分数做决定
- 04限制范围、轮次与改动规模
- 05记录提案、结果和文件状态
- 06失败时按快照恢复工作区
适用场景
- 01在现有代码变更上逐轮改进
- 02依据指定目标优化多个界面路径
- 03比较候选版本并保留通过检查的改动
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ filipgutica/ filip-stack --skill hill-climb安装后在 Claude Code 中输入/hill-climb确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/runs-page.htmlreferences/guidance.mdintake.mdrun-record.mdscripts/build-runs.mjsREADME 原文English
来自项目 READMEREADME 暂无中文译文,以下为英文原文;中文介绍见上方。
Hill Climb
Polish the thread's change after it already works, or climb toward a goal the user names, such as better UX across a set of paths. Each round makes one candidate, gates it, and compares it with the incumbent. Keep the candidate only when it wins under the card's decision rule and every gate passes. The user may add a focus, such as "tighter spacing" or "simpler code".
Requires the council skill. If it is missing, stop and say so.
Terms
- Incumbent: the version the climb keeps. At the start, it is the thread's change as it stands.
- Candidate: the incumbent plus one round's changes.
- Gate: a pass/fail check, such as tests, lint, type checks, or screenshot capture.
- Criteria: three to five named qualities the judges compare.
- Score: one number that a frozen command prints for a version, such as an accessibility violation count.
- Fresh start: the starting incumbent has no changed lines against the base, as in an open-ended climb.
- Queue: the ranked proposals, each marked open, applied, declined, or exhausted.
Authority
- Only this thread edits files. Council members propose and judge read-only.
- Invoking the skill authorizes the card's round budget and member calls, so
council's one-round default does not apply. - Commit only when the thread already authorized commits. Push or merge only when the thread authorized that.
- Change the working tree only, inside the scope. Undo a candidate with the
Restore steps only, never with the Git stash stack,
git clean, or a broad checkout. - Keep public interfaces and features as they are. Keep behavior as it is, except the interactions the card's Behavior field names. A broken change or a new feature is outside the climb.
1. Climb card
Find the base after git fetch origin:
- PR:
git merge-base HEAD origin/<baseRefName>, withbaseRefNamefromgh pr view --json baseRefName. - Local:
git merge-base HEAD origin/<default-branch>. - No remote: a base the user names, or
HEADfor uncommitted changes only. - Open-ended goal, with no change to polish:
HEAD. The scope is the files under the paths the user names, listed when the card freezes.
The change is base to working tree, with untracked files. Its paths are the
scope, unless the user names the paths. Changed lines are added plus deleted
text lines from git diff --numstat <base> -- <scope>, counting each
untracked file's lines as added.
Build the card and freeze it before round 1:
- Goal: one sentence, from the thread's original request or the user's goal.
- Criteria: three to five, from the request, the focus, and the guidance
that owns the work. For UI: the repository's design system, else
~/design-language/DESIGN.mdwhen it exists. For code: repository conventions and~/.agents/CODING_STANDARDS.mdwhen it exists. - Scope: the change's paths.
- Gates: exact commands, run on the runtime versions CI uses, such as its Node version, not whatever the shell resolves. For UI, also the exact routes, states, widths, and themes to capture, and the repository's accessibility checker command if it has one. Record whether screenshots come from the repository's tools or the host's browser tools.
- Evidence: for code, each version's diff from the base. For UI, screenshots of each route and state, plus each version's diff. For reader-facing text, each version's text in reading order, with its code blocks and captions, plus each version's diff. Take every screenshot from a fresh page load: going to a URL that differs only in its hash does not reload the page. For guidance, such as a skill or an instruction file, the evidence is agents' work under each version, as references/guidance.md describes.
- Not covered: what the gates and evidence do not prove. For example, screenshots do not prove keyboard behavior.
- Decision rule: how a candidate wins:
judges(the default),score, orboth. See Decide. - Score: for
scoreandboth, the exact command, which prints the number as its last line of output; whether higher or lower is better; and the smallest gain that counts, a positive number. The command and its scripts and fixtures sit outside the scope. A guidance climb uses the answer-key tally fromreferences/guidance.mdin place of a command. - Behavior:
keep(the default), or the interactions the climb may change, such as "focus order in the settings form". Put the tests that assert those interactions in the scope. - Budget: 3 rounds by default, 10 at most. Member calls: a limit only when the user sets one.
- Size limit: the most lines the whole climb may change, measured against the starting incumbent. By default, the larger of 30% of the original change's changed lines and 20 lines. Use another limit when the user or the invoking thread sets one, or when the request needs more room, such as a rewrite or a fresh start. Record the reason on the card. A guidance climb counts only added lines, so a cut never exceeds it.
When the request gives no change to polish, or leaves the scope, the criteria, the decision rule, or the budget unclear, ask the questions in references/intake.md and wait for the answers. Otherwise derive the card from the thread. Show the card and continue without waiting.
2. Baseline
Stop before round 1 and report the item that blocks when:
- A scoped path has staged changes:
git diff --cached --name-only -- <scope>. - A required gate or evidence item cannot run, such as a missing tool, route, or fixture. A block never counts as a pass.
- A gate fails on the incumbent. For reader-facing text, this includes a false claim, checked against its source as Gate describes.
- For
scoreandboth: the score command fails, prints no number, or gives two different numbers in two runs on the incumbent. Record the incumbent's score. - Fewer than two council members can run. Follow
counciland ask before you launch with fewer members than its lineup.
Finish the baseline before you send any propose brief, so a blocked climb spends no member calls.
Make a work directory outside the repository, $TMPDIR/hill-climb/<random>/.
Keep its path in this thread only: other climbs may share $TMPDIR, so never
write the path to a fixed-name file. Judges never see the directory:
start/: a frozen copy of the scoped files, for the size limit.now/: the current scoped files, recreated empty before each measurement.snapshot/: the incumbent. Copy each scoped path withcp -p, and writemanifest.tsvwith path, exists (yesorno), mode (the first field ofls -l), and SHA-256 hash.ledger.md: the private ledger, with every brief sent, each judge's A/B mapping, and each round's created files and outcome (accepted, rejected, or void).
Refresh snapshot/ and the manifest each time a candidate is accepted.
Files the candidate created join the scope and the manifest. Files it deleted
stay in the manifest as no.
Check
Before each round and after each restore, compare the scope with the
manifest: exists, mode, and hash. Before you restore or accept a candidate,
compare the scope with candidate.tsv, which Apply writes in the same
format. On any difference, something outside the climb changed the files.
Stop, report the paths, and change nothing further. Do not restore: a
restore would overwrite those changes.
Restore
- Copy each manifest path marked
yesback fromsnapshot/withcp -p. This also re-creates a file the candidate deleted. - Remove each manifest path marked
nothat now exists, and each fileledger.mdlists as created in this round. Remove nothing else. - Run the check.
If the climb fails or is cancelled during a round, restore before you report, unless the check found outside changes.
3. Rounds
Convene members by council's Convene section: lineup, model availability,
separate contexts, read-only members, and completion. Each propose and judge
call gets a fresh context. A judge never reuses a proposing context. The
report states when two members come from the same provider. Before you retry
a member that reported an error, check whether it ran: a late reply still
counts, and a retry is a new call.
Run each round in order:
- Check. Run the check.
- Propose. Round 1 only. Send each member the propose brief. Add their proposals to the queue with member and rank. Later rounds use the queue. In a UI climb, a member that lists unread screenshots does not judge. If fewer than two members remain, stop: UI climbs are unavailable.
- Select. Take the highest-ranked open proposals that most members raised, or that the evidence clearly supports. Confirm each one still applies to the incumbent; otherwise mark it exhausted. Break ties by the best rank any member gave, then by the smaller change. Decline proposals outside the scope, and proposals that reverse a decision the user already made; list those as follow-ups. If nothing can be selected, stop as for an empty queue.
- Save evidence. Save the incumbent's evidence before you edit.
- Apply. Make the smallest change that does the selected proposals.
Record each new file's path in
ledger.md, then writecandidate.tsvfor the scope and those new files. Recreatenow/empty, copy the scoped files and new files to it, and measure the climb withgit diff --no-index --numstat start now. If it exceeds the size limit, restore, mark those proposals declined for size, list them as follow-ups, and select again without them. A test the round adds must fail on the incumbent and pass on the candidate; check both before you judge. A test changes only when it asserts an interaction the card's Behavior field names; record each such test inledger.mdfor the report. - Gate. Run every gate on the candidate. Judges see only the bundle, so
they cannot tell a true claim from a false one: when the candidate changes
reader-facing text, check each changed claim against the code or source it
describes, and treat a false claim as a failed gate. When only that
claim check fails, repair once: correct each false claim from its source,
or undo only the edit that made it and mark that proposal declined. Record
the repair in
ledger.md, rewritecandidate.tsv, measure and handle the size limit as in Apply, and run every gate again. On any other failure, or a second failure, restore, record the round as rejected, and mark its proposals declined. End the round there: skip Judge and Decide, and apply the stop rules. Forscoreandboth, run the score command on the candidate once the gates pass. A command that fails or prints no number fails the gate. - Judge. For
judgesandboth, build one bundle per judge and send each the judge brief. - Decide. Apply the decision rules.
Bundle
Put each bundle in its own directory, $TMPDIR/<random>/, away from the work
directory. Give the judge its absolute path. No path or file name may show
the round, the order of versions, or which version is new.
Choose the A/B order at random for each judge and record it only in
ledger.md.
criteria.md: the card's criteria.- Code:
A.diffandB.diff, each version's diff from the base:git diff <base> -- <scope>, plusgit diff --no-index /dev/null <file>for each untracked file. Never the diff between the two versions. - UI:
A/andB/with the same screenshot names for each route, state, width, and theme, holding only the screenshots that differ, plusA.diffandB.diffas for code. Tell the judge when no screenshot differs. - Reader-facing text:
A.txtandB.txt, each version's full text in reading order, plusA.diffandB.diffas for code. - Fresh start: in place of
A.diffandB.diff,A-files/andB-files/hold each version's copy of the scoped files that differ between the two, under the same relative paths. An empty diff would show which version is the incumbent. - Guidance: each version's attempt outputs, as
references/guidance.mddescribes.
Decide
- A judge completes when it replies in the format and lists no unread files. A judge that fails, times out, or could not read the bundle is missing.
- For
judgesandboth, with fewer than two completed judges, the round is void. Restore. - The candidate wins under the card's decision rule:
judges: a strict majority of completed judges prefers it overall.score: its score beats the incumbent's by at least the smallest gain that counts. A tie loses.both: both of the above.- A guidance climb also accepts a shorter candidate that is no worse, as
references/guidance.mddescribes.
- A winning candidate becomes the incumbent: refresh the snapshot, record its score as the incumbent's, and mark its proposals applied. Once the remaining votes cannot change the outcome, you may decide; record each late vote when it arrives. When the majority rests on two members from the same provider, say so in the report.
- Otherwise, restore, record the round as rejected, and mark the round's proposals declined.
- Add a judge's
NEXTto the queue only when it applies to the incumbent after the decision. - In the ledger, map each judge's answers back to incumbent and candidate, with the model that ran, each reason, and any strong dissent.
Propose brief
Review only. Do not edit, commit, push, or start subagents.
Repo: <abs path> Base: <sha> Scope: <paths>
See: git diff <base> -- <scope>; read the untracked scoped files: <list>.
<UI: screenshots of each route and state: <dir>>
Goal: <goal>
Criteria: <criteria>
<Score: <command>; <higher|lower> is better; the incumbent scores <n>.>
Keep public interfaces and features as they are. Stay in scope.
<Behavior keep: Keep behavior as it is.>
<Behavior names interactions: Only these interactions may change: <list>.>
<Guidance: the outputs folders and the cut line from references/guidance.md.>
Return up to three improvements against the criteria, best first.
If nothing would clearly improve the change, reply NONE.
<UI: if you could not open a screenshot, list it under UNREAD.>
Reply:
P1 <criterion> <file>:<line> <change>
<UI: UNREAD: <files, or none>>
Judge brief
Judge only. Do not edit files or start subagents.
Use only the files in <bundle path>. Do not read the working tree or the repository.
A and B are two versions of the same change.
<Code: A.diff and B.diff are each version's diff from the same base.>
<UI: A/ and B/ hold the screenshots that differ, with matching names; A.diff and B.diff are each version's diff from the same base.>
<Text: A.txt and B.txt are each version's full text in reading order; A.diff and B.diff are each version's diff from the same base.>
<Fresh start: A-files/ and B-files/ hold each version's copy of the files that differ, in place of A.diff and B.diff.>
Compare them against each criterion in criteria.md, then overall.
Answer A, B, or SAME (no meaningful difference), with one reason each.
Then name one improvement you would make next to the version you prefer.
<Scorecard: Rate each screen (or task) in each version from 0 to 10, and name its biggest remaining issue. 10: nothing to fix. 8: minor polish. 6: a user would notice a problem. 4: a task is harder than it should be. 2: a task fails.>
If you could not open a bundle file, list it under UNREAD.
Reply:
C1 <criterion>: A|B|SAME - <reason>
OVERALL: A|B|SAME - <reason>
NEXT: <one improvement>
<Scorecard: SCORE <screen or task>: A <0-10> - <issue>[ | B <0-10> - <issue>]>
UNREAD: <files, or none>
4. Stop
Stop at the first of these:
- The queue has no open proposals.
- Two rejected or void rounds in a row.
- For
judgesandboth: a strict majority of completed judges answersSAMEoverall, unless the round accepted a shorter guidance candidate. - The round budget is spent.
- The member-call limit could not cover the next round and the final check.
Under
judgesandboth, a round costs one call per judge, round 1 adds one per proposer, and the final check costs one call per judge. Underscore, only round 1's proposers cost calls. A UI or guidance climb's final check costs one call per judge under every rule, for its scorecard. A guidance climb adds its attempts and reserves its baseline comparison, asreferences/guidance.mddescribes. Before each launch in a round, including a retry, check that the calls left cover it and still leave the final check; otherwise stop. During the final check, the calls left need to cover only that check. - The incumbent's changed lines against
start/reach the size limit. After a restore, measure the incumbent as in Apply.
Leave the final incumbent in the working tree. When other branches are stacked on the change, rebase them onto the climbed result once it is committed, and run their gates.
Final check
Each round compares a candidate only with the version before it, so the
rounds do not show whether the whole climb helped. When at least one round
was accepted under judges or both, run one more judge pass: the starting
incumbent against the final incumbent. Build the bundles and send the judge
brief as in a round, with start/ as one version, and map each vote back to
final, same, or start. Change no files. When a strict majority prefers the start, say so
first in the report and recommend dropping the climb.
A UI or guidance climb's final check also builds a scorecard. Put every
screen's screenshots, or every task's outputs, in A/ and B/, not only
those that differ, and add the brief's scorecard lines. For each screen or
task, record the mean score of each version across judges and the issue
judges cite most for the final version. When the final check sends no judge
pass, because no round was accepted or the rule is score, send each judge
a scorecard pass anyway: the same bundle, with the brief's scorecard lines in
place of its comparison and next-improvement lines. With no accepted round,
put the one version in A/ alone, tell the judge that only A exists, and
record its scores as both start and final.
For score and both, also compare the start's score with the final
score; under score, this comparison and any scorecard pass are the whole
final check. When the
final score does not beat the start's by the smallest gain that counts, say
so first in the report.
A guidance climb then compares the final version with its no-guidance
baseline, as references/guidance.md describes.
5. Report
The final reply contains:
- The climb card, including what it does not cover.
- The round ledger: proposals, what changed, gate results, each score, each judge's preference attributed to the model that ran, and the decision.
- Tests the climb changed because the Behavior field let an interaction change, listed for the user to check.
- The final check's votes, attributed to the model that ran, and for UI or guidance climbs the scorecard: each screen or task with its start and final scores and its biggest remaining issue.
- Before and after evidence. For UI, paired screenshots.
- The climb's diff stat against the starting incumbent.
- Follow-ups declined for size or scope.
- Reusable lessons that the original work missed, as plain text.
- Missing judges, and any two members that came from the same provider.
- Where the run record is saved.
Save the run
Save the run record before the final reply, as
references/run-record.md describes: run.json
and a copy of ledger.md in ~/.hill-climb/runs/<id>/. Then run
node <skill-directory>/scripts/build-runs.mjs to write report.md and the
index. When the host can publish pages and the user keeps a runs page, offer
to republish it from the records.