@hunter-harness/workflow-harness 0.2.14 → 0.2.16
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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- 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 +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 +80 -0
- package/harness/bundles/general/claude-code/scripts/harness_change.py +256 -16
- package/harness/bundles/general/claude-code/scripts/harness_events.py +14 -0
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +75 -0
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +102 -7
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +70 -3
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +17 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +146 -77
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +58 -7
- package/harness/bundles/general/claude-code/scripts/harness_review.py +107 -0
- package/harness/bundles/general/claude-code/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- 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 +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 +80 -0
- package/harness/bundles/general/codebuddy/scripts/harness_change.py +256 -16
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +14 -0
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +75 -0
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +102 -7
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +70 -3
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +17 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +146 -77
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +58 -7
- package/harness/bundles/general/codebuddy/scripts/harness_review.py +107 -0
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- 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 +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 +80 -0
- package/harness/bundles/general/codex/scripts/harness_change.py +256 -16
- package/harness/bundles/general/codex/scripts/harness_events.py +14 -0
- package/harness/bundles/general/codex/scripts/harness_gate.py +75 -0
- package/harness/bundles/general/codex/scripts/harness_integration.py +102 -7
- package/harness/bundles/general/codex/scripts/harness_ledger.py +70 -3
- package/harness/bundles/general/codex/scripts/harness_paths.py +17 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/general/codex/scripts/harness_preflight.py +146 -77
- package/harness/bundles/general/codex/scripts/harness_profile.py +58 -7
- package/harness/bundles/general/codex/scripts/harness_review.py +107 -0
- package/harness/bundles/general/codex/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- 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 +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 +80 -0
- package/harness/bundles/general/cursor/scripts/harness_change.py +256 -16
- package/harness/bundles/general/cursor/scripts/harness_events.py +14 -0
- package/harness/bundles/general/cursor/scripts/harness_gate.py +75 -0
- package/harness/bundles/general/cursor/scripts/harness_integration.py +102 -7
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +70 -3
- package/harness/bundles/general/cursor/scripts/harness_paths.py +17 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +146 -77
- package/harness/bundles/general/cursor/scripts/harness_profile.py +58 -7
- package/harness/bundles/general/cursor/scripts/harness_review.py +107 -0
- package/harness/bundles/general/cursor/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.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 +80 -0
- package/harness/bundles/java/claude-code/scripts/harness_change.py +256 -16
- package/harness/bundles/java/claude-code/scripts/harness_events.py +14 -0
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +75 -0
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +102 -7
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +70 -3
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +17 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +146 -77
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +58 -7
- package/harness/bundles/java/claude-code/scripts/harness_review.py +107 -0
- package/harness/bundles/java/claude-code/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.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 +80 -0
- package/harness/bundles/java/codebuddy/scripts/harness_change.py +256 -16
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +14 -0
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +75 -0
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +102 -7
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +70 -3
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +17 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +146 -77
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +58 -7
- package/harness/bundles/java/codebuddy/scripts/harness_review.py +107 -0
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.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 +80 -0
- package/harness/bundles/java/codex/scripts/harness_change.py +256 -16
- package/harness/bundles/java/codex/scripts/harness_events.py +14 -0
- package/harness/bundles/java/codex/scripts/harness_gate.py +75 -0
- package/harness/bundles/java/codex/scripts/harness_integration.py +102 -7
- package/harness/bundles/java/codex/scripts/harness_ledger.py +70 -3
- package/harness/bundles/java/codex/scripts/harness_paths.py +17 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/java/codex/scripts/harness_preflight.py +146 -77
- package/harness/bundles/java/codex/scripts/harness_profile.py +58 -7
- package/harness/bundles/java/codex/scripts/harness_review.py +107 -0
- package/harness/bundles/java/codex/scripts/harness_state.py +12 -4
- 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-ingest/scripts/harness_knowledge.py +16 -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-review/reference.md +13 -0
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.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 +80 -0
- package/harness/bundles/java/cursor/scripts/harness_change.py +256 -16
- package/harness/bundles/java/cursor/scripts/harness_events.py +14 -0
- package/harness/bundles/java/cursor/scripts/harness_gate.py +75 -0
- package/harness/bundles/java/cursor/scripts/harness_integration.py +102 -7
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +70 -3
- package/harness/bundles/java/cursor/scripts/harness_paths.py +17 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +232 -0
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +146 -77
- package/harness/bundles/java/cursor/scripts/harness_profile.py +58 -7
- package/harness/bundles/java/cursor/scripts/harness_review.py +107 -0
- package/harness/bundles/java/cursor/scripts/harness_state.py +12 -4
- package/harness/manifests/general/claude-code.json +27 -27
- package/harness/manifests/general/codebuddy.json +27 -27
- package/harness/manifests/general/codex.json +27 -27
- package/harness/manifests/general/cursor.json +27 -27
- package/harness/manifests/java/claude-code.json +28 -28
- package/harness/manifests/java/codebuddy.json +28 -28
- package/harness/manifests/java/codex.json +28 -28
- package/harness/manifests/java/cursor.json +28 -28
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-archive — 变更归档
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-codebase-map — 代码库地图
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-knowledge-ingest
|
|
25
25
|
|
|
26
26
|
从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。
|
package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py
CHANGED
|
@@ -4552,6 +4552,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
4552
4552
|
query.add_argument("--status", action="append", dest="statuses", default=[], help="Only return entries with this lifecycle status")
|
|
4553
4553
|
query.add_argument("--type", action="append", dest="types", default=[], help="Only return entries of this knowledge type")
|
|
4554
4554
|
query.add_argument("--change", default=None, help="Write a change-scoped knowledge-context pointer")
|
|
4555
|
+
query.add_argument(
|
|
4556
|
+
"--verbose",
|
|
4557
|
+
action="store_true",
|
|
4558
|
+
help="Emit full payload including matches array (default: compact output for host decisioning)",
|
|
4559
|
+
)
|
|
4555
4560
|
|
|
4556
4561
|
promote = sub.add_parser("promote", help="Promote a candidate knowledge entry to active")
|
|
4557
4562
|
promote.add_argument("--project", default=".", help="Project root containing .harness/knowledge")
|
|
@@ -4652,7 +4657,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
4652
4657
|
Path(args.project), args.query, args.limit, args.files, args.statuses,
|
|
4653
4658
|
args.types, args.change,
|
|
4654
4659
|
)
|
|
4655
|
-
|
|
4660
|
+
if getattr(args, "verbose", False):
|
|
4661
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
4662
|
+
else:
|
|
4663
|
+
# C5: compact output — omit matches array; host reads context pack for details.
|
|
4664
|
+
compact = {
|
|
4665
|
+
"query": result["query"],
|
|
4666
|
+
"matchCount": result["matchCount"],
|
|
4667
|
+
"contextPack": result["contextPack"],
|
|
4668
|
+
"planInput": result["planInput"],
|
|
4669
|
+
}
|
|
4670
|
+
print(json.dumps(compact, ensure_ascii=False, indent=2))
|
|
4656
4671
|
return 0
|
|
4657
4672
|
if args.command == "promote":
|
|
4658
4673
|
try:
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-knowledge-query
|
|
25
25
|
|
|
26
26
|
并行 change 中查询必须传 `--change <change-id>`;成功后以该 change 的 `meta/knowledge-context.json` 为稳定指针。全局 `context-packs/latest.json` 仅向后兼容,不得用于判断多个 active change 的上下文归属。
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-plan — 需求规划
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
- Bash(codegraph *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-review — 代码审查
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -84,3 +84,16 @@ RED 高风险建议: N | YELLOW 中低风险建议: M | 仅供参考,不阻塞
|
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
fixback 是给后续修复循环使用的结构化清单,不替代 review-report;无 RED/YELLOW 时记录跳过原因,不生成空文件。
|
|
87
|
+
|
|
88
|
+
## CodeGraph Identity 合同(C12)
|
|
89
|
+
|
|
90
|
+
调用 `codegraph_explore` 后必须校验响应中的 `repositoryId` / `indexedHead` / `indexedAt`:
|
|
91
|
+
|
|
92
|
+
- `repositoryId` 必须与 `harness_paths.repository_identity(project_root)` 一致
|
|
93
|
+
- `indexedHead` 必须与当前 HEAD 一致
|
|
94
|
+
- `indexedAt` 必须存在
|
|
95
|
+
|
|
96
|
+
identity 不匹配时记 `CODEGRAPH_IDENTITY_MISMATCH` warning,降级为 Grep/Glob + Read。
|
|
97
|
+
identity 匹配时正常使用 codegraph 结果。
|
|
98
|
+
|
|
99
|
+
校验函数:`harness_review.validate_codegraph_identity(response, expected_repository_id, expected_head)`。
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
- Bash(codegraph *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-run — 需求编码
|
|
26
26
|
|
|
27
27
|
## 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
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -19,7 +19,7 @@ disallowed-tools:
|
|
|
19
19
|
- Bash(awk *)
|
|
20
20
|
- Bash(curl *)
|
|
21
21
|
---
|
|
22
|
-
<!-- generated by harness_deploy.py; core=
|
|
22
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
23
23
|
# harness-sync — 元数据同步
|
|
24
24
|
|
|
25
25
|
## Purpose
|
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(node *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=21a7451404d2c0ff; overlay=none; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-test — 测试执行
|
|
27
27
|
|
|
28
28
|
## 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,
|
|
@@ -3231,6 +3259,36 @@ def cmd_finalize(
|
|
|
3231
3259
|
note="review missing on full tier (allowed by user)",
|
|
3232
3260
|
)
|
|
3233
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
|
+
|
|
3234
3292
|
# --- 3. candidate phase.end + freeze cutoff (RET-19 freeze-first) ---
|
|
3235
3293
|
# This terminal exists only inside the isolated operation staging tree. It
|
|
3236
3294
|
# becomes authoritative atomically at publish; validation failure discards
|
|
@@ -3293,6 +3351,28 @@ def cmd_finalize(
|
|
|
3293
3351
|
payload["ok"] = False
|
|
3294
3352
|
return 1, payload
|
|
3295
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
|
+
|
|
3296
3376
|
# --- 6. render (Node, else Python fallback) ---
|
|
3297
3377
|
render_result = render_final_summary(work_dir, summary_path)
|
|
3298
3378
|
payload["steps"]["render"] = render_result
|
|
@@ -590,7 +590,9 @@ def lease_port(
|
|
|
590
590
|
}
|
|
591
591
|
for port in range(start, end + 1):
|
|
592
592
|
if port not in used:
|
|
593
|
+
lease_id = str(uuid.uuid4())
|
|
593
594
|
entry = {
|
|
595
|
+
"leaseId": lease_id,
|
|
594
596
|
"changeId": change_id,
|
|
595
597
|
"runId": run_id,
|
|
596
598
|
"pid": os.getpid(),
|
|
@@ -603,7 +605,7 @@ def lease_port(
|
|
|
603
605
|
leases.append(entry)
|
|
604
606
|
registry["leases"] = leases
|
|
605
607
|
_write_json(registry_path, registry)
|
|
606
|
-
return {"ok": True, "code": "PORT_LEASED", "port": port, "lease": entry}
|
|
608
|
+
return {"ok": True, "code": "PORT_LEASED", "port": port, "leaseId": lease_id, "lease": entry}
|
|
607
609
|
return {
|
|
608
610
|
"ok": False,
|
|
609
611
|
"code": "PORT_RANGE_EXHAUSTED",
|
|
@@ -616,7 +618,16 @@ def release_port(
|
|
|
616
618
|
*,
|
|
617
619
|
change_id: str,
|
|
618
620
|
run_id: str,
|
|
621
|
+
port: int | None = None,
|
|
622
|
+
lease_id: str | None = None,
|
|
619
623
|
) -> dict[str, Any]:
|
|
624
|
+
"""Release port leases by subset (retro §5.16).
|
|
625
|
+
|
|
626
|
+
- `--lease-id`: release only the matching leaseId.
|
|
627
|
+
- `--port`: release only the matching port (must match changeId+runId).
|
|
628
|
+
- Neither: release all leases matching (changeId, runId) — does NOT require
|
|
629
|
+
all leases under changeId to have the same runId.
|
|
630
|
+
"""
|
|
620
631
|
registry_path = project_root / PORTS_REL / "registry.json"
|
|
621
632
|
if not registry_path.is_file():
|
|
622
633
|
return {"ok": True, "code": "PORT_LEASE_ABSENT", "changeId": change_id}
|
|
@@ -628,27 +639,84 @@ def release_port(
|
|
|
628
639
|
leases = registry.get("leases") if isinstance(registry, dict) else None
|
|
629
640
|
if not isinstance(leases, list):
|
|
630
641
|
return {"ok": False, "code": "PORT_REGISTRY_INVALID", "message": "leases is not a list"}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
642
|
+
|
|
643
|
+
# Select the subset to release based on lease_id, port, or (changeId, runId)
|
|
644
|
+
if lease_id is not None:
|
|
645
|
+
to_release = [
|
|
646
|
+
item for item in leases
|
|
647
|
+
if isinstance(item, dict) and str(item.get("leaseId")) == lease_id
|
|
648
|
+
]
|
|
649
|
+
# Validate that the lease belongs to this change
|
|
650
|
+
for item in to_release:
|
|
651
|
+
if str(item.get("changeId")) != change_id:
|
|
652
|
+
return {
|
|
653
|
+
"ok": False,
|
|
654
|
+
"code": "PORT_LEASE_OWNER_MISMATCH",
|
|
655
|
+
"message": "leaseId does not belong to this change",
|
|
656
|
+
"holder": item,
|
|
657
|
+
}
|
|
658
|
+
elif port is not None:
|
|
659
|
+
to_release = [
|
|
660
|
+
item for item in leases
|
|
661
|
+
if isinstance(item, dict)
|
|
662
|
+
and item.get("port") == port
|
|
663
|
+
and str(item.get("changeId")) == change_id
|
|
664
|
+
]
|
|
665
|
+
# Validate runId matches
|
|
666
|
+
for item in to_release:
|
|
667
|
+
if str(item.get("runId")) != run_id:
|
|
668
|
+
return {
|
|
669
|
+
"ok": False,
|
|
670
|
+
"code": "PORT_LEASE_OWNER_MISMATCH",
|
|
671
|
+
"message": f"port {port} owned by different runId",
|
|
672
|
+
"holder": item,
|
|
673
|
+
"conflictingOwners": [
|
|
674
|
+
{"runId": str(i.get("runId")), "port": i.get("port")}
|
|
675
|
+
for i in leases
|
|
676
|
+
if isinstance(i, dict)
|
|
677
|
+
and i.get("port") == port
|
|
678
|
+
and str(i.get("changeId")) == change_id
|
|
679
|
+
],
|
|
680
|
+
}
|
|
681
|
+
else:
|
|
682
|
+
# Release all matching (changeId, runId) — subset release
|
|
683
|
+
to_release = [
|
|
684
|
+
item for item in leases
|
|
685
|
+
if isinstance(item, dict)
|
|
686
|
+
and str(item.get("changeId")) == change_id
|
|
687
|
+
and str(item.get("runId")) == run_id
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
if not to_release:
|
|
691
|
+
# Check if there are other leases under this changeId (different runId)
|
|
692
|
+
other_owned = [
|
|
693
|
+
item for item in leases
|
|
694
|
+
if isinstance(item, dict)
|
|
695
|
+
and str(item.get("changeId")) == change_id
|
|
696
|
+
and str(item.get("runId")) != run_id
|
|
697
|
+
]
|
|
698
|
+
if other_owned:
|
|
699
|
+
return {
|
|
700
|
+
"ok": False,
|
|
701
|
+
"code": "PORT_LEASE_OWNER_MISMATCH",
|
|
702
|
+
"message": "no matching leases for this runId; other runIds exist under this changeId",
|
|
703
|
+
"conflictingOwners": [
|
|
704
|
+
{"runId": str(i.get("runId")), "port": i.get("port"), "leaseId": i.get("leaseId")}
|
|
705
|
+
for i in other_owned
|
|
706
|
+
],
|
|
707
|
+
}
|
|
636
708
|
return {"ok": True, "code": "PORT_LEASE_ABSENT", "changeId": change_id}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
"message": "run id does not match port lease owner",
|
|
642
|
-
"holder": owned[0],
|
|
643
|
-
}
|
|
644
|
-
released_ports = [item.get("port") for item in owned]
|
|
645
|
-
registry["leases"] = [item for item in leases if item not in owned]
|
|
709
|
+
|
|
710
|
+
released_ports = [item.get("port") for item in to_release]
|
|
711
|
+
released_ids = [item.get("leaseId") for item in to_release]
|
|
712
|
+
registry["leases"] = [item for item in leases if item not in to_release]
|
|
646
713
|
_write_json(registry_path, registry)
|
|
647
714
|
return {
|
|
648
715
|
"ok": True,
|
|
649
716
|
"code": "PORT_LEASE_RELEASED",
|
|
650
717
|
"changeId": change_id,
|
|
651
718
|
"ports": released_ports,
|
|
719
|
+
"leaseIds": released_ids,
|
|
652
720
|
}
|
|
653
721
|
|
|
654
722
|
|
|
@@ -848,7 +916,13 @@ def cmd_lease_port(args: argparse.Namespace) -> int:
|
|
|
848
916
|
|
|
849
917
|
def cmd_release_port(args: argparse.Namespace) -> int:
|
|
850
918
|
project = resolve_main_project_root()
|
|
851
|
-
payload = release_port(
|
|
919
|
+
payload = release_port(
|
|
920
|
+
project,
|
|
921
|
+
change_id=args.change,
|
|
922
|
+
run_id=args.run_id,
|
|
923
|
+
port=getattr(args, "port", None),
|
|
924
|
+
lease_id=getattr(args, "lease_id", None),
|
|
925
|
+
)
|
|
852
926
|
if payload.get("ok"):
|
|
853
927
|
emit(payload, as_json=bool(args.json))
|
|
854
928
|
return 0
|
|
@@ -881,6 +955,161 @@ def cmd_integration_lock(args: argparse.Namespace) -> int:
|
|
|
881
955
|
)
|
|
882
956
|
|
|
883
957
|
|
|
958
|
+
def _ensure_change_identity(change_dir: Path) -> dict[str, str]:
|
|
959
|
+
"""Ensure meta/change-identity.json exists with a stable UUID (retro §5.5).
|
|
960
|
+
|
|
961
|
+
Returns the identity dict with changeUuid and changeName.
|
|
962
|
+
"""
|
|
963
|
+
identity_path = change_dir / "meta" / "change-identity.json"
|
|
964
|
+
if identity_path.is_file():
|
|
965
|
+
try:
|
|
966
|
+
data = json.loads(identity_path.read_text(encoding="utf-8-sig"))
|
|
967
|
+
if isinstance(data, dict) and data.get("changeUuid"):
|
|
968
|
+
return data
|
|
969
|
+
except (OSError, json.JSONDecodeError):
|
|
970
|
+
pass
|
|
971
|
+
# Generate new identity
|
|
972
|
+
identity = {
|
|
973
|
+
"schemaVersion": 1,
|
|
974
|
+
"changeUuid": str(uuid.uuid4()),
|
|
975
|
+
"changeName": change_dir.name,
|
|
976
|
+
"createdAt": now_iso(),
|
|
977
|
+
}
|
|
978
|
+
identity_path.parent.mkdir(parents=True, exist_ok=True)
|
|
979
|
+
identity_path.write_text(
|
|
980
|
+
json.dumps(identity, ensure_ascii=False, indent=2) + "\n",
|
|
981
|
+
encoding="utf-8",
|
|
982
|
+
newline="\n",
|
|
983
|
+
)
|
|
984
|
+
return identity
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def cmd_rename(args: argparse.Namespace) -> int:
|
|
988
|
+
"""Rename a change atomically: directory, pointers, worktree, identity (retro §5.5).
|
|
989
|
+
|
|
990
|
+
Appends a change.rename event; does not rewrite history.
|
|
991
|
+
"""
|
|
992
|
+
project = resolve_main_project_root()
|
|
993
|
+
changes_root = project / ".harness" / "changes"
|
|
994
|
+
old_dir = changes_root / args.change
|
|
995
|
+
new_dir = changes_root / args.to
|
|
996
|
+
|
|
997
|
+
if not old_dir.is_dir():
|
|
998
|
+
return emit_error(
|
|
999
|
+
"CHANGE_NOT_FOUND",
|
|
1000
|
+
f"change directory not found: {old_dir}",
|
|
1001
|
+
as_json=bool(args.json),
|
|
1002
|
+
)
|
|
1003
|
+
if new_dir.exists():
|
|
1004
|
+
return emit_error(
|
|
1005
|
+
"CHANGE_ALREADY_EXISTS",
|
|
1006
|
+
f"target change directory already exists: {new_dir}",
|
|
1007
|
+
as_json=bool(args.json),
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
# Ensure identity exists before rename
|
|
1011
|
+
identity = _ensure_change_identity(old_dir)
|
|
1012
|
+
old_uuid = identity.get("changeUuid", "")
|
|
1013
|
+
|
|
1014
|
+
# Rename directory
|
|
1015
|
+
import shutil
|
|
1016
|
+
shutil.move(str(old_dir), str(new_dir))
|
|
1017
|
+
|
|
1018
|
+
# Update knowledge-context.json.changeId if present
|
|
1019
|
+
kc_path = new_dir / "meta" / "knowledge-context.json"
|
|
1020
|
+
if kc_path.is_file():
|
|
1021
|
+
try:
|
|
1022
|
+
kc = json.loads(kc_path.read_text(encoding="utf-8-sig"))
|
|
1023
|
+
if isinstance(kc, dict):
|
|
1024
|
+
kc["changeId"] = args.to
|
|
1025
|
+
kc["changeUuid"] = old_uuid
|
|
1026
|
+
kc_path.write_text(
|
|
1027
|
+
json.dumps(kc, ensure_ascii=False, indent=2) + "\n",
|
|
1028
|
+
encoding="utf-8",
|
|
1029
|
+
newline="\n",
|
|
1030
|
+
)
|
|
1031
|
+
except (OSError, json.JSONDecodeError):
|
|
1032
|
+
pass
|
|
1033
|
+
|
|
1034
|
+
# Update worktree.json path/branch if present
|
|
1035
|
+
wt_path = new_dir / "meta" / "worktree.json"
|
|
1036
|
+
if wt_path.is_file():
|
|
1037
|
+
try:
|
|
1038
|
+
wt = json.loads(wt_path.read_text(encoding="utf-8-sig"))
|
|
1039
|
+
if isinstance(wt, dict):
|
|
1040
|
+
old_name = args.change
|
|
1041
|
+
new_name = args.to
|
|
1042
|
+
# Update path and branch if they contain old name
|
|
1043
|
+
if isinstance(wt.get("path"), str) and old_name in wt["path"]:
|
|
1044
|
+
wt["path"] = wt["path"].replace(old_name, new_name)
|
|
1045
|
+
if isinstance(wt.get("branch"), str) and old_name in wt["branch"]:
|
|
1046
|
+
wt["branch"] = wt["branch"].replace(old_name, new_name)
|
|
1047
|
+
wt_path.write_text(
|
|
1048
|
+
json.dumps(wt, ensure_ascii=False, indent=2) + "\n",
|
|
1049
|
+
encoding="utf-8",
|
|
1050
|
+
newline="\n",
|
|
1051
|
+
)
|
|
1052
|
+
except (OSError, json.JSONDecodeError):
|
|
1053
|
+
pass
|
|
1054
|
+
|
|
1055
|
+
# Update change-identity.json.changeName
|
|
1056
|
+
identity_path = new_dir / "meta" / "change-identity.json"
|
|
1057
|
+
if identity_path.is_file():
|
|
1058
|
+
try:
|
|
1059
|
+
ident = json.loads(identity_path.read_text(encoding="utf-8-sig"))
|
|
1060
|
+
if isinstance(ident, dict):
|
|
1061
|
+
ident["changeName"] = args.to
|
|
1062
|
+
ident["renamedFrom"] = args.change
|
|
1063
|
+
identity_path.write_text(
|
|
1064
|
+
json.dumps(ident, ensure_ascii=False, indent=2) + "\n",
|
|
1065
|
+
encoding="utf-8",
|
|
1066
|
+
newline="\n",
|
|
1067
|
+
)
|
|
1068
|
+
except (OSError, json.JSONDecodeError):
|
|
1069
|
+
pass
|
|
1070
|
+
|
|
1071
|
+
# Append change.rename event (does not rewrite history)
|
|
1072
|
+
try:
|
|
1073
|
+
import harness_events
|
|
1074
|
+
harness_events.append_event(
|
|
1075
|
+
new_dir,
|
|
1076
|
+
phase="plan",
|
|
1077
|
+
type_="change.rename",
|
|
1078
|
+
renamed_from=args.change,
|
|
1079
|
+
renamed_to=args.to,
|
|
1080
|
+
change_uuid=old_uuid,
|
|
1081
|
+
note=f"change renamed from {args.change} to {args.to}",
|
|
1082
|
+
)
|
|
1083
|
+
except Exception:
|
|
1084
|
+
pass # event append failure should not block rename
|
|
1085
|
+
|
|
1086
|
+
payload = {
|
|
1087
|
+
"ok": True,
|
|
1088
|
+
"code": "RENAMED",
|
|
1089
|
+
"changeUuid": old_uuid,
|
|
1090
|
+
"renamedFrom": args.change,
|
|
1091
|
+
"renamedTo": args.to,
|
|
1092
|
+
"changeDir": str(new_dir),
|
|
1093
|
+
}
|
|
1094
|
+
emit(payload, as_json=bool(args.json))
|
|
1095
|
+
return 0
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def cmd_ensure_identity(args: argparse.Namespace) -> int:
|
|
1099
|
+
"""Ensure meta/change-identity.json exists with a stable UUID (retro §5.5)."""
|
|
1100
|
+
project = resolve_main_project_root()
|
|
1101
|
+
change_dir = project / ".harness" / "changes" / args.change
|
|
1102
|
+
if not change_dir.is_dir():
|
|
1103
|
+
return emit_error(
|
|
1104
|
+
"CHANGE_NOT_FOUND",
|
|
1105
|
+
f"change directory not found: {change_dir}",
|
|
1106
|
+
as_json=bool(args.json),
|
|
1107
|
+
)
|
|
1108
|
+
identity = _ensure_change_identity(change_dir)
|
|
1109
|
+
emit(identity, as_json=bool(args.json))
|
|
1110
|
+
return 0
|
|
1111
|
+
|
|
1112
|
+
|
|
884
1113
|
def build_parser() -> argparse.ArgumentParser:
|
|
885
1114
|
parser = argparse.ArgumentParser(prog="harness_change.py")
|
|
886
1115
|
parser.add_argument("--json", action="store_true")
|
|
@@ -923,6 +1152,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
923
1152
|
p_port_release = sub.add_parser("release-port", parents=[shared])
|
|
924
1153
|
p_port_release.add_argument("--change", required=True)
|
|
925
1154
|
p_port_release.add_argument("--run-id", required=True)
|
|
1155
|
+
p_port_release.add_argument("--port", type=int, default=None)
|
|
1156
|
+
p_port_release.add_argument("--lease-id", default=None)
|
|
926
1157
|
p_port_release.set_defaults(func=cmd_release_port)
|
|
927
1158
|
|
|
928
1159
|
p_lock = sub.add_parser("integration-lock", parents=[shared])
|
|
@@ -931,6 +1162,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
931
1162
|
p_lock.add_argument("--ttl-seconds", type=int, default=3600)
|
|
932
1163
|
p_lock.set_defaults(func=cmd_integration_lock)
|
|
933
1164
|
|
|
1165
|
+
p_rename = sub.add_parser("rename", parents=[shared])
|
|
1166
|
+
p_rename.add_argument("--change", required=True)
|
|
1167
|
+
p_rename.add_argument("--to", required=True)
|
|
1168
|
+
p_rename.set_defaults(func=cmd_rename)
|
|
1169
|
+
|
|
1170
|
+
p_identity = sub.add_parser("ensure-identity", parents=[shared])
|
|
1171
|
+
p_identity.add_argument("--change", required=True)
|
|
1172
|
+
p_identity.set_defaults(func=cmd_ensure_identity)
|
|
1173
|
+
|
|
934
1174
|
return parser
|
|
935
1175
|
|
|
936
1176
|
|
|
@@ -50,6 +50,7 @@ EVENT_TYPES = frozenset(
|
|
|
50
50
|
"issue.resolve",
|
|
51
51
|
"decision",
|
|
52
52
|
"correction",
|
|
53
|
+
"change.rename",
|
|
53
54
|
}
|
|
54
55
|
)
|
|
55
56
|
|
|
@@ -94,6 +95,9 @@ OPTIONAL_FIELDS = (
|
|
|
94
95
|
"orchestration_active_ms",
|
|
95
96
|
"wall_clock_ms",
|
|
96
97
|
"user_wait_ms",
|
|
98
|
+
"renamed_from",
|
|
99
|
+
"renamed_to",
|
|
100
|
+
"change_uuid",
|
|
97
101
|
)
|
|
98
102
|
|
|
99
103
|
_PROVENANCE_FIELDS = frozenset(
|
|
@@ -144,6 +148,10 @@ _EVENT_ALLOWED_FIELDS = {
|
|
|
144
148
|
}
|
|
145
149
|
)
|
|
146
150
|
| _PROVENANCE_FIELDS,
|
|
151
|
+
"change.rename": frozenset(
|
|
152
|
+
{"renamed_from", "renamed_to", "change_uuid", "note"}
|
|
153
|
+
)
|
|
154
|
+
| _PROVENANCE_FIELDS,
|
|
147
155
|
}
|
|
148
156
|
_EVENT_REQUIRED_FIELDS = {
|
|
149
157
|
"issue": ("severity",),
|
|
@@ -516,6 +524,9 @@ def append_event(
|
|
|
516
524
|
path: str | None = None,
|
|
517
525
|
run_id: str | None = None,
|
|
518
526
|
executor_tool: str | None = None,
|
|
527
|
+
renamed_from: str | None = None,
|
|
528
|
+
renamed_to: str | None = None,
|
|
529
|
+
change_uuid: str | None = None,
|
|
519
530
|
) -> dict[str, Any]:
|
|
520
531
|
"""Programmatic append API (retro §5.31 C5/T16).
|
|
521
532
|
|
|
@@ -547,6 +558,9 @@ def append_event(
|
|
|
547
558
|
target_field=None,
|
|
548
559
|
old_value_hash=None,
|
|
549
560
|
new_value_json=None,
|
|
561
|
+
renamed_from=renamed_from,
|
|
562
|
+
renamed_to=renamed_to,
|
|
563
|
+
change_uuid=change_uuid,
|
|
550
564
|
attempt=None,
|
|
551
565
|
executor_agent=None,
|
|
552
566
|
executor_model=None,
|