Skip to content

Commit 9d63dc6

Browse files
committed
feat(mcp): add lifecycle_state/passed filters and bulk requirements status
- get_all_requirements/svcs now accept lifecycle_state filter (SQL WHERE) - get_all_mvrs now accepts passed filter (SQL WHERE) - list_requirements/svcs thread lifecycle_state through; list_mvrs threads passed - MCP: list_requirements(lifecycle_state?), list_svcs(lifecycle_state?), list_mvrs(passed?) expose new filters - MCP: add get_requirements_status(urn?) — batch status for all requirements with computed meets_requirements, enabling client-side compound filtering (e.g. not done but has impl and at least one passing test) Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
1 parent 5547307 commit 9d63dc6

4 files changed

Lines changed: 86 additions & 24 deletions

File tree

src/reqstool/common/queries/details.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,27 @@ def get_requirement_status(raw_id: str, repo: RequirementsRepository) -> dict |
212212
"test_summary": test_summary,
213213
"meets_requirements": req.implementation.value != "not_implemented" and all_passing,
214214
}
215+
216+
217+
def get_requirements_status_all(repo: RequirementsRepository, urn: str | None = None) -> list[dict]:
218+
"""Batch status for all requirements. Optionally scoped to a URN."""
219+
reqs = repo.get_all_requirements(urn=urn)
220+
result = []
221+
for req in reqs.values():
222+
svc_urn_ids = repo.get_svcs_for_req(req.id)
223+
test_summary = {"passed": 0, "failed": 0, "skipped": 0, "missing": 0}
224+
for svc_uid in svc_urn_ids:
225+
for t in repo.get_test_results_for_svc(svc_uid):
226+
key = t.status.value
227+
if key in test_summary:
228+
test_summary[key] += 1
229+
all_passing = test_summary["failed"] == 0 and test_summary["missing"] == 0
230+
result.append({
231+
"id": req.id.id,
232+
"urn": req.id.urn,
233+
"lifecycle_state": req.lifecycle.state.value,
234+
"implementation": req.implementation.value,
235+
"test_summary": test_summary,
236+
"meets_requirements": req.implementation.value != "not_implemented" and all_passing,
237+
})
238+
return result

src/reqstool/common/queries/list.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,38 @@
44
from reqstool.storage.requirements_repository import RequirementsRepository
55

66

7-
def get_requirements_list(repo: RequirementsRepository, urn: str | None = None) -> list[dict]:
7+
def get_requirements_list(
8+
repo: RequirementsRepository, urn: str | None = None, lifecycle_state: str | None = None
9+
) -> list[dict]:
810
return [
911
{
1012
"id": r.id.id,
1113
"title": r.title,
1214
"lifecycle_state": r.lifecycle.state.value,
1315
}
14-
for r in repo.get_all_requirements(urn=urn).values()
16+
for r in repo.get_all_requirements(urn=urn, lifecycle_state=lifecycle_state).values()
1517
]
1618

1719

18-
def get_svcs_list(repo: RequirementsRepository, urn: str | None = None) -> list[dict]:
20+
def get_svcs_list(repo: RequirementsRepository, urn: str | None = None, lifecycle_state: str | None = None) -> list[dict]:
1921
return [
2022
{
2123
"id": s.id.id,
2224
"title": s.title,
2325
"lifecycle_state": s.lifecycle.state.value,
2426
"verification": s.verification.value,
2527
}
26-
for s in repo.get_all_svcs(urn=urn).values()
28+
for s in repo.get_all_svcs(urn=urn, lifecycle_state=lifecycle_state).values()
2729
]
2830

2931

30-
def get_mvrs_list(repo: RequirementsRepository, urn: str | None = None) -> list[dict]:
32+
def get_mvrs_list(repo: RequirementsRepository, urn: str | None = None, passed: bool | None = None) -> list[dict]:
3133
return [
3234
{
3335
"id": m.id.id,
3436
"passed": m.passed,
3537
}
36-
for m in repo.get_all_mvrs(urn=urn).values()
38+
for m in repo.get_all_mvrs(urn=urn, passed=passed).values()
3739
]
3840

3941

src/reqstool/mcp/server.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
get_mvr_details,
99
get_requirement_details,
1010
get_requirement_status as _get_requirement_status,
11+
get_requirements_status_all as _get_requirements_status_all,
1112
get_svc_details,
1213
get_urn_details as _get_urn_details,
1314
)
@@ -39,9 +40,10 @@ def start_server(location: LocationInterface) -> None: # noqa: C901
3940
mcp = FastMCP("reqstool")
4041

4142
@mcp.tool()
42-
def list_requirements(urn: str | None = None) -> list[dict]:
43-
"""List requirements with id, title, and lifecycle state. Optionally filter by URN."""
44-
return get_requirements_list(repo, urn=urn)
43+
def list_requirements(urn: str | None = None, lifecycle_state: str | None = None) -> list[dict]:
44+
"""List requirements with id, title, and lifecycle state.
45+
Filter by urn and/or lifecycle_state (draft|effective|deprecated|obsolete)."""
46+
return get_requirements_list(repo, urn=urn, lifecycle_state=lifecycle_state)
4547

4648
@mcp.tool()
4749
def get_requirement(id: str) -> dict:
@@ -52,9 +54,17 @@ def get_requirement(id: str) -> dict:
5254
return result
5355

