Skip to content

feat: add Forgecode agent support#2034

Merged
mnriem merged 34 commits intogithub:mainfrom
toxicafunk:feature/add-forgecode-agent-support
Apr 2, 2026
Merged

feat: add Forgecode agent support#2034
mnriem merged 34 commits intogithub:mainfrom
toxicafunk:feature/add-forgecode-agent-support

Conversation

@ericnoam
Copy link
Copy Markdown
Contributor

@ericnoam ericnoam commented Mar 31, 2026

Description

This PR adds full support for the Forge agent (https://forgecode.dev/) to Spec Kit, enabling users to run the spec-driven development workflow using the forge CLI.

What Changed

Core Implementation:

  • Added Forge to AGENT_CONFIG in src/specify_cli/__init__.py and src/specify_cli/agents.py
  • Agent key matches CLI tool name: forge (following AGENTS.md design principle)
  • Configured Forge with special frontmatter handling:
    • strip_frontmatter_keys: ["handoffs"] - Strips Claude Code-specific handoffs field
    • inject_name: True - Automatically injects required name field
    • args: "{{parameters}}" - Uses Forge's parameter syntax instead of $ARGUMENTS
  • Added Forge case to bash release script (.github/workflows/scripts/create-release-packages.sh)
  • Added Forge case to PowerShell release script (.github/workflows/scripts/create-release-packages.ps1)

Test Updates:

  • Updated test_argument_token_format to handle Forge's {{parameters}} syntax
  • Added test_forge_name_field_in_frontmatter to validate frontmatter requirements
  • All tests passing (901/910, with 9 pre-existing failures unrelated to this PR)

Documentation:

  • Added Forge to README Supported AI Agents table
  • Added to --ai option descriptions
  • Added initialization examples
  • Added to CLI tools check list

Why This Is Needed

Forge is a popular AI coding assistant with a growing user base. Adding support allows Spec Kit users to leverage Forge's capabilities for spec-driven development workflows.

Technical Details

Forge-Specific Requirements:

  1. Name field injection: Forge requires both name and description fields in command frontmatter (unlike most agents that only need description)
  2. Handoffs stripping: The handoffs field from Claude Code templates caused Forge to hang during command listing
  3. Parameter syntax: Forge uses {{parameters}} instead of the standard $ARGUMENTS placeholder
  4. Agent key = CLI tool name: Uses forge as the agent key (not forgecode) to match the actual forge executable, following AGENTS.md design principle

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest (901/910 passing)
  • Tested with a sample project:
    • Ran specify init . --ai forge --offline successfully
    • Verified forge cmd list shows all 9 speckit commands without hanging
    • Confirmed command files have correct frontmatter (name + description only)
    • Verified {{parameters}} placeholder is correctly replaced
    • Confirmed user-facing messages show "forge" (not "forgecode")

Test Results:

# Initialization
specify init . --ai forge --offline  # ✅ Success

# Command listing
forge cmd list  # ✅ Shows all commands without hanging

# User-facing messages
specify check  # ✅ Shows "● forge (available)"

# Generated command frontmatter
---
name: speckit.plan
description: Execute the implementation planning workflow...
---

AI Disclosure

  • I did use AI assistance (describe below)

AI Assistance:
This PR was developed with the assistance of Forge agent (using the Claude Sonnet 4.5 model). The AI helped with:

  • Code implementation (Python, Bash, and PowerShell)
  • Test updates and validation
  • Documentation updates
  • Debugging and troubleshooting the hanging issue
  • UX improvements for binary name display
  • PR description writing

All code was reviewed, tested, and validated by the human contributor before committing.

Related Issues

N/A - This is a new feature addition


Note: This PR demonstrates Spec Kit's extensibility in supporting diverse AI agents with varying requirements and formats.

- Add 'forgecode' to AGENT_CONFIGS in agents.py with .forge/commands
  directory, markdown format, and {{parameters}} argument placeholder
- Add 'forgecode' to AGENT_CONFIG in __init__.py with .forge/ folder,
  install URL, and requires_cli=True
- Add forgecode binary check in check_tool() mapping agent key
  'forgecode' to the actual 'forge' CLI binary
- Add forgecode case to build_variant() in create-release-packages.sh
  generating commands into .forge/commands/ with {{parameters}}
- Add forgecode to ALL_AGENTS in create-release-packages.sh
The forgecode agent hangs when listing commands because the 'handoffs'
frontmatter field (a Claude Code-specific feature) contains 'send: true'
entries that forge tries to act on when indexing .forge/commands/ files.

Additionally, $ARGUMENTS in command bodies was never replaced with
{{parameters}}, so user input was not passed through to commands.

Python path (agents.py):
- Add strip_frontmatter_keys: [handoffs] to the forgecode AGENT_CONFIG
  entry so register_commands drops the key before rendering

Bash path (create-release-packages.sh):
- Add extra_strip_key parameter to generate_commands; pass 'handoffs'
  for the forgecode case in build_variant
- Use regex prefix match (~ "^"extra_key":") instead of exact
  equality to handle trailing whitespace after the YAML key
- Add sed replacement of $ARGUMENTS -> $arg_format in the body
  pipeline so {{parameters}} is substituted in forgecode command files
Forgecode requires both 'name' and 'description' fields in command
frontmatter. This commit adds automatic injection of the 'name' field
during command generation for forgecode.

Changes:
- Python (agents.py): Add inject_name: True to forgecode config and
  implement name injection logic in register_commands
- Bash (create-release-packages.sh): Add post-processing step to inject
  name field into frontmatter after command generation

This complements the existing handoffs stripping fix (d83be82) to fully
support forgecode command requirements.
Forgecode uses {{parameters}} instead of the standard $ARGUMENTS
placeholder. Updated test to check for the correct placeholder format
for forgecode agent.

- Added special case handling for forgecode in test_argument_token_format
- Updated docstring to document forgecode's {{parameters}} format
- Test now passes for all 26 agents including forgecode
Added forgecode agent to all relevant sections:
- Added to Supported AI Agents table
- Added to --ai option description
- Added to specify check command examples
- Added initialization example
- Added to CLI tools check list in detailed walkthrough

Forgecode is now fully documented alongside other supported agents.
@ericnoam ericnoam requested a review from mnriem as a code owner March 31, 2026 16:15
Copilot AI review requested due to automatic review settings March 31, 2026 16:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for the Forgecode agent (forge CLI) across Spec Kit’s scaffold generation, agent configuration, docs, and tests so users can run the spec-driven workflow using --ai forgecode.

Changes:

  • Added Forgecode agent configuration (dirs, args placeholder {{parameters}}, frontmatter adjustments) and CLI tool detection mapping (forgecodeforge).
  • Updated release packaging script to generate .forge/commands, strip problematic frontmatter keys, inject required name, and replace argument placeholders appropriately.
  • Updated tests and README to include Forgecode in supported agents and validate placeholder output.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/specify_cli/agents.py Adds Forgecode agent config and frontmatter/placeholder handling during command registration.
src/specify_cli/__init__.py Adds Forgecode to AGENT_CONFIG and maps forgecode tool checks to the forge binary.
.github/workflows/scripts/create-release-packages.sh Generates Forgecode release artifacts under .forge/commands, strips handoffs, injects name, and replaces placeholders.
tests/test_core_pack_scaffold.py Extends scaffold assertions to handle Forgecode’s {{parameters}} placeholder.
README.md Documents Forgecode support and updates CLI usage examples/lists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Addresses Copilot PR feedback: Users should see the actual executable
name 'forge' in status and error messages, not the agent key 'forgecode'.

Changes:
- Added 'cli_binary' field to forgecode AGENT_CONFIG (set to 'forge')
- Updated check_tool() to accept optional display_key parameter
- Updated check_tool() to use cli_binary from AGENT_CONFIG when available
- Updated check() command to display cli_binary in StepTracker
- Updated init() error message to show cli_binary instead of agent key

UX improvements:
- 'specify check' now shows: '● forge (available/not found)'
- 'specify init --ai forgecode' error shows: 'forge not found'
  (instead of confusing 'forgecode not found')

This makes it clear to users that they need to install the 'forge'
binary, even though they selected the 'forgecode' agent.
@ericnoam
Copy link
Copy Markdown
Contributor Author

✅ Addressed: Binary Name in User-Facing Messages

Copilot's Feedback:

check_tool("forgecode") correctly checks for the forge binary, but downstream user-facing output (StepTracker key/status, and init-time "not found" messages) will still refer to forgecode, which can be confusing when the actual executable users must install is forge.

Solution Implemented (commit c69893c):

I've added a generic cli_binary field to AGENT_CONFIG and updated all user-facing code paths to use it. This ensures users always see the actual binary name forge, never the internal agent key forgecode.

Changes Made:

  1. Added cli_binary field to forgecode's AGENT_CONFIG:

    "forgecode": {
        "name": "Forge",
        "cli_binary": "forge",  # The actual executable
        # ...
    }
  2. Updated check_tool() to accept display_key parameter and use cli_binary from config

  3. Updated check() command to display cli_binary in StepTracker

  4. Updated init() error message to show cli_binary instead of agent key

User Experience After Fix:

specify check output:

├── ● forge (available)

or

├── ● forge (not found)

specify init --ai forgecode error when forge not installed:

forge not found
Install from: https://forgecode.dev/docs/

Design Benefits:

This solution is generic and reusable - any future agent where the binary name differs from the agent key can simply add a cli_binary field. No special-case code needed!

Testing:

  • ✅ All 14 forgecode tests pass
  • ✅ Manual verification confirms correct binary names in all messages
  • specify check shows "forge" not "forgecode"

The confusion identified by Copilot has been completely eliminated. 🎉

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Mar 31, 2026

@ericnoam As per AGENTS.md, we adhere to IMPORTANT: Use the actual CLI tool name as the key, not a shortened version. so you'll have to adhere to that

@ericnoam
Copy link
Copy Markdown
Contributor Author

📌 Note: This Feedback Has Already Been Addressed

Hi Copilot! 👋 This feedback was fully resolved in commit c69893c which was pushed after you reviewed the initial code.

Your Concern:

downstream user-facing output (StepTracker key/status, and init-time "not found" messages) will still refer to forgecode

Our Fix:

We implemented your suggested mapping approach by adding a cli_binary field to AGENT_CONFIG.

Current State (Post-Fix):

All user-facing output now shows forge (not forgecode):

# specify check output
├── ● forge (available)    # ✅ Shows 'forge'
# or when not installed:
├── ● forge (not found)    # ✅ Shows 'forge'

# specify init error message
forge not found           # ✅ Shows 'forge'
Install from: https://forgecode.dev/docs/

Code Changes in c69893c:

  1. Added cli_binary: "forge" to AGENT_CONFIG
  2. Updated check_tool() to use cli_binary for binary lookup
  3. Updated check() to display cli_binary in StepTracker
  4. Updated init() error to show cli_binary instead of agent key

Verification:

✅ StepTracker key = forge (not forgecode)
✅ StepTracker status = forge (not found) (not forgecode (not found))
✅ init error = forge not found (not forgecode not found))
✅ Binary check uses shutil.which('forge') (not shutil.which('forgecode'))

