Merge upstream 0.16.2 into fork (0.16.0+adlc3 → 0.16.2+adlc1) - #112
Merged
Conversation
…github#3757) * fix(agent-context): recurse for nested plans in Python mtime fallback The Python port's mtime fallback discovered plans with a one-level specs/*/plan.md glob, so a scoped layout created via SPECIFY_FEATURE_DIRECTORY (specs/<scope>/<feature>/plan.md) was missed when feature.json is absent — the fallback returned no plan and the managed context section omitted the 'at <plan>' line. The bash and PowerShell twins were already fixed to recurse (github#3024); the Python twin was left behind. Switch to specs.rglob('plan.md') with the same symlink-safe containment check the bash twin uses (resolve each candidate and confirm it stays within the project root before ranking by mtime), so a plan reached through a specs/ symlink pointing outside the project is not selected. Adds parity regression tests (vs bash and vs PowerShell) covering a nested specs/<scope>/<feature>/plan.md; both fail on the pre-fix one-level glob. Fixes github#3733 * test(agent-context): cover symlink containment in the mtime fallback The recursive fallback resolves each candidate before the relative_to() containment check, but nothing exercised that path. Add a parity test for a plan reachable only through a specs/ symlink pointing outside the project: relative_to() is lexical and would accept it, emitting an in-project-looking path for an out-of-project file. Both the bash twin and the Python port skip it, so the "at <plan>" line is omitted. Also correct the module docstring, which still described the fallback as scanning specs/*/plan.md one level deep.
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 24.1.0 to 24.2.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](DavidAnson/markdownlint-cli2-action@6bf21b0...21c1be1) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 24.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ithub#4005) * chore(deps): bump github/codeql-action/analyze from 4.37.3 to 4.37.5 Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.3 to 4.37.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@e4fba86...d1ba80a) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump github/codeql-action/init to match analyze 4.37.5 Dependabot only bumped the analyze step; keep init on the same 4.37.5 SHA so both CodeQL steps use the same release. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 126ceec5-7d64-444c-8cd4-d60b225d46f5 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 126ceec5-7d64-444c-8cd4-d60b225d46f5
…hub#4013) `_local_manifest_source` handles three local bundle sources. The directory and `bundle.yml` branches both go through `BundleManifest.from_file` -> `load_yaml`, which converts a parse failure into a `BundlerError`. The `.zip` branch instead parses inline with a bare `_yaml.safe_load`. `yaml.YAMLError` derives directly from `Exception` -- it is neither a `ValueError` nor an `OSError` -- so it escapes `bundle_install`'s `except BundlerError` and reaches the user as a raw `yaml.parser.ParserError` traceback. The remote counterpart of this same call, `_download_manifest`, already guards it and even names `_yaml.YAMLError` explicitly. Only the local zip path was missed, so the same corrupt manifest is reported cleanly when fetched from a catalog but crashes when installed from disk. Before, for the identical malformed bundle.yml: specify bundle install ./bundle-dir -> Error: Invalid YAML in ... (exit 1) specify bundle install ./bundle.yml -> Error: Invalid YAML in ... (exit 1) specify bundle install ./bundle.zip -> ParserError traceback Two regression tests: one pins the `BundlerError` contract on the zip branch, and one drives all three local sources through the CLI to assert they now fail alike. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(events): skip an unreadable command template _render_command_template() read the resolved template with a bare read_text(), so a template file that exists but cannot be read or decoded (permission error, non-UTF-8 bytes) crashed event dispatch with a raw OSError/UnicodeDecodeError. Every sibling failure in this path (missing template, unresolvable command) already returns None so the dispatcher falls back cleanly. Wrap the read and return None on OSError/UnicodeDecodeError, matching the sibling contract. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: cover the OSError half of the unreadable-template boundary Review follow-up: add a mocked PermissionError case so both promised exception paths are protected under privileged CI. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(integrations): wrap a non-UTF-8 catalog response
`_fetch_single_catalog` decodes the response body with `.decode("utf-8")`
before handing it to `json.loads`. A non-UTF-8 body therefore raises
`UnicodeDecodeError`, which is a sibling of `json.JSONDecodeError` under
`ValueError` rather than a subclass of it, so neither the `URLError` nor the
`JSONDecodeError` handler catches it.
The raw exception escapes `_get_merged_integrations`, whose
`except IntegrationCatalogError` is specifically designed to warn and skip a
bad catalog and carry on with the remaining ones. One catalog served over a
misconfigured proxy or truncated mid-multibyte-sequence thus takes down
`specify integration search` entirely instead of degrading to a warning.
Wrap it in `IntegrationCatalogError`, matching the convention already used
for the same decode in `authentication/azure_devops.py`, which lists
`UnicodeDecodeError` alongside `JSONDecodeError`.
Note that the cache-read path in this same method already tolerates this via
its `UnicodeError` clause; only the network path was unguarded.
Two regression tests: one pins the wrapped-error contract on the fetch, and
one covers the behaviour that actually motivates it — a broken catalog is
skipped with a warning while a healthy sibling catalog still resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(integrations): use the shared urlopen routing fixture
The raw-bytes helper patched `open_url` wholesale, which skipped the real
URL validation and redirect handling inside it. This module already imports
`route_opener_open_through_urlopen`, the repo's shared fixture that routes
`build_opener().open()` back through `urlopen` for exactly this reason, so
patching `urlopen` instead keeps the stub effective while still exercising
`open_url` itself.
Renamed to `_patch_urlopen_bytes` to sit alongside the existing
`_patch_urlopen`, whose signature it now mirrors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(integrations): restore the non-UTF-8 handler
The previous commit reverted the source change by accident while reworking
the tests, leaving the regression tests passing against an unfixed module.
Restores the `except UnicodeDecodeError` clause.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump version to 0.16.1 * chore: begin 0.16.2.dev0 development --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(presets): treat an unreadable core template as missing _substitute_core_template() read the resolved core template with a bare read_text(), so one corrupted project-owned override in .specify/templates/commands/ crashed the whole wrap-strategy command registration with a raw UnicodeDecodeError. Both callers (CommandRegistrar.register_pack and _register_commands) are unguarded here, even though register_pack already skips an unreadable preset source with a warning a few lines above the call. Treat an unreadable core template like a missing one — warn and return the body unchanged with empty frontmatter — matching the function's documented no-core contract. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: assert the unreadable-core warning instead of suppressing it Review follow-up: use pytest.warns so removing or changing the promised warning fails the test. Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…anifest (github#4012) * feat(extensions): accept provides.templates and provides.scripts in manifest Extensions could only formally declare commands under `provides` (plus config/hooks/events); templates and scripts shipped by an extension were picked up purely by filename convention, with no id, description, or metadata. Add optional `provides.templates` and `provides.scripts` sections to the extension manifest schema, mirroring the preset template shape minus an authorable `strategy` (extension artifacts always resolve as replace, so a present `strategy` key is now a validation error rather than a silently accepted no-op). ExtensionManifest gains `templates`/`scripts` properties so tooling can enumerate an extension's declared artifacts directly from the manifest. An extension may now satisfy the "must provide something" rule with only a template or script, not just a command/hook/event. Addresses the manifest-schema portion of github#4010; resolver authoritative-vs-convention precedence for these new sections is left for a follow-up. * fix(presets): wire extension-declared templates/scripts into resolver collect_all_layers only consulted ExtensionManifest for command resolution, leaving provides.templates/.scripts purely decorative -- a declared entry whose file didn't sit at the conventional path was validated but never resolved. Extend the existing manifest-fallback branch to cover template_type "template" and "script" the same way it already does "command": convention lookup first, manifest lookup as fallback so undeclared on-disk files keep resolving unchanged. * fix(presets): make extension manifest lookup authoritative over convention Copilot review on github#4012 found the manifest-declared template/script lookup was gated on convention lookup missing first, so a stale conventional file could shadow a declared entry at a non-conventional path, and resolve() never consulted the manifest at all (only collect_all_layers() did). Add a shared _extension_manifest_declared_template() helper and check it before convention-based lookup in both resolve() and collect_all_layers(), mirroring the preset manifest precedence. Also update EXTENSION-DEVELOPMENT-GUIDE.md, which still claimed provides only supports commands and required a command or hook. * fix(presets): stop resolving symlinks in extension manifest candidate path _extension_manifest_declared_template() resolved ext_dir/rel_path before returning it, which follows symlinks in ext_dir's ancestors (e.g. macOS's symlinked tmp dir) and diverges from the unresolved paths convention-based lookup returns for the same directory. Resolve only for the traversal containment check; return the unresolved candidate. Fixes the 4 CI test failures across all OS/Python matrix jobs on github#4012.
…thub#4032) * docs: document installing specify-cli from a custom package index Add a generic section to the PyPI install guide covering how to point uv, pipx, and pip at a non-default package index (env var and flags), with a placeholder URL, plus notes on pins/upgrades and authentication. Link to it from the main installation guide. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d6ef3f75-54e9-4789-902b-4f0adeaadfad * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: d6ef3f75-54e9-4789-902b-4f0adeaadfad
…from core template (github#3996) * Fix preset-wrap-drops-argument-hint: inherit argument-hint from core Apply the remediation from the bug assessment on issue github#3991. Extend the inheritance allowlist in _register_skills and _compose_layers to include 'argument-hint', so wrap-strategy presets that omit this key will inherit it from the core template rather than silently dropping it and risking its value being leaked into description. Refs github#3991 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(presets): guard wrap argument-hint inheritance for unmapped command The existing regression test for github#3991 wraps `speckit.specify`, whose stem is in Claude's ARGUMENT_HINTS map. The string-injection fallback in post_process_skill_content re-adds argument-hint even when wrap composition drops it, so that test passes with or without the inheritance fix and does not actually guard the regression. Add a parallel test that wraps an extension-like command (`speckit.myfeature`) absent from ARGUMENT_HINTS, so the wrap-composition inheritance is the only path that can carry argument-hint into the SKILL.md. This test fails without the fix and passes with it. Refs github#3991 Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 970babe2-48cd-4c41-adae-0282d879a9ce --------- 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: Manfred Riem <15701806+mnriem@users.noreply.github.com> Copilot-Session: 970babe2-48cd-4c41-adae-0282d879a9ce
…#3984) * feat(presets): resolve constitutions at command time Gate install-time constitution materialization behind the constitution-sync preset while preserving one-time init seeding and authored-file safeguards. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): emit composed template content Add a machine-readable preset resolve mode backed by PresetResolver.resolve_content and require the constitution command to consume it. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): unify runtime template composition Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): secure runtime template resolution Align runtime resolution across script variants, validate registry path components, and honor canonical extension ordering and convention paths. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): align runtime priority semantics Normalize and tie-break preset priorities consistently across script variants, and preserve template bytes when Python materializes generated files. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): stop at effective template base Avoid parsing irrelevant lower layers once resolution reaches a replace base, and decode raw bytes so Python preserves source line endings. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): align extension template resolution Support root-level extension templates across runtime resolvers, fail safely when Bash cannot parse an extension registry, and validate requested templates in every prerequisite output mode. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): resolve dotted command identifiers Route safe dotted names through command resolution, correct traversal coverage, and make Windows CI text decoding explicit. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Avoid orphan feature directories on template errors Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Align malformed preset manifest handling Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Complete runtime resolver parity Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Fail closed on resolver input errors Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Force UTF-8 and full manifest validation Force UTF-8 decoding for registry and manifest reads in the Bash and PowerShell embedded-Python parsers so resolution no longer depends on the process locale, and validate every manifest template entry's required fields, type, and strategy consistent with the canonical PresetManifest. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Fail closed on empty manifests and corrupt registries Reject manifests missing the provides/templates sections or declaring an empty template list in all three runtime resolvers, matching the canonical PresetManifest which treats those as invalid instead of silently degrading a composing layer to a convention `replace` lookup. Make a corrupt or unreadable extension registry fail closed in Bash, PowerShell, and Python instead of swallowing the error and treating every on-disk extension directory as unregistered-and-enabled, which could activate a disabled extension. Read the preset and extension registries as explicit UTF-8 in the PowerShell resolver so priority/enabled-state decoding no longer depends on the process code page under Windows PowerShell 5.1. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): fail closed when extension registry is not a regular file The Bash and Python resolvers used is_file()/`-f` to gate reading the extension `.registry`, which returns false for a directory or a broken symlink at that path. In those cases the resolvers treated the registry as absent and scanned every on-disk extension directory as unregistered and enabled — a fail-open path. Detect any filesystem entry at the registry path (including broken symlinks) and reject unless it is a readable regular file. PowerShell now rejects a non-leaf entry explicitly for parity. Adds directory- and broken-symlink parity regressions. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): fail closed on corrupt registry in canonical resolver and PowerShell Two remaining fail-open paths for an invalid extension registry: - The canonical PresetResolver enumerated extensions through ExtensionRegistry, whose _load() normalizes a corrupt or unreadable registry to an empty mapping. The directory scan then admitted every on-disk extension directory as unregistered-and-enabled, so a corrupt registry could still supply constitution content at init and through constitution-sync materialization. Add a non-invasive is_corrupt() probe (recovery behavior for install/enable/disable is unchanged) and raise from _get_all_extensions_by_priority() when the registry exists but is invalid. _load() now also recovers from OSError/UnicodeDecodeError so a directory or unreadable registry no longer crashes construction. - The PowerShell resolver gated the registry read with Test-Path, which returns false for a dangling symlink on Windows, letting a broken .registry symlink bypass the guard and enable every on-disk extension. Detect the entry via directory enumeration (which observes a broken symlink) and reject it unless it is a readable regular file. Adds canonical corrupt/directory-registry regressions and extends the broken-symlink parity test to PowerShell. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): detect dangling registry symlink in ExtensionRegistry.is_corrupt is_corrupt() gated on Path.exists(), which follows symlinks and returns False for a dangling .registry symlink — so the canonical PresetResolver treated it as an absent registry and fell back to scanning every on-disk extension directory as unregistered-and-enabled, reopening the fail-open path this guard closes. Detect lexical existence with os.path.lexists and require a regular file before parsing, so a broken symlink (or directory) is reported corrupt and resolution fails closed. Adds a canonical broken-symlink regression alongside the directory case. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c
…ithub#4016) The resolver returns the first entry matching a declared name, so a later duplicate within provides.templates or provides.scripts was silently unreachable while still counted by ExtensionManifest properties. Reject duplicates at manifest-validation time instead. Also clarify EXTENSION-DEVELOPMENT-GUIDE.md's provides section: hooks and events are top-level manifest fields, not provides sub-fields, so the "at least one of ..." wording doesn't imply they can be nested under provides.
Replace check-then-act pattern (exists()+unlink()) with unlink(missing_ok=True) to eliminate TOCTOU race condition. Matches the pattern already used for per-URL cache files in the same method.
Replace check-then-act pattern (exists()+unlink()) with unlink(missing_ok=True) to eliminate TOCTOU race condition.
Add model-routing-governance preset submitted by @hindermath to: - presets/catalog.community.json (alphabetical order) - docs/community/presets.md community presets table Closes github#4021 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>
Update reconcile extension submitted by @stn1slv: - extensions/catalog.community.json (version, download_url, updated_at) - docs/community/extensions.md community extensions table Closes github#4024 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>
…#3840) Capture and display the exception message when reading preset-catalogs.yml fails, instead of swallowing the error details. Matches the pattern used in preset_catalog_add 54 lines earlier.
Add keel extension submitted by @athulrajeev to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes github#4026 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>
…ithub#4020) * fix(presets): skip an unreadable restore source in `preset remove` `_unregister_skills_in_dir` restores each preset-owned SKILL.md from a core command template or an extension source. Both of those reads were bare `read_text(encoding="utf-8")` calls, so a project-owned override in `.specify/templates/commands/` that exists but cannot be read or decoded raised a raw `UnicodeDecodeError`/`OSError` straight out of `PresetManager.remove()`, which has no handler for it — `specify preset remove` dies with a traceback. Every other failure in this loop degrades with `continue`: an unsafe registry name, a missing skill subdirectory, a foreign owner. Sibling reads of the very same directory are already guarded — `_infer_legacy_skill_ provenance` and `_delete_agent_preset_skills` both wrap their SKILL.md read in `except (OSError, UnicodeDecodeError): continue`, and the read inside `_substitute_core_template` was just given the same boundary in github#3961. The two restore reads were the remaining gap. `continue` is the right recovery here rather than falling through: the `else` branch below removes the skill outright, so treating an unreadable source as "no source" would delete a user's skill at exactly the moment its replacement cannot be generated. Skipping leaves the skill in place and keeps it out of the returned `mutated_names`, so callers don't record a restore that never happened. Two regression tests, one per exception arm: a non-UTF-8 core template, and a mocked `PermissionError` so the `OSError` half is also covered under privileged CI where permission bits aren't enforced. Both assert the skill survives untouched and is not reported as mutated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(presets): warn when a skill keeps preset content after a failed restore Review follow-up on two points. Surface the skipped restore. Skipping is still the correct recovery — the alternative branch deletes the skill — but it was silent, and it is a partial removal: `remove()` goes on to delete the preset directory and the registry entry, while this `SKILL.md` keeps the removed preset's content, and leaving the name out of `mutated_names` also keeps it out of reconciliation, so nothing retries it. Both arms now emit a warning naming the skill, the unreadable source, and the exception, and pointing at the re-run that refreshes it once the file is fixed. `warnings.warn` matches how the surrounding code reports non-fatal degradation (the reconciliation failures in `remove()`/`install_from_directory`, the unreadable core template in `_substitute_core_template` from github#3961). Cover the extension arm. A skill backed by an installed extension never reaches the core-template read, so the two branches can regress independently and both prior tests exercised only the core one. `test_unregister_skills_in_dir_unreadable_extension_source_skips` installs an extension whose command file is non-UTF-8 and asserts the skill survives byte-for-byte and is absent from `mutated_names`. Verified it raises the raw `UnicodeDecodeError` against unpatched source. The two existing tests now assert the warning via `pytest.warns` so dropping it fails the suite. pytest tests/test_presets.py -> 583 passed, 2 skipped, 7 failed; the 7 are the pre-existing Windows symlink tests that need elevation, unchanged from main. ruff check passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…github#4023) * fix(bundle): escape Rich markup in bundle CLI error and status output `specify bundle`'s `_fail` helper interpolated its message straight into `err_console.print`, which has Rich markup enabled. Every caller passes `str(exc)` from a `BundlerError`, and those messages embed untrusted data -- including the command's own argument -- so a `[...]` in it was parsed as a style tag. Balanced tags were silently swallowed; an unbalanced closer raised `MarkupError`, which replaced the error message with a traceback and left the output completely empty. Three commands crashed on user input alone, with no project state required: specify bundle catalog add 'ssh://ex[/red]ample.com/c.json' specify bundle catalog remove 'no[/red]such' specify bundle update 'no[/red]such' `bundle validate` had the same failure on both branches: its errors echo `requires.speckit_version`, and its warnings echo component ids, which are not charset-validated -- so a structurally *valid* manifest crashed on the success path too. Fixed centrally in `_fail`, plus the remaining raw interpolations: the `validate` warning/error/success lines, the install overlap and plan warnings, the install/update/remove/catalog-add confirmations, the `catalog list` id/url, and the `bundle init` project path. Regression tests cover the four crashing error paths (parametrized) and both `validate` branches; all six fail without this change. Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bundle): escape markup in `bundle list` records and `bundle build` output path Review follow-up: two raw interpolations the first sweep missed, both on success paths rather than error paths. `bundle_list` rendered `record.bundle_id`, `record.version` and `record.installed_at` unescaped. `InstalledBundleRecord.from_dict` only requires non-empty strings for the first two and applies no charset check to any of them, so a records file that *loads cleanly* still crashed the command that displays it — confirmed as `MarkupError: closing tag '[/red]' at position 12 doesn't match any open tag`. `bundle_build` echoed `result.artifact_path` twice in its success line. Brackets are legal in a directory name, so a bracketed `--output` built the artifact and then misreported it: the work is already on disk when the markup is consumed, so the line names a path that does not exist. Re-scanned every `{...}` interpolation in the module to confirm nothing else remains: the rest are either `BundlerError` messages that funnel through the already-escaped `_fail`, `_format_component` output escaped at its call site (:293), ints, or hardcoded enum `.value`s. Two regression tests. The list case uses the unbalanced-closer form that raises outright. The build case deliberately uses `[bold]` instead: `/` is a path separator on Windows, so `dist[/red]out` becomes the directory `dist[\red]out` and the fixture stops testing what it claims — the silent-swallow form keeps it portable while still asserting the reported path matches what was written. Verified both fail against 1d2184d. tests/contract/test_bundle_cli.py -> 42 passed. tests/contract tests/integration tests/unit -> 364 passed, 6 skipped, 5 failed; the 5 are the pre-existing `*_refuses_symlinked_*` tests needing symlink privileges on Windows, unchanged from main. ruff check passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: provision Python test deps for bug-test workflow * test: anchor bug-test workflow domain assertions Address CodeQL py/incomplete-url-substring-sanitization alerts (14-17) by anchoring the PyPI domain assertions to their structural context: the `network.allowed` YAML list items in the source and the quoted JSON entries in the compiled lock. This defeats the incomplete-URL-substring pattern and strengthens the test to confirm the domains are real allowlist entries rather than incidental substrings. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b54442f-ccc5-4be1-a05c-b360889670e5 * fix: provision test deps without creating a project lock Replace `uv sync --extra test` with `uv pip install --system -e ".[test]"` in the bug-test provisioning step. `uv sync` writes a root `uv.lock` (and `.venv`) into the working tree. This repository intentionally has no `uv.lock`/`[tool.uv]` (uv.lock is gitignored), so the sync produced an untracked lockfile before the agent checks out the fix ref in Step 2. `uv pip install` installs the test extra into the runner's Python without generating a project lock, keeping the working tree clean before the fix checkout. The editable install means the agent's `python3 -m pytest` runs against the checked-out fix code. Recompiled the lock and updated the assertions accordingly. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b54442f-ccc5-4be1-a05c-b360889670e5 * chore(workflows): sync gh-aw action-pin metadata to latest across all workflows Dependabot bumps the third-party action `uses:` pins (and header comments) directly, but does not update gh-aw's own metadata: the per-file `gh-aw-manifest` JSON blob and the shared `.github/aw/actions-lock.json` pin cache. As a result the executing pins were already uniform and current (checkout v7.0.1, setup-node v7.0.0) while the manifest/cache metadata still recorded checkout v6.0.3 / setup-node v6.4.0. This is a latent downgrade hazard: a plain `gh aw compile` reads the stale cache and can silently revert the `uses:` lines back to the older pins, undoing Dependabot's bumps and breaking lockstep. Sync all four pin surfaces (uses / header comment / manifest / cache) to the current pins so every workflow agrees and a future recompile is a no-op: - actions-lock.json: checkout v6.0.3 -> v7.0.1, setup-node v6.4.0 -> v7.0.0, and add the setup-python v7.0.0 + setup-uv v9.0.0 entries now used by bug-test. - gh-aw-manifest blobs in the 5 non-bug-test lock files: checkout + setup-node bumped to match their own uses lines (bug-test was already current). No workflow body changes; only pin metadata. `uses:` pins are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b54442f-ccc5-4be1-a05c-b360889670e5 Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) --------- Co-authored-by: root <kinsonnee@gmail.com> Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b54442f-ccc5-4be1-a05c-b360889670e5
) `_parse_edit` reads `operation` straight from hand-edited YAML and then does `if operation not in VALID_OPERATIONS`. `VALID_OPERATIONS` is a frozenset, so that membership test hashes the value — and an unhashable one raises: operation={'insert_after': 'a'} -> TypeError: unhashable type: 'dict' operation=['insert_after'] -> TypeError: unhashable type: 'list' `validate_overlay_yaml`'s docstring promises "validation never raises", and nothing upstream catches TypeError (layer_sources wraps only YAMLError/OSError/UnicodeDecodeError; _commands catches only ValueError), so the CLI dies with a raw traceback instead of reporting the error. The trigger is an ordinary authoring mistake: nesting the recommended shorthand form under the explicit key. Every other field in the same function is isinstance-guarded first (`anchor`, `step`, `step["id"]`); `operation` was the outlier. Check the type first and return the message the function already uses for `operation: None` / `operation: 7`. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
github#3883) evaluate_condition() special-cases the strings "false"/"true" so that `condition: "false"` behaves as a boolean, but it matches with `result.lower()` and never strips. The most common way a *string* reaches a condition is captured command output, and the shell step stores stdout verbatim (steps/shell/__init__.py:67 `"stdout": proc.stdout`). So `run: echo false` resolves to "false\n", which matches neither branch and falls through to `bool("false\n")` -> True: 'false' -> False 'false\n' -> True <-- bug 'false\r\n' -> True <-- bug ' false' -> True <-- bug An `if` step therefore takes its `then` branch on a step that printed "false", and `while`/`do-while` keep dispatching their body. A workflow author cannot work around it: the registered filters are default/join/map/contains/from_json — there is no `trim`. `InitStep._resolve_bool` and both catalog readers already strip before matching boolean text. `bool(result)` still sees the raw string, so no non-boolean text changes truthiness. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add Command Code integration to spec-kit Adds `command-code` as a built-in skills-based integration so Spec Kit can be installed into Command Code. Command Code loads agent skills from `.commandcode/skills/speckit-<name>/SKILL.md` and invokes them in chat as `$speckit-<command>`. - New `CommandCodeIntegration` (SkillsIntegration) writing to `.commandcode/skills/`; declared multi-install safe (static, isolated agent root). - Register in `_register_builtins()` and the integration catalog. - Add `command-code` to `DOLLAR_SKILLS_AGENTS` so next-steps guidance renders `$speckit-*` invocations. - Tests: reuse `SkillsIntegrationTests` mixin plus a dollar-invocation next-steps test; registry completeness updated. - Docs: README and docs/reference/integrations.md (supported agents + multi-install-safe table). Co-authored-by: CommandCodeBot <noreply@commandcode.ai> Assisted-by: Command Code (autonomous) * Fix issue template agent lists to include command-code The runtime AGENT_CONFIG now includes command-code, but the GitHub issue templates and the consistency test's expected key list were not updated, failing test_issue_template_agent_lists_match_runtime_integrations. Co-authored-by: CommandCodeBot <noreply@commandcode.ai> Assisted-by: Command Code (autonomous) --------- Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
* chore: bump version to 0.16.2 * chore: begin 0.16.3.dev0 development --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ub#4041) Community extension, preset, and bundle submissions are validated by label-triggered agentic workflows that only run once the corresponding `*-submission` label is applied. On this public repo contributors cannot apply that label themselves, so a maintainer applies it during issue triage. Document this in the three submission issue templates, the preset publishing guide, and correct the extension guide's inaccurate claim that issues are "automatically labeled and assigned". Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3b6f6854-9be6-4b67-b5b9-34b19ededcb8
Update security-governance preset submitted by @hindermath to: - presets/catalog.community.json (version, download_url, description, documentation, provides, tags, updated_at) - docs/community/presets.md community presets table Closes github#4039 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>
Update archive extension submitted by @stn1slv: - extensions/catalog.community.json (version, download_url, updated_at) - docs/community/extensions.md community extensions table Closes github#4049 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>
* docs: clarify custom checklist lifecycle * docs: clarify implement checklist marker ownership --------- Co-authored-by: root <kinsonnee@gmail.com>
Co-authored-by: root <kinsonnee@gmail.com>
Catalog submission issues no longer auto-assign mnriem. The workflow now only posts the team notification comment (cc @github/spec-kit-maintainers), since GitHub issues cannot be assigned to a team. Renamed the workflow and job to reflect its notification-only purpose. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 63ae7e23-13e3-4a83-96b3-1178422aaa6e
Update architecture-governance preset submitted by @hindermath to: - presets/catalog.community.json (version, download_url, documentation, description, provides, tags, updated_at) - docs/community/presets.md community presets table Closes github#4042 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 grill extension submitted by @yoshi1220 to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes github#4047 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>
…tions (github#4045) * fix(claude): make argument-hint injection fold-aware for long descriptions ClaudeIntegration.inject_argument_hint spliced argument-hint: "..." as a raw text line right after the first line starting with "description:". When a description is long enough for the YAML dumper to fold it across indented continuation lines, that splice landed inside the scalar, producing invalid YAML (plain scalar) or silently absorbing the hint into the description string (quoted scalar). This reproduces github#3991 for the case github#3996 didn't cover: bundled core commands have no argument-hint in their source frontmatter, so the structural apply_argument_hint path is a no-op and this raw-text fallback is what actually runs. Skip every continuation line of the description scalar (anything more indented than the key itself) before inserting, so the new key always lands after the whole scalar ends rather than in the middle of it. Fixes github#4044 * fix(claude): also skip unindented blank lines in description scalar PyYAML serializes an embedded paragraph break ("\n\n") inside a quoted description as unindented blank lines, not indented continuation lines. inject_argument_hint only skipped indented lines, so it still inserted argument-hint mid-scalar for multi-paragraph descriptions, reproducing the github#4044 failure modes. Skip blank lines too, and add a regression test for the multi-paragraph case.
Update isaqb-architecture-governance preset submitted by @hindermath: - presets/catalog.community.json (version, download_url, documentation, description, templates count, tags, updated_at) - docs/community/presets.md community presets table Closes github#4055 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>
* fix: use bounded read for bundle download HTTP responses The bundle download used unbounded resp.read() to read HTTP responses into memory. A malicious or misconfigured catalog server could return an arbitrarily large payload causing OOM. Replace with read_response_limited() capped at MAX_DOWNLOAD_BYTES (50 MiB), consistent with how other download paths in the codebase enforce bounded reads. Add regression test that monkeypatches MAX_DOWNLOAD_BYTES to 100 bytes and verifies oversized responses are rejected. * fix: remove duplicate import of MAX_DOWNLOAD_BYTES and read_response_limited
Upstream merge: 50 commits, releases 0.16.1 + 0.16.2. - New integration: command-code (skills-based, $speckit-* invocation) - New feature: constitution template resolved at command time (github#3984) - New feature: provides.templates/scripts in extension manifest (github#4012) - Checklist ownership semantics (reviewer-owned markers, read-only gate) - Template content resolution (TASKS_TEMPLATE_CONTENT/TEMPLATE_CONTENT) - 9 conflicts resolved, preserving fork theming/modules - Template-to-preset alignment for 5 preset command files - Test adaptations: catalog test took upstream, command_code prefix guard Assisted-by: opencode (model: glm-5.2, supervised)
… iSAQB v0.2.2 - fix: use bounded read for bundle download HTTP responses (github#3764) - Update iSAQB Architecture Governance preset to v0.2.2 (github#4056) - Fixed broken merge commit (single parent -> proper 2-parent merge) Assisted-by: opencode (model: glm-5.2, supervised)
itaior
approved these changes
Aug 12, 2026
The test_resolve_accepts_dotted_command_name test asserted 'constitution.md' as a substring of CliRunner output, but Rich wraps long runner worktree paths (e.g. Windows CI: 'D:\a\...\constitutio\nn.md') at 80 columns mid-word, breaking the assertion. Strip all whitespace from the output before checking so the assertion is stable regardless of terminal width or wrap position. Assisted-by: opencode (model: glm-5.2, supervised)
Co-authored-by: root <kinsonnee@gmail.com>
… with main - fix: Alquimia argument hints after folded descriptions (github#4063) - Merge origin/main to incorporate PR #109/#110/#111 merge commits (0.16.0+adlc3 release tagged on main) Assisted-by: opencode (model: glm-5.2, supervised)
The fork's update-agent-context.ps1 does extra work vs upstream (team- directives block, self-create template, init-options.json read) that adds latency on Windows PowerShell 5.1. Under CI load the 30s timeout was too tight, causing test_powershell_script_discovers_nested_plan to time out. 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.
Upstream merge: 0.16.0+adlc3 → 0.16.2+adlc1
Syncs the fork with upstream
github/spec-kitreleases 0.16.1 and 0.16.2 (52 commits total).New upstream features adopted
command-codeintegration (Add Command Code integration to spec-kit github/spec-kit#4019) — new skills-based CLI agent, registered alphabetically in_register_builtins(), uses$speckit-*invocationfeat(presets): resolve constitution templates at command time(feat(presets): resolve constitution templates at command time github/spec-kit#3984) — constitution template resolved viaresolve-templatescript at command time instead of install-time materializationfeat(extensions): accept provides.templates and provides.scripts in manifest(feat(extensions): accept provides.templates and provides.scripts in manifest github/spec-kit#4012) — extension manifests can declare templates and scripts[x]= reviewed, not implemented), read-only gate in implement command,--template checklist-templateflagTASKS_TEMPLATE_CONTENT/TEMPLATE_CONTENTreplace path-based template loading intasks/checklist/constitutioncommandsscripts:frontmatter added toconstitutioncommand forresolve-template.sh/ps1/pyUpstream fixes adopted
operationgithub/spec-kit#3881, fix(workflows): strip a resolved condition before the true/false check github/spec-kit#3883)provides.templates/scriptsrejection (fix(extensions): reject duplicate provides.templates/scripts names github/spec-kit#4016)preset removegithub/spec-kit#4020)missing_ok(fix: use missing_ok=True in extension cache clear github/spec-kit#3845)missing_ok(fix: use missing_ok=True in integration JSON removal github/spec-kit#3846)Community catalog
Conflicts resolved (10 total)
pyproject.toml0.16.2+adlc1scripts/bash/common.shextract_constitution_rules/load_team_directives_config+ adopted upstream_python3_command/_sorted_extension_ids/resolve_template_contentscripts/bash/create-new-feature.shreplace_date_placeholders+ restored missingSPEC_FILEdefinitionscripts/powershell/create-new-feature.ps1\$specFilescripts/python/create_new_feature.pyshlex)src/specify_cli/commands/bundle/__init__.pyaccent()theming + adopted upstream_escape_markupfor user-supplied values (9 sites); fixed duplicate_consoleimportpresets/catalog.jsonupdated_atextensions/EXTENSION-API-REFERENCE.mdruntime_hooks+ upstreamtemplates/scriptsREADME.md/spec.*prefix + upstream Command Code mentiontests/integrations/test_integration_catalog.pyadbb0146open_urlmock workaround obsolete with upstream'sroute_opener_open_through_urlopenfixtureFork customizations preserved
accent()theming (tikalk orange#f47721) in bundle CLI_init_fork.py,_core_fork.py,_assets_fork.py,_base_fork.py,_workflows_fork.py,extensions_fork.py)speccommand prefix (vs upstreamspeckit)load_team_directives_config/extract_constitution_rulesincommon.shreplace_date_placeholdersin create-new-feature scriptstikalk/agentic-sdlc-spec-kitvs upstreamgithub/spec-kit)Test adaptations
test_integration_command_code.py— forkPKG_NAMESprefix adaptation for\$spec-constitutionvs$speckit-constitutionTemplate-to-preset alignment
Ported upstream template changes to 5 preset command files per the FORK.md alignment map:
adlc.spec.checklist.md—--templateflag, ownership section,TEMPLATE_CONTENTadlc.spec.constitution.md—scripts:frontmatter forresolve-templateadlc.spec.implement.md— checklist marker semantics (completed→checked, read-only gate)adlc.spec.tasks.md—TASKS_TEMPLATE_CONTENTchecklist-template.md— Review Ownership + Marker SemanticsVerification
ruff@0.15.0)command-codeintegration registered (INTEGRATION_REGISTRYcount = 38)ACCENT_COLOR = #f47721)Assisted-by: opencode (model: glm-5.2, supervised)