@hunter-harness/workflow-harness 0.2.17 → 0.2.19
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 +7 -1
- package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -0
- 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-plan/reference.md +5 -5
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +11 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +24 -8
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +199 -30
- package/harness/bundles/general/claude-code/scripts/harness_events.py +5 -18
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +153 -0
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +15 -2
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +170 -1
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/claude-code/scripts/harness_service.py +1 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +7 -1
- package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -0
- 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-plan/reference.md +5 -5
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +11 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +24 -8
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +199 -30
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +5 -18
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +153 -0
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +15 -2
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +170 -1
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +1 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +7 -1
- package/harness/bundles/general/codex/harness-archive/checklist.md +6 -0
- 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-plan/reference.md +5 -5
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/reference.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +11 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +24 -8
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +199 -30
- package/harness/bundles/general/codex/scripts/harness_events.py +5 -18
- package/harness/bundles/general/codex/scripts/harness_integration.py +153 -0
- package/harness/bundles/general/codex/scripts/harness_ledger.py +15 -2
- package/harness/bundles/general/codex/scripts/harness_paths.py +170 -1
- package/harness/bundles/general/codex/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/codex/scripts/harness_service.py +1 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +7 -1
- package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -0
- 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-plan/reference.md +5 -5
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +11 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +24 -8
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +199 -30
- package/harness/bundles/general/cursor/scripts/harness_events.py +5 -18
- package/harness/bundles/general/cursor/scripts/harness_integration.py +153 -0
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +15 -2
- package/harness/bundles/general/cursor/scripts/harness_paths.py +170 -1
- package/harness/bundles/general/cursor/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/cursor/scripts/harness_service.py +1 -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 +7 -1
- package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -0
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/reference.md +5 -5
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/checklist.md +2 -2
- package/harness/bundles/java/claude-code/harness-run/reference.md +352 -352
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +11 -2
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +24 -8
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +199 -30
- package/harness/bundles/java/claude-code/scripts/harness_events.py +5 -18
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +153 -0
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +15 -2
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +170 -1
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/claude-code/scripts/harness_service.py +1 -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 +7 -1
- package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -0
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +5 -5
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +2 -2
- package/harness/bundles/java/codebuddy/harness-run/reference.md +352 -352
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +11 -2
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +24 -8
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +199 -30
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +5 -18
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +153 -0
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +15 -2
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +170 -1
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +1 -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 +7 -1
- package/harness/bundles/java/codex/harness-archive/checklist.md +6 -0
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/reference.md +5 -5
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/checklist.md +2 -2
- package/harness/bundles/java/codex/harness-run/reference.md +352 -352
- package/harness/bundles/java/codex/harness-submit/SKILL.md +11 -2
- package/harness/bundles/java/codex/harness-submit/checklist.md +24 -8
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +199 -30
- package/harness/bundles/java/codex/scripts/harness_events.py +5 -18
- package/harness/bundles/java/codex/scripts/harness_integration.py +153 -0
- package/harness/bundles/java/codex/scripts/harness_ledger.py +15 -2
- package/harness/bundles/java/codex/scripts/harness_paths.py +170 -1
- package/harness/bundles/java/codex/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/codex/scripts/harness_service.py +1 -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 +7 -1
- package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -0
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/reference.md +5 -5
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/checklist.md +2 -2
- package/harness/bundles/java/cursor/harness-run/reference.md +352 -352
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +11 -2
- package/harness/bundles/java/cursor/harness-submit/checklist.md +24 -8
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +2 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +199 -30
- package/harness/bundles/java/cursor/scripts/harness_events.py +5 -18
- package/harness/bundles/java/cursor/scripts/harness_integration.py +153 -0
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +15 -2
- package/harness/bundles/java/cursor/scripts/harness_paths.py +170 -1
- package/harness/bundles/java/cursor/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/cursor/scripts/harness_service.py +1 -0
- package/harness/manifests/general/claude-code.json +26 -26
- package/harness/manifests/general/codebuddy.json +26 -26
- package/harness/manifests/general/codex.json +26 -26
- package/harness/manifests/general/cursor.json +26 -26
- package/harness/manifests/java/claude-code.json +29 -29
- package/harness/manifests/java/codebuddy.json +29 -29
- package/harness/manifests/java/codex.json +29 -29
- package/harness/manifests/java/cursor.json +29 -29
- package/hunter-workflow-family.json +1 -1
- package/package.json +1 -1
|
@@ -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=20fd2cc48741f11b; overlay=java; agent=codex; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -79,12 +79,17 @@ M3. **merge** — `--no-ff` 合并 feature 分支;merge diff 出现其他 Chan
|
|
|
79
79
|
M4. **verify** — 在 integration worktree 内执行组合态验证;他人提交引入或 ledger 不可复用时必跑
|
|
80
80
|
M5. **push** — 仅在验证身份与远端基线仍匹配时 push;远端漂移 → `TARGET_MOVED` 结构化失败,不继续
|
|
81
81
|
M6. **cleanup** — `git worktree remove --force` 精确路径 + 临时分支 + (push 成功后)保护 ref;释放 integration lock;失败保留 journal 与诊断证据;更新 `worktree.json`(`created=false` + removedAt)
|
|
82
|
+
M6.5. **formal snapshot** — mergeFinalHash 写入后调用 `harness_paths.snapshot_change_formal_layer(projectRoot, changeId)`,备份到主仓 `.harness/cache/change-snapshots/<change>/`(不得写在 feature worktree 内)
|
|
82
83
|
M7. **ledger + 收尾** — 经 `harness_ledger.py record` 写入 `mergeFinalHash`(= journal `pushedHead`);**`harness_gate.py close --phase merge`**(禁止手工 phase.end);提示 `/harness-archive`
|
|
83
84
|
|
|
84
|
-
> Ledger v3(v2 契约 / split-v1 布局起):`record` 强制顶层身份(`schemaVersion=3/repositoryId/baseCommit/currentHead/diffHash/ownershipHash
|
|
85
|
+
> Ledger v3(v2 契约 / split-v1 布局起):`record` 强制顶层身份(`schemaVersion=3/repositoryId/baseCommit/currentHead/diffHash/ownershipHash`,缺失非零退出、`LEDGER_IDENTITY_INVALID`,不写账本)与 typed metrics;legacy 契约行为不变。详见 `../protocols/ledger-protocol.md` 第十节。
|
|
85
86
|
|
|
86
87
|
正常路径**禁止创建、应用或删除仓库级 stash**。中断恢复:`harness_integration.py status` 读 journal,`recover` 从首个未完成步骤续跑;protection refs 只在 push 成功后的 cleanup 删除。
|
|
87
88
|
|
|
89
|
+
**失败 txn 回收(abandon)**:verify/push 未成功且 remote 不含 mergeCommit 时,运行 `harness_integration.py abandon --change … --run-id …`:清理 integrationRoot / temp branch / protection refs / lock;**绝不删除 feature worktree**。push 已成功或 remote 已含 merge → `ABANDON_REFUSED`,改走 cleanup。
|
|
90
|
+
|
|
91
|
+
**清 feature worktree 迁根硬门槛(H-1/H-2)**:删除 feature worktree 前,若 Agent root / cwd 落在待删路径内,必须先 `move_agent_to_root(projectRoot)`(目标为主仓已存在的 `main`/`master`;**禁止**为迁根去 fetch 已删除的 feature 分支)。迁根失败 → **拒绝删除**。清理前调用 `harness_paths.assert_cleanup_safe(cleanupRoot, stateRoots, archiveRoots)`;state/archive(含 junction/symlink 解析后)落在 cleanup 内 → `CLEANUP_TOPOLOGY_REFUSED`。
|
|
92
|
+
|
|
88
93
|
### Java 提交前验证补充
|
|
89
94
|
|
|
90
95
|
通用 `harness-submit` 的最终门禁已统一为 `unitTestFull`(见通用 SKILL.md 步骤 2 / checklist 步骤 2)。Java overlay **不重复定义门禁规则**,只补两点:
|
|
@@ -123,6 +128,10 @@ test-tracking manifest 是 ignored test 的唯一强制暂存授权:只允许
|
|
|
123
128
|
- **`git merge --no-ff` 固定**;禁止 fast-forward
|
|
124
129
|
- **冲突不自动解** → 停下 → 用户手动解 → 确认后继续
|
|
125
130
|
- **`mergeFinalHash`** = 主分支 push 后 HEAD;archive 优先读此字段
|
|
131
|
+
- **迁根后再删 feature WT**:Agent 仍在待删 root 内则拒绝删除;目标分支已删时切主仓 `main`/`master`,禁止强行 fetch
|
|
132
|
+
- **snapshot 后再清**:正式层已写入 `.harness/cache/change-snapshots/<change>/`
|
|
133
|
+
- **拓扑安全**:`assert_cleanup_safe` 拒绝 state/archive ⊆ cleanup(含 junction)
|
|
134
|
+
- **abandon ≠ cleanup**:仅失败 txn;永不删 feature worktree
|
|
126
135
|
|
|
127
136
|
### 五、Shell 安全 / 敏感信息 / 证据化
|
|
128
137
|
|
|
@@ -328,7 +328,7 @@ append `phase.start`(`--phase merge`;`note` 含 worktree 分支、主分支
|
|
|
328
328
|
### 步骤 M1:preflight
|
|
329
329
|
|
|
330
330
|
```powershell
|
|
331
|
-
python harness/scripts/harness_integration.py preflight --change <change-name> --run-id <run-id> --feature-branch
|
|
331
|
+
python harness/scripts/harness_integration.py preflight --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
获取 integration lock、解析 base/feature head、写 journal 与保护 refs(`refs/harness/integration/<txn>/base|head`)。锁被其他 run 持有 → **停止**,不改动工作区与状态文件。
|
|
@@ -336,7 +336,7 @@ python harness/scripts/harness_integration.py preflight --change <change-name> -
|
|
|
336
336
|
### 步骤 M2:prepare
|
|
337
337
|
|
|
338
338
|
```powershell
|
|
339
|
-
python harness/scripts/harness_integration.py prepare --change <change-name> --run-id <run-id> --feature-branch
|
|
339
|
+
python harness/scripts/harness_integration.py prepare --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
340
340
|
```
|
|
341
341
|
|
|
342
342
|
fetch 后从**已提交** target 创建临时 integration worktree;primary 的 staged/unstaged/untracked/ignored 与 mtime 均不被触碰。
|
|
@@ -344,7 +344,7 @@ fetch 后从**已提交** target 创建临时 integration worktree;primary 的
|
|
|
344
344
|
### 步骤 M3:merge
|
|
345
345
|
|
|
346
346
|
```powershell
|
|
347
|
-
python harness/scripts/harness_integration.py merge --change <change-name> --run-id <run-id> --feature-branch
|
|
347
|
+
python harness/scripts/harness_integration.py merge --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
348
348
|
```
|
|
349
349
|
|
|
350
350
|
- 方式固定 `--no-ff`;merge diff 出现其他 Change 的 contract/runtime 路径 → 结构化 `FOREIGN_CHANGE_PATHS` 拒绝
|
|
@@ -356,7 +356,7 @@ python harness/scripts/harness_integration.py merge --change <change-name> --run
|
|
|
356
356
|
在 integration worktree 内执行验证命令;远端引入他人提交或 ledger 不可复用时必跑:
|
|
357
357
|
|
|
358
358
|
```powershell
|
|
359
|
-
python harness/scripts/harness_integration.py verify --change <change-name> --run-id <run-id> --feature-branch
|
|
359
|
+
python harness/scripts/harness_integration.py verify --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp> --command "<resolved command>"
|
|
360
360
|
```
|
|
361
361
|
|
|
362
362
|
任一命令非零退出 → step FAILED,不进入 push。
|
|
@@ -370,7 +370,7 @@ powershell.exe -Command "python harness/scripts/harness_check_gate.py --write"
|
|
|
370
370
|
### 步骤 M5:push
|
|
371
371
|
|
|
372
372
|
```powershell
|
|
373
|
-
python harness/scripts/harness_integration.py push --change <change-name> --run-id <run-id> --feature-branch
|
|
373
|
+
python harness/scripts/harness_integration.py push --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
374
374
|
```
|
|
375
375
|
|
|
376
376
|
远端基线漂移 → `TARGET_MOVED` 结构化失败,不继续。成功后 journal `pushedHead` 即 `mergeFinalHash`。
|
|
@@ -378,7 +378,7 @@ python harness/scripts/harness_integration.py push --change <change-name> --run-
|
|
|
378
378
|
### 步骤 M6:cleanup
|
|
379
379
|
|
|
380
380
|
```powershell
|
|
381
|
-
python harness/scripts/harness_integration.py cleanup --change <change-name> --run-id <run-id> --feature-branch
|
|
381
|
+
python harness/scripts/harness_integration.py cleanup --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
382
382
|
```
|
|
383
383
|
|
|
384
384
|
- `git worktree remove --force` 精确路径 → 验证注销 → 临时分支 `branch -D` → (push 成功后)删除保护 refs → 释放 integration lock
|
|
@@ -386,11 +386,26 @@ python harness/scripts/harness_integration.py cleanup --change <change-name> --r
|
|
|
386
386
|
- 失败保留 journal 与诊断证据,状态 🟡WARN,不得宣称清理完成
|
|
387
387
|
- 若存在 test-tracking manifest,确认其路径已由合并后的目标分支跟踪(`git ls-files --error-unmatch -- <exact-path>`),缺失即停止
|
|
388
388
|
|
|
389
|
+
**失败 txn 改走 abandon(勿强行 cleanup)**:
|
|
390
|
+
|
|
391
|
+
```powershell
|
|
392
|
+
python harness/scripts/harness_integration.py abandon --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
仅当 push ≠ DONE 且 remote 不含 mergeCommit;清理 integration 产物后可换新 run-id 重开。push 成功 → `ABANDON_REFUSED`。
|
|
396
|
+
|
|
389
397
|
更新 `meta/worktree.json`:`created=false` + `removedAt`/`removedBy`/`removalNote`。
|
|
390
398
|
|
|
399
|
+
**删除 feature worktree 前(硬门槛)**:
|
|
400
|
+
|
|
401
|
+
1. 若 Agent root / cwd 落在待删 worktree → 先 `move_agent_to_root(<projectRoot>)`;目标为主仓,feature 分支已删则切 `main`/`master`,禁止 fetch 已不存在分支
|
|
402
|
+
2. 迁根失败 → **停止删除**
|
|
403
|
+
3. `assert_cleanup_safe(cleanupRoot, [.harness/changes/<id>, .harness/state/...], [.harness/archive])`;`CLEANUP_TOPOLOGY_REFUSED` → 停止
|
|
404
|
+
4. mergeFinalHash 已写后先 `snapshot_change_formal_layer`,再清 feature WT
|
|
405
|
+
|
|
391
406
|
### 步骤 M7:更新 ledger + 收尾
|
|
392
407
|
|
|
393
|
-
ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_gate.py close --phase merge` 关闭(禁止手工 phase.end
|
|
408
|
+
ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_gate.py close --phase merge` 关闭(禁止手工 phase.end)。正式层快照确认存在于 `.harness/cache/change-snapshots/<change-name>/`。
|
|
394
409
|
|
|
395
410
|
```markdown
|
|
396
411
|
## 合并完成 — <change-name>
|
|
@@ -399,6 +414,7 @@ ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_ga
|
|
|
399
414
|
- 主分支 push: ✅(<old>..<new> → origin/<主分支>)
|
|
400
415
|
- mergeFinalHash: `<merge-final-hash>`
|
|
401
416
|
- 验证策略: 🔁REUSED / 🔄已重跑
|
|
402
|
-
-
|
|
417
|
+
- snapshot: `.harness/cache/change-snapshots/<change-name>/`
|
|
418
|
+
- worktree: 已清理(迁根后)
|
|
403
419
|
- 下一步: `/harness-archive`
|
|
404
420
|
```
|
|
@@ -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=20fd2cc48741f11b; overlay=java; 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=20fd2cc48741f11b; overlay=java; agent=codex; do not edit -->
|
|
6
6
|
# harness-test — 测试执行
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -43,6 +43,8 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
43
43
|
|
|
44
44
|
`append` 写入契约(Task 4 §6.1):普通 append = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex`);仅 `--type phase.end` append 成功后渲染一次 `logs/execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。人类可读摘要(触发指令、降级原因、阶段结论)写入事件的 `note` 字段;阶段开始/结束、命令、验证、artifact、问题、决策各写对应 `type` 事件。旧 archive 缺少 events 可回放兼容;新变更不得以 execution-log 已存在为理由跳过 events。
|
|
45
45
|
|
|
46
|
+
**artifact 硬门禁(H-8)**:`--type artifact` **必须**带非空 `--path`(change 相对路径)。知识探索/预览类说明改用 `--type issue` 或 `--type decision`,禁止再用 `--kind informational` 伪装无 path 的 artifact。
|
|
47
|
+
|
|
46
48
|
基础事件结构(schema_version 3;兼容读取 v1/v2):
|
|
47
49
|
|
|
48
50
|
```json
|
|
@@ -520,6 +520,41 @@ def check_status(
|
|
|
520
520
|
}
|
|
521
521
|
)
|
|
522
522
|
|
|
523
|
+
# --- H-4 formal-layer minimum set ---
|
|
524
|
+
plans_dir = change_dir / "plans"
|
|
525
|
+
plan_files = (
|
|
526
|
+
sorted(plans_dir.glob("*-plan.md")) if plans_dir.is_dir() else []
|
|
527
|
+
)
|
|
528
|
+
checks["plan_files"] = [str(p.relative_to(change_dir)) for p in plan_files]
|
|
529
|
+
if not plan_files:
|
|
530
|
+
blockers.append(
|
|
531
|
+
{
|
|
532
|
+
"code": "missing-plan",
|
|
533
|
+
"message": "plans/*-plan.md is required before archive",
|
|
534
|
+
}
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
events_path = change_dir / "events.ndjson"
|
|
538
|
+
events_present = events_path.is_file() and events_path.stat().st_size > 0
|
|
539
|
+
checks["events_ndjson"] = events_present
|
|
540
|
+
if not events_present:
|
|
541
|
+
blockers.append(
|
|
542
|
+
{
|
|
543
|
+
"code": "missing-events",
|
|
544
|
+
"message": "events.ndjson is required and must be non-empty",
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
ledger = load_ledger(change_dir)
|
|
549
|
+
checks["verification_ledger"] = ledger is not None
|
|
550
|
+
if ledger is None:
|
|
551
|
+
blockers.append(
|
|
552
|
+
{
|
|
553
|
+
"code": "missing-verification-ledger",
|
|
554
|
+
"message": "evidence/verification-ledger.json is required before archive",
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
|
|
523
558
|
# --- commit pushed ---
|
|
524
559
|
code, out, err = git_run(project, "rev-parse", "--is-inside-work-tree")
|
|
525
560
|
if code != 0:
|
|
@@ -559,7 +594,8 @@ def check_status(
|
|
|
559
594
|
|
|
560
595
|
expected_hash: str | None = None
|
|
561
596
|
hash_source: str | None = None
|
|
562
|
-
ledger
|
|
597
|
+
if ledger is None:
|
|
598
|
+
ledger = load_ledger(change_dir)
|
|
563
599
|
if worktree_requested(change_dir) and ledger:
|
|
564
600
|
merge_hash = ledger.get("mergeFinalHash") or (
|
|
565
601
|
ledger.get("merge") or {}
|
|
@@ -625,13 +661,25 @@ def check_status(
|
|
|
625
661
|
# --- test / review reports ---
|
|
626
662
|
test_reports = find_test_reports(change_dir)
|
|
627
663
|
review_reports = find_review_reports(change_dir)
|
|
628
|
-
events_path = change_dir / "events.ndjson"
|
|
629
664
|
events = he.load_events(events_path) if events_path.is_file() else []
|
|
630
665
|
review_ran = review_evidence_present(change_dir, events)
|
|
631
666
|
|
|
632
667
|
checks["test_reports"] = [str(p.relative_to(change_dir)) for p in test_reports]
|
|
633
668
|
checks["review_reports"] = [str(p.relative_to(change_dir)) for p in review_reports]
|
|
634
669
|
|
|
670
|
+
# H-4 min blockers are plan/events/ledger only. Missing test/review evidence
|
|
671
|
+
# remains a warning so archive-before-review stays possible (advisory).
|
|
672
|
+
if not test_reports and not review_reports and not review_ran:
|
|
673
|
+
warnings.append(
|
|
674
|
+
{
|
|
675
|
+
"code": "missing-test-or-review-report",
|
|
676
|
+
"message": (
|
|
677
|
+
"no test report or review evidence yet; prefer completing "
|
|
678
|
+
"test/review before archive (not a hard blocker)"
|
|
679
|
+
),
|
|
680
|
+
}
|
|
681
|
+
)
|
|
682
|
+
|
|
635
683
|
if test_reports:
|
|
636
684
|
checks["test_report_status"] = "present"
|
|
637
685
|
else:
|
|
@@ -842,15 +890,18 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
842
890
|
elif str(unit.get("reused") or "").lower() in {"true", "1"} or "REUSED" in status:
|
|
843
891
|
source = "committed"
|
|
844
892
|
|
|
845
|
-
if pass_rate is None
|
|
893
|
+
if pass_rate is None:
|
|
846
894
|
passed = (
|
|
847
895
|
passed_count
|
|
848
896
|
if passed_count is not None
|
|
849
|
-
else max(run - failures - errors - skipped, 0)
|
|
897
|
+
else max(int(run or 0) - int(failures or 0) - int(errors or 0) - int(skipped or 0), 0)
|
|
850
898
|
)
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
899
|
+
# H-13: passRate denominator excludes skipped.
|
|
900
|
+
denom = int(passed) + int(failures or 0) + int(errors or 0)
|
|
901
|
+
if denom > 0:
|
|
902
|
+
pass_rate = f"{passed / denom:.0%}"
|
|
903
|
+
else:
|
|
904
|
+
pass_rate = NOT_AVAILABLE
|
|
854
905
|
|
|
855
906
|
result = {
|
|
856
907
|
"run": int(run or 0),
|
|
@@ -1332,7 +1383,19 @@ def _stage_status_from_sources(
|
|
|
1332
1383
|
elif raw in {"SKIP", "SKIPPED"}:
|
|
1333
1384
|
raw = "USER_SKIPPED"
|
|
1334
1385
|
status[phase] = raw
|
|
1335
|
-
# Issues in events can downgrade
|
|
1386
|
+
# Issues in events can downgrade. H-14: informational / hygiene / agent-preflight
|
|
1387
|
+
# notes must not downgrade an already-OK phase.end.
|
|
1388
|
+
_informational_markers = (
|
|
1389
|
+
"[archive-hygiene]",
|
|
1390
|
+
"informational",
|
|
1391
|
+
"custom_agents_unsupported",
|
|
1392
|
+
"definition_not_found_host_capable",
|
|
1393
|
+
"知识查询",
|
|
1394
|
+
"knowledge query",
|
|
1395
|
+
"harness-explorer",
|
|
1396
|
+
"harness-evaluator",
|
|
1397
|
+
"委派",
|
|
1398
|
+
)
|
|
1336
1399
|
for e in he.current_issues(events):
|
|
1337
1400
|
sev = str(e.get("severity") or "").lower()
|
|
1338
1401
|
issue_text = " ".join(str(e.get(key) or "") for key in ("message", "note", "code")).lower()
|
|
@@ -1341,6 +1404,10 @@ def _stage_status_from_sources(
|
|
|
1341
1404
|
sev = "error"
|
|
1342
1405
|
elif any(token in issue_text for token in ("warn", "skip", "风险", "警告")):
|
|
1343
1406
|
sev = "warn"
|
|
1407
|
+
if sev in {"warn", "warning"} and any(m in issue_text for m in _informational_markers):
|
|
1408
|
+
continue
|
|
1409
|
+
if sev in {"", "info", "note", "informational"}:
|
|
1410
|
+
continue
|
|
1344
1411
|
phase = str(e.get("phase") or "").lower()
|
|
1345
1412
|
if phase in status and sev in {"error", "fail", "failed", "critical"}:
|
|
1346
1413
|
status[phase] = "FAIL"
|
|
@@ -1651,6 +1718,51 @@ def _review_summary(
|
|
|
1651
1718
|
return base
|
|
1652
1719
|
|
|
1653
1720
|
|
|
1721
|
+
def _resolve_base_commit(
|
|
1722
|
+
ledger: dict[str, Any] | None,
|
|
1723
|
+
change_dir: Path,
|
|
1724
|
+
project: Path,
|
|
1725
|
+
final: str | None,
|
|
1726
|
+
) -> str:
|
|
1727
|
+
"""Resolve baseCommit: ledger → latest phase-context → merge first parent → merge-base."""
|
|
1728
|
+
if ledger:
|
|
1729
|
+
base = str(ledger.get("baseCommit") or "").strip()
|
|
1730
|
+
if base and base != NOT_AVAILABLE:
|
|
1731
|
+
return base
|
|
1732
|
+
|
|
1733
|
+
ctx_dir = change_dir / "runtime" / "phase-context"
|
|
1734
|
+
if ctx_dir.is_dir():
|
|
1735
|
+
candidates = sorted(
|
|
1736
|
+
(p for p in ctx_dir.glob("*.json") if p.is_file()),
|
|
1737
|
+
key=lambda p: p.stat().st_mtime,
|
|
1738
|
+
reverse=True,
|
|
1739
|
+
)
|
|
1740
|
+
for path in candidates:
|
|
1741
|
+
try:
|
|
1742
|
+
payload = read_json(path)
|
|
1743
|
+
except (OSError, json.JSONDecodeError, TypeError):
|
|
1744
|
+
continue
|
|
1745
|
+
if not isinstance(payload, dict):
|
|
1746
|
+
continue
|
|
1747
|
+
base = str(payload.get("baseCommit") or "").strip()
|
|
1748
|
+
if base and base != NOT_AVAILABLE:
|
|
1749
|
+
return base
|
|
1750
|
+
|
|
1751
|
+
final_commit = str(final or "").strip()
|
|
1752
|
+
if final_commit and final_commit != NOT_AVAILABLE:
|
|
1753
|
+
code, parents_line, _ = git_run(
|
|
1754
|
+
project, "rev-list", "--parents", "-n", "1", final_commit
|
|
1755
|
+
)
|
|
1756
|
+
if code == 0 and parents_line:
|
|
1757
|
+
parts = parents_line.split()
|
|
1758
|
+
if len(parts) >= 2:
|
|
1759
|
+
return parts[1]
|
|
1760
|
+
code, merge_base, _ = git_run(project, "merge-base", final_commit, "HEAD")
|
|
1761
|
+
if code == 0 and merge_base and merge_base != final_commit:
|
|
1762
|
+
return merge_base
|
|
1763
|
+
return ""
|
|
1764
|
+
|
|
1765
|
+
|
|
1654
1766
|
def _final_commit_from_sources(
|
|
1655
1767
|
ledger: dict[str, Any] | None,
|
|
1656
1768
|
events: list[dict[str, Any]],
|
|
@@ -1864,12 +1976,21 @@ def collect_summary_data(
|
|
|
1864
1976
|
data["finalCommit"] = final_commit or (NOT_AVAILABLE if for_replay else "")
|
|
1865
1977
|
|
|
1866
1978
|
if not data.get("baseCommit") or str(data.get("baseCommit")).startswith("<"):
|
|
1867
|
-
if
|
|
1868
|
-
data["baseCommit"] = ledger["baseCommit"]
|
|
1869
|
-
elif existing and existing.get("baseCommit"):
|
|
1979
|
+
if for_replay and existing and existing.get("baseCommit"):
|
|
1870
1980
|
data["baseCommit"] = existing["baseCommit"]
|
|
1871
1981
|
else:
|
|
1872
|
-
|
|
1982
|
+
resolved_base = _resolve_base_commit(
|
|
1983
|
+
ledger,
|
|
1984
|
+
change_dir,
|
|
1985
|
+
project,
|
|
1986
|
+
str(data.get("finalCommit") or "") or None,
|
|
1987
|
+
)
|
|
1988
|
+
if resolved_base:
|
|
1989
|
+
data["baseCommit"] = resolved_base
|
|
1990
|
+
elif existing and existing.get("baseCommit"):
|
|
1991
|
+
data["baseCommit"] = existing["baseCommit"]
|
|
1992
|
+
else:
|
|
1993
|
+
data["baseCommit"] = NOT_AVAILABLE if for_replay else ""
|
|
1873
1994
|
|
|
1874
1995
|
if not data.get("finalCommitBranch") or str(data.get("finalCommitBranch")).startswith("<"):
|
|
1875
1996
|
code, branch, _ = git_run(project, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
|
|
@@ -2002,6 +2123,16 @@ def collect_summary_data(
|
|
|
2002
2123
|
}
|
|
2003
2124
|
data["changedFiles"] = []
|
|
2004
2125
|
|
|
2126
|
+
# H-11: surface identity + diff facts for adequacy (top-level + gitFacts).
|
|
2127
|
+
diff_for_facts = data.get("diffStat") if isinstance(data.get("diffStat"), dict) else {}
|
|
2128
|
+
data["gitFacts"] = {
|
|
2129
|
+
"baseCommit": data.get("baseCommit") or "",
|
|
2130
|
+
"finalCommit": data.get("finalCommit") or "",
|
|
2131
|
+
"filesChanged": int(diff_for_facts.get("filesChanged") or 0),
|
|
2132
|
+
"insertions": int(diff_for_facts.get("insertions") or 0),
|
|
2133
|
+
"deletions": int(diff_for_facts.get("deletions") or 0),
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2005
2136
|
data.setdefault(
|
|
2006
2137
|
"ownershipDiff",
|
|
2007
2138
|
{
|
|
@@ -2837,18 +2968,52 @@ def validate_report_adequacy(summary: dict[str, Any]) -> dict[str, Any]:
|
|
|
2837
2968
|
"""
|
|
2838
2969
|
issues: list[dict[str, str]] = []
|
|
2839
2970
|
|
|
2971
|
+
# H-12: prefer top-level baseCommit + diffStat; gitFacts is a mirror.
|
|
2972
|
+
base = str(summary.get("baseCommit") or "").strip()
|
|
2973
|
+
final = str(summary.get("finalCommit") or "").strip()
|
|
2974
|
+
merge_final = str(summary.get("mergeFinalHash") or "").strip()
|
|
2975
|
+
diff_stat = summary.get("diffStat") if isinstance(summary.get("diffStat"), dict) else {}
|
|
2976
|
+
files_changed = int(diff_stat.get("filesChanged") or 0)
|
|
2977
|
+
|
|
2840
2978
|
git_facts = summary.get("gitFacts") or {}
|
|
2841
2979
|
if isinstance(git_facts, dict):
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
if
|
|
2847
|
-
|
|
2848
|
-
"
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2980
|
+
if not base or base == NOT_AVAILABLE:
|
|
2981
|
+
base = str(git_facts.get("baseCommit") or "").strip() or base
|
|
2982
|
+
if not final or final == NOT_AVAILABLE:
|
|
2983
|
+
final = str(git_facts.get("finalCommit") or "").strip() or final
|
|
2984
|
+
if "filesChanged" in git_facts and files_changed == 0:
|
|
2985
|
+
try:
|
|
2986
|
+
files_changed = int(git_facts.get("filesChanged") or 0)
|
|
2987
|
+
except (TypeError, ValueError):
|
|
2988
|
+
files_changed = 0
|
|
2989
|
+
|
|
2990
|
+
base_missing = not base or base == NOT_AVAILABLE
|
|
2991
|
+
final_present = bool(
|
|
2992
|
+
(final and final != NOT_AVAILABLE) or (merge_final and merge_final != NOT_AVAILABLE)
|
|
2993
|
+
)
|
|
2994
|
+
if final_present and base_missing:
|
|
2995
|
+
issues.append({
|
|
2996
|
+
"code": "IDENTITY_BASE_MISSING",
|
|
2997
|
+
"severity": "error",
|
|
2998
|
+
"message": (
|
|
2999
|
+
"final/merge identity present but baseCommit is empty; "
|
|
3000
|
+
"refuse CONDITIONAL_OK+ without a resolved base"
|
|
3001
|
+
),
|
|
3002
|
+
})
|
|
3003
|
+
|
|
3004
|
+
if (
|
|
3005
|
+
base
|
|
3006
|
+
and final
|
|
3007
|
+
and base != NOT_AVAILABLE
|
|
3008
|
+
and final != NOT_AVAILABLE
|
|
3009
|
+
and base != final
|
|
3010
|
+
and files_changed == 0
|
|
3011
|
+
):
|
|
3012
|
+
issues.append({
|
|
3013
|
+
"code": "DIFF_ZERO_WITH_NONEMPTY_COMMIT",
|
|
3014
|
+
"severity": "error",
|
|
3015
|
+
"message": f"final commit {final} differs from base {base} but filesChanged=0",
|
|
3016
|
+
})
|
|
2852
3017
|
|
|
2853
3018
|
# Typed metrics missing despite test report artifacts present.
|
|
2854
3019
|
verification = summary.get("verification") or {}
|
|
@@ -2886,11 +3051,11 @@ def validate_report_adequacy(summary: dict[str, Any]) -> dict[str, Any]:
|
|
|
2886
3051
|
|
|
2887
3052
|
|
|
2888
3053
|
def artifact_preflight(change_dir: Path) -> dict[str, Any]:
|
|
2889
|
-
"""Classify artifact events before destructive finalize (retro §5.31).
|
|
3054
|
+
"""Classify artifact events before destructive finalize (retro §5.31 / H-8).
|
|
2890
3055
|
|
|
2891
|
-
Returns per-artifact classification:
|
|
2892
|
-
canonicalizable (same-change repo-relative path
|
|
2893
|
-
|
|
3056
|
+
Returns per-artifact classification: blocking (missing / escaping /
|
|
3057
|
+
cross-change path), canonicalizable (same-change repo-relative path),
|
|
3058
|
+
or file-backed (change-relative path). Pathless legacy rows fail closed.
|
|
2894
3059
|
"""
|
|
2895
3060
|
change_dir = change_dir.resolve()
|
|
2896
3061
|
events_p = change_dir / "events.ndjson"
|
|
@@ -2910,12 +3075,16 @@ def artifact_preflight(change_dir: Path) -> dict[str, Any]:
|
|
|
2910
3075
|
kind = str(event.get("kind") or "").strip()
|
|
2911
3076
|
event_id = str(event.get("id") or "")
|
|
2912
3077
|
if not path:
|
|
2913
|
-
|
|
3078
|
+
item = {
|
|
2914
3079
|
"eventId": event_id,
|
|
2915
|
-
"category": "
|
|
2916
|
-
"
|
|
3080
|
+
"category": "blocking",
|
|
3081
|
+
"path": "",
|
|
3082
|
+
"kind": kind or "",
|
|
3083
|
+
"reason": "artifact path missing",
|
|
2917
3084
|
"note": str(event.get("note") or "")[:80],
|
|
2918
|
-
}
|
|
3085
|
+
}
|
|
3086
|
+
items.append(item)
|
|
3087
|
+
blocking.append(item)
|
|
2919
3088
|
continue
|
|
2920
3089
|
# Check for escaping/cross-change paths.
|
|
2921
3090
|
parts = path.replace("\\", "/").split("/")
|
|
@@ -679,29 +679,16 @@ def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
|
|
|
679
679
|
"CORRECTION_VALUE_INVALID_JSON",
|
|
680
680
|
f"CORRECTION_VALUE_INVALID_JSON: {exc}",
|
|
681
681
|
)
|
|
682
|
-
# Retro
|
|
683
|
-
#
|
|
684
|
-
# (preview/summary) must not masquerade as a file artifact. Legacy kind
|
|
685
|
-
# values (ledger, report, etc.) are treated as file-backed aliases.
|
|
682
|
+
# Retro 2026-07-21 H-8: artifact events always require a non-empty path.
|
|
683
|
+
# Knowledge/exploration notes must use issue/decision, not pathless artifacts.
|
|
686
684
|
if event_type == "artifact":
|
|
687
|
-
kind = str(getattr(args, "kind", "") or "").strip()
|
|
688
685
|
path = str(getattr(args, "path", "") or "").strip()
|
|
689
|
-
|
|
690
|
-
if kind == "file-backed" and not path:
|
|
691
|
-
return (
|
|
692
|
-
"ARTIFACT_PATH_REQUIRED",
|
|
693
|
-
"ARTIFACT_PATH_REQUIRED: file-backed artifact requires --path",
|
|
694
|
-
)
|
|
695
|
-
# If kind is explicitly informational, path is optional. If kind is
|
|
696
|
-
# absent and path is absent, reject (can't infer file-backed without path).
|
|
697
|
-
if not kind and not path:
|
|
686
|
+
if not path:
|
|
698
687
|
return (
|
|
699
688
|
"ARTIFACT_PATH_REQUIRED",
|
|
700
|
-
"ARTIFACT_PATH_REQUIRED: artifact
|
|
701
|
-
"use
|
|
689
|
+
"ARTIFACT_PATH_REQUIRED: artifact requires --path "
|
|
690
|
+
"(use issue/decision for informational notes)",
|
|
702
691
|
)
|
|
703
|
-
# If no kind is provided, infer from path presence: a path implies
|
|
704
|
-
# file-backed, absence implies informational (legacy compatibility).
|
|
705
692
|
return None
|
|
706
693
|
|
|
707
694
|
|