chore: merge upstream 0.16.0 (0.15.2+adlc4 → 0.16.0+adlc1) - #110
Merged
Conversation
* 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
…ithub#3999) `workflow status <run_id>` and `workflow resume <run_id>` both call `RunState.load()`, and a prior fix aligned them on the FileNotFoundError and ValueError boundaries. `resume` also handles OSError; `status` never gained that handler. So an unreadable `state.json` -- wrong permissions, an I/O error, or a directory sitting where the file belongs -- escapes as a raw traceback with no output at all, while `resume` on the same run prints a clean `Error:` line and exits 1. `state_path.exists()` is True for a directory, so the existing guard passes and `open()` raises. Add the missing `except OSError` next to its siblings, using the same `_escape_markup` + `typer.Exit(1)` shape, and routing through `err` so the message lands on stderr under `--json` and the stdout JSON stream stays parseable. Two regression tests: the end-to-end CLI path (a directory in place of state.json) and the `--json` stderr-routing path. Both fail without the source change. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thub#3803) * 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. * fix: use missing_ok for temp file cleanup to avoid masking errors
* fix: bound response read in integration catalog fetch * fix: address review - update FakeResponse for bounded reads and add regression test - Update FakeResponse.read() to accept size parameter for bounded reads - Add test_fetch_rejects_oversized_catalog_response regression test - Verifies _fetch_single_catalog uses MAX_JSON_METADATA_BYTES Fixes github#3812 * fix: resolve lint errors and update FakeResponse to support bounded reads - Remove duplicate imports of MAX_JSON_METADATA_BYTES and read_response_limited - Update FakeResponse.read() to accept size argument for read_response_limited - Add offset tracking for proper bounded read behavior Refs: github#3812
…ub#3787) * fix(init): escape user-supplied values in `specify init` output commands/init.py interpolated the project name, --integration/--script values and paths straight into Rich markup f-strings. It was the only CLI command module without escaping -- extensions, presets, workflows and integrations all wrap user-controlled display values already. Two consequences, both reproduced end-to-end through the real CLI: 1. SILENT WRONG OUTPUT. `specify init "proj [v2]"` exits 0 and creates the directory, but the Next Steps panel prints 1. Go to the project folder: cd proj Rich ate `[v2]` as a style tag, so the command the user copy-pastes fails. 2. CRASH AFTER SUCCESS. `specify init "app[/red]x"` creates the project and then dies with MarkupError("closing tag '[/red]' ... doesn't match any open tag") -> exit 1 with a traceback for work that actually completed. Wrap the user-controlled display values in rich.markup.escape: project name (error/warning/conflict/next-steps), project and working paths, the echoed --integration and --script values, and the agent folder in the gitignore hint. Display only -- no control flow, exit codes or messages change, and escape is a no-op for any value without a tag-shaped bracket run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(init): shell-quote the project name in the Next Steps cd line Rich-escaping stopped the brackets being swallowed, but the printed command was still unusable for any name containing whitespace: `cd proj v2` is two arguments in every shell. $ cd proj v2 -> /bin/bash: line 1: cd: too many arguments (rc=1) $ cd "proj v2" -> rc=0, lands in "proj v2" Quote it for the host the same way _version._render_argv renders its copy-pasteable installer command: subprocess.list2cmdline on Windows, shlex.quote elsewhere. Windows must use double quotes -- cd 'my project' is a path-not-found in cmd.exe, while cd "my project" is accepted by cmd.exe, PowerShell and Git Bash alike. Names needing no quoting are returned unchanged, so the common case is byte-identical. Shell-quote inner, Rich-escape outer. Tests execute the printed command through a real shell rather than only inspecting the string, and pin that an ordinary name stays unquoted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(init): drop the now-redundant local escape imports that shadowed the module one Rebasing onto main brought in three new extension-install helpers, and two of them carry a function-local from rich.markup import escape as _escape_markup inside `register > init`. This PR adds the same import at module level, so the locals made `_escape_markup` a local variable for the whole `init` function — every use *before* those import lines then raised UnboundLocalError: cannot access local variable '_escape_markup' where it is not associated with a value which broke `specify init` outright (7 of 8 tests in this file failed after the rebase, all with exit_code 1). The locals are redundant now that the module-level import exists, so remove them. Verified with an AST scope walk that the only remaining `_escape_markup` imports are the module-level one and the one inside `_confirm_extension_url_trust`, which has no module-level use to shadow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…thub#3834) Both extension and preset registry read/write calls used platform-default encoding, which on Windows (cp1252/UTF-16) would corrupt UTF-8 JSON data or raise UnicodeDecodeError. Explicitly specify encoding='utf-8' to match the JSON contract. Assisted-by: opencode (autonomous)
…directive Add version-history row for the 0.16.0+adlc1 upstream merge (18 commits, 4 conflicts resolved, .gitignore scaffolding, catalog mock-target fix). Refresh the AGENTS.md managed Spec Kit section: mandate team-boot skill invocation before any task, add anti-patterns for skipping the skill check, and fix the team-ai-directives constitution path. Assisted-by: opencode (model: glm-5.2, autonomous)
…ence) The levelup extension's 6 commands (/levelup.init, /levelup.clarify, /levelup.specify, /levelup.skill, /levelup.implement, /levelup.validate) overlap with adlc-team-skills' levelup-* skills/commands. When both toolkits are used together (the recommended flow: install adlc-team-skills first, run team-setup, then specify init without --team-ai-directives), this creates command redundancy with different naming conventions (dot vs hyphen) for the same lifecycle. Setting levelup.preinstall to false removes it from auto-install while keeping the extension bundled and available on demand via 'specify extension install levelup'. Version bump: 0.16.0+adlc1 → 0.16.0+adlc2 Assisted-by: opencode (model: glm-5.2, supervised)
itaior
approved these changes
Aug 9, 2026
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.
Summary
Upstream merge: 0.15.2+adlc4 → 0.16.0+adlc1 (18 commits, includes the 0.16.0 release
6fa8c9aa).What's new from upstream
Feature:
feat(init): scaffold managed .specify/.gitignore(feat(init): scaffold managed .specify/.gitignore github/spec-kit#4000) — manifest-tracked.specify/.gitignoreexcludes machine-local state (feature.json,extensions/*/local-config.yml); routed through shared-infra overwrite/skip/preserve policy.Fixes (14):
fix(init): escape user-supplied values in specify init output(fix(init): escape user-supplied values inspecify initoutput github/spec-kit#3787) —_escape_markup+_shell_quote_argfor thecdline.fix: reapply presets/extensions on init --here --force([bug-fix] Fix init-force-preset-desync: reapply presets/extensions oninit --here --forcegithub/spec-kit#3995).fix: bound response read in integration catalog fetch(fix: bound response read in integration catalog fetch github/spec-kit#3812) —read_response_limited+MAX_JSON_METADATA_BYTES.fix: use missing_ok for temp file cleanup(fix: use missing_ok for temp file cleanup to avoid masking errors github/spec-kit#3803).fix(workflows): handle an unreadable run state in workflow status(fix(workflows): handle an unreadable run state inworkflow statusgithub/spec-kit#3999).fix: skip corrupted run state files in list_runs(fix: skip corrupted run state files in list_runs github/spec-kit#3814/fix: skip corrupted run state files in list_runs github/spec-kit#3817).fix(extensions): start fresh on a non-UTF-8 extension registry(fix(extensions): start fresh on a non-UTF-8 extension registry github/spec-kit#3998).fix(presets): return None for an unreadable layer in resolve_content(fix(presets): return None for an unreadable layer in resolve_content github/spec-kit#3959).fix(archives): wrap the bare EOFError a truncated tar.gz raises(fix(archives): wrap the bare EOFError a truncated tar.gz raises github/spec-kit#3938).fix(skills): apply the line-anchored delimiter scan to hermes and kimi(fix(skills): match closing frontmatter delimiter on its own line github/spec-kit#3739).fix: keep long frontmatter values on a single line(fix: keep long frontmatter values on a single line github/spec-kit#3989) —yaml.dump(width=float("inf")).fix(scripts): stop check-prerequisites/setup-tasks text mode crashing on a legacy code page(fix(scripts): stop check-prerequisites text mode crashing on a legacy stdout code page github/spec-kit#3890/fix(scripts): stop setup-tasks text mode crashing on a legacy stdout code page github/spec-kit#3892).Conflicts resolved (4)
commands/init.py— wrapped user-controlled display values with_escape_markupinside fork'saccent()theming (project name, paths, echoed--integration/--script, agent folder in gitignore hint); adopted_shell_quote_argfor the Next Stepscdline. Thef31b2b45reapply-on---forceblock auto-merged cleanly.tests/integrations/test_integration_catalog.py— adopted upstream's restructured file + boundedFakeResponse. Fixed the mock target: upstream's_patch_urlopenpatchedurllib.request.urlopen, butopen_urlusesopener.open()which never calls the module-levelurlopen→ tests hit real network. Switched the mock toopen_urldirectly, preserving upstream's bounded-readFakeResponsefor theread_response_limitedcontract (applies to both_patch_urlopenand the oversized-response regression test).pyproject.toml— version →0.16.0+adlc1, kept fork name/description.tests/integrations/test_integration_base_toml.py— added.specify/.gitignoreto fork'sstem_pfxinventory.Auto-merged (fork customizations preserved)
shared_infra.py(.gitignoreblock +missing_ok+COMMAND_PREFIX/project_path/theming),extensions/__init__.py(non-UTF-8 registry + catalog-URL override + alias logic),hermes/__init__.py(line-anchored delimiter +resolve_command_alias/COMMAND_PREFIX),agents.py(width=float("inf")+_skip_primary/inject_model_invocation_flag),presets/__init__.py(resolve_contentguard +_cleanup_replaced_commands),workflows/_commands.py(unreadable-run-state guard + theming),update_agent_context.py(symlink-safe recursive plan discovery +missing_ok+ fork team-directives block). Fork modules (_*_fork.py,extensions_fork.py) untouched. Notemplates/changes upstream → no preset porting.Verification
ruff@0.15.0).specify initscaffolds.specify/.gitignore(manifest-tracked);specify extension update(after clearing.specify/extensions/.cache/) finds all fork-bundled extensions up-to-date via fork-repo catalog URL.Agent disclosure
Posted on behalf of @kanfil by opencode (model: glm-5.2, autonomous). All commits in this PR carry
Assisted-by:trailers. The merge commit and this docs commit were authored autonomously by the agent.