chore: merge upstream 0.15.2 + 13 post-release commits, bump to 0.15.2+adlc2 - #107
Merged
Conversation
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ithub#2000) * feat(extensions): scaffold config templates on extension add/enable Deploy an extension's provides.config templates into .specify/ when the extension is added or enabled. Existing files are never overwritten, so user customizations are preserved. Addresses the review on github#2000: - ExtensionManifest.config returns [] unless provides.config is a list of dicts, so a malformed manifest cannot crash callers. - scaffold_config returns a consistent (deployed, skipped_existing, failed) tuple on every path, including a missing manifest. - Template paths must resolve inside the extension dir and targets inside .specify/; symlinks and non-regular files are rejected. - Callers distinguish "already exists (preserved)" from "not scaffolded", and extension_enable no longer crashes on a corrupt manifest. - Tests cover traversal, absolute paths, symlinks, directory templates, malformed provides.config, and the missing-manifest tuple shape. Ported onto the extensions package introduced by github#3014: the manager and manifest changes land in extensions/__init__.py and the CLI wiring in extensions/_commands.py. * fix(extensions): deploy config where it is read, and contain the write Addresses @Copilot's review. Config now lands in .specify/extensions/<id>/ rather than the .specify/ root. ConfigManager._get_project_config() reads .specify/extensions/<id>/<id>-config.yml, and the bundled scripts and READMEs use the same path, so a scaffolded git-config.yml was being written somewhere the git extension never looks. Containment is checked component by component before .specify is used as the root. Resolving it first and trusting the result let a symlinked component point outside the project, after which every target satisfied relative_to and copy2 wrote externally. This matches the project safe-write path in shared_infra. mkdir moved inside the OSError handler. A nested target like foo/config.yml raised out of scaffolding when its parent could not be created, and on extension add that happened after the extension was already installed. The 'Configuration may be required' warning is now conditional. It ran unconditionally after the scaffolding block, so it contradicted the success output directly above it and fired for extensions with no provides.config at all. Tests cover the corrected location, a symlinked config root, and an uncreatable nested target. * fix(extensions): only scaffold config targets that removal preserves remove(keep_config=True) rmtree's every subdirectory and keeps only top-level -config.yml / -config.local.yml files; the backup path globs the same top-level pattern. Scaffolding a nested or differently-named target therefore handed the user a file that 'extension add --force' silently deleted and replaced with the template default, losing customization. Constrain scaffold targets to that convention rather than widening four removal paths. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Add adrkit extension submitted by @mbeacom to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes github#3942 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ON_DEFAULT (github#3952) * feat: allow overriding default init integration via SPECKIT_INTEGRATION_DEFAULT Resolve the non-interactive/init default integration from the SPECKIT_INTEGRATION_DEFAULT environment variable, fitting the existing SPECKIT_INTEGRATION_* namespace. Falls back to the hardcoded "copilot" default when unset, and warns to stderr (rather than silently falling back) when the value is not a registered integration key. Wires the resolver into specify init (interactive prompt default and non-interactive fallback), the init workflow step, and the bundle init default. Adds unit and CLI tests and documents the variable. Closes github#3939 Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: eecda55f-fa13-42f7-99bf-bfb0bb8565a0 * test: cover env-var default wiring for picker, workflow step, and bundle Address PR review: add regression tests so each SPECKIT_INTEGRATION_DEFAULT wiring site cannot silently revert to the hardcoded constant. - init.py: interactive picker receives the resolved key as default_key. - workflow init step: no step/workflow default + env var drives output integration and argv. - bundle _resolve_init_integration: env-var default applies when unspecified, while explicit override and manifest-declared integration still win. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: eecda55f-fa13-42f7-99bf-bfb0bb8565a0 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: eecda55f-fa13-42f7-99bf-bfb0bb8565a0
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…fault on an explicit null (github#3889) The step documents the default twice: class docstring: "Because workflows run unattended, the step defaults to ``--ignore-agent-tools``" field docs: "Skip checks for the coding agent CLI (defaults to ``true``)" It implements that with `config.get("ignore_agent_tools", True)`, which applies the default only when the key is ABSENT. A bare `ignore_agent_tools:` in YAML parses to None, and `_resolve_bool(None)` returns False: key ABSENT -> True flag emitted: YES bare ignore_agent_tools: -> False flag emitted: NO <-- bug explicit true -> True flag emitted: YES explicit false -> False flag emitted: NO So the flag is dropped, `specify init` re-runs the agent-CLI presence check, and an unattended run fails with "Agent Detection Error" for any integration whose CLI is not installed on the runner. Normalize an explicit null to the default, mirroring the while/do-while `max_iterations` handling. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hub#3856) * fix: narrow exception in invoke separator resolution and add regression test Narrow 'except Exception' to 'except (ImportError, ValueError, KeyError)' in register_commands() invoke separator resolution. Add regression test that verifies TypeError propagates instead of being silently swallowed. * fix: remove duplicate pass statement in agents.py Remove redundant second pass statement in the except block for invoke separator resolution. The narrowed exception handler now has a single clean pass statement. Assisted-by: GitHub Copilot (model: mimo-v2-free, supervised)
…ithub#3943) * fix(manifests): reject non-string metadata instead of crashing on it `ExtensionManifest` and `PresetManifest` checked only key PRESENCE for `id`/`name`/`version`/`description`, then fed the values straight to `re.match()` and `packaging.Version()`. Both raise a bare `TypeError` on a non-string, which is neither `ValidationError` nor `PresetValidationError`, so it escaped every caller that already handles a malformed manifest. YAML makes this an easy authoring slip rather than a contrived one: an unquoted `version: 1.0` parses as a float and `id: 2` as an int. The user-visible symptom is the one the in-tree comment above the section guards was written to prevent (github#3898 for presets, and its extension twin): `list_installed()` degrades a bad manifest to "⚠️ Corrupted extension" but catches only the domain error, so a single bad manifest made `specify extension list` / `specify preset list` exit 1 with a raw traceback and *no output at all* — hiding every healthy extension/preset too, not just the broken one. Also unguarded on the same path: - extension `provides.commands[].name` → `TypeError` from the command-name pattern match. The sibling `file` field was already safe, since `relative_extension_path_violation()` rejects a non-string. - preset `provides.templates[].name`/`.file` → `TypeError` from `re.match` and `os.path.normpath` respectively. The third manifest twin, `IntegrationDescriptor`, is already hardened: it type-checks the same four fields and catches `TypeError` alongside `InvalidVersion`. This brings the other two in line with it. Tests: 68 added across both suites, covering each field against float, int, None, list, dict, and bool, plus an end-to-end guard per manifest type asserting a healthy entry still lists while the bad one degrades. All 68 fail with the source change reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…remove (github#3929) * fix(presets): restore core skills instead of deleting them on preset remove Skill restoration looked for core command templates under .specify/templates/commands, a directory specify init never populates in real projects. Since that lookup always missed, presets overriding a core command (e.g. speckit.plan) had their skill deleted outright on removal instead of restored — the actual core templates live in the bundled core_pack (wheel install) or the repo-root templates/ tree. Restoration now falls back to that bundled location, gated behind a restore_from_bundled_core flag so the existing "retire a stale skill superseded by a command-mode winner" path keeps deleting rather than resurrecting a duplicate skill. Fixes github#3928 * fix(tests): use explicit utf-8 encoding reading restored skill content read_text() defaults to the platform locale encoding, which is cp1252 ("charmap") on Windows. The bundled specify.md core template contains a UTF-8 multi-byte emoji whose bytes aren't valid cp1252, so the Windows CI job failed decoding the restored SKILL.md with UnicodeDecodeError. * fix(presets): keep extension restore priority over bundled-core fallback The bundled-core fallback added for github#3928 ran before the extension_restore_index lookup, so a skill an installed extension owns could be silently replaced by lower-priority bundled core content on preset removal instead of preserving the extension's winning layer.
* chore: bump version to 0.15.2 * chore: begin 0.15.3.dev0 development --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: cap stdin read at 1 MiB to prevent DoS Unbounded sys.stdin.read() allowed a malicious caller to exhaust memory by sending a multi-gigabyte payload. Cap at 1 MiB and raise typer.Exit if truncated. * fix: improve stdin payload limit error handling in event.py - Rename _MAX_PAYLOAD to MAX_STDIN_BYTES (clearer constant naming) - Improve error message to suggest truncation or smaller payload - Better code formatting for readability Assisted-by: GitHub Copilot (model: mimo-v2-free, supervised)
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merges github/spec-kit upstream main (tip ab468c4) — 14 commits since the last merge base d1e86f6, including the 0.15.2 release (github#3953) and 3 post-release fixes (github#3899, github#3857, github#3897). New upstream features: - scaffold extension config templates on extension add/enable (github#2000) - SPECKIT_INTEGRATION_DEFAULT env-var for specify init / workflow / bundle (github#3952) - adrkit community catalog entry (github#3947) New upstream hardening: - restore core skills (not delete) on preset remove (github#3929) - reject non-string manifest metadata (github#3943) - narrow bare except Exception in invoke-separator resolution (github#3856) - workflow ignore_agent_tools explicit-null default (github#3889) - reject mismatched workflow run state IDs (github#3899) - cap stdin read at 1 MiB to prevent DoS (github#3857) - non-UTF-8 tolerance: events/presets/extension manifests/event overrides (github#3900, github#3896, github#3895, github#3897) - validate required preset manifest mappings (github#3898) 1 conflict resolved (pyproject.toml: kept fork name/description, version -> 0.15.2+adlc1). All semantic hotspots auto-merged cleanly; fork modules untouched. No templates/ changes upstream -> no preset command porting. Ruff clean (ruff@0.15.0). Tests deferred to CI. Assisted-by: opencode (model: glm-5.2, autonomous)
* feat(copilot): default integration to skills Make Copilot skills the default while retaining the commands layout behind --integration-options="--commands". Preserve historical project layouts and validate conflicting mode flags before switch teardown. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 930d846b-8921-44ef-9f45-3e77c036b6b5 * fix(copilot): preserve layout state during migration Keep target integration options isolated from fallback state, prefer the Copilot manifest when resolving layouts, and update dispatch coverage for the skills-first default. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 930d846b-8921-44ef-9f45-3e77c036b6b5 --------- Copilot-Session: 930d846b-8921-44ef-9f45-3e77c036b6b5
Update archive extension submitted by @stn1slv: - extensions/catalog.community.json (version, download_url, updated_at) - docs/community/extensions.md community extensions table Closes github#3977 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tdd extension submitted by @d0whc3r to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes github#3978 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hooks (github#3934) * feat(events): context injection for opencode and JSON-envelope agent hooks Adds first-class context injection to agent runtime events: 1. opencode: maps session_start to experimental.chat.system.transform (injects into system prompt) and user_prompt_submit to chat.message (injects synthetic TextPart). TS plugin captures runEvent stdout (stdio pipe, encoding utf-8) and pushes into output objects. Part IDs derive from output.parts[last].id to preserve OpenCode's prt_ brand and prevent session schema crashes. 2. JSON-envelope hook wrapping: adds events_context_envelope to IntegrationBase so agents that require JSON on stdout receive their target envelope via the dispatcher's 5th argument: - gemini, tabnine, qwen, devin: hookSpecificOutput.additionalContext on session_start/user_prompt_submit; suppress on non-injectable events (prevents systemMessage user-facing noise) - copilot: top-level additionalContext on session_start - cursor: top-level additional_context on session_start; suppress elsewhere - claude, codex: plain stdout passthrough (already injected) 3. Dispatcher template and resolve_and_run_event_command parse the 5th envelope arg and wrap stdout accordingly. Tests added for opencode TextPart schema, part ID derivation, envelope command generation, and dispatcher output wrapping. All 162 events/integration tests pass. * fix(events): address code review on github#3934 - Qwen/Gemini/Tabnine/Devin: include native hookEventName inside hookSpecificOutput envelope (required by Qwen's hooks spec). Thread the native event name from the integration's CANONICAL_TO_NATIVE through _dispatcher_command as a 6th dispatcher argument, through the dispatcher template's main()/_run_inline()/_emit(), and through resolve_and_run_event_command()/_emit_event_stdout(). - Copilot: map user_prompt_submit to additionalContext (previously unmapped, breaking per-prompt context injection despite Copilot CLI supporting it via userPromptSubmitted). - OpenCode: guard experimental.chat.system.transform so canonical session_start handlers only run when input.sessionID is present — OpenCode fires this hook for non-session operations (e.g. agent generation) with no sessionID. Assisted-by: opencode (model: glm-5.2, supervised) * fix(events): address second Copilot review round on github#3934 - Positional arg alignment: always emit default timeout (60s) as the 4th dispatcher argument even when timeout_seconds is omitted, so the envelope (5th) and native_event (6th) land in the correct argv slots. Previously, omitting timeout_seconds caused the envelope to be parsed as an invalid timeout, silently falling back to plain stdout. - OpenCode session_start caching: cache handler output per sessionID in the generated TS plugin so non-idempotent handlers (setup, telemetry, file-mutating scripts) run once per session instead of on every LLM request. Cache is evicted on session.deleted. - Updated PR description to reflect Copilot user_prompt_submit now maps to additionalContext (was documented as plain/unprocessed). Assisted-by: opencode (model: glm-5.2, supervised)
_script_command() split the configured command with a bare shlex.split(), so a command string with unbalanced quotes crashed event dispatch with a raw ValueError. The dispatcher-template twin a few lines up already wraps the same call in try/except ValueError and returns None so dispatch falls back cleanly. Wrap the split the same way and return None, restoring parity between the two paths. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update Charter extension to v0.5.1 Update charter extension submitted by @Huljo: - extensions/catalog.community.json (version, download_url, updated_at) Closes github#3944 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Limit catalog diff to Charter fields and top-level timestamp Assisted-by: GitHub Copilot (model: unknown, autonomous) Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
…ithub#3960) The keep-config rescue branch of install_from_directory() reads each preserved config with bare read_bytes()/stat() calls, so a kept config that cannot be read (permission or I/O error) crashed the reinstall with a raw OSError. The sibling symlink guard four lines above already rejects with a ValidationError and resolution guidance for the same reason: bytes that cannot be safely rescued must not reach the rmtree below. Wrap the read and raise ValidationError with guidance, while dest_dir is still untouched so the preserved bytes are never lost. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`requires.speckit_version` was presence-checked but never type-checked in both the extension and preset manifest validators, so an unquoted YAML `speckit_version: 1.0` (a float) passed validation and reached `SpecifierSet(required)` in `check_compatibility()`. That call is guarded by `except InvalidSpecifier` alone, which a non-string escapes two different ways: - a float/int/bool/None raises `TypeError: 'float' object is not iterable` from the `SpecifierSet` constructor; - a list or dict is an *iterable*, so `SpecifierSet` accepts it and the failure surfaces much later as `AttributeError: 'str' object has no attribute 'filter'` from inside `.contains()`. Neither is a `CompatibilityError`/`PresetCompatibilityError`, so both bypass the CLI's "Compatibility Error" handler in `_commands.py` and exit 1 with a raw traceback that names no field, leaving the author with no hint which manifest key is wrong. Type-check the field in both validators, requiring a non-empty string, and additionally guard `check_compatibility()` in both managers since each is public and reachable with a hand-built or mutated manifest. This mirrors the sibling `IntegrationDescriptor`, which already requires a non-empty string for the same key, and completes the type-checking pass started in github#3943 for the neighbouring `extension`/`preset` fields. Adds 33 regression tests across both modules covering every escape path; 26 of them fail without this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-by: Claude Code (model: Claude Opus 5, supervised)
…hub#3962) The rescue-retry loop in install_from_directory() reads each staged backup with bare stat()/read_bytes() calls, so a staged config that cannot be read crashed the reinstall with a raw OSError. Every sibling read in this path — the live twin four lines below, the packaged baseline check, the mode sidecar — already catches OSError. Treat an unreadable staged file like an uncomparable live config: add it to the conflict set so both copies are preserved and the retry aborts with the existing resolution guidance while dest_dir is still untouched. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github#3963) _merge_toml_fragment() and _remove_toml_entries() read the user's config.toml with bare read_text() calls, so a non-UTF-8 (or otherwise unreadable) file crashed install_integration_events() and remove_integration_events() with a raw UnicodeDecodeError — and the merge path regenerates the file from what it read, so it would have discarded the user's bytes had it not crashed first. Every JSON merge/remove path already goes through _load_user_json(), which skips on an unreadable file to preserve user content (#22). Abort the merge (returning False so the caller skips tracking, S5) and skip the teardown cleanup with a warning, leaving the user's bytes untouched in both directions. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 31dc6b66-8484-46b5-a282-360029e14ff2
PresetRegistry._load() catches json.JSONDecodeError and FileNotFoundError to start fresh on a corrupted or missing registry, but a registry file with invalid UTF-8 bytes raised UnicodeDecodeError before JSON parsing began, crashing every preset command. Catch UnicodeDecodeError in the same clause: undecodable bytes are the same corruption class as unparseable JSON. OSError stays uncaught on purpose — the data may be intact on disk, and starting fresh would let a later _save() wipe it (same fail-closed reasoning as the workflow catalog cache loader). Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add July 2026 newsletter * docs(newsletters): remove internal press-index figures from earlier editions Replace article counts, volume superlatives, and discovery-methodology references (derived from an internal press index) with qualitative phrasing in the April, May, and June editions, keeping only publicly verifiable data.
Upstream merge (ab468c4..03d71b3): Copilot skills-default flip (github#3976), events context injection for opencode + JSON-envelope hooks (github#3934, by fork maintainer upstream), non-UTF-8/hardening fixes (github#3955/github#3957/github#3960/github#3962/ github#3963/github#3980), community catalog additions (github#3981/github#3982/github#3983), docs (github#3985/ github#3987). 5 conflicts resolved: AGENTS.md (optional-overrides section + PR-prioritization), copilot/__init__.py (re-applied fork customizations + prefix-aware is_skills_mode), test_cli.py/test_integration_copilot.py/test_integration_ subcommand.py (adapted to fork naming for skills-default). Fork fix: build_command_invocation() bare-name canonicalization before alias resolution. Ruff clean (ruff@0.15.0). 1375+ tests pass. Assisted-by: opencode (model: glm-5.2, supervised)
…er py3.14 Latest uv (setup-uv v9) resolves typing-extensions differently under --universal + Python 3.14 than the committed snapshot — the dependency-audit check failed on the pyproject.toml version bump in this PR. Regenerated with: uvx uv@latest pip compile pyproject.toml --extra test --universal --generate-hashes --quiet --no-header (under Python 3.14). Assisted-by: opencode (model: glm-5.2, supervised)
…tion Two CI failures from github#3976 copilot skills-default flip: 1. build_command_invocation: only canonicalize truly bare names (no dot). "git.commit" is a dotted alias-form name, not bare — prepending speckit. made it speckit.git.commit which resolves differently in clean envs (CI checkout has no .specify/ → no alias → /speckit-git-commit instead of /git-commit). Test documents the contract: "git.commit is not in the alias map as a key, so it's always unchanged". 2. test_upgrade_preserves_historical_copilot_commands_without_options: fork's _register_model_invocation_skills (since 0.12.8+adlc6) generates .github/skills/quick-* during init for commands-mode agents (agentic-quick is preinstalled with model-invocation:true commands). These skills exist before the upgrade runs — not an upgrade bug. Assert the migration signal (no command-derived speckit-plan/spec-plan skill dir) instead of skills-dir absence. Assisted-by: opencode (model: glm-5.2, supervised)
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.
Combined upstream merge — 0.15.1 → 0.15.2 → post-0.15.2 tip
This PR covers two merge rounds (adlc1 was never tagged/released; the release tag after merge will be
agentic-sdlc-v0.15.2+adlc2).PR updated on behalf of @kanfil by opencode (model: glm-5.2, supervised).
Round 2 (new) — post-0.15.2 tip, bump to 0.15.2+adlc2
Merges upstream main (
ab468c4d..03d71b33) — 13 commits, no new release tag (upstream on0.15.3.dev0) → suffix bump only.New upstream features
feat(copilot): default integration to skills. Copilot now defaults to the skills layout (speckit-<name>/SKILL.mdunder.github/skills/); commands layout (.agent.md+.prompt.md+.vscode/settings.json) is opt-in via--integration-options="--commands". Fork adaptations:is_skills_mode()checks bothspec-/speckit-prefixes (fork skills usespec-*when presets are active); 4 fork customizations (resolve_command_alias,run_and_tee,_get_command_prefix()×2) re-applied onto upstream's rewritten module;build_command_invocation()now canonicalizes bare command names ("plan"→speckit.plan) before alias resolution (fixes/plan→/{prefix}-plan).feat(events): context injection for opencode and JSON-envelope agent hooks(authored by fork maintainer upstream; auto-merged clean). opencodesession_start/user_prompt_submitinjection via TS plugin; JSON-envelope wrapping for Gemini/Tabnine/Qwen/Devin/Copilot/Cursor.Upstream fixes (all auto-merged clean)
github#3955 non-UTF-8 preset registry · github#3963 non-UTF-8 config.toml on hook install/teardown · github#3962 unreadable staged backup as conflict · github#3980 non-string
requires.speckit_version· github#3960 reinstall rejected when kept config unreadable · github#3957 None for unparseable script command · migration target-options validation in_migrate_commands.pyCommunity catalog / docs
github#3982 TDD extension (note: fork has its own bundled
tdd— separate) · github#3983 Charter v0.5.1 · github#3981 Archive v1.1.0 · github#3985 agent PR-review prioritization · github#3987 July newsletterConflict resolution — 5 conflicts
AGENTS.mdintegrations/copilot/__init__.pyis_skills_mode()prefix-awaretests/integrations/test_cli.pytests/integrations/test_integration_copilot.pybuild_command_invocationtests adapted to alias-aware namingtests/integrations/test_integration_subcommand.pyAuto-merged clean:
events.py,extensions/__init__.py,presets/__init__.py,base.py,_migrate_commands.py,integration_runtime.py. Fork modules untouched. Notemplates/changes → no preset porting.Bonus fix (pre-existing, reported by user during this round)
commands/init.py:typer.confirm()could raiseOSError(e.g.BrokenPipeError) escaping both Click's and the fork'sexcept (typer.Abort, EOFError)handlers → crash onspecify init .. AddedOSErrorto the handler.Verification
ruff@0.15.0)test_events.py+test_integration_copilot.py+ copilot-relatedtest_cli.pytest_extensions.py,test_presets.py,test_extension_skills.py,test_extra_args.py,test_integration_state.py(13 skipped, 0 failures)test_integration_subcommand.pyswitch tests (full file is slow; CI covers rest)Round 1 — 0.15.1 → 0.15.2, bump to 0.15.2+adlc1
Merges
github/spec-kitupstream main (tipab468c4d) — 14 commits since the last merge based1e86f63, including the 0.15.2 release (github#3953) and 3 post-release fixes (github#3899, github#3857, github#3897).New upstream features
extension add/enable(provides.config→.specify/). Inert for the fork's bundled extensions today (they declare onlyprovides.commands); theagent-contextextension keeps its own.template+ script self-seed mechanism.SPECKIT_INTEGRATION_DEFAULTenv-var picker forspecify init/ workflow init step / bundle init.New upstream hardening
restore_from_bundled_corewith extension-restore priorityexcept Exceptionin invoke-separator resolutionignore_agent_toolsdefault on an explicit null github/spec-kit#3889 — workflowignore_agent_toolsexplicit-null defaultMAX_STDIN_BYTESinevent.py)Conflict resolution
1 conflict (
pyproject.toml) — kept fork name/description, version →0.15.2+adlc1(base reset, counter reset).All semantic hotspots auto-merged cleanly:
__init__.py,agents.py,extensions/__init__.py,extensions/_commands.py,presets/__init__.py,commands/init.py,commands/bundle/__init__.py,events.py,event.py,workflows/engine.py,workflows/steps/init/__init__.py,_agent_config.py. Fork modules untouched. Notemplates/changes upstream → no preset command porting needed.Verification (round 1)
ruff@0.15.0)specify --version→0.15.2+adlc1pytest --cocollects 6578 tests cleanlyHygiene
CHANGELOG.md—0.15.2+adlc1+0.15.2+adlc2entries at topFORK.md— version-history rows for both roundsNote on the working tree
The fork's main has unrelated uncommitted changes under
evals/(stashed during merge, restored after) and an untracked.adlc/. Neither is part of this PR. A local agent-context-managed refresh ofAGENTS.md's SPECKIT section is also uncommitted and not part of this PR.