Skip to content

feat: add get_release_schedule tool for direct milestone lookup#16

Merged
jeremyeder merged 5 commits intomainfrom
feat/get-release-schedule
Apr 15, 2026
Merged

feat: add get_release_schedule tool for direct milestone lookup#16
jeremyeder merged 5 commits intomainfrom
feat/get-release-schedule

Conversation

@jeremyeder
Copy link
Copy Markdown
Contributor

Summary

  • Add get_release_schedule tool with fuzzy matching on product, version, and milestone type — queries both release_schedule and release_milestone tables, returns past and future dates with status/days_away annotations
  • Fix release_risk_summary to include milestones from the past 30 days via lookback_days parameter (was silently dropping all past milestones)
  • Add smoke tests for the new tool in scripts/test.sh

Problem

Natural-language questions like "when is code freeze for AcmeProduct 1.0?" required 3-4 round trips:

  1. release_risk_summary silently returned nothing (milestone was in the past)
  2. LLM had to discover schema via sqlite_master
  3. LLM had to sample rows to learn column names
  4. Finally write correct SQL manually

Now it's one call: get_release_schedule(product="acme", version="1.0", milestone="code freeze")

Test plan

  • scripts/test.sh passes (16/16, includes 4 new tool smoke tests)
  • get_release_schedule() with no filters returns all milestones
  • get_release_schedule(product="Acme", version="1.0", milestone="code freeze") returns the correct date
  • get_release_schedule(product="nonexistent") returns error with available releases hint
  • release_risk_summary() now includes milestones from the past 30 days
  • MCP server starts without errors (uv run mcp_server.py)
  • ruff check and ruff format --check pass

🤖 Generated with Claude Code

Ambient Code Bot and others added 5 commits April 15, 2026 11:47
Queries both release_schedule and release_milestone tables with
fuzzy matching on product, version, and milestone type. Returns
past and future dates with days_away annotation. Eliminates the
need for schema discovery when asking release date questions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Milestone entries from release_milestone table were missing
status (past/upcoming) and days_away annotations that schedule
entries already had, creating an asymmetry for LLM consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add lookback_days parameter (default 30) so milestones that just
passed are still visible. Previously, a code freeze from yesterday
would silently disappear from results.

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

coderabbitai Bot commented Apr 15, 2026

Walkthrough

Added a new MCP tool get_release_schedule() that fuzzy-matches product, version, and milestone parameters to query release schedule data with status and days-away annotations. Modified release_risk_summary() to accept a configurable lookback_days parameter for milestone filtering. Added corresponding smoke tests.

Changes

