draftgo-cli 3.0.38 → 3.0.39

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draftgo-cli",
3
- "version": "3.0.38",
3
+ "version": "3.0.39",
4
4
  "description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro).",
5
5
  "bin": {
6
6
  "draftgo": "bin/draftgo.js"
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1.0",
3
3
  "id": "draftgo",
4
4
  "name": "DraftGo 开发助手",
5
- "version": "3.0.38",
5
+ "version": "3.0.39",
6
6
  "entry": "SKILL.md",
7
7
  "description": "以 Skill/reference 优先路由、MCP 实时发现、长正文 checkout/commit、验证、本地运行与 Skill 分发的 DraftGo 工作流。",
8
8
  "license": "MIT",
package/src/mcp/tools.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { DraftGoMcpClient } = require('./client');
4
4
  const { WorktreeError } = require('../worktree/errors');
5
- const { unwrapToolResult, toolNameMatches } = require('../worktree/backend');
5
+ const { unwrapToolResult, unwrapProtectedResult, toolNameMatches } = require('../worktree/backend');
6
6
 
7
7
  const TOOL_NAMES = Object.freeze({
8
8
  projectOverview: 'draftgo_project_overview',
@@ -15,6 +15,13 @@ const TOOL_NAMES = Object.freeze({
15
15
  apiCall: 'draftgo_api_call',
16
16
  });
17
17
 
18
+ const PROTECTED_RESOURCE_TOOLS = new Set([
19
+ TOOL_NAMES.resourceList,
20
+ TOOL_NAMES.resourceSearch,
21
+ TOOL_NAMES.resourceMetadata,
22
+ TOOL_NAMES.resourceFragment,
23
+ ]);
24
+
18
25
  async function openToolSession(config, expected = [], options = {}) {
19
26
  const client = options.client || new DraftGoMcpClient(config);
20
27
  await client.initialize(options);
@@ -31,7 +38,10 @@ async function openToolSession(config, expected = [], options = {}) {
31
38
  async function callStructured(session, expectedName, args = {}, options = {}) {
32
39
  const name = session.names[expectedName] || expectedName;
33
40
  const result = await session.client.toolsCall(name, args, options);
34
- return unwrapToolResult(result);
41
+ const data = unwrapToolResult(result);
42
+ return PROTECTED_RESOURCE_TOOLS.has(expectedName)
43
+ ? unwrapProtectedResult(data, expectedName)
44
+ : data;
35
45
  }
36
46
 
37
47
  module.exports = { TOOL_NAMES, openToolSession, callStructured };
@@ -24,25 +24,111 @@ function toolNameMatches(name, expected) {
24
24
 
25
25
  function parseJsonText(text, label) {
26
26
  try { return JSON.parse(text); } catch {
27
- throw new WorktreeError('INVALID_BACKEND_RESPONSE', `${label} did not return structured JSON metadata.`);
27
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', `${label} did not return structured JSON.`);
28
28
  }
29
29
  }
30
30
 
31
- function unwrapToolResult(result) {
31
+ function hasOwn(value, key) {
32
+ return Object.prototype.hasOwnProperty.call(value, key);
33
+ }
34
+
35
+ function isMcpPayload(value) {
36
+ return isObject(value) && (
37
+ hasOwn(value, 'jsonrpc')
38
+ || hasOwn(value, 'result')
39
+ || hasOwn(value, 'structuredContent')
40
+ || hasOwn(value, 'structured_content')
41
+ || hasOwn(value, 'content')
42
+ || hasOwn(value, 'isError')
43
+ || hasOwn(value, 'ok')
44
+ );
45
+ }
46
+
47
+ function toolError(envelope, fallbackCode, fallbackMessage) {
48
+ const error = isObject(envelope && envelope.error) ? envelope.error : {};
49
+ return new WorktreeError(
50
+ String(firstValue(error.code, fallbackCode)),
51
+ String(firstValue(error.message, fallbackMessage)),
52
+ isObject(error.details) ? error.details : {},
53
+ );
54
+ }
55
+
56
+ function unwrapToolResult(payload) {
57
+ if (!isObject(payload)) {
58
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool returned no structured result.');
59
+ }
60
+
61
+ let result = payload;
62
+ if (hasOwn(payload, 'jsonrpc') || hasOwn(payload, 'result')) {
63
+ if (isObject(payload.error)) {
64
+ throw toolError({ error: payload.error }, 'MCP_RPC_ERROR', 'DraftGo MCP request failed.');
65
+ }
66
+ if (!isObject(payload.result)) {
67
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned no tool result.');
68
+ }
69
+ result = payload.result;
70
+ }
71
+
32
72
  if (!isObject(result)) {
33
- throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo metadata tool returned no structured result.');
73
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned an invalid tool result.');
74
+ }
75
+ if (hasOwn(result, 'isError') && typeof result.isError !== 'boolean') {
76
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned an invalid isError value.');
34
77
  }
35
- if (isObject(result.structuredContent)) return result.structuredContent;
36
- if (isObject(result.structured_content)) return result.structured_content;
37
- if (isObject(result.data)) return result.data;
38
- if (isObject(result.metadata) || isObject(result.resource)) return result;
39
- if (Array.isArray(result.content)) {
78
+
79
+ let envelope;
80
+ if (hasOwn(result, 'structuredContent')) {
81
+ if (!isObject(result.structuredContent)) {
82
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned invalid structuredContent.');
83
+ }
84
+ envelope = result.structuredContent;
85
+ } else if (hasOwn(result, 'structured_content')) {
86
+ if (!isObject(result.structured_content)) {
87
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned invalid structured_content.');
88
+ }
89
+ envelope = result.structured_content;
90
+ } else if (Array.isArray(result.content)) {
40
91
  const textPart = result.content.find((part) => part && part.type === 'text' && typeof part.text === 'string');
41
92
  if (textPart && Buffer.byteLength(textPart.text, 'utf8') <= MAX_JSON_BYTES) {
42
- return parseJsonText(textPart.text, 'DraftGo metadata tool');
93
+ envelope = parseJsonText(textPart.text, 'DraftGo MCP tool');
43
94
  }
95
+ } else if (hasOwn(result, 'ok')) {
96
+ envelope = result;
97
+ }
98
+
99
+ if (!isObject(envelope)) {
100
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool returned no structured JSON result.');
101
+ }
102
+ if (result.isError === true) {
103
+ throw toolError(envelope, 'MCP_TOOL_ERROR', 'DraftGo MCP tool reported an error.');
104
+ }
105
+ if (envelope.ok !== true) {
106
+ throw toolError(envelope, 'INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool did not report success.');
44
107
  }
45
- return result;
108
+ return envelope.data;
109
+ }
110
+
111
+ function unwrapProtectedResult(data, label = 'DraftGo resource tool') {
112
+ if (!isObject(data) || !hasOwn(data, 'value') || typeof data.omitted !== 'boolean') {
113
+ throw new WorktreeError(
114
+ 'INVALID_PROTECTED_RESULT',
115
+ `${label} returned an invalid protected result.`,
116
+ );
117
+ }
118
+ if (data.artifacts !== undefined && !Array.isArray(data.artifacts)) {
119
+ throw new WorktreeError(
120
+ 'INVALID_PROTECTED_RESULT',
121
+ `${label} returned invalid artifact descriptors.`,
122
+ );
123
+ }
124
+ if (data.omitted || (Array.isArray(data.artifacts) && data.artifacts.length > 0)) {
125
+ throw new WorktreeError(
126
+ 'PROTECTED_RESULT_OMITTED',
127
+ `${label} omitted fields required by the CLI.`,
128
+ { artifacts: data.artifacts || [] },
129
+ );
130
+ }
131
+ return data.value;
46
132
  }
47
133
 
48
134
  function firstObject(...values) {
@@ -53,12 +139,6 @@ function firstValue(...values) {
53
139
  return values.find((value) => value !== undefined && value !== null && value !== '');
54
140
  }
55
141
 
56
- function fillTemplate(template, resourceType, resourceId) {
57
- return String(template)
58
- .replace(/\{resource_type\}/g, encodeURIComponent(resourceType))
59
- .replace(/\{resource_id\}/g, encodeURIComponent(String(resourceId)));
60
- }
61
-
62
142
  function normalizeUrl(config, value, purpose) {
63
143
  if (!value) return null;
64
144
  let url;
@@ -84,25 +164,20 @@ function normalizeUrl(config, value, purpose) {
84
164
  }
85
165
 
86
166
  function normalizeMetadata(config, resourceType, resourceId, payload) {
87
- const outer = unwrapToolResult(payload);
88
- const data = firstObject(outer.metadata, outer.resource, outer.data, outer);
89
- const links = firstObject(data.links, outer.links, data.urls, outer.urls);
167
+ const data = isMcpPayload(payload)
168
+ ? unwrapProtectedResult(unwrapToolResult(payload), 'DraftGo metadata tool')
169
+ : payload;
170
+ if (!isObject(data)) {
171
+ throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo returned invalid checkout metadata.');
172
+ }
173
+ const checkout = isObject(data.checkout) ? data.checkout : {};
90
174
  const checkoutConfig = firstObject(config.checkout);
91
- const downloadTemplate = checkoutConfig.download_url_template;
92
- const commitTemplate = checkoutConfig.commit_url_template;
93
175
  const version = firstValue(data.base_version, data.version, data.revision, data.base_revision);
94
- const hash = firstValue(data.content_hash, data.hash, data.sha256, data.base_hash);
95
- const contentType = firstValue(data.content_type, data.contentType);
96
- const backendExtension = firstValue(data.file_extension, data.extension);
97
- const downloadRaw = firstValue(
98
- data.download_url, data.downloadUrl, links.download, links.content, links.checkout,
99
- downloadTemplate && fillTemplate(downloadTemplate, resourceType, resourceId),
100
- );
101
- const commitRaw = firstValue(
102
- data.commit_url, data.commitUrl, data.upload_url, data.uploadUrl,
103
- links.commit, links.upload,
104
- commitTemplate && fillTemplate(commitTemplate, resourceType, resourceId),
105
- );
176
+ const hash = data.content_hash;
177
+ const contentType = data.content_type;
178
+ const backendExtension = data.file_extension;
179
+ const downloadRaw = firstValue(checkout.download, data.download_url);
180
+ const commitRaw = firstValue(checkout.commit, data.commit_url);
106
181
 
107
182
  if (!contentType || !hash || version === undefined || !downloadRaw || !commitRaw) {
108
183
  throw new WorktreeError(
@@ -115,18 +190,18 @@ function normalizeMetadata(config, resourceType, resourceId, payload) {
115
190
 
116
191
  return {
117
192
  resource_type: canonicalResourceType(firstValue(data.resource_type, resourceType)),
118
- resource_id: String(firstValue(data.resource_id, data.id, resourceId)),
119
- title: String(firstValue(data.title, data.name, '')),
193
+ resource_id: String(firstValue(data.resource_id, resourceId)),
194
+ title: String(firstValue(data.title, '')),
120
195
  route: firstValue(data.route, null),
121
196
  code: firstValue(data.code, null),
122
197
  slug: firstValue(data.slug, null),
123
198
  content_type: String(contentType),
124
199
  file_extension: normalizeExtension(contentType, backendExtension),
125
- content_size: data.content_size == null && data.size == null ? null : Number(firstValue(data.content_size, data.size)),
200
+ content_size: data.content_size == null ? null : Number(data.content_size),
126
201
  content_hash: normalizedHash,
127
202
  base_version: data.base_version !== undefined ? data.base_version : (data.version !== undefined ? data.version : null),
128
203
  base_revision: data.base_revision !== undefined ? data.base_revision : (data.revision !== undefined ? data.revision : null),
129
- etag: firstValue(data.etag, data.e_tag, null),
204
+ etag: firstValue(data.etag, null),
130
205
  updated_at: firstValue(data.updated_at, null),
131
206
  updated_by: firstValue(data.updated_by, null),
132
207
  download_url: normalizeUrl(config, downloadRaw, 'download'),
@@ -202,7 +277,7 @@ async function commit(config, metadata, localPath, current, options = {}) {
202
277
  if (!metadata.commit_url) {
203
278
  throw new WorktreeError(
204
279
  'COMMIT_URL_UNAVAILABLE',
205
- 'DraftGo metadata did not provide a commit URL and no checkout.commit_url_template is configured.',
280
+ 'DraftGo metadata did not provide a commit URL.',
206
281
  );
207
282
  }
208
283
  if (!['POST', 'PUT', 'PATCH'].includes(metadata.commit_method)) {
@@ -241,6 +316,7 @@ module.exports = {
241
316
  METADATA_TOOL,
242
317
  toolNameMatches,
243
318
  unwrapToolResult,
319
+ unwrapProtectedResult,
244
320
  normalizeMetadata,
245
321
  resolveMetadata,
246
322
  download,