draftgo-cli 3.0.55 → 4.0.1
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 +112 -316
- package/package.json +5 -5
- package/resources/skill/SKILL.md +25 -24
- package/resources/skill/init/SKILL.md +5 -10
- package/resources/skill/manifest.json +2 -2
- package/resources/skill/references/aihub.md +10 -5
- package/resources/skill/references/chat-sdk.md +10 -0
- package/resources/skill/references/checkout.md +4 -4
- package/resources/skill/references/custom-services.md +65 -226
- package/resources/skill/references/data.md +3 -2
- package/resources/skill/references/frontend.md +96 -490
- package/resources/skill/references/mcp.md +39 -103
- package/resources/skill/references/runtime.md +3 -2
- package/resources/skill/story/SKILL.md +1 -2
- package/src/apiContractCache.js +112 -0
- package/src/cli.js +1 -21
- package/src/commandRegistry.js +6 -11
- package/src/commands/api.js +28 -8
- package/src/commands/check.js +1 -10
- package/src/commands/customService.js +2 -4
- package/src/commands/delete.js +23 -46
- package/src/commands/deploy.js +1 -1
- package/src/commands/help.js +16 -31
- package/src/commands/init.js +4 -10
- package/src/commands/listTargets.js +1 -1
- package/src/commands/local.js +2 -6
- package/src/commands/map.js +0 -11
- package/src/commands/status.js +1 -1
- package/src/commands/uninstall.js +3 -3
- package/src/commands/update.js +1 -1
- package/src/commands/verify.js +43 -21
- package/src/commands/{verifyUi.js → visualVerify.js} +28 -116
- package/src/commands/worklog.js +86 -0
- package/src/customServices.js +150 -33
- package/src/{localdev → localRuntime}/detect.js +1 -1
- package/src/{localdev → localRuntime}/mysqlClient.js +1 -1
- package/src/{localdev → localRuntime}/services.js +1 -1
- package/src/projectConfig.js +2 -0
- package/src/{installers/index.js → targets.js} +3 -5
- package/src/worklog.js +274 -0
- package/src/workspaceHealth.js +1 -1
- package/src/worktree/index.js +81 -51
- package/src/changelog.js +0 -276
- package/src/commands/changelog.js +0 -24
- package/src/commands/localDev.js +0 -9
- package/src/commands/sync.js +0 -46
- package/src/commands/task.js +0 -408
- package/src/commands/verifyUiCompat.js +0 -16
- /package/src/{localdev → localRuntime}/compose.js +0 -0
- /package/src/{localdev → localRuntime}/index.js +0 -0
|
@@ -4,62 +4,44 @@ read_when: 配置或诊断 DraftGo MCP 时 · 查询实时资源或 API 契约
|
|
|
4
4
|
|
|
5
5
|
# DraftGo MCP
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
根 `SKILL.md` 已负责任务路由。本文件只保留 MCP 的实时契约、调用优化和安全边界,不重复页面、前端或交付规则。
|
|
8
8
|
|
|
9
9
|
## 边界
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
MCP tool result 或参数传输;需要全文时使用 `draftgo checkout`,提交时使用 `draftgo commit`。MCP 返回
|
|
13
|
-
`artifact` / `omitted` 时保留该语义,不要尝试重新展开长内容。
|
|
11
|
+
MCP 用于实时发现、结构化查询和普通 API 操作。pages、navigations、docs/articles、custom_services 的完整正文走 `draftgo checkout` / `draftgo commit`;工具返回 `artifact` 或 `omitted` 时保留该语义,不要求模型展开长内容。
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
不要增加聚合上下文工具或静态 API 路径表。按任务读取最少必要的 Reference,再调用精确工具:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
| 需要 | 工具 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 项目能力、registry 覆盖和 checkout 类型 | `draftgo_project_overview` |
|
|
18
|
+
| 定位资源 | `draftgo_resource_search` / `draftgo_resource_list` |
|
|
19
|
+
| 元数据或短片段 | `draftgo_resource_get_metadata` / `draftgo_resource_read_fragment` |
|
|
20
|
+
| 定位 API operation | `draftgo_api_search` |
|
|
21
|
+
| 读取 operation schema | `draftgo_api_describe` |
|
|
22
|
+
| 结构化读写 | `draftgo_api_call` |
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
2. `draftgo_resource_search` 或 `draftgo_resource_list`
|
|
21
|
-
3. 按需调用 `draftgo_resource_get_metadata` 或 `draftgo_resource_read_fragment`
|
|
22
|
-
4. 需要实时 API 契约时调用 `draftgo_api_search`、`draftgo_api_describe`
|
|
23
|
-
5. 结构化资源读写使用 `draftgo_api_call`
|
|
24
|
+
只在需要对应信息时调用工具,不把 `project_overview` 作为每个任务的固定前置步骤。独立资源或 operation 可并发调用;同一资源的依赖步骤保持顺序。非幂等写入失败后先读状态,不自动重试。
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
这些资源不 checkout,也不生成 `.draftgo/*/index.json` 镜像。
|
|
26
|
+
## API 契约缓存
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
动态 schema 不写入 Skill。Skill 只保存下面的缓存规则:
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
1. operation 未知时才 search;已有精确 `operation_id` 时跳过 search。
|
|
31
|
+
2. 第一次使用 operation 时 describe,检查 method、path、parameters、request body、responses、permission、risk、`destructive`、`idempotent`、`input_schema` 和 `response_policy`。
|
|
32
|
+
3. 同一 server 的 operation 描述按 `registry_revision` 复用。CLI 将缓存写在私有 `.draftgo/api-contract-cache.json`,不把 schema 注入 Skill 或对话上下文。
|
|
33
|
+
4. call 携带缓存的 `registry_revision`。服务返回 `CONTRACT_CHANGED` 时重新 describe 并重试一次;其他工具错误不触发自动重试。
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
describe 缺少本次调用需要的 schema、权限、风险或响应契约时停止并报告,不猜字段、不拼路径、不绕过 MCP。多个 operation 仍可能匹配时继续缩小 search,而不是任选一个。
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
"query": "需要查找的能力或路径片段",
|
|
37
|
-
"module": "可选模块",
|
|
38
|
-
"resource_type": "可选资源类型",
|
|
39
|
-
"limit": 100,
|
|
40
|
-
"cursor": "下一页游标"
|
|
41
|
-
}
|
|
42
|
-
```
|
|
37
|
+
动态 Go 自定义服务 Route 也在 registry 中:module 为 `scripts_dynamic`,resource type 为 `custom_scripts`。Route 的 SDK、身份和运行限制见 `custom-services.md`。
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
动态 Go 自定义服务也在同一 registry:有效的 `app.Route` 会实时物化为实际 method 与 `/api/x/{slug}/{path}` operation,module 为 `scripts_dynamic`、resource type 为 `custom_scripts`。SDK、身份继承、精确匹配和运行限制仍以 `references/custom-services.md` 为准。
|
|
47
|
-
|
|
48
|
-
### 2. 描述唯一 operation
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{ "operation_id": "search 返回的精确 operation_id" }
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
调用前必须检查 describe 返回的 method、path、parameters、request body、responses、examples、permission、risk、`destructive`、`idempotent`、完整 `input_schema` 和 `response_policy`。路径参数放 `path`,查询参数放 `query`,JSON 请求体放 `body`,文件或表单字段放 `multipart`;只发送 schema 允许的字段。
|
|
55
|
-
|
|
56
|
-
describe 缺少与本次调用有关的参数、请求体、responses、permission、risk、`input_schema` 或 `response_policy` 时停止并报告契约不完整,不得自行补字段、改走猜测路径或绕过 MCP。若多个 operation 仍可能匹配,继续缩小搜索范围,而不是任选一个。
|
|
57
|
-
|
|
58
|
-
### 3. 调用
|
|
39
|
+
## 调用形状
|
|
59
40
|
|
|
60
41
|
```json
|
|
61
42
|
{
|
|
62
43
|
"operation_id": "精确 operation_id",
|
|
44
|
+
"registry_revision": "describe 返回的 revision",
|
|
63
45
|
"path": {},
|
|
64
46
|
"query": {},
|
|
65
47
|
"body": null,
|
|
@@ -68,11 +50,11 @@ describe 缺少与本次调用有关的参数、请求体、responses、permissi
|
|
|
68
50
|
}
|
|
69
51
|
```
|
|
70
52
|
|
|
71
|
-
|
|
53
|
+
只传 operation schema 需要的容器。路径参数放 `path`,查询参数放 `query`,JSON 放 `body`,文件或表单字段放 `multipart`。不得传 tenant override、SAT、Authorization 或其他凭据。
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
高风险 operation 仅在用户意图和影响范围明确时设置 `confirm=true`;不要拼 `X-Confirm-Token` 或调用旧 reauth 流程。按 describe 的 responses 与实际 `status_code` 解释结果,不对自定义 Route、OpenAI 兼容流等强套 `{code,data,message}`。`response_policy.checkout_required=true` 时改走 checkout/commit。
|
|
74
56
|
|
|
75
|
-
## CLI
|
|
57
|
+
## CLI 与宿主
|
|
76
58
|
|
|
77
59
|
```bash
|
|
78
60
|
draftgo mcp setup [target...]
|
|
@@ -81,72 +63,26 @@ draftgo mcp test
|
|
|
81
63
|
draftgo mcp serve
|
|
82
64
|
```
|
|
83
65
|
|
|
84
|
-
- `setup`
|
|
85
|
-
- `status`
|
|
86
|
-
- `test`
|
|
87
|
-
- `serve`
|
|
88
|
-
|
|
89
|
-
`draftgo mcp test` 每次使用独立的新连接,因此它证明当前 server、SAT 和协议可以建立新 session,不代表宿主已持有的旧 session 仍然有效。宿主工具报告 session 失效但 `mcp test` 成功时,bridge 应自动恢复;若恢复仍失败,再检查远端日志、代理头传递和服务可用性。
|
|
66
|
+
- `setup` 写项目级宿主配置,只替换 `draftgo` 条目。
|
|
67
|
+
- `status` 检查配置与凭据泄漏风险。
|
|
68
|
+
- `test` 建立独立新连接并验证关键工具;它不证明宿主持有的旧 session 仍有效。
|
|
69
|
+
- `serve` 是 stdio bridge。远端 session 明确失效时重新 initialize 并重放当前请求一次;普通超时、服务错误和工具错误不重试。
|
|
90
70
|
|
|
91
|
-
|
|
92
|
-
也不创建 pages、navigation、docs、db_meta 等本地镜像。底座仍在开发或暂不可达时,报告诊断结果即可,
|
|
93
|
-
不得回退到 `pull --all`。
|
|
71
|
+
支持项目级 setup:Codex、Claude Code、Cursor、Gemini CLI、Kiro、GitHub Copilot。Windsurf 和 Antigravity 没有可靠项目级 MCP 配置,CLI 应明确提示不支持。
|
|
94
72
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
| 宿主 | 项目配置 | MCP setup |
|
|
98
|
-
|---|---|---|
|
|
99
|
-
| Codex | `.codex/config.toml` | 支持 |
|
|
100
|
-
| Claude Code | `.mcp.json` | 支持 |
|
|
101
|
-
| Cursor | `.cursor/mcp.json` | 支持 |
|
|
102
|
-
| Gemini CLI | `.gemini/settings.json` | 支持 |
|
|
103
|
-
| Kiro | `.kiro/settings/mcp.json` | 支持 |
|
|
104
|
-
| GitHub Copilot | `.vscode/mcp.json` | 支持 |
|
|
105
|
-
| Windsurf | 无可靠的项目级适配 | 不支持,CLI 必须明确提示 |
|
|
106
|
-
| Antigravity | 无可靠的项目级适配 | 不支持,CLI 必须明确提示 |
|
|
107
|
-
|
|
108
|
-
Codex 配置:
|
|
109
|
-
|
|
110
|
-
```toml
|
|
111
|
-
[mcp_servers.draftgo]
|
|
112
|
-
command = "draftgo"
|
|
113
|
-
args = ["mcp", "serve"]
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Claude Code、Cursor、Gemini CLI 和 Kiro 使用各自文件中的 `mcpServers`:
|
|
73
|
+
宿主配置只运行:
|
|
117
74
|
|
|
118
75
|
```json
|
|
119
|
-
{
|
|
120
|
-
"mcpServers": {
|
|
121
|
-
"draftgo": {
|
|
122
|
-
"command": "draftgo",
|
|
123
|
-
"args": ["mcp", "serve"]
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
76
|
+
{"command":"draftgo","args":["mcp","serve"]}
|
|
127
77
|
```
|
|
128
78
|
|
|
129
|
-
GitHub Copilot 使用 `servers
|
|
130
|
-
|
|
131
|
-
```json
|
|
132
|
-
{
|
|
133
|
-
"servers": {
|
|
134
|
-
"draftgo": {
|
|
135
|
-
"type": "stdio",
|
|
136
|
-
"command": "draftgo",
|
|
137
|
-
"args": ["mcp", "serve"]
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
79
|
+
Codex 使用 `[mcp_servers.draftgo]`;GitHub Copilot 使用 `servers` 且声明 `type="stdio"`;其他支持宿主使用各自 `mcpServers`。配置不得包含 SAT、token、Authorization、headers、远端 `/mcp` URL、`env` 或带凭据命令。bridge 从当前项目 `.draftgo/config.json` 读取 server/SAT。
|
|
142
80
|
|
|
143
|
-
|
|
144
|
-
bridge 必须从当前工作目录的 `.draftgo/config.json` 读取 server/SAT。
|
|
81
|
+
`draftgo connect` 只保存并验证连接,不下载业务资源或创建本地镜像。MCP 不可用时运行 `draftgo mcp test` 收集证据。
|
|
145
82
|
|
|
146
|
-
##
|
|
83
|
+
## 安全
|
|
147
84
|
|
|
148
|
-
- stdio 只输出 MCP JSON-RPC
|
|
149
|
-
- 代理 initialize、tools/list、tools/call
|
|
150
|
-
- HTTP 401/403
|
|
151
|
-
- `.draftgo/config.json`、`.draftgo/worktree
|
|
152
|
-
- MCP 不可用时运行 `draftgo mcp test` 收集证据;不要读取旧索引作为替代事实来源。
|
|
85
|
+
- stdio stdout 只输出 MCP JSON-RPC;日志写 stderr,且不得包含 SAT。
|
|
86
|
+
- 代理 initialize、tools/list、tools/call、通知、取消、错误和流式响应,不改写底座结果。
|
|
87
|
+
- HTTP 401/403、协议错误和工具缺失必须清晰失败;输出前脱敏 SAT 与 Authorization。
|
|
88
|
+
- `.draftgo/config.json`、`.draftgo/api-contract-cache.json`、worktree 和 conflicts 都是私有运行时状态并应 gitignore。
|
|
@@ -94,8 +94,9 @@ const orderId = query.orderId; // "42"
|
|
|
94
94
|
|
|
95
95
|
### 修改方法
|
|
96
96
|
|
|
97
|
-
system_config
|
|
98
|
-
|
|
97
|
+
system_config 是结构化远端资源。未知 operation 才通过 MCP `draftgo_api_search` 定位;首次使用或
|
|
98
|
+
registry revision 变化时 `draftgo_api_describe`,再用 `draftgo_api_call` 读取并更新目标键;不要 checkout
|
|
99
|
+
或维护本地索引。
|
|
99
100
|
|
|
100
101
|
前端全局层是系统默认配置。更新现有全局配置时只发送 `config_value`;不要带 `description`、`category`、
|
|
101
102
|
`value_type`、`status` 等元信息,避免触发“系统默认字段不允许修改字段描述/分类/状态”。
|
|
@@ -110,9 +110,8 @@ now:
|
|
|
110
110
|
- MCP resource_search/get_metadata → 页面与导航元数据(跳过 tag="系统" 的内置页)
|
|
111
111
|
- MCP api_search/api_describe/api_call → db_meta 与自定义服务结构
|
|
112
112
|
- 只有必须分析完整 pages/nav/docs 正文时才 checkout;不要要求 MCP 返回全文
|
|
113
|
-
- .draftgo/
|
|
113
|
+
- .draftgo/worklog.md → 工作进度与完成记录
|
|
114
114
|
- .draftgo/lessons/ → 开发经验记录
|
|
115
|
-
- .draftgo/Task/ → 历史任务文档
|
|
116
115
|
- 用户提供的设计文档(PRD / 原型说明 / 需求文档)→ 提取产品意图
|
|
117
116
|
|
|
118
117
|
2. 从已有数据推断系统画像,生成推断版 Story(含 design.overview 和 modules)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const SCHEMA_VERSION = 1;
|
|
8
|
+
const WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
9
|
+
|
|
10
|
+
function cachePath(projectDir) {
|
|
11
|
+
return path.join(projectDir, '.draftgo', 'api-contract-cache.json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function emptyCache(server) {
|
|
15
|
+
return { schema_version: SCHEMA_VERSION, server, operations: {} };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readCache(projectDir, server) {
|
|
19
|
+
const file = cachePath(projectDir);
|
|
20
|
+
if (!fs.existsSync(file)) return emptyCache(server);
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
23
|
+
if (!value || value.schema_version !== SCHEMA_VERSION || value.server !== server
|
|
24
|
+
|| !value.operations || Array.isArray(value.operations)) return emptyCache(server);
|
|
25
|
+
return value;
|
|
26
|
+
} catch {
|
|
27
|
+
return emptyCache(server);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeDescription(value) {
|
|
32
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
33
|
+
const operation = value.operation && typeof value.operation === 'object' ? value.operation : value;
|
|
34
|
+
const registryRevision = String(value.registry_revision || operation.registry_revision || '').trim();
|
|
35
|
+
const contractHash = String(value.contract_hash || operation.contract_hash || '').trim();
|
|
36
|
+
const operationId = String(operation.operation_id || '').trim();
|
|
37
|
+
if (!operationId || !registryRevision || !contractHash) return null;
|
|
38
|
+
const normalized = { ...operation };
|
|
39
|
+
delete normalized.registry_revision;
|
|
40
|
+
delete normalized.contract_hash;
|
|
41
|
+
return { operation: normalized, registry_revision: registryRevision, contract_hash: contractHash };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cachedDescription(projectDir, server, operationId) {
|
|
45
|
+
const cached = readCache(projectDir, server).operations[String(operationId)];
|
|
46
|
+
return normalizeDescription(cached);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function acquire(file) {
|
|
50
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
51
|
+
const lock = `${file}.lock`;
|
|
52
|
+
const started = Date.now();
|
|
53
|
+
while (true) {
|
|
54
|
+
try {
|
|
55
|
+
const descriptor = fs.openSync(lock, 'wx', 0o600);
|
|
56
|
+
fs.closeSync(descriptor);
|
|
57
|
+
return lock;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!error || error.code !== 'EEXIST') throw error;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > 30_000) {
|
|
63
|
+
fs.rmSync(lock, { force: true });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (!error || error.code !== 'ENOENT') throw error;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (Date.now() - started >= 2000) return null;
|
|
71
|
+
Atomics.wait(WAIT, 0, 0, 10);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeAtomic(file, value) {
|
|
76
|
+
const temporary = `${file}.tmp-${process.pid}-${crypto.randomBytes(8).toString('hex')}`;
|
|
77
|
+
try {
|
|
78
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
79
|
+
fs.renameSync(temporary, file);
|
|
80
|
+
} finally {
|
|
81
|
+
try { fs.rmSync(temporary, { force: true }); } catch { /* Best-effort cleanup. */ }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function storeDescription(projectDir, server, value) {
|
|
86
|
+
const normalized = normalizeDescription(value);
|
|
87
|
+
if (!normalized) return null;
|
|
88
|
+
const file = cachePath(projectDir);
|
|
89
|
+
const lock = acquire(file);
|
|
90
|
+
if (!lock) return normalized;
|
|
91
|
+
try {
|
|
92
|
+
const cache = readCache(projectDir, server);
|
|
93
|
+
cache.operations[normalized.operation.operation_id] = {
|
|
94
|
+
...normalized,
|
|
95
|
+
described_at: new Date().toISOString(),
|
|
96
|
+
};
|
|
97
|
+
cache.updated_at = new Date().toISOString();
|
|
98
|
+
writeAtomic(file, cache);
|
|
99
|
+
} finally {
|
|
100
|
+
try { fs.rmSync(lock, { force: true }); } catch { /* Cache writes are best effort. */ }
|
|
101
|
+
}
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
SCHEMA_VERSION,
|
|
107
|
+
cachePath,
|
|
108
|
+
readCache,
|
|
109
|
+
normalizeDescription,
|
|
110
|
+
cachedDescription,
|
|
111
|
+
storeDescription,
|
|
112
|
+
};
|
package/src/cli.js
CHANGED
|
@@ -13,8 +13,6 @@ const VALUE_FLAGS = new Set([
|
|
|
13
13
|
'url',
|
|
14
14
|
'browser',
|
|
15
15
|
'browser-path',
|
|
16
|
-
'resource',
|
|
17
|
-
'mobile-check',
|
|
18
16
|
'ui',
|
|
19
17
|
'frame',
|
|
20
18
|
'viewport',
|
|
@@ -31,30 +29,12 @@ const VALUE_FLAGS = new Set([
|
|
|
31
29
|
'params',
|
|
32
30
|
'input',
|
|
33
31
|
'type',
|
|
34
|
-
'title',
|
|
35
|
-
'description',
|
|
36
|
-
'item-id',
|
|
37
|
-
'id',
|
|
38
|
-
'owner',
|
|
39
|
-
'original',
|
|
40
|
-
'original-requirement',
|
|
41
|
-
'clarified',
|
|
42
|
-
'clarified-requirement',
|
|
43
|
-
'expected-effect',
|
|
44
|
-
'effect',
|
|
45
|
-
'scope',
|
|
46
|
-
'out-of-scope',
|
|
47
|
-
'content',
|
|
48
|
-
'acceptance',
|
|
49
|
-
'module-logic',
|
|
50
|
-
'user-journey',
|
|
51
|
-
'item',
|
|
52
32
|
'date',
|
|
53
33
|
'handler',
|
|
54
34
|
'headers',
|
|
55
35
|
'user',
|
|
56
36
|
'side-effect-policy',
|
|
57
|
-
'
|
|
37
|
+
'note',
|
|
58
38
|
]);
|
|
59
39
|
|
|
60
40
|
function parse(argv) {
|
package/src/commandRegistry.js
CHANGED
|
@@ -5,15 +5,13 @@
|
|
|
5
5
|
const commands = [
|
|
6
6
|
{ name: 'init', run: (dir, args, flags) => require('./commands/init')(dir, args, flags) },
|
|
7
7
|
{ name: 'update', run: (dir, args, flags) => require('./commands/update')(dir, args, flags) },
|
|
8
|
-
{ name: 'uninstall',
|
|
8
|
+
{ name: 'uninstall', run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
|
|
9
9
|
{ name: 'status', run: (dir) => require('./commands/status')(dir) },
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: 'task', run: (dir, args, flags) => require('./commands/task')(dir, args, flags) },
|
|
10
|
+
{ name: 'work', run: (dir, args, flags) => require('./commands/worklog')(dir, args, flags) },
|
|
12
11
|
{ name: 'clean', run: (dir, args, flags) => require('./commands/clean')(dir, args, flags) },
|
|
13
12
|
{ name: 'map', run: (dir, _args, flags) => require('./commands/map')(dir, flags) },
|
|
14
13
|
{ name: 'check', run: (dir, args, flags) => require('./commands/check')(dir, args, flags) },
|
|
15
14
|
{ name: 'verify', run: (dir, args, flags) => require('./commands/verify')(dir, args, flags) },
|
|
16
|
-
{ name: 'verify-ui', aliases: ['verifyui'], run: (dir, args, flags) => require('./commands/verifyUiCompat')(dir, args, flags) },
|
|
17
15
|
{ name: 'mcp', run: (dir, args, flags) => require('./commands/mcp')(dir, args, flags) },
|
|
18
16
|
{ name: 'checkout', run: (dir, args, flags) => require('./commands/checkout')(dir, args, flags) },
|
|
19
17
|
{ name: 'refresh', run: (dir, args, flags) => require('./commands/checkout')(dir, args, flags) },
|
|
@@ -26,15 +24,12 @@ const commands = [
|
|
|
26
24
|
{ name: 'conflicts', run: (dir, args, flags) => require('./commands/conflicts')(dir, args, flags) },
|
|
27
25
|
{ name: 'conflict', run: (dir, args, flags) => require('./commands/conflict')(dir, args, flags) },
|
|
28
26
|
{ name: 'api', run: (dir, args, flags) => require('./commands/api')(dir, args, flags) },
|
|
29
|
-
{ name: 'delete',
|
|
27
|
+
{ name: 'delete', run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
|
|
30
28
|
{ name: 'deploy', run: (dir, args, flags) => require('./commands/deploy')(dir, args, flags) },
|
|
31
|
-
{ name: 'auto-push',
|
|
32
|
-
{ name: 'pull', run: (dir, args, flags) => require('./commands/sync')(dir, 'pull', args, flags) },
|
|
33
|
-
{ name: 'push', run: (dir, args, flags) => require('./commands/sync')(dir, 'push', args, flags) },
|
|
29
|
+
{ name: 'auto-push', run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
|
|
34
30
|
{ name: 'local', run: (dir, args, flags) => require('./commands/local')(dir, args, flags) },
|
|
35
|
-
{ name: 'list-targets',
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: 'connect', aliases: ['login'], run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
|
|
31
|
+
{ name: 'list-targets', run: () => require('./commands/listTargets')() },
|
|
32
|
+
{ name: 'connect', run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
|
|
38
33
|
];
|
|
39
34
|
|
|
40
35
|
const aliases = new Map();
|
package/src/commands/api.js
CHANGED
|
@@ -6,6 +6,7 @@ const log = require('../logger');
|
|
|
6
6
|
const { confirm } = require('../prompt');
|
|
7
7
|
const { loadProjectConfig } = require('../projectConfig');
|
|
8
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
9
|
+
const { cachedDescription, normalizeDescription, storeDescription } = require('../apiContractCache');
|
|
9
10
|
|
|
10
11
|
function readCallInput(projectDir, flags) {
|
|
11
12
|
const raw = flags.input
|
|
@@ -66,27 +67,45 @@ async function callOperation(projectDir, operationId, flags = {}) {
|
|
|
66
67
|
let session;
|
|
67
68
|
let description;
|
|
68
69
|
let input;
|
|
70
|
+
let config;
|
|
69
71
|
try {
|
|
70
|
-
|
|
72
|
+
config = loadProjectConfig(projectDir);
|
|
71
73
|
session = await openToolSession(config, [TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall]);
|
|
72
|
-
description =
|
|
74
|
+
description = cachedDescription(projectDir, config.server, operationId);
|
|
75
|
+
if (!description) {
|
|
76
|
+
const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
|
|
77
|
+
description = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
78
|
+
}
|
|
79
|
+
if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
73
80
|
input = readCallInput(projectDir, flags);
|
|
74
81
|
} catch (error) {
|
|
75
82
|
return printCallError(operationId, error, flags);
|
|
76
83
|
}
|
|
77
|
-
const destructive = description
|
|
78
|
-
|| description.operation && description.operation.destructive === true);
|
|
84
|
+
const destructive = description.operation.destructive === true;
|
|
79
85
|
if (destructive && !flags.yes && !flags.y) {
|
|
80
86
|
const approved = await confirm(`Call destructive DraftGo operation ${operationId}?`, { default: false });
|
|
81
87
|
if (!approved) return 0;
|
|
82
88
|
}
|
|
83
89
|
let result;
|
|
84
90
|
try {
|
|
85
|
-
|
|
86
|
-
...input,
|
|
87
|
-
|
|
88
|
-
...(destructive ? { confirm: true } : {}),
|
|
91
|
+
const invoke = (contract, confirmed) => callStructured(session, TOOL_NAMES.apiCall, {
|
|
92
|
+
...input, operation_id: operationId, registry_revision: contract.registry_revision,
|
|
93
|
+
...(confirmed ? { confirm: true } : {}),
|
|
89
94
|
});
|
|
95
|
+
try {
|
|
96
|
+
result = await invoke(description, destructive);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.code !== 'CONTRACT_CHANGED') throw error;
|
|
99
|
+
const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
|
|
100
|
+
description = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
101
|
+
if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
102
|
+
const changedDestructive = description.operation.destructive === true;
|
|
103
|
+
if (changedDestructive && !destructive && !flags.yes && !flags.y) {
|
|
104
|
+
const approved = await confirm(`Call destructive DraftGo operation ${operationId}?`, { default: false });
|
|
105
|
+
if (!approved) return 0;
|
|
106
|
+
}
|
|
107
|
+
result = await invoke(description, changedDestructive);
|
|
108
|
+
}
|
|
90
109
|
} catch (error) {
|
|
91
110
|
return printCallError(operationId, error, flags);
|
|
92
111
|
}
|
|
@@ -130,6 +149,7 @@ async function apiCommand(projectDir, positional, flags = {}) {
|
|
|
130
149
|
const result = await callStructured(session, expected, describe
|
|
131
150
|
? { operation_id: query }
|
|
132
151
|
: { query });
|
|
152
|
+
if (describe) storeDescription(projectDir, config.server, result);
|
|
133
153
|
if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
|
|
134
154
|
else printResult(describe ? `DraftGo API ${query}` : `DraftGo API search: ${query}`, result);
|
|
135
155
|
return 0;
|
package/src/commands/check.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
3
|
const log = require('../logger');
|
|
6
4
|
const { analyzeProject } = require('../projectMap');
|
|
7
5
|
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
8
6
|
const customServices = require('../customServices');
|
|
9
7
|
const { workspaceHealth } = require('../workspaceHealth');
|
|
10
8
|
|
|
11
|
-
function legacyCacheDetected(projectDir) {
|
|
12
|
-
return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
|
|
13
|
-
.some((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
async function check(projectDir, positional = [], flags = {}) {
|
|
17
10
|
if (!Array.isArray(positional)) {
|
|
18
11
|
flags = positional || {};
|
|
@@ -94,7 +87,7 @@ async function check(projectDir, positional = [], flags = {}) {
|
|
|
94
87
|
workspace_hygiene: hygiene,
|
|
95
88
|
};
|
|
96
89
|
if (flags.output === 'json') {
|
|
97
|
-
console.log(JSON.stringify(
|
|
90
|
+
console.log(JSON.stringify(output, null, 2));
|
|
98
91
|
return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
99
92
|
}
|
|
100
93
|
|
|
@@ -111,8 +104,6 @@ async function check(projectDir, positional = [], flags = {}) {
|
|
|
111
104
|
if (remote) log.info(`Remote checkout comparison: ${remote.length} resource(s)`);
|
|
112
105
|
for (const message of result.errors) log.err(message);
|
|
113
106
|
for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
|
|
114
|
-
if (legacyCacheDetected(projectDir)) log.dim(' Legacy .draftgo index caches were detected and ignored.');
|
|
115
|
-
|
|
116
107
|
if (result.errors.length || (flags.strict && result.warnings.length)) return 1;
|
|
117
108
|
log.ok(result.warnings.length ? 'Local checkout validation completed with warnings.' : 'Local checkout validation passed.');
|
|
118
109
|
return 0;
|
|
@@ -5,14 +5,12 @@ const log = require('../logger');
|
|
|
5
5
|
const services = require('../customServices');
|
|
6
6
|
|
|
7
7
|
function ids(positional) { return positional.map(String).filter(Boolean); }
|
|
8
|
-
const TYPE_ALIASES = new Set(['custom-services', 'custom-service', 'custom_services', 'custom_service', 'services', 'scripts', 'custom_scripts']);
|
|
9
8
|
function isServiceType(value) {
|
|
10
|
-
return
|
|
9
|
+
return String(value || '').toLowerCase() === 'custom-services';
|
|
11
10
|
}
|
|
12
11
|
function serviceIds(positional) {
|
|
13
12
|
const values = ids(positional);
|
|
14
|
-
|
|
15
|
-
return values;
|
|
13
|
+
return isServiceType(values[0]) ? values.slice(1) : [];
|
|
16
14
|
}
|
|
17
15
|
function output(flags, value) {
|
|
18
16
|
if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
|
package/src/commands/delete.js
CHANGED
|
@@ -6,23 +6,7 @@ const log = require('../logger');
|
|
|
6
6
|
const { confirm } = require('../prompt');
|
|
7
7
|
const { loadProjectConfig } = require('../projectConfig');
|
|
8
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
9
|
-
|
|
10
|
-
const LEGACY_TYPES = new Set([
|
|
11
|
-
'page', 'pages', 'nav', 'navigation', 'navigations', 'doc', 'docs', 'article',
|
|
12
|
-
'db_meta', 'custom_scripts', 'doc_categories', 'aihub', 'roles', 'users', 'system_config',
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
function resultItems(value) {
|
|
16
|
-
if (Array.isArray(value)) return value;
|
|
17
|
-
if (!value || typeof value !== 'object') return [];
|
|
18
|
-
if (Array.isArray(value.items)) return value.items;
|
|
19
|
-
if (Array.isArray(value.operations)) return value.operations;
|
|
20
|
-
return value.data ? resultItems(value.data) : [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function operationId(value) {
|
|
24
|
-
return value && (value.operation_id || value.operationId || value.id);
|
|
25
|
-
}
|
|
9
|
+
const { cachedDescription, normalizeDescription, storeDescription } = require('../apiContractCache');
|
|
26
10
|
|
|
27
11
|
function readParams(projectDir, flags) {
|
|
28
12
|
const raw = flags.params ? String(flags.params)
|
|
@@ -45,40 +29,25 @@ function pathParameterName(description) {
|
|
|
45
29
|
async function deleteResource(projectDir, positional, flags = {}) {
|
|
46
30
|
if (!positional.length && !flags['operation-id']) {
|
|
47
31
|
log.err('Usage: draftgo delete <operation_id> [id] [--params JSON]');
|
|
48
|
-
log.dim(' Legacy form is supported when discovery is unambiguous: draftgo delete <type> <id>.');
|
|
49
32
|
return 1;
|
|
50
33
|
}
|
|
51
34
|
const config = loadProjectConfig(projectDir);
|
|
52
35
|
const session = await openToolSession(config, [
|
|
53
|
-
TOOL_NAMES.
|
|
36
|
+
TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall,
|
|
54
37
|
]);
|
|
55
38
|
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
let selectedOperation = flags['operation-id'] ? String(flags['operation-id']) : (legacy ? null : first);
|
|
59
|
-
const resourceId = legacy ? positional[1] : positional[1];
|
|
60
|
-
if (legacy) {
|
|
61
|
-
if (!resourceId) {
|
|
62
|
-
log.err('Legacy delete form requires a resource id.');
|
|
63
|
-
return 1;
|
|
64
|
-
}
|
|
65
|
-
const search = await callStructured(session, TOOL_NAMES.apiSearch, { query: `delete ${first}` });
|
|
66
|
-
const candidates = resultItems(search).filter((item) => operationId(item)
|
|
67
|
-
&& (!item.method || String(item.method).toUpperCase() === 'DELETE'));
|
|
68
|
-
if (candidates.length !== 1) {
|
|
69
|
-
log.err(`Delete operation discovery was ambiguous (${candidates.length} matches).`);
|
|
70
|
-
log.dim(` Run \`draftgo api delete ${first}\`, then pass --operation-id explicitly.`);
|
|
71
|
-
return 1;
|
|
72
|
-
}
|
|
73
|
-
selectedOperation = operationId(candidates[0]);
|
|
74
|
-
}
|
|
39
|
+
const selectedOperation = flags['operation-id'] ? String(flags['operation-id']) : String(positional[0]);
|
|
40
|
+
const resourceId = positional[1];
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
42
|
+
let contract = cachedDescription(projectDir, config.server, selectedOperation);
|
|
43
|
+
if (!contract) {
|
|
44
|
+
const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: selectedOperation });
|
|
45
|
+
contract = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
46
|
+
}
|
|
47
|
+
if (!contract) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
79
48
|
const params = readParams(projectDir, flags);
|
|
80
49
|
if (resourceId && Object.keys(params).length === 0) {
|
|
81
|
-
const parameterName = pathParameterName(
|
|
50
|
+
const parameterName = pathParameterName(contract.operation);
|
|
82
51
|
if (!parameterName) {
|
|
83
52
|
log.err('The operation does not expose exactly one path parameter; provide --params JSON explicitly.');
|
|
84
53
|
return 1;
|
|
@@ -89,11 +58,19 @@ async function deleteResource(projectDir, positional, flags = {}) {
|
|
|
89
58
|
const approved = await confirm(`Call destructive DraftGo operation ${selectedOperation}?`, { default: false });
|
|
90
59
|
if (!approved) return 0;
|
|
91
60
|
}
|
|
92
|
-
const
|
|
93
|
-
...params,
|
|
94
|
-
operation_id: selectedOperation,
|
|
95
|
-
confirm: true,
|
|
61
|
+
const invoke = () => callStructured(session, TOOL_NAMES.apiCall, {
|
|
62
|
+
...params, operation_id: selectedOperation, registry_revision: contract.registry_revision, confirm: true,
|
|
96
63
|
});
|
|
64
|
+
let result;
|
|
65
|
+
try {
|
|
66
|
+
result = await invoke();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.code !== 'CONTRACT_CHANGED') throw error;
|
|
69
|
+
const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: selectedOperation });
|
|
70
|
+
contract = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
71
|
+
if (!contract) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
72
|
+
result = await invoke();
|
|
73
|
+
}
|
|
97
74
|
if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
|
|
98
75
|
else log.ok(`DraftGo operation completed: ${selectedOperation}`);
|
|
99
76
|
return 0;
|
package/src/commands/deploy.js
CHANGED
|
@@ -23,7 +23,7 @@ async function deploy(projectDir, positional, flags = {}) {
|
|
|
23
23
|
|
|
24
24
|
const [resourceType, ...ids] = positional;
|
|
25
25
|
if (!resourceType || !ids.length) {
|
|
26
|
-
log.err('
|
|
26
|
+
log.err('Usage: draftgo deploy <pages|nav|docs> <id...> --delivery preview|deploy');
|
|
27
27
|
return 1;
|
|
28
28
|
}
|
|
29
29
|
if (delivery === 'preview') {
|