@hunter-harness/workflow-harness 0.2.63 → 0.2.65

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.
Files changed (154) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -1
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +6 -5
  5. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +11 -5
  6. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
  8. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-run/SKILL.md +2 -2
  10. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  11. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/scripts/harness_archive.py +320 -35
  14. package/harness/bundles/general/claude-code/scripts/harness_context.py +43 -25
  15. package/harness/bundles/general/claude-code/scripts/harness_gate.py +22 -27
  16. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +155 -16
  17. package/harness/bundles/general/claude-code/scripts/harness_runtime.py +31 -5
  18. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  19. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
  20. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  21. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +6 -5
  22. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +11 -5
  23. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
  25. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +2 -2
  27. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  28. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +320 -35
  31. package/harness/bundles/general/codebuddy/scripts/harness_context.py +43 -25
  32. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +22 -27
  33. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +155 -16
  34. package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +31 -5
  35. package/harness/bundles/general/codex/.harness-build.json +1 -1
  36. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
  37. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  38. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +6 -5
  39. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +11 -5
  40. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  41. package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
  42. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  43. package/harness/bundles/general/codex/harness-run/SKILL.md +2 -2
  44. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  45. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/scripts/harness_archive.py +320 -35
  48. package/harness/bundles/general/codex/scripts/harness_context.py +43 -25
  49. package/harness/bundles/general/codex/scripts/harness_gate.py +22 -27
  50. package/harness/bundles/general/codex/scripts/harness_ledger.py +155 -16
  51. package/harness/bundles/general/codex/scripts/harness_runtime.py +31 -5
  52. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  53. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
  54. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  55. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +6 -5
  56. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +11 -5
  57. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  58. package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
  59. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-run/SKILL.md +2 -2
  61. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  62. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/scripts/harness_archive.py +320 -35
  65. package/harness/bundles/general/cursor/scripts/harness_context.py +43 -25
  66. package/harness/bundles/general/cursor/scripts/harness_gate.py +22 -27
  67. package/harness/bundles/general/cursor/scripts/harness_ledger.py +155 -16
  68. package/harness/bundles/general/cursor/scripts/harness_runtime.py +31 -5
  69. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  70. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -1
  71. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  72. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  73. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +6 -5
  74. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +11 -5
  75. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  76. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  77. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
  78. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  79. package/harness/bundles/java/claude-code/harness-run/SKILL.md +2 -2
  80. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  81. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  82. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  83. package/harness/bundles/java/claude-code/scripts/harness_archive.py +320 -35
  84. package/harness/bundles/java/claude-code/scripts/harness_context.py +43 -25
  85. package/harness/bundles/java/claude-code/scripts/harness_gate.py +22 -27
  86. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +155 -16
  87. package/harness/bundles/java/claude-code/scripts/harness_runtime.py +31 -5
  88. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  89. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -1
  90. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  91. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  92. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +6 -5
  93. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +11 -5
  94. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  95. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  96. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
  97. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  98. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +2 -2
  99. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  100. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  101. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  102. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +320 -35
  103. package/harness/bundles/java/codebuddy/scripts/harness_context.py +43 -25
  104. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +22 -27
  105. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +155 -16
  106. package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +31 -5
  107. package/harness/bundles/java/codex/.harness-build.json +1 -1
  108. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -1
  109. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  110. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  111. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +6 -5
  112. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +11 -5
  113. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  114. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  115. package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
  116. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  117. package/harness/bundles/java/codex/harness-run/SKILL.md +2 -2
  118. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  119. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  120. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  121. package/harness/bundles/java/codex/scripts/harness_archive.py +320 -35
  122. package/harness/bundles/java/codex/scripts/harness_context.py +43 -25
  123. package/harness/bundles/java/codex/scripts/harness_gate.py +22 -27
  124. package/harness/bundles/java/codex/scripts/harness_ledger.py +155 -16
  125. package/harness/bundles/java/codex/scripts/harness_runtime.py +31 -5
  126. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  127. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -1
  128. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  129. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  130. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +6 -5
  131. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +11 -5
  132. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  133. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  134. package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
  135. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  136. package/harness/bundles/java/cursor/harness-run/SKILL.md +2 -2
  137. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  138. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  139. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  140. package/harness/bundles/java/cursor/scripts/harness_archive.py +320 -35
  141. package/harness/bundles/java/cursor/scripts/harness_context.py +43 -25
  142. package/harness/bundles/java/cursor/scripts/harness_gate.py +22 -27
  143. package/harness/bundles/java/cursor/scripts/harness_ledger.py +155 -16
  144. package/harness/bundles/java/cursor/scripts/harness_runtime.py +31 -5
  145. package/harness/manifests/general/claude-code.json +19 -19
  146. package/harness/manifests/general/codebuddy.json +19 -19
  147. package/harness/manifests/general/codex.json +19 -19
  148. package/harness/manifests/general/cursor.json +19 -19
  149. package/harness/manifests/java/claude-code.json +21 -21
  150. package/harness/manifests/java/codebuddy.json +21 -21
  151. package/harness/manifests/java/codex.json +21 -21
  152. package/harness/manifests/java/cursor.json +21 -21
  153. package/hunter-workflow-family.json +5 -5
  154. 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
