@hunter-harness/workflow-harness 0.2.64 → 0.2.66
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/contracts/workflow-policy.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 +6 -5
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/general/claude-code/scripts/harness_context.py +43 -25
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +22 -27
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +155 -16
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +31 -5
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +6 -5
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +43 -25
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +22 -27
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +155 -16
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +31 -5
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +6 -5
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/general/codex/scripts/harness_context.py +43 -25
- package/harness/bundles/general/codex/scripts/harness_gate.py +22 -27
- package/harness/bundles/general/codex/scripts/harness_ledger.py +155 -16
- package/harness/bundles/general/codex/scripts/harness_runtime.py +31 -5
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +6 -5
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/general/cursor/scripts/harness_context.py +43 -25
- package/harness/bundles/general/cursor/scripts/harness_gate.py +22 -27
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +155 -16
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +31 -5
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.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 +6 -5
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/java/claude-code/scripts/harness_context.py +43 -25
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +22 -27
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +155 -16
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +31 -5
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.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 +6 -5
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +43 -25
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +22 -27
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +155 -16
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +31 -5
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.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 +6 -5
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/java/codex/scripts/harness_context.py +43 -25
- package/harness/bundles/java/codex/scripts/harness_gate.py +22 -27
- package/harness/bundles/java/codex/scripts/harness_ledger.py +155 -16
- package/harness/bundles/java/codex/scripts/harness_runtime.py +31 -5
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.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 +6 -5
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +11 -5
- 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 +2 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
- 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 +320 -35
- package/harness/bundles/java/cursor/scripts/harness_context.py +43 -25
- package/harness/bundles/java/cursor/scripts/harness_gate.py +22 -27
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +155 -16
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +31 -5
- package/harness/manifests/general/claude-code.json +19 -19
- package/harness/manifests/general/codebuddy.json +19 -19
- package/harness/manifests/general/codex.json +19 -19
- package/harness/manifests/general/cursor.json +19 -19
- package/harness/manifests/java/claude-code.json +21 -21
- package/harness/manifests/java/codebuddy.json +21 -21
- package/harness/manifests/java/codex.json +21 -21
- package/harness/manifests/java/cursor.json +21 -21
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -981,28 +981,24 @@ def read_identity(skills_root: Path) -> dict[str, Any]:
|
|
|
981
981
|
return identity
|
|
982
982
|
|
|
983
983
|
|
|
984
|
+
def resolve_skills_root(raw: str | None) -> Path | None:
|
|
985
|
+
"""Resolve an explicit root or the installed bundle beside this script."""
|
|
986
|
+
if isinstance(raw, str) and raw.strip():
|
|
987
|
+
return Path(raw).expanduser().resolve()
|
|
988
|
+
adjacent = SCRIPTS_DIR.parent.resolve()
|
|
989
|
+
return adjacent if (adjacent / ".harness-build.json").is_file() else None
|
|
990
|
+
|
|
991
|
+
|
|
984
992
|
def change_code_root(change_dir: Path) -> Path:
|
|
985
993
|
project = change_dir.parents[2]
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
)
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
context = json.loads(context_path.read_text(encoding="utf-8"))
|
|
995
|
-
raw_root = next(
|
|
996
|
-
(str(context.get(field) or "").strip() for field in fields if context.get(field)),
|
|
997
|
-
"",
|
|
998
|
-
)
|
|
999
|
-
if raw_root:
|
|
1000
|
-
raw_path = Path(raw_root).expanduser()
|
|
1001
|
-
candidate = (raw_path if raw_path.is_absolute() else project / raw_path).resolve()
|
|
1002
|
-
if candidate.is_dir():
|
|
1003
|
-
return candidate
|
|
1004
|
-
except (OSError, ValueError, json.JSONDecodeError):
|
|
1005
|
-
pass
|
|
994
|
+
inferred = hl.infer_execution_project_root(change_dir)
|
|
995
|
+
if inferred is not None:
|
|
996
|
+
return inferred
|
|
997
|
+
if hl.declares_execution_worktree(change_dir):
|
|
998
|
+
raise ValueError(
|
|
999
|
+
"EXECUTION_WORKTREE_INVALID: declared worktree is missing, not a Git root, "
|
|
1000
|
+
"or belongs to another repository"
|
|
1001
|
+
)
|
|
1006
1002
|
return project
|
|
1007
1003
|
|
|
1008
1004
|
|
|
@@ -2388,16 +2384,15 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2388
2384
|
return emit_error("POLICY_LOAD_FAILED", str(exc), as_json=as_json)
|
|
2389
2385
|
|
|
2390
2386
|
executor_tool = args.executor_tool or os.environ.get("HUNTER_HARNESS_TOOL")
|
|
2391
|
-
|
|
2387
|
+
skills_root = resolve_skills_root(args.skills_root)
|
|
2388
|
+
if skills_root is None:
|
|
2392
2389
|
return emit_error(
|
|
2393
2390
|
"BUNDLE_IDENTITY_REQUIRED",
|
|
2394
2391
|
"--skills-root is required; refresh the selected Harness adapter if identity is missing",
|
|
2395
2392
|
as_json=as_json,
|
|
2396
2393
|
)
|
|
2397
2394
|
try:
|
|
2398
|
-
identity = validate_identity(
|
|
2399
|
-
project, Path(args.skills_root).expanduser().resolve(), executor_tool
|
|
2400
|
-
)
|
|
2395
|
+
identity = validate_identity(project, skills_root, executor_tool)
|
|
2401
2396
|
except (OSError, json.JSONDecodeError, ValueError) as exc:
|
|
2402
2397
|
return emit_error("BUNDLE_IDENTITY_INVALID", str(exc), as_json=as_json)
|
|
2403
2398
|
executor_tool = executor_tool or str(identity.get("adapter") or "") or None
|
|
@@ -2446,7 +2441,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2446
2441
|
run_id=run_id,
|
|
2447
2442
|
project=project,
|
|
2448
2443
|
execution_root=execution_root,
|
|
2449
|
-
skills_root=
|
|
2444
|
+
skills_root=skills_root,
|
|
2450
2445
|
)
|
|
2451
2446
|
except ValueError as exc:
|
|
2452
2447
|
if gate_soft_allowed(severity_mode, args.phase, "capsule"):
|
|
@@ -2532,7 +2527,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2532
2527
|
"projectRoot": str(project.resolve()),
|
|
2533
2528
|
"stateRoot": str(state_root),
|
|
2534
2529
|
"executionRoot": str(execution_root),
|
|
2535
|
-
"skillsRoot": str(
|
|
2530
|
+
"skillsRoot": str(skills_root.resolve()),
|
|
2536
2531
|
"repositoryId": hp.repository_identity(execution_root),
|
|
2537
2532
|
"baseCommit": base_commit or current_head,
|
|
2538
2533
|
"currentHead": current_head,
|
|
@@ -2574,7 +2569,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2574
2569
|
"projectRoot": str(project),
|
|
2575
2570
|
"stateRoot": capsule.get("stateRoot") if capsule else None,
|
|
2576
2571
|
"executionRoot": capsule.get("executionRoot") if capsule else str(execution_root),
|
|
2577
|
-
"skillsRoot": capsule.get("skillsRoot") if capsule else str(
|
|
2572
|
+
"skillsRoot": capsule.get("skillsRoot") if capsule else str(skills_root.resolve()),
|
|
2578
2573
|
"lease": claim.get("lease"),
|
|
2579
2574
|
"identity": identity,
|
|
2580
2575
|
"event": event_result,
|
|
@@ -412,6 +412,131 @@ def input_files_from_args(args: argparse.Namespace) -> tuple[list[str], Path | N
|
|
|
412
412
|
return resolve_input_files(selected, project_root), project_root
|
|
413
413
|
|
|
414
414
|
|
|
415
|
+
def infer_execution_project_root(change_dir: Path) -> Path | None:
|
|
416
|
+
"""Resolve the tree under test from trusted change metadata.
|
|
417
|
+
|
|
418
|
+
Worktree changes keep their durable contract under the main checkout while
|
|
419
|
+
product files live in the execution worktree. Requiring every caller to
|
|
420
|
+
reconstruct that split caused ledger commands to hash the main checkout or
|
|
421
|
+
fail repeatedly. Prefer the persisted execution root and otherwise leave
|
|
422
|
+
legacy callers unchanged.
|
|
423
|
+
"""
|
|
424
|
+
change_dir = change_dir.expanduser().resolve()
|
|
425
|
+
project_root = next(
|
|
426
|
+
(ancestor.parent for ancestor in change_dir.parents if ancestor.name == ".harness"),
|
|
427
|
+
None,
|
|
428
|
+
)
|
|
429
|
+
if project_root is None:
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
def git_common_dir(root: Path) -> Path | None:
|
|
433
|
+
process = subprocess.run(
|
|
434
|
+
["git", "-C", str(root), "rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
435
|
+
text=True,
|
|
436
|
+
stdout=subprocess.PIPE,
|
|
437
|
+
stderr=subprocess.PIPE,
|
|
438
|
+
check=False,
|
|
439
|
+
)
|
|
440
|
+
if process.returncode != 0 or not process.stdout.strip():
|
|
441
|
+
return None
|
|
442
|
+
raw = Path(process.stdout.strip()).expanduser()
|
|
443
|
+
return raw.resolve() if raw.is_absolute() else (root / raw).resolve()
|
|
444
|
+
|
|
445
|
+
project_common_dir = git_common_dir(project_root)
|
|
446
|
+
|
|
447
|
+
def trusted_worktree(candidate: Path) -> Path | None:
|
|
448
|
+
if not candidate.is_dir():
|
|
449
|
+
return None
|
|
450
|
+
process = subprocess.run(
|
|
451
|
+
["git", "-C", str(candidate), "rev-parse", "--show-toplevel"],
|
|
452
|
+
text=True,
|
|
453
|
+
stdout=subprocess.PIPE,
|
|
454
|
+
stderr=subprocess.PIPE,
|
|
455
|
+
check=False,
|
|
456
|
+
)
|
|
457
|
+
if process.returncode != 0 or not process.stdout.strip():
|
|
458
|
+
return None
|
|
459
|
+
top_level = Path(process.stdout.strip()).expanduser().resolve()
|
|
460
|
+
if top_level != candidate:
|
|
461
|
+
return None
|
|
462
|
+
# Origin URL + root commit identifies a repository lineage, not a
|
|
463
|
+
# linked checkout. Independent clones can share both. A real linked
|
|
464
|
+
# worktree must share the exact Git common directory with the main tree.
|
|
465
|
+
if project_common_dir is None or git_common_dir(top_level) != project_common_dir:
|
|
466
|
+
return None
|
|
467
|
+
return top_level
|
|
468
|
+
|
|
469
|
+
candidates = (
|
|
470
|
+
(
|
|
471
|
+
change_dir / "meta" / "change-context.json",
|
|
472
|
+
("worktreePath", "path", "worktreeRoot"),
|
|
473
|
+
),
|
|
474
|
+
(
|
|
475
|
+
change_dir / "meta" / "worktree.json",
|
|
476
|
+
("worktreePath", "path", "worktreeRoot"),
|
|
477
|
+
),
|
|
478
|
+
)
|
|
479
|
+
for metadata_path, keys in candidates:
|
|
480
|
+
if not metadata_path.is_file():
|
|
481
|
+
continue
|
|
482
|
+
try:
|
|
483
|
+
payload = json.loads(metadata_path.read_text(encoding="utf-8-sig"))
|
|
484
|
+
except (OSError, json.JSONDecodeError):
|
|
485
|
+
continue
|
|
486
|
+
if not isinstance(payload, dict):
|
|
487
|
+
continue
|
|
488
|
+
for key in keys:
|
|
489
|
+
raw = payload.get(key)
|
|
490
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
491
|
+
continue
|
|
492
|
+
raw_path = Path(raw).expanduser()
|
|
493
|
+
candidate = (
|
|
494
|
+
raw_path.resolve()
|
|
495
|
+
if raw_path.is_absolute()
|
|
496
|
+
else (project_root / raw_path).resolve()
|
|
497
|
+
)
|
|
498
|
+
if key == "worktreeRoot" and candidate.name != change_dir.name:
|
|
499
|
+
nested = (candidate / change_dir.name).resolve()
|
|
500
|
+
trusted_nested = trusted_worktree(nested)
|
|
501
|
+
if trusted_nested is not None:
|
|
502
|
+
return trusted_nested
|
|
503
|
+
if trusted_worktree(candidate) is None:
|
|
504
|
+
continue
|
|
505
|
+
trusted_candidate = trusted_worktree(candidate)
|
|
506
|
+
if trusted_candidate is not None:
|
|
507
|
+
return trusted_candidate
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def declares_execution_worktree(change_dir: Path) -> bool:
|
|
512
|
+
"""Return whether persisted metadata explicitly selects another checkout."""
|
|
513
|
+
for metadata_path in (
|
|
514
|
+
change_dir / "meta" / "change-context.json",
|
|
515
|
+
change_dir / "meta" / "worktree.json",
|
|
516
|
+
):
|
|
517
|
+
if not metadata_path.is_file():
|
|
518
|
+
continue
|
|
519
|
+
try:
|
|
520
|
+
payload = json.loads(metadata_path.read_text(encoding="utf-8-sig"))
|
|
521
|
+
except (OSError, json.JSONDecodeError):
|
|
522
|
+
continue
|
|
523
|
+
if isinstance(payload, dict) and any(
|
|
524
|
+
isinstance(payload.get(key), str) and str(payload[key]).strip()
|
|
525
|
+
for key in ("worktreePath", "path", "worktreeRoot")
|
|
526
|
+
):
|
|
527
|
+
return True
|
|
528
|
+
return False
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def apply_inferred_project_root(args: argparse.Namespace, change_dir: Path) -> None:
|
|
532
|
+
"""Fill an omitted --project without overriding an explicit caller choice."""
|
|
533
|
+
if getattr(args, "project", None):
|
|
534
|
+
return
|
|
535
|
+
inferred = infer_execution_project_root(change_dir)
|
|
536
|
+
if inferred is not None:
|
|
537
|
+
args.project = str(inferred)
|
|
538
|
+
|
|
539
|
+
|
|
415
540
|
def sha256_file(path: Path) -> str:
|
|
416
541
|
digest = hashlib.sha256()
|
|
417
542
|
with path.open("rb") as handle:
|
|
@@ -1774,20 +1899,10 @@ def worktree_ready(ledger: dict[str, Any], change_dir: Path) -> bool:
|
|
|
1774
1899
|
root = ledger.get("worktreeRoot")
|
|
1775
1900
|
if root is not None and _nonempty_str(root):
|
|
1776
1901
|
return True
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
data = json.loads(meta.read_text(encoding="utf-8-sig"))
|
|
1782
|
-
except (OSError, json.JSONDecodeError):
|
|
1783
|
-
return False
|
|
1784
|
-
if not isinstance(data, dict):
|
|
1785
|
-
return False
|
|
1786
|
-
if data.get("requested") is True and data.get("created") is True:
|
|
1787
|
-
return True
|
|
1788
|
-
if _nonempty_str(data.get("path")) or _nonempty_str(data.get("worktreeRoot")):
|
|
1789
|
-
return True
|
|
1790
|
-
return False
|
|
1902
|
+
# Metadata aliases are accepted only when they resolve to a real linked
|
|
1903
|
+
# worktree of this repository. This keeps install reuse aligned with the
|
|
1904
|
+
# execution root used by context and gate, including `worktreePath`.
|
|
1905
|
+
return infer_execution_project_root(change_dir) is not None
|
|
1791
1906
|
|
|
1792
1907
|
|
|
1793
1908
|
def decide_can_reuse(
|
|
@@ -2242,6 +2357,7 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
2242
2357
|
verbose = bool(getattr(args, "verbose", False))
|
|
2243
2358
|
verification = args.verification
|
|
2244
2359
|
change_dir = resolve_path(args.change_dir)
|
|
2360
|
+
apply_inferred_project_root(args, change_dir)
|
|
2245
2361
|
ownership_check = frozen_ownership_check(change_dir)
|
|
2246
2362
|
if not ownership_check.get("ok"):
|
|
2247
2363
|
return emit_error(
|
|
@@ -2359,6 +2475,9 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
2359
2475
|
return emit_error(f"can-reuse failed: {exc}", as_json=as_json)
|
|
2360
2476
|
|
|
2361
2477
|
payload = _attach_invalidation_code(payload)
|
|
2478
|
+
payload["resolvedProjectRoot"] = (
|
|
2479
|
+
str(project_root) if project_root is not None else None
|
|
2480
|
+
)
|
|
2362
2481
|
emit_compact_or_verbose(
|
|
2363
2482
|
payload, as_json=as_json, verbose=verbose,
|
|
2364
2483
|
compact_fn=_compact_can_reuse_payload,
|
|
@@ -2370,6 +2489,7 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2370
2489
|
as_json = bool(args.json)
|
|
2371
2490
|
verbose = bool(getattr(args, "verbose", False))
|
|
2372
2491
|
change_dir = resolve_path(args.change_dir)
|
|
2492
|
+
apply_inferred_project_root(args, change_dir)
|
|
2373
2493
|
ownership_check = frozen_ownership_check(change_dir)
|
|
2374
2494
|
if not ownership_check.get("ok"):
|
|
2375
2495
|
return emit_error(
|
|
@@ -2836,11 +2956,26 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2836
2956
|
def cmd_diff_hash(args: argparse.Namespace) -> int:
|
|
2837
2957
|
as_json = bool(args.json)
|
|
2838
2958
|
repo_raw = getattr(args, "repo", None)
|
|
2839
|
-
repo = Path(repo_raw).expanduser().resolve() if repo_raw else Path.cwd().resolve()
|
|
2840
2959
|
base = getattr(args, "base", None)
|
|
2841
2960
|
change_dir_raw = getattr(args, "change_dir", None)
|
|
2842
2961
|
try:
|
|
2843
2962
|
change_dir = resolve_path(change_dir_raw) if change_dir_raw else None
|
|
2963
|
+
repo = Path(repo_raw).expanduser().resolve() if repo_raw else Path.cwd().resolve()
|
|
2964
|
+
# Historical skill text passed ``--repo .`` even when the active change
|
|
2965
|
+
# ran in a linked worktree. Treat that shorthand like the default and
|
|
2966
|
+
# recover the authoritative execution root captured during Plan.
|
|
2967
|
+
repo_hint = str(repo_raw or "").strip().replace("\\", "/")
|
|
2968
|
+
if change_dir is not None and repo_hint in {"", ".", "./"}:
|
|
2969
|
+
inferred_repo = infer_execution_project_root(change_dir)
|
|
2970
|
+
if inferred_repo is not None:
|
|
2971
|
+
repo = inferred_repo
|
|
2972
|
+
elif declares_execution_worktree(change_dir):
|
|
2973
|
+
return emit_error(
|
|
2974
|
+
"change metadata declares an execution worktree that is missing or belongs "
|
|
2975
|
+
"to another repository",
|
|
2976
|
+
as_json=as_json,
|
|
2977
|
+
error_code="EXECUTION_WORKTREE_INVALID",
|
|
2978
|
+
)
|
|
2844
2979
|
if change_dir is not None and _contract_is_v2(change_dir):
|
|
2845
2980
|
resolved_base = str(base or "").strip()
|
|
2846
2981
|
if not resolved_base:
|
|
@@ -3086,7 +3221,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3086
3221
|
parents=[shared_json],
|
|
3087
3222
|
help="compute commit-invariant byte-level diff hash for a repo",
|
|
3088
3223
|
)
|
|
3089
|
-
p_diff.add_argument(
|
|
3224
|
+
p_diff.add_argument(
|
|
3225
|
+
"--repo",
|
|
3226
|
+
default=None,
|
|
3227
|
+
help="repo root (default: change worktree when recorded, otherwise cwd)",
|
|
3228
|
+
)
|
|
3090
3229
|
p_diff.add_argument("--base", default=None, help="base commit (default: root commit)")
|
|
3091
3230
|
p_diff.add_argument(
|
|
3092
3231
|
"--change-dir",
|
|
@@ -33,11 +33,15 @@ SCHEMA_VERSION = 1
|
|
|
33
33
|
RUN_SESSION_SCHEMA_VERSION = 1
|
|
34
34
|
RUN_TERMINAL_STATUSES = {"OK", "FAIL", "INCOMPLETE", "CANCELLED"}
|
|
35
35
|
_CHANGE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
|
36
|
-
SECRET_SCAN_RULES_VERSION = "secret-scan-
|
|
36
|
+
SECRET_SCAN_RULES_VERSION = "secret-scan-v2"
|
|
37
37
|
SECRET_SCAN_RECEIPT_REL = Path("meta") / "secret-scan-receipt.json"
|
|
38
38
|
_SENSITIVE_ASSIGNMENT = re.compile(
|
|
39
|
-
rb"(?i)[\"']?(?:password|passwd|token|secret|cookie|
|
|
40
|
-
rb"[\"']?\s*[:=]\s*[\"']?[^\s\"']{4,}"
|
|
39
|
+
rb"(?i)[\"']?(?:password|passwd|token|secret|cookie|database[_-]?url)"
|
|
40
|
+
rb"[\"']?\s*[:=]\s*[\"']?(?P<value>[^\s\"']{4,})"
|
|
41
|
+
)
|
|
42
|
+
_AUTHORIZATION_ASSIGNMENT = re.compile(
|
|
43
|
+
rb"(?i)[\"']?authorization[\"']?\s*[:=]\s*[\"']?"
|
|
44
|
+
rb"(?:(?:bearer|basic)\s+)?(?P<value>[^\s\"']{4,})"
|
|
41
45
|
)
|
|
42
46
|
_ADAPTERS = {
|
|
43
47
|
"claude-code": (".worktrees", "harness/"),
|
|
@@ -193,8 +197,16 @@ def _sensitive_candidates(root: Path) -> list[dict[str, Any]]:
|
|
|
193
197
|
raw = path.read_bytes()
|
|
194
198
|
except OSError:
|
|
195
199
|
continue
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
finding = next(
|
|
201
|
+
(
|
|
202
|
+
match
|
|
203
|
+
for pattern in (_SENSITIVE_ASSIGNMENT, _AUTHORIZATION_ASSIGNMENT)
|
|
204
|
+
for match in pattern.finditer(raw)
|
|
205
|
+
if not _is_sensitive_placeholder(match.group("value"))
|
|
206
|
+
),
|
|
207
|
+
None,
|
|
208
|
+
)
|
|
209
|
+
if finding is None:
|
|
198
210
|
continue
|
|
199
211
|
candidates.append({
|
|
200
212
|
"path": path.relative_to(root).as_posix(),
|
|
@@ -204,6 +216,20 @@ def _sensitive_candidates(root: Path) -> list[dict[str, Any]]:
|
|
|
204
216
|
return candidates
|
|
205
217
|
|
|
206
218
|
|
|
219
|
+
def _is_sensitive_placeholder(value: bytes) -> bool:
|
|
220
|
+
text = value.decode("utf-8", errors="ignore").strip().strip("`'").strip()
|
|
221
|
+
return bool(
|
|
222
|
+
re.fullmatch(r"<[^<>]+>", text)
|
|
223
|
+
or re.fullmatch(r"\$\{[A-Za-z_][A-Za-z0-9_]*\}", text)
|
|
224
|
+
or re.fullmatch(r"\{\{[^{}]+\}\}", text)
|
|
225
|
+
or re.fullmatch(
|
|
226
|
+
r"(?i)(?:example|placeholder|redacted|masked|bearer|basic|"
|
|
227
|
+
r"your[-_][A-Za-z0-9_-]+|x{4,})",
|
|
228
|
+
text,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
207
233
|
def publishable_tree_digest(root: Path) -> str:
|
|
208
234
|
"""Public digest helper shared by the archive publication gate."""
|
|
209
235
|
return _publishable_tree_digest(root)
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"artifacts": ["summary-data", "archive-manifest"],
|
|
111
111
|
"events": ["phase.start", "phase.end", "artifact"],
|
|
112
112
|
"allowedInteractions": [],
|
|
113
|
-
"capabilities": [
|
|
113
|
+
"capabilities": []
|
|
114
114
|
},
|
|
115
115
|
"harness-sync": {
|
|
116
116
|
"phase": "sync",
|
|
@@ -3,7 +3,7 @@ name: harness-archive
|
|
|
3
3
|
description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用
|
|
4
4
|
/harness-archive 时使用;不得被其他阶段 skill 自动接续触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -12,8 +12,8 @@ description: 分析现有代码库并生成 .harness/codebase/map/
|
|
|
12
12
|
|
|
13
13
|
## 核心边界
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
- **禁止** `.planning/`、Git 写操作、修改 `.codegraph/`、自动 commit
|
|
15
|
+
- **内容只写** `.harness/codebase/map/`、`map-summary.md`、`map-manifest.json`、`.harness/reports/codebase-map-*.md`;平台同步只更新 Harness 协议状态
|
|
16
|
+
- **禁止** `.planning/`、Git 写操作、修改 `.codegraph/`、自动 commit 或 Git push
|
|
17
17
|
- `harness-sync` 只**建议**刷新,不自动跑全量 map
|
|
18
18
|
|
|
19
19
|
## When to Use
|
|
@@ -44,6 +44,7 @@ description: 分析现有代码库并生成 .harness/codebase/map/
|
|
|
44
44
|
| 6 | 写 summary + manifest(schema → `templates/map-manifest.schema.json`) |
|
|
45
45
|
| 7 | 更新 `.harness/context-index.json` codebase 入口(若存在) |
|
|
46
46
|
| 8 | 控制台 + `.harness/reports/codebase-map-*.md` 报告 |
|
|
47
|
+
| 9 | 项目已绑定平台时,运行 `npx hunter-harness push --yes --non-interactive --json` 上传受控规则与架构快照;失败只报告“平台同步待重试”,不回滚本地地图 |
|
|
47
48
|
|
|
48
49
|
逐步细节、frontmatter 字段、git 允许列表 → `reference.md` / `checklist.md`
|
|
49
50
|
|
|
@@ -69,7 +70,7 @@ description: 分析现有代码库并生成 .harness/codebase/map/
|
|
|
69
70
|
|---|------|
|
|
70
71
|
| 1 | 唯一输出 `.harness/codebase/map/` |
|
|
71
72
|
| 2 | `generated_reviewable`,不建议手工改 map 正文 |
|
|
72
|
-
| 3 |
|
|
73
|
+
| 3 | 不做 Git 写操作、不改业务代码;受控平台上传不等于 Git push |
|
|
73
74
|
| 4 | 不管理 CodeGraph |
|
|
74
75
|
| 5 | 敏感信息脱敏 → `sensitive-info-protocol` |
|
|
75
76
|
| 6 | 证据化输出 → `evidence-based-reporting-protocol` |
|
|
@@ -77,7 +78,7 @@ description: 分析现有代码库并生成 .harness/codebase/map/
|
|
|
77
78
|
|
|
78
79
|
## Output Format
|
|
79
80
|
|
|
80
|
-
模式、范围、7/7 文档路径与行数、manifest/summary、context-index
|
|
81
|
+
模式、范围、7/7 文档路径与行数、manifest/summary、context-index 状态、平台同步状态、WARN/FAIL → `reference.md` 报告模板
|
|
81
82
|
|
|
82
83
|
## 渐进披露
|
|
83
84
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-ingest
|
|
3
3
|
description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -27,15 +27,21 @@ description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包
|
|
|
27
27
|
|
|
28
28
|
## Normal Workflow
|
|
29
29
|
|
|
30
|
-
1. `
|
|
30
|
+
1. 先读取最近归档操作记录中的 `archiveRemote`,以及本地状态目录
|
|
31
|
+
`.harness/state/local/archive-packages/<change-key>.remote.json`。若 `archiveStatus=durable` 且
|
|
32
|
+
`knowledgeStatus=ready`,直接报告已完成,禁止重新打包或上传。
|
|
33
|
+
2. 若存在 `.harness/state/local/archive-packages/<change-key>.zip`,只重试该包;
|
|
34
|
+
禁止搜索实现源码、调用 Python 内部函数或手工重新拼包。
|
|
35
|
+
3. 只有旧归档没有 ZIP/回执时,调用公开命令重新生成并上传;`harness-archive`
|
|
36
|
+
的 finalize 正常情况下会生成确定性 ZIP,并调用:
|
|
31
37
|
|
|
32
38
|
```powershell
|
|
33
39
|
powershell.exe -Command "npx hunter-harness archive upload --file '<archive.zip>' --change-key '<change-key>' --yes --non-interactive --json"
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
4. 检查响应中的包哈希、服务端保存状态和 `knowledge_status`。
|
|
43
|
+
5. 只有服务端确认原 ZIP 已持久保存且知识状态为 ready,才删除本地待上传 ZIP。
|
|
44
|
+
6. 上传或 ingest 失败时保留 ZIP 与失败收据;修复连接后重试同一个 ZIP,不重新拼散文件。
|
|
39
45
|
|
|
40
46
|
## Ownership Rules
|
|
41
47
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-query
|
|
3
3
|
description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
|
|
|
4
4
|
.harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在
|
|
5
5
|
test 结束后自动接续执行。
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-run
|
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
|
|
4
4
|
/harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-run — 需求编码
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -59,7 +59,7 @@ description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编
|
|
|
59
59
|
|
|
60
60
|
## Workflow 概要
|
|
61
61
|
|
|
62
|
-
0. 加载上下文:普通 Run 先 `harness_context.py prepare --phase run --executor <tool> [--change <id>] --json`,再 **`harness_context.py begin --phase run --change <id> --executor <tool> --json`** 校验交接,最后运行 **`harness_gate.py begin --phase run --change <id
|
|
62
|
+
0. 加载上下文:普通 Run 先 `harness_context.py prepare --phase run --executor <tool> [--change <id>] --json`,再 **`harness_context.py begin --phase run --change <id> --executor <tool> --json`** 校验交接,最后运行 **`harness_gate.py begin --phase run --change <id>`**。`harness_gate.py` 会从当前已安装适配器自动识别 skills root;只有执行复制到别处的脚本时才显式传 `--skills-root`。`--fixback` 不得拼装这些底层步骤,必须只调用一次 `harness_fixback.py launch-review --project . --change <id> --change-dir <change-dir> --executor <tool> --skills-root <skills-root> --product-identity <当前产品身份> --json`:该命令会先筛选结构化评审项,再原子选择 Fixback 分支、确认上下文、取得 Run 门禁并创建已填充批次。返回 `FIXBACK_NOTHING_TO_APPLY` 时直接报告“本轮评审没有需要执行的代码修复”并停止;返回阻塞码时按 `recoveryAction` 停止,不搜索实现、不试探其他参数、不创建空批次。禁止手写 `events.ndjson` / `phase.end`。已连接平台时 begin 会 best-effort 补传事件,失败只告警。
|
|
63
63
|
0.5. **测试基础设施探测**(先写 `CHECKING`,四项证据齐备后再结论)→ `reference.md` Step 0.5;测试基线已由上一步 gate begin 内部建立,不得再次执行 guard begin
|
|
64
64
|
1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`(高风险 + reviewer 预检可用)
|
|
65
65
|
2. 构建验证 + **仅**通过 `harness_ledger.py record` 写 ledger(禁止 Write/Edit `verification-ledger.json`);若本阶段创建/删除了清单、锁文件、源码根或改变技术栈,最后一次产品编辑后、写账本与关门前必须执行 `harness_preflight.py detect --project . --json` 刷新 build profile。`record --project . --profile-input <key>` 会对缺失/陈旧 profile 自动检测并从同一 target 推导 scope、coverage、规范命令和输入闭包;不得再手填另一套身份。`diff-hash --change-dir` 纳入 ignored tests → `reference.md` Step 2c
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
|
|
4
4
|
/harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -54,7 +54,7 @@ worktree 合并前必须运行 `harness_change.py integration-lock acquire --run
|
|
|
54
54
|
|
|
55
55
|
### 提交流程(步骤 0–7)
|
|
56
56
|
|
|
57
|
-
0. **启动准备** — `harness_context.py prepare --phase submit --executor <tool> [--change <id>] --json` 确定唯一变更与 executionRoot;`harness_context.py begin --phase submit --change <id> --executor <tool> --json` 校验 review→submit receipt;**`harness_gate.py begin --phase submit --change <id>`**;读 ledger,以 `harness_ledger.py diff-hash --repo
|
|
57
|
+
0. **启动准备** — `harness_context.py prepare --phase submit --executor <tool> [--change <id>] --json` 确定唯一变更与 executionRoot;`harness_context.py begin --phase submit --change <id> --executor <tool> --json` 校验 review→submit receipt;**`harness_gate.py begin --phase submit --change <id>`**;读 ledger,以 `harness_ledger.py diff-hash --repo <executionRoot> --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 计算 diffHash + post-test 7 类分类(`executionRoot` 必须直接取准备回执;禁止手写 ledger / 手工 phase.end)
|
|
58
58
|
1. **合并最新代码** — 主目录与 worktree 均**不在业务工作区 stash/pull**;远端同步由合并段 integration transaction 在隔离 integration worktree 内完成(见「worktree 合并流程」);**正常路径禁止 `git stash` / `stash pop`**
|
|
59
59
|
2. **最终验证** — ledger 复用优先;提交前只调用 `can-reuse --project . --profile-input unitTestFull --command <profile 规范命令>`。不得传入另一套 `--files`,不得把 runner 包装说明写进 `command`。`reuse=true` 时禁止重跑;只有真实输入、依赖、工具链或环境身份变化时才执行一次同一 profile 验证,并只登记一条结果。不要读取 ledger/archive 实现源码或临时编写散列脚本排查参数。
|
|
60
60
|
- 无远端 CI 且 gate-policy 未强制 remote provider 时,验证通过/复用后运行 `harness_archive.py certify-local --change-dir ... --project . --json`,从同一 ledger 生成 `local-reproducible` 产品候选收据;该命令不执行测试。
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, remote knowledge ownership, instruction entrypoints,
|
|
5
5
|
config origins, or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-test
|
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。仅当用户显式调用 /harness-test
|
|
4
4
|
时使用;不得在 run 结束后自动接续执行。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=bf4f116fcd0ff4f2; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-test — 测试执行
|
|
8
8
|
|
|
9
9
|
## Purpose
|