5456
@mcp.tool()
55-
def list_svcs(urn: str | None = None) -> list[dict]:
56-
"""List SVCs with id, title, lifecycle state, and verification type. Optionally filter by URN."""
57-
return get_svcs_list(repo, urn=urn)
57+
def get_requirements_status(urn: str | None = None) -> list[dict]:
58+
"""Batch status for all requirements: id, urn, lifecycle_state, implementation, test_summary,
59+
meets_requirements. Use this to find requirements that are incomplete, partially tested,
60+
or not yet implemented. Optionally filter by URN."""
61+
return _get_requirements_status_all(repo, urn=urn)
62+
63+
@mcp.tool()
64+
def list_svcs(urn: str | None = None, lifecycle_state: str | None = None) -> list[dict]:
65+
"""List SVCs with id, title, lifecycle state, and verification type.
66+
Filter by urn and/or lifecycle_state (draft|effective|deprecated|obsolete)."""
67+
return get_svcs_list(repo, urn=urn, lifecycle_state=lifecycle_state)
5868

5969
@mcp.tool()
6070
def get_svc(id: str) -> dict:
@@ -65,9 +75,9 @@ def get_svc(id: str) -> dict:
6575
return result
6676

6777
@mcp.tool()
68-
def list_mvrs(urn: str | None = None) -> list[dict]:
69-
"""List MVRs with id and passed status. Optionally filter by URN."""
70-
return get_mvrs_list(repo, urn=urn)
78+
def list_mvrs(urn: str | None = None, passed: bool | None = None) -> list[dict]:
79+
"""List MVRs with id and passed status. Filter by urn and/or passed (True|False)."""
80+
return get_mvrs_list(repo, urn=urn, passed=passed)
7181

7282
@mcp.tool()
7383
def get_mvr(id: str) -> dict:

src/reqstool/storage/requirements_repository.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,53 @@ def is_filtered(self) -> bool:
7171

7272
# -- Entity queries --
7373

74-
def get_all_requirements(self, urn: str | None = None) -> dict[UrnId, RequirementData]:
75-
sql = "SELECT * FROM requirements" + (" WHERE urn = ?" if urn else "")
76-
rows = self._db.connection.execute(sql, (urn,) if urn else ()).fetchall()
74+
def get_all_requirements(
75+
self, urn: str | None = None, lifecycle_state: str | None = None
76+
) -> dict[UrnId, RequirementData]:
77+
clauses: list[str] = []
78+
args: list = []
79+
if urn:
80+
clauses.append("urn = ?")
81+
args.append(urn)
82+
if lifecycle_state:
83+
clauses.append("lifecycle_state = ?")
84+
args.append(lifecycle_state)
85+
sql = "SELECT * FROM requirements" + (" WHERE " + " AND ".join(clauses) if clauses else "")
86+
rows = self._db.connection.execute(sql, args).fetchall()
7787
result = {}
7888
for row in rows:
7989
urn_id = UrnId(urn=row["urn"], id=row["id"])
8090
result[urn_id] = self._row_to_requirement_data(row)
8191
return result
8292

83-
def get_all_svcs(self, urn: str | None = None) -> dict[UrnId, SVCData]:
84-
sql = "SELECT * FROM svcs" + (" WHERE urn = ?" if urn else "")
85-
rows = self._db.connection.execute(sql, (urn,) if urn else ()).fetchall()
93+
def get_all_svcs(self, urn: str | None = None, lifecycle_state: str | None = None) -> dict[UrnId, SVCData]:
94+
clauses: list[str] = []
95+
args: list = []
96+
if urn:
97+
clauses.append("urn = ?")
98+
args.append(urn)
99+
if lifecycle_state:
100+
clauses.append("lifecycle_state = ?")
101+
args.append(lifecycle_state)
102+
sql = "SELECT * FROM svcs" + (" WHERE " + " AND ".join(clauses) if clauses else "")
103+
rows = self._db.connection.execute(sql, args).fetchall()
86104
result = {}
87105
for row in rows:
88106
urn_id = UrnId(urn=row["urn"], id=row["id"])
89107
result[urn_id] = self._row_to_svc_data(row)
90108
return result
91109

92-
def get_all_mvrs(self, urn: str | None = None) -> dict[UrnId, MVRData]:
93-
sql = "SELECT * FROM mvrs" + (" WHERE urn = ?" if urn else "")
94-
rows = self._db.connection.execute(sql, (urn,) if urn else ()).fetchall()
110+
def get_all_mvrs(self, urn: str | None = None, passed: bool | None = None) -> dict[UrnId, MVRData]:
111+
clauses: list[str] = []
112+
args: list = []
113+
if urn:
114+
clauses.append("urn = ?")
115+
args.append(urn)
116+
if passed is not None:
117+
clauses.append("passed = ?")
118+
args.append(1 if passed else 0)
119+
sql = "SELECT * FROM mvrs" + (" WHERE " + " AND ".join(clauses) if clauses else "")
120+
rows = self._db.connection.execute(sql, args).fetchall()
95121
result = {}
96122
for row in rows:
97123
urn_id = UrnId(urn=row["urn"], id=row["id"])

0 commit comments

Comments
 (0)