Extract agent context updates into bundled agent-context extension#2546
Extract agent context updates into bundled agent-context extension#2546Copilot wants to merge 27 commits into
Conversation
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors agent-context-file management (the <!-- SPECKIT START --> / <!-- SPECKIT END --> block injected into files like CLAUDE.md) out of IntegrationBase and into a new bundled agent-context extension. Configuration now flows through .specify/init-options.json (context_file, context_markers), giving users opt-out (specify extension disable agent-context) and customizable markers, with parallel bash/PowerShell scripts that mirror the Python upsert logic.
Changes:
- Adds a bundled
agent-contextextension (manifest, README, command, bash + PowerShell scripts) and registers it inextensions/catalog.json. - Extends
IntegrationBasewith_resolve_context_markers()and_agent_context_extension_enabled(), gating bothupsert_context_section()andremove_context_section()on the registry, and seeds/clearscontext_markersininit-options.jsonfromspecify_cli.__init__. - Updates
AGENTS.mdand adds 25 tests covering layout, marker resolution, custom-marker upsert/remove, the disabled-gate, and init-options writers.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/integrations/base.py |
New marker-resolution + extension-enabled helpers; upsert/remove use them. |
src/specify_cli/__init__.py |
New tracker step that auto-installs bundled agent-context; seeds/clears context_markers. |
extensions/catalog.json |
Registers agent-context as bundled: true. |
extensions/agent-context/extension.yml |
Extension manifest with after_specify / after_plan hooks. |
extensions/agent-context/README.md |
User-facing docs for opt-out and configuration. |
extensions/agent-context/commands/speckit.agent-context.update.md |
Slash-command spec referencing the bash/ps1 scripts. |
extensions/agent-context/scripts/bash/update-agent-context.sh |
Bash hook that re-renders the managed section. |
extensions/agent-context/scripts/powershell/update-agent-context.ps1 |
PowerShell counterpart. |
AGENTS.md |
Removes wrapper-script guidance; documents the new init-options.json flow and opt-out. |
tests/extensions/test_extension_agent_context.py |
25 new tests for the extension and plumbing. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 7
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
- bash: parse init-options.json with a single python3 invocation instead of three separate read_json_field calls, for parity with the PowerShell ConvertFrom-Json approach and to avoid divergent error semantics - bash: use parameter expansion to strip PROJECT_ROOT prefix from plan path instead of sed interpolation, avoiding special-character fragility - powershell: limit Get-ChildItem to -Depth 1 so plan.md discovery matches the bash glob specs/*/plan.md (one level deep) — fixes cross-platform inconsistency with nested plan.md files - powershell: replace Substring+Length relative-path with [System.IO.Path]::GetRelativePath for robustness across case/PSDrive differences - __init__.py: move agent-context extension install to after save_init_options so init-options.json is present when hooks run - __init__.py: seed context_markers in init-options only when context_file is truthy; avoids noise for integrations without a context file - integrations/base.py: narrow blanket except Exception in _resolve_context_markers to ImportError / (OSError, ValueError) so unexpected bugs surface instead of being silently swallowed
…context_file Apply the same gating logic used during `specify init`: only write context_markers to init-options.json when the integration actually has a context_file set. When switching to an integration without a context file the stale markers are removed, keeping the two init paths consistent.
Applied all four changes in commits
|
# Conflicts: # src/specify_cli/__init__.py # src/specify_cli/integrations/base.py
…install, and init flow fix - Add constitution-loading directive to specify, clarify, tasks, checklist, taskstoissues commands - Add deprecation warning (v0.12.0) in upsert_context_section() - Auto-install agent-context extension during specify init - Move context_file from init-options.json to agent-context extension config - Add tests: deprecation warning, corrupt config, constitution directives - Update file inventories across all integration tests
- Move agent-context extension install after init-options.json is saved so skill registration can read ai_skills + integration key - Write extension config after install (avoids template overwriting context_file) - Fix test_defaults_when_markers_field_missing to truly test missing markers key - Update hermes tests to allow extension-installed agent-context skill
…AML key order - Move ensure_executable_scripts after agent-context extension install so extension scripts get execute bits set - Use preserve_markers=True on reinit to keep user-customized markers - Add Python 3 version check in PowerShell fallback (matching bash behavior) - Add sort_keys=False to yaml.safe_dump for stable config output
- Reject absolute paths and '..' segments in context_file in both bash and PowerShell scripts to prevent writes outside the project root - Fix docstring in _update_init_options_for_integration to accurately describe marker preservation behavior
…raversal - Use 'is not False' for enabled check so only literal False disables - Update upsert_context_section docstring to mention disabled-extension return - Fix path traversal guards to check actual path segments, not substrings (allows filenames like 'notes..md' while rejecting '../' traversal)
| # --- agent-context extension (bundled, auto-installed) --- | ||
| # Installed after init-options.json is written so that skill | ||
| # registration can read ai_skills + integration key. | ||
| try: | ||
| from ..extensions import ExtensionManager as _ExtMgr | ||
| bundled_ac = _locate_bundled_extension("agent-context") | ||
| if bundled_ac: | ||
| ac_mgr = _ExtMgr(project_path) | ||
| if ac_mgr.registry.is_installed("agent-context"): | ||
| tracker.complete("agent-context", "already installed") | ||
| else: | ||
| ac_mgr.install_from_directory( | ||
| bundled_ac, get_speckit_version() | ||
| ) | ||
| tracker.complete("agent-context", "extension installed") | ||
| else: | ||
| tracker.skip("agent-context", "bundled extension not found") | ||
| except Exception as ac_err: |
- Add UnicodeError to exception tuples in _load_agent_context_config and _resolve_context_markers so garbled UTF-8 config files fall back to defaults - Emit error (with reinstall command) instead of silent skip when bundled agent-context extension is not found during init
| # Reject absolute paths and '..' path segments in context_file | ||
| if [[ "$CONTEXT_FILE" == /* ]]; then | ||
| echo "agent-context: context_file must be a project-relative path; got '$CONTEXT_FILE'." >&2 | ||
| exit 1 | ||
| fi | ||
| IFS='/' read -ra _cf_parts <<< "$CONTEXT_FILE" | ||
| for _seg in "${_cf_parts[@]}"; do | ||
| if [[ "$_seg" == ".." ]]; then | ||
| echo "agent-context: context_file must not contain '..' path segments; got '$CONTEXT_FILE'." >&2 | ||
| exit 1 | ||
| fi | ||
| done | ||
| unset _cf_parts _seg |
| # --- agent-context extension (bundled, auto-installed) --- | ||
| # Installed after init-options.json is written so that skill | ||
| # registration can read ai_skills + integration key. | ||
| try: | ||
| from ..extensions import ExtensionManager as _ExtMgr | ||
| bundled_ac = _locate_bundled_extension("agent-context") | ||
| if bundled_ac: | ||
| ac_mgr = _ExtMgr(project_path) |
New Feature
Coding agent context file management (
<!-- SPECKIT START -->/<!-- SPECKIT END -->injection intoCLAUDE.md,.github/copilot-instructions.md, etc.) was hardcoded intoIntegrationBase, with no way to opt out or customize markers. This change moves that behavior into a bundledagent-contextextension driven by.specify/extensions/agent-context/agent-context-config.yml.What does this feature do?
Adds
extensions/agent-context/(bundled: true, opt-out) that owns the lifecycle of the managed context section. Both the Python paths and the new shell/PowerShell scripts readcontext_fileandcontext_markersfrom.specify/extensions/agent-context/agent-context-config.yml— single source of truth, no per-agent logic, user-customizable markers.Implementation details
integrations/base.py_resolve_context_markers(project_root)— readscontext_markersfromagent-context-config.yml, falls back toCONTEXT_MARKER_START/CONTEXT_MARKER_ENDconstants per side._agent_context_extension_enabled(project_root)— reads.specify/extensions/.registry; returnsTruewhen registry/entry absent (backwards compat) orenabledis not literallyFalse.upsert_context_section()/remove_context_section()now use the resolved markers and short-circuit when the extension is disabled.upsert_context_section()emits a deprecation warning (v0.12.0) since inline context updates are being replaced by the extension.specify_cli/commands/init.pyspecify initwritescontext_fileto the agent-context extension config (notinit-options.json).agent-contextextension duringspecify init(afterinit-options.jsonis saved, so skill registration works).ensure_executable_scriptsruns after extension install so extension scripts get execute bits.specify_cli/__init__.py_update_init_options_for_integration()writescontext_fileto the extension config, preserving user-customized markers._clear_init_options_for_integration()clearscontext_filein the extension config and removes legacy keys frominit-options.json._load_agent_context_config()/_save_agent_context_config()— helpers for reading/writing the extension config.extensions/agent-context/—extension.yml(idagent-context, hooksafter_specify/after_plan),README.md,commands/speckit.agent-context.update.md, and bash + PowerShellupdate-agent-contextscripts. Both scripts parseagent-context-config.yml, resolve markers (with default fallback), auto-detect the most recentspecs/*/plan.mdwhen no plan path is supplied, and perform the same upsert algorithm as the Python path (CRLF normalization, BOM strip, marker-corruption recovery). Scripts include path traversal guards rejecting absolute paths and..segments.extensions/catalog.json—agent-contextregistered asbundled: true, alphabetized first.AGENTS.md— wrapper-script and dispatcher-script guidance removed; replaced with theagent-context-config.ymlflow and thespecify extension disable agent-contextopt-out.Command templates — All 8 non-constitution commands (
specify,clarify,plan,tasks,checklist,implement,analyze,taskstoissues) now include a directive to load/memory/constitution.mdfor project principles and governance constraints.tests/extensions/test_extension_agent_context.py— 34 tests covering extension layout, catalog entry, marker resolution (defaults / custom / partial / invalid / corrupt YAML), upsert+remove with custom markers, the disabled-extension gate (upsertreturnsNone,removereturnsFalseand leaves file untouched), deprecation warning output, and extension config writers.tests/integrations/test_integration_generic.py— Parametrized test verifying all 8 commands referenceconstitution.md.Backwards compatibility
agent-context-config.ymlor missingcontext_markers→ class-constant defaults, identical to current behavior.context_fileininit-options.json→agents.pyfalls back to reading it when extension config is absent.agent-contextentry → treated as enabled, so existing projects continue to receive context updates without re-init.IntegrationBase.