Skip to content

feat(release): v0.2.20 - #244

Merged
dan-petty merged 15 commits into
mainfrom
release/v0.2.20
Sep 19, 2026
Merged

dan-petty merged 15 commits into
mainfrom
release/v0.2.20

Conversation

@dan-petty

@dan-petty dan-petty commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

feat(release): v0.2.20

Summary

Release v0.2.20 tracking PR under GitHub pull request merge controls.

Included Deliverables

Release Notes

Added

  • Forward-Looking Project Management & Automated Roadmap Synchronization (devops_cli.github.projects, devops_cli.github.issues):
    • Implemented forward-looking project management principles in AGENTS.md and automated roadmap synchronization (devops gh issues sync-roadmap) converting uncompleted roadmap deliverables into GitHub Issues and per-task tracking files (docs/agent/tasks/).
    • Hardened documentation compactor (devops docs compact) against truncating or removing scheduled milestones.
  • Automated Pull Request Synchronization & Branch Update Integrations (devops_cli.github.pr_update):
    • Implemented native CLI command devops pr update (with batch --all, optimistic concurrency --expected-head-sha, and --dry-run), FastMCP tool pr_update_branch, and GitHub Actions workflow .github/workflows/update-prs.yml.
  • Fast CI Execution Caching & Git Pre-Commit File Change Tracking (devops_cli.commands.ci):
    • Introduced input-addressed execution caching for devops ci quality gates, reducing unchanged verification from ~3 minutes to sub-second execution with Git working tree change tracking and --no-cache/--force overrides.
  • Universal Subcommand Option Propagation (devops_cli.core.command_options):
    • Ensured universal trailing --dry-run and --explain options across all CLI subcommands with declarative dry-run callbacks and option inheritance.

Performance & Optimization

  • CI Performance Acceleration, Worker Auto-Scaling & Pathological Test Mocking (devops_cli.commands.ci):
    • Accelerated devops ci quality gate pipeline latency by over 75% with dynamic Pytest worker auto-scaling (min(os.cpu_count(), 8)).
    • Mocked unmocked socket probes in Kubernetes context bootstrapping, bounded .venv workspace traversal in repo map generation, and bypassed whole-tree git hashing in CI tests.
    • Eliminated sequential blocking documentation passes with zero-blocking concurrent pipeline dispatch at timestamp 0.

Fixed & Hardened

  • Review Findings Remediation & Defensive Boundary Hardening (devops_cli.ai.rag, devops_cli.sandbox, devops_cli.core.audit):
    • Enforced path traversal guards (validate_no_path_traversal), forbidden system root blocks, symlink rejection, and pre-flight file size caps ($\le 5\text{MB}$) across RAG chunkers, drift analysis, benchmark chunkers, and prompt evaluation.
    • Decomposed complex workspace directory validation in sandbox engine into single-responsibility helpers, enforcing cyclomatic complexity $M \le 4$.
    • Enforced atomic serialized file exports (write_serialized_file) in Kubernetes diagnostics.
    • Hardened None-safe severity handling in review reporting and pipeline stages.
  • Review Verification Prompts & Closed-Loop Feedback Dataset Export (devops_cli.ai.review):
    • Hardened verification prompts (verify_finding_system.md) with falsification rules distinguishing internal loopback services, CLI path logging, and local scope variable grounding from false-positive vulnerabilities.
    • Updated DevSecOps persona instructions and expanded the anti-hallucination catalog (common_hallucinations.json).
    • Closed the self-improvement loop with automated dataset export (devops review export-feedback --status ALL) to .data/feedback_dataset.jsonl.
  • Review Markdown Code Block & Asterisk Formatting (devops_cli.ai.review):
    • Implemented line-start fence delimiter detection in fix and description formatters to prevent inline backticks (e.g. print("```")) from creating malformed Markdown fences.
    • Hardened title and heading rendering by escaping un-backticked asterisks (\*) to prevent Markdown bold collisions with Python unpacking syntax (**kwargs).
    • Conditioned Markdown bold stripping on matched outer pairs (^\s*\*\*(.+)\*\*\s*$), preserving valid variable names like **kwargs.
  • Dynamic Package Version Loader & Pyproject Single Source of Truth (devops_cli.core.version):
    • Refactored devops_cli.__version__ to read directly from pyproject.toml metadata as the single source of truth.
    • Derived generator test fixtures dynamically from devops_cli.__version__ to prevent stale version assertions.
  • PR Copilot Timeline State Parser Robustness (devops_cli.github.pr_monitor):
    • Safely handles JSON array timeline payloads and empty responses ([]) from gh api .../timeline, preventing AttributeError: 'list' object has no attribute 'get' during PR monitoring.