- metadata = (
987
- (change_dir / "meta" / "change-context.json", ("worktreeRoot",)),
988
- (change_dir / "meta" / "worktree.json", ("worktreePath", "path")),
989
- )
990
- for context_path, fields in metadata:
991
- if not context_path.is_file():
992
- continue
993
- try:
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
- if not args.skills_root:
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=Path(args.skills_root).expanduser(),
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(Path(args.skills_root).expanduser().resolve()),
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(Path(args.skills_root).resolve()),
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
- meta = change_dir / "meta" / "worktree.json"
1778
- if not meta.is_file():
1779
- return False
1780
- try:
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("--repo", default=None, help="repo root (default: cwd)")
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-v1"
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|authorization|database[_-]?url)"
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
- match = _SENSITIVE_ASSIGNMENT.search(raw)
197
- if match is None:
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)
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "codex",
5
- "coreHash": "b737565b4074dee2"
5
+ "coreHash": "81932532e802506f"
6
6
  }
@@ -110,7 +110,7 @@
110
110
  "artifacts": ["summary-data", "archive-manifest"],
111
111
  "events": ["phase.start", "phase.end", "artifact"],
112
112
  "allowedInteractions": [],
113
- "capabilities": ["gate-begin", "gate-close"]
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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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
- - **只写** `.harness/codebase/map/`、`map-summary.md`、`map-manifest.json`、`.harness/reports/codebase-map-*.md`
16
- - **禁止** `.planning/`、Git 写操作、修改 `.codegraph/`、自动 commit/push
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 | Git 写操作、不改业务代码 |
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 状态、WARN/FAIL → `reference.md` 报告模板
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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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. `harness-archive` 的 finalize 生成确定性 ZIP,并调用:
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
- 2. 检查响应中的包哈希、服务端保存状态和 `knowledge_status`。
37
- 3. 只有服务端确认原 ZIP 已持久保存且知识状态为 ready,才删除本地待上传 ZIP。
38
- 4. 上传或 ingest 失败时保留 ZIP 与失败收据;修复连接后重试同一个 ZIP,不重新拼散文件。
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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; do not edit -->
6
6
  # harness-knowledge-query
7
7
 
8
8
  项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
@@ -3,14 +3,14 @@ name: harness-plan
3
3
  description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
4
4
  Harness 规划阶段时使用;不得因用户描述需求就自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; do not edit -->
7
7
  # harness-plan — 需求规划
8
8
 
9
9
  ## Purpose
10
10
 
11
11
  确定 change 后必须运行 `python <skills-root>/scripts/harness_gate.py classify --change <id> --stage plan --json`,并把脚本返回的 risk tier、默认阶段、条件阶段和必需验证写入计划;不得凭模型印象另建风险分级。
