@ikon85/agent-workflow-kit 0.37.0 → 0.38.0
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/.agents/skills/orchestrate-wave/SKILL.md +5 -5
- package/.agents/skills/setup-workflow/SKILL.md +8 -1
- package/.agents/skills/setup-workflow/worktree-lifecycle.md +18 -1
- package/.agents/skills/wrapup/SKILL.md +22 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +5 -5
- package/.claude/skills/setup-workflow/SKILL.md +8 -1
- package/.claude/skills/setup-workflow/worktree-lifecycle.md +18 -1
- package/.claude/skills/wrapup/SKILL.md +22 -0
- package/README.md +36 -0
- package/agent-workflow-kit.package.json +24 -16
- package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +88 -0
- package/docs/agents/workflow-capabilities.json +11 -1
- package/package.json +1 -1
- package/scripts/test_orchestrate_wave_contract.py +9 -0
- package/scripts/test_worktree_wrapup_contract.py +1004 -3
- package/scripts/worktree-lifecycle/README.md +108 -0
- package/scripts/worktree-lifecycle/capabilities.json +9 -1
- package/scripts/worktree-lifecycle/cleanup.py +22 -51
- package/scripts/worktree-lifecycle/core.py +1054 -4
- package/scripts/worktree-lifecycle/profile.py +38 -3
- package/scripts/worktree-lifecycle/session.py +1857 -0
- package/scripts/worktree-lifecycle/setup.py +15 -0
- package/scripts/wrapup-land.py +314 -12
- package/src/lib/bundle.mjs +1 -0
|
@@ -10,6 +10,9 @@ import zlib
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
from core import (
|
|
13
|
+
BaselineBackfillDeferred,
|
|
14
|
+
capture_artifact_baseline,
|
|
15
|
+
ensure_artifact_baseline,
|
|
13
16
|
LifecycleError,
|
|
14
17
|
load_profile,
|
|
15
18
|
local_branch_exists,
|
|
@@ -137,6 +140,17 @@ def create(args: argparse.Namespace) -> Path:
|
|
|
137
140
|
ensure_reusable_base(
|
|
138
141
|
main, repo=target, rev="HEAD", base=args.base, label=f"worktree {target}"
|
|
139
142
|
)
|
|
143
|
+
try:
|
|
144
|
+
ensure_artifact_baseline(
|
|
145
|
+
target,
|
|
146
|
+
reject_ignored_patterns=profile.landing_generated_artifact_patterns,
|
|
147
|
+
)
|
|
148
|
+
except BaselineBackfillDeferred as error:
|
|
149
|
+
print(
|
|
150
|
+
"Baseline backfill deferred; preserve the worktree, remove generated "
|
|
151
|
+
f"blockers or commit/stash tracked work, then retry: {error}",
|
|
152
|
+
file=sys.stderr,
|
|
153
|
+
)
|
|
140
154
|
print(f"Worktree already exists: {target} ({branch})")
|
|
141
155
|
return target
|
|
142
156
|
|
|
@@ -157,6 +171,7 @@ def create(args: argparse.Namespace) -> Path:
|
|
|
157
171
|
issue=args.issue,
|
|
158
172
|
branch=branch,
|
|
159
173
|
)
|
|
174
|
+
capture_artifact_baseline(target)
|
|
160
175
|
except Exception:
|
|
161
176
|
remove_failed_worktree(main, target, branch, not branch_existed)
|
|
162
177
|
raise
|
package/scripts/wrapup-land.py
CHANGED
|
@@ -437,18 +437,189 @@ def load_worktree_cleanup_core():
|
|
|
437
437
|
return module
|
|
438
438
|
|
|
439
439
|
|
|
440
|
-
def
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
440
|
+
def load_candidate_landing_profile(core, wt: str):
|
|
441
|
+
"""Let the committed worktree policy nominate evidence without deletion."""
|
|
442
|
+
result = core.run(
|
|
443
|
+
[
|
|
444
|
+
"git", "show",
|
|
445
|
+
"HEAD:docs/agents/workflow-capabilities.json",
|
|
446
|
+
],
|
|
447
|
+
cwd=Path(wt),
|
|
448
|
+
check=False,
|
|
449
|
+
)
|
|
450
|
+
if result.returncode != 0:
|
|
451
|
+
detail = (result.stderr or result.stdout).strip()
|
|
452
|
+
raise core.LifecycleError(
|
|
453
|
+
f"committed landing profile cannot be read from worktree HEAD: {detail}"
|
|
454
|
+
)
|
|
455
|
+
candidate = core.load_profile_text(result.stdout)
|
|
456
|
+
if not candidate.landing_generated_artifact_policy_configured:
|
|
457
|
+
raise core.LifecycleError(
|
|
458
|
+
"worktree landing artifact policy is not configured; "
|
|
459
|
+
"run setup-workflow and commit the explicit policy decision first"
|
|
460
|
+
)
|
|
461
|
+
return candidate
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def load_canonical_landing_profile(core, wt: str, main_tree: str):
|
|
465
|
+
"""Authorize deletion only after the candidate policy is canonical."""
|
|
466
|
+
candidate = load_candidate_landing_profile(core, wt)
|
|
467
|
+
result = core.run(
|
|
468
|
+
[
|
|
469
|
+
"git", "show",
|
|
470
|
+
"origin/main:docs/agents/workflow-capabilities.json",
|
|
471
|
+
],
|
|
472
|
+
cwd=Path(main_tree),
|
|
473
|
+
check=False,
|
|
474
|
+
)
|
|
475
|
+
if result.returncode != 0:
|
|
476
|
+
detail = (result.stderr or result.stdout).strip()
|
|
477
|
+
raise core.LifecycleError(
|
|
478
|
+
f"canonical landing profile cannot be read from origin/main: {detail}"
|
|
479
|
+
)
|
|
480
|
+
canonical = core.load_profile_text(result.stdout)
|
|
481
|
+
if not canonical.landing_generated_artifact_policy_configured:
|
|
482
|
+
raise core.LifecycleError(
|
|
483
|
+
"merged canonical landing artifact policy is not configured"
|
|
484
|
+
)
|
|
485
|
+
if (
|
|
486
|
+
candidate.landing_generated_artifact_patterns
|
|
487
|
+
!= canonical.landing_generated_artifact_patterns
|
|
488
|
+
or candidate.scratch_patterns != canonical.scratch_patterns
|
|
489
|
+
):
|
|
490
|
+
raise core.LifecycleError(
|
|
491
|
+
"worktree cleanup policy differs from merged canonical origin/main"
|
|
492
|
+
)
|
|
493
|
+
attempt_path = core.artifact_baseline_path(Path(wt)).with_name(
|
|
494
|
+
core.LANDING_ATTEMPT_FILE
|
|
495
|
+
)
|
|
496
|
+
if not os.path.lexists(attempt_path):
|
|
497
|
+
raise core.LifecycleError(
|
|
498
|
+
"landing attempt is missing before canonical cleanup authorization"
|
|
499
|
+
)
|
|
500
|
+
attempt = core.landing_start_artifact_inventory(candidate, Path(wt))
|
|
501
|
+
if (
|
|
502
|
+
attempt["policyDigest"]
|
|
503
|
+
!= core.landing_cleanup_policy_digest(canonical)
|
|
504
|
+
):
|
|
505
|
+
raise core.LifecycleError(
|
|
506
|
+
"landing attempt cleanup policy differs from merged canonical origin/main"
|
|
507
|
+
)
|
|
508
|
+
return canonical
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def landing_artifact_baseline_digest(wt: str, main_tree: str) -> str:
|
|
444
512
|
core = load_worktree_cleanup_core()
|
|
445
513
|
try:
|
|
446
|
-
|
|
447
|
-
|
|
514
|
+
load_candidate_landing_profile(core, wt)
|
|
515
|
+
return core.load_artifact_baseline(Path(wt)).digest
|
|
516
|
+
except core.LifecycleError as error:
|
|
517
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def landing_start_artifact_inventory(wt: str, main_tree: str) -> dict:
|
|
521
|
+
core = load_worktree_cleanup_core()
|
|
522
|
+
try:
|
|
523
|
+
profile = load_candidate_landing_profile(core, wt)
|
|
524
|
+
return core.landing_start_artifact_inventory(profile, Path(wt))
|
|
525
|
+
except core.LifecycleError as error:
|
|
526
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def landing_verified_scratch_evidence(
|
|
530
|
+
wt: str,
|
|
531
|
+
main_tree: str,
|
|
532
|
+
*,
|
|
533
|
+
expected_baseline_digest: str | None = None,
|
|
534
|
+
landing_start_files: tuple[str, ...] = (),
|
|
535
|
+
) -> tuple[dict, ...]:
|
|
536
|
+
core = load_worktree_cleanup_core()
|
|
537
|
+
try:
|
|
538
|
+
profile = load_candidate_landing_profile(core, wt)
|
|
539
|
+
return core.verified_landing_scratch_evidence(
|
|
448
540
|
profile,
|
|
449
|
-
Path(main_tree),
|
|
450
541
|
Path(wt),
|
|
451
|
-
|
|
542
|
+
expected_baseline_digest=expected_baseline_digest,
|
|
543
|
+
landing_start_files=landing_start_files,
|
|
544
|
+
)
|
|
545
|
+
except core.LifecycleError as error:
|
|
546
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def freeze_landing_artifact_evidence(
|
|
550
|
+
wt: str,
|
|
551
|
+
main_tree: str,
|
|
552
|
+
*,
|
|
553
|
+
push_succeeded: bool,
|
|
554
|
+
) -> tuple[dict, ...]:
|
|
555
|
+
core = load_worktree_cleanup_core()
|
|
556
|
+
try:
|
|
557
|
+
profile = load_candidate_landing_profile(core, wt)
|
|
558
|
+
return core.freeze_landing_artifact_evidence(
|
|
559
|
+
profile,
|
|
560
|
+
Path(wt),
|
|
561
|
+
push_succeeded=push_succeeded,
|
|
562
|
+
)
|
|
563
|
+
except core.LifecycleError as error:
|
|
564
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def reopen_frozen_landing_attempt(wt: str, main_tree: str) -> tuple[dict, ...]:
|
|
568
|
+
core = load_worktree_cleanup_core()
|
|
569
|
+
try:
|
|
570
|
+
profile = load_candidate_landing_profile(core, wt)
|
|
571
|
+
return core.reopen_frozen_landing_attempt(profile, Path(wt))
|
|
572
|
+
except core.LifecycleError as error:
|
|
573
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def abandon_unfinished_landing_attempt(wt: str, main_tree: str) -> str:
|
|
577
|
+
core = load_worktree_cleanup_core()
|
|
578
|
+
try:
|
|
579
|
+
return str(core.abandon_unfinished_landing_attempt(Path(wt)))
|
|
580
|
+
except core.LifecycleError as error:
|
|
581
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def landing_verified_scratch_files(
|
|
585
|
+
wt: str,
|
|
586
|
+
main_tree: str,
|
|
587
|
+
*,
|
|
588
|
+
expected_baseline_digest: str | None = None,
|
|
589
|
+
) -> tuple[str, ...]:
|
|
590
|
+
core = load_worktree_cleanup_core()
|
|
591
|
+
try:
|
|
592
|
+
profile = load_candidate_landing_profile(core, wt)
|
|
593
|
+
return core.verified_landing_scratch_files(
|
|
594
|
+
profile,
|
|
595
|
+
Path(wt),
|
|
596
|
+
expected_baseline_digest=expected_baseline_digest,
|
|
597
|
+
)
|
|
598
|
+
except core.LifecycleError as error:
|
|
599
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def ensure_worktree_removable(
|
|
603
|
+
wt: str,
|
|
604
|
+
main_tree: str,
|
|
605
|
+
*,
|
|
606
|
+
verified_scratch_files: tuple[str, ...] = (),
|
|
607
|
+
verified_scratch_evidence: tuple[dict, ...] = (),
|
|
608
|
+
):
|
|
609
|
+
core = load_worktree_cleanup_core()
|
|
610
|
+
try:
|
|
611
|
+
profile = load_canonical_landing_profile(core, wt, main_tree)
|
|
612
|
+
kwargs = {"merge_target": "origin/main"}
|
|
613
|
+
paths = (
|
|
614
|
+
tuple(item["path"] for item in verified_scratch_evidence)
|
|
615
|
+
or verified_scratch_files
|
|
616
|
+
)
|
|
617
|
+
if paths:
|
|
618
|
+
kwargs["verified_scratch_files"] = paths
|
|
619
|
+
if verified_scratch_evidence:
|
|
620
|
+
kwargs["verified_scratch_evidence"] = verified_scratch_evidence
|
|
621
|
+
assessment = core.cleanup_assessment(
|
|
622
|
+
profile, Path(main_tree), Path(wt), **kwargs
|
|
452
623
|
)
|
|
453
624
|
except core.LifecycleError as error:
|
|
454
625
|
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
@@ -461,6 +632,69 @@ def ensure_worktree_removable(wt: str, main_tree: str):
|
|
|
461
632
|
return assessment
|
|
462
633
|
|
|
463
634
|
|
|
635
|
+
def remove_verified_worktree_scratch(
|
|
636
|
+
wt: str,
|
|
637
|
+
main_tree: str,
|
|
638
|
+
assessment,
|
|
639
|
+
*,
|
|
640
|
+
verified_scratch_files: tuple[str, ...] = (),
|
|
641
|
+
verified_scratch_evidence: tuple[dict, ...] = (),
|
|
642
|
+
):
|
|
643
|
+
"""Re-verify and delete only assessed regular scratch files."""
|
|
644
|
+
core = load_worktree_cleanup_core()
|
|
645
|
+
try:
|
|
646
|
+
profile = load_canonical_landing_profile(core, wt, main_tree)
|
|
647
|
+
evidence = verified_scratch_evidence
|
|
648
|
+
paths = tuple(item["path"] for item in evidence) or verified_scratch_files
|
|
649
|
+
latest = core.cleanup_assessment(
|
|
650
|
+
profile,
|
|
651
|
+
Path(main_tree),
|
|
652
|
+
Path(wt),
|
|
653
|
+
merge_target="origin/main",
|
|
654
|
+
verified_scratch_files=paths,
|
|
655
|
+
verified_scratch_evidence=evidence,
|
|
656
|
+
)
|
|
657
|
+
if latest.reasons:
|
|
658
|
+
raise core.LifecycleError(
|
|
659
|
+
"cleanup changed before removal: " + "; ".join(latest.reasons)
|
|
660
|
+
)
|
|
661
|
+
if (
|
|
662
|
+
latest.branch != assessment.branch
|
|
663
|
+
or latest.scratch_files != assessment.scratch_files
|
|
664
|
+
or latest.assumptions != assessment.assumptions
|
|
665
|
+
or latest.root_device != assessment.root_device
|
|
666
|
+
or latest.root_inode != assessment.root_inode
|
|
667
|
+
or latest.scratch_evidence != assessment.scratch_evidence
|
|
668
|
+
):
|
|
669
|
+
raise core.LifecycleError(
|
|
670
|
+
"cleanup changed before removal: inventory no longer matches preview"
|
|
671
|
+
)
|
|
672
|
+
with core.verified_worktree_root(
|
|
673
|
+
latest.worktree,
|
|
674
|
+
latest.root_device,
|
|
675
|
+
latest.root_inode,
|
|
676
|
+
) as root_descriptor:
|
|
677
|
+
core.remove_authorized_scratch(
|
|
678
|
+
profile,
|
|
679
|
+
root_descriptor,
|
|
680
|
+
latest.scratch_files,
|
|
681
|
+
latest.scratch_evidence,
|
|
682
|
+
)
|
|
683
|
+
final = core.cleanup_assessment(
|
|
684
|
+
profile,
|
|
685
|
+
Path(main_tree),
|
|
686
|
+
Path(wt),
|
|
687
|
+
merge_target="origin/main",
|
|
688
|
+
)
|
|
689
|
+
if final.reasons:
|
|
690
|
+
raise core.LifecycleError(
|
|
691
|
+
"cleanup changed after scratch removal: " + "; ".join(final.reasons)
|
|
692
|
+
)
|
|
693
|
+
return final
|
|
694
|
+
except core.LifecycleError as error:
|
|
695
|
+
raise Stop("cleanup", f"shared cleanup guard failed: {error}") from error
|
|
696
|
+
|
|
697
|
+
|
|
464
698
|
# ---------- drift log (ANNAHMEN.md) ----------
|
|
465
699
|
|
|
466
700
|
def parse_annahmen(text: str, default_section: str) -> tuple[list[dict], list[str]]:
|
|
@@ -702,9 +936,39 @@ def cmd_land(args) -> dict:
|
|
|
702
936
|
|
|
703
937
|
# drift markers from the build-time log — mechanical, no gate
|
|
704
938
|
markers: list[dict] = []
|
|
939
|
+
artifact_baseline_digest: str | None = None
|
|
940
|
+
landing_start_files: tuple[str, ...] = ()
|
|
941
|
+
landing_attempt: dict | None = None
|
|
942
|
+
generated_evidence: tuple[dict, ...] = ()
|
|
705
943
|
if wt_exists:
|
|
706
944
|
if git(["status", "--porcelain"], cwd=wt, check=True).stdout.strip():
|
|
707
945
|
raise Stop("land", "worktree dirty — run `commit` first", wt)
|
|
946
|
+
if getattr(args, "abandon_unfinished_attempt", False):
|
|
947
|
+
return {
|
|
948
|
+
"landing_attempt_abandoned": abandon_unfinished_landing_attempt(
|
|
949
|
+
wt, main_tree
|
|
950
|
+
),
|
|
951
|
+
"files_removed": [],
|
|
952
|
+
"next": (
|
|
953
|
+
"current files remain protected; classify or move blockers, "
|
|
954
|
+
"then rerun land"
|
|
955
|
+
),
|
|
956
|
+
}
|
|
957
|
+
landing_attempt = landing_start_artifact_inventory(wt, main_tree)
|
|
958
|
+
if landing_attempt["state"] == "started" and not landing_attempt["newAttempt"]:
|
|
959
|
+
raise Stop(
|
|
960
|
+
"cleanup",
|
|
961
|
+
"unfinished landing generator attempt has no frozen output evidence; "
|
|
962
|
+
"classify its files, then rerun with --abandon-unfinished-attempt",
|
|
963
|
+
)
|
|
964
|
+
landing_start_files = tuple(landing_attempt["generatedFiles"])
|
|
965
|
+
if landing_start_files:
|
|
966
|
+
raise Stop(
|
|
967
|
+
"cleanup",
|
|
968
|
+
"landing-start generated paths are consumer-owned and protected: "
|
|
969
|
+
+ ", ".join(landing_start_files),
|
|
970
|
+
)
|
|
971
|
+
artifact_baseline_digest = landing_attempt["baselineDigest"]
|
|
708
972
|
annahmen = Path(wt) / "ANNAHMEN.md"
|
|
709
973
|
if annahmen.is_file():
|
|
710
974
|
markers, malformed = parse_annahmen(annahmen.read_text(), default_section)
|
|
@@ -716,9 +980,25 @@ def cmd_land(args) -> dict:
|
|
|
716
980
|
|
|
717
981
|
# Step 0b — push
|
|
718
982
|
if wt_exists:
|
|
719
|
-
|
|
720
|
-
if
|
|
721
|
-
|
|
983
|
+
assert landing_attempt is not None
|
|
984
|
+
if landing_attempt["state"] == "frozen" and landing_attempt["pushSucceeded"]:
|
|
985
|
+
generated_evidence = freeze_landing_artifact_evidence(
|
|
986
|
+
wt, main_tree, push_succeeded=True
|
|
987
|
+
)
|
|
988
|
+
else:
|
|
989
|
+
if landing_attempt["state"] == "frozen":
|
|
990
|
+
# Validate every previously frozen identity before a retry can
|
|
991
|
+
# invoke the generator-capable pre-push hook.
|
|
992
|
+
reopen_frozen_landing_attempt(wt, main_tree)
|
|
993
|
+
p = git(["push", "-u", "origin", branch], cwd=wt)
|
|
994
|
+
generated_evidence = freeze_landing_artifact_evidence(
|
|
995
|
+
wt, main_tree, push_succeeded=p.returncode == 0
|
|
996
|
+
)
|
|
997
|
+
if p.returncode != 0:
|
|
998
|
+
raise Stop(
|
|
999
|
+
"0b push", "push rejected",
|
|
1000
|
+
(p.stderr or p.stdout).strip()[-2000:],
|
|
1001
|
+
)
|
|
722
1002
|
|
|
723
1003
|
# Step 0c — ensure PR + final body
|
|
724
1004
|
p = run(["gh", "pr", "view", branch, "--json", "number,state,body"])
|
|
@@ -789,12 +1069,26 @@ def cmd_land(args) -> dict:
|
|
|
789
1069
|
# Step 2 — kill the worktree's dev server, then Step 4 — teardown
|
|
790
1070
|
if wt_exists:
|
|
791
1071
|
git(["fetch", "origin", "main"], cwd=main_tree, check=True)
|
|
792
|
-
|
|
1072
|
+
generated = tuple(item["path"] for item in generated_evidence)
|
|
1073
|
+
cleanup = ensure_worktree_removable(
|
|
1074
|
+
wt,
|
|
1075
|
+
main_tree,
|
|
1076
|
+
verified_scratch_files=generated,
|
|
1077
|
+
verified_scratch_evidence=generated_evidence,
|
|
1078
|
+
)
|
|
793
1079
|
report["cleanup_guard"] = {
|
|
794
1080
|
"active": cleanup is not None,
|
|
795
1081
|
"assumptions_read": bool(cleanup and cleanup.assumptions),
|
|
1082
|
+
"landing_generated_files": list(generated),
|
|
796
1083
|
}
|
|
797
1084
|
report["killed_processes"] = kill_worktree_processes(wt)
|
|
1085
|
+
if cleanup is not None:
|
|
1086
|
+
cleanup = remove_verified_worktree_scratch(
|
|
1087
|
+
wt,
|
|
1088
|
+
main_tree,
|
|
1089
|
+
cleanup,
|
|
1090
|
+
verified_scratch_evidence=generated_evidence,
|
|
1091
|
+
)
|
|
798
1092
|
p = git(["worktree", "remove", wt], cwd=main_tree)
|
|
799
1093
|
if p.returncode != 0:
|
|
800
1094
|
raise Stop("4 worktree-remove", "git worktree remove refused — no --force; "
|
|
@@ -922,6 +1216,14 @@ def main() -> int:
|
|
|
922
1216
|
l.add_argument("--body-file", help="final PR body (create or overwrite)")
|
|
923
1217
|
l.add_argument("--anchor", help="wave-anchor issue # (derived via parent-of when omitted)")
|
|
924
1218
|
l.add_argument("--skip-malformed-drift", action="store_true")
|
|
1219
|
+
l.add_argument(
|
|
1220
|
+
"--abandon-unfinished-attempt",
|
|
1221
|
+
action="store_true",
|
|
1222
|
+
help=(
|
|
1223
|
+
"archive an interrupted pre-freeze landing attempt without deleting "
|
|
1224
|
+
"or claiming its ambiguous files"
|
|
1225
|
+
),
|
|
1226
|
+
)
|
|
925
1227
|
args = ap.parse_args()
|
|
926
1228
|
|
|
927
1229
|
try:
|
package/src/lib/bundle.mjs
CHANGED
|
@@ -134,6 +134,7 @@ export const HELPER_FILES = [
|
|
|
134
134
|
{ path: 'scripts/worktree-lifecycle/profile.py', kind: 'script', mode: 0o644 },
|
|
135
135
|
{ path: 'scripts/worktree-lifecycle/setup.py', kind: 'script', mode: 0o755 },
|
|
136
136
|
{ path: 'scripts/worktree-lifecycle/cleanup.py', kind: 'script', mode: 0o755 },
|
|
137
|
+
{ path: 'scripts/worktree-lifecycle/session.py', kind: 'script', mode: 0o755 },
|
|
137
138
|
{ path: 'scripts/worktree-lifecycle/capabilities.json', kind: 'doc', mode: 0o644 },
|
|
138
139
|
{ path: 'scripts/worktree-lifecycle/README.md', kind: 'doc', mode: 0o644 },
|
|
139
140
|
// Shared hook utility imported by the shipped hooks (drift-guard,
|