@cnrai/pave 0.3.35 → 0.3.51

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -218
  3. package/package.json +32 -35
  4. package/pave.js +3 -0
  5. package/sandbox/SandboxRunner.js +1 -0
  6. package/sandbox/pave-run.js +2 -0
  7. package/sandbox/permission.js +1 -0
  8. package/sandbox/utils/yaml.js +1 -0
  9. package/MARKETPLACE.md +0 -406
  10. package/build-binary.js +0 -591
  11. package/build-npm.js +0 -537
  12. package/build.js +0 -230
  13. package/check-binary.js +0 -26
  14. package/deploy.sh +0 -95
  15. package/index.js +0 -5776
  16. package/lib/agent-registry.js +0 -1037
  17. package/lib/args-parser.js +0 -837
  18. package/lib/blessed-widget-patched.js +0 -93
  19. package/lib/cli-markdown.js +0 -590
  20. package/lib/compaction.js +0 -153
  21. package/lib/duration.js +0 -94
  22. package/lib/hash.js +0 -22
  23. package/lib/marketplace.js +0 -866
  24. package/lib/memory-config.js +0 -166
  25. package/lib/skill-manager.js +0 -891
  26. package/lib/soul.js +0 -31
  27. package/lib/tool-output-formatter.js +0 -180
  28. package/start-pave.sh +0 -149
  29. package/status.js +0 -271
  30. package/test/abort-stream.test.js +0 -445
  31. package/test/agent-auto-compaction.test.js +0 -552
  32. package/test/agent-comm-abort.test.js +0 -95
  33. package/test/agent-comm.test.js +0 -598
  34. package/test/agent-inbox.test.js +0 -576
  35. package/test/agent-init.test.js +0 -264
  36. package/test/agent-interrupt.test.js +0 -314
  37. package/test/agent-lifecycle.test.js +0 -520
  38. package/test/agent-log-files.test.js +0 -349
  39. package/test/agent-mode.manual-test.js +0 -392
  40. package/test/agent-parsing.test.js +0 -228
  41. package/test/agent-post-stream-idle.test.js +0 -762
  42. package/test/agent-registry.test.js +0 -359
  43. package/test/agent-rm.test.js +0 -442
  44. package/test/agent-spawn.test.js +0 -933
  45. package/test/agent-status-api.test.js +0 -624
  46. package/test/agent-update.test.js +0 -435
  47. package/test/args-parser.test.js +0 -391
  48. package/test/auto-compaction-chat.manual-test.js +0 -227
  49. package/test/auto-compaction.test.js +0 -941
  50. package/test/build-config.test.js +0 -120
  51. package/test/build-npm.test.js +0 -388
  52. package/test/chat-command.test.js +0 -137
  53. package/test/chat-leading-lines.test.js +0 -159
  54. package/test/config-flag.test.js +0 -272
  55. package/test/cursor-drift.test.js +0 -135
  56. package/test/debug-require.js +0 -23
  57. package/test/dir-migration.test.js +0 -323
  58. package/test/duration.test.js +0 -229
  59. package/test/ghostty-term.test.js +0 -202
  60. package/test/http500-backoff.test.js +0 -854
  61. package/test/integration.test.js +0 -86
  62. package/test/memory-guard-env.test.js +0 -220
  63. package/test/pr233-fixes.test.js +0 -259
  64. package/test/run-agent-init.js +0 -297
  65. package/test/run-all.js +0 -64
  66. package/test/run-config-flag.js +0 -159
  67. package/test/run-cursor-drift.js +0 -82
  68. package/test/run-session-path.js +0 -154
  69. package/test/run-tests.js +0 -643
  70. package/test/sandbox-redirect.test.js +0 -202
  71. package/test/session-path.test.js +0 -132
  72. package/test/shebang-strip.test.js +0 -241
  73. package/test/soul-reinject.test.js +0 -1027
  74. package/test/soul-reread.test.js +0 -281
  75. package/test/tool-output-formatter.test.js +0 -486
  76. package/test/tool-output-gating.test.js +0 -143
  77. package/test/tool-states.test.js +0 -167
  78. package/test/tools-flag.test.js +0 -65
  79. package/test/tui-attach.test.js +0 -1255
  80. package/test/tui-compaction.test.js +0 -354
  81. package/test/tui-wrap.test.js +0 -568
  82. package/test-binary.js +0 -52
  83. package/test-binary2.js +0 -36
