draftgo-cli 4.0.24 → 4.0.26
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 +23 -37
- package/package.json +3 -5
- package/resources/skill/SKILL.md +9 -5
- package/resources/skill/manifest.json +2 -5
- package/resources/skill/references/ai.md +41 -0
- package/resources/skill/references/app-api.md +2 -50
- package/resources/skill/references/architecture.md +1 -1
- package/resources/skill/references/chat-sdk.md +29 -37
- package/resources/skill/references/checkout.md +4 -4
- package/resources/skill/references/data.md +0 -46
- package/resources/skill/references/delivery.md +3 -3
- package/resources/skill/references/diagnostics.md +10 -11
- package/resources/skill/references/frontend.md +23 -20
- package/resources/skill/references/mcp.md +4 -14
- package/resources/skill/references/methods.md +15 -68
- package/resources/skill/references/modules.md +23 -44
- package/resources/skill/references/runtime.md +3 -20
- package/resources/skill/story/SKILL.md +2 -2
- package/src/apiContractCache.js +14 -6
- package/src/cli.js +0 -7
- package/src/commandRegistry.js +0 -6
- package/src/commands/api.js +87 -17
- package/src/commands/apiKey.js +2 -6
- package/src/commands/autoPush.js +15 -51
- package/src/commands/capabilities.js +22 -15
- package/src/commands/check.js +19 -53
- package/src/commands/checkout.js +1 -4
- package/src/commands/clean.js +1 -1
- package/src/commands/commit.js +1 -4
- package/src/commands/components.js +12 -8
- package/src/commands/conflict.js +4 -6
- package/src/commands/conflicts.js +1 -2
- package/src/commands/connect.js +0 -8
- package/src/commands/delete.js +15 -11
- package/src/commands/deploy.js +64 -26
- package/src/commands/diff.js +1 -4
- package/src/commands/group.js +2 -3
- package/src/commands/help.js +22 -43
- package/src/commands/init.js +13 -6
- package/src/commands/local.js +4 -1
- package/src/commands/map.js +138 -23
- package/src/commands/reconcile.js +1 -15
- package/src/commands/role.js +1 -2
- package/src/commands/status.js +12 -40
- package/src/commands/update.js +18 -24
- package/src/commands/verify.js +8 -7
- package/src/commands/worklog.js +11 -5
- package/src/contractCompatibility.js +10 -2
- package/src/localRuntime/compose.js +41 -27
- package/src/localRuntime/detect.js +6 -6
- package/src/localRuntime/index.js +47 -47
- package/src/localRuntime/services.js +2 -39
- package/src/mcp/client.js +99 -134
- package/src/mcp/parallel.js +25 -2
- package/src/mcp/protocol.js +38 -9
- package/src/mcp/tools.js +10 -19
- package/src/projectConfig.js +1 -4
- package/src/{workspaceHealth.js → projectHealth.js} +5 -5
- package/src/projectMap.js +1 -1
- package/src/runtimeFiles.js +2 -1
- package/src/worklog.js +3 -2
- package/src/worktree/backend.js +127 -15
- package/src/worktree/index.js +64 -22
- package/src/worktree/locks.js +52 -0
- package/src/worktree/manifest.js +18 -4
- package/src/worktree/status.js +4 -2
- package/resources/custom-service-sdk/ai.go +0 -520
- package/resources/custom-service-sdk/ai_test.go +0 -156
- package/resources/custom-service-sdk/auth_test.go +0 -56
- package/resources/custom-service-sdk/billing.go +0 -596
- package/resources/custom-service-sdk/billing_test.go +0 -150
- package/resources/custom-service-sdk/go.mod +0 -3
- package/resources/custom-service-sdk/manifest.json +0 -77
- package/resources/custom-service-sdk/platform.go +0 -352
- package/resources/custom-service-sdk/platform_logger_test.go +0 -24
- package/resources/custom-service-sdk/registration_test.go +0 -39
- package/resources/custom-service-sdk/resources.go +0 -247
- package/resources/custom-service-sdk/resources_billing_test.go +0 -115
- package/resources/custom-service-sdk/resources_files_test.go +0 -57
- package/resources/custom-service-sdk/resources_scope_test.go +0 -92
- package/resources/custom-service-sdk/sdk.go +0 -209
- package/resources/skill/references/aihub.md +0 -116
- package/resources/skill/references/custom-services.md +0 -201
- package/src/commands/customService.js +0 -95
- package/src/commands/dataRange.js +0 -33
- package/src/commands/grant.js +0 -29
- package/src/commands/space.js +0 -41
- package/src/customServices.js +0 -484
package/src/cli.js
CHANGED
|
@@ -35,11 +35,6 @@ const VALUE_FLAGS = new Set([
|
|
|
35
35
|
'limit',
|
|
36
36
|
'cursor',
|
|
37
37
|
'date',
|
|
38
|
-
'handler',
|
|
39
|
-
'source',
|
|
40
|
-
'headers',
|
|
41
|
-
'user',
|
|
42
|
-
'side-effect-policy',
|
|
43
38
|
'page',
|
|
44
39
|
'instance',
|
|
45
40
|
'note',
|
|
@@ -52,8 +47,6 @@ const VALUE_FLAGS = new Set([
|
|
|
52
47
|
'tags',
|
|
53
48
|
'source-type',
|
|
54
49
|
'status',
|
|
55
|
-
'scope-type',
|
|
56
|
-
'space-id',
|
|
57
50
|
'module',
|
|
58
51
|
'resource-type',
|
|
59
52
|
'method',
|
package/src/commandRegistry.js
CHANGED
|
@@ -16,20 +16,14 @@ const commands = [
|
|
|
16
16
|
{ name: 'checkout', run: (dir, args, flags) => require('./commands/checkout')(dir, args, flags) },
|
|
17
17
|
{ name: 'refresh', run: (dir, args, flags) => require('./commands/checkout')(dir, args, flags) },
|
|
18
18
|
{ name: 'commit', run: (dir, args, flags) => require('./commands/commit')(dir, args, flags) },
|
|
19
|
-
{ name: 'validate', run: (dir, args, flags) => require('./commands/customService').validate(dir, args, flags) },
|
|
20
|
-
{ name: 'test', run: (dir, args, flags) => require('./commands/customService').test(dir, args, flags) },
|
|
21
|
-
{ name: 'publish', run: (dir, args, flags) => require('./commands/customService').publish(dir, args, flags) },
|
|
22
19
|
{ name: 'reconcile', run: (dir, args, flags) => require('./commands/reconcile')(dir, args, flags) },
|
|
23
20
|
{ name: 'diff', run: (dir, args, flags) => require('./commands/diff')(dir, args, flags) },
|
|
24
21
|
{ name: 'conflicts', run: (dir, args, flags) => require('./commands/conflicts')(dir, args, flags) },
|
|
25
22
|
{ name: 'conflict', run: (dir, args, flags) => require('./commands/conflict')(dir, args, flags) },
|
|
26
23
|
{ name: 'api', run: (dir, args, flags) => require('./commands/api')(dir, args, flags) },
|
|
27
|
-
{ name: 'space', run: (dir, args, flags) => require('./commands/space')(dir, args, flags) },
|
|
28
24
|
{ name: 'group', run: (dir, args, flags) => require('./commands/group')(dir, args, flags) },
|
|
29
|
-
{ name: 'grant', run: (dir, args, flags) => require('./commands/grant')(dir, args, flags) },
|
|
30
25
|
{ name: 'api-key', run: (dir, args, flags) => require('./commands/apiKey')(dir, args, flags) },
|
|
31
26
|
{ name: 'role', run: (dir, args, flags) => require('./commands/role')(dir, args, flags) },
|
|
32
|
-
{ name: 'data-range', run: (dir, args, flags) => require('./commands/dataRange')(dir, args, flags) },
|
|
33
27
|
{ name: 'delete', run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
|
|
34
28
|
{ name: 'deploy', run: (dir, args, flags) => require('./commands/deploy')(dir, args, flags) },
|
|
35
29
|
{ name: 'auto-push', run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
|
package/src/commands/api.js
CHANGED
|
@@ -5,17 +5,51 @@ const path = require('path');
|
|
|
5
5
|
const log = require('../logger');
|
|
6
6
|
const { confirm } = require('../prompt');
|
|
7
7
|
const { loadProjectConfig } = require('../projectConfig');
|
|
8
|
-
const { TOOL_NAMES, openToolSession, callStructured
|
|
8
|
+
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
9
9
|
const { cachedDescription, normalizeDescription, storeDescription } = require('../apiContractCache');
|
|
10
10
|
const {
|
|
11
11
|
isContractCompatibilityError,
|
|
12
12
|
isHardContractIncompatibility,
|
|
13
13
|
isDangerousOperation,
|
|
14
14
|
sameOperationContract,
|
|
15
|
+
canRetryAfterContractChange,
|
|
15
16
|
incompatibleOperationError,
|
|
16
17
|
} = require('../contractCompatibility');
|
|
17
18
|
|
|
19
|
+
async function registryRevision(session, operationId) {
|
|
20
|
+
const result = await callStructured(session, TOOL_NAMES.apiSearch, {
|
|
21
|
+
query: operationId,
|
|
22
|
+
limit: 100,
|
|
23
|
+
});
|
|
24
|
+
const items = result && Array.isArray(result.items) ? result.items : [];
|
|
25
|
+
const operation = items.find((item) => item && item.operation_id === operationId);
|
|
26
|
+
const revision = String(result && result.registry_revision || '').trim();
|
|
27
|
+
if (!operation || !revision) {
|
|
28
|
+
throw new Error(`DraftGo Registry could not resolve operation ${operationId} with a revision.`);
|
|
29
|
+
}
|
|
30
|
+
return revision;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function describeOperation(projectDir, config, session, operationId, knownRevision = '') {
|
|
34
|
+
let revision = String(knownRevision || '').trim();
|
|
35
|
+
if (!revision) revision = await registryRevision(session, operationId);
|
|
36
|
+
const described = await callStructured(session, TOOL_NAMES.apiDescribe, {
|
|
37
|
+
operation_id: operationId,
|
|
38
|
+
registry_revision: revision,
|
|
39
|
+
});
|
|
40
|
+
return storeDescription(projectDir, config.server, described, revision)
|
|
41
|
+
|| normalizeDescription(described, revision);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function descriptionForRevision(projectDir, config, session, operationId, knownRevision = '') {
|
|
45
|
+
const revision = String(knownRevision || '').trim() || await registryRevision(session, operationId);
|
|
46
|
+
const cached = cachedDescription(projectDir, config.server, operationId);
|
|
47
|
+
if (cached && cached.registry_revision === revision) return cached;
|
|
48
|
+
return describeOperation(projectDir, config, session, operationId, revision);
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
function readCallInput(projectDir, flags) {
|
|
52
|
+
if (flags.input && flags.params) throw new Error('Use either --input or --params, not both.');
|
|
19
53
|
const raw = flags.input
|
|
20
54
|
? fs.readFileSync(path.resolve(projectDir, String(flags.input)))
|
|
21
55
|
: flags.params ? Buffer.from(String(flags.params), 'utf8') : Buffer.from('{}', 'utf8');
|
|
@@ -32,17 +66,38 @@ function diagnosticResult(operationId, result) {
|
|
|
32
66
|
const statusCode = source.status_code ?? source.statusCode ?? source.status ?? null;
|
|
33
67
|
const headerRequestId = source.headers
|
|
34
68
|
&& (source.headers['x-request-id'] || source.headers['X-Request-Id']);
|
|
35
|
-
const
|
|
36
|
-
const
|
|
69
|
+
const rawRequestId = source.request_id ?? source.requestId ?? headerRequestId ?? null;
|
|
70
|
+
const requestId = Array.isArray(rawRequestId) ? rawRequestId[0] || null : rawRequestId;
|
|
71
|
+
const data = source.data && typeof source.data === 'object' ? source.data : {};
|
|
72
|
+
const error = source.error || data.error || {};
|
|
37
73
|
return {
|
|
38
74
|
operation_id: operationId,
|
|
39
75
|
status_code: statusCode,
|
|
40
|
-
server_code: error
|
|
76
|
+
server_code: error.code || data.code || source.code || null,
|
|
41
77
|
request_id: requestId,
|
|
42
78
|
result,
|
|
43
79
|
};
|
|
44
80
|
}
|
|
45
81
|
|
|
82
|
+
function assertInvocationResult(operationId, result) {
|
|
83
|
+
const diagnostic = diagnosticResult(operationId, result);
|
|
84
|
+
if (result && result.workflow && result.workflow.required) {
|
|
85
|
+
const error = new Error(`Operation ${operationId} requires the ${result.workflow.workflow || result.workflow.mode} workflow; it has not executed.`);
|
|
86
|
+
error.code = 'WORKFLOW_REQUIRED';
|
|
87
|
+
error.details = { ...diagnostic, workflow: result.workflow };
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
if (Number(diagnostic.status_code) >= 400) {
|
|
91
|
+
const data = result.data || {};
|
|
92
|
+
const error = new Error(data.error && data.error.message || data.message || `DraftGo returned HTTP ${diagnostic.status_code}.`);
|
|
93
|
+
error.code = diagnostic.server_code || 'HTTP_ERROR';
|
|
94
|
+
error.status = diagnostic.status_code;
|
|
95
|
+
error.details = { ...data, request_id: diagnostic.request_id };
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
46
101
|
function diagnosticError(operationId, error) {
|
|
47
102
|
const source = error && (error.details || error.data) || {};
|
|
48
103
|
const nested = source.error && typeof source.error === 'object' ? source.error : {};
|
|
@@ -77,11 +132,12 @@ async function callOperation(projectDir, operationId, flags = {}) {
|
|
|
77
132
|
let config;
|
|
78
133
|
try {
|
|
79
134
|
config = loadProjectConfig(projectDir);
|
|
80
|
-
session = await openToolSession(config, [
|
|
135
|
+
session = await openToolSession(config, [
|
|
136
|
+
TOOL_NAMES.apiSearch, TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall,
|
|
137
|
+
]);
|
|
81
138
|
description = cachedDescription(projectDir, config.server, operationId);
|
|
82
139
|
if (!description) {
|
|
83
|
-
|
|
84
|
-
description = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
140
|
+
description = await describeOperation(projectDir, config, session, operationId);
|
|
85
141
|
}
|
|
86
142
|
if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
87
143
|
input = readCallInput(projectDir, flags);
|
|
@@ -90,14 +146,20 @@ async function callOperation(projectDir, operationId, flags = {}) {
|
|
|
90
146
|
}
|
|
91
147
|
const dangerous = isDangerousOperation(description);
|
|
92
148
|
if (dangerous && !flags.yes && !flags.y) {
|
|
149
|
+
if (flags.output === 'json' || !process.stdin.isTTY) {
|
|
150
|
+
console.log(JSON.stringify({
|
|
151
|
+
operation_id: operationId,
|
|
152
|
+
error: { code: 'CONFIRMATION_REQUIRED', message: 'Dangerous operations require --yes in JSON output mode.' },
|
|
153
|
+
}, null, 2));
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
93
156
|
const approved = await confirm(`Call destructive DraftGo operation ${operationId}?`, { default: false });
|
|
94
|
-
if (!approved) return
|
|
157
|
+
if (!approved) return 1;
|
|
95
158
|
}
|
|
96
159
|
let result;
|
|
97
160
|
try {
|
|
98
161
|
const invoke = (contract, confirmed) => callStructured(session, TOOL_NAMES.apiCall, {
|
|
99
162
|
...input, operation_id: operationId, registry_revision: contract.registry_revision,
|
|
100
|
-
...configuredScope(config, contract),
|
|
101
163
|
...(confirmed ? { confirm: true } : {}),
|
|
102
164
|
});
|
|
103
165
|
try {
|
|
@@ -108,15 +170,19 @@ async function callOperation(projectDir, operationId, flags = {}) {
|
|
|
108
170
|
throw incompatibleOperationError(operationId, error);
|
|
109
171
|
}
|
|
110
172
|
const previousDescription = description;
|
|
111
|
-
const
|
|
112
|
-
description =
|
|
173
|
+
const revision = await registryRevision(session, operationId);
|
|
174
|
+
description = await describeOperation(projectDir, config, session, operationId, revision);
|
|
113
175
|
if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
114
176
|
const changedDangerous = isDangerousOperation(description);
|
|
115
177
|
if ((dangerous || changedDangerous) && !sameOperationContract(previousDescription, description)) {
|
|
116
178
|
throw incompatibleOperationError(operationId, error);
|
|
117
179
|
}
|
|
180
|
+
if (!canRetryAfterContractChange(previousDescription, description)) {
|
|
181
|
+
throw incompatibleOperationError(operationId, error);
|
|
182
|
+
}
|
|
118
183
|
result = await invoke(description, changedDangerous);
|
|
119
184
|
}
|
|
185
|
+
assertInvocationResult(operationId, result);
|
|
120
186
|
} catch (error) {
|
|
121
187
|
return printCallError(operationId, error, flags);
|
|
122
188
|
}
|
|
@@ -155,12 +221,12 @@ async function apiCommand(projectDir, positional, flags = {}) {
|
|
|
155
221
|
}
|
|
156
222
|
|
|
157
223
|
const config = loadProjectConfig(projectDir);
|
|
158
|
-
const expected = describe ? TOOL_NAMES.apiDescribe : TOOL_NAMES.apiSearch;
|
|
159
|
-
const session = await openToolSession(config,
|
|
160
|
-
const result =
|
|
161
|
-
?
|
|
162
|
-
:
|
|
163
|
-
|
|
224
|
+
const expected = describe ? [TOOL_NAMES.apiSearch, TOOL_NAMES.apiDescribe] : [TOOL_NAMES.apiSearch];
|
|
225
|
+
const session = await openToolSession(config, expected);
|
|
226
|
+
const result = describe
|
|
227
|
+
? await describeOperation(projectDir, config, session, query)
|
|
228
|
+
: await callStructured(session, TOOL_NAMES.apiSearch,
|
|
229
|
+
require('./capabilities').searchArguments(query, flags, flags.cursor));
|
|
164
230
|
if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
|
|
165
231
|
else printResult(describe ? `DraftGo API ${query}` : `DraftGo API search: ${query}`, result);
|
|
166
232
|
return 0;
|
|
@@ -172,3 +238,7 @@ module.exports.diagnosticResult = diagnosticResult;
|
|
|
172
238
|
module.exports.diagnosticError = diagnosticError;
|
|
173
239
|
module.exports.printCallError = printCallError;
|
|
174
240
|
module.exports.callOperation = callOperation;
|
|
241
|
+
module.exports.registryRevision = registryRevision;
|
|
242
|
+
module.exports.describeOperation = describeOperation;
|
|
243
|
+
module.exports.descriptionForRevision = descriptionForRevision;
|
|
244
|
+
module.exports.assertInvocationResult = assertInvocationResult;
|
package/src/commands/apiKey.js
CHANGED
|
@@ -6,12 +6,9 @@ const { callOperation } = require('./api');
|
|
|
6
6
|
const OPERATIONS = Object.freeze({
|
|
7
7
|
status: 'getCurrentUserAPIKey',
|
|
8
8
|
create: 'createCurrentUserAPIKey',
|
|
9
|
-
update: '
|
|
9
|
+
update: 'setCurrentUserAPIKeyActive',
|
|
10
10
|
delete: 'deleteCurrentUserAPIKey',
|
|
11
11
|
rotate: 'rotateCurrentUserAPIKey',
|
|
12
|
-
reset: 'resetCurrentUserAPIKey',
|
|
13
|
-
enable: 'enableCurrentUserAPIKey',
|
|
14
|
-
disable: 'disableCurrentUserAPIKey',
|
|
15
12
|
});
|
|
16
13
|
|
|
17
14
|
function operationKey(value) {
|
|
@@ -22,10 +19,9 @@ function operationKey(value) {
|
|
|
22
19
|
async function apiKeyCommand(projectDir, positional, flags = {}) {
|
|
23
20
|
const action = operationKey(positional[0]);
|
|
24
21
|
if (!action) {
|
|
25
|
-
log.err('Usage: draftgo api-key status|create|update|delete|rotate
|
|
22
|
+
log.err('Usage: draftgo api-key status|create|update|delete|rotate [--input <json-file>]');
|
|
26
23
|
return 1;
|
|
27
24
|
}
|
|
28
|
-
if (action === 'reset') log.warn('api-key reset is deprecated; prefer rotate.');
|
|
29
25
|
return callOperation(projectDir, OPERATIONS[action], flags);
|
|
30
26
|
}
|
|
31
27
|
|
package/src/commands/autoPush.js
CHANGED
|
@@ -1,72 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const log = require('../logger');
|
|
5
|
-
const check = require('./check');
|
|
6
4
|
const { loadProjectConfig } = require('../projectConfig');
|
|
7
5
|
const { loadManifest, absolutePath } = require('../worktree/manifest');
|
|
8
6
|
const { hashFile } = require('../worktree/streams');
|
|
9
|
-
const {
|
|
7
|
+
const { targetsFrom, deliverTargets, outputResult } = require('./deploy');
|
|
10
8
|
|
|
11
9
|
async function changedEntries(projectDir, manifest) {
|
|
12
10
|
const changed = [];
|
|
13
11
|
for (const entry of Object.values(manifest.entries)) {
|
|
14
12
|
const local = absolutePath(projectDir, entry.local_path);
|
|
15
|
-
|
|
16
|
-
if ((await hashFile(local)).hash !== entry.base_hash) changed.push(entry);
|
|
13
|
+
// Missing files must be validated, not silently dropped from delivery.
|
|
14
|
+
if (!fs.existsSync(local) || (await hashFile(local)).hash !== entry.base_hash) changed.push(entry);
|
|
17
15
|
}
|
|
18
16
|
return changed;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
async function autoPush(projectDir, positional, flags = {}) {
|
|
22
|
-
let config;
|
|
23
20
|
try {
|
|
24
|
-
config
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (config.auto_push !== true) {
|
|
31
|
-
log.dim('auto-push skipped: .draftgo/config.json does not set auto_push to true.');
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
const conflicts = listConflicts(projectDir);
|
|
35
|
-
if (conflicts.length) {
|
|
36
|
-
log.err(`auto-push stopped: ${conflicts.length} unresolved DraftGo conflict(s).`);
|
|
37
|
-
return 1;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const checkCode = await check(projectDir, { strict: flags.strict });
|
|
41
|
-
if (checkCode !== 0) {
|
|
42
|
-
log.err('auto-push stopped because local validation failed.');
|
|
43
|
-
return checkCode;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const groups = new Map();
|
|
47
|
-
if (positional.length) {
|
|
48
|
-
const [resourceType, ...ids] = positional;
|
|
49
|
-
if (!resourceType || !ids.length) {
|
|
50
|
-
log.err('Usage: draftgo auto-push [<pages|nav|docs> <id...>]');
|
|
51
|
-
return 1;
|
|
52
|
-
}
|
|
53
|
-
groups.set(resourceType, ids);
|
|
54
|
-
} else {
|
|
55
|
-
for (const entry of await changedEntries(projectDir, loadManifest(projectDir))) {
|
|
56
|
-
if (!groups.has(entry.resource_type)) groups.set(entry.resource_type, []);
|
|
57
|
-
groups.get(entry.resource_type).push(entry.resource_id);
|
|
21
|
+
let config;
|
|
22
|
+
try { config = loadProjectConfig(projectDir); }
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (fs.existsSync(require('path').join(projectDir, '.draftgo', 'config.json'))) throw error;
|
|
25
|
+
return outputResult({ skipped: 'auto-push skipped: this project is not connected to DraftGo.' }, flags);
|
|
58
26
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
await commitResources(projectDir, resourceType, ids, { config });
|
|
67
|
-
}
|
|
68
|
-
log.ok('auto-push committed all changed checked-out content.');
|
|
69
|
-
return 0;
|
|
27
|
+
if (config.auto_push !== true) return outputResult({ skipped: 'auto-push skipped: auto_push is not enabled.' }, flags);
|
|
28
|
+
const targets = positional.length ? targetsFrom(positional)
|
|
29
|
+
: (await changedEntries(projectDir, loadManifest(projectDir))).map(({ resource_type, resource_id }) => ({ resource_type, resource_id }));
|
|
30
|
+
if (!targets.length) return outputResult({ skipped: 'auto-push: no checked-out content changed.' }, flags);
|
|
31
|
+
const value = await deliverTargets(projectDir, targets, { ...flags, delivery: 'deploy' }, { config });
|
|
32
|
+
return outputResult({ ...value, delivery: 'auto-push' }, flags);
|
|
33
|
+
} catch (error) { return outputResult({ error: { code: error.code || 'AUTO_PUSH_FAILED', message: error.message } }, flags); }
|
|
70
34
|
}
|
|
71
35
|
|
|
72
36
|
module.exports = autoPush;
|
|
@@ -23,10 +23,12 @@ function print(value, flags, title) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function searchArguments(query, flags, cursor) {
|
|
26
|
-
const
|
|
26
|
+
const limit = flags.limit == null ? 20 : Number(flags.limit);
|
|
27
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error('--limit must be an integer between 1 and 100.');
|
|
28
|
+
const input = { limit };
|
|
27
29
|
if (query) input.query = query;
|
|
28
30
|
if (flags.module) input.module = String(flags.module);
|
|
29
|
-
if (flags
|
|
31
|
+
if (flags.type) input.module = String(flags.type);
|
|
30
32
|
if (flags.method) input.method = String(flags.method).toUpperCase();
|
|
31
33
|
if (flags.risk) input.risk = String(flags.risk);
|
|
32
34
|
if (flags.permission) input.permission = String(flags.permission);
|
|
@@ -36,43 +38,48 @@ function searchArguments(query, flags, cursor) {
|
|
|
36
38
|
|
|
37
39
|
async function collect(session, query, flags, allPages) {
|
|
38
40
|
const result = [];
|
|
41
|
+
let registryRevision = null;
|
|
42
|
+
let total = null;
|
|
39
43
|
let cursor = flags.cursor || null;
|
|
40
44
|
const seen = new Set();
|
|
41
45
|
do {
|
|
42
46
|
const page = await callStructured(session, TOOL_NAMES.apiSearch, searchArguments(query, flags, cursor));
|
|
43
47
|
result.push(...itemsOf(page));
|
|
48
|
+
registryRevision ||= page && page.registry_revision || null;
|
|
49
|
+
if (Number.isFinite(Number(page && page.total))) total = Number(page.total);
|
|
44
50
|
cursor = nextCursorOf(page);
|
|
45
51
|
if (!allPages || !cursor) break;
|
|
46
52
|
if (seen.has(cursor)) throw new Error('DraftGo capability search repeated a cursor.');
|
|
47
53
|
seen.add(cursor);
|
|
48
54
|
} while (true);
|
|
49
|
-
return { items: result, next_cursor: cursor };
|
|
55
|
+
return { items: result, next_cursor: cursor, registry_revision: registryRevision, total };
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
async function capabilities(projectDir, positional = [], flags = {}) {
|
|
59
|
+
if (flags['resource-type'] != null) {
|
|
60
|
+
throw new Error('Capabilities --resource-type is not supported; use --type or --module for a Registry module.');
|
|
61
|
+
}
|
|
53
62
|
const action = String(positional[0] || 'list').toLowerCase();
|
|
54
63
|
const config = loadProjectConfig(projectDir);
|
|
55
64
|
if (action === 'show') {
|
|
56
65
|
const operationID = String(positional[1] || '').trim();
|
|
57
66
|
if (!operationID) throw new Error('Usage: draftgo capabilities show <operation_id>');
|
|
58
|
-
const
|
|
59
|
-
const
|
|
67
|
+
const { registryRevision, descriptionForRevision } = require('./api');
|
|
68
|
+
const session = await openToolSession(config, [TOOL_NAMES.apiSearch, TOOL_NAMES.apiDescribe]);
|
|
69
|
+
const revision = await registryRevision(session, operationID);
|
|
70
|
+
const result = await descriptionForRevision(projectDir, config, session, operationID, revision);
|
|
60
71
|
print(result, flags, `DraftGo capability ${operationID}`);
|
|
61
72
|
return 0;
|
|
62
73
|
}
|
|
63
74
|
if (action === 'audit') {
|
|
64
|
-
const session = await openToolSession(config, [TOOL_NAMES.
|
|
65
|
-
const overview = await callStructured(session, TOOL_NAMES.projectOverview, {});
|
|
75
|
+
const session = await openToolSession(config, [TOOL_NAMES.apiSearch]);
|
|
66
76
|
const catalog = await collect(session, '', flags, true);
|
|
67
|
-
const registry = overview && (overview.operation_registry || overview.registry) || {};
|
|
68
77
|
const result = {
|
|
69
78
|
server: config.server,
|
|
70
|
-
registry_revision:
|
|
71
|
-
|
|
72
|
-
excluded_operations: registry.excluded_operations ?? registry.excluded ?? null,
|
|
73
|
-
total_operations: registry.total_operations ?? registry.total ?? null,
|
|
79
|
+
registry_revision: catalog.registry_revision,
|
|
80
|
+
total_operations: catalog.total ?? catalog.items.length,
|
|
74
81
|
discovered_operations: catalog.items.length,
|
|
75
|
-
complete:
|
|
82
|
+
complete: catalog.next_cursor == null && (catalog.total == null || catalog.total === catalog.items.length),
|
|
76
83
|
};
|
|
77
84
|
print(result, flags, 'DraftGo capability audit');
|
|
78
85
|
return result.complete ? 0 : 1;
|
|
@@ -83,8 +90,8 @@ async function capabilities(projectDir, positional = [], flags = {}) {
|
|
|
83
90
|
throw new Error('Capability search needs a query or filter.');
|
|
84
91
|
}
|
|
85
92
|
const session = await openToolSession(config, [TOOL_NAMES.apiSearch]);
|
|
86
|
-
const result = await collect(session, query, flags,
|
|
87
|
-
print(
|
|
93
|
+
const result = await collect(session, query, flags, false);
|
|
94
|
+
print(result, flags, action === 'search' ? `DraftGo capabilities: ${query || 'filtered'}` : 'DraftGo capabilities');
|
|
88
95
|
return 0;
|
|
89
96
|
}
|
|
90
97
|
|
package/src/commands/check.js
CHANGED
|
@@ -3,24 +3,19 @@
|
|
|
3
3
|
const log = require('../logger');
|
|
4
4
|
const { analyzeProject } = require('../projectMap');
|
|
5
5
|
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
6
|
-
const
|
|
7
|
-
const { workspaceHealth } = require('../workspaceHealth');
|
|
6
|
+
const { projectHealth } = require('../projectHealth');
|
|
8
7
|
|
|
9
8
|
async function check(projectDir, positional = [], flags = {}) {
|
|
10
9
|
if (!Array.isArray(positional)) {
|
|
11
10
|
flags = positional || {};
|
|
12
11
|
positional = [];
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
log.err('Usage: draftgo check [custom-services <id...>] [--remote]');
|
|
17
|
-
return 1;
|
|
18
|
-
}
|
|
19
|
-
if (requestedType && !requestedIds.length) {
|
|
20
|
-
log.err('Usage: draftgo check custom-services <id...> [--remote]');
|
|
13
|
+
if (positional.length) {
|
|
14
|
+
if (!flags.quiet) log.err('Usage: draftgo check [--remote]');
|
|
21
15
|
return 1;
|
|
22
16
|
}
|
|
23
17
|
const resourceKeys = flags.resourceKeys || null;
|
|
18
|
+
const quiet = Boolean(flags.quiet);
|
|
24
19
|
const result = analyzeProject(projectDir, { resourceKeys });
|
|
25
20
|
if (resourceKeys) {
|
|
26
21
|
const manifestKeys = new Set(Object.keys(result.map.checkouts.reduce((entries, entry) => {
|
|
@@ -29,44 +24,17 @@ async function check(projectDir, positional = [], flags = {}) {
|
|
|
29
24
|
}, {})));
|
|
30
25
|
for (const key of resourceKeys) if (!manifestKeys.has(key)) result.errors.push(`${key}: resource is not checked out`);
|
|
31
26
|
}
|
|
32
|
-
const hygiene =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const customServiceChecks = [];
|
|
37
|
-
const requested = new Set(requestedIds.map(String));
|
|
38
|
-
const serviceEntries = Object.values(serviceManifest.entries)
|
|
39
|
-
.filter((entry) => !resourceKeys && (!requestedType || requested.has(String(entry.resource_id))));
|
|
40
|
-
if (requestedType) {
|
|
41
|
-
for (const id of requested) {
|
|
42
|
-
if (!serviceEntries.some((entry) => String(entry.resource_id) === id)) {
|
|
43
|
-
result.errors.push(`custom_services ${id}: resource is not checked out`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const entry of serviceEntries) {
|
|
48
|
-
try {
|
|
49
|
-
const state = customServices.diff(projectDir, entry.resource_id);
|
|
50
|
-
customServiceChecks.push({ resource_type: 'custom_services', resource_id: entry.resource_id, state: state.changed ? 'local_modified' : 'clean_local', files: state.files });
|
|
51
|
-
} catch (error) {
|
|
52
|
-
result.errors.push(`custom_services ${entry.resource_id}: ${error.message}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
27
|
+
const hygiene = projectHealth(projectDir);
|
|
28
|
+
const hygieneWarnings = resourceKeys ? [] : hygiene.warnings;
|
|
29
|
+
hygieneWarnings.forEach((warning) => result.warningDetails.push({ code: warning.code, confidence: 'high', message: `${warning.path}${warning.size ? ` (${warning.size} bytes)` : ''}` }));
|
|
30
|
+
hygieneWarnings.forEach((warning) => result.warnings.push(`${warning.code}: ${warning.path}`));
|
|
55
31
|
let remote = null;
|
|
56
32
|
let remoteError = null;
|
|
57
33
|
if (flags.remote) {
|
|
58
34
|
try {
|
|
59
|
-
|
|
60
|
-
inspectRemoteCheckouts(projectDir, resourceKeys ? {
|
|
35
|
+
remote = await inspectRemoteCheckouts(projectDir, resourceKeys ? {
|
|
61
36
|
entries: result.map.checkouts.filter((entry) => resourceKeys.includes(`${entry.resource_type}:${entry.resource_id}`)),
|
|
62
|
-
} : {})
|
|
63
|
-
resourceKeys
|
|
64
|
-
? Promise.resolve([])
|
|
65
|
-
: customServices.inspectRemote(projectDir, requestedType ? { ids: [...requested] } : {}),
|
|
66
|
-
]);
|
|
67
|
-
remote = requestedType
|
|
68
|
-
? serviceRemote.filter((entry) => requested.has(String(entry.resource_id)))
|
|
69
|
-
: [...contentRemote, ...serviceRemote];
|
|
37
|
+
} : {});
|
|
70
38
|
for (const entry of remote) {
|
|
71
39
|
if (!['clean', 'clean_local', 'local_modified'].includes(entry.state)) {
|
|
72
40
|
const detail = `${entry.resource_type} ${entry.resource_id}: ${entry.state}`
|
|
@@ -82,25 +50,27 @@ async function check(projectDir, positional = [], flags = {}) {
|
|
|
82
50
|
}
|
|
83
51
|
}
|
|
84
52
|
const output = {
|
|
85
|
-
content_validation: { map: result.map, errors: result.errors, warnings: result.warnings, warning_details: result.warningDetails
|
|
53
|
+
content_validation: { map: result.map, errors: result.errors, warnings: result.warnings, warning_details: result.warningDetails },
|
|
86
54
|
remote_validation: { resources: remote, error: remoteError },
|
|
87
|
-
|
|
55
|
+
project_hygiene: hygiene,
|
|
88
56
|
};
|
|
57
|
+
const code = result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
58
|
+
if (flags.returnResult) return { code, ...output };
|
|
59
|
+
if (quiet) return code;
|
|
89
60
|
if (flags.output === 'json') {
|
|
90
|
-
console.log(JSON.stringify(output, null, 2));
|
|
61
|
+
if (!quiet) console.log(JSON.stringify(output, null, 2));
|
|
91
62
|
return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
92
63
|
}
|
|
93
64
|
|
|
94
|
-
log.title('draftgo check');
|
|
65
|
+
if (!quiet) log.title('draftgo check');
|
|
95
66
|
const selectedCount = (type, entries) => resourceKeys
|
|
96
67
|
? entries.filter((entry) => resourceKeys.includes(`${type}:${entry.resource_id}`)).length
|
|
97
68
|
: entries.length;
|
|
98
69
|
log.info(`Checked-out pages: ${selectedCount('pages', result.map.pages)}`);
|
|
99
70
|
log.info(`Checked-out navigations: ${selectedCount('navigations', result.map.navigations)}`);
|
|
100
71
|
log.info(`Checked-out docs: ${selectedCount('docs', result.map.docs)}`);
|
|
101
|
-
log.info(`
|
|
102
|
-
log.info(`
|
|
103
|
-
log.info(`Workspace size: ${hygiene.total_bytes} bytes; reclaimable: ${hygiene.reclaimable_bytes} bytes`);
|
|
72
|
+
log.info(`Project: ${hygiene.managed_files} managed, ${hygiene.unknown_files} unknown, ${hygiene.temporary_files} temporary, ${hygiene.artifact_files} artifact file(s)`);
|
|
73
|
+
log.info(`Project size: ${hygiene.total_bytes} bytes; reclaimable: ${hygiene.reclaimable_bytes} bytes`);
|
|
104
74
|
if (remote) log.info(`Remote checkout comparison: ${remote.length} resource(s)`);
|
|
105
75
|
for (const message of result.errors) log.err(message);
|
|
106
76
|
for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
|
|
@@ -109,8 +79,4 @@ async function check(projectDir, positional = [], flags = {}) {
|
|
|
109
79
|
return 0;
|
|
110
80
|
}
|
|
111
81
|
|
|
112
|
-
function customServicesCommand() {
|
|
113
|
-
return require('./customService');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
82
|
module.exports = check;
|
package/src/commands/checkout.js
CHANGED
|
@@ -6,12 +6,9 @@ const { checkoutResources } = require('../worktree');
|
|
|
6
6
|
async function checkout(projectDir, positional, flags = {}) {
|
|
7
7
|
const [resourceType, ...ids] = positional;
|
|
8
8
|
if (!resourceType || !ids.length) {
|
|
9
|
-
log.err('Usage: draftgo checkout <pages|nav|docs
|
|
9
|
+
log.err('Usage: draftgo checkout <pages|nav|docs> <id...>');
|
|
10
10
|
return 1;
|
|
11
11
|
}
|
|
12
|
-
if (require('./customService').isServiceType(resourceType)) {
|
|
13
|
-
return require('./customService').checkout(projectDir, ids, flags);
|
|
14
|
-
}
|
|
15
12
|
const results = await checkoutResources(projectDir, resourceType, ids, { force: Boolean(flags.force) });
|
|
16
13
|
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
17
14
|
else for (const entry of results) log.ok(`Checked out ${entry.resource_type} ${entry.resource_id} -> ${entry.local_path}`);
|
package/src/commands/clean.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const log = require('../logger');
|
|
6
6
|
const runtime = require('../runtimeFiles');
|
|
7
7
|
|
|
8
|
-
const MANAGED_PREFIXES = ['tmp/', 'artifacts/ui/', 'artifacts/api/'
|
|
8
|
+
const MANAGED_PREFIXES = ['tmp/', 'artifacts/ui/', 'artifacts/api/'];
|
|
9
9
|
function fileSize(file) { try { const stat = fs.statSync(file); return stat.isFile() ? stat.size : 0; } catch { return 0; } }
|
|
10
10
|
function olderThan(value) {
|
|
11
11
|
if (value == null || value === '') return 0;
|
package/src/commands/commit.js
CHANGED
|
@@ -6,12 +6,9 @@ const { commitResources } = require('../worktree');
|
|
|
6
6
|
async function commit(projectDir, positional, flags = {}) {
|
|
7
7
|
const [resourceType, ...ids] = positional;
|
|
8
8
|
if (!resourceType || !ids.length) {
|
|
9
|
-
log.err('Usage: draftgo commit <pages|nav|docs
|
|
9
|
+
log.err('Usage: draftgo commit <pages|nav|docs> <id...>');
|
|
10
10
|
return 1;
|
|
11
11
|
}
|
|
12
|
-
if (require('./customService').isServiceType(resourceType)) {
|
|
13
|
-
return require('./customService').commit(projectDir, ids, flags);
|
|
14
|
-
}
|
|
15
12
|
const streamed = [];
|
|
16
13
|
const printStatus = (result) => {
|
|
17
14
|
streamed.push(result);
|