ci(refactor): [ENG-AUDIT-01] verify dedicated task completion commits

This commit is contained in:
Harvey Zhao committed 2026-09-12 09:45:21 +08:00
1 parent 4f324a8438
commit e0aacc5d6a
12 files changed
+450 -6

No files matched your search

+3
View File
@@ -37,6 +37,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
@@ -69,6 +70,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
@@ -116,6 +118,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
+3 -2
View File
@@ -47,7 +47,7 @@
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
"typecheck": "node scripts/typecheck.mjs",
@@ -65,7 +65,8 @@
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs",
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast.test.js test/vast-lifecycle.test.js"
"test:vast": "node --test test/vast.test.js test/vast-lifecycle.test.js",
"check:commits": "node refactor/scripts/commit-audit.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+1
View File
@@ -33,6 +33,7 @@
| [已实现的 CI 入口](ci-setup.md) | Yarn 检查/构建、只读 lint、Pages 隔离及远端待验收状态 |
| [GitHub CI/CD](github-ci-cd.md) | PR/兼容矩阵、构建报告、Pages、npm 发布及远端准入验证 |
| [AI 协作流程](ai-workflow.md) | AI 接续工作、任务边界、验证、记录和交接模板 |
| [每任务提交审计](commit-audit.md) | 实际Git状态迁移、独立提交、初始例外、分支合并和CI报告 |
| [架构决策](decisions.md) | 已选方向、待验证方案及被拒绝方案 |
| [进度与证据](progress.md) | 本次会话结果、阻塞、下一步;不重复维护每个任务状态 |
| [变更记录模板](changes/TEMPLATE.md) | 每次行为/类型/结构变化的详细记录 |
+4
View File
@@ -46,6 +46,10 @@ AI 用于源码分析、任务拆分、实现、测试和差异审查。现代
- 本地 commit 已获授权;push、merge、tag 和公开发布仍按用户明确授权执行。
- 新规则生效前已完成但未提交的 DOC-01 至 DOC-04 文档,作为 DOC-05 的初始文档基线一次入库,记录真实情况,不伪造之前存在的提交。
执行`yarn check:commits --report`核对真实Git完成历史,详见[提交审计](commit-audit.md)。
任务仍doing时跑适用检查;标done后立即独立提交,再运行审计验证自身。未提交的done
状态会被拒绝,这不是可跳过的CI错误。审计与现有历史源码测试要求完整Git历史。
## 后续任务提示模板
重构实现完成后继续执行 [多轮复盘与 npm 准入](release-reviews.md)。用户计划分多次任务复盘,后续 AI 从 REVIEW 任务与持久报告接续;不以迁移完成或 Chrome 已连接宣称发布就绪。每项复盘发现的修复仍独立建任务、验证和提交。
@@ -0,0 +1,73 @@
{
"schemaVersion": 1,
"task": "ENG-AUDIT-01",
"status": "done",
"sourceCommit": "4f324a84",
"checks": [
{
"command": "node --test refactor/scripts/commit-audit.test.mjs",
"result": "pass",
"tests": 4,
"scope": "Real isolated Git repositories, negative cases and a real merge",
"log": {
"file": "refactor/.cache/eng-audit-tests-first.log",
"sha256": "95a8c9fa5388f828a37a35c97667bb641107ddd027073ce027a6126e986c2761"
}
},
{
"command": "yarn check:commits --report",
"result": "pass before task completion",
"doneTasks": 80,
"dedicatedCommits": 76,
"bootstrapBundledTasks": 4,
"head": "4f324a84389186be7acd099ab074c4b21011ee81",
"report": {
"file": "refactor/.cache/eng-audit-before.json",
"sha256": "8dd298e4efd1b8ee21c06f245e3c73fe24d21fcfa77bbd83e2beea0d447c2a22"
}
},
{
"command": "actionlint 1.7.12",
"result": "pass",
"workflows": 2
},
{
"command": "yarn ci:check",
"result": "pass",
"tests": {
"unit": 832,
"engineering": 14,
"baseline": 58,
"total": 904
},
"log": {
"file": "refactor/.cache/eng-audit-ci.log",
"sha256": "73d92161c74c921a6755298efd18b49b1b68caf693c0c31f8aba040e050ebf92"
}
}
],
"limitations": [
"Local Git and workflow static validation only; no remote GitHub run",
"Commit/evidence provenance is not semantic test or implementation acceptance",
"ENG-09 impact and coverage subtasks remain open"
],
"inputs": [
{
"file": "refactor/scripts/commit-audit.mjs",
"sha256LF": "3b2076e428042017df2eb12b02c827a81059704f7435d86279e20f3e986f3d57"
},
{
"file": "refactor/scripts/commit-audit.test.mjs",
"sha256LF": "74d807947473ccafb783a9127a8fac480c3051b422a5e2edb9e1509ac02cd49c"
},
{
"file": "package.json",
"sha256LF": "94f3162e4ed876f5d4dcfbb4a93948a87b46314cf09d67ceaf9c3a8fd8960624"
},
{
"file": ".github/workflows/nodejs.yml",
"sha256LF": "a88eee053a57333c4234b88d93535ee8de1d805bf884ce2ad168e3617a1becc1"
}
],
"postCommitVerification": "Run yarn check:commits --report immediately after this task commit; its actual HEAD and completion must appear in the generated report. The report is not embedded in its own commit."
}
@@ -0,0 +1,26 @@
# ENG-AUDIT-01 独立完成提交的Git审计
起点4f324a84。把ENG-09拆出可独立验收的Git审计、依赖影响和契约覆盖三个子任务;
本步实现Git审计,其他两项及父任务保持todo。VAST初始化选择仍待用户确认,没有
更改其默认行为或将VAST-03标完成。
`commit-audit.mjs`核对真实任务状态迁移、父分支/merge、准确ID、plan和当时的本地
证据文件,不从done标志或提交标题推断工作已提交。初始80个done任务对应76个
独立完成提交,DOC-01~04只在固定DOC-05提交中合并。历史标题符号差异保留,
任务ID仍准确可追溯;没有重写、amend或伪造过去的提交。
新命令`yarn check:commits --report`接入`ci:check`,报告由既有CI日志artifact收集。
三个GitHub checkout补`fetch-depth: 0`;除审计外,已有测试也会读取历史源码Git对象。
没有新增依赖或更改Yarn锁文件。详细行为与本地提交前后顺序见
[维护说明](../commit-audit.md)。
使用隔离的真实Git仓库验证反例和分支合并,不能用mock成功输出替代历史验证。
实际仓库审计、测试、lint与工作流静态检查结果见
[验证记录](../baselines/commit-audit-validation.json)。远端GitHub运行仍由CI验收任务
处理,本次没有push,不能把本地通过称为远端通过。
完成后独立本地提交`ENG-AUDIT-01`,提交后重新运行审计验证本任务自身。回退本步
撤销脚本/测试/命令/checkout设置及文档台账,生产包API和已有历史均不变。
最终本地CI904项通过(832单元、14工程、58基线);脚本lint、plan及两个工作流的
actionlint检查通过。提交后审计将验证新增的第81个done任务及第77个独立完成提交。
+46
View File
@@ -0,0 +1,46 @@
# 每任务提交审计
`ENG-AUDIT-01`负责ENG-09的Git审计子项。运行`yarn check:commits`,或增加`--report`
将本次HEAD及完成记录写入`refactor/.cache/ci/commit-audit.json`。检查已接入`ci:check`,
GitHub日志artifact收集该报告。CI三个checkout使用完整历史:审计以及既有历史源码
兼容测试均依赖旧Git对象,浅克隆不能提供它们。
## 实际核对内容
脚本从可达Git历史读取每次tasks.json变化及其父提交,而不是从提交标题推断完成。
一项从非done转为done,必须在同一个提交包含:准确任务ID、tasks.json、生成plan.md、
存在于该提交树中的本地证据,以及至少一份该任务证据文件的实际变更。
普通完成提交只能完成一个任务。历史中的checkpoint不会被当作完成提交。合并时,
父分支已经done的任务沿用该分支的原始完成提交,不要求merge标题冒充任务完成。
当前工作区新增done但尚未提交会失败,已完成历史任务被从台账删除也会失败。
脚本核实起点与bootstrap提交可达;浅仓库明确报错,不悄悄跳过历史验证。
唯一合并完成例外固定为完整SHA `570600d2f6ffd580a890e0b87dfa9843bf8dd8d0`:
DOC-05携带先前已完成的DOC-01~04;要求该提交的新增done集合精确匹配这五项。
见 [原始说明](changes/2026-09-10-DOC-05-task-commits.md)。不能把例外按ID无限复用。
早期历史标题存在`test(BASE-04): ...`和`... ENG-04 ...`等格式。审计接受准确的任务
编号token,拒绝把TEST-010冒充TEST-01;这保留真实的独立提交,不是额外合并例外。
新增提交仍遵循AI工作流的`type(scope): [TASK-ID] description`格式。
## 本地实施顺序
1. 任务保持doing时运行适用测试、`ci:check`与审计;已提交的其他done任务必须可追溯。
2. 验收完成后将本任务标done,生成plan并暂存具体成果,立即独立提交。
3. 提交后再次运行`yarn check:commits --report`确认本任务的真实完成记录,并检查Git状态。
第2步尚未提交时审计会报告未提交完成,这是预期保护。没有“临时豁免”参数,也不能
在CI中通过环境变量绕过。审计不代替源码、测试结果或发布审查:证据文件共存不证明
其中断言有效,因此报告明确限制。它也不凭历史标题判断某个实现是否有足够测试。
## 验证和维护
`refactor/scripts/commit-audit.test.mjs`建立临时真实Git仓库,覆盖独立完成、固定
bootstrap例外、多个任务混在一提交、错误ID、缺plan/证据、复用未变化旧证据、路径
逃逸、未提交done、删除历史任务、浅仓库以及实际feature分支合并。临时仓库使用
专用作者,不修改用户Git配置;清理只针对已验证位于测试cache内的目录。
更改任务状态或提交政策时同时维护这些反例;禁止仅更新计数或忽略失败使审计变绿。
`ENG-IMPACT-01`继续处理影响范围,`ENG-COVERAGE-01`继续契约覆盖索引;本子项完成
不代表ENG-09整体、远端GitHub执行或npm发布门槛完成。
+7 -3
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 220 项,范围 22 个包及工作区/示例。
状态:todo 131 / doing 6 / blocked 0 / done 80 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 133 / doing 6 / blocked 0 / done 81 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -82,7 +82,10 @@
| ENG-06 | workspace<br>支持按包非交互与 JS/TS 构建 | ENG-02, BASE-05 | build/dev 入口解析、指定包参数、原交互保留 | 三种产物、Less/SVG/worker 和本地 8082 demo 正常;保持 BASE-05 的 AMD 同时写入全局行为及 i18n/legacy 入口 | H | done |
| ENG-07 | workspace<br>建立 tarball 消费与产物检查 | ENG-04, ENG-06 | 隔离 npm 消费 fixtures、API/声明/入口差分 | 不借 workspace 源码通过,能识别缺文件与默认导出变化 | H | done |
| ENG-08 | workspace<br>增加覆盖率、资源与性能报告 | ENG-03, ENG-05, BASE-06 | 覆盖率基线、资源清理断言、性能报告与阈值 | 关键生命周期分支有门槛,报告不靠无意义断言堆数量;将 BASE-06 的同环境多组配对、计时/压缩审查阈值与资源异常分开接入候选,不把历史现象冻结成正常要求 | M | done |
| ENG-09 | workspace<br>建立全包依赖影响和文档检查 | ENG-07, DOC-04 | 共享核心/构建影响映射、文档及每任务完成提交的 Git 审计接入;契约-支持版本-测试 ID-命令-候选/报告-任务的覆盖索引 | 核心变化触发必需生态检查;原有 DOC-01 至 04 基线例外明确,后续 done 任务不能缺失独立 commit;计划/已执行/缺证据分开,识别缺少验证归属的公开契约 | M | todo |
| ENG-AUDIT-01 | workspace<br>核对每任务完成提交的真实Git历史 | ENG-07, DOC-04 | 独立完成提交、初始DOC例外、父分支合并和证据共提交审计;CI完整历史与报告 | 真实仓库及隔离Git反例通过,done任务不允许缺独立commit,固定DOC-01~04例外可追溯;提交后验证自身 | M | done |
| ENG-IMPACT-01 | workspace<br>建立全包依赖与共享变更影响映射 | ENG-07, DOC-04 | 核心/构建/类型/锁文件到受影响包和必需生态检查的映射及CI接入 | 核心变化触发必需生态检查;新增包、未知共享文件或失配依赖不能静默漏检;解释受影响路径和测试命令 | M | todo |
| ENG-COVERAGE-01 | workspace<br>建立公开契约与版本测试证据覆盖索引 | BASE-08, CORE-22, ENG-07 | 契约-包-支持版本-固定测试ID-命令-候选/报告-责任任务索引和文档检查 | 计划、已执行和缺证据状态分开,所有公开契约都有验证归属;无效路径、版本依据或报告对应不明确会被识别 | M | todo |
| ENG-09 | workspace<br>建立全包依赖影响和文档检查 | ENG-07, DOC-04, ENG-AUDIT-01, ENG-IMPACT-01, ENG-COVERAGE-01 | 共享核心/构建影响映射、文档及每任务完成提交的 Git 审计接入;契约-支持版本-测试 ID-命令-候选/报告-任务的覆盖索引 | 核心变化触发必需生态检查;原有 DOC-01 至 04 基线例外明确,后续 done 任务不能缺失独立 commit;计划/已执行/缺证据分开,识别缺少验证归属的公开契约 | M | todo |
| ENG-10 | workspace<br>建立历史失败分级和测试可靠性规则 | ENG-03, ENG-04, ENG-05, ENG-07, BASE-07 | 历史失败 ID/环境/旧版复现/负责修复任务、逐模块门槛、受控等待与 trace/retry 规则 | 不靠全局忽略或无理由 skip 隐藏问题,新增回归阻止交付,设备缺口和偶发失败单独可见;以 risks.json 为统一差异索引;关闭必须有 resolutionEvidence/rationale,已复现、源码事实、未验证分开,登记不等于豁免 | M | done |
| ENG-11 | workspace<br>增加不改变产物的模块构建分析 | ENG-03, ENG-08 | 正式构建的可选模块归因报告及源码/产物指纹 | 相同构建开关前后三格式字节一致,报告不进入分发包;区分 Rollup 渲染字节与压缩体积,并保留真实工作区/发布来源 | L | done |
@@ -437,6 +440,7 @@
- ENG-06: [记录](changes/2026-09-10-ENG-06-build-development.md) [记录](build-development.md) [记录](baselines/build-validation.json)
- ENG-07: [记录](changes/2026-09-10-ENG-07-package-consumers.md) [记录](baselines/package-validation.json)
- ENG-08: [记录](coverage-performance.md) [记录](baselines/quality-validation.json) [记录](changes/2026-09-11-ENG-08-quality-reports.md)
- ENG-AUDIT-01: [记录](commit-audit.md) [记录](changes/2026-09-12-ENG-AUDIT-01-commits.md) [记录](baselines/commit-audit-validation.json)
- ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md)
- ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md)
- PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json)
+10
View File
@@ -1,5 +1,15 @@
# 进度与证据
## 最新完成:ENG-AUDIT-01 每任务提交审计
新增实际Git历史审计并接入ci:check,核对状态迁移、独立提交、证据/plan共提交和合并分支。
原80个done对应76个独立完成提交,另4项为固定DOC-05初始例外;保留真实历史标题格式。
真实Git反例及完整本地CI904项通过,GitHub三个checkout改为完整历史,actionlint通过。
提交后再审计本任务自身。ENG-09拆成Git审计、影响映射、契约覆盖三个子项,后二者
和父任务仍未完成;没有远端运行或推送。当前220项:81 done、6 doing、133 todo。
VAST初始化选择仍待确认;其余实施继续。见 [本步记录](changes/2026-09-12-ENG-AUDIT-01-commits.md)
和 [验证证据](baselines/commit-audit-validation.json)。
## 当前实施:VAST-03 TS拆分与生命周期修复
原入口拆成5个严格TS模块,处理核心终止、显式广告重建、SDK/DOM回滚与晚到/重入回调。
+99
View File
@@ -0,0 +1,99 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const root = fileURLToPath(new URL('../../', import.meta.url))
export const commitPolicy = {
bootstrapCommit: '570600d2f6ffd580a890e0b87dfa9843bf8dd8d0',
bootstrapTask: 'DOC-05',
bundledTasks: ['DOC-01', 'DOC-02', 'DOC-03', 'DOC-04'],
}
export function auditCommits(directory = root, policy = commitPolicy) {
const git = args => execFileSync('git', args, { cwd: directory, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] }).trimEnd()
assert.equal(git(['rev-parse', '--is-shallow-repository']), 'false', 'Commit audit requires full Git history; use checkout fetch-depth: 0')
const head = git(['rev-parse', 'HEAD'])
const current = JSON.parse(fs.readFileSync(path.join(directory, 'refactor/tasks.json'), 'utf8'))
git(['merge-base', '--is-ancestor', current.baselineCommit, head])
git(['merge-base', '--is-ancestor', policy.bootstrapCommit, head])
const snapshots = new Map()
function snapshot(commit) {
if (!snapshots.has(commit)) {
const entry = git(['ls-tree', commit, '--', 'refactor/tasks.json'])
const tasks = entry ? JSON.parse(git(['show', `${commit}:refactor/tasks.json`])).tasks : []
snapshots.set(commit, new Map(tasks.map(task => [task.id, task])))
}
return snapshots.get(commit)
}
const history = git(['log', '--full-history', '--reverse', '--format=%H%x00%P%x00%s', head, '--', 'refactor/tasks.json']).split('\n').filter(Boolean)
const completions = []
for (const line of history) {
const [commit, parentText, subject] = line.split('\0')
const parents = parentText.split(' ').filter(Boolean).map(snapshot)
const newlyDone = [...snapshot(commit).values()].filter(task => task.status === 'done' && parents.every(parent => parent.get(task.id)?.status !== 'done'))
if (!newlyDone.length)
continue
const bootstrap = commit === policy.bootstrapCommit
const exceptions = bootstrap ? policy.bundledTasks : []
const dedicated = newlyDone.filter(task => !exceptions.includes(task.id))
assert.equal(dedicated.length, 1, `${commit}: one completion commit must complete exactly one task; found ${dedicated.map(task => task.id).join(', ')}`)
if (bootstrap) {
assert.equal(dedicated[0].id, policy.bootstrapTask, 'Bootstrap exception is restricted to DOC-05')
assert.deepEqual(newlyDone.map(task => task.id).sort(), [...policy.bundledTasks, policy.bootstrapTask].sort(), 'Bootstrap exception task set changed')
}
const id = dedicated[0].id
assert(/^[A-Z]+(?:-[A-Z]+)*-\d{2}$/.test(id), 'Invalid task ID')
// Older subjects use parentheses or plain tokens; punctuation does not erase a real task commit.
assert(new RegExp(`(?:^|[^A-Z0-9-])${id}(?:$|[^A-Z0-9-])`).test(subject), `${commit}: completion subject does not identify exact task ${id}`)
const changes = git(['diff-tree', '--root', '-m', '--no-commit-id', '--name-only', '-r', '-z', commit]).split('\0').filter(Boolean)
assert(changes.includes('refactor/tasks.json'), `${commit}: missing task-state change`)
assert(changes.includes('refactor/plan.md'), `${commit}: missing generated plan change`)
const tree = new Set(git(['ls-tree', '-r', '--name-only', '-z', commit, '--', 'refactor']).split('\0').filter(Boolean))
for (const task of newlyDone) {
assert(task.evidence?.length, `${commit}: ${task.id} has no evidence`)
const local = task.evidence.filter(file => !/^https?:\/\//.test(file)).map((file) => {
const name = file.split('#')[0]
const normalized = path.posix.normalize(name)
assert(name && !name.includes('\\') && !path.posix.isAbsolute(name) && !/^[A-Z]:/i.test(name) && normalized !== '..' && !normalized.startsWith('../'), `${commit}: evidence escapes refactor`)
return `refactor/${normalized}`
})
assert(local.length, `${commit}: ${task.id} needs durable local completion evidence`)
for (const file of local) assert(tree.has(file), `${commit}: ${task.id} evidence missing at completion: ${file}`)
if (!exceptions.includes(task.id))
assert(local.some(file => changes.includes(file)), `${commit}: ${task.id} has no evidence updated in its completion commit`)
completions.push({ task: task.id, commit, subject, bundledWith: exceptions.includes(task.id) ? policy.bootstrapTask : null, evidence: local, changedEvidence: local.filter(file => changes.includes(file)) })
}
}
const currentTasks = new Map(current.tasks.map(task => [task.id, task]))
const committed = snapshot(head)
for (const completion of completions)
assert(currentTasks.has(completion.task), `Historical completed task was removed: ${completion.task}`)
for (const task of current.tasks.filter(task => task.status === 'done')) {
assert.equal(committed.get(task.id)?.status, 'done', `Uncommitted completion: ${task.id}; commit this task before the final audit`)
assert(completions.some(completion => completion.task === task.id), `No dedicated completion found for ${task.id}`)
}
return {
schemaVersion: 1,
head,
baselineCommit: current.baselineCommit,
policy,
doneTasks: current.tasks.filter(task => task.status === 'done').length,
dedicatedCommits: new Set(completions.filter(item => !item.bundledWith).map(item => item.commit)).size,
completions,
limitation: 'Verifies actual completion transitions, task identity and durable evidence co-committed with the plan; does not prove the semantic quality of implementation or tests.',
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
assert(process.argv.slice(2).every(arg => arg === '--report'), 'Use --report or no arguments')
const report = auditCommits()
if (process.argv.includes('--report')) {
const directory = path.join(root, 'refactor/.cache/ci')
fs.mkdirSync(directory, { recursive: true })
fs.writeFileSync(path.join(directory, 'commit-audit.json'), `${JSON.stringify(report, null, 2)}\n`)
}
console.log(`Commit audit: ${report.doneTasks} done tasks, ${report.dedicatedCommits} dedicated completion commits, ${report.policy.bundledTasks.length} documented bootstrap tasks; HEAD ${report.head}`)
}
+118
View File
@@ -0,0 +1,118 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- Exercise real isolated Git histories, not mocked command output.
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { auditCommits } from './commit-audit.mjs'
const cache = fileURLToPath(new URL('../.cache/', import.meta.url))
function repository(t) {
fs.mkdirSync(cache, { recursive: true })
const directory = fs.mkdtempSync(path.join(cache, 'commit-audit-test-'))
t.after(() => {
const relative = path.relative(cache, directory)
assert(relative.startsWith('commit-audit-test-') && !relative.includes(path.sep))
fs.rmSync(directory, { recursive: true, force: true })
})
const git = args => execFileSync('git', args, {
cwd: directory,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_AUTHOR_NAME: 'Audit Fixture', GIT_AUTHOR_EMAIL: 'audit@example.test', GIT_COMMITTER_NAME: 'Audit Fixture', GIT_COMMITTER_EMAIL: 'audit@example.test', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: process.platform === 'win32' ? 'NUL' : '/dev/null' },
}).trim()
const write = (file, text) => {
fs.mkdirSync(path.dirname(path.join(directory, file)), { recursive: true })
fs.writeFileSync(path.join(directory, file), text)
}
const commit = (subject) => {
git(['add', '.'])
git(['-c', 'core.hooksPath=', '-c', 'commit.gpgSign=false', 'commit', '-m', subject])
return git(['rev-parse', 'HEAD'])
}
git(['init', '-b', 'main'])
write('README.md', 'Test-only Git repository\n')
const baselineCommit = commit('Baseline')
const bundledTasks = ['DOC-01', 'DOC-02', 'DOC-03', 'DOC-04']
const data = { baselineCommit, tasks: [...bundledTasks, 'DOC-05'].map(id => ({ id, status: 'done', evidence: ['changes/bootstrap.md'] })) }
for (const id of ['TEST-01', 'TEST-02']) data.tasks.push({ id, status: 'doing', evidence: [] })
const save = (plan = true) => {
write('refactor/tasks.json', JSON.stringify(data))
if (plan)
write('refactor/plan.md', data.tasks.map(task => `${task.id}: ${task.status}`).join('\n'))
}
write('refactor/changes/bootstrap.md', 'Initial documentation and the per-task rule\n')
save()
const bootstrapCommit = commit('docs: [DOC-05] establish initial documentation')
const policy = { bootstrapCommit, bootstrapTask: 'DOC-05', bundledTasks }
const finish = (ids = ['TEST-01'], options = {}) => {
for (const id of ids) {
const task = data.tasks.find(task => task.id === id)
task.status = 'done'
task.evidence = options.evidence || [`changes/${id}.md`]
if (options.writeEvidence !== false)
write(`refactor/changes/${id}.md`, `${id}: tested completion\n`)
}
save(options.plan !== false)
if (options.commit !== false)
return commit(options.subject || `test: [${ids[0]}] completed work`)
}
return { directory, git, write, commit, save, finish, data, policy, audit: () => auditCommits(directory, policy) }
}
test('Commit audit proves dedicated transitions and only the pinned bootstrap bundling', (t) => {
const repo = repository(t)
const first = repo.finish()
const second = repo.finish(['TEST-02'], { subject: 'test(TEST-02): historical punctuation remains traceable' })
const report = repo.audit()
assert.equal(report.doneTasks, 7)
assert.equal(report.dedicatedCommits, 3)
assert.deepEqual(report.completions.filter(item => item.bundledWith).map(item => item.task), repo.policy.bundledTasks)
assert.equal(report.completions.find(item => item.task === 'TEST-01').commit, first)
assert.equal(report.completions.find(item => item.task === 'TEST-02').commit, second)
assert.throws(() => auditCommits(repo.directory, { ...repo.policy, bundledTasks: [...repo.policy.bundledTasks, 'TEST-01'] }), /Bootstrap exception task set changed/)
})
test('Commit audit rejects combined task completion, misleading subjects and missing co-committed evidence', (t) => {
for (const [ids, options, expected] of [
[['TEST-01', 'TEST-02'], {}, /exactly one task/],
[['TEST-01'], { subject: 'test: [TEST-010] not the completed task' }, /exact task TEST-01/],
[['TEST-01'], { plan: false }, /missing generated plan change/],
[['TEST-01'], { writeEvidence: false }, /evidence missing at completion/],
[['TEST-01'], { evidence: ['changes/bootstrap.md'], writeEvidence: false }, /no evidence updated/],
[['TEST-01'], { evidence: ['../README.md'] }, /evidence escapes refactor/],
]) {
const repo = repository(t)
repo.finish(ids, options)
assert.throws(repo.audit, expected)
}
})
test('Commit audit rejects uncommitted done state and shallow or removed completion history', (t) => {
const repo = repository(t)
repo.finish(['TEST-01'], { commit: false })
assert.throws(repo.audit, /Uncommitted completion: TEST-01/)
repo.commit('test: [TEST-01] finish with proof')
repo.data.tasks = repo.data.tasks.filter(task => task.id !== 'TEST-01')
repo.save()
assert.throws(repo.audit, /Historical completed task was removed/)
repo.write('.git/shallow', `${repo.git(['rev-parse', 'HEAD'])}\n`)
assert.throws(repo.audit, /requires full Git history/)
})
test('Commit audit finds feature-branch completion through a real merge without calling the merge a second completion', (t) => {
const repo = repository(t)
repo.git(['checkout', '-b', 'feature'])
const completed = repo.finish()
repo.git(['checkout', 'main'])
repo.write('notes.md', 'Unrelated main branch work\n')
repo.commit('docs: independent work')
repo.git(['-c', 'core.hooksPath=', '-c', 'commit.gpgSign=false', 'merge', '--no-ff', 'feature', '-m', 'Merge feature branch'])
const report = repo.audit()
assert.equal(report.doneTasks, 6)
assert.equal(report.dedicatedCommits, 2)
assert.equal(report.completions.filter(item => item.task === 'TEST-01').length, 1)
assert.equal(report.completions.find(item => item.task === 'TEST-01').commit, completed)
})
+60 -1
View File
@@ -604,6 +604,62 @@
"changes/2026-09-11-ENG-08-quality-reports.md"
]
},
{
"phase": "2 工程保障",
"scope": [
"workspace"
],
"risk": "M",
"id": "ENG-AUDIT-01",
"title": "核对每任务完成提交的真实Git历史",
"status": "done",
"dependsOn": [
"ENG-07",
"DOC-04"
],
"deliverable": "独立完成提交、初始DOC例外、父分支合并和证据共提交审计;CI完整历史与报告",
"acceptance": "真实仓库及隔离Git反例通过,done任务不允许缺独立commit,固定DOC-01~04例外可追溯;提交后验证自身",
"evidence": [
"commit-audit.md",
"changes/2026-09-12-ENG-AUDIT-01-commits.md",
"baselines/commit-audit-validation.json"
]
},
{
"phase": "2 工程保障",
"scope": [
"workspace"
],
"risk": "M",
"id": "ENG-IMPACT-01",
"title": "建立全包依赖与共享变更影响映射",
"status": "todo",
"dependsOn": [
"ENG-07",
"DOC-04"
],
"deliverable": "核心/构建/类型/锁文件到受影响包和必需生态检查的映射及CI接入",
"acceptance": "核心变化触发必需生态检查;新增包、未知共享文件或失配依赖不能静默漏检;解释受影响路径和测试命令",
"evidence": []
},
{
"phase": "2 工程保障",
"scope": [
"workspace"
],
"risk": "M",
"id": "ENG-COVERAGE-01",
"title": "建立公开契约与版本测试证据覆盖索引",
"status": "todo",
"dependsOn": [
"BASE-08",
"CORE-22",
"ENG-07"
],
"deliverable": "契约-包-支持版本-固定测试ID-命令-候选/报告-责任任务索引和文档检查",
"acceptance": "计划、已执行和缺证据状态分开,所有公开契约都有验证归属;无效路径、版本依据或报告对应不明确会被识别",
"evidence": []
},
{
"id": "ENG-09",
"phase": "2 工程保障",
@@ -613,7 +669,10 @@
],
"dependsOn": [
"ENG-07",
"DOC-04"
"DOC-04",
"ENG-AUDIT-01",
"ENG-IMPACT-01",
"ENG-COVERAGE-01"
],
"status": "todo",
"risk": "M",