@ikon85/agent-workflow-kit 0.36.5 → 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 +20 -12
- package/.agents/skills/setup-workflow/SKILL.md +24 -3
- package/.agents/skills/setup-workflow/orchestrate-wave-seed.md +3 -2
- package/.agents/skills/setup-workflow/worktree-lifecycle.md +29 -1
- package/.agents/skills/wrapup/SKILL.md +42 -9
- package/.claude/hooks/migration-snapshot-reminder.py +1 -1
- package/.claude/skills/orchestrate-wave/SKILL.md +11 -11
- package/.claude/skills/setup-workflow/SKILL.md +24 -3
- package/.claude/skills/setup-workflow/orchestrate-wave-seed.md +3 -2
- package/.claude/skills/setup-workflow/worktree-lifecycle.md +29 -1
- package/.claude/skills/skill-manifest.json +1 -1
- package/.claude/skills/wrapup/SKILL.md +33 -10
- package/README.md +88 -1
- package/agent-workflow-kit.package.json +36 -28
- package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +88 -0
- package/docs/agents/workflow-capabilities.json +11 -0
- package/package.json +1 -1
- package/scripts/anchor_table.py +14 -8
- package/scripts/project-skill-extension.mjs +21 -2
- package/scripts/readiness.mjs +32 -4
- package/scripts/release-state.mjs +19 -9
- package/scripts/release-state.test.mjs +71 -8
- package/scripts/test_anchor_table.py +69 -0
- package/scripts/test_board_sync_create_idempotency.py +15 -2
- package/scripts/test_board_sync_wave_title.py +14 -2
- package/scripts/test_census_backstop.py +33 -0
- package/scripts/test_orchestrate_wave_contract.py +44 -0
- package/scripts/test_retro_wrapup_contract.py +19 -2
- package/scripts/test_worktree_wrapup_contract.py +1004 -3
- package/scripts/test_wrapup_land.py +428 -0
- package/scripts/workflow-advisories/core.py +44 -2
- package/scripts/worktree-lifecycle/README.md +126 -4
- package/scripts/worktree-lifecycle/capabilities.json +9 -1
- package/scripts/worktree-lifecycle/cleanup.py +143 -5
- package/scripts/worktree-lifecycle/core.py +1383 -20
- package/scripts/worktree-lifecycle/profile.py +40 -3
- package/scripts/worktree-lifecycle/session.py +1857 -0
- package/scripts/worktree-lifecycle/setup.py +15 -0
- package/scripts/wrapup-land.py +650 -27
- package/src/cli.mjs +60 -27
- package/src/lib/bundle.mjs +1 -0
- package/src/lib/manifest.mjs +173 -3
- package/src/lib/projectSkillExtension.mjs +78 -1
- package/src/lib/updateCandidate.mjs +3 -1
- package/src/lib/updateDecisions.mjs +2 -2
- package/src/lib/verifyUpdateCandidateProtocol.mjs +15 -0
|
@@ -4,49 +4,187 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
|
+
from dataclasses import asdict, replace
|
|
7
8
|
import json
|
|
8
9
|
import sys
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
11
|
-
from core import
|
|
12
|
+
from core import (
|
|
13
|
+
LifecycleError,
|
|
14
|
+
bind_cleanup_scratch_evidence,
|
|
15
|
+
classify_cleanup,
|
|
16
|
+
collect_cleanup_facts,
|
|
17
|
+
collect_sweep,
|
|
18
|
+
load_profile,
|
|
19
|
+
main_worktree,
|
|
20
|
+
remove_authorized_scratch,
|
|
21
|
+
run,
|
|
22
|
+
verified_worktree_root,
|
|
23
|
+
)
|
|
12
24
|
|
|
13
25
|
|
|
14
26
|
def parse_args() -> argparse.Namespace:
|
|
15
27
|
parser = argparse.ArgumentParser()
|
|
16
28
|
parser.add_argument("--profile", default="docs/agents/workflow-capabilities.json")
|
|
17
29
|
parser.add_argument("--remove", action="store_true")
|
|
18
|
-
parser.add_argument("
|
|
30
|
+
parser.add_argument("--gh-command", default="gh")
|
|
31
|
+
parser.add_argument("worktree", nargs="?")
|
|
19
32
|
return parser.parse_args()
|
|
20
33
|
|
|
21
34
|
|
|
35
|
+
def parse_sweep_args(argv: list[str]) -> argparse.Namespace:
|
|
36
|
+
parser = argparse.ArgumentParser(prog="cleanup.py sweep")
|
|
37
|
+
parser.add_argument("--profile", default="docs/agents/workflow-capabilities.json")
|
|
38
|
+
parser.add_argument("--gh-command", default="gh")
|
|
39
|
+
return parser.parse_args(argv)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def pr_state(gh_command: str, main: Path, branch: str) -> str:
|
|
43
|
+
remote = run(
|
|
44
|
+
["git", "remote", "get-url", "origin"],
|
|
45
|
+
cwd=main,
|
|
46
|
+
check=False,
|
|
47
|
+
)
|
|
48
|
+
if remote.returncode != 0:
|
|
49
|
+
return "none"
|
|
50
|
+
result = run(
|
|
51
|
+
[
|
|
52
|
+
gh_command, "pr", "list", "--state", "all", "--head", branch,
|
|
53
|
+
"--json", "number,state,mergedAt",
|
|
54
|
+
],
|
|
55
|
+
cwd=main,
|
|
56
|
+
check=False,
|
|
57
|
+
)
|
|
58
|
+
if result.returncode != 0:
|
|
59
|
+
detail = (result.stderr or result.stdout).strip()
|
|
60
|
+
raise LifecycleError(f"cannot determine PR state for {branch}: {detail}")
|
|
61
|
+
try:
|
|
62
|
+
prs = json.loads(result.stdout)
|
|
63
|
+
except json.JSONDecodeError as error:
|
|
64
|
+
raise LifecycleError(f"cannot determine PR state for {branch}: invalid gh output") from error
|
|
65
|
+
if any(str(pr.get("state", "")).upper() == "OPEN" for pr in prs):
|
|
66
|
+
return "open"
|
|
67
|
+
if any(pr.get("mergedAt") or str(pr.get("state", "")).upper() == "MERGED" for pr in prs):
|
|
68
|
+
return "merged"
|
|
69
|
+
return "none"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def collect_assessment(profile, main: Path, worktree: Path, gh_command: str):
|
|
73
|
+
facts = collect_cleanup_facts(
|
|
74
|
+
main,
|
|
75
|
+
worktree,
|
|
76
|
+
)
|
|
77
|
+
state = pr_state(gh_command, main, facts.branch)
|
|
78
|
+
assessment = classify_cleanup(profile, replace(facts, pr_state=state))
|
|
79
|
+
try:
|
|
80
|
+
return bind_cleanup_scratch_evidence(
|
|
81
|
+
profile,
|
|
82
|
+
assessment,
|
|
83
|
+
require_generator_evidence=True,
|
|
84
|
+
)
|
|
85
|
+
except LifecycleError as error:
|
|
86
|
+
return replace(
|
|
87
|
+
assessment,
|
|
88
|
+
reasons=assessment.reasons + (f"scratch evidence stop: {error}",),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
22
92
|
def execute(args: argparse.Namespace) -> dict:
|
|
23
93
|
main = main_worktree(Path.cwd())
|
|
24
94
|
profile_path = Path(args.profile)
|
|
25
95
|
if not profile_path.is_absolute():
|
|
26
96
|
profile_path = main / profile_path
|
|
27
97
|
profile = load_profile(profile_path)
|
|
28
|
-
|
|
98
|
+
if not args.worktree:
|
|
99
|
+
raise LifecycleError("worktree is required")
|
|
100
|
+
worktree = Path(args.worktree)
|
|
101
|
+
assessment = collect_assessment(profile, main, worktree, args.gh_command)
|
|
29
102
|
report = {
|
|
30
103
|
"worktree": str(assessment.worktree),
|
|
31
104
|
"branch": assessment.branch,
|
|
32
105
|
"removable": assessment.removable,
|
|
33
106
|
"reasons": list(assessment.reasons),
|
|
34
107
|
"assumptions": assessment.assumptions,
|
|
108
|
+
"scratchFiles": list(assessment.scratch_files),
|
|
35
109
|
"removed": False,
|
|
36
110
|
}
|
|
37
111
|
if not args.remove:
|
|
38
112
|
return report
|
|
39
113
|
if not assessment.removable:
|
|
40
114
|
raise LifecycleError("; ".join(assessment.reasons))
|
|
41
|
-
|
|
115
|
+
latest = collect_assessment(profile, main, worktree, args.gh_command)
|
|
116
|
+
if not latest.removable:
|
|
117
|
+
raise LifecycleError(f"cleanup changed before removal: {'; '.join(latest.reasons)}")
|
|
118
|
+
if (
|
|
119
|
+
latest.branch != assessment.branch
|
|
120
|
+
or latest.scratch_files != assessment.scratch_files
|
|
121
|
+
or latest.assumptions != assessment.assumptions
|
|
122
|
+
or latest.root_device != assessment.root_device
|
|
123
|
+
or latest.root_inode != assessment.root_inode
|
|
124
|
+
or latest.scratch_evidence != assessment.scratch_evidence
|
|
125
|
+
):
|
|
126
|
+
raise LifecycleError("cleanup changed before removal: inventory no longer matches preview")
|
|
127
|
+
with verified_worktree_root(
|
|
128
|
+
latest.worktree,
|
|
129
|
+
latest.root_device,
|
|
130
|
+
latest.root_inode,
|
|
131
|
+
) as root_descriptor:
|
|
132
|
+
remove_authorized_scratch(
|
|
133
|
+
profile,
|
|
134
|
+
root_descriptor,
|
|
135
|
+
latest.scratch_files,
|
|
136
|
+
assessment.scratch_evidence,
|
|
137
|
+
)
|
|
138
|
+
run(["git", "worktree", "remove", str(latest.worktree)], cwd=main)
|
|
42
139
|
run(["git", "branch", "-d", assessment.branch], cwd=main)
|
|
43
140
|
report["removed"] = True
|
|
44
141
|
return report
|
|
45
142
|
|
|
46
143
|
|
|
144
|
+
def sweep(args: argparse.Namespace) -> dict:
|
|
145
|
+
main = main_worktree(Path.cwd())
|
|
146
|
+
profile_path = Path(args.profile)
|
|
147
|
+
if not profile_path.is_absolute():
|
|
148
|
+
profile_path = main / profile_path
|
|
149
|
+
profile = load_profile(profile_path)
|
|
150
|
+
report = collect_sweep(
|
|
151
|
+
profile,
|
|
152
|
+
main,
|
|
153
|
+
lambda branch: pr_state(args.gh_command, main, branch),
|
|
154
|
+
)
|
|
155
|
+
payload = asdict(report)
|
|
156
|
+
return {
|
|
157
|
+
"mainBranch": payload["main_branch"],
|
|
158
|
+
"worktreeCount": payload["worktree_count"],
|
|
159
|
+
"localBranchCount": payload["local_branch_count"],
|
|
160
|
+
"mergedRemoteBranchCount": payload["merged_remote_branch_count"],
|
|
161
|
+
"rows": [
|
|
162
|
+
{
|
|
163
|
+
"kind": row["kind"],
|
|
164
|
+
"path": row["path"],
|
|
165
|
+
"branch": row["branch"],
|
|
166
|
+
"issue": row["issue"],
|
|
167
|
+
"prState": row["pr_state"],
|
|
168
|
+
"mergedIntoMain": row["merged_into_main"],
|
|
169
|
+
"lastCommitAgeSeconds": row["last_commit_age_seconds"],
|
|
170
|
+
"removable": row["removable"],
|
|
171
|
+
"reasons": list(row["reasons"]),
|
|
172
|
+
"verdictReason": row["verdict_reason"],
|
|
173
|
+
"scratchFiles": list(row["scratch_files"]),
|
|
174
|
+
"assumptions": row["assumptions"],
|
|
175
|
+
}
|
|
176
|
+
for row in payload["rows"]
|
|
177
|
+
],
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
47
181
|
def main() -> int:
|
|
48
182
|
try:
|
|
49
|
-
|
|
183
|
+
if len(sys.argv) > 1 and sys.argv[1] == "sweep":
|
|
184
|
+
result = sweep(parse_sweep_args(sys.argv[2:]))
|
|
185
|
+
else:
|
|
186
|
+
result = execute(parse_args())
|
|
187
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
50
188
|
return 0
|
|
51
189
|
except LifecycleError as error:
|
|
52
190
|
print(f"STOP: {error}", file=sys.stderr)
|