gitlab-api 26.2.0__tar.gz → 26.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {gitlab_api-26.2.0/gitlab_api.egg-info → gitlab_api-26.4.0}/PKG-INFO +5 -4
  2. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/README.md +3 -2
  3. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/agent_server.py +1 -1
  4. gitlab_api-26.4.0/gitlab_api/kg_ingest.py +269 -0
  5. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp_server.py +65 -1
  6. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/ontology/gitlab.ttl +59 -1
  7. {gitlab_api-26.2.0 → gitlab_api-26.4.0/gitlab_api.egg-info}/PKG-INFO +5 -4
  8. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api.egg-info/requires.txt +1 -1
  9. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/pyproject.toml +2 -2
  10. gitlab_api-26.4.0/tests/test_kg_ingest.py +182 -0
  11. gitlab_api-26.2.0/gitlab_api/kg_ingest.py +0 -144
  12. gitlab_api-26.2.0/tests/test_kg_ingest.py +0 -93
  13. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/LICENSE +0 -0
  14. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/MANIFEST.in +0 -0
  15. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/__init__.py +0 -0
  16. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/__main__.py +0 -0
  17. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/__init__.py +0 -0
  18. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_base.py +0 -0
  19. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_environments.py +0 -0
  20. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_issues.py +0 -0
  21. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_merge_requests.py +0 -0
  22. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_other.py +0 -0
  23. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_pipelines.py +0 -0
  24. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_projects.py +0 -0
  25. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_repositories.py +0 -0
  26. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_system.py +0 -0
  27. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_users_groups.py +0 -0
  28. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api/api_client_vulnerabilities.py +0 -0
  29. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/api_client.py +0 -0
  30. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/auth.py +0 -0
  31. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/connectors/__init__.py +0 -0
  32. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/connectors/mcp_source_presets.json +0 -0
  33. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/gitlab_gql.py +0 -0
  34. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/gitlab_input_models.py +0 -0
  35. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/gitlab_response_models.py +0 -0
  36. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/instances.py +0 -0
  37. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/main_agent.json +0 -0
  38. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/__init__.py +0 -0
  39. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_branches.py +0 -0
  40. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_commits.py +0 -0
  41. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_custom_api.py +0 -0
  42. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_deploy_tokens.py +0 -0
  43. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_environments.py +0 -0
  44. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_epics.py +0 -0
  45. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_graphql.py +0 -0
  46. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_graphql_ops.py +0 -0
  47. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_groups.py +0 -0
  48. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_issues.py +0 -0
  49. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_jobs.py +0 -0
  50. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_labels.py +0 -0
  51. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_members.py +0 -0
  52. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_merge_requests.py +0 -0
  53. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_merge_rules.py +0 -0
  54. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_milestones.py +0 -0
  55. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_misc.py +0 -0
  56. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_namespaces.py +0 -0
  57. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_notes.py +0 -0
  58. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_packages.py +0 -0
  59. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_pipeline_schedules.py +0 -0
  60. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_pipelines.py +0 -0
  61. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_projects.py +0 -0
  62. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_protected_branches.py +0 -0
  63. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_releases.py +0 -0
  64. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_runners.py +0 -0
  65. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_snippets.py +0 -0
  66. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_tags.py +0 -0
  67. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_users.py +0 -0
  68. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_vulnerabilities.py +0 -0
  69. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp/mcp_wiki.py +0 -0
  70. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/mcp_config.json +0 -0
  71. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/ontology/__init__.py +0 -0
  72. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/prompts/__init__.py +0 -0
  73. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/prompts/devops_specialist.json +0 -0
  74. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/prompts/main_agent.json +0 -0
  75. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/__init__.py +0 -0
  76. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/gitlab-issues/SKILL.md +0 -0
  77. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/gitlab-merge-request-create/SKILL.md +0 -0
  78. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/gitlab-merge-request-review/SKILL.md +0 -0
  79. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/gitlab-pipelines/SKILL.md +0 -0
  80. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api/skills/gitlab-vulnerabilities/SKILL.md +0 -0
  81. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api.egg-info/SOURCES.txt +0 -0
  82. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api.egg-info/dependency_links.txt +0 -0
  83. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api.egg-info/entry_points.txt +0 -0
  84. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/gitlab_api.egg-info/top_level.txt +0 -0
  85. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/requirements.txt +0 -0
  86. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/scripts/security_sanitizer.py +0 -0
  87. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/scripts/validate_a2a_agent.py +0 -0
  88. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/scripts/validate_agent.py +0 -0
  89. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/scripts/verify_api_integration.py +0 -0
  90. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/setup.cfg +0 -0
  91. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/__init__.py +0 -0
  92. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/conftest.py +0 -0
  93. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_action_discovery.py +0 -0
  94. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_api_wrapper.py +0 -0
  95. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_auth.py +0 -0
  96. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_concept_parity.py +0 -0
  97. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_gitlab_a2a_validation.py +0 -0
  98. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_gitlab_api_brute_force_coverage.py +0 -0
  99. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_gitlab_gql.py +0 -0
  100. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_gitlab_mcp_validation.py +0 -0
  101. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_gitlab_models.py +0 -0
  102. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_init_dynamics.py +0 -0
  103. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_instances.py +0 -0
  104. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_mock_coverage.py +0 -0
  105. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_startup.py +0 -0
  106. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_tool_modes.py +0 -0
  107. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/test_verify_agent.py +0 -0
  108. {gitlab_api-26.2.0 → gitlab_api-26.4.0}/tests/verify_a2a_queries.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlab-api
