Commit Graph

4 Commits

Author SHA1 Message Date
Manfred Riem
52c0a5f88f fix: resolve command references per integration type (dot vs hyphen) (#2354)
* fix: resolve command references per integration type (dot vs hyphen)

Replace hardcoded /speckit.<cmd> references in templates with
__SPECKIT_COMMAND_<NAME>__ placeholders that are resolved at
setup time based on the integration type:

- Markdown/TOML/YAML agents: separator='.' → /speckit.plan
- Skills agents: separator='-' → /speckit-plan

Changes:
- Add resolve_command_refs() static method to IntegrationBase
- Add invoke_separator class attribute (.  for base, - for skills)
- Wire into process_template() as step 8
- Update _install_shared_infra() to process page templates
- Replace /speckit.* in 5 command templates and 3 page templates
- Add unit tests for resolve_command_refs (positive + negative)
- Add integration tests verifying on-disk content for all agents
- Add end-to-end CLI tests for Claude (skills) and Copilot (markdown)

Fixes #2347

* review: use effective_invoke_separator() for Copilot skills mode

Address PR review feedback: instead of bleeding _skills_mode
knowledge into the CLI layer, add effective_invoke_separator()
method to IntegrationBase that accepts parsed_options.

CopilotIntegration overrides it to return "-" when skills
mode is requested. The CLI layer simply asks the integration
for its separator — no hasattr or _skills_mode coupling.

Also adds tests for the new method on both base and Copilot,
plus an end-to-end test for 'specify init --integration copilot
--integration-options --skills' verifying page templates get
hyphen refs.

* fix: build_command_invocation preserves full suffix for extension commands

Previously rsplit('.', 1)[-1] on 'speckit.git.commit' yielded
just 'commit', producing /speckit.commit instead of
/speckit.git.commit (or /speckit-git-commit for skills).

Fix: strip only the 'speckit.' prefix when present, then join
remaining segments with the appropriate separator.

Updated in IntegrationBase, SkillsIntegration, and
CopilotIntegration. Added tests for extension commands in
build_command_invocation across all three.

* fix: Copilot dispatch_command() preserves full extension command suffix

dispatch_command() had the same rsplit('.', 1)[-1] bug as
build_command_invocation() — speckit.git.commit would dispatch
as /speckit-commit instead of /speckit-git-commit in skills
mode, or --agent speckit.commit instead of speckit.git.commit
in default mode.
2026-04-24 10:04:14 -05:00
Manfred Riem
fc3d1244c0 fix: replace shell-based context updates with marker-based upsert (#2259)
* Replace shell-based context updates with marker-based upsert

Replace ~3500 lines of bash/PowerShell agent context update scripts
with a Python-based approach using <!-- SPECKIT START/END --> markers.

IntegrationBase now manages the agent context file directly:
- upsert_context_section(): creates or updates the marked section at
  init/install/switch time with a directive to read the current plan
- remove_context_section(): removes the section at uninstall, deleting
  the file only if it becomes empty
- __CONTEXT_FILE__ placeholder in command templates is resolved per
  integration so the plan command references the correct agent file
- context_file is persisted in init-options.json for extension access

The plan command template instructs the LLM to update the plan
reference between the markers in the agent context file.

Removed:
- scripts/bash/update-agent-context.sh (857 lines)
- scripts/powershell/update-agent-context.ps1 (515 lines)
- 56 integration wrapper scripts (update-context.sh/.ps1)
- templates/agent-file-template.md
- agent_scripts frontmatter key and {AGENT_SCRIPT} replacement logic
- update-context reference from integration.json
- tests/test_cursor_frontmatter.py (tested deleted scripts)

Added:
- upsert/remove context section methods on IntegrationBase
- __CONTEXT_FILE__ placeholder support in process_template()
- context_file field in init-options.json (init/switch/uninstall)
- Per-integration tests: context file correctness, plan reference,
  init-options persistence (78 new context_file tests)
- End-to-end CLI validation across all 28 integrations

* fix: search for end marker after start marker in context section methods

Address Copilot review: content.find(CONTEXT_MARKER_END) searched from
the start of the file rather than after the located start marker. If
the file contained a stray end marker before the start marker, the
wrong slice could be replaced.

Now both upsert_context_section() and remove_context_section() pass
start_idx as the second argument to find() and validate end_idx >
start_idx before performing the replacement.

* fix: address Copilot review feedback on context section handling

1. Fix grammar in _build_context_section() directive text — add commas
   for a complete sentence.

2. Resolve __CONTEXT_FILE__ in resolve_skill_placeholders() — skills
   generated via extensions/presets for codex/kimi now replace the
   placeholder using the context_file value from init-options.json.

3. Handle Cursor .mdc frontmatter — when creating a new .mdc context
   file, prepend alwaysApply: true YAML frontmatter so Cursor
   auto-loads the rules.

4. Fix empty-file leading newline — when the context file exists but
   is empty, write the section directly instead of prepending a blank
   line.

* fix: address second round of Copilot review feedback

1. Ensure .mdc frontmatter on existing files — upsert_context_section()
   now checks for missing YAML frontmatter on .mdc files during updates
   (not just creation), so pre-existing Cursor files get alwaysApply.

2. Guard against context_file=None — use 'or ""' instead of a default
   arg so explicit null values in init-options.json don't cause a
   TypeError in str.replace().

3. Clean up .mdc files on removal — remove_context_section() treats
   files containing only the Speckit-generated frontmatter block as
   empty, deleting them rather than leaving orphaned frontmatter.

* fix: address third round of Copilot review feedback

1. CRLF-safe .mdc frontmatter check — use lstrip().startswith('---')
   instead of startswith('---\n') so CRLF files don't get duplicate
   frontmatter.

2. CRLF-safe .mdc removal check — normalize line endings before
   comparing against the sentinel frontmatter string.

3. Call remove_context_section() during integration_uninstall() — the
   manifest-only uninstall was leaving the managed SPECKIT markers
   behind in the agent context file.

4. Fix stale docstring — remove 'agent_scripts' mention from
   test_lean_commands_have_no_scripts().

* fix: address fourth round of Copilot review feedback

1. Remove unused script_type parameter from _write_integration_json()
   and all 3 call sites — the parameter was no longer referenced after
   the update-context script removal.

2. Fix _build_context_section() docstring — correct example path from
   '.specify/plans/plan.md' to 'specs/<feature>/plan.md'.

3. Improve .mdc frontmatter-only detection in remove_context_section()
   — use regex to match any YAML frontmatter block (not just the exact
   Speckit-generated one), so .mdc files with additional frontmatter
   keys are also cleaned up when no body content remains.

* fix: handle corrupted markers and parse .mdc frontmatter robustly

1. Handle partial/corrupted markers in upsert_context_section() —
   if only the START marker exists (no END), replace from START
   through EOF. If only the END marker exists, replace from BOF
   through END. This keeps upsert idempotent even when a user
   accidentally deletes one marker.

2. Parse .mdc YAML frontmatter properly — new _ensure_mdc_frontmatter()
   helper parses existing frontmatter and ensures alwaysApply: true is
   set, rather than just checking for the --- delimiter. Handles
   missing frontmatter, existing frontmatter without alwaysApply, and
   already-correct frontmatter.

* fix: preserve .mdc frontmatter, add tests, clean up on switch

1. Rewrite _ensure_mdc_frontmatter() with regex — preserves comments,
   formatting, and custom keys in existing frontmatter instead of
   destructively re-serializing via yaml.safe_dump(). Inserts or
   fixes alwaysApply: true in place.

2. Add 6 focused .mdc frontmatter tests to cursor-agent test file:
   new file creation, missing frontmatter, preserved custom keys,
   wrong alwaysApply value, idempotent upserts, removal cleanup.

3. Call remove_context_section() during integration switch Phase 1 —
   prevents stale SPECKIT markers from being left in the old
   integration's context file. Also clear context_file from
   init-options during the metadata reset.

* fix: remove unused MDC_FRONTMATTER, preserve inline comments, normalize bare CR

1. Remove unused MDC_FRONTMATTER class variable — dead code after
   _ensure_mdc_frontmatter() was rewritten with regex.

2. Preserve inline comments when fixing alwaysApply — the regex
   substitution now captures trailing '# comment' text and keeps it.

3. Normalize bare CR in upsert_context_section() — match the
   behavior of remove_context_section() which already normalizes
   both CRLF and bare CR.

4. Clarify .mdc removal comment — 'treat frontmatter-only as empty'
   instead of misleading 'strip frontmatter'.

* fix: handle corrupted markers in remove, CRLF-safe end-marker consumption

1. Handle corrupted markers in remove_context_section() — mirror
   upsert's behavior: start-only removes start→EOF, end-only removes
   BOF→end. Previously bailed out leaving partial markers behind.

2. CRLF-safe end-marker consumption — both upsert and remove now
   handle \r\n after the end marker, not just \n. Prevents extra
   blank lines at replacement boundaries in CRLF files.

3. Clarify path rule in plan template — distinguish filesystem
   operations (absolute paths) from documentation/agent context
   references (project-relative paths).

* fix: only remove context section when both markers are well-ordered

remove_context_section() previously treated mismatched markers as
corruption and aggressively removed from BOF→end-marker or
start-marker→EOF, which could delete user-authored content if only
one marker remained. Now it only removes when both START and END
markers exist and are properly ordered, returning False otherwise.
2026-04-17 13:57:51 -05:00
toxicafunk
71143598be fix(forge): use hyphen notation in frontmatter name field (#2075)
* fix(forge): use hyphen notation in frontmatter name field

- Changed injected name field from 'speckit.{command}' to 'speckit-{command}'
- Keeps standard filename format 'speckit.{command}.md'
- Aligns with Forge's command naming convention requirements
- All tests pass

* feat(forge): centralize name formatting to fix extension/preset command names

Address PR feedback by centralizing Forge command name formatting to ensure
consistent hyphenated names across both core template setup and
extension/preset command registration.

Changes:
- Add format_forge_command_name() utility function in forge integration
- Update ForgeIntegration._apply_forge_transformations() to use centralized formatter
- Add _format_name_for_agent() helper in CommandRegistrar to apply agent-specific formatting
- Update CommandRegistrar.register_commands() to format names for Forge (both primary commands and aliases)
- Add comprehensive test coverage for the formatter and registrar behavior

Impact:
- Extension commands installed for Forge now use 'name: speckit-my-extension-example'
  instead of 'name: speckit.my-extension.example'
- Fixes ZSH/shell compatibility issues with dot notation in command names
- Maintains backward compatibility for all other agents (they continue using dot notation)
- Eliminates duplication between integration setup and registrar paths

Example transformation:
  Before: name: speckit.jira.sync-status  (breaks in ZSH/Forge)
  After:  name: speckit-jira-sync-status  (works everywhere)

Fixes inconsistency where core templates used hyphens but extension/preset
commands preserved dots, breaking Forge's naming requirements.

* refactor(forge): move name formatting logic to integration module

Move _format_name_for_agent function logic into Forge integration's
registrar_config as a 'format_name' callback, improving separation of
concerns and keeping Forge-specific logic within its integration module.

Changes:
- Remove _format_name_for_agent() from agents.py (shared module)
- Add 'format_name' callback to Forge's registrar_config pointing to format_forge_command_name
- Update CommandRegistrar to use format_name callback when available
- Maintains same behavior: Forge commands use hyphenated names, others use dot notation

Benefits:
- Better encapsulation: Forge-specific logic lives in forge integration
- More extensible: Other integrations can provide custom formatters via registrar_config
- Cleaner separation: agents.py doesn't need to know about specific agent requirements

* fix(forge): make format_forge_command_name idempotent

Handle already-hyphenated names (speckit-foo) to prevent double-prefixing
(speckit-speckit-foo). The function now returns already-formatted names
unchanged, making it safe to call multiple times.

Changes:
- Add early return for names starting with 'speckit-'
- Update docstring to clarify accepted input formats
- Add examples showing idempotent behavior
- Add test coverage for idempotent behavior

Examples:
  format_forge_command_name('speckit-plan') -> 'speckit-plan' (unchanged)
  format_forge_command_name('speckit.plan') -> 'speckit-plan' (converted)
  format_forge_command_name('plan') -> 'speckit-plan' (prefixed)

* test(forge): strengthen name field assertions and clarify comments

Improve test_name_field_uses_hyphenated_format to fail loudly when
the name field is missing instead of silently passing.

Changes:
- Add explicit assertion that name_match is not None before validating value
- Ensures test fails if regex doesn't match (e.g., frontmatter rendering changes)
- Clarify Claude comment: it doesn't use inject_name path but SKILL.md
  frontmatter still includes hyphenated name via build_skill_frontmatter()

Before: Test would silently pass if 'name:' field was missing from frontmatter
After: Test explicitly asserts field presence before validating format

* docs(forge): clarify frontmatter name requirement and improve test isolation

Fix misleading docstring and improve test to properly validate that the
format_name callback is Forge-specific.

Changes to src/specify_cli/integrations/forge/__init__.py:
- Reword module docstring to clarify the requirement is specifically for
  the frontmatter 'name' field value, not command files or invocation
- Before: 'Requires hyphenated command names ... instead of dot notation'
  (implied dot notation unsupported overall)
- After: 'Uses a hyphenated frontmatter name value ... for shell compatibility'
  (clarifies it's the frontmatter field, and Forge still supports dot filenames)

Changes to tests/integrations/test_integration_forge.py:
- Replace Claude with Windsurf in test_registrar_does_not_affect_other_agents
- Claude uses build_skill_frontmatter() which always includes hyphenated names,
  so testing it didn't validate that format_name callback is Forge-only
- Windsurf is a standard markdown agent without inject_name
- Now asserts NO 'name:' field is present, proving format_name isn't invoked
- This properly validates the callback mechanism is isolated to Forge

* test(forge): use parse_frontmatter for precise YAML validation

Replace regex and string searches with CommandRegistrar.parse_frontmatter()
to validate only YAML frontmatter, not entire file content. Prevents false
positives if command body contains 'name:' lines.

Changes:
- test_forge_specific_transformations: Parse frontmatter dict instead of string search
- test_name_field_uses_hyphenated_format: Replace regex with frontmatter parsing
- test_registrar_formats_extension_command_names_for_forge: Use dict validation
- test_registrar_formats_alias_names_for_forge: Use dict validation

Benefits: More precise, robust against body content, better error messages,
consistent with existing codebase utilities.

---------

Co-authored-by: ericnoam <eric.rodriguez@leovegas.com>
2026-04-08 14:37:19 -05:00
Eric Rodriguez Suazo
b8e7851234 feat: add Forgecode agent support (#2034)
* feat: add Forgecode (forge) agent support

- 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

* fix: strip handoffs frontmatter and replace $ARGUMENTS for forgecode

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

* feat: add name field injection for forgecode agent

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.

* test: update test_argument_token_format for forgecode special case

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

* docs: add forgecode to README documentation

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.

* fix: show 'forge' binary name in user-facing messages for forgecode

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.

* refactor: rename forgecode agent key to forge

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.

* fix: ensure forge alias commands have correct name in frontmatter

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.

* feat: add forge to PowerShell script and fix test whitespace

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.

* fix: use .NET Regex.Replace for count-limited replacement in PowerShell

Addresses Copilot feedback: PowerShell's -replace operator does not
support a third argument for replacement count. Using it causes an
error or mis-parsing that would break forge package generation on
Windows.

Changed from:
  $content -replace '(?m)^---$', "---`nname: $cmdName", 1

To:
  $regex = [regex]'(?m)^---$'
  $content = $regex.Replace($content, "---`nname: $cmdName", 1)

The .NET Regex.Replace() method properly supports the count parameter,
ensuring the name field is injected only after the first frontmatter
delimiter (not the closing one).

This fix is critical for Windows users running:
  specify init --ai forge --offline

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: migrate Forge agent to Python integration system

- Create ForgeIntegration class with custom processing for {{parameters}}, handoffs stripping, and name injection
- Add update-context scripts (bash and PowerShell) for Forge
- Register Forge in integration registry
- Update AGENTS.md with Forge documentation and special processing requirements section
- Add comprehensive test suite (11 tests, all passing)

Closes migration from release packaging to Python-based scaffolding for Forge agent.

* 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.

* refactor: make ForgeIntegration extend MarkdownIntegration

- Change base class from IntegrationBase to MarkdownIntegration
- Eliminates ~30 lines of duplicated validation/setup boilerplate
- Aligns with the pattern used by 20+ other markdown agents (Bob, Claude, Windsurf, etc.)
- Update AGENTS.md to reflect new inheritance hierarchy
- All Forge-specific processing retained ({{parameters}}, handoffs stripping, name injection)
- All 535 integration tests pass

This addresses reviewer feedback about using the MarkdownIntegration convenience base class.

* style: remove trailing whitespace from test file

- Strip trailing spaces from blank lines in test_integration_forge.py
- Fixes W291 linting warnings
- No functional changes

* style: remove trailing whitespace from Forge integration

- Strip trailing spaces from blank lines in __init__.py
- Fixes whitespace on lines 20, 86, 90, 93, 139, 143
- Verified other files in forge/ directory have no trailing whitespace
- No functional changes, all tests pass

* test: derive expected commands from templates dynamically

- Remove hard-coded command count (9) and command set from test_directory_structure
- Use forge.list_command_templates() to derive expected commands
- Test now auto-syncs when core command templates are added/removed
- Prevents test breakage when template set changes
- All 11 tests pass

* fix: make Forge update-context scripts handle AGENTS.md directly

- Add fallback logic to update/create AGENTS.md when shared script doesn't support forge yet
- Check if shared dispatcher knows about 'forge' before delegating
- If shared script doesn't support forge, handle AGENTS.md updates directly:
  - Add Forge section to existing AGENTS.md if not present
  - Create new AGENTS.md with Forge section if file doesn't exist
- Both bash and PowerShell scripts implement same logic
- Prevents 'Unknown agent type' errors until shared scripts add forge support
- Future-compatible: automatically delegates when shared script supports forge

Addresses reviewer feedback about update-context scripts failing without forge support.

* feat: add Forge support to shared update-agent-context scripts

- Add forge case to bash and PowerShell update-agent-context scripts
- Add FORGE_FILE variable mapping to AGENTS.md (like opencode/codex/pi)
- Add forge to all usage/help text and ValidateSet parameters
- Include forge in update_all_existing_agents functions

Wrapper script improvements:
- Simplify Forge wrapper scripts to unconditionally delegate to shared script
- Remove complex fallback logic that created stub AGENTS.md files
- Add clear error messages if shared script is missing/not executable
- Align with pattern used by other integrations (opencode, bob, etc.)

Benefits:
- Plan command's {AGENT_SCRIPT} now works for Forge users
- No more incomplete/stub context files masking missing support
- Cleaner, more maintainable code (-39 lines in wrappers)
- Consistent architecture across all integrations

Update AGENTS.md to document that Forge integration ensures shared scripts
include forge support for context updates.

Addresses reviewer feedback about Forge support being incomplete for
workflow steps that run {AGENT_SCRIPT}.

* fix: resolve unbound variable and duplicate file update issues

- Fix undefined FORGE_FILE variable in bash update-agent-context.sh
  - Add missing FORGE_FILE definition pointing to AGENTS.md
  - Update comment to include Forge in list of agents sharing AGENTS.md
  - Prevents crash with 'set -u' when running without explicit agent type

- Add deduplication logic to PowerShell update-agent-context.ps1
  - Implement Update-IfNew helper to track processed files by real path
  - Prevents AGENTS.md from being rewritten multiple times
  - Matches existing deduplication behavior in bash script

- Prevent duplicate YAML keys in Forge frontmatter injection
  - Check for existing 'name:' field before injection in both scripts
  - PowerShell: Parse frontmatter to detect existing name field
  - Bash: Enhanced awk script to check frontmatter state
  - Future-proofs against template changes that add name fields

All scripts now have consistent behavior and proper error handling.

* fix: import timezone from datetime for rate limit header parsing

The _parse_rate_limit_headers() function uses timezone.utc on line 82
but timezone was never imported from datetime. This would raise a
NameError the first time GitHub API rate-limit headers are parsed.

Import timezone alongside datetime to fix the missing import.

* fix: correct variable scope in PowerShell deduplication and update docs

- Fix Update-IfNew in PowerShell update-agent-context.ps1
  - Changed from $script: scope to Set-Variable -Scope 1
  - Properly mutates parent function's local variables
  - Fixes deduplication tracking for shared AGENTS.md file
  - Prevents incorrect default Claude file creation

- Update create-release-packages.sh documentation
  - Add missing 'forge' to AGENTS list in header comment
  - Documentation now matches actual ALL_AGENTS array

Without this fix, AGENTS.md would be updated multiple times (once
for each agent sharing it: opencode, codex, amp, kiro, bob, pi, forge)
and the script would always create a default Claude file even when
agent files exist.

* fix: resolve missing scaffold_from_core_pack import in tests

The test_core_pack_scaffold.py imports scaffold_from_core_pack from
specify_cli, but that symbol does not exist in the current codebase.
This causes an ImportError when the test module is loaded.

Implement a resilient resolver that:
- Tries scaffold_from_core_pack first (expected name)
- Falls back to alternative names (scaffold_from_release_pack, etc.)
- Gracefully skips tests if no compatible entrypoint exists

This prevents import-time failures and makes the test future-proof
for when the actual scaffolding function is added or restored.

* fix: prevent duplicate path prefixes and consolidate shared file updates

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.

* refactor: remove unused rate-limit helpers and improve PowerShell scripts

- 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.

* fix: add missing 'forge' to PowerShell usage text and fix agent order

- 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.

* refactor: remove old architecture files deleted in b1832c9

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.

* refactor: remove unused timezone import from __init__.py

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.

* docs: clarify that handoffs is a Claude Code feature, not Forge's

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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-02 16:40:43 -05:00