Skip to content

Commit 84df982

Browse files
authored
Fix remaining agent-context review findings
1 parent 2e822be commit 84df982

5 files changed

Lines changed: 56 additions & 26 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ Implementation: Extends `YamlIntegration` (parallel to `TomlIntegration`):
399399
## Common Pitfalls
400400

401401
1. **Using shorthand keys for CLI-based integrations**: For CLI-based integrations (`requires_cli: True`), the `key` must match the executable name (e.g., `"cursor-agent"` not `"cursor"`). `shutil.which(key)` is used for CLI tool checks — mismatches require special-case mappings. IDE-based integrations (`requires_cli: False`) are not subject to this constraint.
402-
2. **Forgetting context configuration**: The bundled `agent-context` extension reads from `.specify/init-options.json`. New integrations only need to set `context_file` on the class — markers and dispatcher scripts are managed centrally.
402+
2. **Forgetting context configuration**: The bundled `agent-context` extension reads from `.specify/extensions/agent-context/agent-context-config.yml`. New integrations only need to set `context_file` on the class — markers and dispatcher scripts are managed centrally.
403403
3. **Incorrect `requires_cli` value**: Set to `True` only for agents that have a CLI tool; set to `False` for IDE-based agents.
404404
4. **Wrong argument format**: Use `$ARGUMENTS` for Markdown agents, `{{args}}` for TOML agents.
405405
5. **Skipping registration**: The import and `_register()` call in `_register_builtins()` must both be added.

extensions/agent-context/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ context_markers:
3535
3636
- `context_file` — the project-relative path to the coding agent context file, written by `specify init` and `specify integration install`.
3737
- `context_markers.start` / `.end` — the delimiters around the managed section. Edit these to use custom markers.
38+
- Runtime note: the bundled update scripts require Python 3 for YAML/upsert processing (PowerShell can also use `ConvertFrom-Yaml` when available).
3839

3940
## Disable
4041

extensions/agent-context/scripts/bash/update-agent-context.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ elif command -v python >/dev/null 2>&1 && python --version 2>&1 | grep -q "^Pyth
3535
fi
3636

3737
if [[ -z "$_python" ]]; then
38-
echo "agent-context: Python 3 not found on PATH; cannot parse extension config." >&2
39-
exit 1
38+
echo "agent-context: Python 3 not found on PATH; skipping update." >&2
39+
exit 0
4040
fi
4141

4242
# Parse extension config once; emit three newline-separated fields:

