Skip to content

fix: opt-in OpenCode discovery of hidden Spec Kit files - #5032

Open
mvanhorn wants to merge 4 commits into
github:mainfrom
mvanhorn:fix/5028-opencode-hidden-discovery
Open

mvanhorn wants to merge 4 commits into
github:mainfrom
mvanhorn:fix/5028-opencode-hidden-discovery

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Description

OpenCode's file search reports that the hidden Spec Kit project directory contains no files. Local and open-weight models then miss the constitution, templates, and scripts the Spec Kit commands need. Models that retry by reading a known path directly often continue anyway. OpenCode 1.18.31 and 1.19.3 run that glob through ripgrep without --hidden, so dot-directories are skipped. Re-including the tree on that path takes both !.specify/ and !.specify/** in the project-root .ignore; the contents rule alone still returns no .specify/**/* matches. The OpenCode integration only installed command files and never wrote those exceptions.

OpenCode install and upgrade accept an opt-in --configure-ignore-file flag that appends whichever of !.specify/ and !.specify/** is not already an exact line, keeps existing .ignore bytes as a prefix (including non-ASCII text, CRLF, and a missing final newline), and records the root file as partial ownership. Symlinks, non-files, and unwritable paths are rejected before any write. Uninstall, including with --force, leaves .ignore in place, and installs without the flag, along with other integrations, leave it untouched. Upgrade replays the stored flag, a new --integration-options value replaces the whole stored set, and the integrations reference documents the option and the checked ripgrep behavior.

Fixes #5028

Testing

  • Tested locally with uv run specify --help
    Ran uv run python -m pytest tests/test_agent_config_consistency.py -q locally on macOS for the spec-kit OpencodeIntegration change; it fails the same way on the base branch, so the failure predates this change (it fails the same way on upstream/main).
  • Ran existing tests with uv sync && uv run pytest
    uv sync && uv run pytest was not run locally for the spec-kit OpencodeIntegration change either.
  • Tested with a sample project (if applicable)
    No one has checked the spec-kit OpencodeIntegration change with a sample project yet.

Ran uv run python -m pytest tests/test_agent_config_consistency.py -q locally on macOS for the spec-kit OpencodeIntegration change; it fails the same way on the base branch, so the failure predates this change (it fails the same way on upstream/main). Tests for the spec-kit OpencodeIntegration change live in tests/integrations/test_integration_opencode.py.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

AI disclosure

AI was used for assistance.

  • Extent: AI wrote the code changes and drafted this description.

OpenCode install and upgrade accept an opt-in --configure-ignore-file
flag that appends whichever of !.specify/ and !.specify/** is not
already an exact line, keeps existing .ignore bytes as a prefix
(including non-ASCII text, CRLF, and a missing final newline), and
records the root file as partial ownership. Symlinks, non-files, and
unwritable paths are rejected before any write. Uninstall, including
with --force, leaves .ignore in place, and installs without the flag,
along with other integrations, leave it untouched. Upgrade replays the
stored flag, a new --integration-options value replaces the whole stored
set, and the integrations reference documents the option and the checked
ripgrep behavior.

Fixes github#5028

Assisted-by: AI
@mvanhorn
mvanhorn requested a review from mnriem as a code owner October 20, 2026 07:45
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Oct 20, 2026
@mnriem
mnriem requested a max review from Copilot October 20, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Write failures can destroy existing ignore rules, and rejected destinations can leave integration updates partially applied.

5 open issues
What changed in this PR

Adds an opt-in OpenCode integration option so file search can discover hidden Spec Kit project files.

Changes:

  • Adds .ignore exceptions with partial ownership tracking.
  • Tests preservation, upgrade persistence, uninstall, and failure handling.
  • Documents usage and checked OpenCode behavior.

Validation used isolated checks; project tests and baseline comparison were not run.

AI-generated for the requester by GitHub Copilot (model: unknown; autonomous review).

File Description
tests/​integrations/​test_integration_opencode.py Adds option and lifecycle coverage.
src/​specify_cli/​integrations/​opencode/​__init__.py Implements opt-in ignore configuration.
docs/​reference/​integrations.md Explains discovery behavior and usage.

Review effort: Max

Comment thread docs/reference/integrations.md Outdated
Comment thread docs/reference/integrations.md Outdated
specify integration upgrade opencode --integration-options="--configure-ignore-file"
```

The flag appends whichever of those two rules is not already an exact line, placing them at the end of the root `.ignore`. Bytes already in the file stay as a prefix, including non-ASCII text, CRLF, and a missing final newline. Default installs leave `.ignore` untouched. The file is recorded with partial ownership. `specify integration uninstall` and `specify integration uninstall --force` both leave it in place, exceptions included. Delete the two lines manually to restore the previous glob behavior.
Comment on lines +86 to +88
created = super().setup(
project_root, manifest, parsed_options=parsed_options, **opts
)
missing = [
rule
for rule in _SPECIFY_IGNORE_RULES
if rule not in _present_ignore_rules(raw.decode("utf-8"))
raise PermissionError(f"Cannot update .ignore at {path}")
if not path.exists() and not os.access(path.parent, os.W_OK):
raise PermissionError(f"Cannot create .ignore at {path}")
return manifest.record_file(".ignore", updated, ownership="partial")
@mnriem

mnriem commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback and fix test & lint errors

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Failed writes can destroy existing .ignore rules, and non-UTF-8 comments abort configuration.

4 open issues
4 previously missed issues

In code that hasn't changed since last review

Medium severity Deleting rules does not persistently opt out while OpenCode is installed

docs/​reference/​integrations.md:518

Deleting these lines is not a persistent opt-out while OpenCode remains installed. Partial ownership still hashes the entire .ignore, so any user edit blocks a normal upgrade (manifest.py:340–364, command_upgrade.py:80–87). Adding --force replays the stored flag and restores the rules; it also permits overwriting customized shared scripts and templates. Document the hash-tracking consequence and the safe order: first upgrade with options that omit this flag while preserving other options, then delete the lines. After uninstall, deleting them directly is sufficient.

AI-generated by GitHub Copilot for the review requester (model: unknown; autonomous).

Medium severity Non-UTF-8 .ignore content aborts install or upgrade

src/​specify_cli/​integrations/​opencode/​__init__.py:153

An existing .ignore with a non-UTF-8 comment, such as Latin-1 # café, raises UnicodeDecodeError here and aborts the opted-in install or upgrade. Only ASCII rules need recognition. Use lossless decoding for this membership check and add a non-UTF-8 preservation case; the original bytes can then remain untouched.

AI-generated by GitHub Copilot for the review requester (model: unknown; autonomous).

Medium severity Non-atomic .ignore writes can destroy existing user rules

src/​specify_cli/​integrations/​opencode/​__init__.py:192

record_file() calls Path.write_bytes() (manifest.py:208), which truncates the existing .ignore before writing. A write failure after opening can destroy user rules, and built-in-only installs/upgrades have no lifecycle journal to restore them (_lifecycle.py:468–469). The new disk-full mock raises before opening, so it misses this failure. Use an atomic replacement that preserves permissions, safe-path checks, and lifecycle notifications, and test a failure after output begins.

AI-generated by GitHub Copilot for the review requester (model: unknown; autonomous).

Medium severity Platform-dependent symlink and permission tests fail unexpectedly

tests/​integrations/​test_integration_opencode.py:341

Direct symlink creation can fail on Windows without the required privilege, and the permission-rejection assertions fail under root, which can still read and write these files. Either condition fails this combined test before its remaining cases run. Split the cases so only unsupported symlink and permission checks are skipped, following tests/integrations/test_integration_kimi.py:17–30 and tests/extensions/github/test_github_extension.py:1408–1411. Keep directory and write-failure checks platform-independent.

AI-generated by GitHub Copilot for the review requester (model: unknown; autonomous).

1 resolved since last review

Review effort: Max

@mnriem

mnriem commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback. If not applicable please explain why

Address the rest of the Copilot review on github#5032. The code fence fix
already landed in b395863; this covers the other four comments:

- Document that upgrades still hash-check .ignore, that --force also
  overwrites edited command files, and that the stored flag must be
  cleared before removing the two rules.
- Check .ignore (symlink, directory, unreadable, unwritable) before
  command files are rendered, and check it again before the write, so
  a rejected destination leaves existing command files unchanged.
- Match the two rules as ASCII bytes so a non-UTF-8 .ignore is kept
  byte for byte instead of raising UnicodeDecodeError.
- Write .ignore through the journal-aware atomic writer, keeping its
  file mode, and record partial ownership afterward, so a failed write
  can no longer truncate existing user rules.

Refs github#5028
Bring the branch up to date with main (505781d). github#5034 split
docs/reference/integrations.md into per-agent guides under
docs/integrations/ and deleted the old page, which conflicted with this
branch's edits to it.

Resolution: accept the deletion and move this branch's "OpenCode:
hidden Spec Kit files" section into docs/integrations/opencode.md as
"Hidden Spec Kit Files", and list it in the Integration-Specific
Options table in docs/integrations/cli.md, which is where main now
points readers for agent-specific options. The old catalog table rows
and options table row are dropped with the deleted page.
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Addressed the Copilot review: the .ignore section now documents that upgrades still hash-check the file, that --force also overwrites edited command files, and that the stored flag must be cleared before removing the rules. .ignore is checked before command files are rendered and again before the write, the two rules are matched as bytes so a non-UTF-8 file is kept as-is, and the write goes through the journal-aware atomic writer with the file mode kept. Also merged main; since #5034 moved agent docs, the OpenCode section now lives in docs/integrations/opencode.md. ruff, markdownlint and the full pytest suite pass locally.

@mnriem
mnriem requested a max review from Copilot October 21, 2026 13:18

This branch has not been deployed

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

Labels

triage-must-have Verdict: high-value, important work for Spec Kit — do first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: .specify ignored with other dot-files by opencode glob

3 participants