Cohort / File(s) Summary
MCP Tool Additions & Modifications
mcp_server.py
Added new get_release_schedule() tool with fuzzy-matching logic against release_schedule and release_milestone tables, annotating results with status and days_away fields; returns JSON with schedule, milestones, counts, and as_of timestamp or error payload. Modified release_risk_summary() to accept lookback_days parameter (default 30) for filtering milestones by age. Added timedelta import.
Tool Smoke Tests
scripts/test.sh
Added new "5b. Tool smoke tests" section testing get_release_schedule() with unfiltered and filtered calls, asserting schedule_count > 0, and testing error response for non-matching product; also validates release_risk_summary() returns JSON with either releases or message field.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: adding a new get_release_schedule tool for direct milestone lookup.
Description check ✅ Passed The description clearly relates to the changeset, explaining the new tool, the bug fix to release_risk_summary, and the test coverage added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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/get-release-schedule

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp_server.py`:
- Around line 522-527: The SQL ORDER BY on raw TEXT (date_start/event_date)
yields incorrect ordering for mixed date formats; after fetching rows from the
query that returns columns like release, task AS milestone, date_start,
date_finish (and similar queries at the other noted blocks), remove the ORDER BY
clause and instead sort the fetched list in Python using the existing
_parse_date() helper (e.g. rows.sort(key=lambda r:
_parse_date(r.get('date_start') or r.get('event_date')) or datetime.min)) so
dates are normalized and correctly ordered; apply the same change to the other
query sites (the blocks around the referenced lines) and ensure you handle
None/parse failures consistently.

In `@scripts/test.sh`:
- Around line 79-85: The smoke test assertion in the Python one-liner that calls
get_release_schedule() is too strict by requiring r.get('schedule_count', 0) >
0; change it to accept either schedule rows or milestone-only results by
asserting (r.get('schedule_count', 0) > 0) or (r.get('milestone_count', 0) > 0).
Update both occurrences that run the Python check (the block using
mcp_server.get_release_schedule()) so the new assertion checks schedule_count OR
milestone_count and keep the same error message or adjust it to reflect "no
schedule or milestone rows".
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 81b8ae8f-ed22-4ac5-9d15-df7576e3e407

📥 Commits

Reviewing files that changed from the base of the PR and between 041b343 and 13c8030.

📒 Files selected for processing (2)
  • mcp_server.py
  • scripts/test.sh

Comment thread mcp_server.py
Comment on lines +520 to +527
sched_where = " AND ".join(sched_conditions) if sched_conditions else "1=1"
sched_rows = conn.execute(
f"""SELECT release, task AS milestone, date_start, date_finish
FROM release_schedule
WHERE {sched_where}
ORDER BY date_start""",
sched_params,
).fetchall()
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

Unbounded result sets can degrade tool latency and memory use.

With no filters, both queries become WHERE 1=1 and return full tables without any cap. This tool should enforce a bounded response like other endpoints that use MAX_QUERY_ROWS.

Proposed fix
-    sched_rows = conn.execute(
+    sched_rows = conn.execute(
         f"""SELECT release, task AS milestone, date_start, date_finish
             FROM release_schedule
             WHERE {sched_where}
-            ORDER BY date_start""",
-        sched_params,
+            ORDER BY date_start
+            LIMIT ?""",
+        [*sched_params, MAX_QUERY_ROWS],
     ).fetchall()
@@
-    mile_rows = conn.execute(
+    mile_rows = conn.execute(
         f"""SELECT product, version, event_type AS milestone, event_date
             FROM release_milestone
             WHERE {mile_where}
-            ORDER BY event_date""",
-        mile_params,
+            ORDER BY event_date
+            LIMIT ?""",
+        [*mile_params, MAX_QUERY_ROWS],
     ).fetchall()

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 542-549

Comment thread mcp_server.py
Comment on lines +522 to +527
f"""SELECT release, task AS milestone, date_start, date_finish
FROM release_schedule
WHERE {sched_where}
ORDER BY date_start""",
sched_params,
).fetchall()
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

Date ordering is currently incorrect for mixed date formats.

ORDER BY date_start/event_date sorts raw TEXT, but this file already acknowledges non-ISO date formats via _parse_date(). That can return misordered results while the tool promises date ordering.

Proposed fix
-    sched_rows = conn.execute(
-        f"""SELECT release, task AS milestone, date_start, date_finish
-            FROM release_schedule
-            WHERE {sched_where}
-            ORDER BY date_start""",
-        sched_params,
-    ).fetchall()
+    sched_rows = conn.execute(
+        f"""SELECT release, task AS milestone, date_start, date_finish
+            FROM release_schedule
+            WHERE {sched_where}""",
+        sched_params,
+    ).fetchall()
@@
-    mile_rows = conn.execute(
-        f"""SELECT product, version, event_type AS milestone, event_date
-            FROM release_milestone
-            WHERE {mile_where}
-            ORDER BY event_date""",
-        mile_params,
-    ).fetchall()
+    mile_rows = conn.execute(
+        f"""SELECT product, version, event_type AS milestone, event_date
+            FROM release_milestone
+            WHERE {mile_where}""",
+        mile_params,
+    ).fetchall()
@@
     for entry in schedule:
         d = entry.get("date_finish") or entry.get("date_start")
+        parsed = _parse_date(d) if d else None
+        entry["_sort_date"] = parsed
-        if d:
-            parsed = _parse_date(d)
-            if parsed:
-                entry["status"] = "past" if parsed < today else "upcoming"
-                entry["days_away"] = (parsed - today).days
+        if parsed:
+            entry["status"] = "past" if parsed < today else "upcoming"
+            entry["days_away"] = (parsed - today).days
@@
     for entry in milestones_list:
         d = entry.get("event_date")
-        if d:
-            parsed = _parse_date(d)
-            if parsed:
-                entry["status"] = "past" if parsed < today else "upcoming"
-                entry["days_away"] = (parsed - today).days
+        parsed = _parse_date(d) if d else None
+        entry["_sort_date"] = parsed
+        if parsed:
+            entry["status"] = "past" if parsed < today else "upcoming"
+            entry["days_away"] = (parsed - today).days
+
+    schedule.sort(key=lambda e: e.get("_sort_date") or date.max)
+    milestones_list.sort(key=lambda e: e.get("_sort_date") or date.max)
+    for e in schedule:
+        e.pop("_sort_date", None)
+    for e in milestones_list:
+        e.pop("_sort_date", None)

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 543-549, 554-570

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp_server.py` around lines 522 - 527, The SQL ORDER BY on raw TEXT
(date_start/event_date) yields incorrect ordering for mixed date formats; after
fetching rows from the query that returns columns like release, task AS
milestone, date_start, date_finish (and similar queries at the other noted
blocks), remove the ORDER BY clause and instead sort the fetched list in Python
using the existing _parse_date() helper (e.g. rows.sort(key=lambda r:
_parse_date(r.get('date_start') or r.get('event_date')) or datetime.min)) so
dates are normalized and correctly ordered; apply the same change to the other
query sites (the blocks around the referenced lines) and ensure you handle
None/parse failures consistently.

