@youcloud/mantis-mcp 0.0.0-stage → 1.0.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,126 @@
1
+ import { z } from 'zod';
2
+ import { RestError } from '../client.js';
3
+ import { ConnectionConfigError } from '../connection-config.js';
4
+ export const id = z.number().int().positive().describe('正整数编号');
5
+ export const text = z.string().refine(v => v.trim().length > 0, '不可为空白').describe('非空正文');
6
+ const ref = z.union([z.literal('me'), id]).describe('当前用户 me 或明确的用户编号');
7
+ const iso = z.iso.datetime({ offset: true }).describe('含时区的 ISO8601 时间');
8
+ const evidence = z.array(z.strictObject({ kind: z.enum(['url', 'text']), value: text }));
9
+ const environment = { environment: text.optional(), version_id: id.optional() };
10
+ const validationContext = z.strictObject(environment).refine(v => v.environment || v.version_id, '须提供环境或版本');
11
+ const common = {
12
+ operation_id: z.uuid().optional().describe('同一次执行及恢复保持不变的 UUID;真实写入必填'),
13
+ dry_run: z.boolean().default(false).describe('只读预览,不验证写入权限'),
14
+ resume: z.boolean().default(false).describe('恢复同一操作;不得变更原业务参数'),
15
+ expected_revision: text.optional().describe('context 返回的并发前置标识;修改已有对象必填'),
16
+ };
17
+ const notes = { note_view_state: z.enum(['public', 'private']).optional().describe('备注可见性;缺省使用项目策略') };
18
+ const fields = { custom_fields: z.array(z.strictObject({ id, value: z.string() })).optional().describe('已登记的业务自定义字段') };
19
+ const issue = { ...common, issue_id: id };
20
+ const lifecycle = { ...issue, ...notes, ...fields };
21
+ const versionFields = { name: text.optional(), description: z.string().optional(), released: z.boolean().optional(),
22
+ obsolete: z.boolean().optional(), timestamp: z.union([iso, z.iso.date()]).optional() };
23
+ export const schemas = {
24
+ get_project_context: z.strictObject({ project_id: id.optional(),
25
+ sections: z.array(z.enum(['categories', 'versions', 'members', 'filters', 'enums', 'workflow', 'custom_fields', 'uploads'])).optional(),
26
+ member_query: text.optional(), limit: z.number().int().min(1).max(100).default(20), cursor: text.optional() }),
27
+ list_issues: z.strictObject({ project_id: id, assignee: ref.optional(), reporter: ref.optional(),
28
+ queue: z.enum(['unresolved', 'awaiting_verification', 'postponed']).optional(), filter_id: id.optional(),
29
+ updated_after: iso.optional(), updated_before: iso.optional(), limit: z.number().int().min(1).max(100).default(20),
30
+ cursor: text.optional() }).refine(v => !v.updated_after || !v.updated_before || Date.parse(v.updated_after) <= Date.parse(v.updated_before), '时间区间反向'),
31
+ get_issue_context: z.strictObject({ issue_id: id, sections: z.array(z.enum(['description', 'notes', 'attachments', 'relationships', 'tags', 'monitors'])).optional(),
32
+ max_chars: z.number().int().min(2000).max(80000).default(20000), cursor: text.optional(), previous_revision: text.optional() })
33
+ .refine(v => !(v.cursor && v.previous_revision), '续读和版本比较不能同时使用'),
34
+ get_workflow_operation: z.strictObject({ operation_id: z.uuid() }),
35
+ report_issue: z.strictObject({ ...common, ...notes, ...fields, project_id: id, category_id: id, summary: text,
36
+ description: text, expected_behavior: text, actual_behavior: text, steps_to_reproduce: text.optional(), environment: text.optional(),
37
+ priority_id: id.optional(), severity_id: id.optional(), reproducibility_id: id.optional(), version_id: id.optional() }),
38
+ accept_issue: z.strictObject({ ...lifecycle, reason: text, assignee: ref.optional(), priority_id: id.optional() }),
39
+ assign_issue: z.strictObject({ ...lifecycle, assignee: ref, reason: text }),
40
+ submit_fix: z.strictObject({ ...lifecycle, fix_summary: text,
41
+ self_test: z.strictObject({ result: z.literal('passed'), summary: text, evidence: evidence.optional() }),
42
+ root_cause: text.optional(), code_refs: z.array(z.strictObject({ type: z.enum(['commit', 'merge_request']), value: text })).optional(),
43
+ fixed_in_version_id: id.optional() }),
44
+ close_issue: z.strictObject({ ...lifecycle, verification: z.strictObject({ result: z.literal('passed'), summary: text,
45
+ ...environment, evidence: evidence.optional() }).refine(v => v.environment || v.version_id, '须提供验收环境或版本') }),
46
+ reopen_issue: z.strictObject({ ...lifecycle, reason_code: z.enum(['verification_failed', 'recurrence', 'resume_postponed']), reason: text,
47
+ actual_behavior: text.optional(), verification_context: validationContext.optional(), evidence: evidence.optional() })
48
+ .refine(v => v.reason_code === 'resume_postponed' || (v.actual_behavior && v.verification_context), '复测失败或复发须描述实际现象及验证环境'),
49
+ reject_issue: z.strictObject({ ...lifecycle, reason_code: z.enum(['not_a_bug', 'wont_fix', 'cannot_reproduce', 'cannot_fix']), reason: text }),
50
+ postpone_issue: z.strictObject({ ...lifecycle, reason: text, target_version_id: id.optional(), review_on: z.iso.date().optional() }),
51
+ add_note: z.strictObject({ ...issue, ...notes, text }),
52
+ upload_file: z.strictObject({ ...issue, file_path: text, filename: text.optional(), expected_file_sha256: z.string().regex(/^[a-fA-F0-9]{64}$/).optional() }),
53
+ delete_issue: z.strictObject(issue),
54
+ delete_note: z.strictObject({ ...issue, note_id: id }),
55
+ remove_relationship: z.strictObject({ ...issue, relationship_id: id }),
56
+ remove_monitor: z.strictObject({ ...issue, username: text }),
57
+ detach_tag: z.strictObject({ ...issue, tag_id: id }),
58
+ create_version: z.strictObject({ ...common, project_id: id, ...versionFields, name: text }),
59
+ update_version: z.strictObject({ ...common, project_id: id, version_id: id, ...versionFields })
60
+ .refine(v => Object.keys(versionFields).some(k => v[k] !== undefined), '至少提供一个版本字段'),
61
+ release_version: z.strictObject({ ...common, project_id: id, version_id: id, timestamp: versionFields.timestamp, next_version: text.optional() }),
62
+ delete_version: z.strictObject({ ...common, project_id: id, version_id: id }),
63
+ };
64
+ export const defaultTools = ['get_project_context', 'list_issues', 'get_issue_context', 'report_issue', 'accept_issue', 'assign_issue',
65
+ 'submit_fix', 'reopen_issue', 'reject_issue', 'postpone_issue', 'add_note', 'upload_file', 'get_workflow_operation'];
66
+ export const adminTools = ['close_issue', 'delete_issue', 'delete_note', 'remove_relationship', 'remove_monitor', 'detach_tag',
67
+ 'create_version', 'update_version', 'release_version', 'delete_version'];
68
+ export const readTools = new Set(['get_project_context', 'list_issues', 'get_issue_context', 'get_workflow_operation']);
69
+ export class WorkflowError extends Error {
70
+ code;
71
+ conflict;
72
+ constructor(code, message, conflict = false) {
73
+ super(message);
74
+ this.code = code;
75
+ this.conflict = conflict;
76
+ }
77
+ }
78
+ export function problem(error) {
79
+ if (error instanceof WorkflowError)
80
+ return { code: error.code, message: error.message };
81
+ if (error instanceof ConnectionConfigError)
82
+ return { code: error.code, message: error.message };
83
+ if (error instanceof z.ZodError)
84
+ return { code: 'INVALID_INPUT', message: '输入不符合业务契约,请核对必填字段、类型及额外字段' };
85
+ if (error instanceof RestError)
86
+ return { code: !error.requestSent ? 'REQUEST_PREPARATION_FAILED'
87
+ : error.status === 403 || error.status === 401 ? 'MANTIS_FORBIDDEN'
88
+ : error.status === 404 ? 'NOT_FOUND_OR_NOT_VISIBLE' : error.status === 412 ? 'REVISION_CONFLICT'
89
+ : !error.definiteRejection ? error.method === 'GET' ? 'SERVICE_UNAVAILABLE' : 'WRITE_OUTCOME_UNKNOWN' : 'MANTIS_REJECTED', message: error.message };
90
+ return { code: 'SERVICE_UNAVAILABLE', message: '本地服务或读取不可用;未包含敏感错误正文' };
91
+ }
92
+ const record = z.record(z.string(), z.unknown());
93
+ export const stepSchema = z.strictObject({ name: z.string(), state: z.enum(['not_started', 'in_flight', 'succeeded', 'failed', 'unknown']),
94
+ evidence: z.string().optional(), error_code: z.string().optional(), server_id: z.number().optional(),
95
+ http_status: z.number().int().nullable().optional(), request_sent: z.boolean().optional() });
96
+ export const writeOutput = z.strictObject({ schema_version: z.literal(1), action: z.string(), operation_id: z.string().nullable(),
97
+ outcome: z.enum(['preview', 'in_progress', 'succeeded', 'rejected', 'conflict', 'partial_success', 'unknown']),
98
+ subject: record, result: record.nullable(), steps: z.array(stepSchema), verification: record,
99
+ recovery: z.strictObject({ action: z.enum(['none', 'retry_same_operation', 'read_again', 'manual_review']), automatic_retry: z.literal(false),
100
+ tool: z.string().optional(), resume: z.boolean().optional(), resume_steps: z.array(z.string()).optional() }),
101
+ warnings: z.array(z.string()), error: z.strictObject({ code: z.string(), message: z.string() }).optional(), plan: record.optional(),
102
+ current_observation: record.optional() });
103
+ export const readOutput = z.object({ schema_version: z.literal(1), error: z.strictObject({ code: z.string(), message: z.string() }).optional() }).catchall(z.unknown());
104
+ const nullableCursor = z.string().nullable();
105
+ const queryResult = z.object({ schema_version: z.literal(1), items: z.array(record), returned_count: z.number().int().nonnegative(),
106
+ scanned_count: z.number().int().min(0).max(500), matched_in_scan_count: z.number().int().nonnegative(), scan_limit_reached: z.boolean(),
107
+ source_exhausted: z.boolean(), next_cursor: nullableCursor, filter_execution: record,
108
+ sort_execution: z.enum(['verified_updated_desc', 'source_order_unverified']), warnings: z.array(z.string()),
109
+ total_count: z.null(), snapshot_consistent: z.literal(false) });
110
+ const issueResult = z.object({ schema_version: z.literal(1), issue: record, sections: record, expected_revision: z.string(),
111
+ revision_expires_at: z.iso.datetime(), revision_comparison: z.enum(['not_requested', 'unchanged', 'changed', 'unavailable']),
112
+ concurrency_mode: z.enum(['snapshot_precheck', 'conditional_request']), supported_actions: z.array(z.string()), unconfigured_actions: z.array(record), warnings: z.array(z.string()) });
113
+ const projectResult = z.object({ schema_version: z.literal(1), current_user: record, extensions_enabled: z.boolean(),
114
+ project: record.optional(), projects: record.optional(), sections: record.optional() }).refine(v => !!v.project !== !!v.projects);
115
+ export function readOutputFor(name) {
116
+ const success = name === 'list_issues' ? queryResult : name === 'get_issue_context' ? issueResult : name === 'get_project_context' ? projectResult : writeOutput;
117
+ // 错误同样是 object;成功字段在处理器中强校验,避免 SDK 对 isError 跳过输出校验。
118
+ return readOutput.refine(value => !!value.error || success.safeParse(value).success, '领域输出不符合工具契约');
119
+ }
120
+ export function failure(action, input, error) {
121
+ const info = problem(error);
122
+ return { schema_version: 1, action, operation_id: input.operation_id ?? null,
123
+ outcome: error instanceof WorkflowError && error.conflict || info.code === 'REVISION_CONFLICT' ? 'conflict' : 'rejected',
124
+ subject: { issue_id: input.issue_id ?? null, project_id: input.project_id ?? null }, result: null, steps: [],
125
+ verification: { status: 'unverified', concurrency_mode: 'snapshot_precheck' }, recovery: { action: 'none', automatic_retry: false }, warnings: [], error: info };
126
+ }