Changed & Improved

  • Release v0.2.20 Branch Initialization:
    • Created release/v0.2.20 tracking branch, bumped package version to 0.2.20, and synchronized documentation across the repository.

Quality Gate Checklist

  • Gated CI Quality Gate passing (devops ci)
  • Documentation and Command Matrix in README.md synchronized
  • Version matching across pyproject.toml and src/devops_cli/__init__.py
  • CodeQL & Static Analysis passing
  • Pre-commit & CI validation passing
  • Milestone deliverables reviewed and merged into release/v0.2.20
  • Final release readiness verified before converting from draft

…t_state (#245) (#246)

* fix(pr): handle JSON array timeline payload in _parse_timeline_copilot_state (#245)

* docs(tasks): update task 245 status to In Review and link PR #246
@dan-petty
dan-petty marked this pull request as ready for review September 18, 2026 14:26
Copilot AI lite review requested due to automatic review settings September 18, 2026 14:26

Copilot AI 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.

🟡 Changes recommended

The single-object parser path needs regression coverage, and release documentation has unresolved scope and roadmap inconsistencies.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR prepares the v0.2.20 release and improves Copilot timeline payload parsing.

Changes:

  • Bumps package and lockfile versions to 0.2.20.
  • Supports JSON arrays, objects, NDJSON, and empty payloads.
  • Adds parser tests and updates release documentation.
File summaries
File Summary Review note
uv.lock Updates locked package version. —
tests/test_review_runner.py Updates release branch fixtures. —
tests/test_github_pr_monitor.py Adds parser regression coverage. —
src/devops_cli/github/pr_monitor.py Supports multiple timeline payload formats. Moderate: Add coverage for a single top-level JSON object.
src/devops_cli/ai/review/runner.py Updates release branch example. —
src/devops_cli/__init__.py Updates runtime version. —
RELEASE_CYCLE.md Updates milestone lifecycle metadata. Nit: Reconcile entries with the canonical roadmap.
pyproject.toml Updates project version. —
docs/agent/tasks/task-245-parse-timeline-copilot-state-json-array.md Records parser task scope and status. —
CHANGELOG.md Adds v0.2.20 release notes. Nit: Include the shipped parser fix in the release PR description.
Review details

Suppressed comments (2)

CHANGELOG.md:12

  • The v0.2.20 changelog now includes the PR Copilot timeline parser fix, but the PR description's Release Notes section only lists branch initialization. Please update or regenerate the release PR body to include this shipped fix so the stated release scope matches the contents being released.
- **PR Copilot Timeline State Parser Robustness (`devops_cli.github.pr_monitor`)**:
  - Safely handles JSON array timeline payloads and empty responses (`[]`) from `gh api .../timeline`, preventing `AttributeError: 'list' object has no attribute 'get'` during PR monitoring.

src/devops_cli/github/pr_monitor.py:271

  • The new extraction path explicitly handles a single top-level JSON object, but the added tests cover only arrays, NDJSON, and empty input. Add a regression case such as {"event": "copilot_work_started"} so a future regression in this distinct object path cannot pass the suite despite the task's promised dict-payload support.
    top_dicts = _safe_json_to_dicts(stripped)
    return top_dicts if top_dicts else _parse_ndjson_dicts(stripped)
  • Files reviewed: 9/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread RELEASE_CYCLE.md
…bad patterns (#247)

* docs(agents): add roadmap ingestion rules for missing parameters and bad patterns

* fix(agents): gate devops roadmap governance block on is_devops_cli
Copilot AI review requested due to automatic review settings September 18, 2026 16:28

Copilot AI 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.

🟡 Changes recommended

Unresolved moderate Markdown-formatting defects and release-documentation inconsistencies remain.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (12)

CHANGELOG.md:16

  • The v0.2.20 release notes mention only the timeline parser and branch/version initialization, but this PR also changes review-report rendering, finding sanitization, and generated AGENTS.md governance (including task 250). That makes the release description incomplete and prevents consumers from understanding the behavioral changes shipped in this version; add those changes to the release notes or split them from the release-only PR.
### Fixed & Hardened
- **PR Copilot Timeline State Parser Robustness (`devops_cli.github.pr_monitor`)**:
  - Safely handles JSON array timeline payloads and empty responses (`[]`) from `gh api .../timeline`, preventing `AttributeError: 'list' object has no attribute 'get'` during PR monitoring.

### Changed & Improved
- **Release v0.2.20 Branch Initialization**:
  - Created `release/v0.2.20` tracking branch, bumped package version to `0.2.20`, and synchronized documentation across the repository.

RELEASE_CYCLE.md:220

  • This release-cycle update makes v0.2.20 the active milestone, but the canonical roadmap still marks the entire v0.2.0 – v0.2.23 range completed and has no active v0.2.20 milestone section (docs/ROADMAP.md:36-44). The release-cycle and roadmap documents therefore contradict the PR's claim that release documentation is synchronized; update the canonical milestone state as part of this release or keep this line aligned with it.
- **Current Active Development**: Milestone `v0.2.20`.

src/devops_cli/ai/review/pipeline.py:2125

  • This has the same false-positive fence detection as _format_markdown_fix: a description containing a literal triple-backtick sequence can get an extra closing fence even though it has no open Markdown block. Inspect actual fence lines rather than counting substrings before appending a closer.
        if clean_desc.count("```") % 2 != 0:
            clean_desc += "\n```"

src/devops_cli/ai/review/pipeline.py:2102

  • The new formatter repeats the same unconditional leading-asterisk removal. Calling the exported format_markdown_fix with a legitimate fix beginning **kwargs changes the code before it is rendered, so the recommendation no longer describes the original Python operation. Only remove a paired outer marker (or escape literal asterisks).
        clean_fix = re.sub(r"^\s*\*\*\s*", "", clean_fix)
        clean_fix = re.sub(r"\s*\*\*\s*$", "", clean_fix)

src/devops_cli/ai/review/pipeline.py:2121

  • Descriptions are subjected to the same unconditional ** stripping. A description whose first line is a code fragment such as **kwargs is silently changed to kwargs, which conflicts with the formatter's goal of preserving finding content. Make this cleanup pair-aware instead of treating every leading double-asterisk as formatting.
        clean_desc = re.sub(r"^\s*\*\*\s*", "", clean_desc)
        clean_desc = re.sub(r"\s*\*\*\s*$", "", clean_desc)

src/devops_cli/ai/review/pipeline.py:2149

  • The consolidated findings table escapes pipes and newlines but leaves title asterisks untouched. Titles containing **kwargs or other Markdown markers can therefore render with unintended emphasis in the table, despite this PR's stated Markdown collision hardening. Escape literal asterisks in clean_title before building the row.
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()
            if clean_title.count("`") % 2 != 0:
                clean_title += "`"

src/devops_cli/ai/review/pipeline.py:2149

  • This table escapes pipes and newlines but leaves angle-bracket placeholders untouched. A finding title such as Syntax error: unquoted placeholder <masked-secret> is then emitted as an HTML-like tag and may not render literally, despite task 250's stated requirement to escape <token>/<digest> values. Use one Markdown text-escaping helper for this table and the other report renderers, including detailed headings.
            clean_sev = f.severity.replace("|", "\\|").replace("\n", " ").strip()
            clean_loc = f.location.strip("`").replace("|", "\\|").replace("\n", " ").strip()
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()
            if clean_title.count("`") % 2 != 0:
                clean_title += "`"

src/devops_cli/ai/review/pipeline.py:2147

  • Escaping only pipe characters breaks titles that already contain an escaped pipe. For input bad \\| title, this produces two backslashes before the pipe; Markdown consumes the first pair as a literal backslash and treats the pipe as a column delimiter, violating the table invariant. Escape backslashes before pipes, as the existing Markdown renderer does in src/devops_cli/ai/review/runner.py:538-539.
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()

src/devops_cli/ai/review/stages/reporting.py:40

  • The new delimiter requires whitespace on both sides of :, so ordinary finding titles such as Syntax error: missing ... (used throughout the existing review fixtures) no longer reduce to the canonical Syntax error theme. That changes grouping and makes the executive summary produce one theme per full sentence; allow the normal word: description form while still preserving URL colons.
    parts = re.split(r"\s+[-—:]\s+|\s*\(", candidate)

src/devops_cli/ai/review/stages/reporting.py:245

  • The stage-level review_report.md builder has the same gap: clean_title only escapes pipes and newlines, so <masked-secret>-style finding text is rendered as raw HTML-like markup instead of literal text. Apply the shared Markdown escaping used by the consolidated report here as well.
            clean_sev = f.severity.replace("|", "\\|").replace("\n", " ").strip()
            clean_loc = f.location.strip("`").replace("|", "\\|").replace("\n", " ").strip()
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()
            if clean_title.count("`") % 2 != 0:
                clean_title += "`"
            clean_status = f.status.replace("|", "\\|").replace("\n", " ").strip()
            md_lines.append(
                f"| {idx} | {clean_sev} | `{clean_loc}` | {clean_title} | {clean_status} |"

src/devops_cli/ai/review/stages/reporting.py:240

  • This table path has the same pre-escaped-pipe failure: replacing | without first escaping \\ turns an input such as bad \\| title into a Markdown delimiter after parsing. Normalize backslashes before pipe escaping here as well, matching src/devops_cli/ai/review/runner.py:538-539, otherwise the new table-column invariant is not guaranteed.
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()

src/devops_cli/ai/review_schema.py:87

  • This new exact-marker tuple is a filtering collection embedded in review_schema.py, while the repository instructions require all matching/filtering string collections to live in audited constants/defaults (AGENTS.md:22-24). Move it to a named configuration constant and reuse it across these branches so the sanitization vocabulary is centrally maintained.
            if str(item).strip() and str(item).strip() not in ("**", "*", "---")
  • Files reviewed: 19/20 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread src/devops_cli/ai/review/pipeline.py Outdated
Comment thread src/devops_cli/ai/review/pipeline.py Outdated
Comment thread src/devops_cli/ai/review_schema.py Outdated
…ion (#248) (#249)

* refactor(core): use pyproject.toml as single source of truth for version (#248)

* fix(release): require __version__ presence in init before dynamic fallback (#249)

* docs(roadmap): add universal subcommand option propagation task
Copilot AI review requested due to automatic review settings September 18, 2026 16:43

Copilot AI 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.

🟡 Changes recommended

Unresolved moderate Markdown and version-validation findings remain, along with release documentation and task-status updates.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (14)

Previously missed (3) — in code that hasn't changed since the last review.

src/devops_cli/ai/review/pipeline.py:2125

  • The description formatter repeats the same fence-length problem: an unclosed four-backtick fence gets a three-backtick terminator, so the generated Markdown remains structurally unclosed even though the substring count is even. Use the detected opening fence length when balancing descriptions as well.
    src/devops_cli/commands/release.py:111
  • These checks treat any occurrence of __version__ as an assignment. A target whose __init__.py merely mentions __version__ in a docstring or comment is therefore reported as matching pyproject.toml even though it exports no version; anchor both checks to an actual assignment line before falling back to the project version.

This issue also appears on line 295 of the same file.
src/devops_cli/ai/review_schema.py:88

  • These new exact marker tuples are inline filtering data and the same set is duplicated below. AGENTS.md:272-275 requires string collections used for matching or filtering to live in config/constants.py or config/defaults.py; move this marker set to a named constant and reuse it in both branches.

docs/agent/tasks/task-245-parse-timeline-copilot-state-json-array.md:5

  • This task record is stale relative to the repository lifecycle: PR #246 is already merged and issue #245 is closed, but the file remains In Review. AGENTS.md:142-148 defines Done for a merged PR; update this record (and synchronize the project card) before treating the v0.2.20 deliverable as complete.
**Status**: In Review

docs/agent/tasks/task-248-pyproject-version-single-source.md:5

  • PR #249 is already merged, but this task remains In Review. That contradicts the lifecycle in AGENTS.md:142-148, where a merged PR must be tracked as Done; synchronize this task and its still-open issue/project card before release readiness is declared.
**Status**: In Review

docs/agent/tasks/task-250-remediate-review-markdown-formatting.md:5

  • This task still says PR: None (Draft pending) and In Progress, but issue #250 is closed and the current release PR #244 is already open and not a draft. The task is therefore disconnected from the change and violates the required PR/status synchronization; link it to the current PR and move it to In Review (or correct the external state before release).
**PR**: None (Draft pending)
**Status**: In Progress

src/devops_cli/ai/review/pipeline.py:2109

  • Counting literal ``` substrings and always appending exactly three backticks does not balance longer Markdown fences. For an unclosed four-backtick fence, this adds a three-backtick closer that CommonMark does not recognize as its closing fence, while the parity test still passes; preserve the opener length (or parse fence runs) when closing it.
        fence_count = clean_fix.count("```")
        if fence_count > 0:
            if fence_count % 2 != 0:
                clean_fix += "\n```"

src/devops_cli/ai/review/pipeline.py:2121

  • This cleanup only removes ** at the beginning or end of the entire description. An LLM header such as ** Description from LLM **: leaves the second marker before the colon, so the generated - **Description** item contains an unmatched emphasis span and can affect the rest of the report. Normalize or escape stray emphasis markers before concatenating the report label, and assert this representative case.
        clean_desc = description.strip()
        clean_desc = re.sub(r"^\s*\*\*\s*", "", clean_desc)
        clean_desc = re.sub(r"\s*\*\*\s*$", "", clean_desc)

src/devops_cli/ai/review/pipeline.py:2149

  • The new table sanitization removes pipes and newlines but leaves Markdown emphasis markers untouched. A valid finding title such as Unused **kwargs in resolve_stage_flags is emitted with an unmatched **, which can change the rendering of the rest of the row; escape Markdown asterisks in the title before inserting it into the table.
            clean_title = f.title.replace("|", "\\|").replace("\n", " ").strip()
            if clean_title.count("`") % 2 != 0:
                clean_title += "`"

src/devops_cli/ai/review/pipeline.py:2167

  • Detailed finding headings still insert titles containing raw Markdown emphasis. The Unused **kwargs... case covered by the new theme test will render as an unclosed bold span in this heading because only backticks are balanced here; escape asterisks (or render the title as literal code) before constructing the heading.
            clean_title = f.title.replace("\n", " ").strip()
            if clean_title.count("`") % 2 != 0:
                clean_title += "`"

src/devops_cli/ai/review/stages/reporting.py:40

  • The \s*\( alternative still splits on every opening parenthesis, including parentheses inside inline-code/function calls. For example, a title such as Potential foo(bar) issue is truncated to Potential foo`, then the backtick-balancing code changes the theme and grouping. Delimiters should be recognized only outside backtick/bracket spans (or narrowed to an actual parenthetical suffix).
    parts = re.split(r"\s+[-—:]\s+|\s*\(", candidate)

src/devops_cli/ai/review/stages/reporting.py:111

  • When a title already contains an inline backtick, this branch emits the title raw. A title containing inline code plus a literal parameter such as **kwargs therefore leaves an unmatched bold marker in the executive-summary line, which can corrupt the remaining Markdown. Escape/sanitize emphasis markers outside code spans or render the representative title with a safe delimiter.
        if "`" in rep.title:
            rep_title_str = rep.title
            if rep_title_str.count("`") % 2 != 0:
                rep_title_str += "`"
        else:
            rep_title_str = f"`{rep.title}`"

src/devops_cli/ai/review_schema.py:197

  • These cleanup expressions only remove ** at the absolute start/end of the full value. The new invariant fixture ** Description from LLM **:\n... therefore retains **:; _format_markdown_description then emits an unmatched bold marker into review.md. Normalize bold header markers with an optional trailing colon, or escape them, before returning the sanitized value.
    # Strip leading/trailing stray asterisks or markdown bold markers
    val = re.sub(r"^\s*\*\*\s*", "", val)
    val = re.sub(r"\s*\*\*\s*$", "", val)

    return val.strip()

src/devops_cli/commands/release.py:297

  • The new presence check has the same false positive in the updater: a comment or docstring containing __version__ makes this return True without updating any version, and the following unanchored literal regex can rewrite a comment instead of a real assignment. Detect an actual assignment line first and only then treat a non-literal assignment as dynamic.
    if "__version__" not in content:
        return False
    match = re.search(r'(__version__\s*=\s*["\'])[^"\']+(["\'])', content)
  • Files reviewed: 24/25 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread CHANGELOG.md
Comment thread tests/test_instruction_generator.py
Copilot AI review requested due to automatic review settings September 18, 2026 17:14

Copilot AI 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.

🔵 Needs a closer look

Unresolved moderate findings remain in dry-run propagation, Markdown handling, instruction scoping, version validation, tests, and release documentation.

Review details

Suppressed comments (12)

Previously missed (1) — in code that hasn't changed since the last review.

src/devops_cli/commands/release.py:111

  • Checking only for the __version__ substring treats comments or docstrings as a valid version declaration. A package whose __init__.py merely mentions __version__ will therefore be reported as matching pyproject.toml even though it has no runtime version, allowing release verification to pass incorrectly. Detect an actual assignment (including dynamic assignments such as __version__ = get_version()) instead.

This issue also appears on line 295 of the same file.

CHANGELOG.md:16

  • The new v0.2.20 changelog section records only the timeline parser and branch initialization, while this release diff also ships user-visible changes for dynamic version sourcing, review Markdown rendering, and trailing repos --dry-run. Since the section is the release's stated list of changed/improved behavior, add entries for the other shipped changes or remove them from this release diff before publishing.
### Changed & Improved
- **Release v0.2.20 Branch Initialization**:
  - Created `release/v0.2.20` tracking branch, bumped package version to `0.2.20`, and synchronized documentation across the repository.

docs/agent/tasks/task-252-universal-trailing-dry-run-propagation.md:21

  • The task's deliverables require preserving the existing dry-run state in main.py and teaching _lazy_proxy to detect and strip trailing --dry-run, but neither of those implementation changes is present. main() still calls set_dry_run(dry_run) with False for a trailing flag, and _lazy_proxy still only checks is_dry_run() before forwarding the flag to the target app. As a result, trailing dry-run remains unsupported for delegated commands that lack their own option, so this task/release is incomplete.
1. **Preserve Global Dry-Run Mode in `src/devops_cli/main.py`**:
   - Ensure the `@app.callback()` in `main.py` only sets `set_dry_run(True)` when `dry_run` is truthy, never overwriting active state set by `entry.py` or environment variables.
2. **Detect & Propagate Trailing `--dry-run` in `src/devops_cli/core/cli.py`**:
   - Update `_lazy_proxy` in `OTelTyper.add_typer` to check `is_dry_run() or "--dry-run" in ctx.args`.
   - When active, strip `--dry-run` from `args`, activate `set_dry_run(True)`, and execute simulated command execution cleanly.

src/devops_cli/ai/instruction_generator.py:320

  • Gating only the newly added roadmap block does not make generated instructions target-agnostic: the same f-string still unconditionally emits DevOps-specific PR monitoring and GitHub Projects/roadmap governance later in this template (including devops pr, .github/project-template.json, and devops gh project sync). For ProjectMetadata.is_devops_cli == False, those policies still leak into arbitrary repositories; move the entire DevOps-only governance section behind the same gate and test for these strings too.
{devops_roadmap_governance_block}- **Continuous Interaction & Collaborative Value Improvement (Proactive Improvement Suggestions)**:

src/devops_cli/ai/review/pipeline.py:2110

  • Counting the literal substring ``` does not account for fence width. An unclosed four-backtick fence is counted as one and is closed with three backticks here, which CommonMark does not recognize as a matching close, so later report sections can still be swallowed. Balance contiguous fence runs using the opener's width (and validate mismatched existing widths) rather than parity of a substring count.
        fence_count = clean_fix.count("```")
        if fence_count > 0:
            if fence_count % 2 != 0:
                clean_fix += "\n```"
            return f"- **Fix Recommendation**:\n\n{clean_fix}"

src/devops_cli/ai/review/pipeline.py:2102

  • The formatter repeats the same destructive stripping for raw fix text: a fix beginning with the valid Python expression **kwargs loses the operator before it is placed in the code fence. Strip only balanced outer bold markers, not every leading/trailing double-asterisk sequence.
        clean_fix = fix.strip()
        clean_fix = re.sub(r"^\s*\*\*\s*", "", clean_fix)
        clean_fix = re.sub(r"\s*\*\*\s*$", "", clean_fix)

src/devops_cli/ai/review_schema.py:195

  • These new regexes remove any leading ** without requiring a matching closing marker, so a legitimate finding that starts with Python's **kwargs is rewritten to kwargs. That corrupts the finding title/body rather than merely removing Markdown bold syntax; only strip paired bold markers (or otherwise require a matching closing **).
    # Strip leading/trailing stray asterisks or markdown bold markers
    val = re.sub(r"^\s*\*\*\s*", "", val)
    val = re.sub(r"\s*\*\*\s*$", "", val)

src/devops_cli/commands/release.py:300

  • This updater has the same false-positive substring check: a file that only mentions __version__ in a comment or docstring returns True without changing anything, and the release command then reports that the init version was updated. Check for an actual module assignment before returning success, and return False when no version attribute exists.
    if "__version__" not in content:
        return False
    match = re.search(r'(__version__\s*=\s*["\'])[^"\']+(["\'])', content)
    if not match:
        # Dynamically derived from pyproject.toml (e.g. __version__ = get_version())
        return True

src/devops_cli/commands/repos.py:330

  • Adding --dry-run to this leaf only fixes repos sync; the universal trailing-flag path described by this task is still broken. OTelTyper._lazy_proxy only checks is_dry_run() (src/devops_cli/core/cli.py:123-136), while the main callback resets the state to False for a trailing flag (src/devops_cli/main.py:191-200), so commands without their own option (for example devops tf apply --dry-run) still delegate the flag to Click and fail instead of rendering a dry-run. Implement the proxy/state propagation and cover a command without an explicit option before treating this as universal.
    dry_run: Annotated[bool, typer.Option("--dry-run", help=HELP.options.dry_run)] = False,

tests/test_all_commands_help_dryrun.py:100

  • The new check inspects only the top-level Click group returned for each app; it never traverses click_cmd.commands. A registered nested command with add_help_option=False would therefore still make this test pass, despite the test claiming every registered subcommand has help enabled. Recursively inspect each registered command (or enumerate the command tree) before asserting the invariant.
        click_cmd = typer.main.get_command(app)
        has_help = getattr(click_cmd, "add_help_option", True)
        if not has_help:
            all_have_help = False
            break

tests/test_instruction_generator.py:340

  • This new devops-specific fixture still hardcodes 0.2.20, even though this release establishes pyproject.toml as the version source of truth. Every subsequent release will require editing this test even though the version is not part of the behavior under test; derive it from the package/project metadata instead.
        version="0.2.20",

tests/test_main_dry_run.py:41

  • This test asserts the old broken contract: it calls the Typer app directly, expects _delegate to receive the trailing flag, and never verifies simulated output or state activation. Task 252's stated behavior is to detect trailing --dry-run in the lazy proxy, strip it, and avoid delegation/mutation; exercising only this direct path lets the unchanged entry.py/main.py reset bug and missing proxy handling pass unnoticed. Test the real entry path and assert dry-run output with no delegate call.
    result = runner.invoke(main_module.app, ["repos", "sync", "--dry-run"])

    assert (result.exit_code, delegated_args) == (0, ["sync", "--dry-run"])
  • Files reviewed: 30/31 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

… compactor hardening & issue sync (#254) (#255)

* feat(pm): forward-looking project management, roadmap reconstruction, compactor hardening & issue sync (#254)

* docs(agent): link PR #255 to task-254

* fix(github): handle remote issue query failures gracefully in roadmap sync (#254)
Copilot AI review requested due to automatic review settings September 18, 2026 17:54

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…nge tracking integration (#256) (#257)

* feat(ci): fast execution caching when unchanged & pre-commit file change tracking integration (#256)

* docs(agent): link PR #257 to task-256
Copilot AI review requested due to automatic review settings September 18, 2026 18:47

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

)

* feat(pr): add integrations to keep pull requests up to date (#258)

* docs(agent): link PR #259 to task-258

* fix(ci): address actionlint shellcheck warning and add shellcheck to devcontainer

* docs(roadmap): prune completed items from Value vs. Effort Prioritization Matrix
Copilot AI review requested due to automatic review settings September 18, 2026 19:35

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…test hotspot isolation, and zero-blocking dispatch (#260) (#261)

* docs(roadmap): add v0.2.24 milestone for Vibes architectural alignment

- POSIX Process Group Sandbox Enforcement (Obs 05)
- Structural Pre-Commit Hook Inversion (Systems Obs 08)
- Anti-Brittle Constant Elimination (Obs 06)
- Semantic Validator Deprecation & Structural Positional Oracles (Obs 17)

* feat(ci): accelerate devops ci performance with worker auto-scaling, test hotspot isolation, and zero-blocking dispatch (#260)
Copilot AI review requested due to automatic review settings September 18, 2026 20:27

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… and improve review feedback loop (#262) (#263)

* fix(review): remediate session findings, harden defensive boundaries, and improve review feedback loop (#262)

* docs(agent): link PR #263 and update status to In Review
Copilot AI review requested due to automatic review settings September 18, 2026 22:34

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 18, 2026 22:50

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…terisk parsing, and update release notes (#265) (#266)

* fix(review): resolve PR #244 review threads, harden markdown fence/asterisk parsing, and update release notes (#265)

* docs(agent): link PR #266 and set status to In Review
Copilot AI review requested due to automatic review settings September 18, 2026 23:24

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…) (#268)

* refactor(core): replace inconsistent N-Gate references with Gated (#267)

* docs(agent): link PR #268 in task-267 tracking
Copilot AI review requested due to automatic review settings September 19, 2026 00:31

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 19, 2026 00:54

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dan-petty
dan-petty marked this pull request as draft September 19, 2026 00:59
@dan-petty
dan-petty marked this pull request as ready for review September 19, 2026 01:02
@dan-petty
dan-petty merged commit c5b8989 into main Sep 19, 2026
7 checks passed
@dan-petty
dan-petty deleted the release/v0.2.20 branch September 19, 2026 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Release pull requests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants