fix(agent-context): support multiple context files safely#2969
Open
AustinZ21 wants to merge 2 commits into
Open
fix(agent-context): support multiple context files safely#2969AustinZ21 wants to merge 2 commits into
AustinZ21 wants to merge 2 commits into
Conversation
Author
|
Hi maintainers, I've marked this PR as ready for review. It looks like the workflows are awaiting approval before the checks can run. Thanks for taking a look. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the bundled agent-context extension and related integration plumbing to support syncing the managed Spec Kit context block into multiple configured context files (via optional context_files), while keeping the legacy single context_file behavior as a fallback.
Changes:
- Add
context_filessupport across Python integration context upsert/remove, command/template rendering, and init-option updates (preserve non-empty lists across integration switches). - Update bash and PowerShell agent-context update scripts to iterate over all configured context files.
- Expand tests to cover multi-file upsert/remove, opt-out behavior, and path-safety rejection cases.
Show a summary per file
| File | Description |
|---|---|
| tests/integrations/test_integration_codex.py | Adds coverage ensuring Codex skill templates render multi-file context targets and ignore context_files when agent-context is disabled. |
| tests/extensions/test_extension_agent_context.py | Adds tests for multi-file upsert/remove behavior, invalid-path rejection, and opt-out behavior with stale config. |
| src/specify_cli/integrations/hermes/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/generic/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/forge/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/copilot/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/base.py | Implements context_files resolution + validation and updates context upsert/remove to operate over multiple files. |
| src/specify_cli/integrations/_helpers.py | Ensures integration switching/clearing handles context_files preservation/clearing appropriately. |
| src/specify_cli/agents.py | Updates SKILL placeholder resolution to prefer context_files when agent-context is enabled. |
| src/specify_cli/init.py | Extends agent-context config load/update helpers to understand/preserve context_files. |
| extensions/agent-context/scripts/powershell/update-agent-context.ps1 | Updates the updater to parse context_files and iterate updates over multiple files. |
| extensions/agent-context/scripts/bash/update-agent-context.sh | Updates the updater to parse context_files and iterate updates over multiple files. |
| extensions/agent-context/README.md | Documents multi-file configuration and opt-out behavior. |
| extensions/agent-context/commands/speckit.agent-context.update.md | Updates command docs to include context_files semantics and validation notes. |
| extensions/agent-context/agent-context-config.yml | Adds context_files to the default config template and clarifies semantics. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 15/15 changed files
- Comments generated: 5
Comment on lines
+759
to
+763
| def _context_file_display(self, project_root: Path) -> str: | ||
| """Return human-readable context file target(s) for templates.""" | ||
| if not self._agent_context_extension_enabled(project_root): | ||
| return self.context_file | ||
| return ", ".join(self._resolve_context_files(project_root)) |
Comment on lines
+412
to
+422
| ac_cfg = _load_agent_context_config(project_root) | ||
| context_files = ac_cfg.get("context_files") | ||
| if isinstance(context_files, list): | ||
| context_file_values = [ | ||
| value.strip() | ||
| for value in context_files | ||
| if isinstance(value, str) and value.strip() | ||
| ] | ||
| context_file = ", ".join(dict.fromkeys(context_file_values)) | ||
| if not context_file: | ||
| context_file = ac_cfg.get("context_file") or "" |
Comment on lines
+165
to
176
| foreach ($ContextFile in $ContextFiles) { | ||
| # Reject absolute paths and '..' path segments in context files | ||
| if ([System.IO.Path]::IsPathRooted($ContextFile)) { | ||
| Write-Warning "agent-context: context files must be project-relative paths; got '$ContextFile'." | ||
| exit 1 | ||
| } | ||
| $cfSegments = $ContextFile -split '[/\\]' | ||
| if ($cfSegments -contains '..') { | ||
| Write-Warning "agent-context: context files must not contain '..' path segments; got '$ContextFile'." | ||
| exit 1 | ||
| } | ||
| } |
Comment on lines
+194
to
+198
| for CONTEXT_FILE in "${CONTEXT_FILES[@]}"; do | ||
| CTX_PATH="$PROJECT_ROOT/$CONTEXT_FILE" | ||
| mkdir -p "$(dirname "$CTX_PATH")" | ||
|
|
||
| "$_python" - "$CTX_PATH" "$MARKER_START" "$MARKER_END" "$TMP_SECTION" <<'PY' |
Comment on lines
+220
to
+227
| foreach ($ContextFile in $ContextFiles) { | ||
| $CtxPath = Join-Path $ProjectRoot $ContextFile | ||
| $CtxDir = Split-Path -Parent $CtxPath | ||
| if ($CtxDir -and -not (Test-Path -LiteralPath $CtxDir)) { | ||
| New-Item -ItemType Directory -Path $CtxDir -Force | Out-Null | ||
| } | ||
|
|
||
| if (Test-Path -LiteralPath $CtxPath) { |
Author
|
Addressed the Copilot review feedback in commit Summary:
Validation:
Posted by @AustinZ21 with fixes prepared using Codex. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR lets the bundled
agent-contextextension manage more than one coding-agent context file from the same Spec Kit run.The motivating workflow is a repo that uses Claude Code and Codex interchangeably and wants Spec Kit's generated context block synced to both
CLAUDE.mdandAGENTS.md. The implementation keeps the existing singularcontext_filebehavior, while adding an optionalcontext_fileslist for projects that intentionally keep multiple agent anchors in sync.What Changed
context_filessupport to the agent-context config template.context_filesis configured.context_filesduring integration switches...path segmentsspecify extension disable agent-contextas a full opt-out: disabled projects skip upsert/removal and ignore stalecontext_filesduring command rendering.Why
Today Spec Kit stores one managed context block target in
context_file. That works well for a single coding agent, but mixed-agent projects can need more than one context anchor. For example, Codex readsAGENTS.md, while Claude-oriented setups often useCLAUDE.md. Without first-class multi-file support, teams have to choose one anchor or maintain duplicate context manually.This change makes the multi-agent case explicit and configurable without hard-coding any specific agent names or file paths.
Safety Notes
The script and Python integration paths now enforce matching path constraints so a configured context file cannot escape the project root. The disabled-extension path also avoids validating stale config before the opt-out gate, so disabling
agent-contextremains a complete opt-out even if old config contains invalid paths.Validation
bash -n extensions/agent-context/scripts/bash/update-agent-context.shpython -m compileall -q src\specify_clipytest tests/extensions/test_extension_agent_context.py -q50 passedpytest tests/integrations/test_integration_codex.py::TestCodexInitFlow::test_plan_skill_references_configured_context_files tests/integrations/test_integration_codex.py::TestCodexInitFlow::test_plan_skill_ignores_context_files_when_agent_context_disabled -q2 passedpytest tests/test_agent_config_consistency.py -q28 passedspecify --helpcontext_files: [AGENTS.md, CLAUDE.md]update-agent-context.ps1 specs/123-test/plan.mdcontext_files: [AGENTS.md, CLAUDE.md]update-agent-context.sh specs/123-test/plan.mdKnown Existing Test Failure
The broader Codex integration slice still has two inventory failures:
TestCodexIntegration.test_complete_file_inventory_shTestCodexIntegration.test_complete_file_inventory_psBoth failures reproduce on a clean
upstream/mainworktree at1b0556c, before this PR's changes. They expect bundledagent-contextextension artifacts in the generated project, but the generated project does not include them. The focused tests added by this PR pass.AI Assistance Disclosure
I used Codex/ChatGPT to inspect the integration paths, draft the implementation, and generate test coverage. I reviewed the code paths and validated the behavior locally with automated and manual checks.