3
- Version: 26.2.0
3
+ Version: 26.4.0
4
4
  Summary: GitLab API + MCP Server + A2A Server
5
5
  Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
6
  License: MIT
@@ -22,7 +22,7 @@ Requires-Dist: agent-utilities[agent,logfire]>=1.0.0; extra == "agent"
22
22
  Provides-Extra: gql
23
23
  Requires-Dist: gql[requests]>=4.0.0; extra == "gql"
24
24
  Provides-Extra: all
25
- Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=26.2.0; extra == "all"
25
+ Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=26.4.0; extra == "all"
26
26
  Provides-Extra: test
27
27
  Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
28
28
  Requires-Dist: pytest; extra == "test"
@@ -52,7 +52,7 @@ Dynamic: license-file
52
52
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
53
53
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
54
54
 
55
- *Version: 26.2.0*
55
+ *Version: 26.4.0*
56
56
 
57
57
  > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
58
58
  > interfaces, the integrated A2A agent server, and guidance for provisioning the
@@ -116,6 +116,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
116
116
  | `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
117
117
  | `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
118
118
  | `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
119
+ | `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into epistemic-graph. |
119
120
  | `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into epistemic-graph as typed :Project nodes. |
120
121
  | `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
121
122
  | `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
@@ -346,7 +347,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
346
347
 
347
348
  </details>
348
349
 
349
- _33 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
350
+ _34 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
350
351
  <!-- MCP-TOOLS-TABLE:END -->
351
352
 
352
353
  Detailed tool schemas, parameter shapes, and validation constraints are preserved in [docs/mcp.md](docs/mcp.md).
@@ -20,7 +20,7 @@
20
20
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
21
21
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
22
22
 
23
- *Version: 26.2.0*
23
+ *Version: 26.4.0*
24
24
 
25
25
  > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
26
26
  > interfaces, the integrated A2A agent server, and guidance for provisioning the
@@ -84,6 +84,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
84
84
  | `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
85
85
  | `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
86
86
  | `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
87
+ | `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into epistemic-graph. |
87
88
  | `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into epistemic-graph as typed :Project nodes. |
88
89
  | `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
89
90
  | `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
@@ -314,7 +315,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
314
315
 
315
316
  </details>
316
317
 
317
- _33 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
318
+ _34 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
318
319
  <!-- MCP-TOOLS-TABLE:END -->
319
320
 
320
321
  Detailed tool schemas, parameter shapes, and validation constraints are preserved in [docs/mcp.md](docs/mcp.md).
@@ -4,7 +4,7 @@ import os
4
4
  import sys
