ci-score

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ci-score

ci-score

CI Score gauges best-practice adherence on CI speed and gate hygiene: a straightforward pass/fail rubric of best-practice checks computed from a repo's workflow configuration. Eleven configuration facts, each self-verifiable in the repo's own YAML in under a minute; score = checks passed / applicable; one refusal (no workflows to check). Registry: references/ci-score-spec.json (frozen v0.1.6); rubric write-up: references/ci-score-methodology.md.
The score measures adherence, not speed — measured, faster repos can hold lower scores. The report says this beside the card; never let a user read the score as a speed verdict. Nor is it a security audit: the rubric grades best practices, and exactly two of its eleven checks (action pinning, job-scoped OIDC tokens) happen to be security-related — it claims nothing further about workflow security.
CI Score 衡量 CI 速度和门禁卫生方面的最佳实践遵循度:使用一套直接了当的通过/失败最佳实践检查规则,根据仓库的 workflow 配置计算结果。 共 11 项配置事实,每项都可在仓库自身的 YAML 中于一分钟内自行验证;分数 = 通过的检查数 / 适用检查数;存在一种拒绝情况(没有可检查的 workflow)。相关规范:references/ci-score-spec.json(冻结版本 v0.1.6);评分标准说明:references/ci-score-methodology.md。
该分数衡量的是遵循度,而非速度——经实际测量,速度更快的仓库得分反而可能更低。报告会在评分卡旁说明这一点;绝不能让用户将分数理解为速度结论。它也不是安全审计:该规则集用于评估最佳实践,11 项检查中恰好只有两项(action 固定版本、限定到 job 作用域的 OIDC token)与安全相关;除此之外,它不对 workflow 的安全性作任何进一步声明。

Requirements

Requirements

  • A full local checkout of the target repo. The score reads three input classes — workflow YAML, local composite actions, and repo-root build-tool configs — so a partial view (an API fetch of workflows alone) silently inflates scores. The collector refuses outside a git checkout. A full clone you make yourself is a valid path (it carries the full input surface — the ban is on partial fetches, never on cloning): ask before cloning (network + disk), and a score of a repo the user does not own stays for their eyes — never published or shared.
  • PyYAML (
    pip install pyyaml
    ). The scripts exit loudly with that hint when it is missing.
  • No network access is needed or used; everything reads the local tree.
  • 目标仓库的完整本地 checkout。 评分需要读取三类输入——workflow YAML、本地 composite action,以及仓库根目录中的构建工具配置——因此不完整的视图(例如仅通过 API 获取 workflow)会悄然抬高分数。收集器拒绝处理 git checkout 之外的目标。自行执行完整 clone 是有效路径(它包含完整的输入范围——禁止的是部分 fetch,绝非 clone):克隆前须征得同意(会使用网络和磁盘);对于用户并不拥有的仓库,评分结果仅供其本人查看——绝不发布或分享。
  • PyYAML(
    pip install pyyaml
    )。缺少该依赖时,脚本会明确退出并提示安装方式。
  • 不需要也不会使用网络访问;所有内容均从本地工作树读取。

Flow

Flow

