Skip to content

Integrate upstream github/spec-kit v0.16.5 - #109

Merged
satwareAG-ironMike merged 127 commits into
main-speckfrom
sync/upstream-v0.16.5
Aug 20, 2026
Merged

Integrate upstream github/spec-kit v0.16.5#109
satwareAG-ironMike merged 127 commits into
main-speckfrom
sync/upstream-v0.16.5

Conversation

@satwareAG-ironMike

Copy link
Copy Markdown

Integrates upstream github/spec-kit v0.16.5 into the fork (spanning v0.16.0 - v0.16.5, 123 commits, 178 files).

Changes

  • Merge tag v0.16.5 (9 conflicts resolved: CHANGELOG.md, pyproject.toml - took upstream; version 0.15.2 -> 0.16.5; 7 fork-parity test files - union of fork script inventory + upstream's new resolve-template.sh)
  • fix(tests): junie inventory expectations - upstream v0.16.5 replaced the fork's thin junie test subclass with a standalone file carrying upstream's script list; re-added the fork's extra scripts (check-privacy-leaks.sh, check-upstream-sync.sh, eod.sh, pre-pr.sh, sod.sh)
  • chore(ipadp): bump specs/metadata.json to fork_version: satware-v0.16.5, project_version: 0.16.5

Fork files preserved: AGENTS.md IPADP section, llms.txt, specs/metadata.json, tests/integrations/test_fork_agent_parity.py, privacy/upstream-sync workflows, .privacy-whitelist, fork scripts (sod.sh, eod.sh, pre-pr.sh, check-privacy-leaks.sh, check-upstream-sync.sh).

Notable upstream highlights (v0.16.0 - v0.16.5): Mistral Vibe integration, Command Code integration, Junie dot-to-hyphen command formatting, feature-assess agentic workflow, TOCTOU/UTF-8/overlay hardening series, event hook path confinement (github#4133), extension catalog trust model clarification.

Validation

  • pytest --no-cov (full suite): 6989 passed, 10 skipped, 0 failures (1 pre-existing fork-parity fix applied)
  • daily-routine.sh pre-pr: ruff + tests + privacy + upstream-sync + provenance - all passed
  • check-upstream-sync.sh: OK: Up to date with upstream v0.16.5

Closes #103, closes #104, closes #105, closes #106, closes #107, closes #108.


Authored by the nightshift opencode agent session (model: z-ai/glm-5.3) on behalf of @mw, per the satware AG IPADP L3 workflow. Commits carry Assisted-by: trailers.

mnriem and others added 30 commits August 3, 2026 14:08
* 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>
* 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.
* fix: skip corrupted run state files in list_runs

* fix: address review comments - add UnicodeDecodeError, dict validation, and regression tests

- Catch UnicodeDecodeError for invalid UTF-8 encoding
- Validate loaded JSON is a dict with required 'run_id' key
- Add 5 regression tests for corrupted state files

Fixes github#3814
* fix: skip corrupted run state files in list_runs

* fix: address review comments - add UnicodeDecodeError, dict validation, and regression tests

- Catch UnicodeDecodeError for invalid UTF-8 encoding
- Validate loaded JSON is a dict with required 'run_id' key
- Add 5 regression tests for corrupted state files

Fixes github#3817
`CommandRegistrar.render_frontmatter` calls `yaml.dump()` without `width=`,
so PyYAML applies its default ~80-column wrap and folds any long scalar onto
a continuation line.

A `description` longer than roughly 80 characters is therefore rendered as:

    ---
    name: speckit-implement
    description: Execute the implementation plan by processing and executing all tasks
      defined in tasks.md
    ---

The YAML remains valid and round-trips faithfully through `yaml.safe_load`,
so this is not data loss. It is a shape inconsistency with real consequences:

- Hand-written core command templates always keep `description` on one line,
  so preset- and extension-rendered commands do not match the files they sit
  beside in the same directory.
- Consumers that read frontmatter line-wise rather than with a YAML parser
  see the description truncated at the fold, followed by a stray line. Spec
  Kit itself hand-builds SKILL.md frontmatter in the skills path (see github#3391),
  so this is not a hypothetical class of consumer.
- `speckit.implement`'s own description is 89 characters, so a preset that
  overrides it hits this immediately.

`width=float("inf")` disables the line-wrapping only; escaping, quoting and
the handling of genuinely multi-line values are unchanged, since PyYAML
selects the scalar style before applying width.

Adds a regression test that fails without the change.

Verified against the repo's own suite: 6354 passed. Four failures in
tests/integrations/test_integration_subcommand.py are present on a clean
checkout too (ANSI escapes in captured output) and are unrelated.
* chore: bump version to 0.16.0

* chore: begin 0.16.1.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ge (github#3892)

_check_file/_check_dir hard-code U+2713/U+2717 and print() them to
sys.stdout. On Windows sys.stdout falls back to the ANSI code page whenever
stdout is not a console — which is every time an agent or a workflow step
captures the output — and U+2713 is unencodable in cp1252, so the document
listing aborted mid-report with UnicodeEncodeError.

This is the byte-identical twin of the block in
scripts/python/check_prerequisites.py, which I flagged in the PR for that
file rather than widening its scope.

Fall back to ASCII when stdout cannot encode the glyph. "[OK]"/"[FAIL]" is
the rendering these markers already have in-tree: Test-FileExists in
scripts/powershell/common.ps1 emits exactly those, and
normalize_status_text in tests/parity_helpers.py maps the glyphs onto them.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… escaping (github#3392)

Add regression tests for SkillsIntegration mixin that verify:
- Multiline (block-scalar) description round-trips byte-for-byte
- C0/DEL control characters in description survive YAML escaping

Tests properly isolate Path.home() for Hermes to prevent overwriting
a developer's real global skill directory.

Refs: github#3392
…ub#3938)

* fix(archives): wrap the bare EOFError a truncated tar.gz raises

`tarfile` wraps most decompression failures in `TarError`, but a gzip
stream that ends before its end-of-stream marker escapes as a bare
`EOFError` from the gzip layer. `EOFError` derives from neither
`TarError` nor `OSError`, so it bypassed all three of the tar handlers
added with tar archive support (github#3874):

- the format probe in `detect_archive_format`, which caught only
  `tarfile.TarError`;
- `tarfile.open` in `safe_extract_tar`;
- member iteration in `safe_extract_tar`.

A truncated `.tar.gz` — an interrupted download, a partially written
file — therefore raised a raw `EOFError` straight through the caller's
`error_type`, so callers catching `ValueError`/`ExtensionError`/
`PresetError` never saw it. In `specify workflow add` the effect is worse
than a traceback: Typer treats a bare `EOFError` as a Ctrl-D abort, so
the command printed only "Aborted." with no diagnostic at all. The ZIP
twin reports "Invalid workflow archive: Invalid ZIP archive: <path>".

Route all three sites through a shared `_TAR_DECOMPRESSION_ERRORS`
tuple so they stay in sync. `zlib.error` is included alongside
`EOFError`: it is likewise neither a `TarError` nor an `OSError` and can
surface from a corrupt deflate block. `OSError` is kept only on the two
`safe_extract_tar` sites, which report genuine I/O failures; adding it
to the probe would silently swallow them instead.

Truncated tar.gz now reports the same clean, domain-typed error as the
ZIP path. Tests cover both the short prefix that fails in
`tarfile.open` and the longer ones that fail during member iteration —
`tarfile` decompresses lazily, so the leak surfaced at different sites
depending on how much of the stream survived.

Assisted-by: Claude Opus 5 (1M context)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(archives): cover the bare zlib.error a corrupt deflate block raises

Review feedback: the `zlib.error` arm of `_TAR_DECOMPRESSION_ERRORS` was
not exercised. Every regression added with the fix truncates a valid
deflate stream, which raises `EOFError`, so `zlib.error` could regress
independently of the EOF handling.

It is genuinely reachable, but only under a narrower condition than the
truncation cases. `tarfile` converts `zlib.error` to `ReadError` while
reading a member *header*, but the forward seek it performs to skip
member *data* (`tarfile.next`) sits outside that conversion, so a corrupt
region past the first header escapes raw. Reaching that seek needs
members larger than the gzip read buffer: with small members the whole
stream is decompressed during the first header read and the error is
wrapped. The new fixture therefore uses two 256 KiB members at
`compresslevel=1` — a ~7 KiB archive — corrupted past the midpoint so
the first header still reads clean.

Adds four tests: the two `safe_extract_tar` sites (plain and with a
caller-supplied `error_type`), the `safe_extract_archive` entry point
with a caller-supplied `error_type`, and a guard asserting the fixture
still reaches the module as a bare `zlib.error` — so if a future Python
wraps it, that fails loudly instead of the coverage silently decaying
into a duplicate of the `EOFError` cases.

Verified test-the-test: the three wrapping tests fail against the
unmodified `_download_security.py` with a raw
`zlib.error: Error -3 while decompressing data: invalid distance code`,
and pass with the fix.

Also corrects the scope claimed for the probe site. Fuzzing 2800 corrupt
archives never produced a bare `zlib.error` from `tarfile.open` alone,
because the only read it performs is the header read that `tarfile`
already converts. The probe's `zlib.error` arm is defensive, not
load-bearing; the tuple comment and a detection test now say so rather
than implying coverage that cannot exist.

Assisted-by: Claude Opus 5 (1M context)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(archives): make the corrupt-deflate fixture zlib-version independent

CI failure on macos-latest/3.13: `test_corrupt_deflate_fixture_raises_bare_zlib_error`
failed with `gzip.BadGzipFile: CRC check failed`. The other five pytest jobs
were fail-fast cancellations, not real failures, and ruff was already green.

The fixture built its corruption by XOR-ing 64 arbitrary bytes mid-stream.
Whether that produces a *structural* deflate error is zlib-version dependent:
on the macOS runner the mangled bytes still decoded, so the stream instead
failed the trailing gzip CRC check and raised `BadGzipFile` -- an `OSError`,
which the pre-fix `(TarError, OSError)` handler already caught. The guard test
exists precisely to catch that degradation, and it did its job.

Replaces the XOR with a deflate block header whose `BTYPE` is the reserved
value `0b11`. Every zlib rejects that identically as "invalid block type", and
it fails during decompression rather than at the CRC check, so no version can
turn it into a `TarError` or `OSError`. The stream is assembled by hand
(`compressobj(-15)` + explicit gzip header/trailer) so the invalid block lands
a controlled 256 KiB into the first member's data -- past the gzip read buffer,
so the first header still reads clean and the failure surfaces from the forward
seek in `tarfile.next`, which is the site the raw `zlib.error` escapes from.

A sweep over clean-prefix sizes confirms a wide margin: with 512 KiB members
every prefix from 160 KiB up yields a bare `zlib.error`, versus the transition
below ~131 KiB where `tarfile` still wraps it as `ReadError`. The hand-built
gzip header also zeroes the mtime field, so the fixture is now byte-identical
across builds instead of embedding a timestamp.

Strengthens the guard to assert what the fix actually depends on -- that the
exception is neither a `TarError` nor an `OSError` -- so the fixture cannot
silently decay into an already-caught type again.

Production code is unchanged from ef49acc; this is test-only.

Verified test-the-test by dropping the `zlib.error` arm from
`_TAR_DECOMPRESSION_ERRORS`: the three wrapping tests fail with the raw
`zlib.error: Error -3 while decompressing data: invalid block type`, and pass
with it restored. `tests/test_download_security.py`: 193 passed.
`ruff check src tests` (the exact CI command): all checks passed.

Assisted-by: Claude Opus 4.8 (1M context)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
github#3739)

Hermes overrides SkillsIntegration.setup() with its own copy of the
frontmatter parse and body strip, and Kimi's _is_speckit_generated_skill()
parses frontmatter independently, so all three carried the same
split("---", 2) bug the base class just fixed. A description such as
"Separate sections with --- markers" truncates the parsed frontmatter at the
embedded marker, dropping later keys and spilling the remainder into the
body; for Kimi that means a Speckit-generated skill is no longer recognized
on teardown and gets left behind.

Scan for a closing "---" on its own line instead. The body slice keeps
whatever trails the marker so output stays byte-for-byte identical for
well-formed templates.
…re --force (github#3995)

Apply the remediation from the bug assessment on issue github#3990.

After integration setup() and manifest.save(), when --force is used
(re-initializing an existing project), call _register_presets_for_agent
and _register_extensions_for_agent so that previously-installed presets
and extensions are recomposed on top of the freshly-regenerated core
files. Without this, preset-composed files reverted to pure core while
the preset registry continued to report them as installed.

This mirrors the same pattern already present in integration_upgrade()
(added in PR github#3853 / issue github#3849 for the upgrade path).

Refs github#3990

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>
…b#3998)

ExtensionRegistry._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 from
the text-mode read before JSON parsing began. Because the registry is
loaded in __init__, that bare traceback broke every extension command --
`specify extension list` on such a project exits with a raw
UnicodeDecodeError instead of the module's clean path.

Catch UnicodeDecodeError in the same clause: undecodable bytes are the
same corruption class as unparseable JSON, only the exception type
differs. OSError stays uncaught on purpose -- the data may be intact on
disk, and starting fresh would let a later _save() wipe it.

This is the exact twin of the PresetRegistry._load() fix in github#3955; the
two registries are parallel implementations and only the preset side was
corrected. _get_installed_sibling_ids() already worked around this gap
locally by catching UnicodeError at its own call site; its comment is
updated to reflect that _load() now handles the case itself, with the
local catch kept as belt-and-braces against regression.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ithub#3959)

* fix(presets): return None for an unreadable layer in resolve_content

PresetResolver.resolve_content() reads the winning layer (and each
composition layer) with a bare read_text(), so a layer file that cannot
be read or decoded crashed command registration with a raw
OSError/UnicodeDecodeError. The docstring already promises 'Composed
content string, or None if not found', and since github#3896
collect_all_layers() deliberately tolerates a non-UTF-8 legacy layer —
moving the crash here, where both callers (_register_commands and
_reconcile_composed_commands) are unguarded.

Return None when the winning or base layer cannot be read, treating an
unreadable layer like a missing one per the documented contract.

Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: cover the base guard and composing-layer read

Review follow-up: add an unreadable replace base beneath a valid
composing layer, and a mocked-PermissionError composing layer over a
valid base, so every new boundary and both exception types are covered.

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>
… stdout code page (github#3890)

* fix(scripts): stop check-prerequisites text mode crashing on a legacy code page

_check_file/_check_dir hard-code U+2713/U+2717 and print() them to
sys.stdout. On Windows sys.stdout falls back to the ANSI code page whenever
stdout is not a console — which is every time an agent or a workflow step
captures the output — and U+2713 is unencodable in cp1252:

  stdout encoding: cp1252
  UnicodeEncodeError: 'charmap' codec can't encode character '✓'

So text mode aborted right after printing "AVAILABLE_DOCS:", losing every
per-document line.

Fall back to ASCII when stdout cannot encode the glyph. "[OK]"/"[FAIL]" is
the rendering these markers already have in-tree: Test-FileExists in
scripts/powershell/common.ps1 emits exactly those, and
normalize_status_text in tests/parity_helpers.py maps the glyphs onto them,
so the twins already treat the two forms as equivalent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(scripts): cover both status markers in the cp1252 regression

Review catch: the fixture left every reported document absent (the empty
contracts/ also reports missing), so the test only ever called
_status_marker(False). The assertion was `"[OK]" in out or "[FAIL]" in out`,
which "[FAIL]" alone satisfied.

Proved the hole by mutation: replacing the fallback body with a bare
`return "[FAIL]"` — deleting the success branch outright — left the test
GREEN.

Add research.md so one document is present, and assert both markers
explicitly. The strengthened test now kills all three mutations:

  fallback always "[FAIL]"  -> FAILS (was passing)
  fallback always "[OK]"    -> FAILS
  no fallback at all        -> FAILS (the original bug)
  unmutated                 -> 12 passed, 8 skipped

Missing documents are still present in the fixture, so the failure path
stays covered too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(scripts): restore the _status_marker ASCII fallback

The previous commit on this branch unintentionally reverted the source fix
while adding the strengthened test, so the branch carried the test without
the implementation it tests.

Cause: my local verification script reverted the file for its red run with
`git checkout upstream/main -- <file>`, which writes the INDEX as well as
the working tree. Restoring the working-tree copy afterwards left main's
version staged, and the next commit captured it.

Restores the fix from 275663b. Verified: 12 passed / 8 skipped, and the red
run (source reverted) produces 1 new-vs-baseline failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(init): scaffold managed .specify/.gitignore

Write a manifest-tracked `.specify/.gitignore` during shared-infra install
so machine-local Spec Kit state stays out of version control while everything
else under `.specify/` remains shareable:

- `feature.json` — the current-feature pointer, rewritten on every feature
  switch (per-checkout state, not something to share).
- `extensions/*/local-config.yml` — per-machine extension config overrides.

The file is routed through the same overwrite/skip/preserve policy as shared
templates: `--force` refreshes it, user edits are preserved on re-init, and
uninstall removes it via the manifest. Addresses github#2304.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 98faefd1-9fc8-48fc-bd25-d4f3ccbb2ab9

* docs: correct .specify/.gitignore uninstall claim

The file is tracked in the shared-infra manifest (speckit.manifest.json),
not the per-integration manifest that `specify integration uninstall` loads.
Shared infrastructure is deliberately preserved on uninstall
(see test_uninstall_preserves_shared_infra), so `.specify/.gitignore` is
left in place rather than removed. Reword the code comment and core.md note
to state the actual behavior; keep the true benefits (force-refresh and
preserve-on-edit).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 98faefd1-9fc8-48fc-bd25-d4f3ccbb2ab9

* revert: drop manual CHANGELOG.md edit

CHANGELOG.md is auto-generated; do not hand-edit it.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 98faefd1-9fc8-48fc-bd25-d4f3ccbb2ab9

* test: add .specify/.gitignore to integration file inventories

The complete-file-inventory tests assert an exact match of every file
produced by `specify init`. Now that shared infra scaffolds a managed
`.specify/.gitignore`, add it to the expected inventories so the exact-match
assertions pass on both sh and ps script types.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 98faefd1-9fc8-48fc-bd25-d4f3ccbb2ab9
github-actions Bot and others added 27 commits August 14, 2026 10:17
Update intake-authoring-governance preset submitted by @hindermath:
- presets/catalog.community.json (version, download_url, documentation, description, templates count)
- docs/community/presets.md community presets table

Closes github#4118

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 specassay preset submitted by @rdryfoos to:
- presets/catalog.community.json (alphabetical order)
- docs/community/presets.md community presets table

Closes github#4058

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>
* chore: bump version to 0.16.4

* chore: begin 0.16.5.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Adds specassay v0.3.4 to bundles/catalog.community.json and
docs/community/bundles.md.

Validation results:
- Bundle ID matches ^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$: pass
- Version 0.3.4 is valid semver X.Y.Z: pass
- Repository https://github.com/rdryfoos/specassay is a public GitHub repo
  containing bundle.yml, README.md, and LICENSE: pass
- bundle.yml fields match submission (id, name, version, role, author,
  license, speckit_version, provides 1 extension + 1 preset): pass
- README documents role, components, required catalogs, and install steps: pass
- Download URL is a valid HTTPS GitHub release asset under the submitted repo: pass
- Release v0.3.4 exists and specassay-0.3.4.zip is attached: pass
- Catalog entry fields match submission and manifest; verified=false: pass
- Tags are 2-5 lowercase strings: pass (5 tags)
- Required catalogs documented (extensions + presets); README includes
  catalog add commands and testing details confirm catalog registration: pass
- All checklist items checked: pass

Closes github#4059
cc @rdryfoos

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ent references (github#3885)

* fix(bundler): resolve built-in step types when checking bundle references

`_resolved_locally` gives three of the four component kinds a
"is it bundled with Spec Kit?" check before the installed-in-project one:

  presets    -> _locate_bundled_preset   or PresetManager.get_pack
  extensions -> _locate_bundled_extension or ExtensionManager...is_installed
  workflows  -> _locate_bundled_workflow  or WorkflowRegistry.is_installed
  steps      -> StepRegistry.is_installed          <-- no bundled check

`StepRegistry` tracks *community* step types installed under
`.specify/workflows/steps/`. Spec Kit ships 11 step types as built-ins
registered in `STEP_REGISTRY`, so every one of them looked unresolved:

  steps/shell   -> False
  steps/gate    -> False
  steps/command -> False
  steps/if      -> False

A bundle declaring a dependency on any built-in step type was therefore
reported as an unresolved reference — an error online, a warning offline.

There is no `_locate_bundled_step` to mirror, because step types are not
an on-disk asset directory; `STEP_REGISTRY` is the equivalent check, and
is what `specify workflow step info` reports as "built-in".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(bundler): check an immutable built-in step set, not the mutable registry

Review catch: `STEP_REGISTRY` is not limited to bundled steps.
`load_custom_steps` adds project-installed ids to that process-global
mapping and never removes them, so in a long-lived process a community step
loaded while working on project A would be accepted as "bundled" when
validating a bundle for project B — before B's own StepRegistry is
consulted.

Snapshot the shipped ids into `BUILTIN_STEP_TYPES` immediately after
`_register_builtin_steps()`, before `load_custom_steps` is even defined,
and check that frozenset instead.

Verified: with the check on STEP_REGISTRY the new cross-project test fails
(a leaked community id resolves as bundled); with BUILTIN_STEP_TYPES it
passes. 1 failed, 5 passed -> 6 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e error (github#3884)

* fix(workflows): report a falsy non-mapping overlay manifest as a shape error

`ProjectOverlaySource.collect` did `yaml.safe_load(...) or {}`.
`validate_overlay_yaml` opens with an `isinstance(data, dict)` check, so a
truthy non-mapping is reported correctly — but `or {}` replaced the falsy
non-mappings with an empty mapping first, so those files were reported as
three bogus missing-field errors instead of the wrong shape:

  '- a'    -> ['Overlay manifest must be a mapping.']
  'hello'  -> ['Overlay manifest must be a mapping.']
  '[]'     -> ["Overlay 'id' is required...", "'extends' is required...",
               "'edits' is required..."]
  'false'  -> same three
  '0'      -> same three
  "''"     -> same three

The sibling reader for these same files in the same package, `_read_overlay`
in overlays/_commands.py, does not coerce.

Only an empty document (None) now becomes an empty mapping, so a genuinely
empty overlay still reports its missing fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(workflows): distinguish an empty document from an explicit YAML null

Review catch: `safe_load` returns None for an explicit null scalar
(`null`, `~`, `Null`, `NULL`) as well as for an empty document, so the
`data is None` normalization still converted those manifests to `{}` and
they still received missing-field errors instead of the mapping-shape
error.

Use `yaml.compose`, which yields no node only for a genuinely empty
document, to tell the two apart. Measured:

  empty doc          -> missing-field   (correct)
  explicit null      -> SHAPE
  explicit ~         -> SHAPE
  NULL               -> SHAPE
  [] false 0 ''      -> SHAPE
  - a / hello        -> SHAPE

Extends the parametrized cases with null/~/NULL, and corrects the article
before `isinstance` in the docstring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
github#4134)

`specify workflow add --from <url>` creates a delete=False temp file
before streaming the response body into it. The except clauses around
that read only handled typer.Exit (re-raise, no cleanup) and Exception
(cleanup + re-raise). KeyboardInterrupt is a BaseException, so Ctrl+C
during the size-limited read skipped both and left the file behind in
the system temp directory.

Adds a shared cleanup helper and a BaseException handler so any exit
path after the temp file is created -- error, typer.Exit, or interrupt
-- unlinks it, matching the existing best-effort cleanup on other
download errors.

Assisted-by: Claude Sonnet 5 (autonomous)
…event terminal line-wrap failures (github#4166)

Signed-off-by: aoright <102943475+aoright@users.noreply.github.com>
Update intake-review-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, provides)
- docs/community/presets.md community presets table

Closes github#4127


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 ascii-diagram extension submitted by @MRZHUH to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes github#4161

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>
…amps (github#4175)

* Initial plan

* Add pay-x402 community extension with catalog-addition date timestamps

Assisted-by: GitHub Copilot (model: unknown, autonomous)

Co-authored-by: mnriem <15701806+mnriem@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#4177)

* Clarify extension catalog trust model in docs, help, and messaging (github#4176)

Extension catalog management gave no explanation of why the community
catalog is discovery-only, and the install-error text nudged users to
flip a discovery catalog to install_allowed — exactly the wrong move.

- Docs: add a "discovery-only vs. install sources" trust-model section,
  document `add --from <url>` as the lightweight vetted-install path, and
  stop implying you should make community installable.
- Help: expand the `catalog` app and `--install-allowed` help to state the
  vetting intent instead of bare mechanics.
- Messaging: rewrite the not-installable errors in `add`, `search`, and
  `info` to point at `--from` and self-curated catalogs, and to say
  explicitly not to flip a discovery-only catalog to install_allowed.
- `catalog list` now prints trust-model guidance when a discovery-only
  catalog is active.
- Tests cover the new list guidance (present/absent).

Deliberately does not add a verb to toggle install_allowed on an existing
catalog: discovery-only is a security boundary, not an inconvenience.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a86c498e-f129-4422-9983-d1a33513fd4d

* Address PR review: copy-pasteable install hint and accurate --from warning (github#4176)

- The discovery-only "install directly" hint used the user-typed argument,
  which can be a display name with spaces (resolved via search) and would
  break when copied as a shell command. Emit the resolved catalog ID
  (ext_info['id']) instead. Added a regression test.
- The `--from` untrusted-source warning claimed the URL was "not listed in
  any of your configured extension catalogs", which is false for a URL
  copied from a discovery-only catalog — the exact flow this PR documents.
  Reword it to state the install is bypassing trusted (install-allowed)
  catalogs, which is accurate regardless of discovery-catalog membership.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a86c498e-f129-4422-9983-d1a33513fd4d

* Harden install hints against catalog-controlled IDs; expose archive URL (github#4176)

Second review round on github#4177.

Shell-safety: catalog entry IDs (especially from discovery-only catalogs)
are not validated during catalog merge, and rich.markup.escape only
neutralizes Rich markup, not shell metacharacters. A malicious ID like
`foo; rm -rf ~` was interpolated into the `specify extension add ... --from`
command we encourage the user to copy. Add `_command_safe_id`, which only
emits an ID matching the manifest rule `^[a-z0-9-]+$` (via
VALID_EXTENSION_ARTIFACT_NAME_PATTERN) and otherwise falls back to a literal
`<extension-id>` placeholder. Applied to every suggested command in `add`,
`search`, and `info`.

Discoverability: the documented `--from <archive-url>` flow gave no CLI path
to obtain the URL. `extension info` now prints the candidate `download_url`
for a discovery-only entry (clearly flagged as needing vetting), and the docs
show `extension info <name>` as the way to get the archive URL.

Tests cover the resolved-ID hint, the unsafe-ID neutralization, and pass the
full extensions + CLI suites (635).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a86c498e-f129-4422-9983-d1a33513fd4d

* Reject leading-hyphen catalog IDs; test info archive-URL branch (github#4176)

Third review round on github#4177.

_command_safe_id: an ID like `--force` satisfies the manifest character rule
`^[a-z0-9-]+$` but Typer parses a leading hyphen as an option rather than the
positional extension argument, so an untrusted catalog could still yield a
non-copyable or option-altering suggested command. Reject a leading hyphen and
fall back to the `<extension-id>` placeholder.

Tests: cover the new `extension info` discovery-only branch that surfaces the
candidate `download_url` (plus the no-URL fallback), and the leading-hyphen
rejection. Full extensions suite green (528).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a86c498e-f129-4422-9983-d1a33513fd4d

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a86c498e-f129-4422-9983-d1a33513fd4d
* fix: confine event hook scripts to the project tree

Event dispatch joined the first scripts: token onto the .specify or
extension base with Path. An absolute token discarded the base and ran
a host binary. Reject anchored tokens and require the resolved path to
stay inside the project root.

Assisted-by: Grok (model: grok-4.6, supervised)
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix: refuse stale specify_cli.events without path confinement

Generated dispatchers only delegate when EVENT_SCRIPT_PATH_CONFINEMENT
is True, so an older global install cannot bypass the project-tree
guard.

Assisted-by: Grok (xAI, under direct human supervision)
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Update keel extension submitted by @athulrajeev:
- extensions/catalog.community.json (version, download_url, description, provides.commands)
- docs/community/extensions.md community extensions table

Closes github#4154

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>
* Initial plan

* Add AgentPay x402 extension to community catalog with review-corrected timestamps

Assisted-by: GitHub Copilot (model: unknown, autonomous)

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* chore: re-trigger CI (transient CodeQL infra 503)

The default-setup CodeQL run failed with a transient GitHub server error and could not be retried on its own. Empty commit to force a clean re-run.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 20f1fc64-ba5b-4f3d-82af-855c43952ab3

* chore: re-trigger CodeQL after GitHub 503 incident

Prior default-setup CodeQL init failed on transient GitHub API 503s. Retrying now that the API has recovered.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 20f1fc64-ba5b-4f3d-82af-855c43952ab3

* chore: re-trigger CodeQL after API Requests recovery

GitHub API Requests outage (feature-enablement endpoint) has recovered; retriggering CodeQL for a clean run.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 20f1fc64-ba5b-4f3d-82af-855c43952ab3

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Copilot-Session: 20f1fc64-ba5b-4f3d-82af-855c43952ab3
…4170)

* Add DUBSAR Memory extension to community catalog

Add dubsar extension submitted by @kotnisofiane-bit to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes github#4130

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review: minimize catalog churn and add dubsar sha256

Reserialize the community catalog back to its original formatting and
entry order so the patch is limited to the top-level timestamp plus the
new dubsar entry, and add the published asset's sha256 digest so catalog
installs enforce archive verification.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@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: Manfred Riem <15701806+mnriem@users.noreply.github.com>
…rnesses (github#4178)

* fix(init): stop specify init hanging on arrow-key pickers in agent harnesses

Agent harnesses often allocate a PTY so isatty is true, but they cannot send
arrow keys. Fail fast when stdin is not a TTY, and add --non-interactive so
scripted init applies defaults instead of hanging.

Fixes github#4152.

* test(init): assert --non-interactive never prompts for URL extension trust

Cover the HTTPS --extension confirmation path when stdin is a TTY: deny
without --trust-extension-urls, and install with it, both without calling
typer.confirm.
…b#4183)

* Update Superpowers Implementation Bridge to v1.2.0

Update speckit-superpowers-bridge extension submitted by @lihan3238:
- extensions/catalog.community.json (version, download_url, updated_at)
- docs/community/extensions.md community extensions table (no change needed)

Closes github#4180

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pin speckit-superpowers-bridge download_url to v1.2.0 tag

Replace the floating releases/latest alias with the tag-pinned asset URL
so the catalog entry serves the immutable v1.2.0 artifact, matching the
convention used by every other entry and the workflow's required pattern.

Assisted-by: GitHub Copilot (model: claude-opus-4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd1776f5-b547-41af-a28e-286bf336f4a4

---------

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: fd1776f5-b547-41af-a28e-286bf336f4a4
… Kit (github#4186)

* feat: add feature-assess agentic workflow that installs and runs Spec Kit

Add a gh-aw agentic workflow (Copilot engine) that, when an issue is labeled
`feature-assess`, installs the Spec Kit CLI, initializes it for Copilot, installs
the `assess` extension, and runs its five-stage idea-assessment pipeline
(intake → research → define → shape → decide) against the issue. Setup and
execution are captured entirely as prose the agent runs with its bash tools —
no imperative steps: block. Each stage's artifact is posted as its own issue
comment (summarized if it exceeds the comment size limit), then one verdict
label is applied (feature-go / feature-needs-clarification / feature-kill, or
feature-invalid).

Frontmatter grants the bash commands (uv, specify, curl, …) and network egress
(python, github, astral.sh) needed for the prompt-driven install, and pins the
Copilot engine. Includes the compiled feature-assess.lock.yml (gh aw compile,
v0.79.8).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2ec8128b-ee80-4222-b58a-570990994454

* fix: address PR review — reproducible install, correct failure labeling, network parity

Address the three findings from the automated review on github#4186:

- Install the Spec Kit CLI from the checked-out revision ($GITHUB_WORKSPACE)
  instead of the mutable default branch, so each run uses the exact CLI and
  bundled assess instructions of the workflow commit under evaluation (with a
  pinned git+…@$GITHUB_SHA fallback). Fixes reproducibility.
- On install/network failure, stop and post a comment WITHOUT applying any
  verdict label; feature-invalid is reserved for unassessable request content,
  not operational/runner failures. Fixes mislabeling valid requests.
- Add gitlab.com, stackoverflow.com, and *.stackexchange.com to network.allowed
  so the firewall allowlist matches the hosts the prompt permits fetching from.

Recompiled feature-assess.lock.yml (gh aw compile, v0.79.8).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2ec8128b-ee80-4222-b58a-570990994454

* fix: address review round 2 — gist host in allowlist, honest failure note

Address the two findings from the second automated review on github#4186:

- Add gist.github.com to network.allowed. gh-aw domain entries are exact and
  the github ecosystem does not cover the gist subdomain, so gist fetches the
  URL policy permits were being blocked by the firewall. Regenerated the lock.
- Reword the comment-failure note: add_comment safe outputs are only queued
  during the agent job and delivered in a later safe_outputs job the agent
  cannot observe, so it cannot detect or report a post-time delivery failure.
  Restrict the recovery instruction to queue-time errors and defer delivery
  failures to the run logs/conclusion.

Recompiled feature-assess.lock.yml (gh aw compile, v0.79.8).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2ec8128b-ee80-4222-b58a-570990994454

* fix: address review round 3 — keep exactly one verdict label on reassessment

Address the finding from the third automated review on github#4186:

- add-labels only adds, so re-running the assessment (feature-assess removed
  and re-added) could leave a stale feature-* verdict alongside the new one.
  Configure remove-labels for all four verdict labels and instruct Step 7 to
  strip any existing verdict label before adding the current result, so the
  issue always carries exactly one feature-* verdict (feature-invalid included).

Recompiled feature-assess.lock.yml (gh aw compile, v0.79.8).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2ec8128b-ee80-4222-b58a-570990994454

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2ec8128b-ee80-4222-b58a-570990994454
The feature-assess agentic workflow installs and runs the Spec Kit CLI
via `uv`/`python3`, but its job had no `steps:` to provision them, so
`uv`/`uv tool install`/`python3` were unavailable inside the gh-aw
firewall agent container (only Node is preinstalled). This mirrors the
`bug-test` workflow, which already sets up uv + Python.

Add `Setup uv` (astral-sh/setup-uv) and `Set up Python`
(actions/setup-python) steps to feature-assess.md, recompile the lock
file, and note in Step 1 that both are preinstalled by the setup steps.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…st setup steps (github#4195)

The prior fix (github#4193) added setup-uv/setup-python actions but the CLI was
still installed by the agent at runtime, which fails: inside the gh-aw
firewall container `uv` is not on PATH, bare `python3` resolves to PyPy, and
the Copilot permission gate blocks ad-hoc interpreter/installer fallbacks. As
a result `specify` never installed and the assess skills only "worked" by the
agent reading raw command files.

Move provisioning into host setup steps that run before the agent starts
(full network, working PATH):
- Install the CLI with `uv pip install --system` so the `specify` entry point
  lands in the tool-cache Python bin the agent container adds to PATH.
- Run `specify init --here --integration copilot` and
  `specify extension add assess` on the host so the five `speckit.assess.*`
  skills exist when the agent runs.

Rewrite intro + Step 1 so the agent confirms (not installs) the preinstalled
environment, and renumber the pipeline steps accordingly. Mark the setup steps
`continue-on-error` so a provisioning failure still lets the agent start and
post the operational-failure comment instead of hard-failing the job.

Recompile feature-assess.lock.yml.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed10e45c-6fce-48c8-815f-cf905a4e553f
…lines (github#4188)

Test-FileExists / Test-DirHasFiles report their line with Write-Output and
ALSO return $true/$false -- both on the Success stream. setup-tasks.ps1's
text-mode branch piped each call to `| Out-Null` to discard the boolean,
which discarded the report line with it, so AVAILABLE_DOCS: printed with
nothing under it:

  BEFORE (measured, powershell.exe -NoProfile -File ...):
    FEATURE_DIR:...\specs\001-my-feature
    TASKS_TEMPLATE:...\tasks-template.md
    AVAILABLE_DOCS:
    (3 lines)

  AFTER:
    FEATURE_DIR:...\specs\001-my-feature
    TASKS_TEMPLATE:...\tasks-template.md
    AVAILABLE_DOCS:
      [OK] research.md
      [FAIL] data-model.md
      [FAIL] contracts/
      [FAIL] quickstart.md
    (7 lines)

The bash twin (scripts/bash/setup-tasks.sh) lists every document under that
header, so the PowerShell variant silently returned less information for
the same inputs.

Same bug, same fix shape (filter out only the boolean with Where-Object)
as the sibling that was just fixed in check-prerequisites.ps1 (upstream
commit 2b36f0c, PR github#3891) -- this is the unfixed twin call site sharing
the same Test-FileExists/Test-DirHasFiles helpers in common.ps1.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Release v0.16.5

# Conflicts:
#	CHANGELOG.md
#	pyproject.toml
#	tests/integrations/test_integration_base_markdown.py
#	tests/integrations/test_integration_base_skills.py
#	tests/integrations/test_integration_base_toml.py
#	tests/integrations/test_integration_base_yaml.py
#	tests/integrations/test_integration_cline.py
#	tests/integrations/test_integration_copilot.py
#	tests/integrations/test_integration_generic.py
Assisted-by: opencode (model: z-ai/glm-5.3, autonomous)
…(upstream v0.16.5)

Upstream v0.16.5 replaced the thin junie test subclass with a standalone
file carrying upstream's script inventory. Re-add the fork's extra scripts
(check-privacy-leaks, check-upstream-sync, eod, pre-pr, sod) to the expected
bash inventory, mirroring the other fork-parity test resolutions.

Assisted-by: opencode (model: z-ai/glm-5.3, autonomous)
Review finding on PR #109: version was frozen at 0.13.0 while
project_version tracked 0.15.x/0.16.x - bump in lockstep so consumers
reading either field get the same release.

Assisted-by: opencode (model: z-ai/glm-5.3, autonomous)
@satwareAG-ironMike
satwareAG-ironMike merged commit 1e079a6 into main-speck Aug 20, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet