@gaodes/pi-gitlab 0.3.0

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 (47) hide show
  1. package/.primecodex.json +19 -0
  2. package/.upstream.json +49 -0
  3. package/CHANGELOG.md +47 -0
  4. package/LICENSE +21 -0
  5. package/README.md +132 -0
  6. package/package.json +79 -0
  7. package/skills/gitlab-assistant/SKILL.md +28 -0
  8. package/skills/gitlab-issue/SKILL.md +41 -0
  9. package/skills/gitlab-mr/SKILL.md +49 -0
  10. package/skills/gitlab-pipeline/SKILL.md +36 -0
  11. package/skills/gitlab-release/SKILL.md +45 -0
  12. package/skills/gitlab-workflow/SKILL.md +39 -0
  13. package/src/commands/gitlab-doctor.ts +228 -0
  14. package/src/config/guard.ts +96 -0
  15. package/src/config/loader.ts +170 -0
  16. package/src/config/types.ts +57 -0
  17. package/src/events/resourcesDiscover.ts +27 -0
  18. package/src/index.ts +87 -0
  19. package/src/lib/confirm.ts +47 -0
  20. package/src/lib/env.ts +48 -0
  21. package/src/lib/errors.ts +156 -0
  22. package/src/lib/gitRemoteParse.ts +41 -0
  23. package/src/lib/glab.ts +34 -0
  24. package/src/lib/pagination.ts +3 -0
  25. package/src/lib/projectCache.ts +39 -0
  26. package/src/lib/projectFallback.ts +19 -0
  27. package/src/lib/redact.ts +24 -0
  28. package/src/lib/resolveProjectId.ts +37 -0
  29. package/src/lib/schemas.ts +16 -0
  30. package/src/tools/IMPLEMENTATION-NOTE-1B.md +33 -0
  31. package/src/tools/gitlab_api.ts +146 -0
  32. package/src/tools/gitlab_force_push_safe.ts +246 -0
  33. package/src/tools/gitlab_issue_close.ts +64 -0
  34. package/src/tools/gitlab_issue_create.ts +71 -0
  35. package/src/tools/gitlab_issue_list.ts +110 -0
  36. package/src/tools/gitlab_job_logs.ts +89 -0
  37. package/src/tools/gitlab_mr_bulk_approve.ts +109 -0
  38. package/src/tools/gitlab_mr_create.ts +83 -0
  39. package/src/tools/gitlab_mr_list.ts +108 -0
  40. package/src/tools/gitlab_mr_merge.ts +86 -0
  41. package/src/tools/gitlab_mr_view.ts +99 -0
  42. package/src/tools/gitlab_pipeline_run.ts +68 -0
  43. package/src/tools/gitlab_pipeline_status.ts +122 -0
  44. package/src/tools/gitlab_project_resolve.ts +59 -0
  45. package/src/tools/gitlab_release_create.ts +92 -0
  46. package/src/tools/gitlab_release_list.ts +97 -0
  47. package/src/tools/gitlab_release_view.ts +78 -0
