feat: board_create_from_plan: batch-create features from a structured decomposition (#92)#94
Conversation
There was a problem hiding this comment.
QA panel review — FAIL
code-review-structural · head 8c21cc19cc7f · formal
[review-synthesizer completed: workflow code-review-structural:report]
This PR introduces a batch feature-creation path (board_create_from_plan → create_from_plan → /features/batch) that carries real correctness risk despite its limited surface. The by_title.setdefault bug (finding 1) is the fix-first item: it can silently break valid dependency edges whenever plan items share a normalized title. The panel agreed on all six findings — none were refuted. Verification confirmed every finding against the diff; the only nuance is that finding 4's residual gap is narrower than it first appears (JSON parse handles most quoting). A coverage gap worth noting: create_from_plan (~40 new lines in store.py) has no unit tests in this diff, and all six findings emerge from that method and its wrappers — the batch path was read but not exercised.
[
{
"file": "store.py",
"line": 416,
"severity": "major",
"category": "correctness",
"claim": "Duplicate plan-item titles cause dependency resolution to pick the FIRST item (possibly failed) rather than any successfully-created item with that title, silently breaking valid dependency edges.",
"evidence": "In Phase 2, `by_title.setdefault(_plan_title_key(it[\"title\"]), it)` keeps only the first item per normalized title. If that first item failed to create (`id` is None), every `depends_on` reference to that title resolves to `None` and reports 'its create failed' — even when a later item with the SAME title was created successfully and has a valid id.",
"source": "correctness",
"verdict": "confirmed",
"note": "setdefault keeps only the first item per key; if item 0 fails (id=None) and item 1 succeeds with same normalized title, by_title maps to the failed item — every dep on that title hits the dead id path, ignoring the successful duplicate."
},
{
"file": "store.py",
"line": 380,
"severity": "minor",
"category": "correctness",
"claim": "Priority 0 is silently coerced to 2 because `int(section.get(\"priority\", 2) or 2)` treats the integer 0 as falsy.",
"evidence": "`int(section.get(\"priority\", 2) or 2)` — when `section.get(\"priority\")` returns the integer `0`, `0 or 2` evaluates to `2`, then `int(2)` gives `2`. A user who explicitly sets `\"priority\": 0` in a plan item gets priority 2 instead, with no warning.",
"source": "correctness",
"verdict": "confirmed",
"note": "Python: 0 is falsy, so `0 or 2` → 2, then int(2) → 2. Explicit priority=0 silently maps to 2."
},
{
"file": "store.py",
"line": 103,
"severity": "minor",
"category": "conventions",
"claim": "`_plan_title_key` is an exact duplicate of `__init__.py`'s `_norm_title` — identical implementation, diverging only in docstring — creating a maintenance hazard where future changes to normalization logic could be applied to only one copy.",
"evidence": "store.py: `return \" \".join(str(t or \"\").strip().lower().split())` vs __init__.py: `return \" \".join(str(t or \"\").strip().lower().split())` — identical byte-for-byte. Docstring of `_plan_title_key` acknowledges 'the same exact-after-normalize matching the tool-boundary dedup uses.'",
"source": "conventions",
"verdict": "confirmed",
"note": "Both functions: `' '.join(str(t or '').strip().lower().split())` — identical implementation. store.py's docstring even acknowledges it mirrors the tool-boundary version."
},
{
"file": "api.py",
"line": 224,
"severity": "minor",
"category": "correctness",
"claim": "`bool(body.get(\"mark_ready\"))` coerces the string `\"false\"` to `True`, causing mark_ready to activate when a string false was intended.",
"evidence": "`bool(body.get(\"mark_ready\"))` — `bool(\"false\")` returns `True` because any non-empty string is truthy. Normal JSON `false` → Python `False` is fine, but a caller that sends the string `\"false\"` (e.g. from a form-encoded body or a mis-serialized payload) gets unexpected promotion.",
"source": "correctness",
"verdict": "confirmed",
"note": "bool('false') → True (any non-empty string is truthy). JSON false → Python False works; the gap is string 'false' from non-JSON callers."
},
{
"file": "__init__.py",
"line": 239,
"severity": "minor",
"category": "cross-file",
"claim": "The `board_create_from_plan` tool does not perform the `_open_duplicate` dedup check that `board_create_feature` does, despite its docstring claiming 'Every section gets board_create_feature's validation.' Re-calling the batch tool with overlapping plan items would silently stack duplicate features.",
"evidence": "`board_create_feature` calls `_open_duplicate(existing, title)` and returns a 'Skipped' message if a duplicate exists. `board_create_from_plan` calls `get_store(**store_kw).create_from_plan(parsed, mark_ready=mark_ready)` directly with no dedup check. The store's `create_from_plan` in turn calls `self.create_feature(...)` for each item — and `create_feature` (store.py) has no dedup logic either.",
"source": "cross-file",
"verdict": "confirmed",
"note": "No _open_duplicate call anywhere in the batch path; store.create_feature also lacks dedup. Re-calling with overlapping items would stack duplicates with no guard."
},
{
"file": "__init__.py",
"line": 239,
"severity": "minor",
"category": "cross-file",
"claim": "The `board_create_from_plan` tool does not apply `_strip_wrapping_quotes` to individual plan-item fields (title, spec, acceptance_criteria, design, etc.), unlike `board_create_feature` which strips every string field. The store's `create_feature` relies on the tool layer for this hygiene, so batch-created features can retain literal wrapping double-quotes that the single-create path removes.",
"evidence": "In `board_create_from_plan`: `json.loads(_strip_wrapping_quotes(parsed.strip()) or \"[]\")` strips only the outer JSON string, then passes parsed dicts directly to `create_from_plan`. In `board_create_feature`, every string field goes through `_strip_wrapping_quotes`: `title = _strip_wrapping_quotes(title)`, `spec = _strip_wrapping_quotes(spec)`, etc. The store's `create_feature` does NOT strip wrapping quotes — that contract lives entirely at the tool boundary.",
"source": "cross-file",
"verdict": "confirmed",
"note": "Only the outer JSON string is stripped; individual fields are not. JSON parse handles JSON-level quoting, so the residual gap is narrower than single-create (embedded literal \"…\" inside JSON string values), but the inconsistency with board_create_feature is real."
}
]|
Operator note on the FAIL verdict: all six findings ground-truthed against head 8c21cc1 — the major (by_title.setdefault dep-resolution on duplicate titles) verified real. Ruling: fix-all-now. The feature is being requeued with the findings as explicit criteria (including unit tests for create_from_plan, which shipped untested); this branch will be force-pushed with the fix round. |
There was a problem hiding this comment.
QA panel review — WARN
code-review-structural · head cdfdbd0f6738 · formal
[review-synthesizer completed: workflow code-review-structural:report]
Prose Brief
Overall risk: Moderate. The batch board_create_from_plan codepath has at least one confirmed crash vector (double-dash dep strings) that bypasses the tool's error guard and terminates the entire batch. The prior round's major finding about duplicate plan-item titles silently resolving to the wrong dependency edge remains unverified — I cannot tell whether it was fixed or still lurks.
Fix first: The _resolve_plan_dep crash (store.py:567). Wrap the int(dep.strip()) call in a try/except, or replace the .lstrip("-") heuristic with removeprefix("-") so only a single dash is stripped before the digit test. Also wrap the call-site in create_from_plan so one malformed dep string doesn't kill the whole batch.
Panel disagreement: None surfaced in this verification pass — only one finding was submitted for verification and it was confirmed unanimously.
What verification changed: The verifier confirmed the crash path by reading head-SHA code: "--5".lstrip("-") → "5" (isdigit→True), then int("--5") raises ValueError with no handler anywhere in the call chain. The finding stands as confirmed.
Gaps: Five prior-round findings (including one major at store.py:416 on duplicate-title dependency misrouting) were not re-examined in this verification pass. The structural pass appears to have been skipped or not included. I am carrying the major as open below.
Prior Round Dispositions
[{"prior": "store.py:416", "disposition": "open", "why": "Not addressed in this verification pass — the verifier only examined one finding. Cannot determine from available evidence whether the duplicate-title dependency-routing defect was fixed."}]Canonical Findings
[
{
"file": "store.py",
"line": 567,
"severity": "minor",
"category": "correctness",
"claim": "_resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the remainder — but int() rejects the original double-dash string.",
"evidence": "`elif isinstance(dep, str) and dep.strip().lstrip(\"-\").isdigit():\n idx = int(dep.strip())` — for dep='--5', lstrip('-') → '5' (isdigit() → True), then int('--5') raises ValueError. The exception propagates out of create_from_plan uncaught, bypassing the tool's `except BoardError` guard and crashing the entire batch.",
"source": "correctness",
"verdict": "confirmed",
"note": "Code read at head SHA confirms exact bug: dep.strip().lstrip('-') strips ALL dashes ('--5'→'5'), isdigit()→True, but int(dep.strip()) calls int('--5') which raises ValueError in Python. Call-site in create_from_plan pass 2 has no try/except; tool wrapper's except BoardError won't catch ValueError. Also affects '--0', '---3', etc. — any dep string with ≥2 leading dashes whose remainder is all-digits."
}
]1 finding(s) downgraded to uncertain: the code they quote as evidence does not appear in the file at the reviewed head, nor in this PR's patch for it. A finding that cannot be grounded does not gate a merge (issue #25) — it still stands for a human to judge.
store.py(minor) — quoted evidence not found at this head:— for dep='--5', lstrip('-') → '5' (isdigit() → True), then int('--5') raises ValueError. The exception propagates out o
Superseded — a later head cleared the QA panel (see the newest verdict).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
|
Operator adjudication of the round-2 WARN (brain-ruled): fix-now. The residual |
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
There was a problem hiding this comment.
Promoting the WARN verdict for head cdfdbd0f6738: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
store.py:567— _resolve_plan_dep crashes with uncaught ValueError on dependency strings containing multiple leading dashes (e.g. '--5'), because str.lstrip('-') strips all dashes — allowing isdigit() to pass on the
Approving a WARN does not resolve its findings (issue #22).
… decomposition (#92)
Summary
board_create_from_plan: batch-create features from a structured decomposition (#92) (
bd-2np)See the diff for details.