gitlab-mcp-agent-server 0.1.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.
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitLabApiClient = void 0;
4
+ const errors_1 = require("../../shared/errors");
5
+ class GitLabApiClient {
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ async listProjects() {
11
+ const data = await this.request('/projects?simple=true&membership=true');
12
+ return data.map(mapProject);
13
+ }
14
+ async createIssue(input) {
15
+ const projectPath = encodeProjectRef(input.project);
16
+ const body = {
17
+ title: input.title,
18
+ description: input.description,
19
+ labels: input.labels?.join(','),
20
+ assignee_ids: input.assigneeIds
21
+ };
22
+ const data = await this.request(`/projects/${projectPath}/issues`, {
23
+ method: 'POST',
24
+ body: JSON.stringify(body)
25
+ });
26
+ return mapIssue(data);
27
+ }
28
+ async getIssue(input) {
29
+ const projectPath = encodeProjectRef(input.project);
30
+ const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`);
31
+ return mapIssue(data);
32
+ }
33
+ async closeIssue(input) {
34
+ const projectPath = encodeProjectRef(input.project);
35
+ const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`, {
36
+ method: 'PUT',
37
+ body: JSON.stringify({ state_event: 'close' })
38
+ });
39
+ return mapIssue(data);
40
+ }
41
+ async updateIssueLabels(input) {
42
+ const projectPath = encodeProjectRef(input.project);
43
+ const data = await this.request(`/projects/${projectPath}/issues/${input.issueIid}`, {
44
+ method: 'PUT',
45
+ body: JSON.stringify({ labels: input.labels.join(',') })
46
+ });
47
+ return mapIssue(data);
48
+ }
49
+ async listLabels(input) {
50
+ const projectPath = encodeProjectRef(input.project);
51
+ const query = input.search
52
+ ? `/projects/${projectPath}/labels?search=${encodeURIComponent(input.search)}`
53
+ : `/projects/${projectPath}/labels`;
54
+ const data = await this.request(query);
55
+ return data.map(mapLabel);
56
+ }
57
+ async listIssues(input) {
58
+ const projectPath = encodeProjectRef(input.project);
59
+ const query = new URLSearchParams();
60
+ if (input.state) {
61
+ query.set('state', input.state);
62
+ }
63
+ if (input.search) {
64
+ query.set('search', input.search);
65
+ }
66
+ if (input.labels && input.labels.length > 0) {
67
+ query.set('labels', input.labels.join(','));
68
+ }
69
+ if (input.perPage) {
70
+ query.set('per_page', String(input.perPage));
71
+ }
72
+ if (input.page) {
73
+ query.set('page', String(input.page));
74
+ }
75
+ const querySuffix = query.size > 0 ? `?${query.toString()}` : '';
76
+ const data = await this.request(`/projects/${projectPath}/issues${querySuffix}`);
77
+ return data.map(mapIssue);
78
+ }
79
+ async createLabel(input) {
80
+ const projectPath = encodeProjectRef(input.project);
81
+ const data = await this.request(`/projects/${projectPath}/labels`, {
82
+ method: 'POST',
83
+ body: JSON.stringify({
84
+ name: input.name,
85
+ color: input.color ?? '#1f75cb',
86
+ description: input.description
87
+ })
88
+ });
89
+ return mapLabel(data);
90
+ }
91
+ async request(path, init) {
92
+ const token = await this.options.tokenProvider.getAccessToken();
93
+ if (!token) {
94
+ throw new errors_1.ConfigurationError('GitLab access token is not configured. Set GITLAB_OAUTH_ACCESS_TOKEN (oauth) or GITLAB_PAT (pat).');
95
+ }
96
+ const response = await fetch(`${this.options.apiUrl}${path}`, {
97
+ ...init,
98
+ headers: {
99
+ Authorization: `Bearer ${token}`,
100
+ 'Content-Type': 'application/json',
101
+ ...(init?.headers ?? {})
102
+ }
103
+ });
104
+ if (!response.ok) {
105
+ const body = await safeReadBody(response);
106
+ throw new Error(`GitLab API ${response.status}: ${body}`);
107
+ }
108
+ return (await response.json());
109
+ }
110
+ }
111
+ exports.GitLabApiClient = GitLabApiClient;
112
+ function mapProject(project) {
113
+ return {
114
+ id: project.id,
115
+ name: project.name,
116
+ pathWithNamespace: project.path_with_namespace
117
+ };
118
+ }
119
+ function mapIssue(issue) {
120
+ return {
121
+ id: issue.id,
122
+ iid: issue.iid,
123
+ projectId: issue.project_id,
124
+ title: issue.title,
125
+ description: issue.description,
126
+ state: issue.state,
127
+ labels: issue.labels,
128
+ webUrl: issue.web_url,
129
+ updatedAt: issue.updated_at,
130
+ closedAt: issue.closed_at
131
+ };
132
+ }
133
+ function mapLabel(label) {
134
+ return {
135
+ name: label.name,
136
+ color: label.color,
137
+ description: label.description
138
+ };
139
+ }
140
+ function encodeProjectRef(project) {
141
+ return encodeURIComponent(String(project));
142
+ }
143
+ async function safeReadBody(response) {
144
+ try {
145
+ return await response.text();
146
+ }
147
+ catch {
148
+ return 'Unable to read response body';
149
+ }
150
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMcpServer = createMcpServer;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const issue_workflow_policy_1 = require("../../application/policies/issue-workflow-policy");
6
+ const project_resolver_1 = require("../../application/services/project-resolver");
7
+ const close_issue_1 = require("../../application/use-cases/close-issue");
8
+ const create_issue_1 = require("../../application/use-cases/create-issue");
9
+ const ensure_labels_1 = require("../../application/use-cases/ensure-labels");
10
+ const get_issue_1 = require("../../application/use-cases/get-issue");
11
+ const health_check_1 = require("../../application/use-cases/health-check");
12
+ const list_labels_1 = require("../../application/use-cases/list-labels");
13
+ const list_issues_1 = require("../../application/use-cases/list-issues");
14
+ const update_issue_labels_1 = require("../../application/use-cases/update-issue-labels");
15
+ const gitlab_oauth_manager_1 = require("../../infrastructure/auth/gitlab-oauth-manager");
16
+ const token_provider_1 = require("../../infrastructure/auth/token-provider");
17
+ const gitlab_api_client_1 = require("../../infrastructure/gitlab/gitlab-api-client");
18
+ const config_1 = require("../../shared/config");
19
+ const register_tools_1 = require("./register-tools");
20
+ function createMcpServer() {
21
+ const config = (0, config_1.loadConfig)();
22
+ const tokenProvider = config.gitlab.authMode === 'oauth'
23
+ ? new gitlab_oauth_manager_1.GitLabOAuthManager({
24
+ apiUrl: config.gitlab.apiUrl,
25
+ clientId: config.gitlab.oauth.clientId,
26
+ clientSecret: config.gitlab.oauth.clientSecret,
27
+ redirectUri: config.gitlab.oauth.redirectUri,
28
+ scopes: config.gitlab.oauth.scopes,
29
+ bootstrapAccessToken: config.gitlab.accessToken,
30
+ tokenStorePath: config.gitlab.oauth.tokenStorePath,
31
+ autoLogin: config.gitlab.oauth.autoLogin,
32
+ openBrowser: config.gitlab.oauth.openBrowser
33
+ })
34
+ : new token_provider_1.StaticTokenProvider(config.gitlab.accessToken);
35
+ const gitlabApiClient = new gitlab_api_client_1.GitLabApiClient({
36
+ apiUrl: config.gitlab.apiUrl,
37
+ tokenProvider
38
+ });
39
+ const server = new mcp_js_1.McpServer({
40
+ name: 'gitlab-mcp-server',
41
+ version: '0.1.0'
42
+ });
43
+ const projectResolver = new project_resolver_1.ProjectResolver(config);
44
+ const issueWorkflowPolicy = new issue_workflow_policy_1.IssueWorkflowPolicy(config);
45
+ (0, register_tools_1.registerTools)(server, {
46
+ config,
47
+ projectResolver,
48
+ issueWorkflowPolicy,
49
+ healthCheckUseCase: new health_check_1.HealthCheckUseCase(),
50
+ createIssueUseCase: new create_issue_1.CreateIssueUseCase(gitlabApiClient),
51
+ getIssueUseCase: new get_issue_1.GetIssueUseCase(gitlabApiClient),
52
+ closeIssueUseCase: new close_issue_1.CloseIssueUseCase(gitlabApiClient),
53
+ updateIssueLabelsUseCase: new update_issue_labels_1.UpdateIssueLabelsUseCase(gitlabApiClient),
54
+ listIssuesUseCase: new list_issues_1.ListIssuesUseCase(gitlabApiClient),
55
+ listLabelsUseCase: new list_labels_1.ListLabelsUseCase(gitlabApiClient),
56
+ ensureLabelsUseCase: new ensure_labels_1.EnsureLabelsUseCase(gitlabApiClient)
57
+ });
58
+ return server;
59
+ }
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTools = registerTools;
4
+ const zod_1 = require("zod");
5
+ const errors_1 = require("../../shared/errors");
6
+ function registerTools(server, deps) {
7
+ server.registerTool('health_check', {
8
+ title: 'Health Check',
9
+ description: 'Returns server status.',
10
+ inputSchema: {
11
+ ping: zod_1.z.string().optional()
12
+ }
13
+ }, async ({ ping }) => {
14
+ return runTool(async () => deps.healthCheckUseCase.execute({ ping }));
15
+ });
16
+ server.registerTool('gitlab_create_issue', {
17
+ title: 'GitLab Create Issue',
18
+ description: 'Create an issue in a GitLab project.',
19
+ inputSchema: {
20
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
21
+ title: zod_1.z.string().min(1),
22
+ description: zod_1.z.string().optional(),
23
+ labels: zod_1.z.array(zod_1.z.string()).optional(),
24
+ assignee_ids: zod_1.z.array(zod_1.z.number()).optional()
25
+ }
26
+ }, async ({ project, title, description, labels, assignee_ids }) => {
27
+ return runTool(async () => {
28
+ deps.issueWorkflowPolicy.assertActionAllowed('create');
29
+ deps.issueWorkflowPolicy.assertLabelsAllowed(labels ?? []);
30
+ const resolvedProject = deps.projectResolver.resolveProject(project);
31
+ const issue = await deps.createIssueUseCase.execute({
32
+ project: resolvedProject,
33
+ title,
34
+ description,
35
+ labels,
36
+ assigneeIds: assignee_ids
37
+ });
38
+ return { resolved_project: resolvedProject, issue };
39
+ });
40
+ });
41
+ server.registerTool('gitlab_get_issue', {
42
+ title: 'GitLab Get Issue',
43
+ description: 'Get issue details by issue IID.',
44
+ inputSchema: {
45
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
46
+ issue_iid: zod_1.z.number().int().positive()
47
+ }
48
+ }, async ({ project, issue_iid }) => {
49
+ return runTool(async () => {
50
+ deps.issueWorkflowPolicy.assertEnabled();
51
+ const resolvedProject = deps.projectResolver.resolveProject(project);
52
+ const issue = await deps.getIssueUseCase.execute({
53
+ project: resolvedProject,
54
+ issueIid: issue_iid
55
+ });
56
+ return { resolved_project: resolvedProject, issue };
57
+ });
58
+ });
59
+ server.registerTool('gitlab_close_issue', {
60
+ title: 'GitLab Close Issue',
61
+ description: 'Close an issue by issue IID.',
62
+ inputSchema: {
63
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
64
+ issue_iid: zod_1.z.number().int().positive()
65
+ }
66
+ }, async ({ project, issue_iid }) => {
67
+ return runTool(async () => {
68
+ deps.issueWorkflowPolicy.assertActionAllowed('close');
69
+ const resolvedProject = deps.projectResolver.resolveProject(project);
70
+ const issue = await deps.closeIssueUseCase.execute({
71
+ project: resolvedProject,
72
+ issueIid: issue_iid
73
+ });
74
+ return { resolved_project: resolvedProject, issue };
75
+ });
76
+ });
77
+ server.registerTool('gitlab_update_issue_labels', {
78
+ title: 'GitLab Update Issue Labels',
79
+ description: 'Update labels on an issue.',
80
+ inputSchema: {
81
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
82
+ issue_iid: zod_1.z.number().int().positive(),
83
+ mode: zod_1.z.enum(['replace', 'add', 'remove']),
84
+ labels: zod_1.z.array(zod_1.z.string()).min(1)
85
+ }
86
+ }, async ({ project, issue_iid, mode, labels }) => {
87
+ return runTool(async () => {
88
+ deps.issueWorkflowPolicy.assertActionAllowed('label_update');
89
+ deps.issueWorkflowPolicy.assertLabelsAllowed(labels);
90
+ const resolvedProject = deps.projectResolver.resolveProject(project);
91
+ const stateLabels = deps.config.issueWorkflow.allowedLabels;
92
+ const issue = await deps.updateIssueLabelsUseCase.execute({
93
+ project: resolvedProject,
94
+ issueIid: issue_iid,
95
+ mode,
96
+ labels,
97
+ autoRemovePreviousStateLabels: deps.config.issueWorkflow.autoRemovePreviousStateLabels,
98
+ stateLabels
99
+ });
100
+ return { resolved_project: resolvedProject, issue };
101
+ });
102
+ });
103
+ server.registerTool('gitlab_list_issues', {
104
+ title: 'GitLab List Issues',
105
+ description: 'List issues in a project.',
106
+ inputSchema: {
107
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
108
+ state: zod_1.z.enum(['opened', 'closed', 'all']).optional(),
109
+ search: zod_1.z.string().optional(),
110
+ labels: zod_1.z.array(zod_1.z.string()).optional(),
111
+ per_page: zod_1.z.number().int().min(1).max(100).optional(),
112
+ page: zod_1.z.number().int().min(1).optional()
113
+ }
114
+ }, async ({ project, state, search, labels, per_page, page }) => {
115
+ return runTool(async () => {
116
+ deps.issueWorkflowPolicy.assertEnabled();
117
+ if (labels) {
118
+ deps.issueWorkflowPolicy.assertLabelsAllowed(labels);
119
+ }
120
+ const resolvedProject = deps.projectResolver.resolveProject(project);
121
+ const issues = await deps.listIssuesUseCase.execute({
122
+ project: resolvedProject,
123
+ state,
124
+ search,
125
+ labels,
126
+ perPage: per_page,
127
+ page
128
+ });
129
+ return { resolved_project: resolvedProject, issues };
130
+ });
131
+ });
132
+ server.registerTool('gitlab_list_labels', {
133
+ title: 'GitLab List Labels',
134
+ description: 'List labels for a project.',
135
+ inputSchema: {
136
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
137
+ search: zod_1.z.string().optional()
138
+ }
139
+ }, async ({ project, search }) => {
140
+ return runTool(async () => {
141
+ deps.issueWorkflowPolicy.assertEnabled();
142
+ const resolvedProject = deps.projectResolver.resolveProject(project);
143
+ const labels = await deps.listLabelsUseCase.execute({
144
+ project: resolvedProject,
145
+ search
146
+ });
147
+ return { resolved_project: resolvedProject, labels };
148
+ });
149
+ });
150
+ server.registerTool('gitlab_ensure_labels', {
151
+ title: 'GitLab Ensure Labels',
152
+ description: 'Create missing labels in a project.',
153
+ inputSchema: {
154
+ project: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
155
+ labels: zod_1.z
156
+ .array(zod_1.z.object({
157
+ name: zod_1.z.string().min(1),
158
+ color: zod_1.z.string().optional(),
159
+ description: zod_1.z.string().optional()
160
+ }))
161
+ .min(1)
162
+ }
163
+ }, async ({ project, labels }) => {
164
+ return runTool(async () => {
165
+ deps.issueWorkflowPolicy.assertActionAllowed('label_update');
166
+ deps.issueWorkflowPolicy.assertLabelsAllowed(labels.map((label) => label.name));
167
+ const resolvedProject = deps.projectResolver.resolveProject(project);
168
+ const result = await deps.ensureLabelsUseCase.execute({
169
+ project: resolvedProject,
170
+ labels
171
+ });
172
+ return { resolved_project: resolvedProject, ...result };
173
+ });
174
+ });
175
+ }
176
+ async function runTool(execute) {
177
+ try {
178
+ const data = await execute();
179
+ return asJsonResult({ ok: true, data });
180
+ }
181
+ catch (error) {
182
+ const message = error instanceof errors_1.PolicyError
183
+ ? error.message
184
+ : error instanceof Error
185
+ ? error.message
186
+ : 'Unexpected tool error';
187
+ return asJsonResult({ ok: false, error: message });
188
+ }
189
+ }
190
+ function asJsonResult(payload) {
191
+ return {
192
+ content: [
193
+ {
194
+ type: 'text',
195
+ text: JSON.stringify(payload)
196
+ }
197
+ ]
198
+ };
199
+ }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadConfig = loadConfig;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_os_1 = require("node:os");
6
+ const node_path_1 = require("node:path");
7
+ const zod_1 = require("zod");
8
+ const DEFAULT_TOKEN_STORE_PATH = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'gitlab-mcp', 'token.json');
9
+ const EnvSchema = zod_1.z.object({
10
+ GITLAB_API_URL: zod_1.z.string().url().default('https://gitlab.com/api/v4'),
11
+ GITLAB_AUTH_MODE: zod_1.z.enum(['oauth', 'pat']).default('oauth'),
12
+ GITLAB_OAUTH_ACCESS_TOKEN: zod_1.z.string().optional(),
13
+ GITLAB_OAUTH_CLIENT_ID: zod_1.z.string().optional(),
14
+ GITLAB_OAUTH_CLIENT_SECRET: zod_1.z.string().optional(),
15
+ GITLAB_OAUTH_REDIRECT_URI: zod_1.z.string().optional(),
16
+ GITLAB_OAUTH_SCOPES: zod_1.z.string().default('api'),
17
+ GITLAB_OAUTH_TOKEN_STORE_PATH: zod_1.z.string().default(DEFAULT_TOKEN_STORE_PATH),
18
+ GITLAB_OAUTH_AUTO_LOGIN: zod_1.z
19
+ .enum(['true', 'false'])
20
+ .optional()
21
+ .transform((value) => value !== 'false'),
22
+ GITLAB_OAUTH_OPEN_BROWSER: zod_1.z
23
+ .enum(['true', 'false'])
24
+ .optional()
25
+ .transform((value) => value !== 'false'),
26
+ GITLAB_PAT: zod_1.z.string().optional(),
27
+ GITLAB_DEFAULT_PROJECT: zod_1.z.string().optional(),
28
+ GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT: zod_1.z
29
+ .enum(['true', 'false'])
30
+ .optional()
31
+ .transform((value) => value !== 'false'),
32
+ ISSUE_WORKFLOW_ENABLED: zod_1.z
33
+ .enum(['true', 'false'])
34
+ .optional()
35
+ .transform((value) => value !== 'false'),
36
+ ISSUE_WORKFLOW_ALLOW_CREATE: zod_1.z
37
+ .enum(['true', 'false'])
38
+ .optional()
39
+ .transform((value) => value !== 'false'),
40
+ ISSUE_WORKFLOW_ALLOW_CLOSE: zod_1.z
41
+ .enum(['true', 'false'])
42
+ .optional()
43
+ .transform((value) => value !== 'false'),
44
+ ISSUE_WORKFLOW_ALLOW_LABEL_UPDATE: zod_1.z
45
+ .enum(['true', 'false'])
46
+ .optional()
47
+ .transform((value) => value !== 'false'),
48
+ ISSUE_WORKFLOW_ALLOWED_LABELS: zod_1.z.string().optional(),
49
+ ISSUE_WORKFLOW_AUTO_REMOVE_PREVIOUS_STATE_LABELS: zod_1.z
50
+ .enum(['true', 'false'])
51
+ .optional()
52
+ .transform((value) => value !== 'false')
53
+ });
54
+ function loadConfig() {
55
+ const env = EnvSchema.parse(process.env);
56
+ const autoDetectedProject = env.GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT
57
+ ? detectProjectFromGitRemote()
58
+ : undefined;
59
+ return {
60
+ gitlab: {
61
+ apiUrl: env.GITLAB_API_URL,
62
+ authMode: env.GITLAB_AUTH_MODE,
63
+ accessToken: resolveAccessToken(env),
64
+ oauth: {
65
+ clientId: env.GITLAB_OAUTH_CLIENT_ID,
66
+ clientSecret: env.GITLAB_OAUTH_CLIENT_SECRET,
67
+ redirectUri: env.GITLAB_OAUTH_REDIRECT_URI,
68
+ scopes: splitCsv(env.GITLAB_OAUTH_SCOPES),
69
+ tokenStorePath: env.GITLAB_OAUTH_TOKEN_STORE_PATH,
70
+ autoLogin: env.GITLAB_OAUTH_AUTO_LOGIN,
71
+ openBrowser: env.GITLAB_OAUTH_OPEN_BROWSER
72
+ },
73
+ defaultProject: env.GITLAB_DEFAULT_PROJECT,
74
+ autoResolveProjectFromGit: env.GITLAB_AUTO_RESOLVE_PROJECT_FROM_GIT,
75
+ autoDetectedProject
76
+ },
77
+ issueWorkflow: {
78
+ enabled: env.ISSUE_WORKFLOW_ENABLED,
79
+ allowCreate: env.ISSUE_WORKFLOW_ALLOW_CREATE,
80
+ allowClose: env.ISSUE_WORKFLOW_ALLOW_CLOSE,
81
+ allowLabelUpdate: env.ISSUE_WORKFLOW_ALLOW_LABEL_UPDATE,
82
+ allowedLabels: splitCsv(env.ISSUE_WORKFLOW_ALLOWED_LABELS),
83
+ autoRemovePreviousStateLabels: env.ISSUE_WORKFLOW_AUTO_REMOVE_PREVIOUS_STATE_LABELS
84
+ }
85
+ };
86
+ }
87
+ function resolveAccessToken(env) {
88
+ if (env.GITLAB_AUTH_MODE === 'oauth') {
89
+ return env.GITLAB_OAUTH_ACCESS_TOKEN;
90
+ }
91
+ return env.GITLAB_PAT;
92
+ }
93
+ function splitCsv(value) {
94
+ if (!value) {
95
+ return [];
96
+ }
97
+ return value
98
+ .split(',')
99
+ .map((entry) => entry.trim())
100
+ .filter((entry) => entry.length > 0);
101
+ }
102
+ function detectProjectFromGitRemote() {
103
+ try {
104
+ const origin = (0, node_child_process_1.execSync)('git config --get remote.origin.url', {
105
+ stdio: ['ignore', 'pipe', 'ignore']
106
+ })
107
+ .toString()
108
+ .trim();
109
+ return parseProjectPathFromRemote(origin);
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
115
+ function parseProjectPathFromRemote(remote) {
116
+ const sshMatch = remote.match(/^[^@]+@[^:]+:(.+?)(?:\.git)?$/);
117
+ if (sshMatch?.[1]) {
118
+ return sshMatch[1];
119
+ }
120
+ const httpsMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
121
+ if (httpsMatch?.[1]) {
122
+ return httpsMatch[1];
123
+ }
124
+ return undefined;
125
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigurationError = exports.PolicyError = void 0;
4
+ class PolicyError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'PolicyError';
8
+ }
9
+ }
10
+ exports.PolicyError = PolicyError;
11
+ class ConfigurationError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'ConfigurationError';
15
+ }
16
+ }
17
+ exports.ConfigurationError = ConfigurationError;
package/docs/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Docs
2
+
3
+ Документация разделена на два блока:
4
+
5
+ - `docs/agent/` — настройки ИИ-агента (роли, правила, запреты, commit policy).
6
+ - `docs/architecture/` — архитектурные решения и структура модулей.
7
+ - `docs/tasks/` — задачи для агента и статус выполнения.
8
+ - `docs/agent/context7-policy.md` — регламент использования актуальной документации.
9
+ - `docs/agent/stack-decision.md` — выбранный стек и решение по framework.
10
+ - `docs/architecture/issue-tools-v0.md` — контракт MCP tools для issue/labels workflow.
11
+ - `docs/architecture/auth-strategy.md` — стратегия аутентификации (OAuth-first).
12
+ - `docs/USER_GUIDE.md` — инструкция для конечного пользователя.
13
+ - `docs/GITHUB_DESCRIPTION.md` — тексты для оформления GitHub-репозитория.
14
+ - `docs/RELEASE_CHECKLIST.md` — чеклист первого релиза (GitHub + npm).
15
+
16
+ Точка входа для агента: `AGENTS.md` в корне репозитория.