Comment thread scripts/test.sh
Comment on lines +79 to +85
run "get_release_schedule(all)" uv run python3 -c "
import json, sys
sys.path.insert(0, '$REPO_ROOT')
import mcp_server
r = json.loads(mcp_server.get_release_schedule())
assert r.get('schedule_count', 0) > 0, 'no schedule rows'
"
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

Smoke tests are too strict: they ignore milestone-only matches.

Both tests require schedule_count > 0, but get_release_schedule() is valid when only milestone_count is non-zero. This can create false CI failures.

Proposed fix
-r = json.loads(mcp_server.get_release_schedule())
-assert r.get('schedule_count', 0) > 0, 'no schedule rows'
+r = json.loads(mcp_server.get_release_schedule())
+assert (r.get('schedule_count', 0) + r.get('milestone_count', 0)) > 0, 'no release rows'
@@
-r = json.loads(mcp_server.get_release_schedule(product='Acme', version='1.0', milestone='freeze'))
-assert r.get('schedule_count', 0) > 0, 'no filtered rows'
+r = json.loads(mcp_server.get_release_schedule(product='Acme', version='1.0', milestone='freeze'))
+assert (r.get('schedule_count', 0) + r.get('milestone_count', 0)) > 0, 'no filtered rows'

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 87-93

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test.sh` around lines 79 - 85, The smoke test assertion in the Python
one-liner that calls get_release_schedule() is too strict by requiring
r.get('schedule_count', 0) > 0; change it to accept either schedule rows or
milestone-only results by asserting (r.get('schedule_count', 0) > 0) or
(r.get('milestone_count', 0) > 0). Update both occurrences that run the Python
check (the block using mcp_server.get_release_schedule()) so the new assertion
checks schedule_count OR milestone_count and keep the same error message or
adjust it to reflect "no schedule or milestone rows".

@jeremyeder jeremyeder merged commit 7813614 into main Apr 15, 2026
6 of 8 checks passed
@jeremyeder jeremyeder deleted the feat/get-release-schedule branch April 15, 2026 18:54
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.

1 participant