draftgo-cli 3.0.56 → 4.0.22

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.
Files changed (96) hide show
  1. package/README.md +169 -297
  2. package/package.json +12 -7
  3. package/resources/custom-service-sdk/ai.go +520 -0
  4. package/resources/custom-service-sdk/ai_test.go +156 -0
  5. package/resources/custom-service-sdk/billing.go +596 -0
  6. package/resources/custom-service-sdk/billing_test.go +150 -0
  7. package/resources/custom-service-sdk/go.mod +3 -0
  8. package/resources/custom-service-sdk/manifest.json +72 -0
  9. package/resources/custom-service-sdk/platform.go +360 -0
  10. package/resources/custom-service-sdk/platform_logger_test.go +24 -0
  11. package/resources/custom-service-sdk/registration_test.go +39 -0
  12. package/resources/custom-service-sdk/resources.go +246 -0
  13. package/resources/custom-service-sdk/resources_billing_test.go +115 -0
  14. package/resources/custom-service-sdk/resources_files_test.go +57 -0
  15. package/resources/custom-service-sdk/resources_scope_test.go +87 -0
  16. package/resources/custom-service-sdk/sdk.go +208 -0
  17. package/resources/skill/SKILL.md +36 -87
  18. package/resources/skill/init/SKILL.md +9 -14
  19. package/resources/skill/manifest.json +6 -2
  20. package/resources/skill/references/aihub.md +28 -5
  21. package/resources/skill/references/app-api.md +56 -6
  22. package/resources/skill/references/architecture.md +2 -2
  23. package/resources/skill/references/chat-sdk.md +4 -2
  24. package/resources/skill/references/checkout.md +21 -7
  25. package/resources/skill/references/custom-services.md +124 -222
  26. package/resources/skill/references/data.md +22 -6
  27. package/resources/skill/references/delivery.md +33 -0
  28. package/resources/skill/references/diagnostics.md +51 -0
  29. package/resources/skill/references/frontend.md +93 -499
  30. package/resources/skill/references/mcp.md +65 -101
  31. package/resources/skill/references/methods.md +189 -0
  32. package/resources/skill/references/modules.md +36 -8
  33. package/resources/skill/references/runtime.md +26 -3
  34. package/resources/skill/story/SKILL.md +1 -2
  35. package/src/apiContractCache.js +112 -0
  36. package/src/cli.js +24 -20
  37. package/src/commandRegistry.js +15 -12
  38. package/src/commands/api.js +41 -10
  39. package/src/commands/apiKey.js +34 -0
  40. package/src/commands/capabilities.js +93 -0
  41. package/src/commands/check.js +1 -10
  42. package/src/commands/checkout.js +1 -1
  43. package/src/commands/commit.js +1 -1
  44. package/src/commands/components.js +550 -0
  45. package/src/commands/conflict.js +1 -1
  46. package/src/commands/connect.js +18 -8
  47. package/src/commands/customService.js +22 -8
  48. package/src/commands/dataRange.js +33 -0
  49. package/src/commands/delete.js +34 -46
  50. package/src/commands/deploy.js +1 -1
  51. package/src/commands/diff.js +18 -2
  52. package/src/commands/grant.js +29 -0
  53. package/src/commands/group.js +38 -0
  54. package/src/commands/help.js +80 -51
  55. package/src/commands/init.js +6 -12
  56. package/src/commands/listTargets.js +1 -1
  57. package/src/commands/local.js +2 -6
  58. package/src/commands/map.js +145 -28
  59. package/src/commands/mcp.js +2 -2
  60. package/src/commands/reconcile.js +1 -1
  61. package/src/commands/role.js +32 -0
  62. package/src/commands/space.js +41 -0
  63. package/src/commands/status.js +111 -8
  64. package/src/commands/uninstall.js +3 -3
  65. package/src/commands/update.js +24 -12
  66. package/src/commands/verify.js +118 -21
  67. package/src/commands/{verifyUi.js → visualVerify.js} +28 -116
  68. package/src/commands/worklog.js +90 -0
  69. package/src/consoleEncoding.js +34 -0
  70. package/src/contractCompatibility.js +57 -0
  71. package/src/customServices.js +278 -41
  72. package/src/diffReport.js +106 -0
  73. package/src/index.js +2 -0
  74. package/src/{localdev → localRuntime}/compose.js +14 -17
  75. package/src/{localdev → localRuntime}/detect.js +1 -1
  76. package/src/{localdev → localRuntime}/index.js +22 -23
  77. package/src/{localdev → localRuntime}/mysqlClient.js +1 -1
  78. package/src/{localdev → localRuntime}/services.js +28 -37
  79. package/src/mcp/client.js +11 -2
  80. package/src/mcp/protocol.js +2 -2
  81. package/src/mcp/tools.js +14 -1
  82. package/src/platforms.js +9 -0
  83. package/src/projectConfig.js +8 -4
  84. package/src/releaseInstall.js +105 -0
  85. package/src/{installers/index.js → targets.js} +3 -5
  86. package/src/updateCheck.js +48 -28
  87. package/src/worklog.js +275 -0
  88. package/src/workspaceHealth.js +1 -1
  89. package/src/worktree/backend.js +1 -1
  90. package/src/worktree/index.js +86 -51
  91. package/src/changelog.js +0 -276
  92. package/src/commands/changelog.js +0 -24
  93. package/src/commands/localDev.js +0 -9
  94. package/src/commands/sync.js +0 -46
  95. package/src/commands/task.js +0 -408
  96. package/src/commands/verifyUiCompat.js +0 -16
@@ -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
@@ -8,13 +8,12 @@ const VALUE_FLAGS = new Set([
8
8
  'target',
9
9
  'server',
10
10
  'mcp-url',
11
+ 'api-key',
11
12
  'token',
12
13
  'output',
13
14
  'url',
14
15
  'browser',
15
16
  'browser-path',
16
- 'resource',
17
- 'mobile-check',
18
17
  'ui',
19
18
  'frame',
20
19
  'viewport',
@@ -31,30 +30,35 @@ const VALUE_FLAGS = new Set([
31
30
  'params',
32
31
  'input',
33
32
  'type',
33
+ 'route',
34
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',
35
+ 'limit',
36
+ 'cursor',
52
37
  'date',
53
38
  'handler',
39
+ 'source',
54
40
  'headers',
55
41
  'user',
56
42
  'side-effect-policy',
57
- 'evidence',
43
+ 'page',
44
+ 'instance',
45
+ 'note',
46
+ 'file',
47
+ 'library',
48
+ 'name',
49
+ 'slug',
50
+ 'category',
51
+ 'description',
52
+ 'tags',
53
+ 'source-type',
54
+ 'status',
55
+ 'scope-type',
56
+ 'space-id',
57
+ 'module',
58
+ 'resource-type',
59
+ 'method',
60
+ 'risk',
61
+ 'permission',
58
62
  ]);
59
63
 
60
64
  function parse(argv) {
@@ -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', aliases: ['remove'], run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
9
- { name: 'status', run: (dir) => require('./commands/status')(dir) },
10
- { name: 'changelog', run: (dir, args, flags) => require('./commands/changelog')(dir, args, flags) },
11
- { name: 'task', run: (dir, args, flags) => require('./commands/task')(dir, args, flags) },
8
+ { name: 'uninstall', run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
9
+ { name: 'status', run: (dir, args, flags) => require('./commands/status')(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,20 @@ 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', aliases: ['del', 'rm'], run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
27
+ { name: 'space', run: (dir, args, flags) => require('./commands/space')(dir, args, flags) },
28
+ { 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
+ { name: 'api-key', run: (dir, args, flags) => require('./commands/apiKey')(dir, args, flags) },
31
+ { 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
+ { name: 'delete', run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
30
34
  { name: 'deploy', run: (dir, args, flags) => require('./commands/deploy')(dir, args, flags) },
31
- { name: 'auto-push', aliases: ['autopush'], run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
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) },
35
+ { name: 'auto-push', run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
34
36
  { name: 'local', run: (dir, args, flags) => require('./commands/local')(dir, args, flags) },
35
- { name: 'list-targets', aliases: ['targets'], run: () => require('./commands/listTargets')() },
36
- { name: 'local-dev', aliases: ['localdev'], run: (dir, args, flags) => require('./commands/localDev')(dir, args, flags) },
37
- { name: 'connect', aliases: ['login'], run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
37
+ { name: 'list-targets', run: () => require('./commands/listTargets')() },
38
+ { name: 'connect', run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
39
+ { name: 'components', run: (dir, args, flags) => require('./commands/components')(dir, args, flags) },
40
+ { name: 'capabilities', run: (dir, args, flags) => require('./commands/capabilities')(dir, args, flags) },
38
41
  ];
39
42
 
40
43
  const aliases = new Map();
@@ -5,7 +5,15 @@ 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 } = require('../mcp/tools');
8
+ const { TOOL_NAMES, openToolSession, callStructured, configuredScope } = require('../mcp/tools');
9
+ const { cachedDescription, normalizeDescription, storeDescription } = require('../apiContractCache');
10
+ const {
11
+ isContractCompatibilityError,
12
+ isHardContractIncompatibility,
13
+ isDangerousOperation,
14
+ sameOperationContract,
15
+ incompatibleOperationError,
16
+ } = require('../contractCompatibility');
9
17
 
10
18
  function readCallInput(projectDir, flags) {
11
19
  const raw = flags.input
@@ -66,27 +74,49 @@ async function callOperation(projectDir, operationId, flags = {}) {
66
74
  let session;
67
75
  let description;
68
76
  let input;
77
+ let config;
69
78
  try {
70
- const config = loadProjectConfig(projectDir);
79
+ config = loadProjectConfig(projectDir);
71
80
  session = await openToolSession(config, [TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall]);
72
- description = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
81
+ description = cachedDescription(projectDir, config.server, operationId);
82
+ if (!description) {
83
+ const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
84
+ description = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
85
+ }
86
+ if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
73
87
  input = readCallInput(projectDir, flags);
74
88
  } catch (error) {
75
89
  return printCallError(operationId, error, flags);
76
90
  }
77
- const destructive = description && (description.destructive === true
78
- || description.operation && description.operation.destructive === true);
79
- if (destructive && !flags.yes && !flags.y) {
91
+ const dangerous = isDangerousOperation(description);
92
+ if (dangerous && !flags.yes && !flags.y) {
80
93
  const approved = await confirm(`Call destructive DraftGo operation ${operationId}?`, { default: false });
81
94
  if (!approved) return 0;
82
95
  }
83
96
  let result;
84
97
  try {
85
- result = await callStructured(session, TOOL_NAMES.apiCall, {
86
- ...input,
87
- operation_id: operationId,
88
- ...(destructive ? { confirm: true } : {}),
98
+ const invoke = (contract, confirmed) => callStructured(session, TOOL_NAMES.apiCall, {
99
+ ...input, operation_id: operationId, registry_revision: contract.registry_revision,
100
+ ...configuredScope(config, contract),
101
+ ...(confirmed ? { confirm: true } : {}),
89
102
  });
103
+ try {
104
+ result = await invoke(description, dangerous);
105
+ } catch (error) {
106
+ if (!isContractCompatibilityError(error)) throw error;
107
+ if (isHardContractIncompatibility(error)) {
108
+ throw incompatibleOperationError(operationId, error);
109
+ }
110
+ const previousDescription = description;
111
+ const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationId });
112
+ description = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
113
+ if (!description) throw new Error('DraftGo API description is missing contract revision metadata.');
114
+ const changedDangerous = isDangerousOperation(description);
115
+ if ((dangerous || changedDangerous) && !sameOperationContract(previousDescription, description)) {
116
+ throw incompatibleOperationError(operationId, error);
117
+ }
118
+ result = await invoke(description, changedDangerous);
119
+ }
90
120
  } catch (error) {
91
121
  return printCallError(operationId, error, flags);
92
122
  }
@@ -130,6 +160,7 @@ async function apiCommand(projectDir, positional, flags = {}) {
130
160
  const result = await callStructured(session, expected, describe
131
161
  ? { operation_id: query }
132
162
  : { query });
163
+ if (describe) storeDescription(projectDir, config.server, result);
133
164
  if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
134
165
  else printResult(describe ? `DraftGo API ${query}` : `DraftGo API search: ${query}`, result);
135
166
  return 0;
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { callOperation } = require('./api');
5
+
6
+ const OPERATIONS = Object.freeze({
7
+ status: 'getCurrentUserAPIKey',
8
+ create: 'createCurrentUserAPIKey',
9
+ update: 'updateCurrentUserAPIKey',
10
+ delete: 'deleteCurrentUserAPIKey',
11
+ rotate: 'rotateCurrentUserAPIKey',
12
+ reset: 'resetCurrentUserAPIKey',
13
+ enable: 'enableCurrentUserAPIKey',
14
+ disable: 'disableCurrentUserAPIKey',
15
+ });
16
+
17
+ function operationKey(value) {
18
+ const action = String(value || 'status').trim().toLowerCase();
19
+ return OPERATIONS[action] ? action : '';
20
+ }
21
+
22
+ async function apiKeyCommand(projectDir, positional, flags = {}) {
23
+ const action = operationKey(positional[0]);
24
+ if (!action) {
25
+ log.err('Usage: draftgo api-key status|create|update|delete|rotate|reset|enable|disable [--input <json-file>]');
26
+ return 1;
27
+ }
28
+ if (action === 'reset') log.warn('api-key reset is deprecated; prefer rotate.');
29
+ return callOperation(projectDir, OPERATIONS[action], flags);
30
+ }
31
+
32
+ module.exports = apiKeyCommand;
33
+ module.exports.OPERATIONS = OPERATIONS;
34
+ module.exports.operationKey = operationKey;
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { loadProjectConfig } = require('../projectConfig');
5
+ const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
6
+
7
+ function itemsOf(value) {
8
+ if (Array.isArray(value)) return value;
9
+ return value && Array.isArray(value.items) ? value.items : [];
10
+ }
11
+
12
+ function nextCursorOf(value) {
13
+ return value && (value.next_cursor ?? value.nextCursor) || null;
14
+ }
15
+
16
+ function print(value, flags, title) {
17
+ if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
18
+ else {
19
+ log.title(title);
20
+ if (Array.isArray(value)) value.forEach(item => log.plain(` ${item.operation_id || item.name || JSON.stringify(item)}`));
21
+ else console.log(JSON.stringify(value, null, 2));
22
+ }
23
+ }
24
+
25
+ function searchArguments(query, flags, cursor) {
26
+ const input = { limit: Math.min(100, Math.max(1, Number(flags.limit) || 100)) };
27
+ if (query) input.query = query;
28
+ if (flags.module) input.module = String(flags.module);
29
+ if (flags['resource-type'] || flags.type) input.resource_type = String(flags['resource-type'] || flags.type);
30
+ if (flags.method) input.method = String(flags.method).toUpperCase();
31
+ if (flags.risk) input.risk = String(flags.risk);
32
+ if (flags.permission) input.permission = String(flags.permission);
33
+ if (cursor) input.cursor = cursor;
34
+ return input;
35
+ }
36
+
37
+ async function collect(session, query, flags, allPages) {
38
+ const result = [];
39
+ let cursor = flags.cursor || null;
40
+ const seen = new Set();
41
+ do {
42
+ const page = await callStructured(session, TOOL_NAMES.apiSearch, searchArguments(query, flags, cursor));
43
+ result.push(...itemsOf(page));
44
+ cursor = nextCursorOf(page);
45
+ if (!allPages || !cursor) break;
46
+ if (seen.has(cursor)) throw new Error('DraftGo capability search repeated a cursor.');
47
+ seen.add(cursor);
48
+ } while (true);
49
+ return { items: result, next_cursor: cursor };
50
+ }
51
+
52
+ async function capabilities(projectDir, positional = [], flags = {}) {
53
+ const action = String(positional[0] || 'list').toLowerCase();
54
+ const config = loadProjectConfig(projectDir);
55
+ if (action === 'show') {
56
+ const operationID = String(positional[1] || '').trim();
57
+ if (!operationID) throw new Error('Usage: draftgo capabilities show <operation_id>');
58
+ const session = await openToolSession(config, [TOOL_NAMES.apiDescribe]);
59
+ const result = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: operationID });
60
+ print(result, flags, `DraftGo capability ${operationID}`);
61
+ return 0;
62
+ }
63
+ if (action === 'audit') {
64
+ const session = await openToolSession(config, [TOOL_NAMES.projectOverview, TOOL_NAMES.apiSearch]);
65
+ const overview = await callStructured(session, TOOL_NAMES.projectOverview, {});
66
+ const catalog = await collect(session, '', flags, true);
67
+ const registry = overview && (overview.operation_registry || overview.registry) || {};
68
+ const result = {
69
+ server: config.server,
70
+ registry_revision: registry.registry_revision || overview.registry_revision || null,
71
+ mapped_operations: registry.mapped_operations ?? registry.mapped ?? catalog.items.length,
72
+ excluded_operations: registry.excluded_operations ?? registry.excluded ?? null,
73
+ total_operations: registry.total_operations ?? registry.total ?? null,
74
+ discovered_operations: catalog.items.length,
75
+ complete: (registry.mapped_operations ?? registry.mapped ?? catalog.items.length) === catalog.items.length,
76
+ };
77
+ print(result, flags, 'DraftGo capability audit');
78
+ return result.complete ? 0 : 1;
79
+ }
80
+ if (action !== 'list' && action !== 'search') throw new Error('Usage: draftgo capabilities list|search|show|audit');
81
+ const query = action === 'search' ? positional.slice(1).join(' ').trim() : '';
82
+ if (action === 'search' && !query && !flags.module && !flags.type && !flags.method && !flags.risk && !flags.permission) {
83
+ throw new Error('Capability search needs a query or filter.');
84
+ }
85
+ const session = await openToolSession(config, [TOOL_NAMES.apiSearch]);
86
+ const result = await collect(session, query, flags, action === 'list' && !flags.cursor);
87
+ print(flags.output === 'json' ? result : result.items, flags, action === 'search' ? `DraftGo capabilities: ${query || 'filtered'}` : 'DraftGo capabilities');
88
+ return 0;
89
+ }
90
+
91
+ module.exports = capabilities;
92
+ module.exports.searchArguments = searchArguments;
93
+ module.exports.itemsOf = itemsOf;
@@ -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({ ...output, legacy_cache_ignored: legacyCacheDetected(projectDir) }, null, 2));
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;
@@ -6,7 +6,7 @@ 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> <id...>');
9
+ log.err('Usage: draftgo checkout <pages|nav|docs|custom-services> <id...>');
10
10
  return 1;
11
11
  }
12
12
  if (require('./customService').isServiceType(resourceType)) {
@@ -6,7 +6,7 @@ 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> <id...>');
9
+ log.err('Usage: draftgo commit <pages|nav|docs|custom-services> <id...>');
10
10
  return 1;
11
11
  }
12
12
  if (require('./customService').isServiceType(resourceType)) {