draftgo-cli 3.0.49 → 3.0.52
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 +24 -10
- package/package.json +5 -4
- package/resources/skill/SKILL.md +13 -4
- package/resources/skill/init/SKILL.md +3 -1
- package/resources/skill/manifest.json +1 -1
- package/resources/skill/references/checkout.md +9 -3
- package/resources/skill/references/custom-services.md +1 -0
- package/resources/skill/references/frontend.md +1 -1
- package/src/cli.js +35 -9
- package/src/commandRegistry.js +9 -2
- package/src/commands/check.js +77 -7
- package/src/commands/checkout.js +3 -0
- package/src/commands/clean.js +72 -0
- package/src/commands/commit.js +3 -0
- package/src/commands/conflict.js +5 -3
- package/src/commands/conflicts.js +2 -1
- package/src/commands/context.js +2 -2
- package/src/commands/customService.js +81 -0
- package/src/commands/diff.js +3 -0
- package/src/commands/help.js +41 -12
- package/src/commands/init.js +2 -3
- package/src/commands/map.js +15 -4
- package/src/commands/reconcile.js +14 -0
- package/src/commands/task.js +408 -0
- package/src/commands/verify.js +90 -0
- package/src/commands/verifyUi.js +68 -23
- package/src/commands/verifyUiCompat.js +16 -0
- package/src/context/index.js +28 -12
- package/src/customServices.js +246 -0
- package/src/projectMap.js +20 -8
- package/src/runtimeFiles.js +44 -0
- package/src/skill.js +0 -9
- package/src/workspaceHealth.js +33 -0
- package/src/worktree/inlineScripts.js +99 -0
- package/src/worktree/status.js +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const log = require('../logger');
|
|
5
|
+
const services = require('../customServices');
|
|
6
|
+
|
|
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
|
+
function isServiceType(value) {
|
|
10
|
+
return TYPE_ALIASES.has(String(value || '').toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
function serviceIds(positional) {
|
|
13
|
+
const values = ids(positional);
|
|
14
|
+
if (isServiceType(values[0])) values.shift();
|
|
15
|
+
return values;
|
|
16
|
+
}
|
|
17
|
+
function output(flags, value) {
|
|
18
|
+
if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function checkout(projectDir, positional, flags = {}) {
|
|
22
|
+
const values = ids(positional);
|
|
23
|
+
if (!values.length) { log.err('Usage: draftgo checkout custom-services <id...>'); return 1; }
|
|
24
|
+
const result = await services.checkout(projectDir, values, { force: Boolean(flags.force) });
|
|
25
|
+
if (flags.output === 'json') output(flags, result);
|
|
26
|
+
else result.forEach((item) => log.ok(`custom service ${item.resource_id} -> ${item.local_path}`));
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function commit(projectDir, positional, flags = {}) {
|
|
31
|
+
const values = ids(positional);
|
|
32
|
+
if (!values.length) { log.err('Usage: draftgo commit custom-services <id...>'); return 1; }
|
|
33
|
+
const result = await services.commit(projectDir, values);
|
|
34
|
+
if (flags.output === 'json') output(flags, result);
|
|
35
|
+
else result.forEach((item) => log.ok(`custom service ${item.resource_id}: ${item.status}`));
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function diff(projectDir, positional, flags = {}) {
|
|
40
|
+
const id = positional[0];
|
|
41
|
+
if (!id) { log.err('Usage: draftgo diff custom-services <id>'); return 1; }
|
|
42
|
+
const result = services.diff(projectDir, id);
|
|
43
|
+
if (flags.output === 'json') output(flags, result);
|
|
44
|
+
else if (result.changed) process.stdout.write(result.output);
|
|
45
|
+
else log.dim(`custom service ${id}: no local changes`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function validate(projectDir, positional, flags = {}) {
|
|
50
|
+
const id = serviceIds(positional)[0];
|
|
51
|
+
if (!id) { log.err('Usage: draftgo validate custom-services <id>'); return 1; }
|
|
52
|
+
const result = await services.validate(projectDir, id);
|
|
53
|
+
if (flags.output === 'json') output(flags, result); else log.ok(`custom service ${id}: ${result.validation_status}`);
|
|
54
|
+
return result.validation_status === 'passed' ? 0 : 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function test(projectDir, positional, flags = {}) {
|
|
58
|
+
const id = serviceIds(positional)[0];
|
|
59
|
+
if (!id) { log.err('Usage: draftgo test custom-services <id> [--input <json-file>]'); return 1; }
|
|
60
|
+
let input = {};
|
|
61
|
+
if (flags.input) input = JSON.parse(fs.readFileSync(String(flags.input), 'utf8'));
|
|
62
|
+
const readJSON = (value) => value ? JSON.parse(fs.readFileSync(String(value), 'utf8')) : undefined;
|
|
63
|
+
const policy = String(flags['side-effect-policy'] || 'deny').toLowerCase();
|
|
64
|
+
if (!['deny', 'mock', 'live'].includes(policy)) throw new Error('--side-effect-policy must be deny, mock, or live.');
|
|
65
|
+
const result = await services.test(projectDir, id, input, { handler: flags.handler, headers: readJSON(flags.headers), user: readJSON(flags.user),
|
|
66
|
+
testWrite: Boolean(flags['test-write']), sideEffectPolicy: policy });
|
|
67
|
+
if (flags.output === 'json') output(flags, result);
|
|
68
|
+
else log[result.status === 'success' ? 'ok' : 'err'](`custom service ${id}: ${result.status}`);
|
|
69
|
+
return result.status === 'success' ? 0 : 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function publish(projectDir, positional, flags = {}) {
|
|
73
|
+
const values = serviceIds(positional);
|
|
74
|
+
if (!values.length) { log.err('Usage: draftgo publish custom-services <id...>'); return 1; }
|
|
75
|
+
const result = await services.publish(projectDir, values);
|
|
76
|
+
if (flags.output === 'json') output(flags, result);
|
|
77
|
+
else values.forEach((id) => log.ok(`custom service ${id}: published`));
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { checkout, commit, diff, validate, test, publish, serviceIds, isServiceType };
|
package/src/commands/diff.js
CHANGED
|
@@ -9,6 +9,9 @@ function diff(projectDir, positional, flags = {}) {
|
|
|
9
9
|
log.err('Usage: draftgo diff <pages|nav|docs> <id>');
|
|
10
10
|
return 1;
|
|
11
11
|
}
|
|
12
|
+
if (require('./customService').isServiceType(resourceType)) {
|
|
13
|
+
return require('./customService').diff(projectDir, [resourceId], flags);
|
|
14
|
+
}
|
|
12
15
|
const result = diffResource(projectDir, resourceType, resourceId);
|
|
13
16
|
if (flags.output === 'json') {
|
|
14
17
|
console.log(JSON.stringify({ changed: result.changed, entry: result.entry, diff: result.output }, null, 2));
|
package/src/commands/help.js
CHANGED
|
@@ -16,7 +16,7 @@ function help() {
|
|
|
16
16
|
DraftGo Next frontend baseline: React + Vite.
|
|
17
17
|
Database pages may use native web technologies or bundled component libraries.
|
|
18
18
|
MCP handles live discovery and structured resources; complete page, navigation,
|
|
19
|
-
and
|
|
19
|
+
document, and custom-service bodies use checkout/commit outside MCP context.
|
|
20
20
|
The context command combines exact local Reference sections with live MCP data;
|
|
21
21
|
it is a CLI orchestration command, not an MCP tool.
|
|
22
22
|
|
|
@@ -44,10 +44,20 @@ Usage:
|
|
|
44
44
|
overlay local checkout state.
|
|
45
45
|
draftgo checkout <type> <id...> Download pages/nav/docs body + verified base.
|
|
46
46
|
--force explicitly replaces local changes.
|
|
47
|
-
draftgo check
|
|
47
|
+
draftgo check [custom-services <id...>]
|
|
48
|
+
Validate all checkouts or selected custom services.
|
|
48
49
|
--remote also compares remote hash/version.
|
|
50
|
+
draftgo verify [<type> <id...>] Run unified static verification; --remote adds
|
|
51
|
+
remote comparison and --url enables UI checks.
|
|
49
52
|
draftgo diff <type> <id> Show checkout base versus local body.
|
|
50
53
|
draftgo commit <type> <id...> Validate and upload complete checked-out bodies.
|
|
54
|
+
draftgo refresh <type> <id...> Safely refresh a clean checkout from the cloud.
|
|
55
|
+
draftgo validate custom-services <id>
|
|
56
|
+
Build and inspect the current cloud draft.
|
|
57
|
+
draftgo test custom-services <id> [--handler <selector>]
|
|
58
|
+
Run the cloud draft in the DraftGo Runner.
|
|
59
|
+
draftgo publish custom-services <id...>
|
|
60
|
+
Publish validated drafts atomically.
|
|
51
61
|
draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
|
|
52
62
|
draftgo conflicts List unresolved conflicts; --all includes resolved.
|
|
53
63
|
draftgo conflict show <type> <id>
|
|
@@ -55,7 +65,12 @@ Usage:
|
|
|
55
65
|
draftgo conflict resolve <type> <id>
|
|
56
66
|
Mark a merged worktree file ready against the
|
|
57
67
|
preserved remote base; then check/diff/commit.
|
|
58
|
-
draftgo verify-ui <url>
|
|
68
|
+
draftgo verify-ui <url> Deprecated alias for verify --url <url> --ui always.
|
|
69
|
+
draftgo clean [--dry-run|--yes] Plan or remove all tmp and registered artifacts.
|
|
70
|
+
draftgo task <operation> [...] Track sustained work in one authoritative Task.md.
|
|
71
|
+
Operations: create, list, show, add, claim,
|
|
72
|
+
start, complete, block, reopen, accept, finish,
|
|
73
|
+
and explicit legacy migrate.
|
|
59
74
|
|
|
60
75
|
draftgo api <query> Search the live API contract through MCP.
|
|
61
76
|
draftgo api describe <operation_id>
|
|
@@ -86,7 +101,7 @@ Usage:
|
|
|
86
101
|
draftgo -h | --help Show this help.
|
|
87
102
|
|
|
88
103
|
Resource types:
|
|
89
|
-
pages | nav/navigations | docs/articles
|
|
104
|
+
pages | nav/navigations | docs/articles | custom-services
|
|
90
105
|
|
|
91
106
|
Important flags:
|
|
92
107
|
--project <dir> Operate on <dir> instead of the current directory.
|
|
@@ -115,18 +130,28 @@ Important flags:
|
|
|
115
130
|
--operation-id <id> (delete) Select an operation explicitly.
|
|
116
131
|
--params <json> (api call/delete) Pass an API parameter object.
|
|
117
132
|
--input <file> (api call/delete) Read a UTF-8 JSON parameter object.
|
|
133
|
+
--handler <selector> (custom-service test) route:METHOD:/path, event:name,
|
|
134
|
+
scheduled:name, or a handler name.
|
|
135
|
+
--headers/--user <file> (custom-service test) Read JSON test identity data.
|
|
136
|
+
--test-write (custom-service test) Permit declared write testing.
|
|
137
|
+
--side-effect-policy <mode>
|
|
138
|
+
(custom-service test) deny | mock | live; default deny.
|
|
118
139
|
--delivery <mode> (deploy) local | preview | deploy.
|
|
119
140
|
--dry-run (push) Show diffs without committing.
|
|
120
|
-
--
|
|
121
|
-
--
|
|
141
|
+
--ui <mode> (verify) auto | always | never; auto runs with --url.
|
|
142
|
+
--remote (check/verify) Compare checkout hashes with remote.
|
|
143
|
+
--viewport <mode> (verify) mobile | desktop | both.
|
|
144
|
+
--frame <mode> (verify) auto | top | all | <iframe-selector>.
|
|
145
|
+
--mobile-check <mode> (verify-ui compatibility) auto | always | never.
|
|
146
|
+
--token <mode> (verify) auto | never; auto appends the configured
|
|
122
147
|
SAT to same-origin URLs as the token query parameter.
|
|
123
|
-
--screenshot <mode> (verify
|
|
124
|
-
--browser <name> (verify
|
|
125
|
-
--browser-path <file> (verify
|
|
148
|
+
--screenshot <mode> (verify) on-failure | always | never; default never.
|
|
149
|
+
--browser <name> (verify) chromium | chrome | msedge.
|
|
150
|
+
--browser-path <file> (verify) Explicit browser executable; environment
|
|
126
151
|
fallback: DRAFTGO_BROWSER_PATH.
|
|
127
|
-
--resource <type:id> (verify
|
|
128
|
-
--selector <css> (verify
|
|
129
|
-
--width/--height <px> (verify
|
|
152
|
+
--resource <type:id> (verify compatibility) Assert one remote UI resource.
|
|
153
|
+
--selector <css> (verify) Require a visible element in top or iframe DOM.
|
|
154
|
+
--width/--height <px> (verify) Override the default 390x844 viewport.
|
|
130
155
|
|
|
131
156
|
Security:
|
|
132
157
|
.draftgo/config.json stores the server and SAT and is gitignored. Host MCP
|
|
@@ -146,9 +171,13 @@ Examples:
|
|
|
146
171
|
draftgo map --output json
|
|
147
172
|
draftgo checkout pages 42
|
|
148
173
|
draftgo check --strict
|
|
174
|
+
draftgo verify pages 42 --remote --url http://localhost:5173/example
|
|
149
175
|
draftgo diff pages 42
|
|
150
176
|
draftgo commit pages 42
|
|
151
177
|
draftgo changelog add "Complete document management and role permissions"
|
|
178
|
+
draftgo task create "Personal growth" --original "Build a Notion-like system" \
|
|
179
|
+
--clarified "Build goals, habits, notes, and reviews" \
|
|
180
|
+
--expected-effect "Users manage personal growth in one workspace"
|
|
152
181
|
draftgo deploy docs 7 --delivery preview
|
|
153
182
|
`);
|
|
154
183
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const log = require('../logger');
|
|
5
5
|
const { detectTargets } = require('../detect');
|
|
6
6
|
const { resolveTargets, all } = require('../installers');
|
|
7
|
-
const { ensureRuntime,
|
|
7
|
+
const { ensureRuntime, getPackageVersion } = require('../skill');
|
|
8
8
|
const { exists } = require('../fsx');
|
|
9
9
|
const { ask } = require('../prompt');
|
|
10
10
|
|
|
@@ -65,8 +65,7 @@ async function init(projectDir, positional, flags) {
|
|
|
65
65
|
log.err(`安装未完成,失败目标:${failures.join(', ')}`);
|
|
66
66
|
return 1;
|
|
67
67
|
}
|
|
68
|
-
if (
|
|
69
|
-
if (skippedCount > 0) log.dim(' 存在未覆盖目标,未更新全局 skill 版本标记;运行 draftgo update 可统一刷新。');
|
|
68
|
+
if (skippedCount > 0) log.dim(' 存在未覆盖目标;运行 draftgo update 可统一刷新。');
|
|
70
69
|
log.dim(` CLI 版本:${getPackageVersion()}`);
|
|
71
70
|
|
|
72
71
|
// 3) Environment check (advisory).
|
package/src/commands/map.js
CHANGED
|
@@ -8,11 +8,13 @@ const { canonicalResourceType } = require('../worktree/types');
|
|
|
8
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
9
9
|
const { allWithAbort } = require('../mcp/parallel');
|
|
10
10
|
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
11
|
+
const customServices = require('../customServices');
|
|
11
12
|
|
|
12
13
|
const REMOTE_RESOURCE_TYPES = Object.freeze({
|
|
13
14
|
pages: 'pages',
|
|
14
15
|
navigations: 'navigations',
|
|
15
16
|
docs: 'docs/articles',
|
|
17
|
+
custom_services: 'custom_services',
|
|
16
18
|
});
|
|
17
19
|
const DEFAULT_REMOTE_RESOURCE_TYPES = Object.freeze(Object.values(REMOTE_RESOURCE_TYPES));
|
|
18
20
|
|
|
@@ -41,6 +43,7 @@ function claimsMorePages(value) {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
function normalizeMapResourceType(value) {
|
|
46
|
+
if (require('./customService').isServiceType(value)) return 'custom_services';
|
|
44
47
|
return REMOTE_RESOURCE_TYPES[canonicalResourceType(value)];
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -86,6 +89,13 @@ function legacyCaches(projectDir) {
|
|
|
86
89
|
.filter((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
function checkoutChanged(entry) {
|
|
93
|
+
if (entry.resource_type === 'custom_services') {
|
|
94
|
+
return ['local_modified', 'diverged', 'committed_unrecorded'].includes(entry.state);
|
|
95
|
+
}
|
|
96
|
+
return entry.local_hash !== null && entry.local_hash !== entry.base_hash;
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
async function mapCommand(projectDir, flags = {}) {
|
|
90
100
|
const resourceTypes = requestedResourceTypes(flags);
|
|
91
101
|
const config = loadProjectConfig(projectDir);
|
|
@@ -96,17 +106,17 @@ async function mapCommand(projectDir, flags = {}) {
|
|
|
96
106
|
(options) => listRemoteResources(session, resourceTypes, options),
|
|
97
107
|
], options),
|
|
98
108
|
async (options) => {
|
|
99
|
-
const
|
|
109
|
+
const [contentStatus, serviceStatus] = await Promise.all([inspectRemoteCheckouts(projectDir, {
|
|
100
110
|
config,
|
|
101
111
|
client: session.client,
|
|
102
112
|
tools: session.tools,
|
|
103
113
|
signal: options.signal,
|
|
104
|
-
});
|
|
105
|
-
return
|
|
114
|
+
}), customServices.inspectRemote(projectDir, { config, signal: options.signal })]);
|
|
115
|
+
return [...contentStatus, ...serviceStatus].map((entry) => ({
|
|
106
116
|
...entry,
|
|
107
117
|
exists: entry.local_hash !== null,
|
|
108
118
|
current_hash: entry.local_hash,
|
|
109
|
-
changed: entry
|
|
119
|
+
changed: checkoutChanged(entry),
|
|
110
120
|
}));
|
|
111
121
|
},
|
|
112
122
|
]);
|
|
@@ -150,3 +160,4 @@ module.exports.itemsFrom = itemsFrom;
|
|
|
150
160
|
module.exports.normalizeMapResourceType = normalizeMapResourceType;
|
|
151
161
|
module.exports.requestedResourceTypes = requestedResourceTypes;
|
|
152
162
|
module.exports.listRemoteResources = listRemoteResources;
|
|
163
|
+
module.exports.checkoutChanged = checkoutChanged;
|
|
@@ -9,6 +9,20 @@ async function reconcile(projectDir, positional, flags = {}) {
|
|
|
9
9
|
log.err('Usage: draftgo reconcile <pages|nav|docs> <id...>');
|
|
10
10
|
return 1;
|
|
11
11
|
}
|
|
12
|
+
if (require('./customService').isServiceType(resourceType)) {
|
|
13
|
+
const services = require('../customServices');
|
|
14
|
+
const states = await services.inspectRemote(projectDir);
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const id of ids) {
|
|
17
|
+
const state = states.find((entry) => String(entry.resource_id) === String(id));
|
|
18
|
+
if (!state || state.state !== 'committed_unrecorded') throw new Error(`custom_services ${id} cannot be reconciled from state ${state?.state || 'missing'}.`);
|
|
19
|
+
await services.checkout(projectDir, [id], { force: true });
|
|
20
|
+
results.push({ resource_type: 'custom_services', resource_id: String(id), base_revision: state.remote_revision });
|
|
21
|
+
}
|
|
22
|
+
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
23
|
+
else results.forEach((result) => log.ok(`Reconciled custom_services ${result.resource_id} -> ${result.base_revision}`));
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
12
26
|
const results = await reconcileResources(projectDir, resourceType, ids);
|
|
13
27
|
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
14
28
|
else for (const result of results) {
|