5
5
  import warnings
6
6
 
7
- __version__ = "26.2.0"
7
+ __version__ = "26.4.0"
8
8
 
9
9
  logging.basicConfig(
10
10
  level=logging.INFO,
@@ -0,0 +1,269 @@
1
+ """Native epistemic-graph ingestion for GitLab records (typed graph nodes).
2
+
3
+ CONCEPT:AU-KG.ingest.enterprise-source-extractor. This is the record-source twin of
4
+ media-downloader's blob ingestion: the package natively pushes its data into the
5
+ epistemic-graph knowledge graph as **typed OWL nodes** (`:Project`, `:GitLabGroup`,
6
+ `:MergeRequest`, `:Issue`, …) + links, using the lightweight engine client
7
+ (``GraphComputeEngine()._client`` + ``txn``) — the same fast client the blob
8
+ ``MediaStore`` uses, NOT the heavy in-process ingestion engine.
9
+
10
+ Entirely best-effort and dependency-/engine-guarded: with no agent-utilities KG stack
11
+ or no reachable engine, every entry point **no-ops** (returns ``None``), so the
12
+ connector keeps working with zero KG infrastructure. Nodes carry the shared provenance
13
+ (``domain``/``source``) and match the classes federated by ``gitlab_api.ontology``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from typing import Any
20
+
21
+ logger = logging.getLogger("gitlab_api.kg")
22
+
23
+ _SOURCE = "gitlab-api"
24
+ _DOMAIN = "gitlab"
25
+
26
+
27
+ def _client() -> tuple[Any | None, str]:
28
+ """Return ``(engine_client, graph_name)`` or ``(None, "")`` when unavailable."""
29
+ try:
30
+ from agent_utilities.knowledge_graph.core.graph_compute import (
31
+ GraphComputeEngine,
32
+ )
33
+ except Exception as e: # noqa: BLE001 — KG stack absent
34
+ logger.debug("KG ingest unavailable (import): %s", e)
35
+ return None, ""
36
+ try:
37
+ engine = GraphComputeEngine()
38
+ client = getattr(engine, "_client", None)
39
+ if client is None:
40
+ return None, ""
41
+ graph = getattr(engine, "graph_name", None) or "__commons__"
42
+ return client, graph
43
+ except Exception as e: # noqa: BLE001 — engine unreachable
44
+ logger.debug("KG ingest: engine unreachable: %s", e)
45
+ return None, ""
46
+
47
+
48
+ def ingest_entities(
49
+ entities: list[dict[str, Any]],
50
+ relationships: list[dict[str, Any]] | None = None,
51
+ *,
52
+ client: Any | None = None,
53
+ graph: str | None = None,
54
+ ) -> dict[str, int] | None:
55
+ """Write typed nodes (+ edges) into epistemic-graph via the fast engine client.
56
+
57
+ ``entities``: ``[{"id":..., "type":..., ...props}]``.
58
+ ``relationships``: ``[{"source":id, "target":id, "type":rel}]``.
59
+ Returns ``{"nodes":n, "edges":m}`` or ``None`` (no engine / failure; never raises).
60
+ ``client``/``graph`` may be injected (tests); otherwise resolved on demand.
61
+ """
62
+ entities = [e for e in (entities or []) if e.get("id")]
63
+ if not entities:
64
+ return None
65
+ if client is None:
66
+ client, graph = _client()
67
+ if client is None:
68
+ return None
69
+ graph = graph or "__commons__"
70
+
71
+ try:
72
+ txn = client.txn.begin(graph=graph)
73
+ for ent in entities:
74
+ props = {k: v for k, v in ent.items() if k != "id" and v is not None}
75
+ props.setdefault("source", _SOURCE)
76
+ props.setdefault("domain", _DOMAIN)
77
+ client.txn.add_node(txn, ent["id"], props)
78
+ committed = client.txn.commit(txn)
79
+ except Exception as e: # noqa: BLE001 — engine/txn failure is non-fatal
80
+ logger.warning("KG ingest: txn failed: %s", e)
81
+ return None
82
+ if not committed:
83
+ logger.warning("KG ingest: txn not committed (conflict)")
84
+ return None
85
+
86
+ edges = 0
87
+ for rel in relationships or []:
88
+ try:
89
+ client.edges.add(
90
+ rel["source"], rel["target"], {"type": rel.get("type", "RELATED")}
91
+ )
92
+ edges += 1
93
+ except Exception as e: # noqa: BLE001 — pure edge link, best-effort
94
+ logger.debug("KG ingest: edge skipped: %s", e)
95
+
96
+ logger.info("KG ingest: wrote %d nodes, %d edges", len(entities), edges)
97
+ return {"nodes": len(entities), "edges": edges}
98
+
99
+
100
+ def ingest_projects(
101
+ projects: list[dict[str, Any]],
102
+ *,
103
+ client: Any | None = None,
104
+ graph: str | None = None,
105
+ ) -> dict[str, int] | None:
106
+ """Map GitLab project records → ``:Project`` (+ ``:GitLabGroup``) nodes and ingest."""
107
+ entities: list[dict[str, Any]] = []
108
+ relationships: list[dict[str, Any]] = []
109
+ for proj in projects or []:
110
+ pid = proj.get("id")
111
+ if pid is None:
112
+ continue
113
+ entities.append(
114
+ {
115
+ "id": f"gitlab:project:{pid}",
116
+ "type": "Project",
117
+ "name": proj.get("name"),
118
+ "path_with_namespace": proj.get("path_with_namespace"),
119
+ "web_url": proj.get("web_url"),
120
+ "state": proj.get("state"),
121
+ "last_activity_at": proj.get("last_activity_at"),
122
+ "externalToolId": str(pid),
123
+ }
124
+ )
125
+ namespace = proj.get("namespace") or {}
126
+ gid = namespace.get("id") or namespace.get("full_path")
127
+ if gid is not None:
128
+ entities.append(
129
+ {
130
+ "id": f"gitlab:group:{gid}",
131
+ "type": "GitLabGroup",
132
+ "name": namespace.get("name") or namespace.get("full_path"),
133
+ }
134
+ )
135
+ relationships.append(
136
+ {
137
+ "source": f"gitlab:project:{pid}",
138
+ "target": f"gitlab:group:{gid}",
139
+ "type": "partOfGroup",
140
+ }
141
+ )
142
+ return ingest_entities(entities, relationships, client=client, graph=graph)
143
+
144
+
145
+ def ingest_pipeline_runs(
146
+ project_id: str | int,
147
+ pipelines: list[dict[str, Any]],
148
+ *,
149
+ jobs_by_pipeline: dict[Any, list[dict[str, Any]]] | None = None,
150
+ client: Any | None = None,
151
+ graph: str | None = None,
152
+ ) -> dict[str, int] | None:
153
+ """Map GitLab pipeline runs (+ their jobs) → ``:PipelineRun``/``:CheckRun`` nodes.
154
+
155
+ This is the substrate the autonomous-SDLC loop needs to observe CI (closes gap #2,
156
+ "CI has no graph representation", of ``reports/autonomous-sdlc-loop-design.md``).
157
+
158
+ Uses the SAME ``:PipelineRun``/``:CheckRun`` classes and ``ranFor``/``hasJob`` edge
159
+ names as github-agent's ingestion so GitLab CI/CD and GitHub Actions unify under one
160
+ CI node shape in the knowledge graph. ``ranFor`` is emitted once per known target —
161
+ the ``:Project``, the head ``:Commit``, and (if resolved) the triggering
162
+ ``:MergeRequest``. Stable ids: ``gitlab:pipelinerun:<project>:<id>`` /
163
+ ``gitlab:checkrun:<project>:<pipeline>:<job>``.
164
+
165
+ ``pipelines``: raw GitLab pipeline records (``id``, ``status``, ``ref``, ``sha``,
166
+ ``source``, ``web_url``, ``name``, timestamps, ``duration``; optionally
167
+ ``merge_request_iid`` if the caller has resolved the triggering merge request).
168
+ ``jobs_by_pipeline``: maps a pipeline ``id`` to its list of raw GitLab job records
169
+ (``id``, ``name``, ``stage``, ``status``, ``failure_reason``, ``web_url``,
170
+ timestamps, ``duration``, optional ``runner``); each becomes a child ``:CheckRun``
171
+ linked via ``hasJob`` (and, when known, its ``:Runner`` via the GitLab-specific
172
+ ``ranOnRunner``).
173
+ """
174
+ pipelines = pipelines or []
175
+ if not pipelines:
176
+ return None
177
+ jobs_by_pipeline = jobs_by_pipeline or {}
178
+ entities: list[dict[str, Any]] = []
179
+ relationships: list[dict[str, Any]] = []
180
+ project_node = f"gitlab:project:{project_id}"
181
+
182
+ for pipe in pipelines:
183
+ pid = pipe.get("id")
184
+ if pid is None:
185
+ continue
186
+ pipe_node = f"gitlab:pipelinerun:{project_id}:{pid}"
187
+ entities.append(
188
+ {
189
+ "id": pipe_node,
190
+ "type": "PipelineRun",
191
+ "status": pipe.get("status"),
192
+ "ref": pipe.get("ref"),
193
+ "sha": pipe.get("sha"),
194
+ "triggerSource": pipe.get("source"),
195
+ "webUrl": pipe.get("web_url"),
196
+ "name": pipe.get("name"),
197
+ "createdAt": pipe.get("created_at"),
198
+ "startedAt": pipe.get("started_at"),
199
+ "finishedAt": pipe.get("finished_at"),
200
+ "duration": pipe.get("duration"),
201
+ "externalToolId": str(pid),
202
+ }
203
+ )
204
+ relationships.append(
205
+ {"source": pipe_node, "target": project_node, "type": "ranFor"}
206
+ )
207
+
208
+ sha = pipe.get("sha")
209
+ if sha:
210
+ commit_node = f"gitlab:commit:{project_id}:{sha}"
211
+ entities.append({"id": commit_node, "type": "Commit", "sha": sha})
212
+ relationships.append(
213
+ {"source": pipe_node, "target": commit_node, "type": "ranFor"}
214
+ )
215
+
216
+ mr_iid = pipe.get("merge_request_iid")
217
+ if mr_iid is not None:
218
+ mr_node = f"gitlab:mr:{project_id}:{mr_iid}"
219
+ relationships.append(
220
+ {"source": pipe_node, "target": mr_node, "type": "ranFor"}
221
+ )
222
+
223
+ for job in jobs_by_pipeline.get(pid, []) or []:
224
+ jid = job.get("id")
225
+ if jid is None:
226
+ continue
227
+ job_node = f"gitlab:checkrun:{project_id}:{pid}:{jid}"
228
+ web_url = job.get("web_url")
229
+ entities.append(
230
+ {
231
+ "id": job_node,
232
+ "type": "CheckRun",
233
+ "name": job.get("name"),
234
+ "stage": job.get("stage"),
235
+ "status": job.get("status"),
236
+ "failureReason": job.get("failure_reason"),
237
+ "webUrl": web_url,
238
+ "logUrl": f"{web_url}/raw" if web_url else None,
239
+ "triggerSource": job.get("source"),
240
+ "createdAt": job.get("created_at"),
241
+ "startedAt": job.get("started_at"),
242
+ "finishedAt": job.get("finished_at"),
243
+ "duration": job.get("duration"),
244
+ "externalToolId": str(jid),
245
+ }
246
+ )
247
+ relationships.append(
248
+ {"source": pipe_node, "target": job_node, "type": "hasJob"}
249
+ )
250
+ runner = job.get("runner") or {}
251
+ runner_id = runner.get("id")
252
+ if runner_id is not None:
253
+ runner_node = f"gitlab:runner:{runner_id}"
254
+ entities.append(
255
+ {
256
+ "id": runner_node,
257
+ "type": "Runner",
258
+ "name": runner.get("description") or runner.get("name"),
259
+ }
260
+ )
261
+ relationships.append(
262
+ {
263
+ "source": job_node,
264
+ "target": runner_node,
265
+ "type": "ranOnRunner",
266
+ }
267
+ )
268
+
269
+ return ingest_entities(entities, relationships, client=client, graph=graph)
@@ -37,7 +37,7 @@ from agent_utilities.mcp_utilities import (
37
37
  from gitlab_api.api_client import Api
38
38
  from gitlab_api.auth import get_client
39
39
 
40
- __version__ = "26.2.0"
40
+ __version__ = "26.4.0"
41
41
  print(f"Gitlab MCP v{__version__}", file=sys.stderr)
42
42
 
43
43
  logger = get_logger(name="mcp_server")
@@ -112,6 +112,70 @@ def register_misc_tools(mcp: FastMCP):
112
112
  result = ingest_projects(projects)
113
113
  return {"listed": len(projects), "ingested": result}
114
114
 
115
+ @mcp.tool(tags={"misc", "kg"})
116
+ async def gitlab_ingest_pipelines(
117
+ project_id: str = Field(
118
+ description="GitLab project id or URL-encoded path to ingest recent pipeline runs for."
119
+ ),
120
+ params_json: str = Field(
121
+ default="{}",
122
+ description="JSON string of get_pipelines filters (e.g. status, ref, per_page).",
123
+ ),
124
+ include_jobs: bool = Field(
125
+ default=True,
126
+ description="Also fetch + ingest each pipeline's jobs as :Job/:CheckRun nodes.",
127
+ ),
128
+ client=Depends(get_client),
129
+ ctx: Context | None = None,
130
+ ) -> Any:
131
+ """Natively ingest GitLab CI pipeline runs (+ jobs) into epistemic-graph.
132
+
133
+ Lists recent pipelines for ``project_id`` via the GitLab API (optionally each
134
+ pipeline's jobs) and pushes them as typed ``:PipelineRun``/``:CheckRun`` nodes
135
+ (the SAME classes + ``ranFor``/``hasJob`` edges github-agent uses, so GitLab
136
+ CI/CD and GitHub Actions unify) — ``ranFor`` the ``:Project``, the ``:Commit``,
137
+ and any triggering ``:MergeRequest`` — into the knowledge graph via the fast
138
+ engine client. This is the substrate the autonomous-SDLC loop needs to observe
139
+ CI. Best-effort: returns ``{"ingested": None}`` when no engine is reachable.
140
+ CONCEPT:AU-KG.ingest.enterprise-source-extractor.
141
+ """
142
+ import json as _json
143
+
144
+ from gitlab_api.kg_ingest import ingest_pipeline_runs
145
+
146
+ kwargs = _json.loads(params_json) if params_json else {}
147
+ kwargs["project_id"] = project_id
148
+ resp = await run_blocking(client.get_pipelines, **kwargs)
149
+ data = getattr(resp, "data", resp)
150
+ records = data if isinstance(data, list) else [data]
151
+ pipelines = [
152
+ r.model_dump() if hasattr(r, "model_dump") else r
153
+ for r in records
154
+ if r is not None
155
+ ]
156
+
157
+ jobs_by_pipeline: dict[Any, list[dict[str, Any]]] = {}
158
+ if include_jobs:
159
+ for pipe in pipelines:
160
+ pid = pipe.get("id")
161
+ if pid is None:
162
+ continue
163
+ jresp = await run_blocking(
164
+ client.get_pipeline_jobs, project_id=project_id, pipeline_id=pid
165
+ )
166
+ jdata = getattr(jresp, "data", jresp)
167
+ jrecords = jdata if isinstance(jdata, list) else [jdata]
168
+ jobs_by_pipeline[pid] = [
169
+ j.model_dump() if hasattr(j, "model_dump") else j
170
+ for j in jrecords
171
+ if j is not None
172
+ ]
173
+
174
+ result = ingest_pipeline_runs(
175
+ project_id, pipelines, jobs_by_pipeline=jobs_by_pipeline
176
+ )
177
+ return {"listed": len(pipelines), "ingested": result}
178
+
115
179
  return None
116
180
 
117
181
 
@@ -27,10 +27,25 @@
27
27
  rdfs:label "Pipeline" ;
28
28
  rdfs:comment "A CI/CD pipeline run for a commit/ref (gitlab_pipelines)." .
29
29
 
30
+ :PipelineRun a owl:Class ;
31
+ rdfs:label "Pipeline Run" ;
32
+ rdfs:comment """A single CI/CD execution for a commit/ref/merge request (a GitLab pipeline
33
+ run, ingested by gitlab_api.kg_ingest.ingest_pipeline_runs, id gitlab:pipelinerun:<project>:<id>).
34
+ Shares its class name with the twin github-agent producer so GitLab CI/CD and GitHub Actions
35
+ unify under one node shape in the knowledge graph.""" ;
36
+ owl:equivalentClass :Pipeline .
37
+
30
38
  :Job a owl:Class ;
31
39
  rdfs:label "Job" ;
32
40
  rdfs:comment "A single stage/job within a pipeline (gitlab_jobs)." .
33
41
 
42
+ :CheckRun a owl:Class ;
43
+ rdfs:label "Check Run" ;
44
+ rdfs:comment """A single job/check within a PipelineRun (a GitLab pipeline job, ingested by
45
+ gitlab_api.kg_ingest.ingest_pipeline_runs, id gitlab:checkrun:<project>:<pipeline>:<job>).
46
+ Shares its class name with the twin github-agent producer.""" ;
47
+ owl:equivalentClass :Job .
48
+
34
49
  :Issue a owl:Class ;
35
50
  rdfs:label "Issue" ;
36
51
  rdfs:comment "A tracked work item or defect (gitlab_issues)." .
@@ -83,9 +98,17 @@
83
98
  rdfs:comment "Links a Commit or MergeRequest to a Pipeline it started." ;
84
99
  rdfs:range :Pipeline .
85
100
 
101
+ :ranFor a owl:ObjectProperty ;
102
+ rdfs:label "ran for" ;
103
+ rdfs:comment """Links a PipelineRun to what it ran for — its Project, the Commit it built,
104
+ and/or the MergeRequest it validated. Same predicate name as the twin github-agent producer
105
+ (PipelineRun ranFor Repository/Commit/PullRequest), unifying GitLab CI/CD and GitHub Actions.""" ;
106
+ rdfs:domain :PipelineRun .
107
+
86
108
  :hasJob a owl:ObjectProperty ;
87
109
  rdfs:label "has job" ;
88
- rdfs:comment "Links a Pipeline to a Job it contains." ;
110
+ rdfs:comment """Links a PipelineRun to a CheckRun (job/check) it contains. Same predicate name
111
+ as the twin github-agent producer (PipelineRun hasJob CheckRun).""" ;
89
112
  rdfs:domain :Pipeline ;
90
113
  rdfs:range :Job .
91
114
 
@@ -143,6 +166,41 @@
143
166
  rdfs:comment "Full git commit SHA." ;
144
167
  rdfs:range xsd:string .
145
168
 
169
+ :triggerSource a owl:DatatypeProperty ;
170
+ rdfs:label "trigger source" ;
171
+ rdfs:comment "How a Pipeline or Job was triggered (push, web, schedule, api, merge_request_event, …). Named apart from the shared provenance `source` field." ;
172
+ rdfs:range xsd:string .
173
+
174
+ :failureReason a owl:DatatypeProperty ;
175
+ rdfs:label "failure reason" ;
176
+ rdfs:comment "GitLab's classification of why a Job failed (e.g. script_failure, runner_system_failure)." ;
177
+ rdfs:range xsd:string .
178
+
179
+ :duration a owl:DatatypeProperty ;
180
+ rdfs:label "duration" ;
181
+ rdfs:comment "Wall-clock duration of a Pipeline or Job run, in seconds." ;
182
+ rdfs:range xsd:decimal .
183
+
184
+ :createdAt a owl:DatatypeProperty ;
185
+ rdfs:label "created at" ;
186
+ rdfs:comment "Timestamp an object was created." ;
187
+ rdfs:range xsd:dateTime .
188
+
189
+ :startedAt a owl:DatatypeProperty ;
190
+ rdfs:label "started at" ;
191
+ rdfs:comment "Timestamp a Pipeline or Job started running." ;
192
+ rdfs:range xsd:dateTime .
193
+
194
+ :finishedAt a owl:DatatypeProperty ;
195
+ rdfs:label "finished at" ;
196
+ rdfs:comment "Timestamp a Pipeline or Job finished running." ;
197
+ rdfs:range xsd:dateTime .
198
+
199
+ :logUrl a owl:DatatypeProperty ;
200
+ rdfs:label "log url" ;
201
+ rdfs:comment "URL to the raw log output of a Job." ;
202
+ rdfs:range xsd:string .
203
+
146
204
  <http://knuckles.team/kg/gitlab> a owl:Ontology ;
147
205
  rdfs:label "GitLab DevOps Ontology" ;
148
206
  rdfs:comment """GitLab source-control and CI/CD delivery model — groups, projects,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlab-api
3
- Version: 26.2.0
3
+ Version: 26.4.0
4
4
  Summary: GitLab API + MCP Server + A2A Server
5
5
  Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
6
  License: MIT
@@ -22,7 +22,7 @@ Requires-Dist: agent-utilities[agent,logfire]>=1.0.0; extra == "agent"
22
22
  Provides-Extra: gql
23
23
  Requires-Dist: gql[requests]>=4.0.0; extra == "gql"
24
24
  Provides-Extra: all
25
- Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=26.2.0; extra == "all"
25
+ Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=26.4.0; extra == "all"
26
26
  Provides-Extra: test
27
27
  Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
28
28
  Requires-Dist: pytest; extra == "test"
@@ -52,7 +52,7 @@ Dynamic: license-file
52
52
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
53
53
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
54
54
 
55
- *Version: 26.2.0*
55
+ *Version: 26.4.0*
56
56
 
57
57
  > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
58
58
  > interfaces, the integrated A2A agent server, and guidance for provisioning the
@@ -116,6 +116,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
116
116
  | `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
117
117
  | `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
118
118
  | `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
119
+ | `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into epistemic-graph. |
119
120
  | `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into epistemic-graph as typed :Project nodes. |
120
121
  | `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
121
122
  | `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
@@ -346,7 +347,7 @@ _Auto-generated from the live MCP server — do not edit by hand._
346
347
 
347
348
  </details>
348
349
 
349
- _33 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
350
+ _34 action-routed tool(s) (default) · 196 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (`condensed` default · `verbose` 1:1 · `both`). Auto-generated — do not edit._
350
351
  <!-- MCP-TOOLS-TABLE:END -->
351
352
 
352
353
  Detailed tool schemas, parameter shapes, and validation constraints are preserved in [docs/mcp.md](docs/mcp.md).
@@ -6,7 +6,7 @@ gql[requests]>=4.0.0
6
6
  agent-utilities[agent,logfire]>=1.0.0
7
7
 
8
8
  [all]
9
- gitlab-api[agent,gql,logfire,mcp]>=26.2.0
9
+ gitlab-api[agent,gql,logfire,mcp]>=26.4.0
10
10
 
11
11
  [gql]
12
12
  gql[requests]>=4.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitlab-api"
7
- version = "26.2.0"
7
+ version = "26.4.0"
8
8
  description = "GitLab API + MCP Server + A2A Server"
9
9
  readme = "README.md"
10
10
  classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",]
@@ -21,7 +21,7 @@ text = "MIT"
21
21
  mcp = [ "agent-utilities[mcp]>=1.0.0",]
22
22
  agent = [ "agent-utilities[agent,logfire]>=1.0.0",]
23
23
  gql = [ "gql[requests]>=4.0.0",]
24
- all = [ "gitlab-api[mcp,agent,gql,logfire]>=26.2.0",]
24
+ all = [ "gitlab-api[mcp,agent,gql,logfire]>=26.4.0",]
25
25
  test = [
26
26
  "pytest-xdist>=3.6.0", "pytest", "pytest-asyncio", "pytest-cov",]
27
27