1859 Commits

Author SHA1 Message Date
github-actions[bot] f5d0422e1f Update Parallel Autonomous Run Governance preset to v0.2.6 (#4304)
Update parallel-autonomous-run-governance preset submitted by @hindermath:
- presets/catalog.community.json (version, download_url, documentation, provides.templates, tags, description, updated_at)
- docs/community/presets.md community presets table

Closes #4237

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>
2026-08-24 16:15:16 -05:00
github-actions[bot] b1d9d5626c Update SpecAssay bundle to v0.4.12 (#4257)
Update the specassay community bundle catalog entry from v0.3.4 to v0.4.12.
- Updated version: 0.3.4 → 0.4.12
- Updated download_url to v0.4.12 release asset
- Updated updated_at timestamp to 2026-08-21

Validation results:
- Bundle ID 'specassay' matches naming convention
- Version 0.4.12 is a valid semver X.Y.Z and higher than existing 0.3.4
- Repository https://github.com/rdryfoos/specassay confirmed: bundle.yml, README.md, LICENSE present
- bundle.yml fields match submission (id, name, version, role, author, license, speckit_version, provides)
- Release v0.4.12 confirmed with specassay-0.4.12.zip asset attached
- Download URL matches HTTPS GitHub release asset pattern
- Catalog entry validated: all required fields present, verified=false, 5 tags
- Required component catalogs (extensions + presets) documented in README and tested
- All checklists checked; testing details and example usage are complete

Closes #4255
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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-24 14:29:50 -05:00
github-actions[bot] d0eb9c471b Update SpecAssay preset to v0.4.12 (#4256)
Update specassay preset submitted by @rdryfoos:
- presets/catalog.community.json (version, download_url, updated_at)

Closes #4253

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>
2026-08-24 14:04:38 -05:00
github-actions[bot] f83836c7f9 Update Archive Extension to v1.3.0 (#4298)
Update archive extension submitted by @stn1slv:
- extensions/catalog.community.json (version, download_url, requires.speckit_version, updated_at)
- docs/community/extensions.md community extensions table

Closes #4278

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>
2026-08-24 10:55:19 -05:00
github-actions[bot] d9d27048b4 Update Reconcile extension to v1.2.1 (#4297)
Update reconcile extension submitted by @stn1slv:
- extensions/catalog.community.json (version, download_url, requires.speckit_version, provides.hooks, updated_at)
- docs/community/extensions.md community extensions table (no changes needed)

Closes #4279

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>
2026-08-24 09:53:27 -05:00
Manfred Riem 27f50f7e6b chore: release 1.0.1, begin 1.0.2.dev0 development (#4266)
* chore: bump version to 1.0.1

* chore: begin 1.0.2.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-21 14:57:32 -05:00
Manfred Riem 720b31ffce docs: flatten project history navigation (#4265)
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 0f0ad8f8-ea22-44ca-86c7-a485c888ec91
2026-08-21 14:53:56 -05:00
Manfred Riem 99b5c7c533 docs: use Spec Kit branding on documentation site (#4264)
Use the README logo for the DocFX navbar, favicon, and landing hero, and add Upgrade to the balanced Explore the docs grid.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 78ca683f-2995-44eb-a2fa-f7600c18bbd9
2026-08-21 14:48:59 -05:00
Manfred Riem 214e5104b6 docs: add existing project adoption guide (#4263)
Add a safe brownfield onboarding path and connect it to the docs homepage, quick start, navigation, and spec maintenance guidance.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 663b4e07-d79f-4bd1-aa86-8aeea21a2643
2026-08-21 14:21:29 -05:00
Manfred Riem 9a2c2650a5 docs: add project history page (#4262)
* docs: add project history page

Document Spec Kit's stewardship periods, major technical milestones, community catalogs, and evolution from core SDD processes to a composable toolkit.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 46d71f0b-59fc-4bdb-a57e-620210197597

* docs: clarify stewardship wording

Use the possessive form to make clear that the focus belongs to the maintainer team.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 46d71f0b-59fc-4bdb-a57e-620210197597

---------

Copilot-Session: 46d71f0b-59fc-4bdb-a57e-620210197597
2026-08-21 14:16:04 -05:00
Manfred Riem 8b29f37114 docs: mark Spec Kit's first anniversary (#4260)
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 0a6b3d69-9459-4a26-a2f6-4d946e368c81
2026-08-21 13:11:08 -05:00
Manfred Riem 3f77357107 docs: add workflow quickstarts (#4258)
* docs: add workflow quickstarts

Add concise setup and command recipes for SDD, structured bug fixing, and standalone idea assessment.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: af05cfd0-746d-4292-9380-0a785a991cba

* docs: clarify quickstart release tags

Tell readers to replace the placeholder in every standalone quickstart with the latest tagged release.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: af05cfd0-746d-4292-9380-0a785a991cba

---------

Copilot-Session: af05cfd0-746d-4292-9380-0a785a991cba
2026-08-21 12:56:40 -05:00
dependabot[bot] b41058b5e8 chore(deps): bump astral-sh/setup-uv from 9.0.0 to 10.0.1 (#4244)
* chore(deps): bump astral-sh/setup-uv from 9.0.0 to 10.0.1

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 9.0.0 to 10.0.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/c771a70e6277c0a99b617c7a806ffedaca235ff9...20cfd1bf945f4377ade1205e4dbc17946fc9a30d)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(workflows): align setup-uv generated sources

Update the agentic workflow sources, action cache, generated metadata, and regression expectation for setup-uv v10.0.1.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 394a7929-b17c-470f-a3e6-b5863f9c9d40

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Copilot-Session: 394a7929-b17c-470f-a3e6-b5863f9c9d40
2026-08-21 12:44:25 -05:00
github-actions[bot] 27cc286d52 Update SpecAssay Check extension to v0.4.12 (#4254)
Update specassay-check extension submitted by @rdryfoos to:
- extensions/catalog.community.json (version, download_url, description, provides.commands)
- docs/community/extensions.md community extensions table

Closes #4252

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>
2026-08-21 12:43:24 -05:00
Ali jawwad ca5cd0c0dc fix(workflows): require a 'cases' block on switch steps (#4144)
`SwitchStep.validate` requires `expression` and type-checks `cases`, but
never checks that `cases` is PRESENT. It is the only control-flow step whose
branch payload is optional:

  if       -> requires 'then'
  fan-out  -> requires 'items' and 'step'
  fan-in   -> requires a non-empty 'wait_for'
  gate     -> requires 'message'
  switch   -> cases optional

So a switch whose branch table is absent or mistyped — `case:` for `cases:`
is the obvious slip — passes validation with zero errors:

  if   missing then : ["If step 'x' is missing 'then' field."]
  fanout missing all: ["Fan-out step 'y' is missing 'items' field.", ...]
  switch typo case: : []
  switch no cases   : []

and then at run time reports COMPLETED with
`matched_case: "__default__"` — a default it does not even declare — having
dispatched nothing, so the whole run "succeeds". That is the "silent empty
result + COMPLETED" wiring bug the fan-in guard exists to prevent.

An explicitly declared but empty `cases: {}` is still a declaration and
stays valid, pinned by a test.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 11:32:29 -05:00
Ali jawwad 3cc1472098 fix(workflows): strip the resolved value before matching switch cases (#4143)
`SwitchStep.execute` matched with `str(value)` and no strip. The values a
switch dispatches on are overwhelmingly captured command output, and
`ShellStep` stores `proc.stdout` verbatim, so `run: echo approve` resolves
to "approve\n" — which matches no `approve:` case:

  stdout stored : 'approve\n'
  matched_case  : '__default__'      <-- silently wrong
  next steps    : ['fallback']

The switch falls through to `default:` (or dispatches nothing at all) while
still reporting COMPLETED. A workflow author cannot fix it themselves: the
registered filters are default/join/map/contains/from_json — there is no
`trim`.

spec-kit already treats exactly this as a bug wherever else it matches a
resolved string against declared literals — `evaluate_condition` strips for
this same shell-newline reason, and `InitStep._resolve_bool` does
`resolved.strip().lower()`. Switch case keys are such literals, and this was
the only site not stripping.

`expression_value` still reports the raw value, so nothing downstream loses
information, and a genuine mismatch ("approve-later") still falls through.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 11:29:00 -05:00
Marsel Safin 36ff0158b1 fix(bundler): reject non-string manifest list members (#4091)
* fix(bundler): reject non-string manifest list members

Assisted-by: GitHub Copilot (model: gpt-5.6-sol, autonomous)

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

* docs(bundler): clarify string list validation

Assisted-by: GitHub Copilot (model: gpt-5.6-sol, autonomous)

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 11:24:54 -05:00
Marsel Safin f5ab7796dd fix(presets): reject non-mapping catalog mutations (#4094)
Assisted-by: GitHub Copilot (model: gpt-5.6-sol, autonomous)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 11:23:42 -05:00
Nguyen Thanh Dat 2dddaa54f4 fix(workflows): stop offering a condition correction that inverts it (#4230)
* fix(workflows): stop offering a correction that would not repair the condition

`format_condition_correction` wraps whatever it is handed — correct for a
formatter, wrong to advertise as paste-ready for two inputs it cannot repair.
Both reach the never-evaluated branch, and both were being suggested:

    condition: "   "                -> "{{ }}"
    {{ inputs.name == 'abc         -> "{{ inputs.name == 'abc }}"

Measured what pasting each one does, rather than assuming:

    "   "                       is True   ->  "{{ }}"                     is False
    "{{ inputs.name == 'abc"    is True   ->  "{{ inputs.name == 'abc }}" is False

The blank core interpolates to the empty string. The open quote survives
wrapping, so the raw-close fallback evaluates a truncated comparison whose
result is the string "False", which `evaluate_condition` then reads as the
`false` keyword. In both cases the advertised correction silently inverts the
condition — a different defect, not a fix.

Add `format_condition_remediation`, which the three step validators now call in
place of hand-building the sentence. It offers the correction only when wrapping
would actually repair the input, and otherwise names the fault, matching the
call already made for `condition_has_malformed_expression_block`.

`_has_unbalanced_quote` uses the same left-to-right scan as `_find_block_close`
and `_strip_stray_delimiters`, so "inside a string" means the same thing
everywhere in this module.

I had the second case wrong at first and said the wrapped form "stays always
true" — the new test caught it, and the message and docstring now say inverted.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  133 passed (was 116)
- tests/unit + tests/test_workflows.py  1216 passed (was 1199), 22 failed
  before and after — the pre-existing symlink tests needing Windows elevation.

Mutation-checked: removing either gate fails exactly the 9 new parametrised
cases and nothing else.

* fix(workflows): withhold the correction whenever wrapping cannot repair the core

Copilot found two more holes in the previous commit, and both were real.

1. The quote-balance gate was not sufficient. `inputs.name ==` has a non-empty,
   quote-balanced core, so a correction was still advertised:

       inputs.name ==  ->  "{{ inputs.name == }}"     True -> False

   The missing operand resolves to None, the comparison evaluates False, and the
   author again trades an always-true condition for an always-false one.

2. The message named the wrong mechanism. It said the wrapped form goes through
   the raw-close fallback. Measured: `_is_single_expression("{{ inputs.name ==
   'abc }}")` is True, so it takes the typed fast path instead.

Stop enumerating broken shapes. `_wrapping_would_not_repair` now reports the
first reason wrapping cannot yield the intended expression — empty core,
unclosed quote, unbalanced bracket, or an operator missing an operand — and the
advice names it instead of offering a suggestion.

`_has_incomplete_operand` reads `_COMPARISON_OPERATORS`, extracted from
`_evaluate_simple_expression`, so the check cannot drift from what the evaluator
actually splits on. The messages now describe the text itself rather than the
interpolator path it will take: asserting an internal route is what made the
previous two versions wrong.

Tests state the property rather than listing shapes:
`test_every_offered_correction_is_a_complete_expression` asserts that anything
advertised as paste-ready survives both validators, so a new malformed shape is
caught by the invariant rather than by another fixture row.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  182 passed (was 133)
- tests/unit + tests/test_workflows.py  1282 passed (was 1233), 22 failed
  before and after — pre-existing symlink tests needing Windows elevation.

Mutation-checked, each gate against its own cases: dropping the operand gate
fails 12, the bracket gate 3, and removing an operator from
`_COMPARISON_OPERATORS` fails 1. That last one passed vacuously at first because
the test parametrised over the constant it was checking — the same can't-fail
shape this module rejects — so it is hard-coded now.

* fix(workflows): check every operator position and match bracket types

Copilot found two more, and both were right.

1. `_has_incomplete_operand` inspected only the first occurrence of each
   operator, and its end-of-string check covered only trailing boolean keywords:

       inputs.a == inputs.b ==   -> correction still offered, True -> False
       and inputs.ready          -> correction still offered, True -> False

   That is the same defect this PR's parent commit fixed one level up — stopping
   at the first match — reintroduced in the gate meant to prevent it. It now
   splits on every top-level occurrence and requires every operand to be
   non-empty.

   A stripped core also loses the space that delimits a word operator, so
   `inputs.a not in` matched nothing. `_WORD_OPERATORS` is derived from
   `_COMPARISON_OPERATORS` and matched against both ends without it.

2. `_has_unbalanced_bracket` counted depth, so mismatched types cancelled:

       inputs.f(]   -> correction still offered, True -> False

   It tracks opener types on a stack and rejects a non-matching closer.

The docstring Copilot flagged at line 950 is unchanged on purpose: it does not
attribute the inversion to the raw-close fallback, it records that two earlier
versions did and were wrong because `_is_single_expression` accepts the wrapped
form. That thread is marked outdated and refers to the text before `6944920`.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  207 passed (was 182)
- tests/unit + tests/test_workflows.py  1307 passed (was 1282), 22 failed
  before and after — pre-existing symlink tests needing Windows elevation.

Mutation-checked: depth-only brackets fails 9, first-occurrence-only fails 3,
dropping the end-of-core word scan fails 16.

* fix(workflows): reject an unregistered filter and prose before suggesting a wrap

Copilot's remaining point was the strongest one on this PR: `reason is None` only
excluded four structural shapes, and structural shapes cannot establish that
wrapping produces a working expression. Two inputs proved it:

    inputs.items | length      -> offered; wrapped form raises
                                  ValueError("unknown filter 'length'")
    he said "hi"\nthen left    -> offered; wrapped form resolves to None,
                                  True -> False

The first replaces an always-true condition with a crash, the second inverts it.

Two checks close the gap, both reading the evaluator rather than guessing:

- `_unregistered_filter` walks the top-level `|` segments and reports the first
  name missing from `_REGISTERED_FILTERS`, the same tuple `_apply_filter` raises
  on.
- `_reads_as_prose` reports a core that is several bare terms with no operator
  and no filter joining them. Quoted spans and bracketed groups are skipped, so
  `inputs.f('a b')` and `inputs.name == 'two words'` are unaffected, and a `not `
  prefix is allowed.

`he said "hi"\nthen left` was in `OFFERED_CORRECTION_INPUTS` only because that
fixture was built as `TRICKY_CONDITIONS + [...]`. TRICKY_CONDITIONS exists to
exercise the formatter's quoting and deliberately contains prose, so reusing it
asserted the wrong thing. The list is explicit now, and the tricky-quoting entries
that really are expressions are carried over by hand — adding prose to that
fixture can no longer widen what this invariant claims.

`inputs.tags | length > 0` was also mine, and `length` is not a registered
filter; it is `join(',')` now.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  212 passed (was 207)
- tests/unit + tests/test_workflows.py  1312 passed (was 1307), 22 failed
  before and after — pre-existing symlink tests needing Windows elevation.

Mutation-checked: dropping either new gate fails 3 cases and nothing else.

* fix(workflows): ask the evaluator whether the core parses, instead of guessing

Copilot found two more shapes the structural gates did not know about:

    inputs.tags | join   -> offered; `join` is registered, but with no argument
                            `_apply_filter` raises ValueError
    inputs.count+1       -> offered; the evaluator has no arithmetic, reads it as
                            a key named "count+1", and the wrapped form resolves
                            to None, turning a truthy condition false

That is the fifth shape in four rounds, which is the argument against enumerating
shapes at all. Replace the two structural checks with two that read the evaluator:

- `_evaluator_rejects` runs the core through `_evaluate_simple_expression` against
  a probe namespace and returns its own error. Any filter under an unknown name or
  in an unsupported form is now reported by the code that will actually run, so
  `_unregistered_filter` — which restated the filter table — is gone.
- `_is_not_a_bare_path` covers what a probe cannot: a single-term core is resolved
  as a path lookup, so every dotted segment must be an identifier. `count+1` is
  not, and neither is prose, so `_reads_as_prose` is gone too.

The probe namespace resolves roots but not leaves, deliberately. A namespace that
answers every lookup also answers `inputs.count+1`, hiding the shape the probe
exists to expose.

Net effect is two helpers fewer and no restatement of the evaluator's tables.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  230 passed (was 212)
- tests/unit + tests/test_workflows.py  1330 passed (was 1312), 22 failed
  before and after — pre-existing symlink tests needing Windows elevation.

Mutation-checked: dropping either check fails 6 cases and nothing else.

* fix(workflows): stop the probe rejecting valid expressions, and match the path grammar

Copilot found a false positive in the probe, which is worse than the false
negatives the earlier rounds fixed: it withheld a correction from a condition
that was already correct.

    steps.emit.output.stdout | from_json      -> refused
    inputs.tags | join(inputs.separator)      -> refused

Both are valid; the first is exercised in tests/test_workflows.py. The probe
hands `from_json` a dict and it raises, so treating every probe error as a
rejection blamed the author for the placeholder's type. `_evaluator_rejects` now
reports only the two failures `_apply_filter` raises about the expression itself
-- an unknown filter name, and a registered filter used in an unsupported form.
Everything else a probe run raises is about probe values.

`_TERM_SUFFIX` also accepted any bracket contents and repeated indexes, while
`_resolve_dot_path` matches `^([\w-]+)\[(\d+)\]$` -- one numeric index. So
`inputs.tags[foo]` and `inputs.matrix[0][1]` passed as paths, resolved to None,
and were offered a correction that turns a truthy condition false. `_PATH_SEGMENT`
is that grammar now. It also replaces `str.isidentifier`, which was wrong in the
other direction: the resolver allows a hyphen and a leading digit in a key name.

Lint: this branch had added 3 ruff errors (2x SIM102, PIE810/UP037 on new code)
and left a top-level class without its blank lines. `ruff check` on this file is
back to the 5 pre-existing errors on `main`, all in code this PR does not touch.

On the E305 comment specifically: `ruff rule E305` reports "Selection `E305` has
no effect because preview is not enabled", and `ruff check --select E305` on this
file passes, so the repository's CI does not report it. The blank lines were still
wrong and are fixed.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  236 passed (was 230)
- tests/unit + tests/test_workflows.py  1336 passed (was 1330), 22 failed
  before and after -- pre-existing symlink tests needing Windows elevation.

Mutation-checked: treating every probe error as a rejection fails 2, loosening
the path grammar fails 2.

* fix(workflows): validate operands recursively, and keep probe-value errors out

Copilot found three more, and the first explains why this took so many rounds:
every gate so far only inspected the shape it was written for.

    inputs.a === inputs.b   -> offered; splits cleanly on `==`, and the evaluator
                               reads `= inputs.b` as a path, resolving to None
    bogus == 'x'            -> offered; unknown root, same result
    inputs.payload | from_json()  -> offered; raises at run time

`_unresolvable_term` replaces `_is_not_a_bare_path` and walks operands the way
`_evaluate_simple_expression` does -- filters, `or`/`and`/`not`, comparisons --
down to the leaves. A leaf must be a literal or a dotted path rooted in
`_NAMESPACE_ROOTS`, the roots `_build_namespace` actually supplies. Both shapes
above fall out of that without either being named.

`_evaluator_rejects` now keeps only the errors `_apply_filter` raises about the
filter *expression*. Those quote the segment back as `got '| ...'`; its value
errors name the type they received, which under a probe is the placeholder. The
previous prefix list missed `from_json()` (a wiring error) and, when widened by
filter name, wrongly rejected `steps.emit.output.stdout | from_json` (a value
error) -- the regression the round before had just fixed.

One case fell out that no review raised: `_find_top_level` matches " and " with
literal spaces, so a newline before the keyword is not an operator.
`inputs.x == 1\nand inputs.name == 'abc'` evaluates False wrapped, where the same
expression with a space evaluates True. It was in the offered fixture; it is a
refusal case now.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  253 passed (was 236)
- tests/unit + tests/test_workflows.py  1353 passed (was 1336), 22 failed
  before and after -- pre-existing symlink tests needing Windows elevation.
- ruff check on this file is back to the 5 errors already on main.

Mutation-checked: dropping the recursion fails 15, dropping the namespace-root
check fails 5, treating every probe error as a rejection fails 2.

* fix(workflows): mirror the evaluator's literal and root tests exactly

Three more from Copilot, all cases where my check approximated the evaluator
instead of matching it:

    1e3        -> offered; no "." so the evaluator calls int(), which fails, and
                  it falls through to a path lookup. float() alone accepted it.
    'a' 'b'    -> offered; the evaluator requires the opening quote's match to be
                  the final character, which first/last-character equality is not.
    inputs[0]  -> offered; `_build_namespace` hands back mappings, so an indexed
                  root resolves to None however the index is written.

All three are truthy before wrapping and False after, which is the inversion this
change exists to prevent.

`_looks_numeric` and `_is_literal` now use the evaluator's own tests rather than a
looser stand-in, and the root segment is matched without stripping an index off it
first.

Not fixed, and worth being explicit about: `inputs.tags | join(5)` is still
offered. `join` always raises for a non-string separator, but that is a *type*
rule, and `_evaluator_rejects` deliberately ignores value errors because under a
probe they usually describe the placeholder rather than the author's text. The two
cannot be told apart from the message alone -- `join: expected a string separator,
got int` and `join: ..., got NoneType` differ only in a type name the probe may
have supplied. Catching it means encoding each filter's argument types in the
validator, which is the reimplementation this PR has been backing away from.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  267 passed (was 253)
- tests/unit + tests/test_workflows.py  1367 passed (was 1353), 22 failed
  before and after -- pre-existing symlink tests needing Windows elevation.
- ruff check on this file is back to the 5 errors already on main.

Mutation-checked: restoring the bare float() fails 2, restoring the
first/last-character quote test fails 3.

* fix(workflows): mirror list literals and filter arguments in the operand check

Two shapes the leaf check did not mirror, each wrong in the opposite
direction.

A list literal is a term the evaluator understands -- it recurses into
the elements rather than resolving the brackets as a name. Resolving
them as a path reported `"['x', 'y']" is not a name the evaluator can
resolve` and withheld the correction from `inputs.tag in ['x', 'y']`,
a condition wrapping repairs completely.

A filter argument is an ordinary operand to `_apply_filter`, which
evaluates it with `_evaluate_simple_expression` like any other.
Skipping it offered `inputs.tags | join(bogus)` as paste-ready:
`bogus` is no namespace root, arrives as None, and the wrapped form
raises `join: expected a string separator, got NoneType`. Parsed with
the same pattern `_apply_filter` uses, so a form this does not
recognize is left to the evaluator probe rather than guessed at.

Every case is asserted against what the evaluator does with the
wrapped form, not against a restatement of the check.

* fix(workflows): let an indexed `item` root keep the correction

`item` is the only namespace root that is not always a mapping.
`StepContext.item` is `Any` and a fan-out assigns the item value
itself, so when that value is a list `_resolve_dot_path` indexes it and
`item[0] == 'x'` resolves. Rejecting every indexed root withheld the
correction from a condition that evaluates.

The other roots come back from `_build_namespace` as mappings, so the
index branch finds no list and returns None however the index is
written. The strip is therefore for `item` alone, and the paired test
pins that it does not widen into "any indexed root".

This narrows the root check added earlier in this branch, which was
written as though every root were a mapping.
2026-08-21 11:21:15 -05:00
Quratulain-bilal d3f9212701 fix: use chunked read for integration and preset manifest hash (#3843)
* fix: use chunked read for integration and preset manifest hash

Replace unbounded fh.read() with chunked iteration to prevent excessive
memory allocation on large or corrupted manifest files. Applies to both
integrations/catalog.py and presets/__init__.py get_hash() methods.

* test: verify full hash value in get_hash() tests to cover chunked path

The existing tests only checked the sha256: prefix, which would pass
even if the chunked hash was broken. Now verify the complete hash
matches hashlib.sha256(content).hexdigest() to exercise the multi-chunk
path introduced by the chunked read change.
2026-08-21 10:52:10 -05:00
Manfred Riem 8c31da95be docs: update landing page stats for 1.0.0 (#4251)
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: edc3d861-f065-4747-8ed4-30e3e9f0ea99
2026-08-21 10:38:57 -05:00
github-actions[bot] 95efce42c1 Add Azure Cosmos DB extension to community catalog (#4247)
Add cosmosdb extension submitted by @TheovanKraay to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes #4238

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>
2026-08-21 08:23:33 -05:00
dependabot[bot] 47ca8e148d chore(deps): bump actions/checkout from 6.0.3 to 7.0.1 (#4243)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.3...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 08:06:08 -05:00
dependabot[bot] 5df8c4c6ef chore(deps): bump actions/setup-node from 6.4.0 to 7.0.0 (#4242)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6.4.0...820762786026740c76f36085b0efc47a31fe5020)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 07:29:31 -05:00
dependabot[bot] 28545894a0 chore(deps): bump the codeql-action group with 2 updates (#4241)
Bumps the codeql-action group with 2 updates: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-action
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-action
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 07:25:03 -05:00
Manfred Riem 5cf60225e9 chore: release 1.0.0, begin 1.0.1.dev0 development (#4246)
* chore: bump version to 1.0.0

* chore: begin 1.0.1.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-21 06:42:04 -05:00
github-actions[bot] 17e773595e [extension] Update Security Review extension to v2.0.0 (#4223)
* Update Security Review extension to v2.0.0

Update security-review extension submitted by @DyanGalih:
- extensions/catalog.community.json (version, download_url, repository, author, tags, tools, updated_at)
- docs/community/extensions.md community extensions table

Closes #4217

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

* Preserve security review tool versions

Carry the submitted minimum versions for the required git tool and optional Node.js CLI dependency into the community catalog entry.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

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

Copilot-Session: 312140f1-9c82-4e1e-a0ca-9a687ff71e27

---------

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: 312140f1-9c82-4e1e-a0ca-9a687ff71e27
2026-08-20 12:20:57 -05:00
Noor ul ain 58a7edaf5a fix(presets): reject duplicate provides.templates name+type entries (#4191)
PresetResolver._manifest_declared_template returns the FIRST
'provides.templates' entry matching a given (name, type) pair:

    for tmpl in manifest.templates:
        if tmpl.get("name") == template_name and tmpl.get("type") == template_type:
            ...
            return tmpl, ...

So a preset.yml declaring two templates with the same (name, type) --
e.g. two "command"/"specify" entries pointing at different files -- had
its second entry silently unreachable, while PresetManifest.templates
still counted and exposed both. PresetManifest._validate never checked
for this.

Reject the duplicate at manifest-validation time instead, matching the
sibling fix already applied to ExtensionManifest's provides.templates/
provides.scripts (commit 11e3176, PR #4016): "The resolver returns the
first entry matching a declared name, so a later duplicate ... was
silently unreachable while still counted". Presets use a (name, type)
composite key rather than extensions' bare name, since the same name can
legitimately recur across different template types (e.g. a "specify"
template and a "specify" command); the fix only rejects a duplicate
within the exact same (name, type) pair.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 12:18:20 -05:00
Noor ul ain 77528dc48b fix(bundler): decode a downloaded (non-zip) bundle manifest as UTF-8 (#4190)
_download_remote_manifest's non-zip branch fed the downloaded bytes
straight to `yaml.safe_load(io.BytesIO(raw))`. PyYAML's Reader
auto-detects a UTF-16 BOM on a byte stream, so a well-formed UTF-16
bundle.yml (a realistic PowerShell `Out-File`/`>` output) was silently
*accepted* here, while `yamlio.load_yaml` decodes local sources strictly
as UTF-8 and rejects the identical content with "Could not read ...".

  BEFORE: a UTF-16 manifest downloaded via `bundle info`/`install`
  parses successfully -- exit code 0, no warning.
  AFTER: rejected with "... could not be read: ..." -- exit code 1,
  matching local directory and .zip sources.

This is the same divergence, in the sibling branch of the same function,
that was just fixed for the .zip case in commit 56aec8a (PR #3958):
"feeding PyYAML the byte stream let its Reader honour a UTF-16 BOM and
accept a manifest yamlio.load_yaml rejects, so zip and directory sources
diverged." That fix covered `_local_manifest_source`'s `.zip` branch
(which this same function calls for zip artifacts); the direct
raw-YAML-download branch a few lines below it had the identical bug.

Also drops the now-unused `import io` from this function.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 12:10:24 -05:00
github-actions[bot] 2f96c91f34 Update Intake Sequencing Governance preset to v0.2.3 (#4235)
Update intake-sequencing-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, templates count, tags, updated_at)
- docs/community/presets.md community presets table

Closes #4214

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>
2026-08-20 12:03:39 -05:00
github-actions[bot] 1e28d416a6 Update MAQA — Multi-Agent & Quality Assurance extension to v0.1.6 (#4234)
Update maqa extension submitted by @GenieRobot:
- extensions/catalog.community.json (version, download_url, requires/tools, updated_at)
- docs/community/extensions.md community extensions table (no changes needed)

Closes #4233

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>
2026-08-20 11:54:35 -05:00
github-actions[bot] fa19e1c68b [bug-fix] Fix qodercli-skills-migration: migrate QodercliIntegration to SkillsIntegration (#4205)
* Fix qodercli-skills-migration: migrate QodercliIntegration to SkillsIntegration

Apply the remediation from the bug assessment on issue #4199.
Qoder IDE 1.24+ dropped .qoder/commands/ scanning in favour of the
skills layout (.qoder/skills/{skill-name}/SKILL.md). Migrated
QodercliIntegration from MarkdownIntegration to SkillsIntegration,
updating config[commands_subdir] to 'skills' and
registrar_config[dir] to '.qoder/skills' with extension '/SKILL.md'.
Updated tests to use SkillsIntegrationTests base mixin.

Refs #4199

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

* fix(qodercli): resolve failing skills-flag test and slash invocation

Builds on the qodercli->SkillsIntegration migration (PR #4205). Qoder IDE
1.24+ is always skills-based, so it should not expose a --skills toggle.
Override the inherited SkillsIntegrationTests.test_options_include_skills_flag
to skip (mirroring Grok/Zed/Droid) and add a test asserting no --skills
option, plus a requires_cli/name/multi_install_safe check.

Also add "qodercli" to ALWAYS_SLASH_AGENTS so hooks and next-steps render
the hyphenated /speckit-<name> invocation instead of the legacy dotted
/speckit.<name> form.

Fixes the single failing test reported for #4199.

Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 43394151-ce2a-432d-9cc5-88f587d1b570

* fix(qodercli): migrate legacy extension commands

Retire old flat Qoder extension commands only after their replacement skills are successfully written. Cover old-layout upgrades and both slash invocation states, and update the integration reference path.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, 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>
Copilot-Session: 43394151-ce2a-432d-9cc5-88f587d1b570
2026-08-20 11:03:23 -05:00
github-actions[bot] abfc66b670 [preset] Add Inventory Alignment preset to community catalog (#4229)
* Add Inventory Alignment preset to community catalog

Add inventory-alignment preset submitted by @Yash-Chindam to:
- presets/catalog.community.json (alphabetical order)
- docs/community/presets.md community presets table

Closes #4227

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(presets): complete inventory alignment metadata

Restore the required speckit-inventory dependency and pin the submitted release archive SHA-256.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, 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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-20 09:10:18 -05:00
github-actions[bot] 2d217aef81 [extension] Add Spec Inventory extension to community catalog (#4228)
* Add Spec Inventory extension to community catalog

Add speckit-inventory extension submitted by @Yash-Chindam to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes #4226

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-20 08:55:44 -05:00
github-actions[bot] a7eb6064a1 [extension] Update Architecture Guard extension to v2.3.6 (#4224)
* Update Architecture Guard extension to v2.3.6

Update architecture-guard extension submitted by @DyanGalih to:
- extensions/catalog.community.json (version, download_url, repository, description, etc.)
- docs/community/extensions.md community extensions table

Closes #4219

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

* Revert unrelated community catalog formatting

Keep the Architecture Guard v2.3.6 update while restoring all unrelated catalog entries to their existing formatting.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, 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>
2026-08-20 08:39:44 -05:00
github-actions[bot] 5c171f711b Update SpecKit Companion extension to v0.20.2 (#4225)
Update companion extension submitted by @alfredoperez:
- extensions/catalog.community.json (version 0.11.0 → 0.20.2, download_url, description, provides.commands 13 → 18, tags, category visibility → process, updated_at)
- docs/community/extensions.md community extensions table (description, category)

Closes #4221

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>
2026-08-20 08:34:21 -05:00
Nguyen Thanh Dat 145e5e6889 fix(workflows): reject a condition that has no {{ }} block (#4182)
* fix(workflows): reject a condition that has no {{ }} block

`evaluate_condition` resolves its argument through `evaluate_expression`,
which only substitutes `{{ ... }}` blocks. A string with no such block
comes back unchanged and — unless it reads `true`/`false` — is then
coerced by `bool()`. So a condition authored without the braces is never
evaluated at all:

    evaluate_condition("inputs.count > 100", ctx)      -> True
    evaluate_condition("{{ inputs.count > 100 }}", ctx) -> False

with `inputs.count == 5` in both cases. An `if` step always takes `then`,
and a `while`/`do-while` step always runs to `max_iterations` — ten agent
invocations for a loop the author expected to stop.

This is the same silent-truthiness authoring mistake the three step
validators already reject for a list/dict/number condition, and it is
easier to make: GitHub Actions accepts a bare expression in `if:`, so the
brace-less form is a habit to bring here.

Adds `condition_is_never_evaluated()` and wires it into the `if`,
`while` and `do-while` validators, so the mistake surfaces at validation
with the corrected form spelled out. Boolean literals, real bools, empty
strings and any string containing `{{` stay valid — runtime behaviour is
unchanged.

* fix(workflows): flag an unterminated {{ and quote the correction safely

Two gaps in the condition validator, both raised in review.

An opening `{{` with no `}}` after it is never substituted either:
_interpolate_expressions takes its `raw_close == -1` branch and appends
the tail verbatim. So `condition: "{{ inputs.count > 100"` -- and the
reversed `"}} inputs.count > 100 {{"`, whose only `{{` is last -- come
back unchanged and are coerced to true exactly like a brace-less string.
The helper now looks for a complete block rather than an opening one.

The suggested correction was interpolated into a double-quoted scalar,
so a condition containing a double quote produced YAML that does not
parse: `condition: "{{ inputs.name == "zzz" }}"` raises a ParserError.
format_condition_correction() now picks the quoting from the content and
drops a stray delimiter instead of nesting a second one, so the message
stays paste-ready. All three validators share it.

Tests: 30 more cases -- the incomplete forms, and a YAML round trip over
conditions holding single quotes, double quotes, both, and backslashes,
asserting each correction loads back exactly and is not re-flagged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(workflows): share the evaluator's quote-aware scan, and quote with json.dumps

Both follow-up review points were right.

The completeness check used a plain `find("}}")`, but the substituter closes a
block with a quote-aware scan. So `condition: "{{ inputs.x == '}}'"` looked
complete to the validator while `_interpolate_expressions` found no close, fell
to its raw-close branch, evaluated a truncated body and left residual text
(`False'`) -- a non-empty string, hence true. Rather than restate the quote
rules a third time, the scan moves out of `_interpolate_expressions` into
`_find_block_close`, which the validator now calls: the check and the
substitution it predicts can no longer disagree. A `}}` that is genuinely
inside a string argument still does not close early, so
`{{ inputs.text | default('}}') }}` and `{{ inputs.x == '}}' }}` stay accepted.

The correction's quoting enumerated the characters it escaped, and the
enumeration was short: a condition loaded from a YAML literal block can carry a
newline, which a double-quoted scalar folds, so the corrected form did not
round-trip. `json.dumps` decides it instead -- every JSON string is a valid
YAML double-quoted scalar and it escapes quotes, backslashes, newlines and the
other control characters. `ensure_ascii=False` keeps a non-ASCII operand
readable rather than expanding it into numeric escapes.

Tests: 70 -> 83. The quoted-delimiter condition joins the incomplete-block set,
and the round-trip set gains multiline, newline-with-quote, tab, carriage
return and non-ASCII operands. All four new cases fail on the previous commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(workflows): flag a whitespace condition, and stop the correction nesting a block

Two review findings, both reproduced against the code before changing it.

**1. Non-empty whitespace was excluded, and it should not have been.**

The docstring claimed a whitespace condition "coerces to False, which is a
definite answer". That is true only of the empty string. Measured:

    evaluate_condition("")      -> False
    evaluate_condition("   ")   -> True
    evaluate_condition("\t\n ") -> True

`evaluate_condition` strips only while testing the true/false keywords, then
falls through to `bool()` on the raw string -- and
`test_condition_whitespace_only_string_stays_truthy` pins that on purpose. So
`condition: "   "` is exactly the silent always-true this helper exists to
catch, and it was sailing through. Fixed at validation time rather than in the
evaluator, because that runtime behaviour is deliberate.

The empty string stays excluded: it really does coerce to False.

**2. The correction only removed edge delimiters, so it could nest one.**

    "prefix {{ inputs.ready"  ->  "{{ prefix {{ inputs.ready }}"

The suggestion carried an unclosed inner block, and because its *outer* block
was complete, `condition_is_never_evaluated` waved the corrected form straight
back through. Same for a trailing `}}`.

`_strip_stray_delimiters` now removes every delimiter, and is quote-aware for
the reason the rest of this module is: `inputs.x == '}}'` holds a delimiter as
data, and a blanket `re.sub` would eat it and change what the condition
compares. `_find_top_level` could not be reused -- it counts `{`/`}` as bracket
depth, so it never reports a `{{` as a token at all.

    "prefix {{ inputs.ready"   -> "{{ prefix inputs.ready }}"
    "inputs.ready }} suffix"   -> "{{ inputs.ready suffix }}"
    "{{ inputs.x == '}}'"      -> "{{ inputs.x == '}}' }}"      (data kept)
    '{{ inputs.name == "a  b"' -> '{{ inputs.name == "a  b" }}' (spacing kept)

Whitespace collapses only where a delimiter was removed; inside a quoted
operand it is untouched.

Tests: the two fixtures that asserted whitespace was valid are corrected, and
five cases added for interior delimiters, quoted delimiters and quoted spacing.
87 pass in tests/unit/test_condition_expression_block.py.

tests/test_workflows.py is 20 failed / 903 passed both with and without this
change -- all twenty are symlink tests that need Windows Developer Mode, and
the counts are identical with the diff stashed.

* fix(workflows): separate a malformed block from one that is never evaluated

Third review finding, and like the first two it reproduces. `condition_is_never_evaluated`
returned True for any `{{` the quote-aware scan could not close -- but
`_interpolate_expressions` does not treat those alike. Its own comment spells out
two sub-cases, and only one is "never evaluated":

  * no raw `}}` in the tail -> the text is emitted verbatim, so bool() makes it
    true. Genuinely uninterpolated.
  * a raw `}}` further along -> that is used as the close and the truncated body
    *is* evaluated.

Measured:

    {{ inputs.count > 100                     -> True            (never evaluated)
    }} inputs.count > 100 {{                  -> True            (never evaluated)
    {{ inputs.x == '}}'                       -> True            (raw-close path)
    {{ inputs.missing | default('oops }}      -> raises ValueError

That last one made the old message wrong on both halves: it is evaluated, and it
does not end up true -- it ends the run in `_apply_filter`.

Adds `condition_has_malformed_expression_block` and gives it its own branch in the
three validators, because the two faults need opposite advice: one says "you forgot
the braces", the other says "your delimiters or quotes do not balance". The two
predicates are mutually exclusive, pinned by a test over every fixture.

The malformed branch deliberately offers **no** paste-ready correction. The fault is
unbalanced quoting, so the quote-aware stripper cannot tell operand from delimiter --
for `{{ inputs.missing | default('oops }}` it emits `"{{ inputs.missing | default('oops }} }}"`,
which is not a fix. This is the same "avoid offering an automatic correction for
malformed-block cases" the reviewer raised earlier; it applies exactly here.

Also renders a blank correction as `"{{ }}"` rather than the double-spaced `"{{  }}"`
that concatenation produced for a whitespace-only condition.

106 pass in tests/unit/test_condition_expression_block.py. Across
tests/test_workflows.py + tests/unit the run is 22 failed / 1189 passed, and 22
failed / 1170 passed with this diff stashed -- identical failures, all Windows
symlink cases, none touching conditions or expressions.

* fix(workflows): scan every expression block, not just the first

Both condition validators stopped at the first `{{`. A condition whose first
block closes was accepted regardless of what followed, so a later unterminated
block escaped validation entirely — the case Copilot raised:

    {{ true }} and {{ inputs.ready    -> both validators returned False

Interpolation leaves `and {{ inputs.ready` in the result and bool() makes the
condition always true, which is exactly the silent-branching defect these
validators exist to catch. The same hole applied to the malformed class:

    {{ inputs.name }} {{ inputs.missing | default('oops }}   -> raises at run time

Add `_first_unclosable_block`, which walks blocks the way
`_interpolate_expressions` does — continuing past each block that closes — and
reports how the first unclosable one will fail: `evaluated` when a raw `}}`
follows (the fallback truncates and evaluates), `verbatim` when none does.
Both validators now read from it, so they cannot disagree with the substitution
they predict.

Two wording fixes fall out of scanning further:

- The never-evaluated message said the condition "has no complete '{{ }}'
  block". With an earlier complete block that is false, so it now says the
  condition "is not a single complete '{{ }}' block".
- `condition_has_malformed_expression_block`'s docstring said the truncated body
  raises ValueError. It does for `default('oops`, but `{{ inputs.x == '}}'`
  evaluates to the residual `"False'"` instead. Measured both; the docstring now
  says either can happen and the error message never claimed otherwise.

Verified on Python 3.11:
- tests/unit/test_condition_expression_block.py  116 passed (was 106)
- tests/unit + tests/test_workflows.py  1199 passed (was 1189), 22 failed
  before and after — all pre-existing symlink tests that need Windows elevation.

Mutation-checked: restoring the stop-after-first-block behaviour fails exactly
the 10 new parametrised cases and nothing else.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:24:15 -05:00
Manfred Riem fe9f4587a2 fix: raise feature assessment credit budget (#4222)
Set an explicit 20K daily AI credits guardrail for the multi-stage feature assessment workflow so normal aggregate usage does not block subsequent assessments.\n\nRefs #4216\n\nAssisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 1711a974-353d-4096-96e9-c7d6d2105355
2026-08-20 08:05:52 -05:00
github-actions[bot] ad057b586f [extension] Add AgentDocx extension to community catalog (#4184)
* Add AgentDocx extension to community catalog

Add agentdocx-speckit extension submitted by @abir-ommezzine to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes #4171

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

* Move agentdocx-speckit catalog entry into alphabetical position

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

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

* Align AgentDocx category with published manifest (integration)

Assisted-by: GitHub Copilot (model: GPT-5.2-Codex, 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>
2026-08-20 07:25:17 -05:00
Noor ul ain ead30d9cfb fix(integrations): report a falsy non-mapping integration descriptor as a shape error (#4187)
* fix(integrations): report a falsy non-mapping integration descriptor as a shape error

`IntegrationDescriptor._load` did `yaml.safe_load(fh) or {}`. `_validate`
opens with an `isinstance(self.data, dict)` check, so a truthy non-mapping
(`- a`, `hello`) is reported correctly -- but `or {}` replaced the falsy
non-mappings with an empty mapping first, so those descriptors were
reported as "Missing required field: schema_version" instead of the wrong
shape:

  'false' -> Descriptor root must be a YAML mapping, got bool
  '0'     -> Descriptor root must be a YAML mapping, got int
  "''"    -> Descriptor root must be a YAML mapping, got str
  '[]'    -> Descriptor root must be a YAML mapping, got list

`safe_load` also returns None for an explicit null scalar (`null`, `~`,
`NULL`) as well as for an empty document, so those three hit the same
masking. Use `yaml.compose`, which yields no node only for a genuinely
empty document, to tell the two apart -- only an empty document still
normalizes to `{}` and reports its missing fields.

Same bug class just fixed in the sibling overlay-manifest loader
(upstream commit 39c36c4, PR #3884); this is the unfixed twin in the
integration catalog's descriptor loader.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-19 16:56:31 -05:00
github-actions[bot] e3e6a3c87b Update Autonomous Run Governance preset to v0.4.1 (#4203)
Update autonomous-run-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, description, provides, tags, updated_at)
- docs/community/presets.md community presets table

Closes #4153

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>
2026-08-19 16:26:48 -05:00
WOLIKIMCHENG 7e48738e26 fix(workflows): validate dispatch defaults (#4181)
* fix(workflows): validate dispatch defaults

* fix(workflows): validate dispatch defaults on resume

---------

Co-authored-by: root <kinsonnee@gmail.com>
2026-08-19 16:23:50 -05:00
github-actions[bot] 14bbfd52e5 Update Atlas extension display name in community catalog (#4202)
Update atlas extension submitted by @ashbrener to:
- extensions/catalog.community.json (name: spec-kit-atlas → Atlas)
- docs/community/extensions.md community extensions table

Closes #4196

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>
2026-08-19 16:21:11 -05:00
github-actions[bot] b7a6a6ec45 Add Closed Vocabulary Check preset to community catalog (#4201)
Add closed-vocabulary preset submitted by @yunusdim to:
- presets/catalog.community.json (alphabetical order)
- docs/community/presets.md community presets table

Closes #4192

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>
2026-08-19 16:20:15 -05:00
Noor ul ain 92e8ab56b4 fix(utils): narrow bare except Exception in merge_json_files (#4189)
merge_json_files's read of the existing JSON file caught bare
`Exception` around `json5.load`, so a real bug there (e.g. a
`TypeError`/`AttributeError`) was silently treated the same as a normal
parse failure -- `None` returned, existing settings preserved untouched,
nothing surfaced unless `verbose`. Only `OSError` (inaccessible file) and
`ValueError` (malformed JSON5 -- json5's decode error is a `ValueError`
subclass) are expected outcomes here; anything else should propagate.

Same bug, same fix shape, as the caller `handle_vscode_settings`, whose
own bare `except Exception` was just narrowed to `(OSError, ValueError,
KeyError)` in commit 16f4577 (PR #3844) with the same rationale
("let programming errors like TypeError or AttributeError propagate").
That PR's own regression test monkeypatched `merge_json_files` to prove
the caller's narrowing works; this fixes and tests the callee itself,
which still had the original bare-except bug.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:18:40 -05:00
Manfred Riem 7eee05d0ed chore: release 0.16.5, begin 0.16.6.dev0 development (#4206)
* chore: bump version to 0.16.5

* chore: begin 0.16.6.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-19 09:22:40 -05:00
Noor ul ain 6b7f4aa844 fix(powershell): stop Out-Null swallowing setup-tasks AVAILABLE_DOCS lines (#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 #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>
2026-08-19 07:54:06 -05:00
Manfred Riem f1673cbfb2 fix: provision Spec Kit CLI and assess extension in feature-assess host setup steps (#4195)
The prior fix (#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
2026-08-18 14:24:17 -05:00
Manfred Riem b9899643e9 fix: provision uv and Python for feature-assess workflow (#4193)
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>
2026-08-18 13:41:11 -05:00
Manfred Riem fc6e5f0bfb feat: add feature-assess agentic workflow that installs and runs Spec Kit (#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 #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 #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 #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
2026-08-18 13:24:11 -05:00