12
12
 
13
- 需求 → 设计文档 → 任务拆分 → 测试场景表(编码/测试唯一真相源)。项目已绑定远端平台时先执行一次 `harness-knowledge-query`;远端不可用则记录 issue 并继续,不做本地回退。
13
+ 需求 → 设计文档 → 任务拆分 → 测试场景表(编码/测试唯一真相源)。项目已绑定远端平台时,阶段 1 只执行下表中的远端查询命令;远端不可用则记录 issue 并继续,不做本地回退。
14
14
 
15
15
  ## When to Use
16
16
 
@@ -68,7 +68,7 @@ description: 将需求转化为设计文档+实施计划+完整测试场景表
68
68
  | 0 | 用当前解释器运行 `harness_runtime.py doctor`,后续消费绝对 argv;git status;脏工作区 → baseline 隔离 + `decision`,不询问 |
69
69
  | 0.5 | 确定英文 `change-name` 后,根据需求生成一次简洁的中文展示标题(建议 6~24 个可见字符,保留必要产品名),执行 `harness_context.py prepare --phase plan --executor <tool> [--change <id>] --title "<中文标题>" --json`;英文名继续作为目录与稳定标识。立即运行一次 `harness_state.py capture --project . --change-dir "<executionRoot>" --json`,把此时 HEAD 固定为不可变 `changeBase`。以其唯一 change/executionRoot 初始化 plan-run-id 与 attempt(首次为 1),用同一身份追加 `phase.start`;从第一条知识查询起保留事件证据,并在 finalizer 中复用该身份 |
70
70
  | 0.6 | 使用 `classify` 返回的默认阶段和项目能力生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <id> --phases "plan,run,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,run,archive`。省略项由脚本写入 `skippedPhases`,不得伪造阶段事件 |
71
- | 1 | `harness-knowledge-query` 单次远端 query(失败记 `issue`,不建立本地索引或离线回退) |
71
+ | 1 | 直接执行一次 `npx hunter-harness knowledge query "<用户需求原文>" --limit 10 --json`。这是唯一执行入口,不扫描技能目录、不查找其他脚本;失败记 `issue` 并继续,不建立本地索引或离线回退 |
72
72
  | 2 | 歧义优先检查 + 复杂度分级;先确认会改变实现方向的语义歧义 |
73
73
  | 3 | 按复杂度执行有预算的代码探索;简单修复不得扩散到无关模块 |
74
74
  | 4 | **设计审批包** blocking user confirmation;确认事件早于 approved 设计文档和 `meta/worktree.json` |
@@ -102,7 +102,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
102
102
  | 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
103
103
  | 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
104
104
  | Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
105
- | 知识查询 | 0.5 失败不得假装已读历史 |
105
+ | 知识查询 | 阶段 1 失败不得假装已读历史,也不得改用本地索引或其他执行入口 |
106
106
  | 歧义优先检查 | 否定、对比、动作对象或范围存在多种合理解释时,最小取证后先给推荐理解并一次一问;确认前不深挖错误方向 |
107
107
  | 简单修复探索预算 | 预计不超过 2 个代码文件、且不涉及认证/安全/迁移/并发/API 契约重设时,最多 1 次合并 CodeGraph 查询 + 1 次定向补查、1 个用户澄清问题;无关发现只记非阻断说明 |
108
108
  | 精简产物 | 简单修复只保留实现所需的设计、任务、边界和测试;禁止在 spec/plan/detail/scenarios 中重复同一背景和结论 |
@@ -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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
7
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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>`**。`--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 补传事件,失败只告警。
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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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 . --base <baseCommit> --change-dir ".harness/changes/<change-name>" --json` 计算 diffHash + post-test 7 类分类(禁止手写 ledger / 手工 phase.end)
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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
7
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; 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=b737565b4074dee2; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=81932532e802506f; overlay=none; agent=codex; do not edit -->
7
7
  # harness-test — 测试执行
8
8
 
9
9
  ## Purpose