Skip to content

Commit 35b4a2c

Browse files
fix(formatters): expand ExecPlans guidance injection
Why - Align generated AGENTS guidance with the updated ExecPlans, Milestones, and CLI authoring instructions. - Ensure existing repositories with older ExecPlans wording are upgraded to the new canonical block. What changed - Replaced the single-line ExecPlans insertion in clean_agentrules with a canonical multi-section block: - ExecPlans bullet guidance - Milestones subsection - CLI-first creation/archive commands for plans and milestones - Added required-line detection so idempotence checks validate the full canonical block. - Added replacement logic to update outdated existing ExecPlans sections under Development Principles. - Updated and expanded unit tests to cover: - insertion of the new block - idempotence when the full block already exists - replacement of legacy/outdated ExecPlans text Impact - AGENTS.md generation now injects the full ExecPlans guidance block requested by policy. - Existing AGENTS files with legacy ExecPlans wording are automatically normalized on regeneration. - No user-visible runtime behavior changes outside AGENTS formatting. Tests - pytest -q tests/unit/test_clean_agentrules.py - ruff check src/agentrules/core/utils/formatters/clean_agentrules.py tests/unit/test_clean_agentrules.py Notes - None
1 parent dce1184 commit 35b4a2c

2 files changed

Lines changed: 123 additions & 11 deletions

File tree

src/agentrules/core/utils/formatters/clean_agentrules.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,46 @@
2828
MARKDOWN_HEADING_PATTERN = re.compile(r'^(?P<hashes>#{1,6})\s+\S')
2929
EXECPLANS_HEADING = "## ExecPlans"
3030
EXECPLANS_GUIDANCE_LINE = (
31-
"When writing complex features or significant refactors, "
32-
"use an ExecPlan (as described in .agent/PLANS.md) from design to implementation."
31+
"- When writing complex features or refactors, use an ExecPlan "
32+
"(as described in `.agent/PLANS.md`) from design to implementation."
33+
)
34+
EXECPLANS_MILESTONES_HEADING = "### Milestones"
35+
EXECPLANS_MILESTONES_LINE = (
36+
"- When the feature or refactor your writing is significantly complex, "
37+
"disaggregate the ExecPlan into milestones "
38+
"(as described in `.agent/templates/MILESTONE_TEMPLATE.md`)"
39+
)
40+
EXECPLANS_CLI_HEADING = "### Prefer CLI creation over manual file creation:"
41+
EXECPLANS_CLI_LINES = (
42+
"* ExecPlan:",
43+
" * Create: `agentrules execplan new \"<title>\" --slug <short-slug> --ms <N>` "
44+
"(Use `--ms <N>` for deterministic `MS###` sequence assignment).",
45+
" * Archive: `agentrules execplan archive EP-YYYYMMDD-NNN`",
46+
"* Milestones:",
47+
" * Create: `agentrules execplan milestone new EP-YYYYMMDD-NNN \"<Milestone Title>\"`",
48+
" * Archive: `agentrules execplan milestone archive EP-YYYYMMDD-NNN --ms <N>`",
49+
)
50+
EXECPLANS_GUIDANCE_BLOCK = (
51+
EXECPLANS_HEADING,
52+
EXECPLANS_GUIDANCE_LINE,
53+
"",
54+
EXECPLANS_MILESTONES_HEADING,
55+
EXECPLANS_MILESTONES_LINE,
56+
"",
57+
EXECPLANS_CLI_HEADING,
58+
*EXECPLANS_CLI_LINES,
59+
)
60+
EXECPLANS_REQUIRED_LINES = (
61+
EXECPLANS_GUIDANCE_LINE,
62+
EXECPLANS_MILESTONES_HEADING,
63+
EXECPLANS_MILESTONES_LINE,
64+
EXECPLANS_CLI_HEADING,
65+
*EXECPLANS_CLI_LINES,
3366
)
3467

3568

3669
def _inject_execplans_guidance(content: str) -> tuple[str, bool, str]:
37-
if EXECPLANS_GUIDANCE_LINE in content:
70+
if all(line in content for line in EXECPLANS_REQUIRED_LINES):
3871
return content, False, "ExecPlans guidance already present."
3972

4073
lines = content.splitlines()
@@ -54,8 +87,7 @@ def _inject_execplans_guidance(content: str) -> tuple[str, bool, str]:
5487
[
5588
"# Development Principles",
5689
"",
57-
EXECPLANS_HEADING,
58-
EXECPLANS_GUIDANCE_LINE,
90+
*EXECPLANS_GUIDANCE_BLOCK,
5991
]
6092
)
6193
return "\n".join(lines) + "\n", True, "Added missing Development Principles section with ExecPlans guidance."
@@ -71,16 +103,41 @@ def _inject_execplans_guidance(content: str) -> tuple[str, bool, str]:
71103
break
72104

