@hunter-harness/workflow-harness 0.2.32 → 0.2.33
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-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +6 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +6 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/checklist.md +2 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +6 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +6 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +6 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +6 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/checklist.md +2 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +6 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +43 -11
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +6 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +43 -11
- package/harness/manifests/general/claude-code.json +6 -6
- package/harness/manifests/general/codebuddy.json +6 -6
- package/harness/manifests/general/codex.json +6 -6
- package/harness/manifests/general/cursor.json +6 -6
- package/harness/manifests/java/claude-code.json +6 -6
- package/harness/manifests/java/codebuddy.json +6 -6
- package/harness/manifests/java/codex.json +6 -6
- package/harness/manifests/java/cursor.json +6 -6
- package/hunter-workflow-family.json +4 -4
- package/package.json +1 -1
|
@@ -106,7 +106,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
106
106
|
|------|------|
|
|
107
107
|
| 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
|
|
108
108
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
|
|
109
|
-
| 阶段 8 | spec/plan/detail/scenarios/worktree
|
|
109
|
+
| 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
|
|
110
110
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
111
111
|
| 知识查询 | 0.5 失败不得假装已读历史 |
|
|
112
112
|
| 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
|
|
@@ -214,6 +214,7 @@ source: harness-plan
|
|
|
214
214
|
- [ ] finalizer 返回 `ok=true` 与 `artifactsHash`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
|
+
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
217
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
218
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
219
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
@@ -224,6 +225,7 @@ source: harness-plan
|
|
|
224
225
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
225
226
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
226
227
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
228
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
227
229
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
228
230
|
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
229
231
|
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
@@ -335,10 +335,16 @@ status: approved
|
|
|
335
335
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ |
|
|
336
336
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ |
|
|
337
337
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ |
|
|
338
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ |
|
|
338
339
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ |
|
|
340
|
+
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ |
|
|
341
|
+
| `.harness/changes/<change>/meta/scenario-manifest.json` | ✅ |
|
|
342
|
+
| `.harness/changes/<change>/meta/plan-finalization.json` | ✅ |
|
|
339
343
|
| `.harness/changes/<change>/logs/execution-log.md` | ✅ |
|
|
340
344
|
| `.harness/changes/<change>/events.ndjson` | ✅ |
|
|
341
345
|
|
|
346
|
+
`plan-finalization.json.files` 必须完整列出 design、plan、implementation-detail、test-scenarios、gate-policy、worktree 六项标准输入。`verify` 对缺项、重复项、越界路径以及 symlink/junction/reparse point 一律 fail-closed;不得通过删减收据文件集后重算哈希来绕过完整性检查。
|
|
347
|
+
|
|
342
348
|
### Plan 结束行为规则
|
|
343
349
|
|
|
344
350
|
- **禁止询问执行模式**:Subagent-Driven / Inline Execution 属于 /harness-run 阶段
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shutil
|
|
14
|
+
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
16
17
|
from pathlib import Path, PurePosixPath
|
|
@@ -45,6 +46,7 @@ PRIORITY_EVIDENCE_KIND = {
|
|
|
45
46
|
"P1": "ledger",
|
|
46
47
|
"P2": "advisory",
|
|
47
48
|
}
|
|
49
|
+
_FILE_ATTRIBUTE_REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _result_error(code: str, message: str) -> dict[str, Any]:
|
|
@@ -302,6 +304,28 @@ def _frontmatter(text: str) -> dict[str, str] | None:
|
|
|
302
304
|
return None
|
|
303
305
|
|
|
304
306
|
|
|
307
|
+
def _required_artifact_names(change_name: str) -> set[str]:
|
|
308
|
+
return {
|
|
309
|
+
f"spec/{change_name}-design.md",
|
|
310
|
+
f"plans/{change_name}-plan.md",
|
|
311
|
+
f"plans/{change_name}-implementation-detail.md",
|
|
312
|
+
f"plans/{change_name}-test-scenarios.md",
|
|
313
|
+
"meta/gate-policy.json",
|
|
314
|
+
"meta/worktree.json",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _is_link_or_reparse(path: Path) -> bool:
|
|
319
|
+
"""Detect symlinks and Windows reparse points on every supported Python."""
|
|
320
|
+
try:
|
|
321
|
+
if path.is_symlink():
|
|
322
|
+
return True
|
|
323
|
+
attributes = getattr(os.lstat(path), "st_file_attributes", 0)
|
|
324
|
+
except OSError:
|
|
325
|
+
return False
|
|
326
|
+
return bool(attributes & _FILE_ATTRIBUTE_REPARSE_POINT)
|
|
327
|
+
|
|
328
|
+
|
|
305
329
|
def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
306
330
|
"""Validate the identity, reuse, and storage contract for a slice plan."""
|
|
307
331
|
text = Path(plan_path).read_text(encoding="utf-8-sig")
|
|
@@ -369,7 +393,7 @@ def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
|
369
393
|
def _artifact_files(staging: Path) -> list[Path]:
|
|
370
394
|
files: list[Path] = []
|
|
371
395
|
for path in staging.rglob("*"):
|
|
372
|
-
if path
|
|
396
|
+
if _is_link_or_reparse(path):
|
|
373
397
|
raise ValueError(f"PLAN_ARTIFACT_SYMLINK: {path}")
|
|
374
398
|
if path.is_file():
|
|
375
399
|
files.append(path)
|
|
@@ -378,14 +402,7 @@ def _artifact_files(staging: Path) -> list[Path]:
|
|
|
378
402
|
|
|
379
403
|
def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
|
|
380
404
|
staging = staging.resolve()
|
|
381
|
-
required = {
|
|
382
|
-
Path("spec") / f"{change_name}-design.md",
|
|
383
|
-
Path("plans") / f"{change_name}-plan.md",
|
|
384
|
-
Path("plans") / f"{change_name}-implementation-detail.md",
|
|
385
|
-
Path("plans") / f"{change_name}-test-scenarios.md",
|
|
386
|
-
Path("meta") / "gate-policy.json",
|
|
387
|
-
Path("meta") / "worktree.json",
|
|
388
|
-
}
|
|
405
|
+
required = {Path(name) for name in _required_artifact_names(change_name)}
|
|
389
406
|
try:
|
|
390
407
|
files = _artifact_files(staging)
|
|
391
408
|
except ValueError as exc:
|
|
@@ -621,12 +638,18 @@ def _receipt_artifact_targets(
|
|
|
621
638
|
cursor = change_dir
|
|
622
639
|
for part in rel.parts:
|
|
623
640
|
cursor = cursor / part
|
|
624
|
-
|
|
625
|
-
if cursor.is_symlink() or is_junction():
|
|
641
|
+
if _is_link_or_reparse(cursor):
|
|
626
642
|
return None, _result_error(
|
|
627
643
|
"RECEIPT_FILE_PATH_INVALID",
|
|
628
644
|
f"receipt artifact path traverses a link: {normalized}",
|
|
629
645
|
)
|
|
646
|
+
try:
|
|
647
|
+
target.resolve(strict=False).relative_to(change_dir)
|
|
648
|
+
except (OSError, RuntimeError, ValueError):
|
|
649
|
+
return None, _result_error(
|
|
650
|
+
"RECEIPT_FILE_PATH_INVALID",
|
|
651
|
+
f"receipt artifact path resolves outside the change directory: {normalized}",
|
|
652
|
+
)
|
|
630
653
|
targets.append((normalized, target))
|
|
631
654
|
return targets, None
|
|
632
655
|
|
|
@@ -671,6 +694,15 @@ def verify_plan(change_dir: Path) -> dict[str, Any]:
|
|
|
671
694
|
if path_error is not None:
|
|
672
695
|
return path_error
|
|
673
696
|
assert artifact_targets is not None
|
|
697
|
+
artifact_name_set = {rel for rel, _ in artifact_targets}
|
|
698
|
+
missing_required = sorted(
|
|
699
|
+
_required_artifact_names(change_name) - artifact_name_set
|
|
700
|
+
)
|
|
701
|
+
if missing_required:
|
|
702
|
+
return _result_error(
|
|
703
|
+
"RECEIPT_FILES_INCOMPLETE",
|
|
704
|
+
"receipt omits required artifacts: " + ", ".join(missing_required),
|
|
705
|
+
)
|
|
674
706
|
if receipt.get("status") != "finalized":
|
|
675
707
|
return _result_error(
|
|
676
708
|
"RECEIPT_NOT_FINALIZED",
|
|
@@ -88,7 +88,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
88
88
|
|------|------|
|
|
89
89
|
| 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
|
|
90
90
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
|
|
91
|
-
| 阶段 8 | spec/plan/detail/scenarios/worktree
|
|
91
|
+
| 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
|
|
92
92
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
93
93
|
| 知识查询 | 0.5 失败不得假装已读历史 |
|
|
94
94
|
| 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
|
|
@@ -214,6 +214,7 @@ source: harness-plan
|
|
|
214
214
|
- [ ] finalizer 返回 `ok=true` 与 `artifactsHash`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
|
+
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
217
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
218
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
219
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
@@ -224,6 +225,7 @@ source: harness-plan
|
|
|
224
225
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
225
226
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
226
227
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
228
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
227
229
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
228
230
|
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
229
231
|
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
@@ -335,10 +335,16 @@ status: approved
|
|
|
335
335
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ |
|
|
336
336
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ |
|
|
337
337
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ |
|
|
338
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ |
|
|
338
339
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ |
|
|
340
|
+
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ |
|
|
341
|
+
| `.harness/changes/<change>/meta/scenario-manifest.json` | ✅ |
|
|
342
|
+
| `.harness/changes/<change>/meta/plan-finalization.json` | ✅ |
|
|
339
343
|
| `.harness/changes/<change>/logs/execution-log.md` | ✅ |
|
|
340
344
|
| `.harness/changes/<change>/events.ndjson` | ✅ |
|
|
341
345
|
|
|
346
|
+
`plan-finalization.json.files` 必须完整列出 design、plan、implementation-detail、test-scenarios、gate-policy、worktree 六项标准输入。`verify` 对缺项、重复项、越界路径以及 symlink/junction/reparse point 一律 fail-closed;不得通过删减收据文件集后重算哈希来绕过完整性检查。
|
|
347
|
+
|
|
342
348
|
### Plan 结束行为规则
|
|
343
349
|
|
|
344
350
|
- **禁止询问执行模式**:Subagent-Driven / Inline Execution 属于 /harness-run 阶段
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shutil
|
|
14
|
+
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
16
17
|
from pathlib import Path, PurePosixPath
|
|
@@ -45,6 +46,7 @@ PRIORITY_EVIDENCE_KIND = {
|
|
|
45
46
|
"P1": "ledger",
|
|
46
47
|
"P2": "advisory",
|
|
47
48
|
}
|
|
49
|
+
_FILE_ATTRIBUTE_REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _result_error(code: str, message: str) -> dict[str, Any]:
|
|
@@ -302,6 +304,28 @@ def _frontmatter(text: str) -> dict[str, str] | None:
|
|
|
302
304
|
return None
|
|
303
305
|
|
|
304
306
|
|
|
307
|
+
def _required_artifact_names(change_name: str) -> set[str]:
|
|
308
|
+
return {
|
|
309
|
+
f"spec/{change_name}-design.md",
|
|
310
|
+
f"plans/{change_name}-plan.md",
|
|
311
|
+
f"plans/{change_name}-implementation-detail.md",
|
|
312
|
+
f"plans/{change_name}-test-scenarios.md",
|
|
313
|
+
"meta/gate-policy.json",
|
|
314
|
+
"meta/worktree.json",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _is_link_or_reparse(path: Path) -> bool:
|
|
319
|
+
"""Detect symlinks and Windows reparse points on every supported Python."""
|
|
320
|
+
try:
|
|
321
|
+
if path.is_symlink():
|
|
322
|
+
return True
|
|
323
|
+
attributes = getattr(os.lstat(path), "st_file_attributes", 0)
|
|
324
|
+
except OSError:
|
|
325
|
+
return False
|
|
326
|
+
return bool(attributes & _FILE_ATTRIBUTE_REPARSE_POINT)
|
|
327
|
+
|
|
328
|
+
|
|
305
329
|
def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
306
330
|
"""Validate the identity, reuse, and storage contract for a slice plan."""
|
|
307
331
|
text = Path(plan_path).read_text(encoding="utf-8-sig")
|
|
@@ -369,7 +393,7 @@ def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
|
369
393
|
def _artifact_files(staging: Path) -> list[Path]:
|
|
370
394
|
files: list[Path] = []
|
|
371
395
|
for path in staging.rglob("*"):
|
|
372
|
-
if path
|
|
396
|
+
if _is_link_or_reparse(path):
|
|
373
397
|
raise ValueError(f"PLAN_ARTIFACT_SYMLINK: {path}")
|
|
374
398
|
if path.is_file():
|
|
375
399
|
files.append(path)
|
|
@@ -378,14 +402,7 @@ def _artifact_files(staging: Path) -> list[Path]:
|
|
|
378
402
|
|
|
379
403
|
def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
|
|
380
404
|
staging = staging.resolve()
|
|
381
|
-
required = {
|
|
382
|
-
Path("spec") / f"{change_name}-design.md",
|
|
383
|
-
Path("plans") / f"{change_name}-plan.md",
|
|
384
|
-
Path("plans") / f"{change_name}-implementation-detail.md",
|
|
385
|
-
Path("plans") / f"{change_name}-test-scenarios.md",
|
|
386
|
-
Path("meta") / "gate-policy.json",
|
|
387
|
-
Path("meta") / "worktree.json",
|
|
388
|
-
}
|
|
405
|
+
required = {Path(name) for name in _required_artifact_names(change_name)}
|
|
389
406
|
try:
|
|
390
407
|
files = _artifact_files(staging)
|
|
391
408
|
except ValueError as exc:
|
|
@@ -621,12 +638,18 @@ def _receipt_artifact_targets(
|
|
|
621
638
|
cursor = change_dir
|
|
622
639
|
for part in rel.parts:
|
|
623
640
|
cursor = cursor / part
|
|
624
|
-
|
|
625
|
-
if cursor.is_symlink() or is_junction():
|
|
641
|
+
if _is_link_or_reparse(cursor):
|
|
626
642
|
return None, _result_error(
|
|
627
643
|
"RECEIPT_FILE_PATH_INVALID",
|
|
628
644
|
f"receipt artifact path traverses a link: {normalized}",
|
|
629
645
|
)
|
|
646
|
+
try:
|
|
647
|
+
target.resolve(strict=False).relative_to(change_dir)
|
|
648
|
+
except (OSError, RuntimeError, ValueError):
|
|
649
|
+
return None, _result_error(
|
|
650
|
+
"RECEIPT_FILE_PATH_INVALID",
|
|
651
|
+
f"receipt artifact path resolves outside the change directory: {normalized}",
|
|
652
|
+
)
|
|
630
653
|
targets.append((normalized, target))
|
|
631
654
|
return targets, None
|
|
632
655
|
|
|
@@ -671,6 +694,15 @@ def verify_plan(change_dir: Path) -> dict[str, Any]:
|
|
|
671
694
|
if path_error is not None:
|
|
672
695
|
return path_error
|
|
673
696
|
assert artifact_targets is not None
|
|
697
|
+
artifact_name_set = {rel for rel, _ in artifact_targets}
|
|
698
|
+
missing_required = sorted(
|
|
699
|
+
_required_artifact_names(change_name) - artifact_name_set
|
|
700
|
+
)
|
|
701
|
+
if missing_required:
|
|
702
|
+
return _result_error(
|
|
703
|
+
"RECEIPT_FILES_INCOMPLETE",
|
|
704
|
+
"receipt omits required artifacts: " + ", ".join(missing_required),
|
|
705
|
+
)
|
|
674
706
|
if receipt.get("status") != "finalized":
|
|
675
707
|
return _result_error(
|
|
676
708
|
"RECEIPT_NOT_FINALIZED",
|
|
@@ -87,7 +87,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
87
87
|
|------|------|
|
|
88
88
|
| 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
|
|
89
89
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
|
|
90
|
-
| 阶段 8 | spec/plan/detail/scenarios/worktree
|
|
90
|
+
| 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
|
|
91
91
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
92
92
|
| 知识查询 | 0.5 失败不得假装已读历史 |
|
|
93
93
|
| 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
|
|
@@ -214,6 +214,7 @@ source: harness-plan
|
|
|
214
214
|
- [ ] finalizer 返回 `ok=true` 与 `artifactsHash`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
|
+
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
217
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
218
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
219
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
@@ -224,6 +225,7 @@ source: harness-plan
|
|
|
224
225
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
225
226
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
226
227
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
228
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
227
229
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
228
230
|
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
229
231
|
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
@@ -335,10 +335,16 @@ status: approved
|
|
|
335
335
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ |
|
|
336
336
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ |
|
|
337
337
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ |
|
|
338
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ |
|
|
338
339
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ |
|
|
340
|
+
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ |
|
|
341
|
+
| `.harness/changes/<change>/meta/scenario-manifest.json` | ✅ |
|
|
342
|
+
| `.harness/changes/<change>/meta/plan-finalization.json` | ✅ |
|
|
339
343
|
| `.harness/changes/<change>/logs/execution-log.md` | ✅ |
|
|
340
344
|
| `.harness/changes/<change>/events.ndjson` | ✅ |
|
|
341
345
|
|
|
346
|
+
`plan-finalization.json.files` 必须完整列出 design、plan、implementation-detail、test-scenarios、gate-policy、worktree 六项标准输入。`verify` 对缺项、重复项、越界路径以及 symlink/junction/reparse point 一律 fail-closed;不得通过删减收据文件集后重算哈希来绕过完整性检查。
|
|
347
|
+
|
|
342
348
|
### Plan 结束行为规则
|
|
343
349
|
|
|
344
350
|
- **禁止询问执行模式**:Subagent-Driven / Inline Execution 属于 /harness-run 阶段
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shutil
|
|
14
|
+
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
16
17
|
from pathlib import Path, PurePosixPath
|
|
@@ -45,6 +46,7 @@ PRIORITY_EVIDENCE_KIND = {
|
|
|
45
46
|
"P1": "ledger",
|
|
46
47
|
"P2": "advisory",
|
|
47
48
|
}
|
|
49
|
+
_FILE_ATTRIBUTE_REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _result_error(code: str, message: str) -> dict[str, Any]:
|
|
@@ -302,6 +304,28 @@ def _frontmatter(text: str) -> dict[str, str] | None:
|
|
|
302
304
|
return None
|
|
303
305
|
|
|
304
306
|
|
|
307
|
+
def _required_artifact_names(change_name: str) -> set[str]:
|
|
308
|
+
return {
|
|
309
|
+
f"spec/{change_name}-design.md",
|
|
310
|
+
f"plans/{change_name}-plan.md",
|
|
311
|
+
f"plans/{change_name}-implementation-detail.md",
|
|
312
|
+
f"plans/{change_name}-test-scenarios.md",
|
|
313
|
+
"meta/gate-policy.json",
|
|
314
|
+
"meta/worktree.json",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _is_link_or_reparse(path: Path) -> bool:
|
|
319
|
+
"""Detect symlinks and Windows reparse points on every supported Python."""
|
|
320
|
+
try:
|
|
321
|
+
if path.is_symlink():
|
|
322
|
+
return True
|
|
323
|
+
attributes = getattr(os.lstat(path), "st_file_attributes", 0)
|
|
324
|
+
except OSError:
|
|
325
|
+
return False
|
|
326
|
+
return bool(attributes & _FILE_ATTRIBUTE_REPARSE_POINT)
|
|
327
|
+
|
|
328
|
+
|
|
305
329
|
def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
306
330
|
"""Validate the identity, reuse, and storage contract for a slice plan."""
|
|
307
331
|
text = Path(plan_path).read_text(encoding="utf-8-sig")
|
|
@@ -369,7 +393,7 @@ def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
|
369
393
|
def _artifact_files(staging: Path) -> list[Path]:
|
|
370
394
|
files: list[Path] = []
|
|
371
395
|
for path in staging.rglob("*"):
|
|
372
|
-
if path
|
|
396
|
+
if _is_link_or_reparse(path):
|
|
373
397
|
raise ValueError(f"PLAN_ARTIFACT_SYMLINK: {path}")
|
|
374
398
|
if path.is_file():
|
|
375
399
|
files.append(path)
|
|
@@ -378,14 +402,7 @@ def _artifact_files(staging: Path) -> list[Path]:
|
|
|
378
402
|
|
|
379
403
|
def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
|
|
380
404
|
staging = staging.resolve()
|
|
381
|
-
required = {
|
|
382
|
-
Path("spec") / f"{change_name}-design.md",
|
|
383
|
-
Path("plans") / f"{change_name}-plan.md",
|
|
384
|
-
Path("plans") / f"{change_name}-implementation-detail.md",
|
|
385
|
-
Path("plans") / f"{change_name}-test-scenarios.md",
|
|
386
|
-
Path("meta") / "gate-policy.json",
|
|
387
|
-
Path("meta") / "worktree.json",
|
|
388
|
-
}
|
|
405
|
+
required = {Path(name) for name in _required_artifact_names(change_name)}
|
|
389
406
|
try:
|
|
390
407
|
files = _artifact_files(staging)
|
|
391
408
|
except ValueError as exc:
|
|
@@ -621,12 +638,18 @@ def _receipt_artifact_targets(
|
|
|
621
638
|
cursor = change_dir
|
|
622
639
|
for part in rel.parts:
|
|
623
640
|
cursor = cursor / part
|
|
624
|
-
|
|
625
|
-
if cursor.is_symlink() or is_junction():
|
|
641
|
+
if _is_link_or_reparse(cursor):
|
|
626
642
|
return None, _result_error(
|
|
627
643
|
"RECEIPT_FILE_PATH_INVALID",
|
|
628
644
|
f"receipt artifact path traverses a link: {normalized}",
|
|
629
645
|
)
|
|
646
|
+
try:
|
|
647
|
+
target.resolve(strict=False).relative_to(change_dir)
|
|
648
|
+
except (OSError, RuntimeError, ValueError):
|
|
649
|
+
return None, _result_error(
|
|
650
|
+
"RECEIPT_FILE_PATH_INVALID",
|
|
651
|
+
f"receipt artifact path resolves outside the change directory: {normalized}",
|
|
652
|
+
)
|
|
630
653
|
targets.append((normalized, target))
|
|
631
654
|
return targets, None
|
|
632
655
|
|
|
@@ -671,6 +694,15 @@ def verify_plan(change_dir: Path) -> dict[str, Any]:
|
|
|
671
694
|
if path_error is not None:
|
|
672
695
|
return path_error
|
|
673
696
|
assert artifact_targets is not None
|
|
697
|
+
artifact_name_set = {rel for rel, _ in artifact_targets}
|
|
698
|
+
missing_required = sorted(
|
|
699
|
+
_required_artifact_names(change_name) - artifact_name_set
|
|
700
|
+
)
|
|
701
|
+
if missing_required:
|
|
702
|
+
return _result_error(
|
|
703
|
+
"RECEIPT_FILES_INCOMPLETE",
|
|
704
|
+
"receipt omits required artifacts: " + ", ".join(missing_required),
|
|
705
|
+
)
|
|
674
706
|
if receipt.get("status") != "finalized":
|
|
675
707
|
return _result_error(
|
|
676
708
|
"RECEIPT_NOT_FINALIZED",
|
|
@@ -87,7 +87,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
87
87
|
|------|------|
|
|
88
88
|
| 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
|
|
89
89
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
|
|
90
|
-
| 阶段 8 | spec/plan/detail/scenarios/worktree
|
|
90
|
+
| 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
|
|
91
91
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
92
92
|
| 知识查询 | 0.5 失败不得假装已读历史 |
|
|
93
93
|
| 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
|
|
@@ -214,6 +214,7 @@ source: harness-plan
|
|
|
214
214
|
- [ ] finalizer 返回 `ok=true` 与 `artifactsHash`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
|
+
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
217
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
218
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
219
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
@@ -224,6 +225,7 @@ source: harness-plan
|
|
|
224
225
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
225
226
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
226
227
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
228
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
227
229
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
228
230
|
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
229
231
|
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
@@ -335,10 +335,16 @@ status: approved
|
|
|
335
335
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ |
|
|
336
336
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ |
|
|
337
337
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ |
|
|
338
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ |
|
|
338
339
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ |
|
|
340
|
+
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ |
|
|
341
|
+
| `.harness/changes/<change>/meta/scenario-manifest.json` | ✅ |
|
|
342
|
+
| `.harness/changes/<change>/meta/plan-finalization.json` | ✅ |
|
|
339
343
|
| `.harness/changes/<change>/logs/execution-log.md` | ✅ |
|
|
340
344
|
| `.harness/changes/<change>/events.ndjson` | ✅ |
|
|
341
345
|
|
|
346
|
+
`plan-finalization.json.files` 必须完整列出 design、plan、implementation-detail、test-scenarios、gate-policy、worktree 六项标准输入。`verify` 对缺项、重复项、越界路径以及 symlink/junction/reparse point 一律 fail-closed;不得通过删减收据文件集后重算哈希来绕过完整性检查。
|
|
347
|
+
|
|
342
348
|
### Plan 结束行为规则
|
|
343
349
|
|
|
344
350
|
- **禁止询问执行模式**:Subagent-Driven / Inline Execution 属于 /harness-run 阶段
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shutil
|
|
14
|
+
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
16
17
|
from pathlib import Path, PurePosixPath
|
|
@@ -45,6 +46,7 @@ PRIORITY_EVIDENCE_KIND = {
|
|
|
45
46
|
"P1": "ledger",
|
|
46
47
|
"P2": "advisory",
|
|
47
48
|
}
|
|
49
|
+
_FILE_ATTRIBUTE_REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _result_error(code: str, message: str) -> dict[str, Any]:
|
|
@@ -302,6 +304,28 @@ def _frontmatter(text: str) -> dict[str, str] | None:
|
|
|
302
304
|
return None
|
|
303
305
|
|
|
304
306
|
|
|
307
|
+
def _required_artifact_names(change_name: str) -> set[str]:
|
|
308
|
+
return {
|
|
309
|
+
f"spec/{change_name}-design.md",
|
|
310
|
+
f"plans/{change_name}-plan.md",
|
|
311
|
+
f"plans/{change_name}-implementation-detail.md",
|
|
312
|
+
f"plans/{change_name}-test-scenarios.md",
|
|
313
|
+
"meta/gate-policy.json",
|
|
314
|
+
"meta/worktree.json",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _is_link_or_reparse(path: Path) -> bool:
|
|
319
|
+
"""Detect symlinks and Windows reparse points on every supported Python."""
|
|
320
|
+
try:
|
|
321
|
+
if path.is_symlink():
|
|
322
|
+
return True
|
|
323
|
+
attributes = getattr(os.lstat(path), "st_file_attributes", 0)
|
|
324
|
+
except OSError:
|
|
325
|
+
return False
|
|
326
|
+
return bool(attributes & _FILE_ATTRIBUTE_REPARSE_POINT)
|
|
327
|
+
|
|
328
|
+
|
|
305
329
|
def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
306
330
|
"""Validate the identity, reuse, and storage contract for a slice plan."""
|
|
307
331
|
text = Path(plan_path).read_text(encoding="utf-8-sig")
|
|
@@ -369,7 +393,7 @@ def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
|
|
|
369
393
|
def _artifact_files(staging: Path) -> list[Path]:
|
|
370
394
|
files: list[Path] = []
|
|
371
395
|
for path in staging.rglob("*"):
|
|
372
|
-
if path
|
|
396
|
+
if _is_link_or_reparse(path):
|
|
373
397
|
raise ValueError(f"PLAN_ARTIFACT_SYMLINK: {path}")
|
|
374
398
|
if path.is_file():
|
|
375
399
|
files.append(path)
|
|
@@ -378,14 +402,7 @@ def _artifact_files(staging: Path) -> list[Path]:
|
|
|
378
402
|
|
|
379
403
|
def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
|
|
380
404
|
staging = staging.resolve()
|
|
381
|
-
required = {
|
|
382
|
-
Path("spec") / f"{change_name}-design.md",
|
|
383
|
-
Path("plans") / f"{change_name}-plan.md",
|
|
384
|
-
Path("plans") / f"{change_name}-implementation-detail.md",
|
|
385
|
-
Path("plans") / f"{change_name}-test-scenarios.md",
|
|
386
|
-
Path("meta") / "gate-policy.json",
|
|
387
|
-
Path("meta") / "worktree.json",
|
|
388
|
-
}
|
|
405
|
+
required = {Path(name) for name in _required_artifact_names(change_name)}
|
|
389
406
|
try:
|
|
390
407
|
files = _artifact_files(staging)
|
|
391
408
|
except ValueError as exc:
|
|
@@ -621,12 +638,18 @@ def _receipt_artifact_targets(
|
|
|
621
638
|
cursor = change_dir
|
|
622
639
|
for part in rel.parts:
|
|
623
640
|
cursor = cursor / part
|
|
624
|
-
|
|
625
|
-
if cursor.is_symlink() or is_junction():
|
|
641
|
+
if _is_link_or_reparse(cursor):
|
|
626
642
|
return None, _result_error(
|
|
627
643
|
"RECEIPT_FILE_PATH_INVALID",
|
|
628
644
|
f"receipt artifact path traverses a link: {normalized}",
|
|
629
645
|
)
|
|
646
|
+
try:
|
|
647
|
+
target.resolve(strict=False).relative_to(change_dir)
|
|
648
|
+
except (OSError, RuntimeError, ValueError):
|
|
649
|
+
return None, _result_error(
|
|
650
|
+
"RECEIPT_FILE_PATH_INVALID",
|
|
651
|
+
f"receipt artifact path resolves outside the change directory: {normalized}",
|
|
652
|
+
)
|
|
630
653
|
targets.append((normalized, target))
|
|
631
654
|
return targets, None
|
|
632
655
|
|
|
@@ -671,6 +694,15 @@ def verify_plan(change_dir: Path) -> dict[str, Any]:
|
|
|
671
694
|
if path_error is not None:
|
|
672
695
|
return path_error
|
|
673
696
|
assert artifact_targets is not None
|
|
697
|
+
artifact_name_set = {rel for rel, _ in artifact_targets}
|
|
698
|
+
missing_required = sorted(
|
|
699
|
+
_required_artifact_names(change_name) - artifact_name_set
|
|
700
|
+
)
|
|
701
|
+
if missing_required:
|
|
702
|
+
return _result_error(
|
|
703
|
+
"RECEIPT_FILES_INCOMPLETE",
|
|
704
|
+
"receipt omits required artifacts: " + ", ".join(missing_required),
|
|
705
|
+
)
|
|
674
706
|
if receipt.get("status") != "finalized":
|
|
675
707
|
return _result_error(
|
|
676
708
|
"RECEIPT_NOT_FINALIZED",
|
|
@@ -106,7 +106,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
106
106
|
|------|------|
|
|
107
107
|
| 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
|
|
108
108
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
|
|
109
|
-
| 阶段 8 | spec/plan/detail/scenarios/worktree
|
|
109
|
+
| 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
|
|
110
110
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
111
111
|
| 知识查询 | 0.5 失败不得假装已读历史 |
|
|
112
112
|
| 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
|
|
@@ -214,6 +214,7 @@ source: harness-plan
|
|
|
214
214
|
- [ ] finalizer 返回 `ok=true` 与 `artifactsHash`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
|
+
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
217
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
218
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
219
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
@@ -224,6 +225,7 @@ source: harness-plan
|
|
|
224
225
|
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
225
226
|
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
226
227
|
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
228
|
+
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
227
229
|
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
228
230
|
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
229
231
|
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|