No user-facing output refers to 'forgecode' anymore! 🎉

See my earlier comment for full details of the implementation.

Aligns with AGENTS.md design principle: "Use the actual CLI tool
name as the key, not a shortened version" (AGENTS.md:61-83).

The actual CLI executable is 'forge', so the AGENT_CONFIG key should
be 'forge' (not 'forgecode'). This follows the same pattern as other
agents like cursor-agent and kiro-cli.

Changes:
- Renamed AGENT_CONFIG key: "forgecode" → "forge"
- Removed cli_binary field (no longer needed)
- Simplified check_tool() - removed cli_binary lookup logic
- Simplified init() and check() - removed display_key mapping
- Updated all tests: test_forge_name_field_in_frontmatter
- Updated documentation: README.md

Code simplification:
- Removed 6 lines of workaround code
- Removed 1 function parameter (display_key)
- Eliminated all special-case logic for forge

Note: No backward compatibility needed - forge is a new agent
being introduced in this PR.
Copilot AI review requested due to automatic review settings March 31, 2026 17:58
@ericnoam ericnoam requested a review from mnriem March 31, 2026 18:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

When inject_name is enabled (for forge), alias command files must
have their own name field in frontmatter, not reuse the primary
command's name. This is critical for Forge's command discovery
and dispatch system.

