@ikon85/agent-workflow-kit 0.28.0 → 0.29.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.
Files changed (32) hide show
  1. package/.agents/skills/board-to-waves/SKILL.md +13 -3
  2. package/.agents/skills/to-issues/SKILL.md +102 -27
  3. package/.agents/skills/to-prd/SKILL.md +13 -3
  4. package/.agents/skills/to-waves/SKILL.md +16 -4
  5. package/.agents/skills/wrapup/SKILL.md +0 -1
  6. package/.claude/skills/board-to-waves/SKILL.md +13 -3
  7. package/.claude/skills/codex-build/SKILL.md +69 -23
  8. package/.claude/skills/codex-review/SKILL.md +47 -37
  9. package/.claude/skills/grill-me-codex/SKILL.md +45 -34
  10. package/.claude/skills/grill-with-docs-codex/SKILL.md +46 -34
  11. package/.claude/skills/to-issues/SKILL.md +102 -27
  12. package/.claude/skills/to-prd/SKILL.md +13 -3
  13. package/.claude/skills/to-waves/SKILL.md +16 -4
  14. package/.claude/skills/wrapup/SKILL.md +0 -1
  15. package/README.md +23 -0
  16. package/agent-workflow-kit.package.json +56 -16
  17. package/docs/research/wave-152-consumer-acceptance.md +98 -0
  18. package/package.json +1 -1
  19. package/scripts/board-sync.py +3 -7
  20. package/scripts/build-kit.test.mjs +42 -1
  21. package/scripts/codex-exec-scenarios/fake-codex.mjs +152 -0
  22. package/scripts/codex-exec.sh +566 -0
  23. package/scripts/codex-exec.test.mjs +815 -0
  24. package/scripts/codex_proc.py +602 -0
  25. package/scripts/find-by-marker.py +68 -0
  26. package/scripts/marker_lib.py +88 -0
  27. package/scripts/render-anchor.py +111 -0
  28. package/scripts/test_marker_lib.py +154 -0
  29. package/scripts/test_render_anchor.py +267 -0
  30. package/scripts/test_retro_wrapup_contract.py +6 -0
  31. package/scripts/test_skill_codex_exec_lifecycle.py +123 -0
  32. package/src/lib/bundle.mjs +10 -0