All paths below are relative to this skill's directory.
Interaction contract (steps 0 and 4). Both user-facing questions are a single structured question — one question, fixed-order options (always ≥2 options unless a step below drops one by name), nothing open-ended, no machinery narration — via your platform's structured-question tool where one exists:
AskUserQuestion
on Claude Code; on Codex, its built-in user-input request tool (
request_user_input
/
tool/requestUserInput
, experimental — call it when exposed, never predict it absent (a live ci-score Codex run skipped it on that prediction), and ALSO end your plain message with the same question as a numbered option list, same options, same order, sent before the call: an experimental tool can silently fail to render or never return — live Codex run 2026-10-29). Only when the tool is genuinely absent or the call fails, that plain message alone asks it — same options, same order, the close's save option still last and verbatim (
None, just save the report (.md)
), the target confirm still one keystroke ("Reply y to score <owner/repo>, or name a different repo/path"). Never say you are waiting on a choice unless its options are visible in your own last message. Only the delivery mechanism varies; the contract is agent-independent. This whole contract mirrors ci-speedup's interaction contract — same tools, same fallback shape, same verbatim save option — keep the two parallel when either changes.
  1. Confirm the target before scoring. Default is the repo you are standing in:
    git rev-parse --show-toplevel
    is the checkout root, and the origin remote (when GitHub) names it
    <owner/repo>
    . Ask ONE structured question per the interaction contract — ≥ 2 options: one confirms the detected
    <owner/repo>
    (or path when there is no GitHub remote), one is "a different repo or path" (its pick or Other supplies the target). When the working directory is not a git checkout (
    git rev-parse
    fails) there is no local default to confirm — deliver the question as the plain-message ask or an Other-only prompt (the ≥ 2-option confirm/different shape needs a detected default) and have the user name the target (
    <owner/repo>
    or a path). If the user already named a target, re-confirm only if ambiguous (the named target doesn't resolve to a single local checkout or a single clonable
    <owner/repo>
    ). The confirm is load-bearing even though scoring is local, fast, and free: any applied fix — and the report, once saved — land in the scored repo's root, so a wrong working directory acts on the wrong project. A pick that is another local checkout is scored directly; a pick that is an
    <owner/repo>
    not already on disk follows the clone-then-score rule in Requirements (confirm access with
    gh repo view
    — in sandboxed agent shells (Codex) a keyring credential can be unreachable and gh false-fails: retry with host access before reading a failure as no-access, mirroring ci-speedup's gh gate — then ask before cloning — network + disk). Do not run the collector until the target is settled.
  2. Collect + score (one command, writes the findings document):
    python3 scripts/collect_config.py --repo <checkout-root> --out <workdir>/findings.json
    .
    <workdir>
    = a scratch directory outside the target checkout — the session scratchpad if one exists, else
    mktemp -d
    ; use a per-repo subdirectory (
    <workdir>/<repo-name>/
    ) so a second repo or a re-run never overwrites a prior run's files, and never default to cwd (an untracked findings.json inside the repo makes the next run's provenance
    -dirty
    ). Exit 0 = scored or an honest refusal in the stamp; exit 2 = collection refusal (not a checkout / no parseable workflows); exit 3 = scoring failed (
    data_sources.ci_score_error
    records why). Every outcome is stamped in the document — read it, don't guess.
  3. Render the report to scratch:
    python3 scripts/render_report.py --findings <workdir>/findings.json --out <workdir>/report.md
    — score card, the adherence-not-speed disclosure, then one recommendation per failed check ranked by impact × risk, each with a fix recipe, its best-practices page, and an agent handoff prompt. The rendered file stays in
    <workdir>
    until the user asks for it
    (the ci-speedup convention, issue #18 there): writing it into the target repo is what the close's
    None, just save the report (.md)
    pick does — copy
    <workdir>/report.md
    to
    ./ci-score-report.md
    (the target repo root) and say where it landed in one clause (a generated artifact they can gitignore or delete — never auto-commit it or edit their .gitignore). No other pick writes the report into the working tree. This also keeps the run self-clean: an unsaved report leaves no untracked file behind to flag the NEXT run's provenance
    -dirty
    . Raw
    findings.json
    stays in
    <workdir>
    always.
  4. Verify before presenting:
    python3 scripts/verify_report.py --findings <workdir>/findings.json --report <workdir>/report.md
    must print
    report: OK
    . If it fails, re-run step 2 and re-verify once (a stale report.md or mismatched findings/report pair is the common cause). If it still fails, the rendered report is unsafe to present: tell the user verification failed (a skill bug, not their repo), give them their result by reading the score line directly from the
    ci_score
    stamp, and withhold the recommendations; show the violation lines only if asked.
  5. Close per the close/kickoff protocol — read references/close-contract.md first (the "Close contract — invariants" section below is the point-of-use summary, not the whole protocol) — two parts, both in the SAME turn: (a) the close text (banner → disclosure → recommendations → report note), sent as an ordinary message and never packed into the question call, then (b) the kickoff question per the interaction contract above — a SEPARATE structured-question tool call where one exists (
    AskUserQuestion
    on Claude Code; for Codex's experimental tool, ALSO print the option list below); where none does (most Codex runs), the question IS the FINAL block of that message: the numbered option list itself, same fixed options in the same order, save option last and verbatim, and the turn ends on it. Writing close text never completes the close — on EITHER platform, only the question does (the tool call, or that final printed option list). Ending the turn after (a) leaves the user with no choices at all (live miss #2, quasar 2026-07-29: the close referenced "the last option below" and then ended — no question ever appeared; the user had to type their pick freehand).
Debug tracing: set
STARSLING_LOG_LEVEL=DEBUG
(logs counts, file names, and check states — never file contents).
以下所有路径均相对于此 skill 所在目录。
交互契约(步骤 0 和 4)。 两个面向用户的问题均须作为一个结构化问题提出——一个问题、固定顺序的选项(除非下文某一步明确删去某个选项,否则始终至少 2 个选项)、不得开放式作答、不得叙述工具调用机制——若平台提供结构化提问工具,则通过该工具提出:Claude Code 使用
AskUserQuestion
;Codex 使用其内置的用户输入请求工具(
request_user_input
/
tool/requestUserInput
,实验性——工具暴露时必须调用,绝不能臆测其不存在(一次实时 ci-score Codex 运行因作出这种臆测而跳过了调用)),并且还要在普通消息末尾以编号选项列表附上同一个问题,选项及顺序完全相同,并在调用前发送:实验性工具可能静默无法渲染或永不返回——实时 Codex 运行 2026-10-29)。只有当工具确实不存在或调用失败时,才由该普通消息单独提出问题——选项及顺序相同,结束环节的保存选项仍须置于最后且逐字保持(
None, just save the report (.md)
),目标确认仍只需按一次键(
Reply **y** to score <owner/repo>, or name a different repo/path
)。 除非选项已显示在上一条消息中,否则绝不能声称正在等待用户选择。 只有交付方式有所不同;该契约与具体 Agent 无关。整个契约与 ci-speedup 的交互契约保持一致——工具、备用形式和逐字保存选项均相同——任一方发生变化时,两者须同步更新。
  1. 评分前确认目标。 默认目标是当前所在的仓库:
    git rev-parse --show-toplevel
    返回 checkout 根目录,origin remote(若为 GitHub)会将其标识为
    <owner/repo>
    。按照交互契约提出一个结构化问题——至少 2 个选项:一个用于确认检测到的
    <owner/repo>
    (没有 GitHub remote 时则为路径),另一个是
    a different repo or path
    (选择该项或使用 Other 提供目标)。当前工作目录不是 git checkout(
    git rev-parse
    失败)时,没有可供确认的本地默认值——应以普通消息提问或提供仅含 Other 的提示(至少 2 个选项的确认/不同目标形式需要预先检测到默认值),并让用户指定目标(
    <owner/repo>
    或路径)。如果用户已经指定目标,仅在目标存在歧义时重新确认(指定的目标无法解析为唯一的本地 checkout 或唯一可 clone 的
    <owner/repo>
    )。即使评分在本地执行且快速、免费,确认步骤仍至关重要:任何应用的修复以及保存后的报告都会落入被评分仓库的根目录,因此在错误的工作目录中操作会作用于错误的项目。选择另一个本地 checkout 时直接评分;选择尚未存在于本地的
    <owner/repo>
    时,遵循 Requirements 中的先 clone 后评分规则——使用
    gh repo view
    确认访问权限——在沙箱 Agent shell(Codex)中,keyring 凭据可能无法访问,导致 gh 错误地报告失败:应先使用宿主访问重试,再将失败解读为无访问权限,并与 ci-speedup 的 gh gate 保持一致——然后在 clone 前征得同意——会使用网络和磁盘)。目标确定前不得运行收集器。
  2. 收集并评分(一条命令,写入 findings 文档):
    python3 scripts/collect_config.py --repo <checkout-root> --out <workdir>/findings.json
    。
    <workdir>
    是目标 checkout 之外的临时目录——如果会话提供 scratchpad 则使用它,否则使用
    mktemp -d
    ;应使用按仓库划分的子目录(
    <workdir>/<repo-name>/
    ),避免第二个仓库或重新运行时覆盖先前运行的文件,并且绝不能默认使用 cwd(在仓库内生成未跟踪的 findings.json 会导致下次运行的来源状态变为
    -dirty
    )。 退出码 0 = 已评分,或文档标记中如实记录了拒绝结果;退出码 2 = 收集阶段拒绝(不是 checkout 或没有可解析的 workflow);退出码 3 = 评分失败(
    data_sources.ci_score_error
    会记录原因)。每种结果都会标记在文档中——应读取文档,不要猜测。
  3. 将报告渲染到临时目录:
    python3 scripts/render_report.py --findings <workdir>/findings.json --out <workdir>/report.md
    ——包含评分卡、“衡量遵循度而非速度”的披露说明,随后为每项未通过的检查提供一条建议,并按影响 × 风险排序;每条建议均包含修复方案、对应的最佳实践页面和 Agent 交接提示词。渲染后的文件会一直保留在
    <workdir>
    中,直至用户要求保存
    (遵循 ci-speedup 的约定及其 issue #18):只有结束环节的
    None, just save the report (.md)
    选项会将报告写入目标仓库——把
    <workdir>/report.md
    复制到目标仓库根目录下的
    ./ci-score-report.md
    ,并用一句话说明其落盘位置(这是用户可加入 gitignore 或删除的生成物——绝不能自动提交,也绝不能修改其 .gitignore)。其他任何选项都不会将报告写入工作树。 这也能保持运行过程干净:未保存的报告不会留下未跟踪文件,从而影响下次运行的来源状态
    -dirty
    。原始
    findings.json
    始终保留在
    <workdir>
    中。
  4. 展示前验证:
    python3 scripts/verify_report.py --findings <workdir>/findings.json --report <workdir>/report.md
    必须打印
    report: OK
    。如果验证失败,重新执行步骤 2 并再次验证一次(最常见原因是 report.md 已过期,或 findings 与 report 不匹配)。如果再次失败,则渲染后的报告不适合展示:告知用户验证失败(这是 skill 的缺陷,而非其仓库的问题),直接从
    ci_score
    标记中读取分数行并告知结果,同时暂不提供建议;仅在用户要求时展示违规行。
  5. 按结束/启动协议收尾——先阅读 references/close-contract.md(下文“Close contract — invariants”部分只是使用时的摘要,并非完整协议)——两部分须在同一轮完成: (a) 结束文本(横幅 → 披露说明 → 建议 → 报告说明),以普通消息发送,绝不能打包进问题调用;随后 (b) 按照上述交互契约提出启动问题——若平台存在结构化提问工具,则另起一次结构化提问工具调用(Claude Code 使用
    AskUserQuestion
    ;对于 Codex 的实验性工具,还要打印下述选项列表);若不存在(大多数 Codex 运行均如此),该问题必须作为该消息的最后一个区块:直接使用编号选项列表,保持固定选项及相同顺序,保存选项置于最后且逐字保持,并以该列表结束本轮。写出结束文本绝不表示结束流程已经完成——在任一平台上,只有问题才算完成(无论是工具调用,还是最后打印的选项列表)。如果完成 (a) 后就结束本轮,用户将完全看不到任何选项(实际遗漏 #2,quasar 2026-07-29:结束文本提到“下面的最后一个选项”,随后却直接结束——始终没有出现问题,导致用户不得不手动输入选择)。
调试跟踪:设置
STARSLING_LOG_LEVEL=DEBUG
(记录数量、文件名和检查状态——绝不记录文件内容)。

Close contract — invariants

Close contract — invariants

The full close/kickoff protocol lives in references/close-contract.md. These non-negotiables stay here, at point of use, because burying them regressed live runs before — in this skill, text position is behavior. Each carries its one-line incident anchor; hold them while you follow the protocol:
  • Banner: copy it VERBATIM from the collector's stdout — never redraw, re-pad, or adjust it freehand (a hand-drawn bar mis-counted 29 of 30 blocks in a live run). A refusal or recorded scoring error prints NO banner and keeps its plain-sentence close — never a banner with an empty or invented bar. Read the score straight from the
    ci_score
    stamp; never fabricate a score to fill a gap.
  • The close = the close text, THEN the question, in the SAME turn. Writing the close text never completes the close — on either platform, only the question does. The options exist ONLY in the question: the structured-question tool where the platform has one (
    AskUserQuestion
    on Claude Code); the final printed option list where it doesn't (most Codex runs) — and for Codex's experimental tool, ALSO print that list, since the tool can fail to render. Never narrate the menu in prose ahead of the question (three live misses — quasar 2026-07-29 twice, plus a Codex run — had perfect content and asked no question at all).
  • Menu shapes. First close: up to TWO fix slots (a one-edit-closes-both bundle counts as ONE slot; each slot carries that fix's consent scope + risk note), then a different recommendation, then last and verbatim
    None, just save the report (.md)
    . The post-apply re-offer leads with "Commit this branch and open a PR" — that pick IS the commit + push + PR ask (owner 2026-07-30: a developer lands work before starting more). A ship pick ENDS the loop; NEVER merge. A reported merge starts a fresh banner-led round — re-score the merged base and present it as a full first close, never a bare "re-scored, N/100" aside (live miss 2026-07-30).
  • Only the save pick writes the report into the working tree (copies
    <workdir>/report.md
    →
    ./ci-score-report.md
    ); no other pick does.
  • One recommendation per approval; stop at the diff. The apply "yes" authorizes the EDIT only — never commit, push, or open a PR on its strength (a live session treated apply as a commit license). The re-scored check is the completion oracle; never fabricate a score and never mutate YAML just to force a pass.
READ references/close-contract.md BEFORE composing the close, every session — it is the executable protocol (consent scope rules, deliberate-absence judgment, ship and post-merge rounds, refusal closes, PR-request rules); the invariants above are what you hold in mind while following it.
完整的结束/启动协议位于 references/close-contract.md。以下不可协商项保留在此处,供实际使用时查阅,因为过去将其埋藏曾导致实际运行回退——在此 skill 中,文本位置就是行为。每项均附有一行事故锚点;执行协议时须始终遵守:
  • 横幅:从收集器的 stdout 中逐字复制——绝不能手工重绘、重新填充或随意调整(一次实际运行中,手绘进度条错误计算了 30 个区块中的 29 个)。拒绝结果或已记录的评分错误不显示横幅,并使用普通语句完成收尾——绝不能显示带有空白或虚构进度条的横幅。直接从
    ci_score
    标记中读取分数;绝不能编造分数来填补空缺。
  • 结束流程 = 先发送结束文本,再提出问题,而且位于同一轮。 写出结束文本绝不表示结束流程已经完成——在任一平台上,只有问题才算完成。选项只应存在于问题中:平台提供结构化提问工具时使用该工具;不提供时使用最后打印的选项列表(大多数 Codex 运行均如此);对于 Codex 的实验性工具,还要打印该列表,因为工具可能无法渲染。绝不能在问题之前用叙述性文字介绍菜单(三次实际遗漏——quasar 2026-07-29 两次,以及一次 Codex 运行——内容均完全正确,却根本没有提出问题)。
  • 菜单结构。 首次收尾最多提供两个修复槽位(一个可同时解决两项问题的编辑捆绑包只算一个槽位;每个槽位均须包含该项修复的同意范围和风险说明),随后提供一个不同的建议,最后逐字提供
    None, just save the report (.md)
    。应用修复后再次提供选项时,须以
    Commit this branch and open a PR
    开头——选择该项即表示请求执行 commit、push 和 PR。ship 选项会结束本轮;绝不能执行 merge。 用户报告已合并后,应开启一轮由横幅引导的新流程——对合并后的 base 重新评分,并将其作为完整的首次收尾展示,绝不能只附带一句“已重新评分,N/100”(实际遗漏 2026-07-30)。
  • 只有保存选项会将报告写入工作树(将
    <workdir>/report.md
    复制到
    ./ci-score-report.md
    );其他选项均不会。
  • 每次批准只处理一条建议;执行到 diff 即停止。 回答
    yes
    只会授权进行编辑——绝不能据此执行 commit、push 或创建 PR。重新评分后的结果才是完成状态的判定依据;绝不能编造分数,也绝不能为了让检查通过而修改 YAML。
每次会话编写结束文本前,都要阅读 references/close-contract.md——它是可执行的协议(同意范围规则、刻意缺省判断、ship 和合并后轮次、拒绝收尾、PR 请求规则);上述不可协商项是执行协议时需要牢记的要点。

Gotchas

Gotchas

  • Never write findings.json OR the unsaved report into the target repo — an untracked file makes the NEXT run's provenance
    -dirty
    (and published profiles forbid dirty). Both render to
    <workdir>
    ; only the user's explicit save pick copies the report to
    ./ci-score-report.md
    . A saved report left untracked will honestly read
    -dirty
    on a later run; tell the user to gitignore or delete it first when a clean-provenance run matters.
  • The score is not a speed verdict — measured, the correlation runs the other way (the lowest-scoring repo held the fastest gate in calibration). The disclosure line beside the card exists for this; keep it visible.
  • Path filters are the one risky recommendation — a wrong filter can skip CI that should run, and a skipped required check blocks merges. The report's risk note says so; repeat it when applying that fix.
  • A subdirectory path still scores the whole repo — the collector anchors to the git top level (a partial view would inflate the score), so
    --repo
    anywhere inside the checkout is equivalent.
  • -dirty
    provenance is conservative
    — untracked files count, and an unverifiable tree (git status failing) is marked dirty, never clean.
  • 绝不能将 findings.json 或未保存的报告写入目标仓库——未跟踪文件会导致下次运行的来源状态变为
    -dirty
    (已发布的配置不允许 dirty 状态)。两者均应渲染到
    <workdir>
    ;只有用户明确选择保存时,才会将报告复制到
    ./ci-score-report.md
    。如果后续运行需要干净的来源状态,应告知用户先加入 gitignore 或删除已保存的报告。
  • 分数不是速度结论——经实际测量,相关性甚至呈相反方向(校准过程中得分最低的仓库反而拥有最快的门禁)。评分卡旁的披露说明正是为此而存在;必须保持可见。
  • 路径过滤器是唯一具有风险的推荐项——错误的过滤器可能跳过本应运行的 CI,而被跳过的必需检查会阻止合并。报告中的风险说明已指出这一点;应用该修复时必须再次说明。
  • 即使从子目录路径运行,也会为整个仓库评分——收集器以 git 顶层为锚点(不完整的视图会抬高分数),因此在 checkout 内任意位置使用
    --repo
    都具有相同效果。
  • -dirty
    来源状态采用保守判定
    ——未跟踪文件会计入其中,无法验证的工作树(
    git status
    失败)会被标记为 dirty,绝不会标记为 clean。

Boundaries

Boundaries

ci-score grades configuration hygiene. It never measures speed, never estimates savings, and never renders money. This ban is operational, not just descriptive: if the user asks about speed, cost, or wall-clock at any point — including right after their score ("so why is our CI slow?") — do not improvise an answer from the config facts (a missing cache is not a measurement); say so in one line and route to ci-speedup. For measured wall-clock and runner-minute findings, that is
ci-speedup
— a separate skill; a ci-speedup run never surfaces a score, and this skill never claims a measurement. Prompt-routing contract (which skill answers what): evals/prompt-routing.json.
ci-score 用于评估配置规范性。它绝不测量速度,绝不估算节省量,也绝不呈现金额。这一禁令是操作层面的,而不只是描述层面的: 如果用户在任何时候询问速度、成本或实际耗时——包括在评分后立即询问(“那我们的 CI 为什么这么慢?”)——绝不能根据配置事实临场推断答案(缺少 cache 并不是一次测量);应用一句话说明这一点,并转至 ci-speedup。对于实际耗时和 runner-minute 的测量结果,应使用 ci-speedup——这是另一个独立 skill;ci-speedup 运行不会给出本分数,而本 skill 也绝不声称进行过测量。提示词路由契约(哪个 skill 应回答哪些问题):evals/prompt-routing.json。