extensions/agent-context/scripts/powershell/update-agent-context.ps1

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,49 @@ if (-not (Test-Path -LiteralPath $ExtConfig)) {
3030
try {
3131
$Options = Get-Content -LiteralPath $ExtConfig -Raw | ConvertFrom-Yaml -ErrorAction Stop
3232
} catch {
33-
# ConvertFrom-Yaml may not be available on all systems; fall back to a
34-
# simple line-by-line YAML parser for the keys we need.
35-
$Options = @{}
36-
$inMarkers = $false
37-
foreach ($line in Get-Content -LiteralPath $ExtConfig) {
38-
if ($line -match '^context_file:\s*(.*)$') {
39-
$Options['context_file'] = $Matches[1].Trim().Trim('"').Trim("'")
40-
} elseif ($line -match '^context_markers:') {
41-
$inMarkers = $true
42-
} elseif ($inMarkers -and $line -match '^\s+start:\s*(.+)$') {
43-
if (-not $Options.ContainsKey('context_markers')) { $Options['context_markers'] = @{} }
44-
$Options['context_markers']['start'] = $Matches[1].Trim().Trim('"').Trim("'")
45-
} elseif ($inMarkers -and $line -match '^\s+end:\s*(.+)$') {
46-
if (-not $Options.ContainsKey('context_markers')) { $Options['context_markers'] = @{} }
47-
$Options['context_markers']['end'] = $Matches[1].Trim().Trim('"').Trim("'")
48-
} elseif ($inMarkers -and $line -match '^[a-z]') {
49-
$inMarkers = $false
33+
# ConvertFrom-Yaml may not be available on all systems.
34+
# Fall back to Python+PyYAML for consistent parsing semantics.
35+
$pythonCmd = $null
36+
foreach ($candidate in @('python3', 'python')) {
37+
if (Get-Command $candidate -ErrorAction SilentlyContinue) {
38+
$pythonCmd = $candidate
39+
break
5040
}
5141
}
42+
43+
if ($pythonCmd) {
44+
try {
45+
$jsonOut = & $pythonCmd -c @'
46+
import json
47+
import sys
48+
try:
49+
import yaml
50+
except Exception:
51+
yaml = None
52+
53+
try:
54+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
55+
data = yaml.safe_load(fh) if yaml else {}
56+
except Exception:
57+
data = {}
58+
59+
if not isinstance(data, dict):
60+
data = {}
61+
62+
print(json.dumps(data))
63+
'@ $ExtConfig
64+
if ($LASTEXITCODE -eq 0 -and $jsonOut) {
65+
$Options = $jsonOut | ConvertFrom-Json -AsHashtable -ErrorAction Stop
66+
}
67+
} catch {
68+
$Options = $null
69+
}
70+
}
71+
72+
if (-not $Options) {
73+
Write-Warning "agent-context: unable to parse $ExtConfig; skipping update."
74+
exit 0
75+
}
5276
}
5377

5478
$ContextFile = $Options['context_file']
@@ -70,11 +94,14 @@ if ($cm) {
7094
}
7195

7296
if (-not $PlanPath) {
73-
# Discover plan.md one level deep (specs/<feature>/plan.md), matching the
74-
# bash glob specs/*/plan.md. Wrap in try/catch so access errors under
97+
# Discover plan.md exactly one level deep (specs/<feature>/plan.md),
98+
# matching the bash glob specs/*/plan.md. Wrap in try/catch so access errors under
7599
# $ErrorActionPreference = 'Stop' don't abort the script.
76100
try {
77-
$candidate = Get-ChildItem -Path (Join-Path $ProjectRoot 'specs') -Filter 'plan.md' -Recurse -Depth 1 -ErrorAction SilentlyContinue |
101+
$specsDir = Join-Path $ProjectRoot 'specs'
102+
$candidate = Get-ChildItem -Path $specsDir -Directory -ErrorAction SilentlyContinue |
103+
ForEach-Object { Get-Item -LiteralPath (Join-Path $_.FullName 'plan.md') -ErrorAction SilentlyContinue } |
104+
Where-Object { $_ } |
78105
Sort-Object LastWriteTime -Descending |
79106
Select-Object -First 1
80107
if ($candidate) {

src/specify_cli/integrations/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,8 @@ def upsert_context_section(
579579
managed section. If it exists, the content between the configured
580580
start/end markers (default ``<!-- SPECKIT START -->`` /
581581
``<!-- SPECKIT END -->``) is replaced, or appended when no markers
582-
are found. Markers are read from ``.specify/init-options.json``
582+
are found. Markers are read from the agent-context extension config
583+
(``.specify/extensions/agent-context/agent-context-config.yml``)
583584
when present, falling back to the class-level constants.
584585
585586
Returns the path to the context file, or ``None`` when
@@ -656,8 +657,9 @@ def remove_context_section(self, project_root: Path) -> bool:
656657
"""Remove the managed section from the agent context file.
657658
658659
Returns ``True`` if the section was found and removed. If the
659-
file becomes empty (or whitespace-only) after removal it is
660-
deleted. Markers are read from ``.specify/init-options.json``
660+
file becomes empty (or whitespace-only) after removal it is deleted.
661+
Markers are read from the agent-context extension config
662+
(``.specify/extensions/agent-context/agent-context-config.yml``)
661663
when present, falling back to the class-level constants.
662664
"""
663665
if not self.context_file:

0 commit comments

Comments
 (0)