ultra-builder-pro-cli 0.7.0 → 0.9.0
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/CHANGELOG.md +60 -1
- package/README.md +49 -23
- package/adapters/_shared/codex-assets.cjs +28 -0
- package/adapters/_shared/handbook.cjs +9 -1
- package/adapters/_shared/path-rewrite.cjs +3 -0
- package/adapters/_shared/provenance.cjs +286 -0
- package/adapters/_shared/runtime-assets.cjs +2 -1
- package/adapters/_shared/tests/handbook.test.cjs +6 -1
- package/adapters/_shared/tests/path-rewrite.test.cjs +3 -1
- package/adapters/_shared/tests/provenance.test.cjs +133 -0
- package/adapters/_shared/tests/runtime-assets.test.cjs +4 -1
- package/adapters/claude.js +33 -0
- package/adapters/codex.js +87 -0
- package/adapters/kimi.js +642 -0
- package/adapters/opencode.js +66 -0
- package/adapters/tests/codex.test.cjs +37 -0
- package/adapters/tests/kimi-hook.test.cjs +122 -0
- package/adapters/tests/kimi.test.cjs +255 -0
- package/agents/debugger.md +12 -0
- package/bin/handbook.js +2 -2
- package/bin/install.js +72 -12
- package/bin/orchestrator.js +1 -1
- package/commands/ultra-change.md +3 -2
- package/docs/AGENT-CONTEXT.md +5 -3
- package/docs/ARCHITECTURE.md +40 -6
- package/docs/PLAN.zh-CN.md +8 -6
- package/docs/ROADMAP.md +4 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +109 -58
- package/docs/USER-HANDBOOK-CONTRACT.md +4 -1
- package/hooks/README.md +6 -2
- package/hooks/adapters/kimi.py +162 -0
- package/hooks/workflow_checkpoint.py +15 -1
- package/hooks/workflow_resume.py +96 -7
- package/mcp-server/lib/change-workflow.cjs +69 -0
- package/mcp-server/lib/change-workflow.test.cjs +87 -0
- package/mcp-server/lib/state-db.cjs +85 -2
- package/mcp-server/lib/state-db.test.cjs +56 -0
- package/mcp-server/tests/init-project.test.cjs +2 -2
- package/mcp-server/tests/state-db.test.cjs +3 -3
- package/package.json +4 -2
- package/skills/ultra-change/SKILL.md +12 -3
- package/skills/ultra-deliver/SKILL.md +3 -1
- package/skills/ultra-doctor/SKILL.md +10 -3
- package/skills/ultra-review/SKILL.md +3 -3
- package/spec/mcp-tools.yaml +5 -5
- package/spec/schemas/skill-manifest.schema.json +2 -2
- package/spec/schemas/state-db.sql +4 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { execFileSync } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
const provenance = require('../provenance.cjs');
|
|
11
|
+
|
|
12
|
+
function fixture() {
|
|
13
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-provenance-'));
|
|
14
|
+
fs.mkdirSync(path.join(root, 'runtime'), { recursive: true });
|
|
15
|
+
fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
|
|
16
|
+
fs.writeFileSync(path.join(root, 'runtime', 'launch.cjs'), 'module.exports = true;\n');
|
|
17
|
+
fs.writeFileSync(path.join(root, 'hooks', 'hooks.json'), '{}\n');
|
|
18
|
+
const file = path.join(root, 'provenance.json');
|
|
19
|
+
provenance.writeProvenance({
|
|
20
|
+
file,
|
|
21
|
+
adapter: 'claude',
|
|
22
|
+
packageInfo: { name: 'ultra-builder-pro-cli', version: '1.2.3' },
|
|
23
|
+
repository: 'https://example.test/ultra-builder-pro',
|
|
24
|
+
sourceCommit: '0123456789abcdef0123456789abcdef01234567',
|
|
25
|
+
roots: { plugin: root },
|
|
26
|
+
assets: [
|
|
27
|
+
{ root: 'plugin', path: 'hooks/hooks.json' },
|
|
28
|
+
{ root: 'plugin', path: 'runtime/launch.cjs' },
|
|
29
|
+
],
|
|
30
|
+
contracts: {
|
|
31
|
+
hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
|
|
32
|
+
mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
return { root, file };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test('writeProvenance + inspectProvenance produce a normalized healthy report', () => {
|
|
39
|
+
const fx = fixture();
|
|
40
|
+
try {
|
|
41
|
+
const manifest = JSON.parse(fs.readFileSync(fx.file, 'utf8'));
|
|
42
|
+
assert.equal(manifest.schema_version, 1);
|
|
43
|
+
assert.equal(manifest.adapter, 'claude');
|
|
44
|
+
assert.equal(manifest.package.version, '1.2.3');
|
|
45
|
+
assert.equal(manifest.source.repository, 'https://example.test/ultra-builder-pro');
|
|
46
|
+
assert.match(manifest.content.digest, /^[0-9a-f]{64}$/);
|
|
47
|
+
assert.deepEqual(manifest.assets.map((asset) => asset.path), [
|
|
48
|
+
'hooks/hooks.json', 'runtime/launch.cjs',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const report = provenance.inspectProvenance({
|
|
52
|
+
file: fx.file,
|
|
53
|
+
expectedAdapter: 'claude',
|
|
54
|
+
expectedPackageVersion: '1.2.3',
|
|
55
|
+
});
|
|
56
|
+
assert.equal(report.status, 'healthy');
|
|
57
|
+
assert.deepEqual(report.issues, []);
|
|
58
|
+
assert.equal(report.checks.assets.checked, 2);
|
|
59
|
+
assert.equal(report.checks.contracts.checked, 2);
|
|
60
|
+
} finally {
|
|
61
|
+
fs.rmSync(fx.root, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('inspectProvenance detects content drift and missing contract targets', () => {
|
|
66
|
+
const fx = fixture();
|
|
67
|
+
try {
|
|
68
|
+
fs.writeFileSync(path.join(fx.root, 'runtime', 'launch.cjs'), 'tampered\n');
|
|
69
|
+
fs.unlinkSync(path.join(fx.root, 'hooks', 'hooks.json'));
|
|
70
|
+
const report = provenance.inspectProvenance({
|
|
71
|
+
file: fx.file,
|
|
72
|
+
expectedAdapter: 'claude',
|
|
73
|
+
expectedPackageVersion: '1.2.3',
|
|
74
|
+
});
|
|
75
|
+
assert.equal(report.status, 'degraded');
|
|
76
|
+
assert.ok(report.issues.some((issue) => issue.code === 'ASSET_HASH_MISMATCH'));
|
|
77
|
+
assert.ok(report.issues.some(
|
|
78
|
+
(issue) => issue.code === 'CONTRACT_TARGET_MISSING' && issue.contract === 'hooks_manifest',
|
|
79
|
+
));
|
|
80
|
+
} finally {
|
|
81
|
+
fs.rmSync(fx.root, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('inspectProvenance fails closed for missing, corrupt, or mismatched manifests', () => {
|
|
86
|
+
const fx = fixture();
|
|
87
|
+
try {
|
|
88
|
+
const mismatch = provenance.inspectProvenance({
|
|
89
|
+
file: fx.file,
|
|
90
|
+
expectedAdapter: 'codex',
|
|
91
|
+
expectedPackageVersion: '9.9.9',
|
|
92
|
+
});
|
|
93
|
+
assert.equal(mismatch.status, 'degraded');
|
|
94
|
+
assert.ok(mismatch.issues.some((issue) => issue.code === 'ADAPTER_MISMATCH'));
|
|
95
|
+
assert.ok(mismatch.issues.some((issue) => issue.code === 'PACKAGE_VERSION_MISMATCH'));
|
|
96
|
+
|
|
97
|
+
fs.writeFileSync(fx.file, 'not-json');
|
|
98
|
+
const corrupt = provenance.inspectProvenance({ file: fx.file });
|
|
99
|
+
assert.equal(corrupt.status, 'degraded');
|
|
100
|
+
assert.equal(corrupt.issues[0].code, 'PROVENANCE_INVALID');
|
|
101
|
+
|
|
102
|
+
fs.unlinkSync(fx.file);
|
|
103
|
+
const missing = provenance.inspectProvenance({ file: fx.file });
|
|
104
|
+
assert.equal(missing.status, 'missing');
|
|
105
|
+
assert.equal(missing.issues[0].code, 'PROVENANCE_MISSING');
|
|
106
|
+
} finally {
|
|
107
|
+
fs.rmSync(fx.root, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('packageSource never attributes an enclosing consumer repository commit to the package', () => {
|
|
112
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-provenance-source-'));
|
|
113
|
+
try {
|
|
114
|
+
execFileSync('git', ['init', '-q'], { cwd: root });
|
|
115
|
+
execFileSync('git', ['config', 'user.email', 'test@ubp.dev'], { cwd: root });
|
|
116
|
+
execFileSync('git', ['config', 'user.name', 'ubp-test'], { cwd: root });
|
|
117
|
+
fs.writeFileSync(path.join(root, 'README.md'), '# consumer\n');
|
|
118
|
+
execFileSync('git', ['add', 'README.md'], { cwd: root });
|
|
119
|
+
execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: root });
|
|
120
|
+
const packageRoot = path.join(root, 'node_modules', 'ultra-builder-pro-cli');
|
|
121
|
+
fs.mkdirSync(packageRoot, { recursive: true });
|
|
122
|
+
fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({
|
|
123
|
+
name: 'ultra-builder-pro-cli',
|
|
124
|
+
version: '1.2.3',
|
|
125
|
+
repository: { url: 'https://example.test/ultra-builder-pro' },
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const source = provenance.packageSource(packageRoot);
|
|
129
|
+
assert.equal(source.sourceCommit, null);
|
|
130
|
+
} finally {
|
|
131
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -62,7 +62,10 @@ test('runtime asset manifest exposes only Ultra-owned core and internal skills',
|
|
|
62
62
|
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.opencode, [
|
|
63
63
|
'cc-collab', 'codex-collab', 'ultra-verify',
|
|
64
64
|
]);
|
|
65
|
-
assert.deepEqual(
|
|
65
|
+
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.kimi, [
|
|
66
|
+
'cc-collab', 'codex-collab', 'ultra-verify',
|
|
67
|
+
]);
|
|
68
|
+
assert.deepEqual(SUPPORTED_RUNTIMES, ['claude', 'opencode', 'codex', 'kimi']);
|
|
66
69
|
|
|
67
70
|
for (const runtime of SUPPORTED_RUNTIMES) {
|
|
68
71
|
const names = skillsForRuntime(runtime);
|
package/adapters/claude.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
writeAtomic,
|
|
16
16
|
} = require('./_shared/file-ops.cjs');
|
|
17
17
|
const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
|
|
18
|
+
const provenance = require('./_shared/provenance.cjs');
|
|
18
19
|
const {
|
|
19
20
|
CORE_PUBLIC_SKILLS,
|
|
20
21
|
WORKFLOW_HOOK_FILES,
|
|
@@ -26,6 +27,7 @@ const MCP_SERVER_NAME = PLUGIN_NAME;
|
|
|
26
27
|
const SOURCE_TAG = 'ubp';
|
|
27
28
|
const SENTINEL_KEY = '_ubp_manifest';
|
|
28
29
|
const COMMAND_NAMES = CORE_PUBLIC_SKILLS.filter((name) => name !== 'ultra-review');
|
|
30
|
+
const PROVENANCE_FILE = 'provenance.json';
|
|
29
31
|
|
|
30
32
|
function resolveTarget(ctx = {}) {
|
|
31
33
|
if (ctx.configDir) return ctx.configDir;
|
|
@@ -155,9 +157,39 @@ function install(ctx = {}) {
|
|
|
155
157
|
license: pkg.license || 'MIT',
|
|
156
158
|
}, null, 2) + '\n');
|
|
157
159
|
markManaged(target, { adapter: 'claude', plugin: PLUGIN_NAME });
|
|
160
|
+
const source = provenance.packageSource(repoRoot);
|
|
161
|
+
const provenanceFile = path.join(target, PROVENANCE_FILE);
|
|
162
|
+
report.provenance = provenance.writeProvenance({
|
|
163
|
+
file: provenanceFile,
|
|
164
|
+
adapter: 'claude',
|
|
165
|
+
...source,
|
|
166
|
+
roots: { plugin: target },
|
|
167
|
+
assets: provenance.assetRefsForTree('plugin', target, {
|
|
168
|
+
exclude: ['.ubp-managed', PROVENANCE_FILE],
|
|
169
|
+
}),
|
|
170
|
+
contracts: {
|
|
171
|
+
plugin_manifest: { root: 'plugin', path: '.claude-plugin/plugin.json' },
|
|
172
|
+
mcp_registration: { root: 'plugin', path: '.mcp.json' },
|
|
173
|
+
mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
|
|
174
|
+
hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
|
|
175
|
+
checkpoint_hook: { root: 'plugin', path: 'hooks/workflow_checkpoint.py' },
|
|
176
|
+
resume_hook: { root: 'plugin', path: 'hooks/workflow_resume.py' },
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
report.provenance.file = provenanceFile;
|
|
158
180
|
return report;
|
|
159
181
|
}
|
|
160
182
|
|
|
183
|
+
function doctor(ctx = {}) {
|
|
184
|
+
const repoRoot = resolveRepoRoot(ctx);
|
|
185
|
+
const source = provenance.packageSource(repoRoot);
|
|
186
|
+
return provenance.inspectProvenance({
|
|
187
|
+
file: path.join(resolvePluginRoot(ctx), PROVENANCE_FILE),
|
|
188
|
+
expectedAdapter: 'claude',
|
|
189
|
+
expectedPackageVersion: source.packageInfo.version,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
161
193
|
function uninstall(ctx = {}) {
|
|
162
194
|
const target = resolvePluginRoot(ctx);
|
|
163
195
|
const report = { target, removed: {}, config: { updated: false } };
|
|
@@ -179,4 +211,5 @@ module.exports = {
|
|
|
179
211
|
resolvePluginRoot,
|
|
180
212
|
install,
|
|
181
213
|
uninstall,
|
|
214
|
+
doctor,
|
|
182
215
|
};
|
package/adapters/codex.js
CHANGED
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
buildPlugin,
|
|
34
34
|
installAgents,
|
|
35
35
|
} = require('./_shared/codex-assets.cjs');
|
|
36
|
+
const provenance = require('./_shared/provenance.cjs');
|
|
36
37
|
|
|
37
38
|
const MCP_SERVER_NAME = PLUGIN_NAME;
|
|
38
39
|
const SOURCE_TAG = 'ubp';
|
|
@@ -40,6 +41,7 @@ const MARKER_BEGIN = '# >>> ultra-builder-pro managed block — do not edit by h
|
|
|
40
41
|
const MARKER_END = '# <<< ultra-builder-pro managed block';
|
|
41
42
|
const RUNTIME_MANIFEST_DIR = 'ultra-builder-pro';
|
|
42
43
|
const RUNTIME_MANIFEST_FILE = 'install-manifest.json';
|
|
44
|
+
const PROVENANCE_FILE = 'provenance.json';
|
|
43
45
|
const HOOK_ADAPTER_RELATIVE = path.join('hooks', 'adapters', 'codex.py');
|
|
44
46
|
|
|
45
47
|
function resolveTarget(ctx = {}) {
|
|
@@ -355,6 +357,30 @@ function install(ctx = {}) {
|
|
|
355
357
|
agents: agents.installed,
|
|
356
358
|
hookCacheVersions,
|
|
357
359
|
});
|
|
360
|
+
const source = provenance.packageSource(repoRoot);
|
|
361
|
+
const provenanceFile = path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE);
|
|
362
|
+
const provenanceManifest = provenance.writeProvenance({
|
|
363
|
+
file: provenanceFile,
|
|
364
|
+
adapter: 'codex',
|
|
365
|
+
...source,
|
|
366
|
+
roots: { plugin: pluginRoot, config: configDir },
|
|
367
|
+
assets: [
|
|
368
|
+
...provenance.assetRefsForTree('plugin', pluginRoot, {
|
|
369
|
+
exclude: ['.ubp-managed'],
|
|
370
|
+
}),
|
|
371
|
+
...agents.installed.map((file) => ({ root: 'config', path: path.join('agents', file) })),
|
|
372
|
+
{ root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
|
|
373
|
+
],
|
|
374
|
+
contracts: {
|
|
375
|
+
plugin_manifest: { root: 'plugin', path: '.codex-plugin/plugin.json' },
|
|
376
|
+
mcp_registration: { root: 'plugin', path: '.mcp.json' },
|
|
377
|
+
mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
|
|
378
|
+
hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
|
|
379
|
+
hook_adapter: { root: 'plugin', path: HOOK_ADAPTER_RELATIVE },
|
|
380
|
+
runtime_manifest: { root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
provenanceManifest.file = provenanceFile;
|
|
358
384
|
|
|
359
385
|
return {
|
|
360
386
|
target: configDir,
|
|
@@ -365,10 +391,70 @@ function install(ctx = {}) {
|
|
|
365
391
|
hookCompatibility,
|
|
366
392
|
legacy,
|
|
367
393
|
manifestFile,
|
|
394
|
+
provenance: provenanceManifest,
|
|
368
395
|
config: { updated: legacy.configUpdated },
|
|
369
396
|
};
|
|
370
397
|
}
|
|
371
398
|
|
|
399
|
+
function doctor(ctx = {}) {
|
|
400
|
+
const configDir = resolveTarget(ctx);
|
|
401
|
+
const pluginRoot = resolvePluginRoot(ctx);
|
|
402
|
+
const repoRoot = resolveRepoRoot(ctx);
|
|
403
|
+
const marketplaceFile = resolveMarketplaceFile(ctx);
|
|
404
|
+
const source = provenance.packageSource(repoRoot);
|
|
405
|
+
const report = provenance.inspectProvenance({
|
|
406
|
+
file: path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE),
|
|
407
|
+
expectedAdapter: 'codex',
|
|
408
|
+
expectedPackageVersion: source.packageInfo.version,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
let runtimeManifest = null;
|
|
412
|
+
try {
|
|
413
|
+
runtimeManifest = readRuntimeManifest(configDir);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', message: error.message });
|
|
416
|
+
}
|
|
417
|
+
const runtimeManifestOk = runtimeManifest?.source === SOURCE_TAG
|
|
418
|
+
&& runtimeManifest.adapter === 'codex'
|
|
419
|
+
&& runtimeManifest.plugin?.root === pluginRoot
|
|
420
|
+
&& typeof runtimeManifest.plugin?.version === 'string';
|
|
421
|
+
if (!runtimeManifestOk && !report.issues.some((entry) => entry.code === 'RUNTIME_MANIFEST_INVALID')) {
|
|
422
|
+
report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', path: runtimeManifestFile(configDir) });
|
|
423
|
+
}
|
|
424
|
+
report.checks.runtime_manifest = { status: runtimeManifestOk ? 'pass' : 'fail' };
|
|
425
|
+
|
|
426
|
+
let marketplaceOk = false;
|
|
427
|
+
try {
|
|
428
|
+
const marketplace = loadMarketplace(marketplaceFile);
|
|
429
|
+
const entry = marketplace.plugins.find((plugin) => plugin?.name === PLUGIN_NAME);
|
|
430
|
+
marketplaceOk = entry?.source?.source === 'local'
|
|
431
|
+
&& entry.source.path === `./plugins/${PLUGIN_NAME}`;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', message: error.message });
|
|
434
|
+
}
|
|
435
|
+
if (!marketplaceOk && !report.issues.some((entry) => entry.code === 'MARKETPLACE_REGISTRATION_INVALID')) {
|
|
436
|
+
report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', path: marketplaceFile });
|
|
437
|
+
}
|
|
438
|
+
report.checks.marketplace = { status: marketplaceOk ? 'pass' : 'fail' };
|
|
439
|
+
|
|
440
|
+
let hookTargetsOk = runtimeManifestOk;
|
|
441
|
+
if (runtimeManifestOk) {
|
|
442
|
+
const cacheRoot = pluginCacheRoot(configDir, runtimeManifest.marketplace?.name || 'personal');
|
|
443
|
+
const currentCacheRequired = shouldRunPluginCli(ctx);
|
|
444
|
+
for (const version of runtimeManifest.hook_cache_versions || []) {
|
|
445
|
+
if (version === runtimeManifest.plugin.version && !currentCacheRequired) continue;
|
|
446
|
+
const adapter = path.join(cacheRoot, version, HOOK_ADAPTER_RELATIVE);
|
|
447
|
+
if (!fs.existsSync(adapter)) {
|
|
448
|
+
hookTargetsOk = false;
|
|
449
|
+
report.issues.push({ code: 'HOOK_TARGET_MISSING', version, path: adapter });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
report.checks.hook_targets = { status: hookTargetsOk ? 'pass' : 'fail' };
|
|
454
|
+
if (report.status !== 'missing') report.status = report.issues.length === 0 ? 'healthy' : 'degraded';
|
|
455
|
+
return report;
|
|
456
|
+
}
|
|
457
|
+
|
|
372
458
|
function removeManagedAgents(configDir, manifest) {
|
|
373
459
|
const agentRoot = path.join(configDir, 'agents');
|
|
374
460
|
const requested = manifest && Array.isArray(manifest.agents)
|
|
@@ -440,6 +526,7 @@ module.exports = {
|
|
|
440
526
|
resolveMarketplaceFile,
|
|
441
527
|
install,
|
|
442
528
|
uninstall,
|
|
529
|
+
doctor,
|
|
443
530
|
_internal: {
|
|
444
531
|
stripManagedBlock,
|
|
445
532
|
hasManagedBlock,
|