From 1294d43c24f30938a05a52d211939428ef349f2c Mon Sep 17 00:00:00 2001 From: ericnoam Date: Thu, 2 Apr 2026 15:51:03 +0200 Subject: [PATCH] fix: replace $ARGUMENTS with {{parameters}} in Forge templates - Add replacement of $ARGUMENTS to {{parameters}} after template processing - Use arg_placeholder from config (Copilot's cleaner approach) - Remove unused 'import re' from _apply_forge_transformations() - Enhance tests to verify $ARGUMENTS replacement works correctly - All 11 tests pass Fixes template processing to ensure Forge receives user-supplied parameters correctly. --- .../integrations/forge/__init__.py | 6 ++-- tests/integrations/test_integration_forge.py | 32 ++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/specify_cli/integrations/forge/__init__.py b/src/specify_cli/integrations/forge/__init__.py index da09da13b..b0036e08e 100644 --- a/src/specify_cli/integrations/forge/__init__.py +++ b/src/specify_cli/integrations/forge/__init__.py @@ -82,6 +82,10 @@ class ForgeIntegration(IntegrationBase): # Process template with Forge-specific argument placeholder processed = self.process_template(raw, self.key, script_type, arg_placeholder) + # Ensure any remaining $ARGUMENTS placeholders are converted to the + # Forge argument placeholder ({{parameters}} by default) + processed = processed.replace("$ARGUMENTS", arg_placeholder) + # Apply Forge-specific transformations processed = self._apply_forge_transformations(processed, src_file.stem) @@ -102,8 +106,6 @@ class ForgeIntegration(IntegrationBase): 1. Strip 'handoffs' frontmatter key 2. Inject 'name' field if missing """ - import re - # Parse frontmatter lines = content.split('\n') if not lines or lines[0].strip() != '---': diff --git a/tests/integrations/test_integration_forge.py b/tests/integrations/test_integration_forge.py index 9b8d9df1e..ab8750bd3 100644 --- a/tests/integrations/test_integration_forge.py +++ b/tests/integrations/test_integration_forge.py @@ -110,8 +110,8 @@ class TestForgeIntegration: assert "{SCRIPT}" not in content, f"{cmd_file.name} has unprocessed {{SCRIPT}}" assert "__AGENT__" not in content, f"{cmd_file.name} has unprocessed __AGENT__" assert "{ARGS}" not in content, f"{cmd_file.name} has unprocessed {{ARGS}}" - # Note: $ARGUMENTS may appear in template content (examples, instructions) - # The placeholder that gets replaced is {ARGS}, not $ARGUMENTS + # Check Forge-specific: $ARGUMENTS should be replaced with {{parameters}} + assert "$ARGUMENTS" not in content, f"{cmd_file.name} has unprocessed $ARGUMENTS" # Frontmatter sections should be stripped assert "\nscripts:\n" not in content assert "\nagent_scripts:\n" not in content @@ -134,11 +134,33 @@ class TestForgeIntegration: assert "\nhandoffs:" not in content, f"{cmd_file.name} has unstripped 'handoffs' key" def test_uses_parameters_placeholder(self, tmp_path): - """Verify Forge config specifies {{parameters}} as the args placeholder.""" + """Verify Forge replaces $ARGUMENTS with {{parameters}} in generated files.""" from specify_cli.integrations.forge import ForgeIntegration forge = ForgeIntegration() + # The registrar_config should specify {{parameters}} assert forge.registrar_config["args"] == "{{parameters}}" - # When process_template is called, it should replace {ARGS} with {{parameters}} - # Note: $ARGUMENTS in template content is intentional (examples/instructions) + # Generate files and verify $ARGUMENTS is replaced with {{parameters}} + from specify_cli.integrations.manifest import IntegrationManifest + m = IntegrationManifest("forge", tmp_path) + forge.setup(tmp_path, m) + commands_dir = tmp_path / ".forge" / "commands" + + # Check all generated command files + for cmd_file in commands_dir.glob("speckit.*.md"): + content = cmd_file.read_text(encoding="utf-8") + # $ARGUMENTS should be replaced with {{parameters}} + assert "$ARGUMENTS" not in content, ( + f"{cmd_file.name} still contains $ARGUMENTS - it should be replaced with {{{{parameters}}}}" + ) + # At least some files should have {{parameters}} (those with user input sections) + # We'll check the checklist file specifically as it has a User Input section + + # Verify checklist specifically has {{parameters}} in the User Input section + checklist = commands_dir / "speckit.checklist.md" + if checklist.exists(): + content = checklist.read_text(encoding="utf-8") + assert "{{parameters}}" in content, ( + "checklist should contain {{parameters}} in User Input section" + )