@hunter-harness/workflow-harness 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/claude-code/harness-run/checklist.md +7 -3
- package/harness/bundles/general/claude-code/harness-run/reference.md +3 -9
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/claude-code/harness-test/checklist.md +4 -0
- package/harness/bundles/general/claude-code/harness-test/reference.md +4 -4
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codebuddy/harness-run/reference.md +3 -9
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codebuddy/harness-test/reference.md +4 -4
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/codex/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codex/harness-run/reference.md +3 -9
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/codex/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codex/harness-test/reference.md +4 -4
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/codex/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/codex/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/cursor/harness-run/checklist.md +7 -3
- package/harness/bundles/general/cursor/harness-run/reference.md +3 -9
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/cursor/harness-test/checklist.md +4 -0
- package/harness/bundles/general/cursor/harness-test/reference.md +4 -4
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/cursor/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/codex/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/codex/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/cursor/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +502 -0
- package/harness/manifests/general/claude-code.json +25 -21
- package/harness/manifests/general/codebuddy.json +25 -21
- package/harness/manifests/general/codex.json +25 -21
- package/harness/manifests/general/cursor.json +25 -21
- package/harness/manifests/java/claude-code.json +23 -19
- package/harness/manifests/java/codebuddy.json +23 -19
- package/harness/manifests/java/codex.json +23 -19
- package/harness/manifests/java/cursor.json +23 -19
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -518,19 +518,13 @@ powershell.exe -Command "<测试命令> <模块定位参数>"
|
|
|
518
518
|
- 顶层写入 `diffHash` / `currentHead` / `baseCommit` / `module` / `profile`
|
|
519
519
|
- `baseCommit`:merge-base 或计划起点(worktree 分支从主分支分出点,由 harness-plan 写入、run 读取复用;缺失时用 `git merge-base HEAD <默认分支>` 兜底)
|
|
520
520
|
- `currentHead`:`git rev-parse HEAD`(步骤 2c 在 Step 5 checkpoint commit 之前执行,此时 HEAD==baseCommit;commit 后 HEAD 前移到 checkpoint commit,由 ledger-protocol reuse 规则 #2「currentHead 可前移」容忍,**不需为它改时序**)
|
|
521
|
-
- `diffHash
|
|
521
|
+
- `diffHash`:必须由 ledger v2 的 commit-invariant 内容变更集算法生成,并通过 `--change-dir` 纳入 test-tracking manifest 中被 `.gitignore` 忽略的测试;禁止自行拼接 Git 输出或仅使用某一段 diff:
|
|
522
522
|
|
|
523
523
|
```powershell
|
|
524
|
-
|
|
524
|
+
python <skills-root>/scripts/harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json
|
|
525
525
|
```
|
|
526
526
|
|
|
527
|
-
>
|
|
528
|
-
|
|
529
|
-
> ⚠️ **禁止任何单部分简化(堵字面空子)**:上述教训只点了"仅用未提交 diff"。实际还有两种等价违规简化,均**禁止**:
|
|
530
|
-
> - `git diff <base> HEAD --binary`(仅已提交部分):commit 后工作树 clean 时结果偶然与三部分合并一致,但 commit 前算会漏未提交+未跟踪,且方法本身违反"三部分合并"要求。
|
|
531
|
-
> - `node -e "...crypto.createHash('sha256')..."` 自算:绕过 PowerShell 三部分合并命令,且无法捕获未跟踪文件内容。
|
|
532
|
-
>
|
|
533
|
-
> 无论 commit 前后、无论工作树是否 clean,**必须**用三部分合并命令。"commit 后 clean 致单部分偶然等价"不得作为省略三部分的依据——时序或工作树状态一旦变化即复现复用链断裂。
|
|
527
|
+
> `content-changeset-2` 同时读取 tracked diff、标准 untracked 文件和 manifest 的精确测试路径。manifest 缺失时保持普通行为;manifest 存在但路径越界、内容 hash 漂移或结构非法时命令失败,ledger 不可复用。checkpoint commit 不改变各路径的工作树内容,因此提交前后 hash 保持一致。
|
|
534
528
|
|
|
535
529
|
> 这样 harness-test 的 Phase 1 可读取 ledger 判断是否复用 run 的 unitTest(diffHash commit-invariant + reuse 规则 #2 允许 HEAD 前移 → run 的 checkpoint commit 不破坏复用),submit 也可复用 compile 结果。详见 `../protocols/ledger-protocol.md`。
|
|
536
530
|
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff
|
|
4
4
|
合并回主分支。使用场景:提交代码、commit、push、合并分支、merge to main、完成开发
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=2df0efbebf1f603a; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -53,10 +53,10 @@ description: 最终提交封装:验证→中文 commit→提交/推送;workt
|
|
|
53
53
|
|
|
54
54
|
### 提交流程(步骤 0–7)
|
|
55
55
|
|
|
56
|
-
0. **启动准备** — 确定变更名;读 ledger
|
|
56
|
+
0. **启动准备** — 确定变更名;读 ledger,以 `harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 计算 diffHash + post-test 7 类分类;append `phase.start`
|
|
57
57
|
1. **合并最新代码** — 主目录:`stash` → `pull --rebase` → `stash pop`;**worktree:N/A**(远端同步在合并段主分支完成)
|
|
58
58
|
2. **最终验证** — ledger 复用优先;**提交前最终门禁只调 `can-reuse`**(删除与 coverage 冲突的二次全量门禁):`harness_ledger.py can-reuse --verification unitTestFull --scope module --project . --profile-input unitTestFull --command <resolved commands.unitTestFull.command>`。`--command` **按 profile key resolve**:读 `build-profile.json` 的 `commands.unitTestFull.command`(v2),或 `harness_profile.py resolve --project . --key unitTestFull --json` 取 resolved command,**不复制示例模块名**(文档示例只展示 key)。`--profile-input unitTestFull` 从 `verificationInputs.unitTestFull`(v2 由 `commands.unitTestFull.inputs` 派生)展开依赖闭包,**禁止用仅含 staged 文件的 `--files` 快捷方式**冒充全量闭包。`reuse=true` → 不再执行二次全量测试;仅 `reuse=false` 时执行**同一 resolved verification**(profile `unitTestFull` 命令),成功后用同一文件集、command、`scope=module` 写回 ledger `unitTestFull` 项。增量 `unitTest` 永远不能冒充 `unitTestFull` 门禁。
|
|
59
|
-
3. **.gitignore +
|
|
59
|
+
3. **.gitignore + 精确暂存** ⚠️ — 检查 `.harness/` 在 `.gitignore`;**禁止 `git add -A`**。若存在 `evidence/test-tracking.json`,先执行 `python <skills-root>/scripts/harness_test_guard.py stage --project . --change-dir ".harness/changes/<change-name>" --json`;失败即硬停止。无 manifest 时不使用 `-f`。manifest 之外的文件按精确业务路径正常暂存,**禁止全局 force-add**。
|
|
60
60
|
4. **提交方式** — 主目录:AskUserQuestion 三选项(commit+push / 仅本地 / 取消);**worktree:固定仅本地 commit**
|
|
61
61
|
5. **commit-message.txt** ⚠️ — 展示 staged、diff stat、完整中文 message;用户确认
|
|
62
62
|
6. **commit / push** — `git commit -F`;主目录按选项 push(push 前 fetch 检查远端);**worktree:只 commit,记录 local hash**
|
|
@@ -98,6 +98,8 @@ commit+push / 仅本地 commit / 取消;**不调用 Superpowers 呈现 PR/丢
|
|
|
98
98
|
|
|
99
99
|
见 `checklist.md` 与 `../protocols/ledger-protocol.md`、`../protocols/submit-protocol.md`。
|
|
100
100
|
|
|
101
|
+
test-tracking manifest 是 ignored test 的唯一强制暂存授权:只允许 `harness_test_guard.py stage` 暂存 manifest 中已校验路径,禁止 `git add -f .`、目录级 `git add -f` 或全局修改 `.gitignore`。worktree commit 前须确认 manifest 路径全部进入 cached diff;合并回主分支后、删除 worktree 前须确认这些路径已由目标 commit 跟踪,否则停止清理。
|
|
102
|
+
|
|
101
103
|
### 四、worktree 合并硬规则
|
|
102
104
|
|
|
103
105
|
- **push 只在主分支**;worktree 分支不 push
|
|
@@ -25,7 +25,7 @@ description: harness-submit 的8 步工作流详细步骤和执行日志记录
|
|
|
25
25
|
**读取 verification-ledger + post-test 变更分类**:
|
|
26
26
|
|
|
27
27
|
1. 读取 `.harness/changes/<change-name>/evidence/verification-ledger.json`,记录 `diffHash` / `currentHead` / `unitTest` / `apiTest` 状态
|
|
28
|
-
2.
|
|
28
|
+
2. 执行 `harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 计算当前 diffHash;manifest 校验失败即停止,不得绕过后与 ledger 比对
|
|
29
29
|
3. 若 test 之后有代码变更(当前 diffHash ≠ test 完成时的 diffHash),按 7 类对 post-test diff 分类(见 `../protocols/ledger-protocol.md` 第六章),写入 ledger 的 `postTestClassification`
|
|
30
30
|
4. 行为性变更(BEHAVIORAL_SERVICE_CHANGE / API_CONTRACT_CHANGE / SQL_OR_MAPPER_CHANGE / SECURITY_OR_PERMISSION_CHANGE)→ **停止 submit**,提示先重跑 `/harness-test` 相关场景
|
|
31
31
|
5. 非行为性变更(NON_BEHAVIORAL_CLEANUP / COMMENT_ONLY / TEST_ONLY)→ 可继续 submit,但后续验证按复用规则处理
|
|
@@ -116,6 +116,13 @@ python <skills-root>/scripts/harness_ledger.py can-reuse `
|
|
|
116
116
|
|
|
117
117
|
## 步骤 3:.gitignore 检查 + 暂存业务文件 ⚠️ 强制阻断
|
|
118
118
|
|
|
119
|
+
### Ignored test 精确暂存
|
|
120
|
+
|
|
121
|
+
- [ ] 检查 `.harness/changes/<change-name>/evidence/test-tracking.json` 是否存在
|
|
122
|
+
- [ ] 存在时先执行 `python <skills-root>/scripts/harness_test_guard.py stage --project . --change-dir ".harness/changes/<change-name>" --json`;任一 manifest/path/hash/index 校验失败立即停止
|
|
123
|
+
- [ ] 用 `git diff --cached --name-only` 验证 manifest 中每个精确路径均已暂存,且没有 guard 引入的额外路径
|
|
124
|
+
- [ ] 无 manifest 时禁止使用 `git add -f`;有 manifest 时也只允许 guard 内部的 exact force-add,**禁止全局 force-add**、目录级 force-add 和全局修改 `.gitignore`
|
|
125
|
+
|
|
119
126
|
提交前**强制检查 `.gitignore`** 是否包含 `.harness/`:
|
|
120
127
|
|
|
121
128
|
```powershell
|
|
@@ -406,6 +413,8 @@ powershell.exe -Command "git -C '<项目路径>' push"
|
|
|
406
413
|
|
|
407
414
|
### 步骤 M7:清理 worktree
|
|
408
415
|
|
|
416
|
+
若存在 test-tracking manifest,删除 worktree 前必须逐一确认 manifest 路径已包含在 worktree commit 中,且合并后的主分支可用 `git ls-files --error-unmatch -- <exact-path>` 找到;任一路径缺失即停止清理并报告,防止 ignored test 随 worktree 被永久删除。
|
|
417
|
+
|
|
409
418
|
```powershell
|
|
410
419
|
powershell.exe -Command "git -C '<项目路径>' worktree remove '<worktree-path>' --force"
|
|
411
420
|
powershell.exe -Command "git -C '<项目路径>' branch -D worktree/<change-name>"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-sync
|
|
3
3
|
description: 检查并更新项目AI元数据(CodeGraph索引、harness-codebase-map分析、CLAUDE.md、AGENTS.md、.harness/完整性),确保Claude对代码库的理解与最新代码一致。当用户说'同步/更新索引/刷新元数据/检查一致性'时使用
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=2df0efbebf1f603a; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-sync — 元数据同步
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-test
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。当用户说'跑测试/验证/跑用例/接口测试/单元测试'时使用
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=2df0efbebf1f603a; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-test — 测试执行
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -99,12 +99,24 @@ description: 测试执行:读取场景表,执行单元测试+API接口测试
|
|
|
99
99
|
|
|
100
100
|
### 四、单元测试复用 + 写入 ledger
|
|
101
101
|
|
|
102
|
-
Phase 1 前先读 `.harness/changes/<change-name>/evidence/verification-ledger.json
|
|
102
|
+
Phase 1 前先读 `.harness/changes/<change-name>/evidence/verification-ledger.json`,并用 `harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 重算真实指纹:run 的 unitTest 满足(diffHash 一致 + module/profile 一致 + scope 一致或更严格 + run 后无行为性修改 + run 跑了全量测试)则复用,否则按 **profile key resolve** 执行测试命令(`python <skills-root>/scripts/harness_profile.py resolve --project . --key unitTest|unitTestFull --json`,**不复制示例 `-pl` 命令**)。Phase 1/2 完成后必须写回 ledger:执行增量测试类 → 记 `unitTest`(scope=incremental);执行 profile 模块全量命令 → 记 `unitTestFull`(scope=module,可供 submit 复用);接口测试 → 记 `apiTest`。详见 `checklist.md`「单元测试复用」、`../protocols/ledger-protocol.md`。
|
|
103
103
|
|
|
104
104
|
### 五、命令与请求超时治理
|
|
105
105
|
|
|
106
106
|
所有命令必须有「预期时长 + 超时上限」,超过预期必须输出一次状态行,**不得静默等待**。`durationMs > 10000` → 🟡 SLOW,`> 30000` → ❌ TIMEOUT_RISK。详见 `reference.md`「命令与请求超时治理」。
|
|
107
107
|
|
|
108
|
+
### 五-A、陈旧测试安全修复
|
|
109
|
+
|
|
110
|
+
若测试编译或执行明确指向已移除/改名 API 的陈旧测试,且当前生产代码、已批准计划或可验证历史能唯一确定新契约,可仅修改测试并立即重跑该测试与目标测试,然后记录:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
python <skills-root>/scripts/harness_test_guard.py record --project . --change-dir ".harness/changes/<change-name>" --files "<精确测试文件路径,逗号分隔>" --reason stale-test-repair --json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
普通新增/更新测试使用 `tdd-created` / `test-updated`。存在业务歧义或修复会触及生产代码时,记录 `BLOCKED_PREEXISTING` 并停止复用该验证,不得猜测或绕过。
|
|
117
|
+
|
|
118
|
+
**禁止临时排除测试**:禁止 `.bak`/改名、移出测试目录、删除、禁用注解、构建 exclude、`skipTests`/`maven.test.skip` 充当测试通过证据;服务启动可在单元测试已独立通过后使用 `-Dmaven.test.skip=true` 避免重复编译测试,但不得据此声明测试通过。所有本轮新增、更新或安全修复且被忽略的测试必须写入 test-tracking manifest;仅执行、未修改的只读 ignored test 不获得 force-track 授权。
|
|
119
|
+
|
|
108
120
|
### 六、服务启动 + 生命周期管理
|
|
109
121
|
|
|
110
122
|
启动等待状态机:0–30s 每 2s 探测、30–120s 每 5s 探测、>120s 读日志判定;遇启动失败特征立即停。**Service Gate**:`harness_service.py ensure` 返回 `action=needs-user-decision`(用户自启服务占端口)时 **才** AskUserQuestion;AI 托管服务或端口空闲则自动继续,不询问。服务指纹(`moduleInputsHash`,来自 CLI `--files` ∪ `serviceStart.inputFiles`)+ `startCommandHash` + `profile` + `overlayPath` + 进程身份任一变化即 restart;**空输入被拒绝**,不生成可复用空指纹。测试结束默认清理 AI 启动的服务。详见 `reference.md`「服务决策门」。
|
|
@@ -252,6 +252,10 @@ powershell.exe -NoProfile -Command "try { (Invoke-WebRequest -Uri 'http://127.0.
|
|
|
252
252
|
- [ ] 判断是否复用 run 的 unitTest:diffHash 一致 + module/profile 一致 + scope 一致或更严格 + run 后无行为性修改 + run 实际跑了全量测试
|
|
253
253
|
- [ ] 复用 → 跳过重跑,标记"✅ 复用 harness-run 单元测试结果"
|
|
254
254
|
- [ ] 不复用 → 按 profile key resolve 重跑测试命令(`harness_profile.py resolve --key unitTest`,不复制示例 `-pl` 命令),结果写回 ledger 的 `unitTest` 项
|
|
255
|
+
- [ ] 复用判断前以 `harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 重算指纹;test-tracking manifest 无效或 hash 漂移即停止
|
|
256
|
+
- [ ] 测试失败若明确为陈旧测试,仅在当前代码/批准计划/可验证历史唯一确定新契约且只改测试时自动修复;否则记录 `BLOCKED_PREEXISTING`
|
|
257
|
+
- [ ] 自动修复后立即重跑该测试与目标测试,并以 `harness_test_guard.py record ... --reason stale-test-repair` 记录精确路径
|
|
258
|
+
- [ ] **禁止临时排除测试**:未使用 `.bak`/改名/移目录/删除/禁用注解/build exclude/skip-tests 制造通过;服务启动的 `maven.test.skip` 不作为测试证据
|
|
255
259
|
|
|
256
260
|
### 批量执行器(强制单次 PowerShell + 执行器绝对路径执行)
|
|
257
261
|
|
|
@@ -767,10 +767,10 @@ TEST_<change-name>_<timestamp>_<short-random>
|
|
|
767
767
|
|
|
768
768
|
> diffHash 与 ledger 复用规则遵循 `../protocols/ledger-protocol.md`。
|
|
769
769
|
|
|
770
|
-
后续复用 ledger 前必须生成真实 SHA-256 diffHash
|
|
770
|
+
后续复用 ledger 前必须生成真实 SHA-256 diffHash,且必须同时覆盖 tracked/untracked 变化和 test-tracking manifest 中被忽略的测试。`<baseCommit>` 从 ledger 读取(plan 阶段写入),缺失时用 `git merge-base HEAD <默认分支>`:
|
|
771
771
|
|
|
772
772
|
```powershell
|
|
773
|
-
|
|
773
|
+
python <skills-root>/scripts/harness_ledger.py diff-hash --repo . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json
|
|
774
774
|
```
|
|
775
775
|
|
|
776
776
|
写入 `.harness/changes/<change>/evidence/verification-ledger.json`:
|
|
@@ -778,11 +778,11 @@ powershell.exe -NoProfile -Command "$base = '<baseCommit>'; $patch = '.harness/c
|
|
|
778
778
|
```json
|
|
779
779
|
{
|
|
780
780
|
"diffHash": "sha256:<real_hash>",
|
|
781
|
-
"
|
|
781
|
+
"algorithmVersion": "content-changeset-2"
|
|
782
782
|
}
|
|
783
783
|
```
|
|
784
784
|
|
|
785
|
-
|
|
785
|
+
禁止使用描述性字符串、单段 `git diff` 或自写 hash 替代脚本。manifest 无效、路径越界或 hash 漂移时必须停止,不得删除 manifest 后继续复用旧 ledger。
|
|
786
786
|
|
|
787
787
|
## 执行日志记录
|
|
788
788
|
|
|
@@ -168,8 +168,6 @@ CONDITIONAL_OK
|
|
|
168
168
|
|
|
169
169
|
## 未提交测试证据
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
run/test 本轮新增、更新或安全修复且被 `.gitignore` 忽略的测试,必须先由 `test-tracking.json` + `harness_test_guard.py stage` 精确提交;这类测试不得继续作为“未提交证据”收尾,也不得计入可复用 P0 验证后随 worktree 删除。
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
2. 在 `summary-data.json.uncommittedTestEvidence[]` 中记录文件名、验证了什么、是否随 commit 提交;
|
|
175
|
-
3. final-summary 明确展示(`uncommittedTestEvidence` 非空时渲染"🧪 未提交测试证据"卡片),避免维护者误以为仓库包含该回归测试;无此类证据时不渲染该卡。
|
|
173
|
+
`uncommittedTestEvidence` 只兼容历史归档或明确只读的外部证据:归档到 `backups/uncommitted-tests/`,记录文件名、验证范围与未提交原因,并在 final-summary 展示风险。若它是本轮可修改的回归测试,则 archive 必须阻断并要求先走精确跟踪流程。
|
|
@@ -199,33 +199,25 @@ profile key 缺失 / glob 无匹配 / 结果为空 → `insufficient-evidence`
|
|
|
199
199
|
|
|
200
200
|
## 五、真实 diffHash
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
必须基于真实内容变更集计算 SHA-256,且**必须是 commit-invariant 的**——覆盖本变更集相对 `baseCommit` 的 tracked 变化、标准 untracked 文件,以及 `test-tracking.json` 明确记录的 ignored tests,使 checkpoint commit 前后指纹一致,保证 run→test→submit 复用链不断。
|
|
203
203
|
|
|
204
204
|
`<baseCommit>` 从 ledger 读取(harness-plan 阶段写入);缺失时用 `git merge-base HEAD <默认分支>` 兜底。
|
|
205
205
|
|
|
206
206
|
**canonical 命令(cluster 2 起脚本化,推荐)**:
|
|
207
207
|
|
|
208
208
|
```text
|
|
209
|
-
harness_ledger.py diff-hash --repo <projectRoot> --base <baseCommit> --json
|
|
209
|
+
harness_ledger.py diff-hash --repo <projectRoot> --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
`diff-hash` 用 subprocess 直接捕获 Git bytes
|
|
212
|
+
`diff-hash` 用 subprocess 直接捕获 Git bytes,按 repo-relative path 排序并进行长度 framing(`path-len | path | exists | content-len | content`),独立于 shell/控制台编码/BOM/系统换行。算法版本 `content-changeset-2`,输出 `diffHash`/`algorithmVersion`/`fileCount`/`base`/`head`/`trackedTestFileCount`/`testTrackingManifest`。
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
powershell.exe -NoProfile -Command "$base = '<baseCommit>'; $patch = '.harness/changes/<change>/runtime/current-diff.patch'; & { git diff $base HEAD --binary; git diff --binary; git ls-files --others --exclude-standard | ForEach-Object { Get-Content -Raw -LiteralPath $_ } } | Out-File -Encoding utf8 $patch; (Get-FileHash $patch -Algorithm SHA256).Hash"
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
- 命令为模板:`<baseCommit>`/`<change>` 需替换;经 `powershell.exe -Command "..."` 从 PowerShell/Bash 调用时外层会展开 `$` 变量,建议直接用 PowerShell 工具执行去外壳脚本,或外层用单引号/`--%` 防展开
|
|
221
|
-
- 三部分合并:`git diff $base HEAD`(已提交变更)+ `git diff`(未提交 tracked)+ 未跟踪新文件内容(`git ls-files --others --exclude-standard`);`& { }` 必须带 `&` 才执行 scriptblock(bare `{ } | Out-File` 只写脚本块字符串,不执行)
|
|
222
|
-
- commit 前:`$base..HEAD` 为空 + 未提交 24 文件 + 未跟踪 9 文件 = 全量;commit 后:`$base..HEAD` 24 文件 + 未提交空 + 未跟踪空 = 同内容 → 指纹一致 ✅
|
|
214
|
+
- `--change-dir` 可选以兼容普通调用;run/test/submit 对正式 change 必须传入。manifest 不存在时贡献 0 个额外路径。
|
|
215
|
+
- manifest 存在时必须严格验证 schema/mode/projectRoot、精确相对路径、文件存在性与 SHA-256;结构非法、路径越界或 hash 漂移均非零退出,旧 ledger 不得复用。
|
|
216
|
+
- ignored test 在 checkpoint 前由 manifest 加入内容集;经 guard force-stage/commit 后由 Git diff 加入同一路径,集合去重,因此 hash 保持不变。
|
|
223
217
|
- diffHash 只用于"内容是否变化"判断,不等于 commit hash
|
|
224
218
|
- 账本中 `currentHead`(验证时 HEAD)与 `diffHash` 分开记录;reuse 规则 #2 允许 HEAD 前移(如 harness-run Step 5 checkpoint commit),因 diffHash 已 commit-invariant,HEAD 前移不改变指纹
|
|
225
219
|
- **禁止**使用类似 `3files-84plus-5minus` 的描述性字符串作为复用依据
|
|
226
|
-
-
|
|
227
|
-
- **禁止**任何单部分简化:含 `git diff <base> HEAD`(仅已提交部分)、`git diff`(仅未提交)、`node -e`/`crypto.createHash` 自算 SHA-256。无论 commit 前后、无论工作树是否 clean,必须用上述三部分合并命令。commit 后 clean 致单部分偶然等价不得作为省略依据——时序或工作树状态变化即复现复用链断裂
|
|
228
|
-
- **commit 前时序说明**:harness-run 步骤 2c 在 Step 5 checkpoint commit **之前**写 ledger,此时 `git diff baseCommit..HEAD` 部分为空(HEAD==baseCommit),只有"未提交 + 未跟踪"是全量。**即使第一部分为空也必须保留三部分合并命令**——commit 后第一部分被填充、未提交/未跟踪变空,两者内容相同 → diffHash 一致。省略第一部分只用未提交 diff,commit 后未提交变空 → diffHash 变化 → 复用链断裂(真实教训:run 产出非规范 `8a94c874`,test 重算 `b4c580fc` 不一致被迫重跑全量单元测试)。harness-run 步骤 2c 已 inline 完整三部分命令,执行者直接跑 verbatim,不得自行省略
|
|
220
|
+
- **禁止**删除/忽略 test-tracking manifest 后继续复用,也禁止用手写 Git/Node/PowerShell hash 替代 canonical 命令。
|
|
229
221
|
|
|
230
222
|
## 六、Post-test 变更分类
|
|
231
223
|
|
|
@@ -40,7 +40,9 @@ BROAD_SCOPES = frozenset({"module", "module-am", "full"})
|
|
|
40
40
|
|
|
41
41
|
# --- Ledger v2 (cluster 2) ---
|
|
42
42
|
LEDGER_VERSION = "harness-ledger-2"
|
|
43
|
-
DIFF_HASH_VERSION = "content-changeset-
|
|
43
|
+
DIFF_HASH_VERSION = "content-changeset-2"
|
|
44
|
+
TEST_TRACKING_REL = Path("evidence") / "test-tracking.json"
|
|
45
|
+
TEST_TRACKING_REASONS = frozenset({"tdd-created", "stale-test-repair", "test-updated"})
|
|
44
46
|
# Coverage lattice: a recorded verification's coverage rank must meet the
|
|
45
47
|
# verification's required rank. Prevents incremental evidence from satisfying
|
|
46
48
|
# a module/full gate (UT-015 / API-005).
|
|
@@ -168,7 +170,88 @@ def _changed_paths(repo_root: Path, base: str | None) -> tuple[str, list[str]]:
|
|
|
168
170
|
return base, sorted(paths)
|
|
169
171
|
|
|
170
172
|
|
|
171
|
-
def
|
|
173
|
+
def _inside(path: Path, root: Path) -> bool:
|
|
174
|
+
try:
|
|
175
|
+
path.relative_to(root)
|
|
176
|
+
return True
|
|
177
|
+
except ValueError:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _tracked_test_contents(
|
|
182
|
+
repo_root: Path,
|
|
183
|
+
change_dir: Path | str | None,
|
|
184
|
+
) -> tuple[dict[str, bytes], str | None]:
|
|
185
|
+
"""Load exact test paths recorded by harness_test_guard.
|
|
186
|
+
|
|
187
|
+
The ledger validates the same security-critical manifest fields again so a
|
|
188
|
+
hand-edited manifest cannot silently widen the fingerprint or reuse stale
|
|
189
|
+
evidence. Missing manifests remain backward-compatible and contribute no
|
|
190
|
+
additional paths.
|
|
191
|
+
"""
|
|
192
|
+
if change_dir is None:
|
|
193
|
+
return {}, None
|
|
194
|
+
candidate = Path(change_dir)
|
|
195
|
+
if not candidate.is_absolute():
|
|
196
|
+
candidate = repo_root / candidate
|
|
197
|
+
change_root = candidate.resolve()
|
|
198
|
+
if not _inside(change_root, repo_root):
|
|
199
|
+
raise ValueError("TEST_TRACKING_CHANGE_DIR_OUTSIDE_PROJECT")
|
|
200
|
+
manifest_path = (change_root / TEST_TRACKING_REL).resolve()
|
|
201
|
+
if not _inside(manifest_path, change_root):
|
|
202
|
+
raise ValueError("TEST_TRACKING_MANIFEST_OUTSIDE_CHANGE")
|
|
203
|
+
if not manifest_path.is_file():
|
|
204
|
+
return {}, None
|
|
205
|
+
try:
|
|
206
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
|
207
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
208
|
+
raise ValueError(f"TEST_TRACKING_MANIFEST_INVALID: {exc}") from exc
|
|
209
|
+
if (
|
|
210
|
+
not isinstance(manifest, dict)
|
|
211
|
+
or manifest.get("schemaVersion") != 1
|
|
212
|
+
or manifest.get("mode") != "force-track-touched"
|
|
213
|
+
or manifest.get("projectRoot") != str(repo_root)
|
|
214
|
+
):
|
|
215
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
216
|
+
entries = manifest.get("files")
|
|
217
|
+
if not isinstance(entries, list) or not entries:
|
|
218
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID: EMPTY_FILES")
|
|
219
|
+
|
|
220
|
+
contents: dict[str, bytes] = {}
|
|
221
|
+
for item in entries:
|
|
222
|
+
if not isinstance(item, dict):
|
|
223
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
224
|
+
rel = item.get("path")
|
|
225
|
+
expected_hash = item.get("sha256")
|
|
226
|
+
if (
|
|
227
|
+
not isinstance(rel, str)
|
|
228
|
+
or not rel
|
|
229
|
+
or not isinstance(expected_hash, str)
|
|
230
|
+
or item.get("reason") not in TEST_TRACKING_REASONS
|
|
231
|
+
or type(item.get("ignored")) is not bool
|
|
232
|
+
or type(item.get("trackedBefore")) is not bool
|
|
233
|
+
):
|
|
234
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
235
|
+
raw_path = Path(rel)
|
|
236
|
+
resolved = (repo_root / raw_path).resolve()
|
|
237
|
+
if raw_path.is_absolute() or not _inside(resolved, repo_root):
|
|
238
|
+
raise ValueError(f"TEST_TRACKING_PATH_OUTSIDE_PROJECT: {rel}")
|
|
239
|
+
normalized = resolved.relative_to(repo_root).as_posix()
|
|
240
|
+
if normalized != rel or not resolved.is_file():
|
|
241
|
+
raise ValueError(f"TEST_TRACKING_FILE_INVALID: {rel}")
|
|
242
|
+
content = resolved.read_bytes()
|
|
243
|
+
actual_hash = "sha256:" + hashlib.sha256(content).hexdigest()
|
|
244
|
+
if actual_hash != expected_hash:
|
|
245
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}")
|
|
246
|
+
contents[rel] = content
|
|
247
|
+
return {rel: contents[rel] for rel in sorted(contents)}, str(manifest_path)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def compute_diff_hash(
|
|
251
|
+
repo_root: Path,
|
|
252
|
+
base: str | None = None,
|
|
253
|
+
change_dir: Path | str | None = None,
|
|
254
|
+
) -> tuple[str, dict[str, Any]]:
|
|
172
255
|
"""Byte-level, commit-invariant diff hash (cluster 2).
|
|
173
256
|
|
|
174
257
|
Content-based change set: every file whose working-tree content differs
|
|
@@ -179,6 +262,8 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
179
262
|
"""
|
|
180
263
|
repo_root = Path(repo_root).resolve()
|
|
181
264
|
base, paths = _changed_paths(repo_root, base)
|
|
265
|
+
tracked_test_contents, manifest_path = _tracked_test_contents(repo_root, change_dir)
|
|
266
|
+
paths = sorted(set(paths).union(tracked_test_contents))
|
|
182
267
|
digest = hashlib.sha256()
|
|
183
268
|
digest.update(DIFF_HASH_VERSION.encode("utf-8"))
|
|
184
269
|
digest.update(b"\x00")
|
|
@@ -187,7 +272,12 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
187
272
|
digest.update(len(path_bytes).to_bytes(4, "big"))
|
|
188
273
|
digest.update(path_bytes)
|
|
189
274
|
abs_path = repo_root / rel
|
|
190
|
-
if
|
|
275
|
+
if rel in tracked_test_contents:
|
|
276
|
+
content = tracked_test_contents[rel]
|
|
277
|
+
digest.update(b"\x01")
|
|
278
|
+
digest.update(len(content).to_bytes(8, "big"))
|
|
279
|
+
digest.update(content)
|
|
280
|
+
elif abs_path.is_file():
|
|
191
281
|
content = abs_path.read_bytes()
|
|
192
282
|
digest.update(b"\x01")
|
|
193
283
|
digest.update(len(content).to_bytes(8, "big"))
|
|
@@ -196,6 +286,14 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
196
286
|
# Deleted since base: record absence with no content.
|
|
197
287
|
digest.update(b"\x00")
|
|
198
288
|
digest.update((0).to_bytes(8, "big"))
|
|
289
|
+
for rel, verified_content in tracked_test_contents.items():
|
|
290
|
+
path = repo_root / rel
|
|
291
|
+
try:
|
|
292
|
+
current_content = path.read_bytes()
|
|
293
|
+
except OSError as exc:
|
|
294
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}") from exc
|
|
295
|
+
if current_content != verified_content:
|
|
296
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}")
|
|
199
297
|
try:
|
|
200
298
|
head = _git_bytes(["rev-parse", "HEAD"], repo_root).decode("utf-8", "replace").strip()
|
|
201
299
|
except RuntimeError:
|
|
@@ -205,6 +303,8 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
205
303
|
"fileCount": len(paths),
|
|
206
304
|
"base": base,
|
|
207
305
|
"head": head or None,
|
|
306
|
+
"trackedTestFileCount": len(tracked_test_contents),
|
|
307
|
+
"testTrackingManifest": manifest_path,
|
|
208
308
|
}
|
|
209
309
|
return f"sha256:{digest.hexdigest()}", meta
|
|
210
310
|
|
|
@@ -778,9 +878,10 @@ def cmd_diff_hash(args: argparse.Namespace) -> int:
|
|
|
778
878
|
repo_raw = getattr(args, "repo", None)
|
|
779
879
|
repo = Path(repo_raw).expanduser().resolve() if repo_raw else Path.cwd().resolve()
|
|
780
880
|
base = getattr(args, "base", None)
|
|
881
|
+
change_dir = getattr(args, "change_dir", None)
|
|
781
882
|
try:
|
|
782
|
-
diff_hash, meta = compute_diff_hash(repo, base=base)
|
|
783
|
-
except (OSError, RuntimeError) as exc:
|
|
883
|
+
diff_hash, meta = compute_diff_hash(repo, base=base, change_dir=change_dir)
|
|
884
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
784
885
|
return emit_error(str(exc), as_json=as_json)
|
|
785
886
|
payload = {"ok": True, "action": "diff-hash", "diffHash": diff_hash}
|
|
786
887
|
payload.update(meta)
|
|
@@ -922,6 +1023,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
922
1023
|
)
|
|
923
1024
|
p_diff.add_argument("--repo", default=None, help="repo root (default: cwd)")
|
|
924
1025
|
p_diff.add_argument("--base", default=None, help="base commit (default: root commit)")
|
|
1026
|
+
p_diff.add_argument(
|
|
1027
|
+
"--change-dir",
|
|
1028
|
+
default=None,
|
|
1029
|
+
help="change directory whose evidence/test-tracking.json contributes ignored tests",
|
|
1030
|
+
)
|
|
925
1031
|
p_diff.set_defaults(func=cmd_diff_hash)
|
|
926
1032
|
|
|
927
1033
|
return parser
|
|
@@ -324,6 +324,13 @@ def empty_profile_skeleton(excluded: tuple[str, ...] | list[str]) -> dict[str, A
|
|
|
324
324
|
"knownPreexistingErrors": [],
|
|
325
325
|
"shellQuirks": [],
|
|
326
326
|
"fingerprint": {"mvnVersion": "", "nodeVersion": "", "pomHash": ""},
|
|
327
|
+
"testTracking": {
|
|
328
|
+
"source": "detected",
|
|
329
|
+
"mode": "force-track-touched",
|
|
330
|
+
"paths": [],
|
|
331
|
+
"staleTestPolicy": "safe-repair",
|
|
332
|
+
"forbidTemporaryExclusion": True,
|
|
333
|
+
},
|
|
327
334
|
}
|
|
328
335
|
|
|
329
336
|
|
|
@@ -357,6 +364,9 @@ def _merge_user_overrides(
|
|
|
357
364
|
profile["knownPreexistingErrors"] = list(existing["knownPreexistingErrors"])
|
|
358
365
|
if isinstance(existing.get("shellQuirks"), list):
|
|
359
366
|
profile["shellQuirks"] = list(existing["shellQuirks"])
|
|
367
|
+
test_tracking = existing.get("testTracking")
|
|
368
|
+
if isinstance(test_tracking, dict) and test_tracking.get("source") == "user":
|
|
369
|
+
profile["testTracking"] = dict(test_tracking)
|
|
360
370
|
# 兼容字段:用户在 v1 配置的 verificationInputs 不保留(无 provenance),
|
|
361
371
|
# 由 commands 重新派生。knownPreexistingErrors/shellQuirks 是人工标注,保留。
|
|
362
372
|
|
|
@@ -392,10 +402,28 @@ def detect(project: Path) -> dict[str, Any]:
|
|
|
392
402
|
reactor_modules = find_reactor_modules(project, excluded)
|
|
393
403
|
if reactor_modules:
|
|
394
404
|
profile["commands"] = _java_commands(reactor_modules, pom_hash)
|
|
405
|
+
profile["testTracking"]["paths"] = sorted(
|
|
406
|
+
"src/test/**" if module == "." else f"{module}/src/test/**"
|
|
407
|
+
for module in reactor_modules
|
|
408
|
+
)
|
|
395
409
|
elif project_type == "node":
|
|
396
410
|
node_cmds = _node_commands(project)
|
|
397
411
|
if node_cmds:
|
|
398
412
|
profile["commands"] = node_cmds
|
|
413
|
+
profile["testTracking"]["paths"] = [
|
|
414
|
+
pattern
|
|
415
|
+
for extension in ("js", "jsx", "ts", "tsx", "mjs", "cjs")
|
|
416
|
+
for pattern in (
|
|
417
|
+
f"apps/*/test/**/*.{extension}",
|
|
418
|
+
f"apps/*/tests/**/*.{extension}",
|
|
419
|
+
f"packages/*/test/**/*.{extension}",
|
|
420
|
+
f"packages/*/tests/**/*.{extension}",
|
|
421
|
+
f"test/**/*.{extension}",
|
|
422
|
+
f"tests/**/*.{extension}",
|
|
423
|
+
f"**/*.test.{extension}",
|
|
424
|
+
f"**/*.spec.{extension}",
|
|
425
|
+
)
|
|
426
|
+
]
|
|
399
427
|
|
|
400
428
|
_merge_user_overrides(profile, existing)
|
|
401
429
|
_derive_verification_inputs(profile)
|
|
@@ -719,7 +747,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
719
747
|
if args.command == "check":
|
|
720
748
|
result = check(args.project)
|
|
721
749
|
sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
|
722
|
-
return 0 if not result.get("stale") else
|
|
750
|
+
return 0 if result.get("ok") and not result.get("stale") else 1
|
|
723
751
|
if args.command == "validate":
|
|
724
752
|
profile = load_profile(args.project.resolve())
|
|
725
753
|
if profile is None:
|