Changes:
- For agents with inject_name, create a deepcopy of frontmatter
  for each alias and set the name to the alias name
- Re-render the command content with the alias-specific frontmatter
- Ensures each alias file has the correct name field matching its
  filename

This fixes command discovery issues where forge would try to invoke
aliases using the primary command's name.
@ericnoam
Copy link
Copy Markdown
Contributor Author

Please address Copilot feedback

Addressed

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/specify_cli/init.py:323

  • The runtime agent key is "forge", but the PR description uses --ai forgecode. There is currently no alias mapping for "forgecode" (AI_ASSISTANT_ALIASES only contains "kiro" -> "kiro-cli"), so specify init --ai forgecode will error. Consider either updating documentation/examples to consistently use forge, or adding a forgecode -> forge alias to match the branding/URL and avoid user confusion.
    "forge": {
        "name": "Forge",
        "folder": ".forge/",
        "commands_subdir": "commands",
        "install_url": "https://forgecode.dev/docs/",
        "requires_cli": True,
    },
    "generic": {
        "name": "Generic (bring your own agent)",
        "folder": None,  # Set dynamically via --ai-commands-dir
        "commands_subdir": "commands",
        "install_url": None,
        "requires_cli": False,
    },
}

AI_ASSISTANT_ALIASES = {
    "kiro": "kiro-cli",
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. PowerShell script (create-release-packages.ps1):
   - Added forge agent support for Windows users
   - Enables `specify init --ai forge --offline` on Windows
   - Enhanced Generate-Commands with ExtraStripKey parameter
   - Added frontmatter stripping for handoffs key
   - Added $ARGUMENTS replacement for {{parameters}}
   - Implemented forge case with name field injection
   - Complete parity with bash script

2. Test file (test_core_pack_scaffold.py):
   - Removed trailing whitespace from blank lines
   - Cleaner diffs and no linter warnings

Addresses Copilot PR feedback on both issues.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ericnoam
Copy link
Copy Markdown
Contributor Author

ericnoam commented Apr 2, 2026

Can you pull in the latest changes from main. You'll notice that you now only will need to deliver your integration and its tests class and hooking it in.

I merge it earlier but only modified those files where there was conflict

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

PowerShell release script:
- Add deduplication pass to Rewrite-Paths function
- Prevents .specify.specify/ double prefixes in generated commands
- Matches bash script behavior with regex '(?:\.specify/){2,}' -> '.specify/'

Bash update-agent-context script:
- Consolidate AGENTS.md updates to single call
- Remove redundant calls for $AMP_FILE, $KIRO_FILE, $BOB_FILE, $FORGE_FILE
- Update label to 'Codex/opencode/Amp/Kiro/Bob/Pi/Forge' to reflect all agents
- Prevents always-deduped $FORGE_FILE call that never executed

Both fixes improve efficiency and correctness while maintaining parity
between bash and PowerShell implementations.
@ericnoam ericnoam requested a review from mnriem April 2, 2026 20:12
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like you pulled in the latest from upstream/main?

Copilot AI review requested due to automatic review settings April 2, 2026 20:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 2, 2026

Can you pull in the latest changes from upstream/main ? You'll notice that you now only should have to deal with the ingration file and its test file. I appreciate you working through this. The timing just was a bit tricky for all of this. Thanks a LOT!

ericnoam added 2 commits April 2, 2026 22:43
…ipts

- Remove unused _parse_rate_limit_headers() and _format_rate_limit_error()
  from src/specify_cli/__init__.py (56 lines of dead code)
- Add GENRELEASES_DIR override support to PowerShell release script with
  comprehensive safety checks (parity with bash script)
- Remove redundant shared-file update calls from PowerShell agent context
  script (AMP_FILE, KIRO_FILE, BOB_FILE, FORGE_FILE all resolve to AGENTS.md)
- Update test docstring to accurately reflect Forge's {{parameters}} token

Changes align PowerShell scripts with bash equivalents and reduce maintenance
burden by removing dead code.
- Add 'forge' to usage message in Print-Summary (was missing from list)
- Reorder ValidateSet to match bash script order (vibe before qodercli)

This ensures PowerShell script documentation matches bash script and includes
all supported agents consistently.
@ericnoam
Copy link
Copy Markdown
Contributor Author

ericnoam commented Apr 2, 2026

Can you pull in the latest changes from upstream/main ? You'll notice that you now only should have to deal with the ingration file and its test file. I appreciate you working through this. The timing just was a bit tricky for all of this. Thanks a LOT!

You mean
Stage 6: Complete migration — remove legacy scaffold path (https://github.com/github/spec-kit/issues/1924) (https://github.com/github/spec-kit/pull/2063)

but now I wonder whether it be easier to delete this PR and start one from the new code...

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 2, 2026

Whichever way you decide I will work with you!

@ericnoam
Copy link
Copy Markdown
Contributor Author

ericnoam commented Apr 2, 2026

Whichever way you decide I will work with you!

Or...

  1. Rebase my branch on top of origin/main (which includes b1832c9)
  2. Drop all changes to deleted files (release scripts, scaffold tests, rate-limit helpers)
  3. Keep only Forge integration changes that align with the new architecture
  4. Verify Forge integration works with the new integration system

Remove files that were deleted in b1832c9 (Stage 6 migration) but remained
on this branch due to merge conflicts:

- Remove .github/workflows/scripts/create-release-packages.{sh,ps1}
  (replaced by inline release.yml + uv tool install)
- Remove tests/test_core_pack_scaffold.py
  (scaffold system removed, tests no longer relevant)

These files existed on the feature branch because they were modified before
b1832c9 landed. The merge kept our versions, but they should be deleted to
align with the new integration-only architecture.

This PR now focuses purely on adding NEW Forge integration support, not
restoring old architecture.
Remove unused timezone import that was added in 4a57f79 for rate-limit
header parsing but became obsolete when rate-limit helper functions were
removed in 59c4212 (and also removed in upstream b1832c9).

No functional changes - purely cleanup of unused import.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/specify_cli/integrations/forge/init.py:42

  • registrar_config declares strip_frontmatter_keys and inject_name, but ForgeIntegration.setup() / _apply_forge_transformations() currently hard-code stripping handoffs and always perform name injection logic instead of deriving behavior from those config fields. This duplication can drift over time (e.g., adding another key to strip would update registrar_config but not the transformation). Consider driving the transformations from self.registrar_config (keys-to-strip + inject toggle) so config remains the single source of truth.
    registrar_config = {
        "dir": ".forge/commands",
        "format": "markdown",
        "args": "{{parameters}}",
        "extension": ".md",
        "strip_frontmatter_keys": ["handoffs"],
        "inject_name": True,
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 2, 2026

Please address Copilot feedback. If not applicable, please explain why

Update docstrings to accurately explain that the 'handoffs' frontmatter key
is from Claude Code (for multi-agent collaboration) and is stripped because
it causes Forge to hang, not because it's a Forge-specific feature.

Changes:
- Module docstring: 'Forge-specific collaboration feature' → 'Claude Code feature that causes Forge to hang'
- Class docstring: Add '(incompatible with Forge)' clarification
- Method docstring: Add '(from Claude Code templates; incompatible with Forge)' context

This avoids implying that handoffs belongs to Forge when it actually comes
from spec-kit templates designed for Claude Code compatibility.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mnriem mnriem self-requested a review April 2, 2026 21:40
@mnriem mnriem merged commit b8e7851 into github:main Apr 2, 2026
12 checks passed
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 2, 2026

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants