code-complexity-stats-pr

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Code complexity / cost stats on every PR

每个PR上的代码复杂度/成本统计

A GitHub Actions workflow that runs
scc
(Sloc, Cloc and Code) over the repo on each pull request and posts the results — including a COCOMO estimated cost/effort to build the codebase — as a single sticky comment that updates in place on every push. Drop-in workflow:
templates/code-complexity.yml
.
一个GitHub Actions工作流,会在每次Pull Request时对仓库运行
scc
(Sloc, Cloc and Code)工具,并将结果——包括COCOMO模型估算的代码库构建成本/工作量——发布为单个固定评论,每次推送时会原地更新。可直接使用的工作流模板:
templates/code-complexity.yml

What you get

你将获得的内容

scc
prints a per-language table (files, lines, code, comments, blanks, complexity) plus, with
--avg-wage
, a COCOMO block: estimated person-months, schedule, people, and a dollar cost to develop. The workflow wraps that in a fenced block and posts it as a PR comment titled 📊 Code Complexity Analysis.
It reports the whole codebase at the PR's head, not the diff — it's a "what's this project worth / how big it is" signal, not a per-PR delta.
scc
会输出按语言分类的表格(文件数、行数、代码行、注释行、空行、复杂度),加上使用
--avg-wage
参数后生成的COCOMO模块:估算的人月数、开发周期、所需人数,以及开发所需的货币成本。工作流会将这些内容包裹在代码块中,以标题为「📊 代码复杂度分析」的PR评论形式发布。
它会报告PR头部对应的完整代码库数据,而非代码差异——这是一个「该项目价值多少/规模多大」的参考信号,而非单个PR的增量数据。

Install

安装步骤

Copy
templates/code-complexity.yml
to
.github/workflows/code-complexity.yml
. It works as-is on
ubuntu-latest
; the only thing you'll likely change is the
AVG_WAGE
/
WAGE_LABEL
workflow env (see Customise).
templates/code-complexity.yml
复制到
.github/workflows/code-complexity.yml
。在
ubuntu-latest
环境下可直接运行;你可能需要修改的唯一内容是工作流环境变量
AVG_WAGE
/
WAGE_LABEL
(详见自定义配置部分)。

How it works (the parts that matter)

核心工作原理

  • Trigger:
    pull_request: [opened, synchronize, reopened]
    — runs on open and every push. Kept as its own workflow so it always runs even when other workflows honour a
    [skip ci]
    -style marker; these stats are informational.
  • Permissions: a PR comment is posted via the Issues API, so
    pull-requests: write
    is what actually authorises the create/update;
    issues: write
    is included belt-and-suspenders. (Both are in the template.)
  • Sticky comment: the body starts with a hidden marker
    <!-- code-complexity -->
    . The script lists existing comments, finds the one with that marker, and updates it (else creates one) — so a PR keeps exactly one stats comment that refreshes instead of stacking new ones each push.
  • Concurrency:
    cancel-in-progress: true
    keyed on the PR number (
    github.event.pull_request.number
    ) — a new push kills the in-flight run so you don't race two comment updates. Keyed on the number, not
    head_ref
    , so two forks sharing a branch name can't cancel each other.
  • COCOMO cost:
    scc --avg-wage <annual>
    drives the cost figure. The number is unitless to scc — it's read in your currency. The template defines
    AVG_WAGE
    (
    360000
    , HKD 30,000/month) and
    WAGE_LABEL
    once as workflow
    env
    , so the number scc uses and the label in the comment can't drift — change the salary in one place.
  • 触发条件:
    pull_request: [opened, synchronize, reopened]
    ——在PR打开和每次推送时运行。作为独立工作流存在,因此即使其他工作流遵循
    [skip ci]
    类标记,该统计工作仍会执行;这些统计信息仅作参考用途。
  • 权限: PR评论通过Issues API发布,因此
    pull-requests: write
    是实际授权创建/更新评论的权限;同时包含
    issues: write
    作为双重保障。(这两个权限已在模板中配置。)
  • 固定评论: 评论正文以隐藏标记
    <!-- code-complexity -->
    开头。脚本会列出所有现有评论,找到带有该标记的评论并更新它(如果不存在则创建新评论)——因此每个PR始终只有一条统计评论,每次推送时会刷新内容而非新增评论。
  • 并发控制:
    cancel-in-progress: true
    ,以PR编号
    github.event.pull_request.number
    )作为标识——新推送会终止正在运行的任务,避免出现两条评论更新的竞争情况。使用PR编号而非
    head_ref
    作为标识,可防止两个共享分支名称的Fork仓库互相取消任务。
  • COCOMO成本计算:
    scc --avg-wage <annual>
    参数驱动成本计算。该数值对scc来说是无单位的——它会使用你指定的货币单位。模板中通过工作流
    env
    定义了
    AVG_WAGE
    360000
    ,即港币30,000/月)和
    WAGE_LABEL
    ,因此scc使用的数值和评论中的标签不会出现偏差——只需在一处修改薪资即可。

Customise

自定义配置

Want to…Change
Use a different currency / salarythe
AVG_WAGE
and
WAGE_LABEL
workflow
env
(one place — the comment label reads
WAGE_LABEL
)
Exclude more build/generated dirs
--exclude-dir
list (comma-separated). scc honours
.gitignore
by default; this is for output that isn't ignored
Pin / upgrade scc
SCC_VERSION
in the install step
Run on ARM runnersswap the tarball to
scc_Linux_arm64.tar.gz
and use an ARM runner (e.g.
ubuntu-24.04-arm
)
Count only part of a monorepoadd a path arg to
scc
(e.g.
scc apps/web
)
需求修改内容
使用不同货币/薪资工作流环境变量
AVG_WAGE
WAGE_LABEL
(只需修改一处——评论标签会读取
WAGE_LABEL
的值)
排除更多构建/生成目录
--exclude-dir
列表(逗号分隔)。scc默认遵循
.gitignore
规则;此配置用于排除未被忽略的输出文件
固定/升级scc版本安装步骤中的
SCC_VERSION
在ARM运行器上执行将压缩包替换为
scc_Linux_arm64.tar.gz
并使用ARM运行器(例如
ubuntu-24.04-arm
仅统计单体仓库的部分内容
scc
添加路径参数(例如
scc apps/web

Gotchas

注意事项

  • Fork PRs can't post the comment. For PRs from forks,
    pull_request
    runs with a read-only token and no repo secrets, so the comment API call would 403. Declaring
    pull-requests: write
    does not override this — GitHub enforces read-only on the token itself for fork-triggered runs. The template guards the whole job with
    if: github.event.pull_request.head.repo.full_name == github.repository
    , so on fork PRs the job skips cleanly (green check) instead of failing red — and no compute is wasted running scc for a comment that can't post. Options: (a) accept that stats only post for same-repo branches (fine for solo/team repos — the common case; this is the template default); or (b) split into two workflows — compute on
    pull_request
    (no secrets, untrusted code), upload
    scc-output.txt
    as an artifact, then comment from a separate
    workflow_run
    job that has write access. Do not just switch this workflow to
    pull_request_target
    : that runs with a write token in the base repo context, and checking out + running fork code there is a token-exfiltration footgun.
  • Long output is truncated, not failed. GitHub caps a comment body at 65,536 characters. Default scc output (a per-language table) is tiny, but
    --by-file
    on a big repo could blow past it, which would 422 the step — so the template trims to ~64k with a "truncated" note and the comment still posts.
  • Pin scc; don't track
    latest
    .
    A
    latest
    download can change scc's output format and silently reshape every PR comment. The template pins
    SCC_VERSION
    and verifies the tarball against a pinned
    SCC_SHA256
    (
    sha256sum -c
    ) before running it, so a swapped/compromised release asset fails the run instead of executing in CI. When you bump the version — or switch to the arm64 asset — update the checksum from that release's
    checksums.txt
    .
  • Match the tarball to the runner arch. The release asset is arch-specific (
    scc_Linux_x86_64.tar.gz
    vs
    _arm64
    ). The wrong one fails to run.
  • Paginate when finding the sticky comment. A busy PR can have more comments than one API page; without pagination, the marker comment on a later page is missed, and you get duplicates. The template uses
    github.paginate(...)
    and matches the marker with
    startsWith
    (not
    includes
    ) so a human comment that merely quotes the marker isn't mistaken for the bot's and overwritten.
  • Token:
    GH_TOKEN || github.token
    .
    The template prefers a
    GH_TOKEN
    secret (a PAT/bot you already use) and falls back to
    github.token
    — the canonical reference to the built-in
    GITHUB_TOKEN
    . Prefer
    github.token
    over
    secrets.GITHUB_TOKEN
    ; the built-in token is enough for same-repo comments given the permissions above.
  • COCOMO is illustrative. It's the basic COCOMO model (COCOMO I, Organic mode) on LOC — a fun, relative signal of size/effort, not a real valuation. Don't quote it as the literal worth of the code.
  • Fork PR无法发布评论。 对于来自Fork仓库的PR,
    pull_request
    触发的任务使用只读令牌,且无法访问仓库密钥,因此评论API调用会返回403错误。声明
    pull-requests: write
    权限无法覆盖此限制——GitHub会强制对Fork触发的任务使用只读令牌。模板通过
    if: github.event.pull_request.head.repo.full_name == github.repository
    条件来跳过整个任务(显示绿色对勾),而非失败(显示红色叉号)——避免浪费计算资源去运行scc却无法发布评论。可选方案:(a) 接受仅在同仓库分支的PR上发布统计信息(适用于个人/团队仓库——这是常见场景,也是模板默认设置);或(b) 拆分为两个工作流——在
    pull_request
    中执行计算(无密钥,处理不受信任的代码),将
    scc-output.txt
    作为产物上传,然后通过单独的
    workflow_run
    任务发布评论(该任务拥有写入权限)。请勿直接将工作流切换为
    pull_request_target
    :这会在基础仓库环境中使用写入令牌,若在此环境下检出并运行Fork代码,会存在令牌泄露的风险。
  • 过长输出会被截断,而非失败。 GitHub限制评论正文最多65,536字符。默认的scc输出(按语言分类的表格)很小,但在大型仓库上使用
    --by-file
    参数可能会超出限制,导致步骤返回422错误——因此模板会将内容截断至约64k字符,并添加「已截断」提示,确保评论仍能发布。
  • 固定scc版本;不要使用
    latest
    下载
    latest
    版本可能会改变scc的输出格式,导致所有PR评论无声无息地发生变化。模板会固定
    SCC_VERSION
    在运行前通过固定的
    SCC_SHA256
    验证压缩包(
    sha256sum -c
    ),因此若发布资产被替换/篡改,任务会失败而非在CI中执行。当你升级版本——或切换到arm64资产时——请从对应版本的
    checksums.txt
    中更新校验和。
  • 压缩包需与运行器架构匹配。 发布资产是特定架构的(
    scc_Linux_x86_64.tar.gz
    vs
    _arm64
    )。使用错误的压缩包会导致无法运行。
  • 查找固定评论时需分页。 活跃的PR可能有超过一页的评论;若不分页,位于后续页面的标记评论会被遗漏,导致出现重复评论。模板使用
    github.paginate(...)
    并通过
    startsWith
    匹配标记(而非
    includes
    ),避免将仅引用该标记的人工评论误判为机器人评论并覆盖。
  • 令牌:
    GH_TOKEN || github.token
    模板优先使用
    GH_TOKEN
    密钥(你已在使用的PAT/机器人令牌),若不存在则回退到**
    github.token
    **——这是内置
    GITHUB_TOKEN
    的标准引用方式。在上述权限配置下,内置令牌已足够用于同仓库的评论发布,因此优先使用
    github.token
    而非
    secrets.GITHUB_TOKEN
  • COCOMO仅作参考。 它是基于LOC的基础COCOMO模型(COCOMO I,有机模式)——是一个有趣的、相对的规模/工作量信号,而非真实估值。请勿将其作为代码的实际价值引用。

Provenance

来源说明

Generalised from the working
code-complexity.yml
used in production repos (a production app, a production monorepo): same scc + COCOMO + sticky-comment approach, hardened here with version pinning + checksum verification,
persist-credentials: false
, comment pagination, a portable
ubuntu-latest
runner, and the fork-PR security note.
本方案从生产仓库(一个生产应用、一个生产单体仓库)中正在使用的
code-complexity.yml
通用化而来:采用相同的scc + COCOMO + 固定评论方案,并在此基础上进行了强化,包括版本固定+校验和验证、
persist-credentials: false
、评论分页、可移植的
ubuntu-latest
运行器,以及Fork PR的安全提示。