@@ -0,0 +1,92 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
+ import { glab } from "../lib/glab.js";
8
+ import { requireConfirm } from "../lib/confirm.js";
9
+ import { resolveProject } from "../lib/projectFallback.js";
10
+ import { resolveProjectId } from "../lib/resolveProjectId.js";
11
+ import { OptionalProject } from "../lib/schemas.js";
12
+
13
+ export function registerGitlabReleaseCreate(pi: ExtensionAPI) {
14
+ pi.registerTool({
15
+ name: "gitlab_release_create",
16
+ label: "Create Release",
17
+ description:
18
+ "Create a new release for a tag. Requires confirmation.",
19
+ parameters: Type.Object(
20
+ {
21
+ project: OptionalProject,
22
+ tagName: Type.String({ description: "Tag name (e.g. v0.3.0)" }),
23
+ name: Type.Optional(
24
+ Type.String({ description: "Release title. Defaults to tag name." }),
25
+ ),
26
+ description: Type.Optional(
27
+ Type.String({
28
+ description: "Release notes in markdown.",
29
+ }),
30
+ ),
31
+ ref: Type.Optional(
32
+ Type.String({
33
+ description:
34
+ "Branch, SHA, or tag to create the release from. Required when the tag does not already exist.",
35
+ }),
36
+ ),
37
+ confirm: Type.Optional(Type.Boolean({ default: false })),
38
+ dryRun: Type.Optional(Type.Boolean({ default: false })),
39
+ },
40
+ { additionalProperties: false },
41
+ ),
42
+ async execute(
43
+ _toolCallId,
44
+ params,
45
+ _signal,
46
+ _onUpdate,
47
+ ctx: ExtensionContext,
48
+ ) {
49
+ try {
50
+ requireSetup(ctx.cwd);
51
+ } catch {
52
+ return setupRequiredResult();
53
+ }
54
+
55
+ const projectPath = await resolveProject(params.project, ctx.cwd);
56
+ const projectId = await resolveProjectId(projectPath);
57
+
58
+ const preview = `Create release **${params.name ?? params.tagName}**\nTag: \`${params.tagName}\`${params.ref ? `\nRef: \`${params.ref}\`` : ""}\nProject: \`${projectPath}\``;
59
+ const blocked = requireConfirm(preview, {
60
+ confirm: params.confirm,
61
+ dryRun: params.dryRun,
62
+ });
63
+ if (blocked) return blocked;
64
+
65
+ const body: Record<string, unknown> = {
66
+ tag_name: params.tagName,
67
+ };
68
+ if (params.name) body.name = params.name;
69
+ if (params.description) body.description = params.description;
70
+ if (params.ref) body.ref = params.ref;
71
+
72
+ const result = await glab([
73
+ "api",
74
+ "-X",
75
+ "POST",
76
+ `projects/${projectId}/releases`,
77
+ ...Object.entries(body).flatMap(([k, v]) => ["-f", `${k}=${v}`]),
78
+ ]);
79
+
80
+ const release = result as Record<string, unknown>;
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: `✅ Release created: ${release.tag_name} — ${release.name ?? release.tag_name}\n${(release._links as Record<string, unknown>)?.self ?? ""}`,
86
+ },
87
+ ],
88
+ details: { success: true, release },
89
+ };
90
+ },
91
+ });
92
+ }
@@ -0,0 +1,97 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
+ import { glab } from "../lib/glab.js";
8
+ import { limitRows } from "../lib/pagination.js";
9
+ import { resolveProject } from "../lib/projectFallback.js";
10
+ import { resolveProjectId } from "../lib/resolveProjectId.js";
11
+ import { MaxRows, OptionalProject } from "../lib/schemas.js";
12
+
13
+ interface Release {
14
+ tag_name: string;
15
+ name?: string;
16
+ author?: { name?: string };
17
+ created_at?: string;
18
+ upcoming_release?: boolean;
19
+ assets?: { count?: number };
20
+ }
21
+
22
+ export function registerGitlabReleaseList(pi: ExtensionAPI) {
23
+ pi.registerTool({
24
+ name: "gitlab_release_list",
25
+ label: "List Releases",
26
+ description:
27
+ "List releases for a project with transparent pagination.",
28
+ parameters: Type.Object(
29
+ {
30
+ project: OptionalProject,
31
+ sort: Type.Optional(
32
+ Type.Union(
33
+ [
34
+ Type.Literal("desc"),
35
+ Type.Literal("asc"),
36
+ ],
37
+ { default: "desc" },
38
+ ),
39
+ ),
40
+ maxRows: MaxRows,
41
+ },
42
+ { additionalProperties: false },
43
+ ),
44
+ async execute(
45
+ _toolCallId,
46
+ params,
47
+ _signal,
48
+ _onUpdate,
49
+ ctx: ExtensionContext,
50
+ ) {
51
+ try {
52
+ requireSetup(ctx.cwd);
53
+ } catch {
54
+ return setupRequiredResult();
55
+ }
56
+
57
+ const projectPath = await resolveProject(params.project, ctx.cwd);
58
+ const projectId = await resolveProjectId(projectPath);
59
+
60
+ const query = new URLSearchParams();
61
+ query.set("sort", params.sort ?? "desc");
62
+ query.set("per_page", "100");
63
+
64
+ const releases = (await glab([
65
+ "api",
66
+ "--paginate",
67
+ `projects/${projectId}/releases?${query.toString()}`,
68
+ ])) as Release[];
69
+
70
+ const limited = limitRows(releases, params.maxRows ?? 25);
71
+ if (limited.length === 0) {
72
+ return {
73
+ content: [{ type: "text", text: "No releases found." }],
74
+ details: { success: true, count: 0, releases: [] },
75
+ };
76
+ }
77
+
78
+ const lines = [
79
+ "| Tag | Name | Author | Date | Upcoming |",
80
+ "|---|---|---|---|---|",
81
+ ];
82
+ for (const rel of limited) {
83
+ const date = rel.created_at
84
+ ? new Date(rel.created_at).toISOString().split("T")[0]
85
+ : "-";
86
+ lines.push(
87
+ `| ${rel.tag_name} | ${rel.name ?? "-"} | ${rel.author?.name ?? "-"} | ${date} | ${rel.upcoming_release ? "yes" : "no"} |`,
88
+ );
89
+ }
90
+
91
+ return {
92
+ content: [{ type: "text", text: lines.join("\n") }],
93
+ details: { success: true, count: limited.length, releases: limited },
94
+ };
95
+ },
96
+ });
97
+ }
@@ -0,0 +1,78 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
+ import { glab } from "../lib/glab.js";
8
+ import { resolveProject } from "../lib/projectFallback.js";
9
+ import { resolveProjectId } from "../lib/resolveProjectId.js";
10
+ import { OptionalProject } from "../lib/schemas.js";
11
+
12
+ export function registerGitlabReleaseView(pi: ExtensionAPI) {
13
+ pi.registerTool({
14
+ name: "gitlab_release_view",
15
+ label: "View Release",
16
+ description:
17
+ "View a single release by tag name, including description and asset links.",
18
+ parameters: Type.Object(
19
+ {
20
+ project: OptionalProject,
21
+ tagName: Type.String({ description: "Release tag name (e.g. v1.0.0)" }),
22
+ },
23
+ { additionalProperties: false },
24
+ ),
25
+ async execute(
26
+ _toolCallId,
27
+ params,
28
+ _signal,
29
+ _onUpdate,
30
+ ctx: ExtensionContext,
31
+ ) {
32
+ try {
33
+ requireSetup(ctx.cwd);
34
+ } catch {
35
+ return setupRequiredResult();
36
+ }
37
+
38
+ const projectPath = await resolveProject(params.project, ctx.cwd);
39
+ const projectId = await resolveProjectId(projectPath);
40
+
41
+ const release = (await glab([
42
+ "api",
43
+ `projects/${projectId}/releases/${encodeURIComponent(params.tagName)}`,
44
+ ])) as Record<string, unknown>;
45
+
46
+ const assets = release.assets as Record<string, unknown> | undefined;
47
+ const links = (assets?.links as Array<Record<string, unknown>> | undefined) ?? [];
48
+
49
+ const lines: string[] = [];
50
+ lines.push(`## Release: ${release.tag_name}`);
51
+ if (release.name) lines.push(`**Name:** ${release.name}`);
52
+ lines.push(`**Author:** ${(release.author as Record<string, unknown>)?.name ?? "-"}`);
53
+ if (release.created_at)
54
+ lines.push(`**Created:** ${new Date(release.created_at as string).toISOString()}`);
55
+ if (release.upcoming_release) lines.push("**Upcoming release:** yes");
56
+
57
+ if (release.description) {
58
+ lines.push(`\n### Description\n${release.description}`);
59
+ }
60
+
61
+ if (links.length > 0) {
62
+ lines.push("\n### Asset Links");
63
+ for (const link of links) {
64
+ lines.push(`- [${link.name ?? link.url}](${link.url})`);
65
+ }
66
+ }
67
+
68
+ if (assets?.count !== undefined) {
69
+ lines.push(`\n**Assets count:** ${assets.count}`);
70
+ }
71
+
72
+ return {
73
+ content: [{ type: "text", text: lines.join("\n") }],
74
+ details: { success: true, release },
75
+ };
76
+ },
77
+ });
78
+ }