@@ -0,0 +1,123 @@
1
+ """Guard the Claude-only cross-model skills' thin codex-exec lifecycle."""
2
+
3
+ import json
4
+ import re
5
+ import subprocess
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+
10
+ REPO = Path(__file__).resolve().parents[1]
11
+ MANIFEST = REPO / ".claude/skills/skill-manifest.json"
12
+ TARGETS = {
13
+ "codex-review": ("review", "read-only"),
14
+ "codex-build": ("build", "workspace-write"),
15
+ "grill-me-codex": ("review", "read-only"),
16
+ "grill-with-docs-codex": ("review", "read-only"),
17
+ }
18
+
19
+
20
+ class CodexExecSkillLifecycleTests(unittest.TestCase):
21
+ @classmethod
22
+ def setUpClass(cls):
23
+ manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))["skills"]
24
+ cls.skills = {}
25
+ for name, expected in TARGETS.items():
26
+ entry = manifest[name]
27
+ if entry["surfaces"] != ["claude"]:
28
+ raise AssertionError(f"{name} must remain Claude-only")
29
+ path = REPO / ".claude/skills" / name / "SKILL.md"
30
+ cls.skills[name] = (expected, path.read_text(encoding="utf-8"))
31
+
32
+ def test_all_four_skills_use_the_thin_wrapper_lifecycle(self):
33
+ self.assertEqual(len(self.skills), 4)
34
+ for name, ((profile, mode), body) in self.skills.items():
35
+ with self.subTest(skill=name):
36
+ self.assertIn(
37
+ f"scripts/codex-exec.sh new --profile {profile} --mode {mode}",
38
+ body,
39
+ )
40
+ self.assertRegex(body, r"scripts/codex-exec\.sh resume [\"']?\$RUN_ID")
41
+ self.assertRegex(body, r"scripts/codex-exec\.sh finalize [\"']?\$RUN_ID")
42
+ self.assertEqual(body.count("scripts/codex-exec.sh handle-failure"), 2)
43
+ self.assertNotRegex(
44
+ body,
45
+ r"scripts/codex-exec\.sh resume[^\n]*--mode",
46
+ "resume must inherit the persisted mode",
47
+ )
48
+
49
+ def test_new_and_resume_are_thin_ordered_and_valid_bash(self):
50
+ for name, (_, body) in self.skills.items():
51
+ blocks = re.findall(
52
+ r"```bash\n(if ROUND_RESULT=\$\(scripts/codex-exec\.sh (?:new|resume).*?\nfi)\n```",
53
+ body,
54
+ re.DOTALL,
55
+ )
56
+ self.assertEqual(len(blocks), 2, name)
57
+ for block in blocks:
58
+ first_line = block.splitlines()[0]
59
+ is_resume = "scripts/codex-exec.sh resume " in first_line
60
+ success, failure = block.split("\nelse\n", 1)
61
+ self.assertIn("CODEX_REPORT=", success)
62
+ self.assertNotIn("CODEX_REPORT=", failure)
63
+ self.assertIn("scripts/codex-exec.sh handle-failure", failure)
64
+ self.assertIn("|| :", failure)
65
+ self.assertNotIn("json.load", failure)
66
+ handler = failure.index("handle-failure")
67
+ surfaced = failure.index('printf \'%s\\n\' "$FAILURE_RESULT" >&2')
68
+ stopped = failure.index("exit 1")
69
+ self.assertLess(handler, surfaced)
70
+ self.assertLess(surfaced, stopped)
71
+ if is_resume:
72
+ self.assertIn('--run-id "$RUN_ID"', failure)
73
+ self.assertNotIn("\n RUN_ID=", success)
74
+ else:
75
+ self.assertNotIn("--run-id", failure)
76
+ self.assertLess(success.index("RUN_ID="), success.index("CODEX_REPORT="))
77
+ parsed = subprocess.run(
78
+ ["bash", "-n"],
79
+ input=block,
80
+ text=True,
81
+ capture_output=True,
82
+ check=False,
83
+ )
84
+ self.assertEqual(parsed.returncode, 0, parsed.stderr)
85
+
86
+ def test_all_skills_abort_known_run_on_orchestration_cancellation(self):
87
+ for name, (_, body) in self.skills.items():
88
+ with self.subTest(skill=name):
89
+ cancellation = re.search(
90
+ r"[Cc]ancellation or a decision to stop (?:orchestration|delegation)"
91
+ r".*?scripts/codex-exec\.sh abort \"\$RUN_ID\"",
92
+ body,
93
+ re.DOTALL,
94
+ )
95
+ self.assertIsNotNone(cancellation)
96
+ self.assertRegex(body, r"scripts/codex-exec\.sh finalize [\"']?\$RUN_ID")
97
+
98
+ build = self.skills["codex-build"][1]
99
+ gate = build.split("## Step 5 — Human gate", 1)[1].split("## Hard rules", 1)[0]
100
+ self.assertLess(
101
+ gate.index("Rejected with another requested fix"),
102
+ gate.index("Cancellation or a decision to stop delegation"),
103
+ )
104
+
105
+ def test_no_skill_reimplements_codex_process_or_failure_mechanics(self):
106
+ forbidden = {
107
+ "copied round function": re.compile(r"\brun_codex_round\b"),
108
+ "copied status parser": re.compile(r"\bROUND_STATUS\b|\.get\(\"status\""),
109
+ "copied run-id cleanup": re.compile(r"\bFAILED_RUN_ID\b"),
110
+ "raw codex exec": re.compile(r"(?m)^\s*codex\s+exec\b"),
111
+ "manual liveness sleep": re.compile(r"\bsleep\s+90\b"),
112
+ "manual sandbox override": re.compile(r"\bsandbox_mode\s*="),
113
+ "manual process probe": re.compile(r"\bkill\s+-0\b"),
114
+ "manual codex pid": re.compile(r"\bCODEX_PID\b"),
115
+ }
116
+ for name, (_, body) in self.skills.items():
117
+ for label, pattern in forbidden.items():
118
+ with self.subTest(skill=name, forbidden=label):
119
+ self.assertIsNone(pattern.search(body))
120
+
121
+
122
+ if __name__ == "__main__":
123
+ unittest.main()
@@ -31,6 +31,16 @@ export const HELPER_FILES = [
31
31
  // execute-ready-check.py (drift check). Library (imported, not run) → 0o644.
32
32
  // MUST ship or both ImportError on arrival.
33
33
  { path: 'scripts/issue_deps.py', kind: 'script', mode: 0o644 },
34
+ // Exact issue-identity marker grammar shared by board-sync.py and the
35
+ // all-state lookup CLI. Library → 0o644; invokable CLI → 0o755.
36
+ { path: 'scripts/marker_lib.py', kind: 'script', mode: 0o644 },
37
+ { path: 'scripts/find-by-marker.py', kind: 'script', mode: 0o755 },
38
+ // Structured Codex executor: shell entrypoint delegates process-group
39
+ // lifecycle to its stdlib-only Python library. The pure anchor renderer is
40
+ // invoked directly by to-issues. Entrypoints → 0o755; library → 0o644.
41
+ { path: 'scripts/codex-exec.sh', kind: 'script', mode: 0o755 },
42
+ { path: 'scripts/codex_proc.py', kind: 'script', mode: 0o644 },
43
+ { path: 'scripts/render-anchor.py', kind: 'script', mode: 0o755 },
34
44
  { path: 'scripts/board-sync.py', kind: 'script', mode: 0o755 },
35
45
  { path: 'scripts/execute-ready-check.py', kind: 'script', mode: 0o755 },
36
46
  { path: 'scripts/pr-body-check.py', kind: 'script', mode: 0o755 },