@@ -1,297 +0,0 @@
1
- // Standalone test runner for 'pave agent init' (issue #109)
2
- // Tests args-parser changes and source code patterns.
3
- // Designed for Node 16 sandbox compatibility (no module imports that use # fields).
4
-
5
- const path = require('path');
6
- const fs = require('fs');
7
-
8
- let passed = 0;
9
- let failed = 0;
10
-
11
- function assert(cond, msg) {
12
- if (cond) {
13
- passed++;
14
- console.log('\u2705 ' + msg);
15
- } else {
16
- failed++;
17
- console.log('\u274C ' + msg);
18
- }
19
- }
20
-
21
- function _assertEqual(actual, expected, msg) {
22
- if (actual === expected) {
23
- passed++;
24
- console.log('\u2705 ' + msg);
25
- } else {
26
- failed++;
27
- console.log('\u274C ' + msg + ' (expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual) + ')');
28
- }
29
- }
30
-
31
- // =============================================
32
- // Read source files for pattern verification
33
- // =============================================
34
-
35
- const argsParserPath = path.join(__dirname, '..', 'lib', 'args-parser.js');
36
- const indexPath = path.join(__dirname, '..', 'index.js');
37
- const permissionPath = path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'permission.js');
38
-
39
- const argsParserSrc = fs.readFileSync(argsParserPath, 'utf8');
40
- const indexSrc = fs.readFileSync(indexPath, 'utf8');
41
- const permissionSrc = fs.readFileSync(permissionPath, 'utf8');
42
-
43
- console.log('Agent init tests (issue #109)');
44
- console.log('========================================');
45
- console.log('');
46
-
47
- // =============================================
48
- // Args-parser source verification
49
- // =============================================
50
-
51
- console.log('Args-parser changes:');
52
-
53
- assert(argsParserSrc.indexOf('agentSubcommand') !== -1,
54
- 'args-parser defines agentSubcommand field');
55
-
56
- assert(argsParserSrc.indexOf("agentSubcommand: null") !== -1,
57
- 'agentSubcommand defaults to null');
58
-
59
- assert(argsParserSrc.indexOf("a === 'init'") !== -1,
60
- 'args-parser checks for init token in positional args');
61
-
62
- assert(argsParserSrc.indexOf("agentSubcommand = 'init'") !== -1,
63
- 'args-parser sets agentSubcommand to init');
64
-
65
- assert(argsParserSrc.indexOf("a === 'init' && args.agentSubcommand === null && args.commandArgs.length === 0") !== -1,
66
- 'args-parser detects init as first positional token in main loop');
67
-
68
- assert(argsParserSrc.indexOf("agentSubcommand !== 'init'") !== -1,
69
- 'args-parser skips sleep/SOUL defaults for agent init');
70
-
71
- assert(argsParserSrc.indexOf("agentSubcommand !== 'init' && args.commandArgs.length > 1") !== -1,
72
- 'args-parser skips SOUL validation/warnings for agent init');
73
-
74
- assert(argsParserSrc.indexOf('agent init') !== -1,
75
- 'help text mentions agent init');
76
-
77
- console.log('');
78
-
79
- // =============================================
80
- // Permission.js source verification
81
- // =============================================
82
-
83
- console.log('Permission system changes:');
84
-
85
- assert(permissionSrc.indexOf('let PAVE_DIR') !== -1,
86
- 'PAVE_DIR is declared with let (mutable)');
87
-
88
- assert(permissionSrc.indexOf('let TOKENS_FILE') !== -1,
89
- 'TOKENS_FILE is declared with let (mutable)');
90
-
91
- assert(permissionSrc.indexOf('let PERMISSION_FILE') !== -1,
92
- 'PERMISSION_FILE is declared with let (mutable)');
93
-
94
- assert(permissionSrc.indexOf('function setConfigDir(dir)') !== -1,
95
- 'setConfigDir function exists');
96
-
97
- assert(permissionSrc.indexOf('function getConfigDir()') !== -1,
98
- 'getConfigDir function exists');
99
-
100
- assert(permissionSrc.indexOf('function createDefaultPermissions()') !== -1,
101
- 'createDefaultPermissions function exists');
102
-
103
- // Verify setConfigDir updates all three paths
104
- const setConfigDirStart = permissionSrc.indexOf('function setConfigDir(dir)');
105
- const setConfigDirEnd = permissionSrc.indexOf('\n/**', setConfigDirStart + 1);
106
- if (setConfigDirStart !== -1 && setConfigDirEnd !== -1) {
107
- const body = permissionSrc.substring(setConfigDirStart, setConfigDirEnd);
108
- assert(body.indexOf('PAVE_DIR =') !== -1, 'setConfigDir updates PAVE_DIR');
109
- assert(body.indexOf('TOKENS_FILE =') !== -1, 'setConfigDir updates TOKENS_FILE');
110
- assert(body.indexOf('PERMISSION_FILE =') !== -1, 'setConfigDir updates PERMISSION_FILE');
111
- assert(body.indexOf('_permissionsLoaded = false') !== -1, 'setConfigDir resets _permissionsLoaded');
112
- assert(body.indexOf('_privateTokenValues.clear()') !== -1, 'setConfigDir clears private token values');
113
- assert(body.indexOf('_lastEnvFileMtime = 0') !== -1, 'setConfigDir resets token file mtime');
114
- assert(body.indexOf('_lastEnvFilePath = null') !== -1, 'setConfigDir resets token file path');
115
- assert(body.indexOf('permissions.network.clear()') !== -1, 'setConfigDir resets network permissions');
116
- assert(body.indexOf('permissions.filesystem.read.clear()') !== -1, 'setConfigDir resets filesystem.read permissions');
117
- assert(body.indexOf('permissions.filesystem.write.clear()') !== -1, 'setConfigDir resets filesystem.write permissions');
118
- assert(body.indexOf('permissions.modules.clear()') !== -1, 'setConfigDir resets modules permissions');
119
- assert(body.indexOf('permissions.system.clear()') !== -1, 'setConfigDir resets system permissions');
120
- assert(body.indexOf('tokenConfigs.clear()') !== -1, 'setConfigDir resets token configs');
121
- assert(body.indexOf('loadPermissions(true)') !== -1, 'setConfigDir reloads permissions from new directory');
122
- assert(body.indexOf('_permissionsLoaded = true') !== -1, 'setConfigDir sets _permissionsLoaded to true after reload');
123
- } else {
124
- failed++;
125
- console.log(' FAIL: could not find setConfigDir body');
126
- }
127
-
128
- // Verify createDefaultPermissions has correct structure
129
- assert(permissionSrc.indexOf("version: 4") !== -1 ||
130
- permissionSrc.indexOf("version: 4,") !== -1,
131
- 'createDefaultPermissions sets version 4');
132
-
133
- assert(permissionSrc.indexOf("createDefaultPermissions") !== -1 &&
134
- permissionSrc.substring(permissionSrc.indexOf('module.exports')).indexOf("createDefaultPermissions") !== -1,
135
- 'createDefaultPermissions is exported');
136
-
137
- // Verify exports
138
- const exportsSection = permissionSrc.substring(permissionSrc.indexOf('module.exports'));
139
- assert(exportsSection.indexOf('setConfigDir') !== -1, 'setConfigDir is in exports');
140
- assert(exportsSection.indexOf('getConfigDir') !== -1, 'getConfigDir is in exports');
141
- assert(exportsSection.indexOf('createDefaultPermissions') !== -1, 'createDefaultPermissions is in exports');
142
-
143
- console.log('');
144
-
145
- // =============================================
146
- // Index.js source verification
147
- // =============================================
148
-
149
- console.log('Index.js changes:');
150
-
151
- assert(indexSrc.indexOf('Permission.setConfigDir(configPath)') !== -1,
152
- 'index.js calls Permission.setConfigDir in config setup');
153
-
154
- assert(indexSrc.indexOf('Permission.createDefaultPermissions()') !== -1,
155
- 'index.js calls Permission.createDefaultPermissions in config setup');
156
-
157
- assert(indexSrc.indexOf('handleAgentInit') !== -1,
158
- 'handleAgentInit function exists');
159
-
160
- assert(indexSrc.indexOf("agentSubcommand === 'init'") !== -1,
161
- 'agent command checks for init subcommand');
162
-
163
- // Verify handleAgentInit function exists and has correct structure
164
- const initMatch = indexSrc.match(/function handleAgentInit\(args\)\s*\{/);
165
- assert(initMatch !== null, 'handleAgentInit is defined as a function');
166
-
167
- // Verify handleAgentInit calls setConfigDir and createDefaultPermissions
168
- const initFuncStart = indexSrc.indexOf('function handleAgentInit(args)');
169
- const initFuncEnd = indexSrc.indexOf('\n/**', initFuncStart + 1);
170
- if (initFuncStart !== -1 && initFuncEnd !== -1) {
171
- const initFuncBody = indexSrc.substring(initFuncStart, initFuncEnd);
172
- assert(initFuncBody.indexOf('Permission.setConfigDir') !== -1,
173
- 'handleAgentInit calls Permission.setConfigDir');
174
- assert(initFuncBody.indexOf('Permission.createDefaultPermissions') !== -1 ||
175
- initFuncBody.indexOf('createDefaultPermissions') !== -1,
176
- 'handleAgentInit calls createDefaultPermissions');
177
- assert(initFuncBody.indexOf('return 0') !== -1,
178
- 'handleAgentInit returns 0 on success');
179
- assert(initFuncBody.indexOf('return 1') !== -1,
180
- 'handleAgentInit returns 1 on error');
181
- assert(initFuncBody.indexOf('pave agent init') !== -1,
182
- 'handleAgentInit shows usage error for invalid args');
183
- assert(initFuncBody.indexOf('hasCommandArgs') !== -1 || initFuncBody.indexOf('commandArgs') !== -1,
184
- 'handleAgentInit validates no extra command args');
185
- assert(initFuncBody.indexOf('hasSleepFlag') !== -1 || initFuncBody.indexOf('args.sleep') !== -1,
186
- 'handleAgentInit validates no --sleep flag');
187
- assert(initFuncBody.indexOf('hasSoulFlag') !== -1 || initFuncBody.indexOf('args.soul') !== -1,
188
- 'handleAgentInit validates no --soul flag');
189
- assert(initFuncBody.indexOf('hasNameFlag') !== -1 || initFuncBody.indexOf('args.name') !== -1,
190
- 'handleAgentInit validates no --name flag');
191
- assert(initFuncBody.indexOf('hasReinjectIntervalFlag') !== -1 || initFuncBody.indexOf('args.reinjectInterval') !== -1,
192
- 'handleAgentInit validates no --reinject-interval flag');
193
- } else {
194
- failed++;
195
- console.log(' FAIL: could not extract handleAgentInit function body');
196
- }
197
-
198
- // Verify help text includes init
199
- assert(indexSrc.indexOf('pave agent init') !== -1,
200
- 'agent help text mentions pave agent init');
201
-
202
- console.log('');
203
-
204
- // =============================================
205
- // Verify createDefaultPermissions content
206
- // =============================================
207
-
208
- console.log('Default permissions content:');
209
-
210
- // Extract the default permissions object from createDefaultPermissions
211
- const createFuncStart = permissionSrc.indexOf('function createDefaultPermissions()');
212
- const createFuncEnd = permissionSrc.indexOf('\n// Default safe', createFuncStart);
213
- if (createFuncStart !== -1 && createFuncEnd !== -1) {
214
- const createFuncBody = permissionSrc.substring(createFuncStart, createFuncEnd);
215
-
216
- assert(createFuncBody.indexOf("network: ['*']") !== -1,
217
- 'default permissions have network: [*]');
218
- assert(createFuncBody.indexOf("read: ['*']") !== -1,
219
- 'default permissions have filesystem.read: [*]');
220
- assert(createFuncBody.indexOf("write: ['*']") !== -1,
221
- 'default permissions have filesystem.write: [*]');
222
- assert(createFuncBody.indexOf("modules: ['*']") !== -1,
223
- 'default permissions have modules: [*]');
224
- assert(createFuncBody.indexOf("system: ['*']") !== -1,
225
- 'default permissions have system: [*]');
226
- assert(createFuncBody.indexOf("allowed: ['*']") !== -1,
227
- 'default permissions have skills.allowed: [*]');
228
- assert(createFuncBody.indexOf("denied: []") !== -1,
229
- 'default permissions have skills.denied: []');
230
- assert(createFuncBody.indexOf("fs.existsSync(PERMISSION_FILE)") !== -1,
231
- 'createDefaultPermissions checks if file exists before writing');
232
- assert(createFuncBody.indexOf("0o600") !== -1,
233
- 'permissions file is written with restricted mode 0o600');
234
- assert(createFuncBody.indexOf("fs.openSync(PERMISSION_FILE, 'wx'") !== -1,
235
- 'createDefaultPermissions uses exclusive write (wx flag) to prevent TOCTOU race');
236
- assert(createFuncBody.indexOf("EEXIST") !== -1,
237
- 'createDefaultPermissions handles EEXIST from concurrent creation');
238
- assert(createFuncBody.indexOf("loadPermissions(true)") !== -1,
239
- 'createDefaultPermissions reloads permissions after successful file creation');
240
- } else {
241
- failed++;
242
- console.log(' FAIL: could not extract createDefaultPermissions function body');
243
- }
244
-
245
- console.log('');
246
-
247
- // =============================================
248
- // Verify auto-bootstrap in config setup
249
- // =============================================
250
-
251
- console.log('Auto-bootstrap in config setup:');
252
-
253
- // Find the config setup block in index.js
254
- const configSetupStart = indexSrc.indexOf("// Set custom config path if provided");
255
- const configSetupEnd = indexSrc.indexOf("// Handle skill management commands");
256
- if (configSetupStart !== -1 && configSetupEnd !== -1) {
257
- const configSetup = indexSrc.substring(configSetupStart, configSetupEnd);
258
-
259
- const setPaveHomeIdx = configSetup.indexOf('skillManager.setPaveHome(configPath)');
260
- const setConfigDirIdx = configSetup.indexOf('Permission.setConfigDir(configPath)');
261
- const createDefaultIdx = configSetup.indexOf('Permission.createDefaultPermissions()');
262
-
263
- assert(setPaveHomeIdx !== -1, 'config setup calls skillManager.setPaveHome');
264
- assert(setConfigDirIdx !== -1, 'config setup calls Permission.setConfigDir');
265
- assert(createDefaultIdx !== -1, 'config setup calls Permission.createDefaultPermissions');
266
-
267
- // Verify order: setConfigDir before createDefaultPermissions
268
- if (setConfigDirIdx !== -1 && createDefaultIdx !== -1) {
269
- assert(setConfigDirIdx < createDefaultIdx,
270
- 'setConfigDir is called before createDefaultPermissions');
271
- }
272
-
273
- // Verify agent init skip
274
- assert(configSetup.indexOf("isAgentInit") !== -1,
275
- 'config setup skips auto-bootstrap for agent init');
276
- assert(configSetup.indexOf("!isAgentInit") !== -1,
277
- 'auto-bootstrap is conditional on NOT being agent init');
278
- } else {
279
- failed++;
280
- console.log(' FAIL: could not find config setup block');
281
- }
282
-
283
- // =============================================
284
- // Summary
285
- // =============================================
286
-
287
- console.log('');
288
- console.log('========================================');
289
- console.log('Results: ' + passed + ' passed, ' + failed + ' failed, ' + (passed + failed) + ' total');
290
- console.log('');
291
-
292
- if (failed > 0) {
293
- console.log('Some tests FAILED.');
294
- process.exitCode = 1;
295
- } else {
296
- console.log('All tests passed!');
297
- }
package/test/run-all.js DELETED
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Run all tests for the pave package
4
- * Usage: node test/run-all.js
5
- */
6
-
7
- const { execSync } = require('child_process');
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- const testDir = __dirname;
12
- const testFiles = fs.readdirSync(testDir)
13
- .filter((f) => f.endsWith('.test.js'))
14
- .sort();
15
-
16
- console.log('🧪 Running PAVE Test Suite\n');
17
-
18
- let totalTests = 0;
19
- let passedTests = 0;
20
- let failedTests = 0;
21
-
22
- for (const testFile of testFiles) {
23
- const testPath = path.join(testDir, testFile);
24
- console.log(`📋 Running ${testFile}...`);
25
-
26
- try {
27
- const output = execSync(`node "${testPath}"`, {
28
- encoding: 'utf8',
29
- cwd: path.join(__dirname, '..'),
30
- });
31
-
32
- // Count test results from output
33
- const lines = output.split('\n');
34
- const passed = lines.filter((l) => l.startsWith('✅')).length;
35
- const failed = lines.filter((l) => l.startsWith('❌')).length;
36
-
37
- totalTests += passed + failed;
38
- passedTests += passed;
39
- failedTests += failed;
40
-
41
- if (failed > 0) {
42
- console.log(output);
43
- } else {
44
- console.log(` ✅ ${passed} tests passed\n`);
45
- }
46
- } catch (error) {
47
- console.log(` ❌ Test suite failed to run: ${error.message}\n`);
48
- failedTests++;
49
- }
50
- }
51
-
52
- console.log('=' * 60);
53
- console.log('📊 Final Test Results:');
54
- console.log(` Total: ${totalTests} tests`);
55
- console.log(` Passed: ${passedTests} tests`);
56
- console.log(` Failed: ${failedTests} tests`);
57
-
58
- if (failedTests > 0) {
59
- console.log(`\n❌ ${failedTests} test(s) failed. Please review and fix the issues above.`);
60
- process.exit(1);
61
- } else {
62
- console.log(`\n✅ All tests passed! The tool output functionality is working correctly. 🎉`);
63
- process.exit(0);
64
- }
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Standalone runner for config-flag behavioral tests (issue #126).
4
- * Run directly with: node src/packages/pave/test/run-config-flag.js
5
- *
6
- * Tests parseArgs() and setPaveHome() behavioral correctness.
7
- */
8
-
9
- let passed = 0;
10
- let failed = 0;
11
-
12
- function assert(cond, msg) {
13
- if (cond) {
14
- passed++;
15
- console.log('\u2705 ' + msg);
16
- } else {
17
- failed++;
18
- console.log('\u274c ' + msg);
19
- }
20
- }
21
-
22
- const path = require('path');
23
- const os = require('os');
24
-
25
- const projDir = path.resolve(__dirname, '../../../..');
26
- const tmpPave = path.join(os.tmpdir(), '.pave');
27
- const tmpTestCfg = path.join(os.tmpdir(), 'test-pave-cfg');
28
-
29
- // ---- 1. parseArgs behavioral tests ----
30
-
31
- // pave list --config ./.pave
32
- const r1 = parseArgs(['list', '--config', './.pave']);
33
- assert(r1.command === 'list', 'parseArgs: list command detected');
34
- assert(r1.config === './.pave', 'parseArgs: --config value parsed');
35
- assert(r1.commandArgs.indexOf('./.pave') === -1, 'parseArgs: ./.pave NOT in commandArgs');
36
- assert(r1.commandArgs.length === 0, 'parseArgs: commandArgs empty for list --config');
37
-
38
- // pave install gmail --config <tmpdir>/.pave
39
- const r2 = parseArgs(['install', 'gmail', '--config', tmpPave]);
40
- assert(r2.config === tmpPave, 'parseArgs: --config value parsed for install');
41
- assert(r2.commandArgs.indexOf(tmpPave) === -1, 'parseArgs: config path NOT in install commandArgs');
42
- assert(r2.commandArgs.length === 1 && r2.commandArgs[0] === 'gmail',
43
- 'parseArgs: install commandArgs = [gmail]');
44
-
45
- // pave remove my-skill --config ./.pave
46
- const r3 = parseArgs(['remove', 'my-skill', '--config', './.pave']);
47
- assert(r3.commandArgs.indexOf('./.pave') === -1, 'parseArgs: ./.pave NOT in remove commandArgs');
48
- assert(r3.commandArgs[0] === 'my-skill', 'parseArgs: remove commandArgs = [my-skill]');
49
-
50
- // pave search query --category tools --config /custom
51
- const r4 = parseArgs(['search', 'query', '--category', 'tools', '--config', '/custom']);
52
- assert(r4.commandArgs.indexOf('/custom') === -1, 'parseArgs: /custom NOT in search commandArgs');
53
- assert(r4.commandArgs.indexOf('tools') === -1, 'parseArgs: --category value NOT in commandArgs');
54
- assert(r4.commandArgs[0] === 'query', 'parseArgs: search commandArgs = [query]');
55
-
56
- // pave update --config ./.pave
57
- const r5 = parseArgs(['update', '--config', './.pave']);
58
- assert(r5.commandArgs.length === 0, 'parseArgs: update commandArgs empty');
59
-
60
- // --bind value doesn't leak
61
- const r6 = parseArgs(['list', '--bind', '0.0.0.0', '--config', './.pave']);
62
- assert(r6.commandArgs.indexOf('0.0.0.0') === -1, 'parseArgs: --bind value NOT in commandArgs');
63
-
64
- // -b short form doesn't leak
65
- const r7 = parseArgs(['list', '-b', '0.0.0.0']);
66
- assert(r7.commandArgs.indexOf('0.0.0.0') === -1, 'parseArgs: -b value NOT in commandArgs');
67
-
68
- // --shell doesn't leak
69
- const r8 = parseArgs(['list', '--shell', '/bin/bash']);
70
- assert(r8.commandArgs.indexOf('/bin/bash') === -1, 'parseArgs: --shell value NOT in commandArgs');
71
-
72
- // --last doesn't leak
73
- const r9 = parseArgs(['list', '--last', '5']);
74
- assert(r9.commandArgs.indexOf('5') === -1, 'parseArgs: --last value NOT in commandArgs');
75
-
76
- // --export doesn't leak
77
- const r10 = parseArgs(['list', '--export', 'out.md']);
78
- assert(r10.commandArgs.indexOf('out.md') === -1, 'parseArgs: --export value NOT in commandArgs');
79
-
80
- // Multiple skills
81
- const r11 = parseArgs(['install', 'a', 'b', '--config', './.pave']);
82
- assert(r11.commandArgs.length === 2 && r11.commandArgs[0] === 'a' && r11.commandArgs[1] === 'b',
83
- 'parseArgs: install a b commandArgs = [a, b]');
84
-
85
- // Optional-value flags: --ish-mode and --memory-monitor
86
- const r13 = parseArgs(['list', '--ish-mode', 'true']);
87
- assert(r13.commandArgs.indexOf('true') === -1, 'parseArgs: --ish-mode value NOT in commandArgs');
88
- assert(r13.commandArgs.length === 0, 'parseArgs: list --ish-mode true -> empty commandArgs');
89
-
90
- const r14 = parseArgs(['list', '--memory-monitor', 'off']);
91
- assert(r14.commandArgs.indexOf('off') === -1, 'parseArgs: --memory-monitor value NOT in commandArgs');
92
- assert(r14.commandArgs.length === 0, 'parseArgs: list --memory-monitor off -> empty commandArgs');
93
-
94
- // --ish-mode without a recognized value should not consume next positional token
95
- const r15 = parseArgs(['install', '--ish-mode', 'gmail']);
96
- assert(r15.commandArgs.length === 1 && r15.commandArgs[0] === 'gmail',
97
- 'parseArgs: --ish-mode without value -> gmail stays in commandArgs');
98
-
99
- // --config at end (missing value) - must run in subprocess since parseArgs calls process.exit(1)
100
- const fs = require('fs');
101
- const child_process = require('child_process');
102
- const parseArgs = require('../lib/args-parser').parseArgs;
103
-
104
- const tmpMissing = path.join(projDir, '_test_tmp_missing_' + Date.now() + '.js');
105
- const configMissingScript = [
106
- 'var p = require("./src/packages/pave/lib/args-parser").parseArgs;',
107
- 'p(["list", "--config"]);',
108
- ].join('\n');
109
- try {
110
- fs.writeFileSync(tmpMissing, configMissingScript, 'utf8');
111
- const child = child_process.spawnSync(process.execPath, [tmpMissing], {
112
- cwd: projDir, encoding: 'utf8', timeout: 5000, stdio: 'pipe',
113
- });
114
- assert(child.status !== 0, 'parseArgs: --config at end -> non-zero exit in subprocess');
115
- const output = (child.stderr || '') + (child.stdout || '');
116
- if (output.length > 0) {
117
- assert(output.indexOf('--config') !== -1, 'parseArgs: --config at end -> error mentions --config');
118
- }
119
- } catch (e) {
120
- assert(false, 'parseArgs: --config at end -> subprocess test failed: ' + e.message);
121
- } finally {
122
- try { fs.unlinkSync(tmpMissing); } catch (e2) {}
123
- }
124
-
125
- // ---- 2. setPaveHome / getPaths behavioral tests ----
126
- const sm = require('../lib/skill-manager');
127
- const mp = require('../lib/marketplace');
128
-
129
- const originalHome = sm.getPaveHome();
130
-
131
- sm.setPaveHome(tmpTestCfg);
132
- mp.setPaveHome(tmpTestCfg);
133
-
134
- const p = sm.getPaths();
135
- const c = mp.getCachePaths();
136
-
137
- assert(p.skillsDir.indexOf(tmpTestCfg) !== -1, 'setPaveHome: skillsDir uses custom path');
138
- assert(p.lockFile.indexOf(tmpTestCfg) !== -1, 'setPaveHome: lockFile uses custom path');
139
- assert(p.permissionsFile.indexOf(tmpTestCfg) !== -1, 'setPaveHome: permissionsFile uses custom path');
140
- assert(c.cacheDir.indexOf(tmpTestCfg) !== -1, 'setPaveHome: cacheDir uses custom path');
141
- assert(c.cacheFile.indexOf(tmpTestCfg) !== -1, 'setPaveHome: cacheFile uses custom path');
142
-
143
- // Verify deprecated getters exist and delegate to dynamic functions
144
- assert(typeof sm.SKILLS_DIR === 'string', 'export: SKILLS_DIR is deprecated getter (string)');
145
- assert(typeof sm.LOCK_FILE === 'string', 'export: LOCK_FILE is deprecated getter (string)');
146
- assert(typeof mp.CACHE_DIR === 'string', 'export: CACHE_DIR is deprecated getter (string)');
147
- assert(typeof mp.CACHE_FILE === 'string', 'export: CACHE_FILE is deprecated getter (string)');
148
- assert(sm.SKILLS_DIR === sm.getPaths().skillsDir, 'export: SKILLS_DIR delegates to getPaths()');
149
- assert(sm.LOCK_FILE === sm.getPaths().lockFile, 'export: LOCK_FILE delegates to getPaths()');
150
- assert(mp.CACHE_DIR === mp.getCachePaths().cacheDir, 'export: CACHE_DIR delegates to getCachePaths()');
151
- assert(mp.CACHE_FILE === mp.getCachePaths().cacheFile, 'export: CACHE_FILE delegates to getCachePaths()');
152
-
153
- // Restore
154
- sm.setPaveHome(originalHome);
155
- mp.setPaveHome(originalHome);
156
-
157
- console.log('');
158
- console.log(passed + ' passed, ' + failed + ' failed');
159
- if (failed > 0) { process.exitCode = 1; throw new Error(failed + ' test(s) failed'); }
@@ -1,82 +0,0 @@
1
- #!/usr/bin/env node
2
- // Standalone test runner for cursor drift fix (issue #112).
3
- // Source pattern verification via grep + cursor math tests.
4
- //
5
- // Blessed wraps when line.length > width (not >=), so at exact multiples
6
- // the cursor stays at col=wrapWidth on the same line.
7
- const child_process = require("child_process");
8
- const path = require("path");
9
-
10
- let passed = 0;
11
- let failed = 0;
12
- let skipped = 0;
13
- function assert(cond, msg) {
14
- if (cond) { passed++; console.log("\u2705 " + msg); }
15
- else { failed++; console.log("\u274c " + msg); }
16
- }
17
- function skip(msg) { skipped++; console.log("SKIP: " + msg); }
18
- const tuiPath = path.resolve(__dirname, "../../tui/index.js");
19
- function grepCount(pat, file) {
20
- try {
21
- const cmd = "grep -F -c " + JSON.stringify(pat) + " " + JSON.stringify(file);
22
- return parseInt(child_process.execSync(cmd, { encoding: "utf8" }).trim(), 10);
23
- } catch (e) {
24
- if (e.stdout) {
25
- const n = parseInt(String(e.stdout).trim(), 10);
26
- if (!isNaN(n)) return n;
27
- }
28
- return -1;
29
- }
30
- }
31
- // Source verification  the fix introduces wrapWidth = widgetWidth - 1
32
- const c = grepCount("wrapWidth = widgetWidth - 1", tuiPath);
33
- if (c < 0) { skip("source grep not available in sandbox"); }
34
- else {
35
- assert(c >= 3, "source: 3+ wrapWidth = widgetWidth - 1 locations (found " + c + ")");
36
- // Ensure the old pattern (input.iwidth - 1 baked into widgetWidth) is gone
37
- const old = grepCount("input.iwidth - 1", tuiPath);
38
- assert(old === 0, "source: no old iwidth - 1 in widgetWidth calc (found " + old + ")");
39
- // Fallback value should be 80 (widgetWidth) since wrapWidth = 80-1 = 79
40
- const fb = grepCount(": 80", tuiPath);
41
- assert(fb >= 1, "source: fallback 80 in _typeScroll widgetWidth (found " + fb + ")");
42
- // Check for textarea margin comments
43
- const cm = grepCount("textarea", tuiPath);
44
- assert(cm >= 1, "source: textarea wrap margin comments (found " + cm + ")");
45
- // Check for (cursorCol - 1) pattern (blessed wraps on > not >=)
46
- const cc = grepCount("cursorCol - 1", tuiPath);
47
- assert(cc >= 2, "source: cursorCol - 1 pattern for blessed > boundary (found " + cc + ")");
48
- // Check selection: per-char wrap uses >= wrapWidth, after-last-char uses > wrapWidth
49
- const gte = grepCount(">= wrapWidth", tuiPath);
50
- assert(gte >= 1, "source: >= wrapWidth for per-char wrap (found " + gte + ")");
51
- const gt = grepCount("> wrapWidth", tuiPath);
52
- assert(gt >= 1, "source: > wrapWidth for after-last-char cursor (found " + gt + ")");
53
- }
54
- // Cursor math simulation (model Blessed _wrapContent: no wrap on exact width)
55
- function sim(len, w) {
56
- if (len <= 0) return { wraps: 0, col: 0 };
57
- const wraps = Math.floor((len - 1) / w);
58
- const col = ((len - 1) % w) + 1;
59
- return { wraps, col };
60
- }
61
- const W = 39;
62
- assert(sim(0, W).col === 0, "math: empty -> col 0");
63
- assert(sim(20, W).col === 20 && sim(20, W).wraps === 0, "math: 20 -> col 20");
64
- assert(sim(38, W).col === 38 && sim(38, W).wraps === 0, "math: 38 -> col 38 (end of page 1)");
65
- assert(sim(39, W).col === W && sim(39, W).wraps === 0, "math: 39 -> col 39 (no wrap, matches blessed)");
66
- assert(sim(40, W).col === 1 && sim(40, W).wraps === 1, "math: 40 -> col 1");
67
- assert(sim(78, W).col === W && sim(78, W).wraps === 1, "math: 78 -> end of page 2");
68
- assert(sim(80, W).col === 2 && sim(80, W).wraps === 2, "math: 80 -> col 2");
69
- // Verify no drift at each page boundary (exact multiples stay at col=W)
70
- for (let p = 1; p <= 10; p++) { const ch = p * W; const r = sim(ch, W); assert(r.col === W && r.wraps === p - 1, "math: no drift page " + p); }
71
- // Verify old code drifts but new code does not
72
- const oldW = 40;
73
- for (let p = 1; p <= 5; p++) {
74
- const ch = p * 39;
75
- const oldPos = sim(ch, oldW);
76
- const newPos = sim(ch, W);
77
- assert(newPos.col === W, "no-drift: new code col=" + W + " at " + ch + " chars");
78
- if (p > 1) assert(oldPos.col !== W, "drift: old code col!=" + W + " at " + ch + " chars (col=" + oldPos.col + ")");
79
- }
80
- console.log("");
81
- console.log(passed + " passed, " + failed + " failed" + (skipped ? ", " + skipped + " skipped" : ""));
82
- if (failed > 0) { process.exitCode = 1; throw new Error(failed + " test(s) failed"); }