@cnrai/pave 0.3.32 → 0.3.34
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/MARKETPLACE.md +406 -0
- package/README.md +218 -21
- package/build-binary.js +591 -0
- package/build-npm.js +537 -0
- package/build.js +230 -0
- package/check-binary.js +26 -0
- package/deploy.sh +95 -0
- package/index.js +5775 -0
- package/lib/agent-registry.js +1037 -0
- package/lib/args-parser.js +837 -0
- package/lib/blessed-widget-patched.js +93 -0
- package/lib/cli-markdown.js +590 -0
- package/lib/compaction.js +153 -0
- package/lib/duration.js +94 -0
- package/lib/hash.js +22 -0
- package/lib/marketplace.js +866 -0
- package/lib/memory-config.js +166 -0
- package/lib/skill-manager.js +891 -0
- package/lib/soul.js +31 -0
- package/lib/tool-output-formatter.js +180 -0
- package/package.json +35 -33
- package/start-pave.sh +149 -0
- package/status.js +271 -0
- package/test/abort-stream.test.js +445 -0
- package/test/agent-auto-compaction.test.js +552 -0
- package/test/agent-comm-abort.test.js +95 -0
- package/test/agent-comm.test.js +598 -0
- package/test/agent-inbox.test.js +576 -0
- package/test/agent-init.test.js +264 -0
- package/test/agent-interrupt.test.js +314 -0
- package/test/agent-lifecycle.test.js +520 -0
- package/test/agent-log-files.test.js +349 -0
- package/test/agent-mode.manual-test.js +392 -0
- package/test/agent-parsing.test.js +228 -0
- package/test/agent-post-stream-idle.test.js +762 -0
- package/test/agent-registry.test.js +359 -0
- package/test/agent-rm.test.js +442 -0
- package/test/agent-spawn.test.js +933 -0
- package/test/agent-status-api.test.js +624 -0
- package/test/agent-update.test.js +435 -0
- package/test/args-parser.test.js +391 -0
- package/test/auto-compaction-chat.manual-test.js +227 -0
- package/test/auto-compaction.test.js +941 -0
- package/test/build-config.test.js +120 -0
- package/test/build-npm.test.js +388 -0
- package/test/chat-command.test.js +137 -0
- package/test/chat-leading-lines.test.js +159 -0
- package/test/config-flag.test.js +272 -0
- package/test/cursor-drift.test.js +135 -0
- package/test/debug-require.js +23 -0
- package/test/dir-migration.test.js +323 -0
- package/test/duration.test.js +229 -0
- package/test/ghostty-term.test.js +202 -0
- package/test/http500-backoff.test.js +854 -0
- package/test/integration.test.js +86 -0
- package/test/memory-guard-env.test.js +220 -0
- package/test/pr233-fixes.test.js +259 -0
- package/test/run-agent-init.js +297 -0
- package/test/run-all.js +64 -0
- package/test/run-config-flag.js +159 -0
- package/test/run-cursor-drift.js +82 -0
- package/test/run-session-path.js +154 -0
- package/test/run-tests.js +643 -0
- package/test/sandbox-redirect.test.js +202 -0
- package/test/session-path.test.js +132 -0
- package/test/shebang-strip.test.js +241 -0
- package/test/soul-reinject.test.js +1027 -0
- package/test/soul-reread.test.js +281 -0
- package/test/tool-output-formatter.test.js +486 -0
- package/test/tool-output-gating.test.js +143 -0
- package/test/tool-states.test.js +167 -0
- package/test/tools-flag.test.js +65 -0
- package/test/tui-attach.test.js +1255 -0
- package/test/tui-compaction.test.js +354 -0
- package/test/tui-wrap.test.js +568 -0
- package/test-binary.js +52 -0
- package/test-binary2.js +36 -0
- package/LICENSE +0 -21
- package/pave.js +0 -2
- package/sandbox/SandboxRunner.js +0 -1
- package/sandbox/pave-run.js +0 -2
- package/sandbox/permission.js +0 -1
- package/sandbox/utils/yaml.js +0 -1
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for 'pave agent rm' command (Issue #289)
|
|
4
|
+
*
|
|
5
|
+
* Verifies:
|
|
6
|
+
* - args-parser recognizes 'rm' as agent subcommand
|
|
7
|
+
* - Positional and --name flag parsing, --force parsing
|
|
8
|
+
* - handleAgentRm function exists and is dispatched
|
|
9
|
+
* - Liveness check uses isAgentAlive (not state string comparison)
|
|
10
|
+
* - Force path delegates to registry.stopAgent() (not manual kill)
|
|
11
|
+
* - Re-checks liveness after stop before removal
|
|
12
|
+
* - removeAgent uses recursive deletion (fs.rmSync)
|
|
13
|
+
* - removeAgent only ignores ENOENT, throws on other errors
|
|
14
|
+
* - removeAgent returns result object { removed, error }
|
|
15
|
+
* - Help text includes rm subcommand
|
|
16
|
+
*
|
|
17
|
+
* Run with: node test/agent-rm.test.js
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const assert = require('assert');
|
|
23
|
+
|
|
24
|
+
let passed = 0;
|
|
25
|
+
let failed = 0;
|
|
26
|
+
|
|
27
|
+
function runTest(name, fn) {
|
|
28
|
+
try {
|
|
29
|
+
fn();
|
|
30
|
+
console.log('\u2705 ' + name);
|
|
31
|
+
passed++;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.log('\u274C ' + name + ': ' + e.message);
|
|
34
|
+
failed++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================
|
|
39
|
+
// Load source files for inspection
|
|
40
|
+
// ============================================================
|
|
41
|
+
|
|
42
|
+
const paveSource = fs.readFileSync(
|
|
43
|
+
path.join(__dirname, '..', 'index.js'), 'utf8');
|
|
44
|
+
const argsParserSource = fs.readFileSync(
|
|
45
|
+
path.join(__dirname, '..', 'lib', 'args-parser.js'), 'utf8');
|
|
46
|
+
const registrySource = fs.readFileSync(
|
|
47
|
+
path.join(__dirname, '..', 'lib', 'agent-registry.js'), 'utf8');
|
|
48
|
+
|
|
49
|
+
// Helper: get handleAgentRm function body (use function boundary, not arbitrary window)
|
|
50
|
+
function getRmFuncSection() {
|
|
51
|
+
const idx = paveSource.indexOf('async function handleAgentRm(');
|
|
52
|
+
assert(idx !== -1, 'handleAgentRm function should exist');
|
|
53
|
+
// Find the closing brace at function scope (line starts with })
|
|
54
|
+
let endIdx = paveSource.indexOf('\n}\n', idx);
|
|
55
|
+
if (endIdx === -1) endIdx = idx + 3500; // fallback
|
|
56
|
+
return paveSource.substring(idx, endIdx + 2);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper: get removeAgent function body
|
|
60
|
+
function getRemoveAgentBody() {
|
|
61
|
+
const idx = registrySource.indexOf('function removeAgent(');
|
|
62
|
+
assert(idx !== -1, 'removeAgent function should exist');
|
|
63
|
+
const endIdx = registrySource.indexOf('\n}', idx);
|
|
64
|
+
return registrySource.substring(idx, endIdx + 2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// 1. args-parser: rm subcommand recognition
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
runTest('args-parser: recognizes rm as agentSubcommand', () => {
|
|
72
|
+
assert(argsParserSource.indexOf("a === 'rm'") !== -1,
|
|
73
|
+
'rm should be in the subcommand keyword check');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
runTest('args-parser: rm inline comment includes rm in keyword list', () => {
|
|
77
|
+
const commentIdx = argsParserSource.indexOf('subcommand keyword');
|
|
78
|
+
assert(commentIdx !== -1, 'should have subcommand keyword comment');
|
|
79
|
+
const commentSection = argsParserSource.substring(commentIdx, commentIdx + 200);
|
|
80
|
+
assert(commentSection.indexOf('rm') !== -1,
|
|
81
|
+
'inline comment should list rm as recognized subcommand');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
runTest('args-parser: validation comment includes rm', () => {
|
|
85
|
+
const valIdx = argsParserSource.indexOf('For subcommands (');
|
|
86
|
+
assert(valIdx !== -1, 'should have validation comment');
|
|
87
|
+
const valSection = argsParserSource.substring(valIdx, valIdx + 100);
|
|
88
|
+
assert(valSection.indexOf('rm') !== -1,
|
|
89
|
+
'validation comment should include rm');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
runTest('args-parser: rm accepts at most one agent name', () => {
|
|
93
|
+
assert(argsParserSource.indexOf("agentSubcommand === 'rm'") !== -1,
|
|
94
|
+
'rm should be in the at-most-one-name validation block');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
runTest('args-parser: functional - parse "pave agent rm abc"', () => {
|
|
98
|
+
const parseArgs = require(path.join(__dirname, '..', 'lib', 'args-parser.js')).parseArgs;
|
|
99
|
+
const result = parseArgs(['agent', 'rm', 'abc']);
|
|
100
|
+
assert(result.command === 'agent', 'command should be agent');
|
|
101
|
+
assert(result.agentSubcommand === 'rm', 'agentSubcommand should be rm');
|
|
102
|
+
assert(result.commandArgs[0] === 'abc', 'first commandArg should be abc');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
runTest('args-parser: functional - parse "pave agent rm --force abc"', () => {
|
|
106
|
+
const parseArgs = require(path.join(__dirname, '..', 'lib', 'args-parser.js')).parseArgs;
|
|
107
|
+
const result = parseArgs(['agent', 'rm', '--force', 'abc']);
|
|
108
|
+
assert(result.command === 'agent', 'command should be agent');
|
|
109
|
+
assert(result.agentSubcommand === 'rm', 'agentSubcommand should be rm');
|
|
110
|
+
assert(result.force === true, 'force should be true');
|
|
111
|
+
assert(result.commandArgs[0] === 'abc', 'first commandArg should be abc');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
runTest('args-parser: functional - parse "pave agent rm --name abc"', () => {
|
|
115
|
+
const parseArgs = require(path.join(__dirname, '..', 'lib', 'args-parser.js')).parseArgs;
|
|
116
|
+
const result = parseArgs(['agent', 'rm', '--name', 'abc']);
|
|
117
|
+
assert(result.command === 'agent', 'command should be agent');
|
|
118
|
+
assert(result.agentSubcommand === 'rm', 'agentSubcommand should be rm');
|
|
119
|
+
assert(result.name === 'abc', 'name should be abc');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
runTest('args-parser: functional - parse "pave agent rm" (no name)', () => {
|
|
123
|
+
const parseArgs = require(path.join(__dirname, '..', 'lib', 'args-parser.js')).parseArgs;
|
|
124
|
+
const result = parseArgs(['agent', 'rm']);
|
|
125
|
+
assert(result.command === 'agent', 'command should be agent');
|
|
126
|
+
assert(result.agentSubcommand === 'rm', 'agentSubcommand should be rm');
|
|
127
|
+
assert(!result.commandArgs || result.commandArgs.length === 0, 'no commandArgs');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ============================================================
|
|
131
|
+
// 2. index.js: handleAgentRm function structure
|
|
132
|
+
// ============================================================
|
|
133
|
+
|
|
134
|
+
runTest('index.js: handleAgentRm function exists', () => {
|
|
135
|
+
assert(paveSource.indexOf('async function handleAgentRm(') !== -1,
|
|
136
|
+
'handleAgentRm should be an async function');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
runTest('index.js: rm subcommand is dispatched to handleAgentRm', () => {
|
|
140
|
+
const dispatchIdx = paveSource.indexOf("agentSubcommand === 'rm'");
|
|
141
|
+
assert(dispatchIdx !== -1, 'should dispatch rm subcommand');
|
|
142
|
+
const section = paveSource.substring(dispatchIdx, dispatchIdx + 200);
|
|
143
|
+
assert(section.indexOf('handleAgentRm') !== -1,
|
|
144
|
+
'rm dispatch should call handleAgentRm');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
runTest('index.js: handleAgentRm requires agent name', () => {
|
|
148
|
+
const section = getRmFuncSection();
|
|
149
|
+
assert(section.indexOf('Agent name is required') !== -1,
|
|
150
|
+
'should error if agent name is not provided');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
runTest('index.js: handleAgentRm uses resolveAgentName for prefix matching', () => {
|
|
154
|
+
const section = getRmFuncSection();
|
|
155
|
+
assert(section.indexOf('resolveAgentName') !== -1,
|
|
156
|
+
'should use resolveAgentName for prefix matching');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
runTest('index.js: handleAgentRm handles conflicting positional and --name', () => {
|
|
160
|
+
const section = getRmFuncSection();
|
|
161
|
+
assert(section.indexOf('Conflicting agent names') !== -1,
|
|
162
|
+
'should detect and error on conflicting agent names');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ============================================================
|
|
166
|
+
// 3. Liveness check uses isAgentAlive (not state strings)
|
|
167
|
+
// ============================================================
|
|
168
|
+
|
|
169
|
+
runTest('index.js: handleAgentRm uses isAgentAlive for liveness check', () => {
|
|
170
|
+
const section = getRmFuncSection();
|
|
171
|
+
assert(section.indexOf('isAgentAlive(agentName)') !== -1,
|
|
172
|
+
'should use isAgentAlive for authoritative liveness check');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
runTest('index.js: handleAgentRm checks .alive property not state strings', () => {
|
|
176
|
+
const section = getRmFuncSection();
|
|
177
|
+
assert(section.indexOf('.alive') !== -1,
|
|
178
|
+
'should check .alive property from isAgentAlive result');
|
|
179
|
+
// Must NOT check specific state strings for running detection
|
|
180
|
+
assert(section.indexOf("status.state === 'working'") === -1,
|
|
181
|
+
'should NOT compare state === working');
|
|
182
|
+
assert(section.indexOf("status.state === 'sleeping'") === -1,
|
|
183
|
+
'should NOT compare state === sleeping');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ============================================================
|
|
187
|
+
// 4. Refused removal when alive without --force
|
|
188
|
+
// ============================================================
|
|
189
|
+
|
|
190
|
+
runTest('index.js: handleAgentRm refuses running agent without --force', () => {
|
|
191
|
+
const section = getRmFuncSection();
|
|
192
|
+
assert(section.indexOf('currently running') !== -1,
|
|
193
|
+
'should tell user the agent is currently running');
|
|
194
|
+
assert(section.indexOf('pave agent rm --force') !== -1,
|
|
195
|
+
'should suggest --force in error message');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ============================================================
|
|
199
|
+
// 5. Force path delegates to registry.stopAgent()
|
|
200
|
+
// ============================================================
|
|
201
|
+
|
|
202
|
+
runTest('index.js: handleAgentRm --force uses registry.stopAgent()', () => {
|
|
203
|
+
const section = getRmFuncSection();
|
|
204
|
+
assert(section.indexOf('stopAgent(agentName') !== -1,
|
|
205
|
+
'should use registry.stopAgent to stop the agent');
|
|
206
|
+
// Must NOT hand-roll SIGTERM/SIGKILL
|
|
207
|
+
assert(section.indexOf("process.kill(pid, 'SIGTERM')") === -1,
|
|
208
|
+
'should NOT hand-roll process.kill SIGTERM');
|
|
209
|
+
assert(section.indexOf("process.kill(pid, 'SIGKILL')") === -1,
|
|
210
|
+
'should NOT hand-roll process.kill SIGKILL');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
runTest('index.js: handleAgentRm --force warns but proceeds on stop failure', () => {
|
|
214
|
+
const section = getRmFuncSection();
|
|
215
|
+
// Per issue #289: --force removes even if stop fails
|
|
216
|
+
assert(section.indexOf('stopResult.success') !== -1 ||
|
|
217
|
+
section.indexOf('!stopResult.success') !== -1,
|
|
218
|
+
'should check stopResult.success');
|
|
219
|
+
assert(section.indexOf('Failed to stop agent') !== -1,
|
|
220
|
+
'should warn when stop fails');
|
|
221
|
+
// Should NOT abort (return 1) when stop fails — must proceed to removal
|
|
222
|
+
assert(section.indexOf('Proceeding with removal anyway') !== -1,
|
|
223
|
+
'should proceed with removal even when stop fails (--force semantics)');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
runTest('index.js: handleAgentRm --force does NOT abort on stop failure', () => {
|
|
227
|
+
const section = getRmFuncSection();
|
|
228
|
+
// Should NOT say "Cannot remove" — that would mean aborting on stop failure
|
|
229
|
+
assert(section.indexOf('Cannot remove a running agent') === -1,
|
|
230
|
+
'should NOT abort removal when stop fails (--force means remove anyway)');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
runTest('index.js: handleAgentRm --force re-checks liveness after successful stop', () => {
|
|
234
|
+
const section = getRmFuncSection();
|
|
235
|
+
const stopIdx = section.indexOf('stopAgent(agentName');
|
|
236
|
+
assert(stopIdx !== -1, 'should call stopAgent');
|
|
237
|
+
const afterStop = section.substring(stopIdx);
|
|
238
|
+
// After stopAgent succeeds, should re-check isAgentAlive to guard against restart races
|
|
239
|
+
assert(afterStop.indexOf('isAgentAlive(agentName)') !== -1,
|
|
240
|
+
'should re-check isAgentAlive after stopAgent to guard against restart races');
|
|
241
|
+
// The re-check should warn but still proceed (--force semantics)
|
|
242
|
+
assert(afterStop.indexOf('restarted by another process') !== -1 ||
|
|
243
|
+
afterStop.indexOf('was restarted') !== -1,
|
|
244
|
+
'should warn about restart race but still proceed with removal');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
runTest('index.js: handleAgentRm does NOT use listAgents for existence check', () => {
|
|
248
|
+
const section = getRmFuncSection();
|
|
249
|
+
// Should NOT use listAgents() — it can silently return empty on readdirSync errors,
|
|
250
|
+
// causing rm to report "not found" when the agent actually exists.
|
|
251
|
+
assert(section.indexOf('listAgents()') === -1,
|
|
252
|
+
'should NOT use listAgents() for existence check — rely on removeAgent result instead');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ============================================================
|
|
256
|
+
// 6. registry: removeAgent
|
|
257
|
+
// ============================================================
|
|
258
|
+
|
|
259
|
+
runTest('agent-registry: removeAgent function exists', () => {
|
|
260
|
+
assert(registrySource.indexOf('function removeAgent(') !== -1,
|
|
261
|
+
'removeAgent function should exist');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
runTest('agent-registry: removeAgent is exported', () => {
|
|
265
|
+
const exportIdx = registrySource.indexOf('module.exports');
|
|
266
|
+
assert(exportIdx !== -1, 'should have module.exports');
|
|
267
|
+
const exportBlock = registrySource.substring(exportIdx);
|
|
268
|
+
assert(exportBlock.indexOf('removeAgent') !== -1,
|
|
269
|
+
'removeAgent should be exported');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
runTest('agent-registry: removeAgent uses fs.rmSync recursive', () => {
|
|
273
|
+
const body = getRemoveAgentBody();
|
|
274
|
+
assert(body.indexOf('fs.rmSync(') !== -1,
|
|
275
|
+
'should use fs.rmSync for removal');
|
|
276
|
+
assert(body.indexOf('recursive: true') !== -1,
|
|
277
|
+
'should use recursive: true option');
|
|
278
|
+
// force:true is NOT required — using force:false with ENOENT catch is equally valid
|
|
279
|
+
// and gives more explicit error semantics
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
runTest('agent-registry: removeAgent does NOT use piecemeal unlinkSync', () => {
|
|
283
|
+
const body = getRemoveAgentBody();
|
|
284
|
+
assert(body.indexOf('unlinkSync') === -1,
|
|
285
|
+
'should NOT use unlinkSync (rmSync recursive handles all)');
|
|
286
|
+
assert(body.indexOf('rmdirSync') === -1,
|
|
287
|
+
'should NOT use rmdirSync (rmSync recursive handles all)');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
runTest('agent-registry: removeAgent returns result object', () => {
|
|
291
|
+
const body = getRemoveAgentBody();
|
|
292
|
+
assert(body.indexOf('removed: true') !== -1,
|
|
293
|
+
'should return { removed: true } on success');
|
|
294
|
+
assert(body.indexOf('removed: false') !== -1,
|
|
295
|
+
'should return { removed: false } when nothing to remove');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
runTest('agent-registry: removeAgent only ignores ENOENT', () => {
|
|
299
|
+
const body = getRemoveAgentBody();
|
|
300
|
+
assert(body.indexOf('ENOENT') !== -1,
|
|
301
|
+
'should check for ENOENT specifically');
|
|
302
|
+
assert(body.indexOf('throw e') !== -1 || body.indexOf('throw err') !== -1,
|
|
303
|
+
'should re-throw non-ENOENT errors');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
runTest('agent-registry: removeAgent JSDoc describes full cleanup', () => {
|
|
307
|
+
const funcIdx = registrySource.indexOf('function removeAgent(');
|
|
308
|
+
const jsdocStart = registrySource.lastIndexOf('/**', funcIdx);
|
|
309
|
+
const jsdoc = registrySource.substring(jsdocStart, funcIdx);
|
|
310
|
+
assert(jsdoc.indexOf('all associated files') !== -1 ||
|
|
311
|
+
jsdoc.indexOf('inbox') !== -1 || jsdoc.indexOf('recursive') !== -1,
|
|
312
|
+
'JSDoc should describe that removal is recursive / includes all associated files');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// ============================================================
|
|
316
|
+
// 7. handleAgentRm uses removeAgent result
|
|
317
|
+
// ============================================================
|
|
318
|
+
|
|
319
|
+
runTest('index.js: handleAgentRm checks removeAgent result', () => {
|
|
320
|
+
const section = getRmFuncSection();
|
|
321
|
+
assert(section.indexOf('.removed') !== -1,
|
|
322
|
+
'should check removed property from removeAgent result');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
runTest('index.js: handleAgentRm wraps removeAgent in try/catch', () => {
|
|
326
|
+
const section = getRmFuncSection();
|
|
327
|
+
const removeIdx = section.indexOf('removeAgent(agentName)');
|
|
328
|
+
assert(removeIdx !== -1, 'should call removeAgent');
|
|
329
|
+
// Should be inside try/catch
|
|
330
|
+
const tryIdx = section.lastIndexOf('try {', removeIdx);
|
|
331
|
+
const catchIdx = section.indexOf('catch (err)', removeIdx);
|
|
332
|
+
assert(tryIdx !== -1 && catchIdx !== -1,
|
|
333
|
+
'removeAgent call should be in try/catch');
|
|
334
|
+
assert(section.indexOf('Error removing agent') !== -1,
|
|
335
|
+
'should surface removeAgent errors to user');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// ============================================================
|
|
339
|
+
// 8. Help text and documentation
|
|
340
|
+
// ============================================================
|
|
341
|
+
|
|
342
|
+
runTest('index.js: help SUBCOMMANDS includes rm', () => {
|
|
343
|
+
const helpIdx = paveSource.indexOf('SUBCOMMANDS:');
|
|
344
|
+
assert(helpIdx !== -1, 'should have SUBCOMMANDS section');
|
|
345
|
+
const helpSection = paveSource.substring(helpIdx, helpIdx + 2000);
|
|
346
|
+
assert(helpSection.indexOf('rm') !== -1,
|
|
347
|
+
'SUBCOMMANDS should list rm');
|
|
348
|
+
assert(helpSection.indexOf('--force') !== -1,
|
|
349
|
+
'rm help should mention --force');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
runTest('index.js: USAGE section includes pave agent rm', () => {
|
|
353
|
+
const usageIdx = paveSource.indexOf('USAGE:');
|
|
354
|
+
assert(usageIdx !== -1, 'should have USAGE section');
|
|
355
|
+
const usageSection = paveSource.substring(usageIdx, usageIdx + 500);
|
|
356
|
+
assert(usageSection.indexOf('agent rm') !== -1,
|
|
357
|
+
'USAGE section should include agent rm');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
runTest('index.js: OPTIONS documents --force flag', () => {
|
|
361
|
+
const optIdx = paveSource.indexOf('OPTIONS:');
|
|
362
|
+
assert(optIdx !== -1, 'should have OPTIONS section');
|
|
363
|
+
const optSection = paveSource.substring(optIdx, optIdx + 1000);
|
|
364
|
+
assert(optSection.indexOf('--force') !== -1,
|
|
365
|
+
'OPTIONS should document --force flag');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ============================================================
|
|
369
|
+
// 7. Copilot review round 4 fixes
|
|
370
|
+
// ============================================================
|
|
371
|
+
|
|
372
|
+
runTest('index.js: help text says stops not kills for --force', () => {
|
|
373
|
+
const helpIdx = paveSource.indexOf('SUBCOMMANDS:');
|
|
374
|
+
const helpSection = paveSource.substring(helpIdx, helpIdx + 2000);
|
|
375
|
+
let rmIdx = helpSection.indexOf('rm [name]');
|
|
376
|
+
if (rmIdx === -1) rmIdx = helpSection.indexOf('rm');
|
|
377
|
+
assert(rmIdx !== -1, 'rm should be in help');
|
|
378
|
+
const rmSection = helpSection.substring(rmIdx, rmIdx + 500);
|
|
379
|
+
assert(rmSection.indexOf('attempts to stop') !== -1,
|
|
380
|
+
'help should say "attempts to stop" (not "stops") for --force accuracy');
|
|
381
|
+
assert(rmSection.indexOf('kills the agent') === -1,
|
|
382
|
+
'help should NOT say kills the agent');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
runTest('index.js: handleAgentRm avoids listAgents for existence check', () => {
|
|
386
|
+
const funcSection = getRmFuncSection();
|
|
387
|
+
// Should NOT use listAgents() which can silently return [] on readdirSync errors
|
|
388
|
+
assert(funcSection.indexOf('listAgents()') === -1,
|
|
389
|
+
'should NOT use listAgents which can silently fail on readdirSync errors');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
runTest('agent-registry: removeAgent JSDoc documents throw behavior', () => {
|
|
393
|
+
const funcIdx = registrySource.indexOf('function removeAgent(');
|
|
394
|
+
const jsdocStart = registrySource.lastIndexOf('/**', funcIdx);
|
|
395
|
+
const jsdoc = registrySource.substring(jsdocStart, funcIdx);
|
|
396
|
+
assert(jsdoc.indexOf('@throws') !== -1 || jsdoc.indexOf('throw') !== -1,
|
|
397
|
+
'JSDoc should document throw behavior for non-ENOENT errors');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// ============================================================
|
|
401
|
+
// 8. Copilot review round 7 fixes
|
|
402
|
+
// ============================================================
|
|
403
|
+
|
|
404
|
+
runTest('index.js: handleAgentRm prints candidate suggestions on resolve failure', () => {
|
|
405
|
+
const section = getRmFuncSection();
|
|
406
|
+
const resolveIdx = section.indexOf('resolveAgentName(nameArg)');
|
|
407
|
+
assert(resolveIdx !== -1, 'should call resolveAgentName');
|
|
408
|
+
const afterResolve = section.substring(resolveIdx, resolveIdx + 500);
|
|
409
|
+
assert(afterResolve.indexOf('resolved.candidates') !== -1,
|
|
410
|
+
'should check resolved.candidates on error');
|
|
411
|
+
assert(afterResolve.indexOf('Did you mean') !== -1,
|
|
412
|
+
'should print "Did you mean" hint with candidates');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
runTest('index.js: handleAgentRm JSDoc says "attempts to stop" not "stops"', () => {
|
|
416
|
+
const funcIdx = paveSource.indexOf('async function handleAgentRm');
|
|
417
|
+
assert(funcIdx !== -1, 'should have handleAgentRm function');
|
|
418
|
+
const jsdocStart = paveSource.lastIndexOf('/**', funcIdx);
|
|
419
|
+
const jsdoc = paveSource.substring(jsdocStart, funcIdx);
|
|
420
|
+
assert(jsdoc.indexOf('attempts to stop') !== -1,
|
|
421
|
+
'JSDoc should say "attempts to stop" to reflect that removal proceeds even if stop fails');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
runTest('agent-registry: removeAgent does not use existsSync pre-check', () => {
|
|
425
|
+
const body = getRemoveAgentBody();
|
|
426
|
+
assert(body.indexOf('existsSync') === -1,
|
|
427
|
+
'should NOT use existsSync pre-check (rmSync + ENOENT catch handles the race cleanly)');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
runTest('agent-registry: removeAgent uses force:false (explicit ENOENT handling)', () => {
|
|
431
|
+
const body = getRemoveAgentBody();
|
|
432
|
+
// force:true is NOT used — ENOENT is caught explicitly for clearer semantics
|
|
433
|
+
assert(body.indexOf('force: true') === -1,
|
|
434
|
+
'should NOT use force: true (ENOENT is handled via explicit catch instead)');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ============================================================
|
|
438
|
+
// Summary
|
|
439
|
+
// ============================================================
|
|
440
|
+
|
|
441
|
+
console.log('\nTotal: ' + (passed + failed) + ', Passed: ' + passed + ', Failed: ' + failed);
|
|
442
|
+
if (failed > 0) process.exitCode = 1;
|