@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.
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"profile": "general",
|
|
4
4
|
"adapter": "claude-code",
|
|
5
|
-
"bundle_version": "0.2.
|
|
5
|
+
"bundle_version": "0.2.54",
|
|
6
6
|
"requires": {
|
|
7
|
-
"minimumCliVersion": "0.2.
|
|
7
|
+
"minimumCliVersion": "0.2.74",
|
|
8
8
|
"capabilities": [
|
|
9
9
|
"sync@2",
|
|
10
10
|
"rules-sync@1",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
|
25
25
|
"path": ".harness-build.json",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "a7cbdb7be11d56b509b88d5406209901dc1ae27ed0bab9f7baeb9910043efa33"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"path": "agents/harness-evaluator.md",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
"path": "contracts/workflow-policy.json",
|
|
82
|
-
"sha256": "
|
|
82
|
+
"sha256": "3b671d438a687d800879863590ff04bd953c7d3eac6865bda3d85cdfc26fbc52"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"path": "harness-archive/checklist.md",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
"path": "harness-archive/SKILL.md",
|
|
98
|
-
"sha256": "
|
|
98
|
+
"sha256": "8436d5af255e01e178a4920a176bdc1192e429b637c2c1d1959e7f168bd98f25"
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
"path": "harness-archive/templates/summary-data-template.json",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"path": "harness-codebase-map/SKILL.md",
|
|
114
|
-
"sha256": "
|
|
114
|
+
"sha256": "b19f8a3d4da1e471d3b857c7e914e4ecdef838812ae7b6e3c8a970eec7c7061b"
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"path": "harness-codebase-map/templates/ARCHITECTURE.md",
|
|
@@ -151,11 +151,11 @@
|
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"path": "harness-knowledge-ingest/SKILL.md",
|
|
154
|
-
"sha256": "
|
|
154
|
+
"sha256": "7ece336a562d942fe033a09e0bcfc6454ca1762d6c3e0410a5550a93d4cf9e12"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
"path": "harness-knowledge-query/SKILL.md",
|
|
158
|
-
"sha256": "
|
|
158
|
+
"sha256": "75f01294c4d81abab5d75dd10001607cd6779410c4b2d08202d00b03d13a8132"
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
"path": "harness-plan/checklist.md",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
"path": "harness-plan/SKILL.md",
|
|
174
|
-
"sha256": "
|
|
174
|
+
"sha256": "9922ba328a97497b9eef1937e00f4d79d5a754c0af4bc3f00ed2d60ba097ae1c"
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
"path": "harness-review/checklist.md",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
"path": "harness-review/SKILL.md",
|
|
190
|
-
"sha256": "
|
|
190
|
+
"sha256": "35cd30eb98e61e751e3d92eed371e32ebf550d9919a759c34387c332ce1144a9"
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"path": "harness-run/checklist.md",
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
"path": "harness-run/SKILL.md",
|
|
206
|
-
"sha256": "
|
|
206
|
+
"sha256": "ccadcd4170ca8e9c76611bfee9bda6cb016d627d5848de91da40d74fef28758b"
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"path": "harness-submit/checklist.md",
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"path": "harness-submit/SKILL.md",
|
|
218
|
-
"sha256": "
|
|
218
|
+
"sha256": "a5ce09ea83ef86837a2a641f7c680f18c418c191e31674c1afd12102a25ac55a"
|
|
219
219
|
},
|
|
220
220
|
{
|
|
221
221
|
"path": "harness-sync/reference.md",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
"path": "harness-sync/SKILL.md",
|
|
226
|
-
"sha256": "
|
|
226
|
+
"sha256": "00d5d6ebe2b1a416b4c814e5a14fcb7c3ceb5c2e5219f9d929f702c92f123174"
|
|
227
227
|
},
|
|
228
228
|
{
|
|
229
229
|
"path": "harness-test/checklist.md",
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
},
|
|
248
248
|
{
|
|
249
249
|
"path": "harness-test/SKILL.md",
|
|
250
|
-
"sha256": "
|
|
250
|
+
"sha256": "9e66478326b1f042e705af7d6c7253bf86ced062770debe952337b92297b1f08"
|
|
251
251
|
},
|
|
252
252
|
{
|
|
253
253
|
"path": "protocols/archive-report-protocol.md",
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
},
|
|
320
320
|
{
|
|
321
321
|
"path": "scripts/harness_archive.py",
|
|
322
|
-
"sha256": "
|
|
322
|
+
"sha256": "f029938af890198a737d6fea746a9f5ed885f8c32a462a63cac79dbb0f0b8be0"
|
|
323
323
|
},
|
|
324
324
|
{
|
|
325
325
|
"path": "scripts/harness_change.py",
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
},
|
|
332
332
|
{
|
|
333
333
|
"path": "scripts/harness_context.py",
|
|
334
|
-
"sha256": "
|
|
334
|
+
"sha256": "464b59fea967bf123a7e203524b22ce68ead7db0250e686407ea29520853065f"
|
|
335
335
|
},
|
|
336
336
|
{
|
|
337
337
|
"path": "scripts/harness_deploy.py",
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
},
|
|
364
364
|
{
|
|
365
365
|
"path": "scripts/harness_gate.py",
|
|
366
|
-
"sha256": "
|
|
366
|
+
"sha256": "662f3490217546c9a20ce910f14493133dcf1a5cd1127f6cc6c50747ac22618c"
|
|
367
367
|
},
|
|
368
368
|
{
|
|
369
369
|
"path": "scripts/harness_headless.py",
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
},
|
|
376
376
|
{
|
|
377
377
|
"path": "scripts/harness_ledger.py",
|
|
378
|
-
"sha256": "
|
|
378
|
+
"sha256": "58f45d0a30c124b2493fc4a4c66f40c85c9a3dda952fccafbdc05a9a7a135d02"
|
|
379
379
|
},
|
|
380
380
|
{
|
|
381
381
|
"path": "scripts/harness_migration_head.py",
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
},
|
|
428
428
|
{
|
|
429
429
|
"path": "scripts/harness_runtime.py",
|
|
430
|
-
"sha256": "
|
|
430
|
+
"sha256": "3866980763961f178f44fc04330854b4a3e05867900418316f876bf7f4fca1b7"
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
433
|
"path": "scripts/harness_service.py",
|