73105
section_lines = lines[heading_index:section_end]
74-
for line in section_lines:
106+
execplans_heading_index: int | None = None
107+
execplans_heading_level = 0
108+
for index, line in enumerate(section_lines):
75109
normalized = line.strip().lower().rstrip(":")
76110
if normalized == "## execplans" or normalized == "### execplans":
77-
return content, False, "ExecPlans heading already present under Development Principles."
111+
execplans_heading_index = heading_index + index
112+
heading_match = MARKDOWN_HEADING_PATTERN.match(line.strip())
113+
if heading_match:
114+
execplans_heading_level = len(heading_match.group("hashes"))
115+
break
116+
117+
if execplans_heading_index is not None:
118+
execplans_block_end = section_end
119+
for index in range(execplans_heading_index + 1, section_end):
120+
line = lines[index]
121+
heading_match = MARKDOWN_HEADING_PATTERN.match(line.strip())
122+
if not heading_match:
123+
continue
124+
if len(heading_match.group("hashes")) <= execplans_heading_level:
125+
execplans_block_end = index
126+
break
127+
128+
replacement_lines = list(EXECPLANS_GUIDANCE_BLOCK)
129+
updated_lines = lines[:execplans_heading_index] + replacement_lines + lines[execplans_block_end:]
130+
return (
131+
"\n".join(updated_lines) + "\n",
132+
True,
133+
"Updated existing ExecPlans guidance under Development Principles.",
134+
)
78135

79136
insert_at = section_end
80137
while insert_at > heading_index and not lines[insert_at - 1].strip():
81138
insert_at -= 1
82139

83-
insertion_lines = ["", EXECPLANS_HEADING, EXECPLANS_GUIDANCE_LINE, ""]
140+
insertion_lines = ["", *EXECPLANS_GUIDANCE_BLOCK, ""]
84141
updated_lines = lines[:insert_at] + insertion_lines + lines[section_end:]
85142
return "\n".join(updated_lines) + "\n", True, "Added ExecPlans guidance under Development Principles."
86143

tests/unit/test_clean_agentrules.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ def test_inserts_execplans_under_development_principles(self) -> None:
2727
self.assertIn("Added ExecPlans guidance", message)
2828
self.assertIn("## ExecPlans", content)
2929
self.assertIn(
30-
"When writing complex features or significant refactors, use an ExecPlan",
30+
"- When writing complex features or refactors, use an ExecPlan",
31+
content,
32+
)
33+
self.assertIn("### Milestones", content)
34+
self.assertIn(
35+
"disaggregate the ExecPlan into milestones",
36+
content,
37+
)
38+
self.assertIn("### Prefer CLI creation over manual file creation:", content)
39+
self.assertIn(
40+
"`agentrules execplan new \"<title>\" --slug <short-slug> --ms <N>`",
3141
content,
3242
)
3343
self.assertLess(content.index("## ExecPlans"), content.index("# 2. TEMPORAL FRAMEWORK"))
@@ -40,8 +50,20 @@ def test_is_idempotent_when_guidance_already_exists(self) -> None:
4050
"# Development Principles:\n"
4151
"- Keep changes maintainable.\n\n"
4252
"## ExecPlans\n"
43-
"When writing complex features or significant refactors, "
44-
"use an ExecPlan (as described in .agent/PLANS.md) from design to implementation.\n\n"
53+
"- When writing complex features or refactors, use an ExecPlan "
54+
"(as described in `.agent/PLANS.md`) from design to implementation.\n\n"
55+
"### Milestones\n"
56+
"- When the feature or refactor your writing is significantly complex, "
57+
"disaggregate the ExecPlan into milestones "
58+
"(as described in `.agent/templates/MILESTONE_TEMPLATE.md`)\n\n"
59+
"### Prefer CLI creation over manual file creation:\n"
60+
"* ExecPlan:\n"
61+
" * Create: `agentrules execplan new \"<title>\" --slug <short-slug> --ms <N>` "
62+
"(Use `--ms <N>` for deterministic `MS###` sequence assignment).\n"
63+
" * Archive: `agentrules execplan archive EP-YYYYMMDD-NNN`\n"
64+
"* Milestones:\n"
65+
" * Create: `agentrules execplan milestone new EP-YYYYMMDD-NNN \"<Milestone Title>\"`\n"
66+
" * Archive: `agentrules execplan milestone archive EP-YYYYMMDD-NNN --ms <N>`\n\n"
4567
"# 2. TEMPORAL FRAMEWORK\n"
4668
"It is February 2026.\n"
4769
)
@@ -70,6 +92,39 @@ def test_adds_section_when_development_principles_missing(self) -> None:
7092
self.assertIn("# Development Principles", content)
7193
self.assertIn("## ExecPlans", content)
7294

95+
def test_replaces_outdated_execplans_block(self) -> None:
96+
with tempfile.TemporaryDirectory() as tmpdir:
97+
rules_path = Path(tmpdir) / "AGENTS.md"
98+
rules_path.write_text(
99+
(
100+
"You are a coding agent.\n\n"
101+
"# Development Principles:\n"
102+
"- Keep changes maintainable.\n\n"
103+
"## ExecPlans\n"
104+
"When writing complex features or significant refactors, "
105+
"use an ExecPlan (as described in .agent/PLANS.md) from design to implementation.\n\n"
106+
"# 2. TEMPORAL FRAMEWORK\n"
107+
"It is February 2026.\n"
108+
),
109+
encoding="utf-8",
110+
)
111+
112+
success, message = ensure_execplans_guidance(tmpdir, filename="AGENTS.md")
113+
content = rules_path.read_text(encoding="utf-8")
114+
115+
self.assertTrue(success)
116+
self.assertIn("Updated existing ExecPlans guidance", message)
117+
self.assertIn(
118+
"- When writing complex features or refactors, use an ExecPlan",
119+
content,
120+
)
121+
self.assertIn("### Milestones", content)
122+
self.assertIn("### Prefer CLI creation over manual file creation:", content)
123+
self.assertNotIn(
124+
"When writing complex features or significant refactors, use an ExecPlan",
125+
content,
126+
)
127+
73128

74129
if __name__ == "__main__":
75130
unittest.main()

0 commit comments

Comments
 (0)