chore(vbrief): add issue reconciliation script and close obsoleted issues#357
Conversation
…sues (#322) - Create scripts/reconcile_issues.py: scans all vBRIEF lifecycle folders for github-issue references, fetches open issues via gh CLI, produces structured reconciliation report (linked, unlinked, no-open-issue) - Register as task reconcile:issues in tasks/reconcile.yml - Close obsoleted issue #115 (spec validation gate + SPECIFICATION.md freshness -- entire scope superseded by vbrief_validate.py and SPECIFICATION.md deprecation redirect) - Add 25 tests in tests/cli/test_reconcile_issues.py covering reference extraction, issue parsing, directory scanning, reconciliation logic, output formatting, and CLI integration - Add CHANGELOG.md [Unreleased] entry Part of #309 (Phase 2 vBRIEF Architecture Cutover)
Greptile SummaryAdds Confidence Score: 5/5Safe to merge — all prior P1 bugs resolved; only minor P2 robustness edge cases remain Both flagged issues from the previous review cycle (None guard and silent truncation) are fully fixed. The two remaining comments are P2: detect_repo mis-parsing dotted repo names (visible failure mode, not silent data loss) and a TypeError risk on references: null in malformed vBRIEFs (unlikely in practice given framework-generated files). Neither blocks merge. scripts/reconcile_issues.py — detect_repo regex and null-references robustness Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([task reconcile:issues]) --> B[scan_vbrief_dir\nvbrief/proposed,pending,\nactive,completed,cancelled]
B --> C{parse each\n*.vbrief.json}
C -->|github-issue refs| D[issue_to_vbriefs\nmap: num → files]
C -->|skip non-github\nor malformed| C
A --> E[detect_repo\ngit remote get-url]
E --> F[fetch_open_issues\ngh issue list --limit 200]
F -->|None on error| G([exit 1])
F -->|list of dicts| H[reconcile]
D --> H
H --> I{classify each\nopen issue}
I -->|in issue_to_vbriefs| J[linked]
I -->|not in map| K[unlinked]
H --> L{vBRIEF refs\nwith no open issue}
L --> M[no_open_issue]
J & K & M --> N[format_json\nor format_markdown]
N --> O([stdout])
Prompt To Fix All With AIThis is a comment left during a code review.
Path: scripts/reconcile_issues.py
Line: 388-390
Comment:
**`detect_repo` strips repo names at the first dot**
The second capture group `([^/.]+)` excludes `.` to drop `.git`, but also stops at the first `.` in the repo name itself. For a remote URL like `git@github.com:owner/my.service.git`, this returns `owner/my` instead of `owner/my.service`. The failure mode is either a `gh` CLI error (detectable) or, in the unlikely case that `owner/my` exists, a silent reconciliation run against the wrong repository.
Strip the `.git` suffix explicitly instead of relying on the character class:
```suggestion
m = re.search(r"github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$", url.strip())
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: scripts/reconcile_issues.py
Line: 62-75
Comment:
**`references: null` propagates uncaught `TypeError` and crashes the scan**
`plan.get("references", [])` returns the stored `None` when the key is present with a `null` value — the default `[]` is only substituted when the key is *absent*. Iterating over `None` raises `TypeError`. The same pattern appears in `_walk_items` on the `"references"`, `"subItems"`, and `"items"` keys. Because `extract_references_from_vbrief` is called outside the `try/except (JSONDecodeError, OSError)` block in `scan_vbrief_dir`, any `TypeError` propagates uncaught, crashing the entire script rather than silently skipping the offending file.
Use `or []` to coerce `None` to an empty sequence:
```python
for ref in (plan.get("references") or []):
...
# and in _walk_items:
for ref in (item.get("references") or []):
...
_walk_items(item.get("subItems") or [])
_walk_items(item.get("items") or [])
```
Alternatively, widen the `try/except` in `scan_vbrief_dir` to cover the `extract_references_from_vbrief(data)` call.
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "fix: address Greptile review findings (b..." | Re-trigger Greptile |
- P1: Change fetch_open_issues return type to list[dict] | None; return None on all error paths so the dead None guard in main() correctly exits with code 1 on gh CLI failures - P2: Add warning when fetched issue count hits the limit (200), signaling potentially incomplete data - P2: Fix test count in CHANGELOG entry (22 -> 25)
Closes #322
Summary
Post-cutover GitHub issue reconciliation (Story M, Phase 2 vBRIEF Architecture Cutover).
What changed
scripts/reconcile_issues.py -- Reconciliation script that:
eferences arrays (plan-level, item-level, nested subItems)
tasks/reconcile.yml -- Registered as ask reconcile:issues (not wired to ask check)
Obsoleted issue closure -- Closed fix: strengthen spec validation gate and rendered artifact freshness #115 (strengthen spec validation gate and rendered artifact freshness) with explanatory comment. Entire scope superseded by:
tests/cli/test_reconcile_issues.py -- 25 tests covering reference extraction, issue number parsing, directory scanning, reconciliation logic, output formatting, and CLI integration
CHANGELOG.md -- Entry under [Unreleased]
Pre-existing test failures (not introduced by this PR)
Checklist