Skip to content

feat(ext.generate): unify features into typed variables (#782)#783

Closed
derks wants to merge 19 commits into
mainfrom
feat/generate-features-redesign-pr
Closed

feat(ext.generate): unify features into typed variables (#782)#783
derks wants to merge 19 commits into
mainfrom
feat/generate-features-redesign-pr

Conversation

@derks

@derks derks commented May 30, 2026

Copy link
Copy Markdown
Member

Summary

Resolves #782 (Tom
Freudenberg's feedback on closed PR #780). The generate extension's optional
features mechanism is merged into the template variables mechanism so
that optional/conditional template inputs behave consistently with ordinary
variables — prompted in declaration order, exposed at the top level of the
template context, and with a sensible default-but-overridable prompt.

The entire features: mechanism (boolean features and the prompt_mode: select
variant) was unreleased — it first ships in 3.0.16 — so it is reshaped here
with no backward-compat shim. All changes to the released variables:
schema are strictly additive: a variable that declares none of the new keys
behaves byte-identically to today.

What an author writes now

A single variables: list where each entry carries a type::

variables:
  - name: project_name           # type: string (default) — unchanged
    prompt: "Project Name"
    default: "myproject"

  - name: docker                  # type: boolean -> real Python bool
    type: boolean
    default: true
    extend:
      - when: true                # conditional effects, composing + nesting
        variables:
          - name: python_version
            prompt: "Python Version"
            default: "3.13"
      - when: false
        ignore: ['.*Dockerfile.*']

  - name: docker_compose
    type: boolean
    requires: [docker]            # gated on another variable

  - name: web_framework           # type: choice -> numbered picker, str value
    type: choice
    default: none
    options: [none, flask, fastapi]
    extend:
      - when: [flask, fastapi]
        variables: [{name: framework_version, prompt: false, default: "3.0"}]

Because every variable now lands at the top level of the render context
(data[name]), {% if docker %} and {% if web_framework == "flask" %} work
directly — the original Jinja bug in #782 (features were namespaced under
data['features']).

Changes (by wave)

Wave 1 — unified typed resolver. Collapse _process_features and the old
variables loop into one ordered, single-pass resolve_and_emit walker.
Add type: string|boolean|choice (default string). Every variable emits at
top-level data[name]; the data['features'] namespace is removed. Boolean
string-form prompt renders <prompt> [(Y)es/(N)o] [default]:, parsing
y/yesTrue, n/noFalse, empty→default. D-17 guard: case:/
validate: on a boolean/choice variable is a fail-fast ValueError.

Wave 2 — choice + polymorphic boolean prompt. type: choice numbered
picker (scalar list or {value, prompt} option objects), emitting the chosen
string. Polymorphic boolean prompt: — a plain string (framework owns the
hint), false (silent, uses default), or an object
{text, accept, reject} where the author owns the text and accept:/reject:
are case-insensitive token lists (non-matching input aborts like a failed
validate:). A YAML 1.1 bool-coercion guard rejects unquoted
yes/no/on/off/true/false tokens in accept:/reject: lists with a
clear "quote it" error.

Wave 3 — conditional effects + dependencies; bridge removed. extend.when
composes three match forms — scalar equality, in-list membership, and (string
variables only) regex via re.match — and multiple matching rules accumulate
their variables/exclude/ignore. extend.variables nest depth-first,
prompted in place only when their parent rule fires. Top-level requires:
gates a variable on others (bare-name list = truthy; {name: value} map =
equality/in-list/regex), AND-ed and resolved order-independently; a gated-out
variable takes its typed default so templates never error. The legacy
features: reader (~200 lines) is deleted — features:/prompt_mode:/
enabled:/disabled: no longer exist in the engine.

Wave 4 — demo migration. demo/generate-features/ rewritten to the unified
schema and its README documents type:/extend:/requires:, the polymorphic
boolean prompt, and the {{ bool }} capitalized-repr gotcha. All five CLI
generate templates verified to still generate unchanged.

Wave 5 — coverage audit + changelog. Removed redundant unlabeled
pragma: nocover markers that were masking already-tested mapping logic;
every surviving pragma carries a locked-vocabulary label. CHANGELOG entries
for all four of #782's points.

Post-verification fix. Live interactive testing surfaced a defect the
automated suite missed: a boolean variable with no explicit prompt: rendered
its label as the literal None. Fixed — the label now defaults to
Enable <name> (e.g. Enable docker [(Y)es/(N)o] [Y]:); explicit prompt:
still overrides. Covered by a new test.

Acceptance status

# Criterion Status
SC1 features:/prompt_mode:/enabled:/disabled: removed from the engine PASS
SC2 type: string|boolean|choice; boolean→bool, choice→str, all top-level ({% if x %} works) PASS
SC3 Former features prompt in declaration order (single pass) PASS
SC4 Default [(Y)es/(N)o] [default]: boolean prompt; polymorphic prompt: (string/false/object) PASS
SC5 extend.when value/list/regex (composing) + depth-first nesting + top-level requires: PASS
SC6 Typeless variable byte-identical; demo + CLI templates migrated/verified; 100% coverage, ruff, mypy green PASS

Files touched

  • cement/ext/ext_generate.py — unified typed resolver (+329 / −216).
  • tests/ext/test_ext_generate.py + ~30 tests/data/templates/generate/test*/
    fixtures — migrated to the new schema; new tests for choice, accept/reject,
    YAML guard, extend.when list/regex, nesting, requires:, and the
    default-label fix.
  • demo/generate-features/ — template, README, and webapp files migrated.
  • CHANGELOG.md[ext.generate] entries under the 3.0.16 development
    section.

Test plan

  • make test — 370 passed, 100.00% coverage across cement/
    (requires Redis on 6379 + memcached on 11211 for the ext-cache tests).
  • make comply — ruff clean, mypy clean (51 source files).
  • Released variables: oracle tests (test15/22/27/29) pass
    unchanged.
  • All 5 CLI generate templates produce output under --defaults.
  • Demo generated interactively (piped stdin): prompts render in declaration
    order with correct Enable <name> [(Y)es/(N)o] labels, depth-first nesting,
    and requires: gating.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Replaced the legacy features: workflow with a unified typed variables system (type: string, type: boolean, type: choice) with top-level rendering for Jinja2.
    • Added requires: gating and extend: rules with when: conditions (including list membership and regex matching) to control what gets extended vs defaulted.
    • Enhanced boolean prompts (including accept/reject object forms) and choice selection (label-to-value mapping, validation, defaults).
  • Documentation

    • Updated the changelog and the generate-features demo/template docs for the new schema and migration.
  • Tests

    • Expanded coverage for gating, prompting/validation, and extend behavior.

derks and others added 14 commits May 29, 2026 23:42
- Migrate test6/test7/test13 fixtures from features: to variables:
  with type: boolean + extend: (enabled:->when:true, disabled:->when:false)
- Add test31 string-form bool-prompt fixture + patched-prompt unit tests
  covering y/yes->True, n/no->False, empty->default mapping
- Add test32 D-17 fixture + test: type:boolean declaring case: -> ValueError
- Tests fail until the resolver lands in Task 2 (expected RED)
- Add single recursive resolve_and_emit walking variables: in
  declaration order; every variable emits at top-level data[name]
  (delete the data['features'] namespace — #782 bug-4 fix)
- Add type: key (string|boolean|choice, default string); type: string
  is byte-identical to the released schema (test1-5/22/27/29 oracle)
- type: boolean emits a real Python bool so {% if feature_x %} works;
  string-form prompt '[(Y)es/(N)o] [default]:' with y/yes->True,
  n/no->False, empty->default mapping (covered via patched-prompt test)
- D-17 guard: type: boolean/choice declaring case:/validate: -> ValueError
- Unknown type: -> ValueError (T-05.1-01 input-validation mitigation)
- extend: boolean when:true/false accumulates variables/exclude/ignore
  and recurses nested vars depth-first in place
- Keep a thin legacy features: bridge folding enabled/disabled/options
  into the unified accumulation so un-migrated test8-30 stay green;
  bridge removed in Plan 03 (user-approved Rule 4 branch-by-abstraction)
- Add test33 (silent bool) + test34 (invalid type) for 100% coverage
- migrate test16/19/20/21/26/28/30 from prompt_mode:select to type:
  choice + options: + extend: (when-keyed effects)
- add test35 object-form bool prompt {text, accept, reject} fixture
- add test36 unquoted bool-like accept member (YAML 1.1 coercion)
- add test37 type:choice declaring case: (D-17 string-only) fixture
- rewrite select tests for choice schema; add picker/accept/reject/
  junk-abort/coercion-guard/D-17-choice tests (RED before Task 2)
- type: choice numbered picker emits the chosen option str at
  data[name]; fail-fast ValueError on empty options / option missing
  value / default-not-in-values / duplicate labels (D-16, Q3)
- polymorphic boolean prompt object-form {text, accept, reject}: author
  owns the text; accept/reject case-insensitive token lists map to bool;
  junk input asserts + aborts 'Invalid Response' (D-12/D-14)
- YAML 1.1 bool-coercion load guard: a bool-coerced accept/reject member
  raises ValueError telling the author to quote it (Pitfall 1, T-05.1-04)
- extend.when scalar str()-coerced equality for choice/string; booleans
  still match by Python == (Q2) — test16/test19 fire their per-option
  rules at this commit
- D-17 case:/validate: on type:choice -> ValueError (string-only)
- pragma the now-dead legacy select-validation raises (owned by the new
  resolver under real coverage; bridge removed in Plan 03)
- migrate test8-12/14/15/23/24/25 to type:/extend:/requires: schema
- remove obsolete test17/test18 (prompt_mode/enabled/disabled gone;
  type: whitelist already covered by test34)
- add test38 (extend.when list membership), test39 (string regex),
  test40 (Q1 requires-gated-out renders default)
- adapt assertions to new semantics: gated-out var renders its default
  and omits its extend effects (Q1); requires unknown -> ValueError
- RED until the resolver lands list/regex/nesting/requires in Task 2
- generalize _when_matches to compose scalar/in-list/string-regex
  forms; multiple matching extend rules compose and accumulate
- nested extend.variables resolve depth-first in place only when the
  parent rule fires (D-08)
- add top-level requires: with full match vocab ([name] truthy,
  {name: value} equality, {name: [v]} in-list), AND-ed and resolved
  order-independently via lazy memoized recursion (D-09/D-11)
- a requires-gated-out var renders as its default (Q1) and its extend
  rules do not fire; unknown requires target -> ValueError
- DELETE the legacy features:/_process_features bridge; the engine no
  longer reads a top-level features: key (GEN-01 fully true)
- variable missing name -> ValueError (was assert)
- test38/41 cover boolean list-form when + dict-form requires equality

Resolves #782
- Rewrite webapp .generate.yml under variables: with type:/extend:/requires:
  (docker/docker_compose boolean, web_framework choice; drop features:)
- docker_compose requires: [docker]; per-branch extend.when ignore + silent
  framework_version; docker extend.when true prompts python_version
- Demonstrate #782 fix: app.py/README use {% if docker %} and
  {% if web_framework == ... %} against top-level bool/choice exposure
- Rewrite demo README to type:/extend:/requires: vocabulary, document the
  {{ bool }} -> True/False interpolation gotcha
- Remove the four bare # pragma: nocover markers on the type: choice
  picker setup (default_label / prompt_text / SelectPrompt class) in
  _resolve_choice — they lacked a locked-vocabulary category label and
  excluded code that the patched-prompt test actually exercises.
- test_generate_choice_picker_maps_label_to_value drives the picker
  end to end, so the bug-prone label->value mapping is now under real
  coverage (269 stmts, 0 miss) rather than pragma'd.
- Only the raw SelectPrompt().prompt() input read stays excluded, with
  its existing # defensive: unreachable label.
- Reword the dynamic-import FIXME so it no longer contains a literal
  'pragma: nocover' token that tripped the inverse audit grep.
- Reference PR #780 (Tom Freudenberg's review feedback) alongside #782
  on the unified type:/extend:/requires: Features entry, calling out
  the four addressed points: features after vars in declaration order,
  custom per-feature prompt text, vars-style input, top-level Jinja
  boolean exposure.
- Rewrap the demo-migration [dev] entry to keep every line <=78 chars.
A type: boolean variable with no prompt: key rendered the literal
"None [(Y)es/(N)o] [<hint>]:" because var['prompt'] was None. Default
the label to f"Enable {name}" when prompt is falsy (D-12), mirroring
the choice path's `or f"Select: {name}"` fallback. An explicit
prompt: still overrides; the object-form is unaffected.

Covered by test42 (boolean, no prompt:) via a recorder patched onto
shell.Prompt.prompt that captures the bound Meta.text and asserts the
"Enable enable_thing [(Y)es/(N)o]" label plus the explicit-override
case.
Correct the demo README: requires: uses a bare-name list (truthy) or a
{name: value} map (equality/in-list/regex) — not the list-of-objects
form the engine rejects. Document the boolean default label
(Enable <name>) and the object-form prompt (text/accept/reject) with
the YAML bool-coercion quoting note.

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

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces Cement's legacy features generate flow with typed string, boolean, and choice variables, adds requires gating and extend rules, and updates demo docs, fixtures, and tests to match the new schema and runtime behavior.

Changes

Typed Variable System

Layer / File(s) Summary
Schema and resolution engine
cement/ext/ext_generate.py
Adds typed variable defaults, per-type prompting for string/boolean/choice values, lazy prerequisite lookup, and extend/requires resolution with nested variable handling.
Docs, changelog, and demo template
CHANGELOG.md, demo/generate-features/README.md, demo/generate-features/templates/generate/webapp/.generate.yml, demo/generate-features/templates/generate/webapp/README.md, demo/generate-features/templates/generate/webapp/app.py
Updates the changelog, demo README, and demo webapp template/output files to describe typed variables, boolean/choice behavior, requires gating, and conditional rendering.
Template fixture migration
tests/data/templates/generate/test6–test44/*
Reworks template fixtures from legacy features blocks into typed variables, choice and boolean schemas, conditional extend rules, and requires dependencies, including edge cases for silent prompts, invalid types, list membership, regex matching, and cyclic dependencies.
Test coverage updates
tests/ext/test_ext_generate.py
Expands generate extension tests for boolean and choice prompting, schema validation, requires gating, extend matching, and legacy null handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • datafolklabs/cement#768: Changes the same generate-extension path and introduces the typed variable direction that this PR extends.

Poem

I hopped through templates, neat and bright,
With booleans, choices, all in sight.
No more features in a tangled heap—
Just gated vars that Jinja can keep! 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: merging features into typed variables for ext.generate.
Linked Issues check ✅ Passed The PR addresses #782 by unifying feature prompts into typed variables, improving prompts/ordering, and exposing values at top level.
Out of Scope Changes check ✅ Passed The changes stay focused on generate schema/resolution, docs, demo updates, and tests with no clear unrelated additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/generate-features-redesign-pr

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.22.1)
demo/generate-features/README.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cement/ext/ext_generate.py`:
- Around line 308-328: In _when_matches, don't treat every string when as a
regex; change the regex branch to only run when when is an actual compiled regex
(e.g., isinstance(when, re.Pattern)) or when an explicit regex marker is used,
and otherwise fall back to exact string equality for vtype == 'string' (so
literal when values like "flask" match exactly and values with regex
metacharacters are not interpreted). Update the regex check in the _when_matches
function and ensure the final fallback uses str(when) == str(value) for string
scalars.
- Around line 340-375: Add cycle detection for recursive variable resolution by
introducing a "resolving" set and using it in resolve_and_emit to detect
re-entrance: when resolve_and_emit(var) starts, if var['name'] is already in
resolving raise a ValueError describing the cycle (include the var name and
ideally the current stack of resolving names); otherwise add the name to
resolving, proceed with resolution, memoize into data as before, and finally
remove the name from resolving in a finally block. Keep _requires_satisfied, and
other call sites (the code that calls resolve_and_emit in the other blocks
referenced) unchanged except they will now get a clear ValueError instead of
RecursionError; reference resolve_and_emit, _requires_satisfied, var_by_name,
data and ensure resolving is accessible to both resolve_and_emit and the
callers' scope.
- Around line 330-338: _gated_default currently stringifies None for gated-out
string/choice vars; change _gated_default(var, vtype) to first verify that var
contains a real default (e.g. 'default' in var and var['default'] is not None)
and if not, raise a validation error (or otherwise reject) instead of returning
"None"; keep the boolean branch casting to bool, and for choice/string types
ensure the default passes the same validation used by _resolve_choice (reuse or
call that validation) before returning str(default). Apply the same guard to the
duplicate implementation around the other occurrence (the block at the later
406-412 location).

In `@CHANGELOG.md`:
- Around line 225-230: The CHANGELOG entry for `[ext.generate]` spans multiple
lines; collapse it to a single-line entry prefixed with `[ext.generate]` that
summarizes the change succinctly (mention removal of the unreleased `features:`
schema and deletion of the legacy compatibility bridge, and reference issue `#782`
if needed) so the entire note fits on one line in CHANGELOG.md.
- Around line 190-196: The CHANGELOG entry for [ext.generate] describing the
`type: choice` template variables spans multiple lines; collapse it into a
single-line entry prefixed with `[ext.generate]` that summarizes the feature
(mentioning `type: choice`, `cement.utils.shell.Prompt`, and key behaviors like
`options:` format and fail-fast validation) so it follows the one-line-per-entry
guideline.
- Around line 160-165: The CHANGELOG entry for [ext.generate] spans multiple
lines; collapse it into a single-line entry prefixed with [ext.generate] that
briefly states the change (e.g. "type: boolean template variables now render as
Python bools so jinja2/mustache conditionals work; boolean prompt format and
single-pass prompting; resolves `#782`") and move the detailed explanation about
`data[name]`, `{% if feature_x %}`, the `[(Y)es/(N)o] [default]:` prompt format,
and the removal of the former `features:` namespace/pre-pass into the associated
PR/issue description.
- Around line 197-205: Consolidate the multi-line CHANGELOG entry for
[ext.generate] into a single line: rewrite the paragraph as one line starting
with [ext.generate] and briefly state that `type: boolean` `prompt:` is now
polymorphic — supports framework string, `prompt: false`, or an object form
`{text, accept, reject}` where case-insensitive `accept`/`reject` tokens map
input to a bool (unmatched input errors as `Invalid Response` mirroring
`validate:`) and YAML 1.1 bool-like `accept`/`reject` tokens raise `ValueError`
instructing authors to quote them; ensure no line breaks so the entire entry is
one line.
- Around line 232-236: Consolidate the multi-line CHANGELOG entry into a
single-line entry prefixed with [dev]; replace the 5-line block mentioning
`demo/generate-features/`, the migration from `features:` to
`type:`/`extend:`/`requires:`, and the `#782` rendering note into one concise line
(e.g. "[dev] Migrate demo/generate-features/ template to unified
type/extend/requires schema; demonstrate `#782` fix where `{% if docker %}` and
`{% if web_framework == ... %}` now render against top-level bool/choice
exposure").
- Around line 206-221: The changelog entry for the `[ext.generate]` feature is
currently multi-line; collapse it into a single-line entry starting with the
`[ext.generate]` prefix that summarizes the change (mention compose three match
forms, requires gating, and PR/Issue references) and keep the links `(Issue
`#782`, PR `#780`)` inline; ensure the line is one continuous line (no line breaks)
and follows the "Each CHANGELOG.md entry must be one line, prefixed with [area]"
rule so the existing multi-line block is replaced with that single consolidated
line.

In `@demo/generate-features/README.md`:
- Line 272: Remove the unused Markdown link reference "[779]" by deleting the
line that defines "[779]: https://github.com/datafolklabs/cement/issues/779"
(ensure no other references to [779] exist elsewhere in the document); this
cleans up the README and avoids an orphaned link definition now superseded by
issue `#782`.
- Around line 157-160: The blockquote containing the text starting with "Quote
bool-like tokens (`\"yes\"`, `\"no\"`, `\"on\"`, `\"off\"`) inside `accept:` /
`reject:` — under YAML 1.1 they otherwise decode to a Python `bool` and the
loader rejects them with a clear error." contains an extra blank line that
violates MD028; remove the blank line so the two quoted lines are merged into a
single blockquote paragraph, or alternatively split them into two separate
blockquote blocks so there is no empty line inside a single blockquote.

In `@tests/ext/test_ext_generate.py`:
- Around line 365-374: The test test_generate_boolean_silent is not asserting
the actual typed output and can pass if the boolean is wrongly stringified;
change the test to verify the raw rendered value or flip the default to false so
string 'True' fails — specifically update test_generate_boolean_silent (and/or
the fixture used by GenerateApp) to assert the file contents equal the expected
literal boolean representation (or use a false default so only a real Python
True/False yields the expected branch), e.g., open the generated 'take-me' file
after app.run() and assert the exact value/type rather than only that 'flag-on'
appears.
- Around line 294-305: The new tests (e.g., test_generate_boolean_prompt_yes)
and related helpers like record() lack Python type annotations; update each test
function signature to add parameter and return type annotations (e.g., annotate
the tmp parameter type and the function return as -> None) and annotate the
record() helper's parameters and return type so all new/changed tests comply
with the project's typing rule; ensure every test and helper introduced in this
diff uses explicit type hints for parameters and return values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f13485d2-bbc2-4666-9aee-0416b870d66a

📥 Commits

Reviewing files that changed from the base of the PR and between 48dbae0 and d60766e.

📒 Files selected for processing (64)
  • CHANGELOG.md
  • cement/ext/ext_generate.py
  • demo/generate-features/README.md
  • demo/generate-features/templates/generate/webapp/.generate.yml
  • demo/generate-features/templates/generate/webapp/README.md
  • demo/generate-features/templates/generate/webapp/app.py
  • tests/data/templates/generate/test10/.generate.yml
  • tests/data/templates/generate/test11/.generate.yml
  • tests/data/templates/generate/test12/.generate.yml
  • tests/data/templates/generate/test13/.generate.yml
  • tests/data/templates/generate/test14/.generate.yml
  • tests/data/templates/generate/test14/take-me
  • tests/data/templates/generate/test15/.generate.yml
  • tests/data/templates/generate/test16/.generate.yml
  • tests/data/templates/generate/test17/.generate.yml
  • tests/data/templates/generate/test17/take-me
  • tests/data/templates/generate/test18/.generate.yml
  • tests/data/templates/generate/test18/take-me
  • tests/data/templates/generate/test19/.generate.yml
  • tests/data/templates/generate/test20/.generate.yml
  • tests/data/templates/generate/test21/.generate.yml
  • tests/data/templates/generate/test23/.generate.yml
  • tests/data/templates/generate/test24/.generate.yml
  • tests/data/templates/generate/test25/.generate.yml
  • tests/data/templates/generate/test26/.generate.yml
  • tests/data/templates/generate/test28/.generate.yml
  • tests/data/templates/generate/test30/.generate.yml
  • tests/data/templates/generate/test31/.generate.yml
  • tests/data/templates/generate/test31/take-me
  • tests/data/templates/generate/test32/.generate.yml
  • tests/data/templates/generate/test32/take-me
  • tests/data/templates/generate/test33/.generate.yml
  • tests/data/templates/generate/test33/take-me
  • tests/data/templates/generate/test34/.generate.yml
  • tests/data/templates/generate/test34/take-me
  • tests/data/templates/generate/test35/.generate.yml
  • tests/data/templates/generate/test35/take-me
  • tests/data/templates/generate/test36/.generate.yml
  • tests/data/templates/generate/test36/take-me
  • tests/data/templates/generate/test37/.generate.yml
  • tests/data/templates/generate/test37/take-me
  • tests/data/templates/generate/test38/.generate.yml
  • tests/data/templates/generate/test38/bool-list-ignored
  • tests/data/templates/generate/test38/fastapi-only
  • tests/data/templates/generate/test38/not-web
  • tests/data/templates/generate/test38/take-me
  • tests/data/templates/generate/test39/.generate.yml
  • tests/data/templates/generate/test39/not-v2
  • tests/data/templates/generate/test39/take-me
  • tests/data/templates/generate/test39/v3-only
  • tests/data/templates/generate/test40/.generate.yml
  • tests/data/templates/generate/test40/should-be-kept
  • tests/data/templates/generate/test40/take-me
  • tests/data/templates/generate/test41/.generate.yml
  • tests/data/templates/generate/test41/dep-no-kept
  • tests/data/templates/generate/test41/dep-ok-ignored
  • tests/data/templates/generate/test41/take-me
  • tests/data/templates/generate/test42/.generate.yml
  • tests/data/templates/generate/test42/take-me
  • tests/data/templates/generate/test6/.generate.yml
  • tests/data/templates/generate/test7/.generate.yml
  • tests/data/templates/generate/test8/.generate.yml
  • tests/data/templates/generate/test9/.generate.yml
  • tests/ext/test_ext_generate.py
💤 Files with no reviewable changes (4)
  • tests/data/templates/generate/test18/take-me
  • tests/data/templates/generate/test18/.generate.yml
  • tests/data/templates/generate/test17/take-me
  • tests/data/templates/generate/test17/.generate.yml

Comment on lines +308 to +328
def _when_matches(value: Any, when: Any, vtype: str) -> bool:
# Compose the three `extend.when` / `requires` match forms
# (D-07), dispatching on the carrying variable's `type` (Q2):
# - list -> in-list membership. Booleans compare by Python
# `==`; choice/string members str()-coerce both sides.
# - regex -> `re.match(when, value)` — STRING type only
# (mirrors `validate:`); a non-string `when` on a string
# var still falls through to scalar equality below.
# - scalar -> equality. Booleans by Python `==` (so
# `when: true` matches `True`, not the string "True");
# choice/string by str()-coerced equality (so `when: 1`
# matches a "1" option value; per the L219 discipline).
if isinstance(when, list):
if vtype == 'boolean':
return value in when
return str(value) in [str(w) for w in when]
if vtype == 'string' and isinstance(when, str):
return re.match(when, value) is not None
if vtype == 'boolean':
return bool(value == when)
return str(when) == str(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't treat every string when as a regex.

Line 324 makes scalar string rules impossible: when: flask also matches flask2, and literals containing regex metacharacters are interpreted unexpectedly. That breaks the advertised equality semantics for string variables in both requires and extend; this needs an explicit regex form or a separate exact-match path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cement/ext/ext_generate.py` around lines 308 - 328, In _when_matches, don't
treat every string when as a regex; change the regex branch to only run when
when is an actual compiled regex (e.g., isinstance(when, re.Pattern)) or when an
explicit regex marker is used, and otherwise fall back to exact string equality
for vtype == 'string' (so literal when values like "flask" match exactly and
values with regex metacharacters are not interpreted). Update the regex check in
the _when_matches function and ensure the final fallback uses str(when) ==
str(value) for string scalars.

Comment thread cement/ext/ext_generate.py
Comment thread cement/ext/ext_generate.py
Comment thread CHANGELOG.md
Comment on lines +160 to +165
- `[ext.generate]` `type: boolean` template variables now emit a real
Python bool at the top level of the render context (`data[name]`),
so `{% if feature_x %}` works in jinja2/mustache; the boolean prompt
uses a vars-style `[(Y)es/(N)o] [default]:` format and all variables
prompt in a single declaration-order pass — resolves #782 (the
former `features:` namespace and pre-pass are removed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consolidate to a single-line CHANGELOG entry.

CHANGELOG entries must be one line per the coding guidelines. This entry spans 6 lines. Condense the description or move detail to issue/PR discussions.

As per coding guidelines: "Each CHANGELOG.md entry must be one line, prefixed with [area]".

Example consolidation
-- `[ext.generate]` `type: boolean` template variables now emit a real
-  Python bool at the top level of the render context (`data[name]`),
-  so `{% if feature_x %}` works in jinja2/mustache; the boolean prompt
-  uses a vars-style `[(Y)es/(N)o] [default]:` format and all variables
-  prompt in a single declaration-order pass — resolves `#782` (the
-  former `features:` namespace and pre-pass are removed)
+- `[ext.generate]` `type: boolean` variables emit real Python bools at top level for direct Jinja2/mustache conditionals; unified prompt pass — resolves `#782`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `[ext.generate]` `type: boolean` template variables now emit a real
Python bool at the top level of the render context (`data[name]`),
so `{% if feature_x %}` works in jinja2/mustache; the boolean prompt
uses a vars-style `[(Y)es/(N)o] [default]:` format and all variables
prompt in a single declaration-order pass — resolves #782 (the
former `features:` namespace and pre-pass are removed)
- `[ext.generate]` `type: boolean` variables emit real Python bools at top level for direct Jinja2/mustache conditionals; unified prompt pass — resolves `#782`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 160 - 165, The CHANGELOG entry for [ext.generate]
spans multiple lines; collapse it into a single-line entry prefixed with
[ext.generate] that briefly states the change (e.g. "type: boolean template
variables now render as Python bools so jinja2/mustache conditionals work;
boolean prompt format and single-pass prompting; resolves `#782`") and move the
detailed explanation about `data[name]`, `{% if feature_x %}`, the `[(Y)es/(N)o]
[default]:` prompt format, and the removal of the former `features:`
namespace/pre-pass into the associated PR/issue description.

Comment thread CHANGELOG.md
Comment on lines +190 to +196
- `[ext.generate]` Add `type: choice` template variables — a numbered
`cement.utils.shell.Prompt` picker that emits the chosen option string
at the top level of the render context. `options:` accepts a scalar
list or per-option `{value, prompt}` objects; per-option effects live
in `extend:` rules keyed by `when: <value>`. Misconfig (empty options,
option missing `value`, default not in options, duplicate labels) is
fail-fast `ValueError`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consolidate to a single-line CHANGELOG entry.

This feature entry spans 7 lines. Per coding guidelines, each CHANGELOG entry must be one line.

As per coding guidelines: "Each CHANGELOG.md entry must be one line, prefixed with [area]".

Example consolidation
-- `[ext.generate]` Add `type: choice` template variables — a numbered
-  `cement.utils.shell.Prompt` picker that emits the chosen option string
-  at the top level of the render context. `options:` accepts a scalar
-  list or per-option `{value, prompt}` objects; per-option effects live
-  in `extend:` rules keyed by `when: <value>`. Misconfig (empty options,
-  option missing `value`, default not in options, duplicate labels) is
-  fail-fast `ValueError`.
+- `[ext.generate]` Add `type: choice` variables with numbered picker; emits string at top level; `extend:` rules per option; fail-fast validation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `[ext.generate]` Add `type: choice` template variables — a numbered
`cement.utils.shell.Prompt` picker that emits the chosen option string
at the top level of the render context. `options:` accepts a scalar
list or per-option `{value, prompt}` objects; per-option effects live
in `extend:` rules keyed by `when: <value>`. Misconfig (empty options,
option missing `value`, default not in options, duplicate labels) is
fail-fast `ValueError`.
- `[ext.generate]` Add `type: choice` variables with numbered picker; emits string at top level; `extend:` rules per option; fail-fast validation
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 190 - 196, The CHANGELOG entry for [ext.generate]
describing the `type: choice` template variables spans multiple lines; collapse
it into a single-line entry prefixed with `[ext.generate]` that summarizes the
feature (mentioning `type: choice`, `cement.utils.shell.Prompt`, and key
behaviors like `options:` format and fail-fast validation) so it follows the
one-line-per-entry guideline.

Comment thread CHANGELOG.md
Comment on lines +232 to +236
- `[dev]` Migrate the `demo/generate-features/` webapp template to the
unified `type:`/`extend:`/`requires:` schema (off the removed
`features:` schema) and demonstrate the #782 fix — `{% if docker %}`
and `{% if web_framework == ... %}` now render against the top-level
bool/choice exposure

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consolidate to a single-line CHANGELOG entry.

This refactoring entry spans 5 lines. Per coding guidelines, each CHANGELOG entry must be one line.

As per coding guidelines: "Each CHANGELOG.md entry must be one line, prefixed with [area]".

Example consolidation
-- `[dev]` Migrate the `demo/generate-features/` webapp template to the
-  unified `type:`/`extend:`/`requires:` schema (off the removed
-  `features:` schema) and demonstrate the `#782` fix — `{% if docker %}`
-  and `{% if web_framework == ... %}` now render against the top-level
-  bool/choice exposure
+- `[dev]` Migrate `demo/generate-features/` to unified typed variable schema; demonstrates `#782` top-level bool/choice exposure
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `[dev]` Migrate the `demo/generate-features/` webapp template to the
unified `type:`/`extend:`/`requires:` schema (off the removed
`features:` schema) and demonstrate the #782 fix — `{% if docker %}`
and `{% if web_framework == ... %}` now render against the top-level
bool/choice exposure
- `[dev]` Migrate `demo/generate-features/` to unified typed variable schema; demonstrates `#782` top-level bool/choice exposure
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 232 - 236, Consolidate the multi-line CHANGELOG
entry into a single-line entry prefixed with [dev]; replace the 5-line block
mentioning `demo/generate-features/`, the migration from `features:` to
`type:`/`extend:`/`requires:`, and the `#782` rendering note into one concise line
(e.g. "[dev] Migrate demo/generate-features/ template to unified
type/extend/requires schema; demonstrate `#782` fix where `{% if docker %}` and
`{% if web_framework == ... %}` now render against top-level bool/choice
exposure").

Comment thread demo/generate-features/README.md Outdated
Comment thread demo/generate-features/README.md Outdated
Comment on lines +294 to +305
def test_generate_boolean_prompt_yes(tmp):
# test31: type: boolean with a string-form prompt, run interactively
# (no --defaults). Patch shell.Prompt.prompt to return "yes" → the
# y/yes→True mapping runs and data[name] is True (jinja renders the
# enabled branch).
with patch.object(shell.Prompt, 'prompt', return_value='yes'):
argv = ['generate', 'test31', tmp.dir]
with GenerateApp(argv=argv) as app:
app.run()
with open(os.path.join(tmp.dir, 'take-me')) as f:
assert 'enabled' in f.read()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add type annotations to the new tests and helper.

The new test cases start introducing unannotated tmp params and None returns, and the same issue repeats across the other added tests plus record(). That leaves the changed code out of compliance with the repo's Python typing rule.

Example pattern to apply
+from typing import Any
+
-def test_generate_boolean_prompt_yes(tmp):
+def test_generate_boolean_prompt_yes(tmp: Any) -> None:
     ...
-    def record(self):
+    def record(self: shell.Prompt) -> str:
         seen.append(self._meta.text)
         return ''

As per coding guidelines "Use type annotations for all function parameters and return types in Python code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ext/test_ext_generate.py` around lines 294 - 305, The new tests (e.g.,
test_generate_boolean_prompt_yes) and related helpers like record() lack Python
type annotations; update each test function signature to add parameter and
return type annotations (e.g., annotate the tmp parameter type and the function
return as -> None) and annotate the record() helper's parameters and return type
so all new/changed tests comply with the project's typing rule; ensure every
test and helper introduced in this diff uses explicit type hints for parameters
and return values.

Comment thread tests/ext/test_ext_generate.py Outdated
@TomFreudenberg

Copy link
Copy Markdown
Contributor

Hi @derks

that worked for me.

I also tested with different "whens" and "requires".

Looked complete by now.


Let me please ask about your workflow, while many of your codes now generated by claude.

Do you review all changes or is it mostly "YOLO" since 3.0.14?

Have a nice sunday
Tom

@derks

derks commented May 31, 2026

Copy link
Copy Markdown
Member Author

Hey @TomFreudenberg thanks for the feedback.


Let me please ask about your workflow, while many of your codes now generated by claude.

Right now I'm in a bit of an experimental phase, but have locked in at the moment to using GSD for many projects. Not sure if you've seen this issue/post yet; it outlines the current direction with AI (and the what/why):

Do you review all changes or is it mostly "YOLO" since 3.0.14?

Yes, I review all changes. Working with GSD through a phase looks like this:

I won't say that I read everything - it's a lot. But I do review the plans before execution, otherwise you end up having to backout or redo a lot of the work.

I review the code 100% ... that is where this one is at in the process. I'm treating the agent like I would working with a human ... set the direction, guide the work, let it make some of it's own informed decisions (research), and then submit the work for code review.

Code Rabbit is here for a first pass "second set of eyes" - an alternative AI that is 100% outside the context of the planning/execution/etc. It reviews the outcome - same as if it were written by a human. My next pass will be to go through Code Rabbit's review with Claude, and discuss/implement changes based on the review.

After that - I'll read though all of the code line by line and work through another session with Claude, maybe ask a lot of "why did we do X", and direct a lot of changes to clean it up. I might open my IDE and actually touch code - but to be honest it really disrupts the flow to modify files "out of band" ... changes might get reverted/stomped on by the agents if you don't remember to inform it to re-read files you touched.

Once happy with the code - I'll squash/rebase down to a smaller set of commits and merge to main.

@TomFreudenberg

Copy link
Copy Markdown
Contributor

Good morning @derks,

do you have already defined a timeline when to release that an the last features? 3.0.16?

I guess that I will publish my stuff within the next 10 days as beta. So if you are not ready then, I will use a branch and tag from my fork, otherwise the 3.0.16 stable.

Cheers,
Tom

@TomFreudenberg

Copy link
Copy Markdown
Contributor

Dear @derks

could you be so kind and tell me about delivering date for 3.0.16 as next stable release?

Thanks and hope you are fine
Tom

@derks

derks commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@TomFreudenberg it is a work-in progress ... there is still more that I want to do before 3.0.16 - at the moment I am slightly delayed due to other responsibilities. I will be getting back to Cement progress in roughly a week from now.

derks and others added 5 commits June 24, 2026 11:21
When requires: gates a variable out and it declares no default,
_gated_default returned str(None) -> the literal string "None" landed
in the template context (and the choice path skipped its required-default
validation). Add a fail-fast ValueError guard (survives python -O,
matching the D-17 guard style already in the file).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A requires B + B requires A recursed to RecursionError because
memoization only happened after a var fully resolved (data[name] set
at the end). Add an in-flight resolving set: a name re-entered while
still resolving raises a fail-fast ValueError. The set is cleared in
a finally so every exit path (gated-out early return, normal
completion, exceptions) leaves the happy path unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flip test33 fixture default true -> false. With a false default a real
Python False renders the else branch (flag-off); a regressed impl
emitting the string 'False' is truthy and would render flag-on,
failing the test. Update the assertion to flag-off and the comment to
explain the typed-output contract being guarded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the bare blank line splitting two adjacent blockquotes with a
quoted-empty line (>) so they form one continuous blockquote, clearing
the markdownlint MD028 warning. Remove the unused [779] link reference
definition (MD053; verified no [779] usage remains in the doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add test43 (a string variable gated out by requires: with no default)
and test44 (alpha<->beta requires cycle) fixtures plus tests, exercising
the two new fail-fast ValueError guards added in this branch. Restores
100% coverage on cement/ext/ext_generate.py.

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

derks commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

CodeRabbit review triage

Thanks @coderabbitai. Triaged all 13 findings — 4 addressed, 9 declined (with reasons so it's on the record).

Addressed

Finding Commit
_gated_default leaks the literal "None" for a requires:-gated-out var with no default 1f0987e8 — now fail-fasts with a clear ValueError
A requires B / B requires A recurses to RecursionError e594f90d — in-flight resolving guard raises a clear ValueError
test_generate_boolean_silent couldn't catch a stringified bool (truthy default) 77000439 — fixture flipped to default: false; a stringified 'False' (truthy) now fails the test
README MD028 (blockquote blank line) + unused [779] link ref 533ca5b3

Plus fb142bf5 adds test43/test44 fixtures + tests covering the two new guards, restoring 100% coverage. Full make test: 372 passed, ruff + mypy clean.

Declined

  • "Don't treat every string when as a regex" — by design. Regex matching for type: string variables deliberately mirrors the existing validate: semantics (documented in the _when_matches comments). Authors who want an exact match anchor it (^flask$), exactly as with validate:. Changing this would diverge the two string-matching surfaces.
  • "Add type annotations to the new tests"tests/ is excluded from mypy ([tool.mypy] files/exclude in pyproject.toml), and every existing test in the suite is unannotated. The new tests match the established convention.
  • 6× "Consolidate to a single-line CHANGELOG entry" — these contradict the file's own established style: the active DEVELOPMENT section already wraps multi-line entries at 78 chars, per the repo guideline "wrap longer prose at 78." These are single logical entries that are line-wrapped, not multiple entries.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@derks — thanks for the thorough triage! The fixes look solid:

  • The _gated_default "None" leak and the circular-requires RecursionError are exactly the kind of edge cases that benefit from explicit fail-fast guards — good catches.
  • Flipping the test_generate_boolean_silent fixture to default: false is the right call; a stringified 'False' being truthy is a subtle trap.

The declined items are well-reasoned and I'm happy to accept them:

  • Regex when for strings — mirroring validate: semantics is coherent design; anchoring with ^…$ for exact matches is consistent and documented.
  • Test type annotations — matches the established convention for the suite.
  • CHANGELOG wrapping — the 78-char wrap guideline clearly applies; these are single wrapped entries, not duplicates.

372 passing, ruff + mypy clean — looks good to go. 🐇

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/ext/test_ext_generate.py (1)

281-281: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add type annotations to updated test function signatures.

These tests still have untyped tmp parameters and implicit return types. Annotate params and set -> None.

Suggested patch
+from typing import Any
+
-def test_generate_requires_gated_no_default(tmp):
+def test_generate_requires_gated_no_default(tmp: Any) -> None:
@@
-def test_generate_requires_cycle(tmp):
+def test_generate_requires_cycle(tmp: Any) -> None:
@@
-def test_generate_boolean_silent(tmp):
+def test_generate_boolean_silent(tmp: Any) -> None:

As per coding guidelines "Use type annotations for all function parameters and return types in Python code".

Also applies to: 293-293, 388-388

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ext/test_ext_generate.py` at line 281, The updated test functions still
have untyped parameters and implicit return types; add type annotations to the
affected test signatures and mark them with an explicit None return. Update the
test functions identified by test_generate_requires_gated_no_default and the
other matching tests in the same module so every parameter has a type
annotation, including tmp, and each function uses -> None.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/ext/test_ext_generate.py`:
- Line 281: The updated test functions still have untyped parameters and
implicit return types; add type annotations to the affected test signatures and
mark them with an explicit None return. Update the test functions identified by
test_generate_requires_gated_no_default and the other matching tests in the same
module so every parameter has a type annotation, including tmp, and each
function uses -> None.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c9a48da-ddc8-4de9-8fb5-8f90a5f7c077

📥 Commits

Reviewing files that changed from the base of the PR and between d60766e and fb142bf.

📒 Files selected for processing (8)
  • cement/ext/ext_generate.py
  • demo/generate-features/README.md
  • tests/data/templates/generate/test33/.generate.yml
  • tests/data/templates/generate/test43/.generate.yml
  • tests/data/templates/generate/test43/take-me
  • tests/data/templates/generate/test44/.generate.yml
  • tests/data/templates/generate/test44/take-me
  • tests/ext/test_ext_generate.py
✅ Files skipped from review due to trivial changes (5)
  • tests/data/templates/generate/test44/take-me
  • tests/data/templates/generate/test43/take-me
  • tests/data/templates/generate/test43/.generate.yml
  • tests/data/templates/generate/test44/.generate.yml
  • demo/generate-features/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • cement/ext/ext_generate.py

derks added a commit that referenced this pull request Jun 24, 2026
Add the GSD quick-task PLAN/SUMMARY for the PR #783 CodeRabbit
follow-ups and log the task in STATE.md. Planning/execution docs only —
the code changes land separately in ee0a4ccb and 29e41bc9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@derks derks closed this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ext.generate: select-mode feature prompt improvements (feedback from #780)

2 participants