draftgo-cli 3.0.48 → 3.0.49
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/README.md +8 -2
- package/package.json +1 -1
- package/resources/skill/SKILL.md +2 -2
- package/resources/skill/manifest.json +1 -1
- package/resources/skill/references/checkout.md +2 -1
- package/src/cli.js +2 -0
- package/src/commandRegistry.js +1 -0
- package/src/commands/api.js +102 -0
- package/src/commands/autoPush.js +1 -1
- package/src/commands/check.js +24 -2
- package/src/commands/commit.js +31 -5
- package/src/commands/deploy.js +1 -1
- package/src/commands/help.js +10 -2
- package/src/commands/map.js +20 -22
- package/src/commands/mcp.js +26 -3
- package/src/commands/reconcile.js +20 -0
- package/src/commands/verifyUi.js +92 -10
- package/src/mcp/client.js +52 -19
- package/src/projectMap.js +7 -2
- package/src/worktree/index.js +272 -59
- package/src/worktree/status.js +122 -0
package/README.md
CHANGED
|
@@ -170,6 +170,7 @@ draftgo context --task frontend --output json
|
|
|
170
170
|
draftgo checkout pages 42 43
|
|
171
171
|
# 独立资源可并行编辑;同一资源只由一个 Agent 修改
|
|
172
172
|
draftgo check
|
|
173
|
+
draftgo check --remote # 交付前按需对比远端版本/hash
|
|
173
174
|
draftgo verify-ui http://localhost:5173 # UI 变更时
|
|
174
175
|
draftgo diff pages 42
|
|
175
176
|
draftgo diff pages 43
|
|
@@ -203,9 +204,10 @@ draftgo changelog add "完成文档管理系统及角色权限"
|
|
|
203
204
|
| `draftgo context --task <task> --output json` | 精确读取任务 Reference 章节,并复用一个 MCP 会话并行查询项目、资源、API 和数据结构;保留原始结果及来源。 |
|
|
204
205
|
| `draftgo map` | 并行读取 pages、navigations、docs/articles 元数据,再叠加本地 checkout 状态;`--type` 可限制一种类型。 |
|
|
205
206
|
| `draftgo checkout <pages|nav|docs> <id...>` | 下载完整正文和 base,校验 hash 后原子落盘;`--force` 可显式覆盖已修改的本地正文。 |
|
|
206
|
-
| `draftgo check` |
|
|
207
|
+
| `draftgo check` | 默认只检查本地 checkout;`--remote` 额外对比 local/base/manifest/remote 的版本和 hash。 |
|
|
207
208
|
| `draftgo diff <pages|nav|docs> <id>` | 显示 checkout base 与当前本地正文的差异。 |
|
|
208
209
|
| `draftgo commit <pages|nav|docs> <id...>` | 本地校验后,通过专用 HTTP 上传完整正文并更新 base/manifest。 |
|
|
210
|
+
| `draftgo reconcile <pages|nav|docs> <id...>` | 仅当 local 与经校验的 remote 字节一致时,安全修复落后的 base/manifest 元数据。 |
|
|
209
211
|
| `draftgo conflicts` | 列出未解决冲突;存在冲突时返回非零。`--all` 包括已解决记录。 |
|
|
210
212
|
| `draftgo conflict show <type> <id>` | 显示冲突的 base/local/remote/worktree 路径。 |
|
|
211
213
|
| `draftgo conflict resolve <type> <id>` | 确认 worktree 已合并,把保留的 remote 设为新 base;随后重新 check、diff、commit。 |
|
|
@@ -220,6 +222,7 @@ draftgo changelog add "完成文档管理系统及角色权限"
|
|
|
220
222
|
| `draftgo api <query>` | 通过 MCP `api_search` 查询实时 API 契约。 |
|
|
221
223
|
| `draftgo api search <query>` | `draftgo api <query>` 的显式搜索形式。 |
|
|
222
224
|
| `draftgo api describe <operation_id>` | 通过 MCP `api_describe` 查看指定操作。 |
|
|
225
|
+
| `draftgo api call <operation_id> --input <file>` | 描述后调用实时 operation;JSON 文件严格按 UTF-8 解码并输出状态码、服务端 code 和 request ID。 |
|
|
223
226
|
| `draftgo delete <operation_id> [id]` | 描述并确认后调用实时 MCP `api_call`;支持 `--params JSON`、`--input file` 和 `--yes`。 |
|
|
224
227
|
| `draftgo deploy [<type> <id...>]` | 先 `check`,再按 delivery 模式检查 diff 或 commit。仅 `--delivery local` 可省略 type/id。 |
|
|
225
228
|
| `draftgo auto-push [<type> <id...>]` | `auto_push: true` 时检查并 commit 已 checkout 的变更;有冲突立即停止。 |
|
|
@@ -321,7 +324,7 @@ checkout 会通过 MCP 获取元数据和专用下载地址,流式写入临时
|
|
|
321
324
|
}
|
|
322
325
|
```
|
|
323
326
|
|
|
324
|
-
`draftgo check --output json` 返回 `{ map, errors, warnings, warningDetails, legacy_cache_ignored }`。`--strict` 会把 warnings
|
|
327
|
+
`draftgo check --output json` 返回 `{ map, errors, warnings, warningDetails, remote, remote_error, legacy_cache_ignored }`。`--strict` 会把 warnings 也视为失败。默认 `check` 不访问远端;显式使用 `--remote` 时读取每个 checkout 的实时 metadata,并把 `committed_unrecorded` 等可恢复状态指向 `draftgo reconcile`。它始终不会读取旧全量资源索引。
|
|
325
328
|
|
|
326
329
|
## Skill 与领域约定
|
|
327
330
|
|
|
@@ -343,6 +346,7 @@ checkout 会通过 MCP 获取元数据和专用下载地址,流式写入临时
|
|
|
343
346
|
- `--task <task>`:选择 context 路由;支持 frontend、data、custom-service、aihub、content、project。
|
|
344
347
|
- `--type <type>`:限制 `map` 查询一种正文资源类型;支持 pages、nav/navigations、docs/articles 及其别名。
|
|
345
348
|
- `--strict`:check/deploy/auto-push 将 warnings 视为失败。
|
|
349
|
+
- `--remote`:让 check 按需对比 checkout 的远端版本和 hash;默认仍为快速离线检查。
|
|
346
350
|
- `--force`:init 覆盖已有 Skill;checkout 显式覆盖已修改的本地正文。update 始终刷新目标 Skill。
|
|
347
351
|
- `--purge`:uninstall 时同时删除整个 `.draftgo/` 运行时目录。
|
|
348
352
|
- `--skip-update-check`:update 不访问 npm,直接用当前 CLI 刷新 Skill。
|
|
@@ -353,6 +357,8 @@ checkout 会通过 MCP 获取元数据和专用下载地址,流式写入临时
|
|
|
353
357
|
- `--allow-offline` / `--no-mcp-setup`:允许 connect 在 MCP 暂不可用时保存,或跳过宿主配置。
|
|
354
358
|
- `--yes`:跳过支持该选项的交互确认。
|
|
355
359
|
- `--mobile-check auto|always|never`:控制 `verify-ui` 是否执行。
|
|
360
|
+
- `--resource <type:id>`:verify-ui 打开 URL 前对比本地 checkout 与远端已提交版本,并明确报告实际验证来源。
|
|
361
|
+
- `--browser-path <file>`:verify-ui 显式使用浏览器可执行文件;也可设置 `DRAFTGO_BROWSER_PATH`,否则自动查找系统浏览器和 Playwright 缓存。
|
|
356
362
|
- `--token auto|never`:`verify-ui` 默认读取 `.draftgo/config.json`,对与 `server` 同源的地址自动附加 `token=<SAT>`;配置无 SAT、跨源地址或 `never` 模式均不附加。
|
|
357
363
|
- `--screenshot on-failure|always|never`:控制 UI 截图。
|
|
358
364
|
- `--delivery local|preview|deploy`:控制 deploy 行为。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "draftgo-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.49",
|
|
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"
|
package/resources/skill/SKILL.md
CHANGED
|
@@ -50,8 +50,8 @@ context 会校验本地 Reference,返回逐字章节、来源、实时项目/
|
|
|
50
50
|
|
|
51
51
|
1. MCP 定位 pages/nav/docs 并读取 metadata。
|
|
52
52
|
2. 批量 checkout;在 `.draftgo/worktree/` 按唯一 owner 编辑。
|
|
53
|
-
3. 主 Agent 回读全部修改,统一运行 `draftgo check
|
|
54
|
-
4. 用 `draftgo diff <type> <id>` 检查 base/local 差异,再按类型运行 `draftgo commit
|
|
53
|
+
3. 主 Agent 回读全部修改,统一运行 `draftgo check`;交付前需要核对远端版本时运行 `draftgo check --remote`。
|
|
54
|
+
4. 用 `draftgo diff <type> <id>` 检查 base/local 差异,再按类型运行 `draftgo commit ...`。页面布局或交互变化时在 commit 后用 `draftgo verify-ui <url> --resource <type:id>` 验证远端已提交版本。
|
|
55
55
|
5. 409/412 时停止自动提交,保留 base/local/remote 冲突材料;不得 force、覆盖或自动合并。
|
|
56
56
|
|
|
57
57
|
完整 manifest、流式传输、哈希和冲突规则见 `references/checkout.md`。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": "1.0",
|
|
3
3
|
"id": "draftgo",
|
|
4
4
|
"name": "DraftGo 开发助手",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.49",
|
|
6
6
|
"entry": "SKILL.md",
|
|
7
7
|
"description": "以 context 聚合编排、Skill/reference 原文、MCP 实时发现、长正文 checkout/commit、统一验证和完成日志为边界的 DraftGo 工作流。",
|
|
8
8
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@ db_meta、AIHub、system_config、roles、users、doc_categories 和普通配置
|
|
|
23
23
|
```bash
|
|
24
24
|
draftgo checkout <pages|nav|docs> <id...>
|
|
25
25
|
draftgo commit <pages|nav|docs> <id...>
|
|
26
|
+
draftgo reconcile <pages|nav|docs> <id...>
|
|
26
27
|
draftgo diff <pages|nav|docs> <id>
|
|
27
28
|
draftgo conflicts
|
|
28
29
|
draftgo conflict show <pages|nav|docs> <id>
|
|
@@ -91,7 +92,7 @@ base 已变化时必须拒绝覆盖。
|
|
|
91
92
|
4. 完整正文不得作为 MCP tool 参数发送。
|
|
92
93
|
5. 底座确认 hash 和新版本后,CLI 原子更新 `.base` 与 manifest。返回 hash 不一致时不得推进基线。
|
|
93
94
|
|
|
94
|
-
推荐编辑顺序:主 Agent 执行一次 context -> MCP 按需补充定位 -> Agent 自主设计并批量 checkout -> 独立资源按唯一 owner 并行编辑 -> 主 Agent 汇总回读 -> 统一运行一次 `draftgo check`
|
|
95
|
+
推荐编辑顺序:主 Agent 执行一次 context -> MCP 按需补充定位 -> Agent 自主设计并批量 checkout -> 独立资源按唯一 owner 并行编辑 -> 主 Agent 汇总回读 -> 统一运行一次 `draftgo check` -> `draftgo diff` -> commit -> 页面变化时用 `draftgo verify-ui <url> --resource <type:id>` 验证远端已提交版本。遇到本地正文已经等于远端、但 base/manifest 落后时,先用 `draftgo check --remote` 确认 `committed_unrecorded`,再运行 `draftgo reconcile`;不要手改 manifest。
|
|
95
96
|
|
|
96
97
|
单个 commit 成功不自动写 changelog。只有整个任务统一验证且全部 commit/MCP 交付成功后,主 Agent 才执行一次 `draftgo changelog add "<完成结果>"`。任何检查失败、409/412 或交付失败都不得写入。
|
|
97
98
|
|
package/src/cli.js
CHANGED
package/src/commandRegistry.js
CHANGED
|
@@ -15,6 +15,7 @@ const commands = [
|
|
|
15
15
|
{ name: 'mcp', run: (dir, args, flags) => require('./commands/mcp')(dir, args, flags) },
|
|
16
16
|
{ name: 'checkout', run: (dir, args, flags) => require('./commands/checkout')(dir, args, flags) },
|
|
17
17
|
{ name: 'commit', run: (dir, args, flags) => require('./commands/commit')(dir, args, flags) },
|
|
18
|
+
{ name: 'reconcile', run: (dir, args, flags) => require('./commands/reconcile')(dir, args, flags) },
|
|
18
19
|
{ name: 'diff', run: (dir, args, flags) => require('./commands/diff')(dir, args, flags) },
|
|
19
20
|
{ name: 'conflicts', run: (dir, args, flags) => require('./commands/conflicts')(dir, args, flags) },
|
|
20
21
|
{ name: 'conflict', run: (dir, args, flags) => require('./commands/conflict')(dir, args, flags) },
|
package/src/commands/api.js
CHANGED
|
@@ -1,9 +1,105 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
3
5
|
const log = require('../logger');
|
|
6
|
+
const { confirm } = require('../prompt');
|
|
4
7
|
const { loadProjectConfig } = require('../projectConfig');
|
|
5
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
6
9
|
|
|
10
|
+
function readCallInput(projectDir, flags) {
|
|
11
|
+
const raw = flags.input
|
|
12
|
+
? fs.readFileSync(path.resolve(projectDir, String(flags.input)))
|
|
13
|
+
: flags.params ? Buffer.from(String(flags.params), 'utf8') : Buffer.from('{}', 'utf8');
|
|
14
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
15
|
+
const value = JSON.parse(text);
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
17
|
+
throw new Error('API call input must be a UTF-8 JSON object.');
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function diagnosticResult(operationId, result) {
|
|
23
|
+
const source = result && typeof result === 'object' ? result : { data: result };
|
|
24
|
+
const statusCode = source.status_code ?? source.statusCode ?? source.status ?? null;
|
|
25
|
+
const headerRequestId = source.headers
|
|
26
|
+
&& (source.headers['x-request-id'] || source.headers['X-Request-Id']);
|
|
27
|
+
const requestId = source.request_id ?? source.requestId ?? headerRequestId ?? null;
|
|
28
|
+
const error = source.error && typeof source.error === 'object' ? source.error : null;
|
|
29
|
+
return {
|
|
30
|
+
operation_id: operationId,
|
|
31
|
+
status_code: statusCode,
|
|
32
|
+
server_code: error && error.code || source.code || null,
|
|
33
|
+
request_id: requestId,
|
|
34
|
+
result,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function diagnosticError(operationId, error) {
|
|
39
|
+
const source = error && (error.details || error.data) || {};
|
|
40
|
+
const nested = source.error && typeof source.error === 'object' ? source.error : {};
|
|
41
|
+
return {
|
|
42
|
+
operation_id: operationId,
|
|
43
|
+
status_code: (error && error.status) ?? source.status_code ?? source.status ?? null,
|
|
44
|
+
server_code: nested.code || source.code || error && error.code || null,
|
|
45
|
+
request_id: source.request_id || source.requestId || nested.request_id || null,
|
|
46
|
+
error: { message: error && error.message || String(error) },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printCallError(operationId, error, flags) {
|
|
51
|
+
const diagnostic = diagnosticError(operationId, error);
|
|
52
|
+
if (flags.output === 'json') console.log(JSON.stringify(diagnostic, null, 2));
|
|
53
|
+
else {
|
|
54
|
+
log.err(`DraftGo operation failed: ${operationId}`);
|
|
55
|
+
log.info(`status=${diagnostic.status_code ?? '-'} code=${diagnostic.server_code ?? '-'} request_id=${diagnostic.request_id ?? '-'}`);
|
|
56
|
+
log.err(diagnostic.error.message);
|
|
57
|
+
}
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function callOperation(projectDir, operationId, flags = {}) {
|
|
62
|
+
if (!operationId) {
|
|
63
|
+
log.err('Usage: draftgo api call <operation_id> --input <json-file>');
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
let session;
|
|
67
|
+
let description;
|
|
68
|
+
let input;
|
|
69
|
+
try {
|
|
70
|
+
const config = loadProjectConfig(projectDir);
|
|
71
|
+
session = await openToolSession(config, [TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall]);
|
|
72
|
+
description = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
|
|
73
|
+
input = readCallInput(projectDir, flags);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return printCallError(operationId, error, flags);
|
|
76
|
+
}
|
|
77
|
+
const destructive = description && (description.destructive === true
|
|
78
|
+
|| description.operation && description.operation.destructive === true);
|
|
79
|
+
if (destructive && !flags.yes && !flags.y) {
|
|
80
|
+
const approved = await confirm(`Call destructive DraftGo operation ${operationId}?`, { default: false });
|
|
81
|
+
if (!approved) return 0;
|
|
82
|
+
}
|
|
83
|
+
let result;
|
|
84
|
+
try {
|
|
85
|
+
result = await callStructured(session, TOOL_NAMES.apiCall, {
|
|
86
|
+
...input,
|
|
87
|
+
operation_id: operationId,
|
|
88
|
+
...(destructive ? { confirm: true } : {}),
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return printCallError(operationId, error, flags);
|
|
92
|
+
}
|
|
93
|
+
const diagnostic = diagnosticResult(operationId, result);
|
|
94
|
+
if (flags.output === 'json') console.log(JSON.stringify(diagnostic, null, 2));
|
|
95
|
+
else {
|
|
96
|
+
log.ok(`DraftGo operation completed: ${operationId}`);
|
|
97
|
+
log.info(`status=${diagnostic.status_code ?? '-'} code=${diagnostic.server_code ?? '-'} request_id=${diagnostic.request_id ?? '-'}`);
|
|
98
|
+
console.log(JSON.stringify(result, null, 2));
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
7
103
|
function printResult(title, result) {
|
|
8
104
|
log.title(title);
|
|
9
105
|
if (Array.isArray(result)) {
|
|
@@ -19,6 +115,7 @@ function printResult(title, result) {
|
|
|
19
115
|
|
|
20
116
|
async function apiCommand(projectDir, positional, flags = {}) {
|
|
21
117
|
const first = String(positional[0] || '').toLowerCase();
|
|
118
|
+
if (first === 'call') return callOperation(projectDir, String(positional[1] || ''), flags);
|
|
22
119
|
const describe = first === 'describe';
|
|
23
120
|
const queryParts = first === 'search' || describe ? positional.slice(1) : positional;
|
|
24
121
|
const query = queryParts.join(' ').trim();
|
|
@@ -39,3 +136,8 @@ async function apiCommand(projectDir, positional, flags = {}) {
|
|
|
39
136
|
}
|
|
40
137
|
|
|
41
138
|
module.exports = apiCommand;
|
|
139
|
+
module.exports.readCallInput = readCallInput;
|
|
140
|
+
module.exports.diagnosticResult = diagnosticResult;
|
|
141
|
+
module.exports.diagnosticError = diagnosticError;
|
|
142
|
+
module.exports.printCallError = printCallError;
|
|
143
|
+
module.exports.callOperation = callOperation;
|
package/src/commands/autoPush.js
CHANGED
|
@@ -37,7 +37,7 @@ async function autoPush(projectDir, positional, flags = {}) {
|
|
|
37
37
|
return 1;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const checkCode = check(projectDir, { strict: flags.strict });
|
|
40
|
+
const checkCode = await check(projectDir, { strict: flags.strict });
|
|
41
41
|
if (checkCode !== 0) {
|
|
42
42
|
log.err('auto-push stopped because local validation failed.');
|
|
43
43
|
return checkCode;
|
package/src/commands/check.js
CHANGED
|
@@ -4,16 +4,37 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const log = require('../logger');
|
|
6
6
|
const { analyzeProject } = require('../projectMap');
|
|
7
|
+
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
7
8
|
|
|
8
9
|
function legacyCacheDetected(projectDir) {
|
|
9
10
|
return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
|
|
10
11
|
.some((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
function check(projectDir, flags = {}) {
|
|
14
|
+
async function check(projectDir, flags = {}) {
|
|
14
15
|
const result = analyzeProject(projectDir);
|
|
16
|
+
let remote = null;
|
|
17
|
+
let remoteError = null;
|
|
18
|
+
if (flags.remote) {
|
|
19
|
+
try {
|
|
20
|
+
remote = await inspectRemoteCheckouts(projectDir);
|
|
21
|
+
for (const entry of remote) {
|
|
22
|
+
if (!['clean', 'clean_local', 'local_modified'].includes(entry.state)) {
|
|
23
|
+
const detail = `${entry.resource_type} ${entry.resource_id}: ${entry.state}`
|
|
24
|
+
+ ` (local=${entry.local_hash || '-'}, base=${entry.manifest_hash || '-'}, `
|
|
25
|
+
+ `remote=${entry.remote_hash || '-'}, remote_version=${entry.remote_version || '-'})`
|
|
26
|
+
+ (entry.recommendation ? `; run ${entry.recommendation}` : '');
|
|
27
|
+
result.errors.push(detail);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
remoteError = { code: error.code || 'REMOTE_CHECK_FAILED', message: error.message };
|
|
32
|
+
result.errors.push(`Remote checkout check failed: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const output = { ...result, remote, remote_error: remoteError };
|
|
15
36
|
if (flags.output === 'json') {
|
|
16
|
-
console.log(JSON.stringify({ ...
|
|
37
|
+
console.log(JSON.stringify({ ...output, legacy_cache_ignored: legacyCacheDetected(projectDir) }, null, 2));
|
|
17
38
|
return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
18
39
|
}
|
|
19
40
|
|
|
@@ -21,6 +42,7 @@ function check(projectDir, flags = {}) {
|
|
|
21
42
|
log.info(`Checked-out pages: ${result.map.pages.length}`);
|
|
22
43
|
log.info(`Checked-out navigations: ${result.map.navigations.length}`);
|
|
23
44
|
log.info(`Checked-out docs: ${result.map.docs.length}`);
|
|
45
|
+
if (remote) log.info(`Remote checkout comparison: ${remote.length} resource(s)`);
|
|
24
46
|
for (const message of result.errors) log.err(message);
|
|
25
47
|
for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
|
|
26
48
|
if (legacyCacheDetected(projectDir)) log.dim(' Legacy .draftgo index caches were detected and ignored.');
|
package/src/commands/commit.js
CHANGED
|
@@ -9,11 +9,37 @@ async function commit(projectDir, positional, flags = {}) {
|
|
|
9
9
|
log.err('Usage: draftgo commit <pages|nav|docs> <id...>');
|
|
10
10
|
return 1;
|
|
11
11
|
}
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
12
|
+
const streamed = [];
|
|
13
|
+
const printStatus = (result) => {
|
|
14
|
+
streamed.push(result);
|
|
15
|
+
if (flags.output === 'json') return;
|
|
16
|
+
if (result.status === 'committed') log.ok(`${result.resource_type} ${result.resource_id}: committed`);
|
|
17
|
+
else if (result.status === 'unchanged') log.dim(`${result.resource_type} ${result.resource_id}: unchanged`);
|
|
18
|
+
else if (result.status === 'failed') log.err(`${result.resource_type} ${result.resource_id}: failed (${result.code}) - ${result.message}`
|
|
19
|
+
+ (result.remote_change_possible ? ' Remote change is possible; run draftgo check --remote.' : ''));
|
|
20
|
+
else log.warn(`${result.resource_type} ${result.resource_id}: not started`);
|
|
21
|
+
};
|
|
22
|
+
try {
|
|
23
|
+
const results = await commitResources(projectDir, resourceType, ids, { onStatus: printStatus });
|
|
24
|
+
if (flags.output === 'json') console.log(JSON.stringify({
|
|
25
|
+
completed: results.filter((item) => ['committed', 'unchanged'].includes(item.status)),
|
|
26
|
+
failed: [],
|
|
27
|
+
not_started: [],
|
|
28
|
+
}, null, 2));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const batch = error.details && (error.details.batch || error.details);
|
|
31
|
+
const summary = batch && Array.isArray(batch.completed) ? batch : {
|
|
32
|
+
completed: streamed.filter((item) => ['committed', 'unchanged'].includes(item.status)),
|
|
33
|
+
failed: streamed.filter((item) => item.status === 'failed'),
|
|
34
|
+
not_started: streamed.filter((item) => item.status === 'not_started'),
|
|
35
|
+
};
|
|
36
|
+
if (flags.output === 'json') console.log(JSON.stringify({ error: {
|
|
37
|
+
code: error.code || 'COMMIT_FAILED', message: error.message,
|
|
38
|
+
}, ...summary }, null, 2));
|
|
39
|
+
else log.err(`Commit batch stopped: ${summary.completed.length} completed, `
|
|
40
|
+
+ `${summary.failed.length} failed, ${summary.not_started.length} not started. `
|
|
41
|
+
+ `${summary.completed.length ? 'Remote changes already occurred for completed resources.' : 'No remote content was changed.'}`);
|
|
42
|
+
return 1;
|
|
17
43
|
}
|
|
18
44
|
return 0;
|
|
19
45
|
}
|
package/src/commands/deploy.js
CHANGED
|
@@ -11,7 +11,7 @@ async function deploy(projectDir, positional, flags = {}) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
log.step('deploy: validating checked-out content...');
|
|
14
|
-
const checkCode = check(projectDir, { strict: flags.strict });
|
|
14
|
+
const checkCode = await check(projectDir, { strict: flags.strict });
|
|
15
15
|
if (checkCode !== 0) {
|
|
16
16
|
log.err('Deploy stopped because local validation failed.');
|
|
17
17
|
return checkCode;
|
package/src/commands/help.js
CHANGED
|
@@ -45,8 +45,10 @@ Usage:
|
|
|
45
45
|
draftgo checkout <type> <id...> Download pages/nav/docs body + verified base.
|
|
46
46
|
--force explicitly replaces local changes.
|
|
47
47
|
draftgo check Validate checked-out worktree/base files only.
|
|
48
|
+
--remote also compares remote hash/version.
|
|
48
49
|
draftgo diff <type> <id> Show checkout base versus local body.
|
|
49
50
|
draftgo commit <type> <id...> Validate and upload complete checked-out bodies.
|
|
51
|
+
draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
|
|
50
52
|
draftgo conflicts List unresolved conflicts; --all includes resolved.
|
|
51
53
|
draftgo conflict show <type> <id>
|
|
52
54
|
Show preserved base/local/remote paths.
|
|
@@ -58,6 +60,8 @@ Usage:
|
|
|
58
60
|
draftgo api <query> Search the live API contract through MCP.
|
|
59
61
|
draftgo api describe <operation_id>
|
|
60
62
|
Describe one live API operation through MCP.
|
|
63
|
+
draftgo api call <operation_id> --input <json-file>
|
|
64
|
+
Call one described operation with UTF-8 JSON.
|
|
61
65
|
draftgo delete <operation_id> [id]
|
|
62
66
|
Confirm and call a live delete operation.
|
|
63
67
|
Legacy <type> <id> works only when unambiguous.
|
|
@@ -106,10 +110,11 @@ Important flags:
|
|
|
106
110
|
content | project.
|
|
107
111
|
--type <type> (map) pages | nav/navigations | docs/articles.
|
|
108
112
|
--strict Treat check warnings as failures.
|
|
113
|
+
--remote (check) Compare checkout hashes/versions with remote.
|
|
109
114
|
--yes Skip supported confirmation prompts.
|
|
110
115
|
--operation-id <id> (delete) Select an operation explicitly.
|
|
111
|
-
--params <json> (delete) Pass an API parameter object.
|
|
112
|
-
--input <file> (delete) Read
|
|
116
|
+
--params <json> (api call/delete) Pass an API parameter object.
|
|
117
|
+
--input <file> (api call/delete) Read a UTF-8 JSON parameter object.
|
|
113
118
|
--delivery <mode> (deploy) local | preview | deploy.
|
|
114
119
|
--dry-run (push) Show diffs without committing.
|
|
115
120
|
--mobile-check <mode> (verify-ui) auto | always | never.
|
|
@@ -117,6 +122,9 @@ Important flags:
|
|
|
117
122
|
SAT to same-origin URLs as the token query parameter.
|
|
118
123
|
--screenshot <mode> (verify-ui) on-failure | always | never.
|
|
119
124
|
--browser <name> (verify-ui) chromium | chrome | msedge.
|
|
125
|
+
--browser-path <file> (verify-ui) Explicit browser executable; environment
|
|
126
|
+
fallback: DRAFTGO_BROWSER_PATH.
|
|
127
|
+
--resource <type:id> (verify-ui) Compare local and remote content first.
|
|
120
128
|
--selector <css> (verify-ui) Require a visible key element.
|
|
121
129
|
--width/--height <px> (verify-ui) Override the default 390x844 viewport.
|
|
122
130
|
|
package/src/commands/map.js
CHANGED
|
@@ -4,11 +4,10 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const log = require('../logger');
|
|
6
6
|
const { loadProjectConfig } = require('../projectConfig');
|
|
7
|
-
const { loadManifest, absolutePath } = require('../worktree/manifest');
|
|
8
|
-
const { hashFile } = require('../worktree/streams');
|
|
9
7
|
const { canonicalResourceType } = require('../worktree/types');
|
|
10
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
11
9
|
const { allWithAbort } = require('../mcp/parallel');
|
|
10
|
+
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
12
11
|
|
|
13
12
|
const REMOTE_RESOURCE_TYPES = Object.freeze({
|
|
14
13
|
pages: 'pages',
|
|
@@ -82,23 +81,6 @@ async function listRemoteResources(session, resourceTypes = DEFAULT_REMOTE_RESOU
|
|
|
82
81
|
return groups.flat();
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
async function localCheckouts(projectDir) {
|
|
86
|
-
const manifest = loadManifest(projectDir);
|
|
87
|
-
const entries = [];
|
|
88
|
-
for (const entry of Object.values(manifest.entries)) {
|
|
89
|
-
const local = absolutePath(projectDir, entry.local_path);
|
|
90
|
-
let currentHash = null;
|
|
91
|
-
if (fs.existsSync(local)) currentHash = (await hashFile(local)).hash;
|
|
92
|
-
entries.push({
|
|
93
|
-
...entry,
|
|
94
|
-
exists: currentHash !== null,
|
|
95
|
-
current_hash: currentHash,
|
|
96
|
-
changed: currentHash !== null && currentHash !== entry.base_hash,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return entries;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
84
|
function legacyCaches(projectDir) {
|
|
103
85
|
return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
|
|
104
86
|
.filter((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
|
|
@@ -113,7 +95,20 @@ async function mapCommand(projectDir, flags = {}) {
|
|
|
113
95
|
(options) => callStructured(session, TOOL_NAMES.projectOverview, {}, options),
|
|
114
96
|
(options) => listRemoteResources(session, resourceTypes, options),
|
|
115
97
|
], options),
|
|
116
|
-
() =>
|
|
98
|
+
async (options) => {
|
|
99
|
+
const remoteStatus = await inspectRemoteCheckouts(projectDir, {
|
|
100
|
+
config,
|
|
101
|
+
client: session.client,
|
|
102
|
+
tools: session.tools,
|
|
103
|
+
signal: options.signal,
|
|
104
|
+
});
|
|
105
|
+
return remoteStatus.map((entry) => ({
|
|
106
|
+
...entry,
|
|
107
|
+
exists: entry.local_hash !== null,
|
|
108
|
+
current_hash: entry.local_hash,
|
|
109
|
+
changed: entry.local_hash !== null && entry.local_hash !== entry.base_hash,
|
|
110
|
+
}));
|
|
111
|
+
},
|
|
117
112
|
]);
|
|
118
113
|
const [overview, resources] = remote;
|
|
119
114
|
const result = {
|
|
@@ -138,8 +133,11 @@ async function mapCommand(projectDir, flags = {}) {
|
|
|
138
133
|
for (const [type, count] of [...counts.entries()].sort()) log.plain(` ${type}: ${count}`);
|
|
139
134
|
log.info(`Local checkouts: ${checkouts.length}`);
|
|
140
135
|
for (const entry of checkouts) {
|
|
141
|
-
const state = !entry.exists ? 'missing' : entry.changed ? 'modified' : 'clean';
|
|
142
|
-
log.plain(` ${entry.resource_type} ${entry.resource_id}: ${state}
|
|
136
|
+
const state = entry.state || (!entry.exists ? 'missing' : entry.changed ? 'modified' : 'clean');
|
|
137
|
+
log.plain(` ${entry.resource_type} ${entry.resource_id}: ${state}`
|
|
138
|
+
+ ` local=${entry.local_hash || entry.current_hash || '-'} base=${entry.manifest_hash || entry.base_hash || '-'} remote=${entry.remote_hash || '-'} `
|
|
139
|
+
+ `version=${entry.manifest_version ?? '-'}->${entry.remote_version ?? '-'} `
|
|
140
|
+
+ `(${entry.local_path})`);
|
|
143
141
|
}
|
|
144
142
|
if (result.legacy_cache.detected.length) {
|
|
145
143
|
log.warn(`Ignored legacy cache indexes: ${result.legacy_cache.detected.join(', ')}`);
|
package/src/commands/mcp.js
CHANGED
|
@@ -13,6 +13,20 @@ const {
|
|
|
13
13
|
const { serveStdio } = require('../mcp/stdio');
|
|
14
14
|
const { parseTimeout } = require('../timeout');
|
|
15
15
|
|
|
16
|
+
function classifyMcpFailure(error) {
|
|
17
|
+
const message = String(error && error.message || error || '');
|
|
18
|
+
const code = error && error.code;
|
|
19
|
+
const status = error && error.status;
|
|
20
|
+
if (status === 401 || status === 403) return 'SAT authentication or authorization failed.';
|
|
21
|
+
if (/session|uninitialized|initialize first|not initialized/i.test(message)
|
|
22
|
+
|| ['SESSION_EXPIRED', 'MCP_SESSION_EXPIRED', -32002].includes(code)) {
|
|
23
|
+
return 'The MCP session was lost or the server rejected the initialized state; check sticky sessions and service restarts.';
|
|
24
|
+
}
|
|
25
|
+
if (status >= 500) return 'The MCP service returned a server error; inspect the service request ID and logs.';
|
|
26
|
+
if (/tools\/call|tool/i.test(message)) return 'Tool invocation failed after protocol setup; inspect the tool contract and server handler.';
|
|
27
|
+
return 'Connection or MCP protocol negotiation failed.';
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
function targetArgs(positional, flags) {
|
|
17
31
|
const values = positional.slice();
|
|
18
32
|
if (flags.target) values.push(...String(flags.target).split(','));
|
|
@@ -87,19 +101,27 @@ function status(projectDir, positional = [], flags = {}) {
|
|
|
87
101
|
|
|
88
102
|
async function test(projectDir, _positional = [], flags = {}) {
|
|
89
103
|
let config;
|
|
104
|
+
const stages = [];
|
|
90
105
|
try {
|
|
91
106
|
config = loadProjectConfig(projectDir);
|
|
92
107
|
const result = await testConnection(config, {
|
|
93
108
|
timeoutMs: parseTimeout(flags.timeout),
|
|
109
|
+
onStage(stage, status) {
|
|
110
|
+
stages.push({ stage, status });
|
|
111
|
+
if (status === 'succeeded') log.ok(`MCP ${stage} succeeded.`);
|
|
112
|
+
else if (status === 'failed') log.err(`MCP ${stage} failed.`);
|
|
113
|
+
else log.step(`MCP ${stage}...`);
|
|
114
|
+
},
|
|
94
115
|
});
|
|
95
|
-
log.ok(
|
|
96
|
-
log.ok(`tools/list returned ${result.tools.length} tools.`);
|
|
97
|
-
log.ok(`tools/call succeeded: ${result.testedCalls.join(', ')}`);
|
|
116
|
+
log.ok(`MCP diagnostic completed: ${result.testedCalls.length} tools/call checks passed.`);
|
|
98
117
|
log.dim(` protocol: ${result.protocolVersion}`);
|
|
99
118
|
return 0;
|
|
100
119
|
} catch (error) {
|
|
101
120
|
const token = config && String(config.token || config.sat || '');
|
|
102
121
|
log.err(redactText(error && error.message ? error.message : error, [token]));
|
|
122
|
+
const last = stages[stages.length - 1];
|
|
123
|
+
if (last) log.info(`MCP diagnostic stopped at ${last.stage} (${last.status}).`);
|
|
124
|
+
log.info(classifyMcpFailure(error));
|
|
103
125
|
return 1;
|
|
104
126
|
}
|
|
105
127
|
}
|
|
@@ -125,3 +147,4 @@ mcp.serve = serve;
|
|
|
125
147
|
mcp.printUsage = printUsage;
|
|
126
148
|
|
|
127
149
|
module.exports = mcp;
|
|
150
|
+
module.exports.classifyMcpFailure = classifyMcpFailure;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { reconcileResources } = require('../worktree');
|
|
5
|
+
|
|
6
|
+
async function reconcile(projectDir, positional, flags = {}) {
|
|
7
|
+
const [resourceType, ...ids] = positional;
|
|
8
|
+
if (!resourceType || !ids.length) {
|
|
9
|
+
log.err('Usage: draftgo reconcile <pages|nav|docs> <id...>');
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
const results = await reconcileResources(projectDir, resourceType, ids);
|
|
13
|
+
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
14
|
+
else for (const result of results) {
|
|
15
|
+
log.ok(`Reconciled ${result.resource_type} ${result.resource_id} -> ${result.base_version ?? result.base_revision}`);
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = reconcile;
|