@hunter-harness/workflow-harness 0.2.13 → 0.2.15
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 +1 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +3 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/general/claude-code/scripts/harness_change.py +310 -16
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +177 -0
- package/harness/bundles/general/claude-code/scripts/harness_events.py +113 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +57 -4
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +67 -7
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +15 -3
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +193 -77
- package/harness/bundles/general/claude-code/scripts/harness_state.py +12 -4
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +3 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/general/codebuddy/scripts/harness_change.py +310 -16
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +177 -0
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +113 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +57 -4
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +67 -7
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +15 -3
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +193 -77
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +12 -4
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/general/codex/harness-run/reference.md +3 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/general/codex/scripts/harness_change.py +310 -16
- package/harness/bundles/general/codex/scripts/harness_deploy.py +177 -0
- package/harness/bundles/general/codex/scripts/harness_events.py +113 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +57 -4
- package/harness/bundles/general/codex/scripts/harness_integration.py +67 -7
- package/harness/bundles/general/codex/scripts/harness_ledger.py +15 -3
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/general/codex/scripts/harness_preflight.py +193 -77
- package/harness/bundles/general/codex/scripts/harness_state.py +12 -4
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +3 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/general/cursor/scripts/harness_change.py +310 -16
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +177 -0
- package/harness/bundles/general/cursor/scripts/harness_events.py +113 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +57 -4
- package/harness/bundles/general/cursor/scripts/harness_integration.py +67 -7
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +15 -3
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +193 -77
- package/harness/bundles/general/cursor/scripts/harness_state.py +12 -4
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/java/claude-code/scripts/harness_change.py +310 -16
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +177 -0
- package/harness/bundles/java/claude-code/scripts/harness_events.py +113 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +57 -4
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +67 -7
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +15 -3
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +193 -77
- package/harness/bundles/java/claude-code/scripts/harness_state.py +12 -4
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/java/codebuddy/scripts/harness_change.py +310 -16
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +177 -0
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +113 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +57 -4
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +67 -7
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +15 -3
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +193 -77
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +12 -4
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/java/codex/scripts/harness_change.py +310 -16
- package/harness/bundles/java/codex/scripts/harness_deploy.py +177 -0
- package/harness/bundles/java/codex/scripts/harness_events.py +113 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +57 -4
- package/harness/bundles/java/codex/scripts/harness_integration.py +67 -7
- package/harness/bundles/java/codex/scripts/harness_ledger.py +15 -3
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/java/codex/scripts/harness_preflight.py +193 -77
- package/harness/bundles/java/codex/scripts/harness_state.py +12 -4
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +86 -13
- 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 +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
- 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/scripts/harness_archive.py +204 -0
- package/harness/bundles/java/cursor/scripts/harness_change.py +310 -16
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +177 -0
- package/harness/bundles/java/cursor/scripts/harness_events.py +113 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +57 -4
- package/harness/bundles/java/cursor/scripts/harness_integration.py +67 -7
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +15 -3
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +85 -0
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +193 -77
- package/harness/bundles/java/cursor/scripts/harness_state.py +12 -4
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +86 -13
- package/harness/manifests/general/claude-code.json +25 -25
- package/harness/manifests/general/codebuddy.json +25 -25
- package/harness/manifests/general/codex.json +25 -25
- package/harness/manifests/general/cursor.json +25 -25
- package/harness/manifests/java/claude-code.json +26 -26
- package/harness/manifests/java/codebuddy.json +26 -26
- package/harness/manifests/java/codex.json +26 -26
- package/harness/manifests/java/cursor.json +26 -26
- package/hunter-workflow-family.json +2 -2
- package/package.json +15 -15
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-run
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。使用场景:开始编码、实现功能、写代码、TDD 编码
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=30342f0368faa261; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-run — 需求编码
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -52,7 +52,7 @@ requested=true + path missing
|
|
|
52
52
|
先执行 runtime doctor,并从 `meta/runtime.json` 读取 adapter 的 `path`、`branch` 以及 PowerShell `argvPrefix`;不要依赖裸 `python`/`powershell.exe`,也不要硬编码 Claude 路径。
|
|
53
53
|
|
|
54
54
|
```powershell
|
|
55
|
-
& <powershell-argv-prefix> -Command "git worktree add
|
|
55
|
+
& <powershell-argv-prefix> -Command "git worktree add -b '<adapter-branch>' -- '<adapter-worktree-path>'"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
如果分支已存在:
|
|
@@ -61,6 +61,8 @@ requested=true + path missing
|
|
|
61
61
|
& <powershell-argv-prefix> -Command "git worktree add -- '<adapter-worktree-path>' '<adapter-branch>'"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
> ⚠️ **argv 顺序**(复盘 §5.11):`-b <branch>` 必须在 `--` 之前;`git worktree add -- <path> -b <branch>` 中 `--` 后的 `-b` 会被 Git 视为位置参数,命令直接失败。等价 argv 数组:`["git","worktree","add","-b",branch,"--",path]`。
|
|
65
|
+
|
|
64
66
|
验证:
|
|
65
67
|
|
|
66
68
|
```powershell
|
|
@@ -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=30342f0368faa261; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -106,7 +106,7 @@ commit+push / 仅本地 commit / 取消;**不调用 Superpowers 呈现 PR/丢
|
|
|
106
106
|
|
|
107
107
|
见 `checklist.md` 与 `../protocols/ledger-protocol.md`、`../protocols/submit-protocol.md`。
|
|
108
108
|
|
|
109
|
-
test-tracking manifest 是 ignored test 的唯一强制暂存授权:只允许 `harness_test_guard.py stage` 暂存 manifest
|
|
109
|
+
test-tracking manifest 是 ignored test 的唯一强制暂存授权:只允许 `harness_test_guard.py stage` 暂存 manifest 中已校验且相对 `HEAD` 仍有差异的路径,禁止 `git add -f .`、目录级 `git add -f` 或全局修改 `.gitignore`。worktree commit 前须确认 guard 返回的 `files` 全部进入 cached diff;manifest 中已由 `HEAD` 跟踪且未变化的历史条目允许保留而不进入本次 cached diff。合并回主分支后、删除 worktree 前仍须确认 manifest 路径已由目标 commit 跟踪,否则停止清理。
|
|
110
110
|
|
|
111
111
|
### 四、worktree 合并硬规则
|
|
112
112
|
|
|
@@ -119,7 +119,7 @@ python <skills-root>/scripts/harness_ledger.py can-reuse `
|
|
|
119
119
|
|
|
120
120
|
- [ ] 检查 `.harness/changes/<change-name>/evidence/test-tracking.json` 是否存在
|
|
121
121
|
- [ ] 存在时先执行 `python <skills-root>/scripts/harness_test_guard.py stage --project . --change-dir ".harness/changes/<change-name>" --json`;任一 manifest/path/hash/index 校验失败立即停止
|
|
122
|
-
- [ ] 用 `git diff --cached --name-only` 验证
|
|
122
|
+
- [ ] 用 `git diff --cached --name-only` 验证 guard 响应 `files` 中的每个精确路径均已暂存,且没有 guard 引入的额外路径;manifest 中已由 `HEAD` 跟踪且未变化的历史条目无需进入本次 cached diff
|
|
123
123
|
- [ ] 无 manifest 时禁止使用 `git add -f`;有 manifest 时也只允许 guard 内部的 exact force-add,**禁止全局 force-add**、目录级 force-add 和全局修改 `.gitignore`
|
|
124
124
|
|
|
125
125
|
提交前**强制检查 `.gitignore`** 是否包含 `.harness/`:
|
|
@@ -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=30342f0368faa261; 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=30342f0368faa261; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-test — 测试执行
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -683,6 +683,34 @@ def check_status(
|
|
|
683
683
|
else:
|
|
684
684
|
blockers.append(tier_issue)
|
|
685
685
|
|
|
686
|
+
# --- artifact preflight (retro §5.31) ---
|
|
687
|
+
# Classify artifact events before destructive finalize: informational (OK),
|
|
688
|
+
# canonicalizable (same-change repo-relative path, warning), or blocking
|
|
689
|
+
# (cross-change/absolute/escaping path, fail closed).
|
|
690
|
+
try:
|
|
691
|
+
preflight = artifact_preflight(change_dir)
|
|
692
|
+
except Exception as exc: # noqa: BLE001 — preflight must not crash status
|
|
693
|
+
preflight = {"ok": False, "items": [], "blocking": [], "error": str(exc)}
|
|
694
|
+
checks["artifact_preflight"] = preflight
|
|
695
|
+
for item in preflight.get("blocking") or []:
|
|
696
|
+
blockers.append({
|
|
697
|
+
"code": "artifact-path-blocking",
|
|
698
|
+
"message": (
|
|
699
|
+
f"artifact event {item.get('eventId', '')} path "
|
|
700
|
+
f"{item.get('path', '')}: {item.get('reason', 'blocking')}"
|
|
701
|
+
),
|
|
702
|
+
})
|
|
703
|
+
for item in preflight.get("items") or []:
|
|
704
|
+
if item.get("category") == "canonicalizable":
|
|
705
|
+
warnings.append({
|
|
706
|
+
"code": "artifact-path-canonicalizable",
|
|
707
|
+
"message": (
|
|
708
|
+
f"artifact event {item.get('eventId', '')} path "
|
|
709
|
+
f"{item.get('path', '')} is repo-relative; "
|
|
710
|
+
f"canonical={item.get('canonicalPath', '')}"
|
|
711
|
+
),
|
|
712
|
+
})
|
|
713
|
+
|
|
686
714
|
archivable = len(blockers) == 0
|
|
687
715
|
return {
|
|
688
716
|
"ok": True,
|
|
@@ -2799,6 +2827,130 @@ def _html_escape(text: str) -> str:
|
|
|
2799
2827
|
# ---------------------------------------------------------------------------
|
|
2800
2828
|
|
|
2801
2829
|
|
|
2830
|
+
def validate_report_adequacy(summary: dict[str, Any]) -> dict[str, Any]:
|
|
2831
|
+
"""Validate that final summary is factually complete (retro §5.32).
|
|
2832
|
+
|
|
2833
|
+
The archive validator must not return all-green when the summary is
|
|
2834
|
+
factually incomplete. This gate checks independent sources (git facts,
|
|
2835
|
+
typed metrics, stage status) rather than comparing the summary against
|
|
2836
|
+
a lossy projection of itself.
|
|
2837
|
+
"""
|
|
2838
|
+
issues: list[dict[str, str]] = []
|
|
2839
|
+
|
|
2840
|
+
git_facts = summary.get("gitFacts") or {}
|
|
2841
|
+
if isinstance(git_facts, dict):
|
|
2842
|
+
base = str(git_facts.get("baseCommit") or "").strip()
|
|
2843
|
+
final = str(git_facts.get("finalCommit") or "").strip()
|
|
2844
|
+
files_changed = int(git_facts.get("filesChanged") or 0)
|
|
2845
|
+
# base != final but diff=0 → factually incomplete.
|
|
2846
|
+
if base and final and base != final and files_changed == 0:
|
|
2847
|
+
issues.append({
|
|
2848
|
+
"code": "DIFF_ZERO_WITH_NONEMPTY_COMMIT",
|
|
2849
|
+
"severity": "error",
|
|
2850
|
+
"message": f"final commit {final} differs from base {base} but filesChanged=0",
|
|
2851
|
+
})
|
|
2852
|
+
|
|
2853
|
+
# Typed metrics missing despite test report artifacts present.
|
|
2854
|
+
verification = summary.get("verification") or {}
|
|
2855
|
+
if isinstance(verification, dict):
|
|
2856
|
+
unit = verification.get("unitTests") or {}
|
|
2857
|
+
api = verification.get("apiTests") or {}
|
|
2858
|
+
artifacts = summary.get("artifacts") or []
|
|
2859
|
+
has_test_report = any(
|
|
2860
|
+
isinstance(a, dict) and "test" in str(a.get("path") or "").lower()
|
|
2861
|
+
for a in artifacts
|
|
2862
|
+
) if isinstance(artifacts, list) else False
|
|
2863
|
+
unit_pass = int(unit.get("passed") or 0) if isinstance(unit, dict) else 0
|
|
2864
|
+
api_status = str(api.get("status") or "") if isinstance(api, dict) else ""
|
|
2865
|
+
if has_test_report and unit_pass == 0 and api_status in {"", "not_available"}:
|
|
2866
|
+
issues.append({
|
|
2867
|
+
"code": "TYPED_METRICS_MISSING",
|
|
2868
|
+
"severity": "error",
|
|
2869
|
+
"message": "test report artifacts present but unitTests/apiTests typed metrics are empty",
|
|
2870
|
+
})
|
|
2871
|
+
|
|
2872
|
+
# stageStatus contradicts event reducer.
|
|
2873
|
+
stage = summary.get("stageStatus") or {}
|
|
2874
|
+
stage_from_events = summary.get("stageStatusFromEvents") or {}
|
|
2875
|
+
if isinstance(stage, dict) and isinstance(stage_from_events, dict):
|
|
2876
|
+
for phase, status in stage.items():
|
|
2877
|
+
event_status = stage_from_events.get(phase)
|
|
2878
|
+
if event_status is not None and str(status) != str(event_status):
|
|
2879
|
+
issues.append({
|
|
2880
|
+
"code": "STAGE_STATUS_CONTRADICTION",
|
|
2881
|
+
"severity": "error",
|
|
2882
|
+
"message": f"stageStatus.{phase}={status} but event reducer says {event_status}",
|
|
2883
|
+
})
|
|
2884
|
+
|
|
2885
|
+
return {"ok": not issues, "issues": issues}
|
|
2886
|
+
|
|
2887
|
+
|
|
2888
|
+
def artifact_preflight(change_dir: Path) -> dict[str, Any]:
|
|
2889
|
+
"""Classify artifact events before destructive finalize (retro §5.31).
|
|
2890
|
+
|
|
2891
|
+
Returns per-artifact classification: informational (no path, OK),
|
|
2892
|
+
canonicalizable (same-change repo-relative path, auto-normalize), or
|
|
2893
|
+
blocking (cross-change/absolute/escaping path, fail closed).
|
|
2894
|
+
"""
|
|
2895
|
+
change_dir = change_dir.resolve()
|
|
2896
|
+
events_p = change_dir / "events.ndjson"
|
|
2897
|
+
items: list[dict[str, Any]] = []
|
|
2898
|
+
blocking: list[dict[str, Any]] = []
|
|
2899
|
+
if not events_p.is_file():
|
|
2900
|
+
return {"ok": True, "items": [], "blocking": []}
|
|
2901
|
+
change_id = change_dir.name
|
|
2902
|
+
for line in events_p.read_text(encoding="utf-8").splitlines():
|
|
2903
|
+
try:
|
|
2904
|
+
event = json.loads(line)
|
|
2905
|
+
except json.JSONDecodeError:
|
|
2906
|
+
continue
|
|
2907
|
+
if not isinstance(event, dict) or event.get("type") != "artifact":
|
|
2908
|
+
continue
|
|
2909
|
+
path = str(event.get("path") or "").strip()
|
|
2910
|
+
kind = str(event.get("kind") or "").strip()
|
|
2911
|
+
event_id = str(event.get("id") or "")
|
|
2912
|
+
if not path:
|
|
2913
|
+
items.append({
|
|
2914
|
+
"eventId": event_id,
|
|
2915
|
+
"category": "informational",
|
|
2916
|
+
"kind": kind or "informational",
|
|
2917
|
+
"note": str(event.get("note") or "")[:80],
|
|
2918
|
+
})
|
|
2919
|
+
continue
|
|
2920
|
+
# Check for escaping/cross-change paths.
|
|
2921
|
+
parts = path.replace("\\", "/").split("/")
|
|
2922
|
+
if ".." in parts or path.startswith("/") or re.match(r"^[A-Za-z]:", path):
|
|
2923
|
+
item = {
|
|
2924
|
+
"eventId": event_id,
|
|
2925
|
+
"category": "blocking",
|
|
2926
|
+
"path": path,
|
|
2927
|
+
"reason": "absolute or escaping path",
|
|
2928
|
+
}
|
|
2929
|
+
items.append(item)
|
|
2930
|
+
blocking.append(item)
|
|
2931
|
+
continue
|
|
2932
|
+
# Same-change repo-relative path: canonicalizable.
|
|
2933
|
+
prefix = f".harness/changes/{change_id}/"
|
|
2934
|
+
if path.startswith(prefix):
|
|
2935
|
+
canonical = path[len(prefix):]
|
|
2936
|
+
items.append({
|
|
2937
|
+
"eventId": event_id,
|
|
2938
|
+
"category": "canonicalizable",
|
|
2939
|
+
"path": path,
|
|
2940
|
+
"canonicalPath": canonical,
|
|
2941
|
+
"correction": f"append correction --target-event-id {event_id} --target-field path --new-value-json \"{canonical}\"",
|
|
2942
|
+
})
|
|
2943
|
+
continue
|
|
2944
|
+
# Change-relative path: OK.
|
|
2945
|
+
items.append({
|
|
2946
|
+
"eventId": event_id,
|
|
2947
|
+
"category": "file-backed",
|
|
2948
|
+
"path": path,
|
|
2949
|
+
"exists": (change_dir / path).is_file(),
|
|
2950
|
+
})
|
|
2951
|
+
return {"ok": not blocking, "items": items, "blocking": blocking}
|
|
2952
|
+
|
|
2953
|
+
|
|
2802
2954
|
def run_knowledge_poststeps(project_root: Path) -> dict[str, Any]:
|
|
2803
2955
|
results: dict[str, Any] = {"ran": False, "steps": [], "warnings": []}
|
|
2804
2956
|
if not KNOWLEDGE_SCRIPT.is_file():
|
|
@@ -3107,6 +3259,36 @@ def cmd_finalize(
|
|
|
3107
3259
|
note="review missing on full tier (allowed by user)",
|
|
3108
3260
|
)
|
|
3109
3261
|
|
|
3262
|
+
# --- 2c. artifact preflight (retro §5.31) ---
|
|
3263
|
+
# Classify artifact events before freeze: blocking paths must fail closed
|
|
3264
|
+
# before the staged operation commits a phase.end.
|
|
3265
|
+
try:
|
|
3266
|
+
preflight = artifact_preflight(work_dir)
|
|
3267
|
+
except Exception as exc: # noqa: BLE001 — preflight must not crash finalize
|
|
3268
|
+
preflight = {"ok": False, "items": [], "blocking": [], "error": str(exc)}
|
|
3269
|
+
payload["steps"]["artifact_preflight"] = {
|
|
3270
|
+
"ok": bool(preflight.get("ok")),
|
|
3271
|
+
"blockingCount": len(preflight.get("blocking") or []),
|
|
3272
|
+
"itemsCount": len(preflight.get("items") or []),
|
|
3273
|
+
}
|
|
3274
|
+
if preflight.get("blocking"):
|
|
3275
|
+
payload["error"] = (
|
|
3276
|
+
f"artifact preflight blocking: "
|
|
3277
|
+
f"{len(preflight['blocking'])} path(s) cannot be archived"
|
|
3278
|
+
)
|
|
3279
|
+
payload["issues"] = [
|
|
3280
|
+
{
|
|
3281
|
+
"code": "ARTIFACT_PATH_BLOCKING",
|
|
3282
|
+
"severity": "error",
|
|
3283
|
+
"message": f"{item.get('path', '')}: {item.get('reason', 'blocking')}",
|
|
3284
|
+
}
|
|
3285
|
+
for item in preflight["blocking"]
|
|
3286
|
+
]
|
|
3287
|
+
_restore_finalize_failure()
|
|
3288
|
+
payload["warnings"] = warnings
|
|
3289
|
+
payload["ok"] = False
|
|
3290
|
+
return 1, payload
|
|
3291
|
+
|
|
3110
3292
|
# --- 3. candidate phase.end + freeze cutoff (RET-19 freeze-first) ---
|
|
3111
3293
|
# This terminal exists only inside the isolated operation staging tree. It
|
|
3112
3294
|
# becomes authoritative atomically at publish; validation failure discards
|
|
@@ -3169,6 +3351,28 @@ def cmd_finalize(
|
|
|
3169
3351
|
payload["ok"] = False
|
|
3170
3352
|
return 1, payload
|
|
3171
3353
|
|
|
3354
|
+
# --- 5b. report adequacy (retro §5.32) ---
|
|
3355
|
+
# Validate that the summary is factually complete: no diff=0 with non-empty
|
|
3356
|
+
# commit, no missing typed metrics despite test reports, no stageStatus
|
|
3357
|
+
# contradictions. Fail closed rather than archiving an incomplete summary.
|
|
3358
|
+
adequacy_result = validate_report_adequacy(summary)
|
|
3359
|
+
payload["steps"]["report_adequacy"] = adequacy_result
|
|
3360
|
+
summary.setdefault("reportPipeline", {})["reportAdequacy"] = {
|
|
3361
|
+
"ok": bool(adequacy_result.get("ok")),
|
|
3362
|
+
"issues": adequacy_result.get("issues") or [],
|
|
3363
|
+
}
|
|
3364
|
+
try:
|
|
3365
|
+
write_json(summary_path, summary)
|
|
3366
|
+
except OSError as exc:
|
|
3367
|
+
warnings.append(f"could not write reportAdequacy: {exc}")
|
|
3368
|
+
if not adequacy_result.get("ok"):
|
|
3369
|
+
payload["issues"] = adequacy_result.get("issues") or []
|
|
3370
|
+
payload["error"] = "report adequacy failed; staged operation discarded"
|
|
3371
|
+
_restore_finalize_failure()
|
|
3372
|
+
payload["warnings"] = warnings
|
|
3373
|
+
payload["ok"] = False
|
|
3374
|
+
return 1, payload
|
|
3375
|
+
|
|
3172
3376
|
# --- 6. render (Node, else Python fallback) ---
|
|
3173
3377
|
render_result = render_final_summary(work_dir, summary_path)
|
|
3174
3378
|
payload["steps"]["render"] = render_result
|