@ikon85/agent-workflow-kit 0.41.0 → 0.41.1
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/README.md
CHANGED
|
@@ -469,6 +469,16 @@ the old way. Decision record:
|
|
|
469
469
|
|
|
470
470
|
## Release notes
|
|
471
471
|
|
|
472
|
+
### 0.41.1
|
|
473
|
+
|
|
474
|
+
- changed: `README.md`
|
|
475
|
+
- changed: `agent-workflow-kit.package.json`
|
|
476
|
+
- changed: `package.json`
|
|
477
|
+
- changed: `scripts/pr-body-check.py`
|
|
478
|
+
- changed: `scripts/test_pr_body_check.py`
|
|
479
|
+
- changed: `scripts/test_worktree_wrapup_contract.py`
|
|
480
|
+
- changed: `scripts/wrapup-land.py`
|
|
481
|
+
|
|
472
482
|
### 0.41.0
|
|
473
483
|
|
|
474
484
|
- added: `docs/adr/0009-teardown-authority-is-stateless-repository-classification.md`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.41.
|
|
2
|
+
"kitVersion": "0.41.1",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -2627,7 +2627,7 @@
|
|
|
2627
2627
|
"path": "scripts/pr-body-check.py",
|
|
2628
2628
|
"kind": "script",
|
|
2629
2629
|
"installRole": "consumer",
|
|
2630
|
-
"sha256": "
|
|
2630
|
+
"sha256": "d18a9f1bf9285506bd7d40a1800dd9b66f631bd4666f0ae801e8db1882d3ad2d",
|
|
2631
2631
|
"mode": 493,
|
|
2632
2632
|
"origin": "kit"
|
|
2633
2633
|
},
|
|
@@ -2867,7 +2867,7 @@
|
|
|
2867
2867
|
"path": "scripts/wrapup-land.py",
|
|
2868
2868
|
"kind": "script",
|
|
2869
2869
|
"installRole": "consumer",
|
|
2870
|
-
"sha256": "
|
|
2870
|
+
"sha256": "de41f6c7bee1f67f3f25db4c7750a3caca422be5749012c6d5402cfeed91b2f7",
|
|
2871
2871
|
"mode": 493,
|
|
2872
2872
|
"origin": "kit"
|
|
2873
2873
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.1",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/scripts/pr-body-check.py
CHANGED
|
@@ -125,6 +125,21 @@ def _close_on(body: str, n: int) -> bool:
|
|
|
125
125
|
return bool(pat.search(strip_code(body)))
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
def active_close_targets(body: str) -> list:
|
|
129
|
+
"""All issue numbers an ACTIVE close-keyword targets (outside code spans),
|
|
130
|
+
as sorted, deduplicated strings. Same grammar `_close_on` enforces —
|
|
131
|
+
plain `closes #n`, the colon form, and the full-URL form. This is the one
|
|
132
|
+
close grammar; close-verification (wrapup-land Step 5b) must derive its
|
|
133
|
+
targets from here, never from a branch number."""
|
|
134
|
+
text = strip_code(body)
|
|
135
|
+
pat = re.compile(
|
|
136
|
+
rf"\b(?:{CLOSE_KEYWORDS})(?::\s*|\s+)"
|
|
137
|
+
rf"(?:#0*(\d+)(?!\d)|https?://\S+/issues/0*(\d+)(?!\d))",
|
|
138
|
+
re.IGNORECASE)
|
|
139
|
+
found = {a or b for a, b in pat.findall(text)}
|
|
140
|
+
return sorted(found, key=int)
|
|
141
|
+
|
|
142
|
+
|
|
128
143
|
def _part_of(body: str, n: int) -> bool:
|
|
129
144
|
return bool(re.search(
|
|
130
145
|
rf"\b{PART_OF_RE_SRC}(?::\s*|\s+)#0*{n}(?!\d)", body or "", re.IGNORECASE))
|
|
@@ -79,6 +79,15 @@ class ExistingBodyRules(unittest.TestCase):
|
|
|
79
79
|
def test_retro_line_still_required(self):
|
|
80
80
|
self.assertTrue(any("Retro" in item for item in pbc.check_pr_body("closes #149", 149, None)))
|
|
81
81
|
|
|
82
|
+
def test_active_close_targets_extracts_all_forms(self):
|
|
83
|
+
body = ("closes #12\nFixes: #13\n"
|
|
84
|
+
"resolves https://github.com/iKon85/agent-workflow-kit/issues/14\n"
|
|
85
|
+
"`closes #99` and Part of #320 and closes #12")
|
|
86
|
+
self.assertEqual(pbc.active_close_targets(body), ["12", "13", "14"])
|
|
87
|
+
|
|
88
|
+
def test_active_close_targets_empty_for_part_of_only(self):
|
|
89
|
+
self.assertEqual(pbc.active_close_targets("Part of #320\n**Retro:** skipped — x"), [])
|
|
90
|
+
|
|
82
91
|
def test_program_prd_label_counts_as_anchor(self):
|
|
83
92
|
with mock.patch.object(pbc, "_run",
|
|
84
93
|
return_value=(0, "type:program\npriority:medium")):
|
|
@@ -251,6 +251,13 @@ def downgrade_landing_attempt_to_v1(core, worktree: Path) -> Path:
|
|
|
251
251
|
|
|
252
252
|
|
|
253
253
|
class WorktreeCleanupContract(unittest.TestCase):
|
|
254
|
+
def test_declared_close_targets_is_pr_body_authority(self):
|
|
255
|
+
mod = load_wrapup()
|
|
256
|
+
self.assertEqual(mod.declared_close_targets("Part of #320"), [])
|
|
257
|
+
self.assertEqual(mod.declared_close_targets("closes #341\ncloses #12"),
|
|
258
|
+
["12", "341"])
|
|
259
|
+
self.assertEqual(mod.declared_close_targets("`closes #341`"), [])
|
|
260
|
+
|
|
254
261
|
def test_profile_scratch_and_generated_evidence_share_safe_removal_contract(self):
|
|
255
262
|
wrapup = load_wrapup()
|
|
256
263
|
with tempfile.TemporaryDirectory() as tmp:
|
package/scripts/wrapup-land.py
CHANGED
|
@@ -423,6 +423,18 @@ def issue_from_branch(branch: str) -> str | None:
|
|
|
423
423
|
return m.group(2) if m else None
|
|
424
424
|
|
|
425
425
|
|
|
426
|
+
def declared_close_targets(body: str) -> list:
|
|
427
|
+
"""Close authority for Step 5b: the PR body's ACTIVE close keywords, parsed
|
|
428
|
+
by pr-body-check's one close grammar. A branch number is context, never
|
|
429
|
+
closure authority — branch-derived closing once shut a Program-PRD that a
|
|
430
|
+
PR deliberately referenced only via `Part of`."""
|
|
431
|
+
spec = importlib.util.spec_from_file_location(
|
|
432
|
+
"pr_body_check_grammar", Path(__file__).parent / "pr-body-check.py")
|
|
433
|
+
mod = importlib.util.module_from_spec(spec)
|
|
434
|
+
spec.loader.exec_module(mod)
|
|
435
|
+
return mod.active_close_targets(body)
|
|
436
|
+
|
|
437
|
+
|
|
426
438
|
def load_profile() -> dict:
|
|
427
439
|
try:
|
|
428
440
|
from board_config import load_board_config
|
|
@@ -1245,16 +1257,26 @@ def cmd_land(args) -> dict:
|
|
|
1245
1257
|
elif p.returncode != 0:
|
|
1246
1258
|
report["warnings"].append(f"branch -d {branch} refused: {(p.stderr or '').strip()[:200]}")
|
|
1247
1259
|
|
|
1248
|
-
# Step 5b — verify
|
|
1260
|
+
# Step 5b — verify declared auto-closes (backtick-swallowed `closes`
|
|
1261
|
+
# misses). Targets come from the merged PR body's close keywords, never
|
|
1262
|
+
# from the branch number.
|
|
1249
1263
|
issue = issue_from_branch(branch)
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1264
|
+
p = run(["gh", "pr", "view", pr, "--json", "body"])
|
|
1265
|
+
merged_body = (json.loads(p.stdout).get("body") or "") if p.returncode == 0 else ""
|
|
1266
|
+
targets = declared_close_targets(merged_body)
|
|
1267
|
+
if not targets:
|
|
1268
|
+
report["issue_close"] = "no close targets declared — nothing to verify"
|
|
1269
|
+
else:
|
|
1270
|
+
states = []
|
|
1271
|
+
for t in targets:
|
|
1272
|
+
q = run(["gh", "issue", "view", t, "--json", "state"])
|
|
1273
|
+
if q.returncode == 0 and json.loads(q.stdout)["state"] == "OPEN":
|
|
1274
|
+
run(["gh", "issue", "close", t, "-c",
|
|
1275
|
+
f"Merged via PR #{pr} — auto-close didn't fire; closed manually."])
|
|
1276
|
+
states.append(f"#{t} closed manually")
|
|
1277
|
+
elif q.returncode == 0:
|
|
1278
|
+
states.append(f"#{t} already closed")
|
|
1279
|
+
report["issue_close"] = " · ".join(states)
|
|
1258
1280
|
|
|
1259
1281
|
# Step 5c — local merged-branch sweep (-d only: unreachable-from-main is refused)
|
|
1260
1282
|
swept = []
|