vectorforce 0.0.0-stage → 0.2.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.
Files changed (37) hide show
  1. package/.agents/agents/AGENTS.md +12 -0
  2. package/.agents/agents/vectorforce-architect.md +35 -0
  3. package/.agents/agents/vectorforce-auditor.md +25 -0
  4. package/.agents/agents/vectorforce-samples-builder.md +30 -0
  5. package/.agents/mcp_config.json +11 -0
  6. package/.codex/config.toml +5 -0
  7. package/AGENTS.md +115 -0
  8. package/LICENSE +21 -0
  9. package/README.md +198 -2
  10. package/bin/vf.js +1159 -0
  11. package/codex.json +14 -0
  12. package/commandcode.json +11 -0
  13. package/musecode.json +14 -0
  14. package/package.json +40 -4
  15. package/src/core/errors.js +45 -0
  16. package/src/core/hash.js +56 -0
  17. package/src/core/types.js +194 -0
  18. package/src/index.js +17 -0
  19. package/src/mcp/harness-config.js +546 -0
  20. package/src/mcp/mcp-server.js +507 -0
  21. package/src/operations/briefing-service.js +313 -0
  22. package/src/operations/checkpoint-service.js +120 -0
  23. package/src/operations/handoff-service.js +343 -0
  24. package/src/operations/project-service.js +132 -0
  25. package/src/operations/reasoning-service.js +356 -0
  26. package/src/policy/authorizer.js +147 -0
  27. package/src/providers/code-index-provider.js +285 -0
  28. package/src/providers/document-provider.js +105 -0
  29. package/src/providers/git-provider.js +98 -0
  30. package/src/providers/llm-provider.js +425 -0
  31. package/src/storage/sqlite-store.js +387 -0
  32. package/src/studio/server.js +764 -0
  33. package/studio/dist/assets/index-C0_eVpV1.js +9 -0
  34. package/studio/dist/assets/index-CxTK-2ty.css +2 -0
  35. package/studio/dist/favicon.svg +1 -0
  36. package/studio/dist/icons.svg +24 -0
  37. package/studio/dist/index.html +17 -0
@@ -0,0 +1,546 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { homedir, tmpdir } from 'node:os';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, realpathSync } from 'node:fs';
5
+
6
+ function isTempWorkspace(workspacePath) {
7
+ if (process.env.VF_NO_GLOBAL_CONFIG === '1' || process.env.NODE_TEST_CONTEXT) return true;
8
+ try {
9
+ const realWs = realpathSync(workspacePath);
10
+ const realTmp = realpathSync(tmpdir());
11
+ return realWs.startsWith(realTmp);
12
+ } catch {
13
+ return workspacePath.startsWith(tmpdir()) || workspacePath.includes('vf-test-') || workspacePath.includes('vf-practical-');
14
+ }
15
+ }
16
+
17
+ export class HarnessConfig {
18
+ /**
19
+ * Get the absolute path to bin/vf.js (cross-platform: macOS, Linux, Windows)
20
+ */
21
+ static getBinPath() {
22
+ return fileURLToPath(new URL('../../bin/vf.js', import.meta.url));
23
+ }
24
+
25
+ /**
26
+ * Generate configuration object for Antigravity.
27
+ */
28
+ static getAntigravityServerEntry() {
29
+ return {
30
+ command: 'node',
31
+ args: [HarnessConfig.getBinPath(), 'mcp']
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Preview changes to Antigravity configuration.
37
+ * @param {string} workspacePath
38
+ * @returns {{ filePath: string, exists: boolean, currentContent: object|null, mergedContent: object }}
39
+ */
40
+ static previewAntigravity(workspacePath) {
41
+ const dir = join(workspacePath, '.agents');
42
+ const filePath = join(dir, 'mcp_config.json');
43
+
44
+ let current = null;
45
+ if (existsSync(filePath)) {
46
+ try {
47
+ current = JSON.parse(readFileSync(filePath, 'utf8'));
48
+ } catch {
49
+ current = {};
50
+ }
51
+ }
52
+
53
+ const merged = {
54
+ ...current,
55
+ mcpServers: {
56
+ ...(current?.mcpServers || {}),
57
+ vectorforce: HarnessConfig.getAntigravityServerEntry()
58
+ }
59
+ };
60
+
61
+ return {
62
+ filePath,
63
+ exists: existsSync(filePath),
64
+ currentContent: current,
65
+ mergedContent: merged
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Install or update Antigravity MCP configuration reversibly.
71
+ * @param {string} workspacePath
72
+ * @returns {string} filePath
73
+ */
74
+ static installAntigravity(workspacePath) {
75
+ const { filePath, mergedContent } = HarnessConfig.previewAntigravity(workspacePath);
76
+ const dir = join(workspacePath, '.agents');
77
+ if (!existsSync(dir)) {
78
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
79
+ }
80
+ writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
81
+
82
+ if (!isTempWorkspace(workspacePath)) {
83
+ try {
84
+ const globalAgyDir = join(homedir(), '.gemini', 'antigravity');
85
+ if (existsSync(globalAgyDir)) {
86
+ const globalMcpPath = join(globalAgyDir, 'mcp_config.json');
87
+ let globalConfig = {};
88
+ if (existsSync(globalMcpPath)) {
89
+ try {
90
+ globalConfig = JSON.parse(readFileSync(globalMcpPath, 'utf8'));
91
+ } catch {
92
+ globalConfig = {};
93
+ }
94
+ }
95
+ globalConfig.mcpServers = {
96
+ ...(globalConfig.mcpServers || {}),
97
+ vectorforce: {
98
+ command: 'node',
99
+ args: [HarnessConfig.getBinPath(), 'mcp', '--workspace', workspacePath],
100
+ env: {
101
+ VF_WORKSPACE: workspacePath,
102
+ VF_DB_PATH: join(workspacePath, '.vectorforce', 'vectorforce.db')
103
+ }
104
+ }
105
+ };
106
+ writeFileSync(globalMcpPath, JSON.stringify(globalConfig, null, 2) + '\n', 'utf8');
107
+ }
108
+ } catch {}
109
+ }
110
+
111
+ return filePath;
112
+ }
113
+
114
+ /**
115
+ * Reversibly uninstall VectorForce from Antigravity configuration.
116
+ * @param {string} workspacePath
117
+ * @returns {boolean} whether removed
118
+ */
119
+ static uninstallAntigravity(workspacePath) {
120
+ const filePath = join(workspacePath, '.agents', 'mcp_config.json');
121
+ if (!existsSync(filePath)) return false;
122
+
123
+ try {
124
+ const config = JSON.parse(readFileSync(filePath, 'utf8'));
125
+ if (config?.mcpServers?.vectorforce) {
126
+ delete config.mcpServers.vectorforce;
127
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
128
+ return true;
129
+ }
130
+ } catch {}
131
+ return false;
132
+ }
133
+
134
+ /**
135
+ * Preview changes to Command Code configuration.
136
+ * @param {string} workspacePath
137
+ */
138
+ /**
139
+ * Generate configuration object for Command Code (.mcp.json).
140
+ */
141
+ static getCommandCodeServerEntry() {
142
+ return {
143
+ type: 'stdio',
144
+ command: 'node',
145
+ args: [HarnessConfig.getBinPath(), 'mcp']
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Preview changes to Command Code configuration.
151
+ * @param {string} workspacePath
152
+ */
153
+ static previewCommandCode(workspacePath) {
154
+ const filePath = join(workspacePath, 'commandcode.json');
155
+ const mcpJsonPath = join(workspacePath, '.mcp.json');
156
+ let current = null;
157
+ if (existsSync(filePath)) {
158
+ try {
159
+ current = JSON.parse(readFileSync(filePath, 'utf8'));
160
+ } catch {
161
+ current = {};
162
+ }
163
+ }
164
+
165
+ const merged = {
166
+ ...current,
167
+ mcpServers: {
168
+ ...(current?.mcpServers || {}),
169
+ vectorforce: HarnessConfig.getAntigravityServerEntry()
170
+ }
171
+ };
172
+
173
+ let mcpCurrent = null;
174
+ if (existsSync(mcpJsonPath)) {
175
+ try {
176
+ mcpCurrent = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
177
+ } catch {
178
+ mcpCurrent = {};
179
+ }
180
+ }
181
+
182
+ const mcpMerged = {
183
+ ...mcpCurrent,
184
+ mcpServers: {
185
+ ...(mcpCurrent?.mcpServers || {}),
186
+ vectorforce: HarnessConfig.getCommandCodeServerEntry()
187
+ }
188
+ };
189
+
190
+ const cliCommand = `cmd mcp add --transport stdio vectorforce -- node ${HarnessConfig.getBinPath()} mcp`;
191
+
192
+ return {
193
+ filePath,
194
+ mcpJsonPath,
195
+ exists: existsSync(filePath),
196
+ cliCommand,
197
+ currentContent: current,
198
+ mergedContent: merged,
199
+ mcpMerged
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Install Command Code configuration reversibly.
205
+ * Writes both .mcp.json (native Command Code v1.84.0 format) and commandcode.json.
206
+ * @param {string} workspacePath
207
+ */
208
+ static installCommandCode(workspacePath) {
209
+ const { filePath, mcpJsonPath, mergedContent, mcpMerged } = HarnessConfig.previewCommandCode(workspacePath);
210
+ writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
211
+ writeFileSync(mcpJsonPath, JSON.stringify(mcpMerged, null, 2) + '\n', 'utf8');
212
+ return filePath;
213
+ }
214
+
215
+ /**
216
+ * Uninstall VectorForce from Command Code configuration.
217
+ * @param {string} workspacePath
218
+ */
219
+ static uninstallCommandCode(workspacePath) {
220
+ const filePath = join(workspacePath, 'commandcode.json');
221
+ const mcpJsonPath = join(workspacePath, '.mcp.json');
222
+ let removed = false;
223
+
224
+ if (existsSync(filePath)) {
225
+ try {
226
+ const config = JSON.parse(readFileSync(filePath, 'utf8'));
227
+ if (config?.mcpServers?.vectorforce) {
228
+ delete config.mcpServers.vectorforce;
229
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
230
+ removed = true;
231
+ }
232
+ } catch {}
233
+ }
234
+
235
+ if (existsSync(mcpJsonPath)) {
236
+ try {
237
+ const mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
238
+ if (mcpConfig?.mcpServers?.vectorforce) {
239
+ delete mcpConfig.mcpServers.vectorforce;
240
+ writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
241
+ removed = true;
242
+ }
243
+ } catch {}
244
+ }
245
+
246
+ return removed;
247
+ }
248
+
249
+ /**
250
+ * Generate configuration object for Muse Code.
251
+ */
252
+ static getMuseCodeServerEntry() {
253
+ return {
254
+ command: 'node',
255
+ args: [HarnessConfig.getBinPath(), 'mcp'],
256
+ env: {
257
+ VF_HARNESS: 'muse-code'
258
+ }
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Preview changes to Muse Code configuration.
264
+ * @param {string} workspacePath
265
+ */
266
+ static previewMuseCode(workspacePath) {
267
+ const dir = join(workspacePath, '.muse');
268
+ const filePath = join(dir, 'mcp_config.json');
269
+ const museJsonPath = join(workspacePath, 'musecode.json');
270
+
271
+ let current = null;
272
+ if (existsSync(filePath)) {
273
+ try {
274
+ current = JSON.parse(readFileSync(filePath, 'utf8'));
275
+ } catch {
276
+ current = {};
277
+ }
278
+ }
279
+
280
+ const merged = {
281
+ ...current,
282
+ mcpServers: {
283
+ ...(current?.mcpServers || {}),
284
+ vectorforce: HarnessConfig.getMuseCodeServerEntry()
285
+ }
286
+ };
287
+
288
+ return {
289
+ filePath,
290
+ museJsonPath,
291
+ exists: existsSync(filePath),
292
+ currentContent: current,
293
+ mergedContent: merged
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Install Muse Code MCP configuration reversibly.
299
+ * @param {string} workspacePath
300
+ */
301
+ static installMuseCode(workspacePath) {
302
+ const { filePath, museJsonPath, mergedContent } = HarnessConfig.previewMuseCode(workspacePath);
303
+ const dir = join(workspacePath, '.muse');
304
+ if (!existsSync(dir)) {
305
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
306
+ }
307
+ writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
308
+ writeFileSync(museJsonPath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
309
+
310
+ if (!isTempWorkspace(workspacePath)) {
311
+ try {
312
+ const globalMuseDir = join(homedir(), '.config', 'muse');
313
+ const globalSettingsPath = join(globalMuseDir, 'settings.json');
314
+ if (existsSync(globalSettingsPath)) {
315
+ const settings = JSON.parse(readFileSync(globalSettingsPath, 'utf8'));
316
+ settings.mcp_servers = {
317
+ ...(settings.mcp_servers || {}),
318
+ vectorforce: {
319
+ transport: 'stdio',
320
+ command: 'node',
321
+ args: [HarnessConfig.getBinPath(), 'mcp'],
322
+ env: {
323
+ VF_HARNESS: 'muse-code',
324
+ VF_WORKSPACE: workspacePath
325
+ },
326
+ mode: 'optional'
327
+ }
328
+ };
329
+ delete settings.mcpServers;
330
+ writeFileSync(globalSettingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
331
+ }
332
+ } catch {}
333
+ }
334
+
335
+ return filePath;
336
+ }
337
+
338
+ /**
339
+ * Uninstall VectorForce from Muse Code configuration reversibly.
340
+ * @param {string} workspacePath
341
+ */
342
+ static uninstallMuseCode(workspacePath) {
343
+ const filePath = join(workspacePath, '.muse', 'mcp_config.json');
344
+ const museJsonPath = join(workspacePath, 'musecode.json');
345
+ let removed = false;
346
+
347
+ if (existsSync(filePath)) {
348
+ try {
349
+ const config = JSON.parse(readFileSync(filePath, 'utf8'));
350
+ if (config?.mcpServers?.vectorforce) {
351
+ delete config.mcpServers.vectorforce;
352
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
353
+ removed = true;
354
+ }
355
+ } catch {}
356
+ }
357
+
358
+ if (existsSync(museJsonPath)) {
359
+ try {
360
+ const config = JSON.parse(readFileSync(museJsonPath, 'utf8'));
361
+ if (config?.mcpServers?.vectorforce) {
362
+ delete config.mcpServers.vectorforce;
363
+ writeFileSync(museJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
364
+ removed = true;
365
+ }
366
+ } catch {}
367
+ }
368
+
369
+ if (!isTempWorkspace(workspacePath)) {
370
+ try {
371
+ const globalSettingsPath = join(homedir(), '.config', 'muse', 'settings.json');
372
+ if (existsSync(globalSettingsPath)) {
373
+ const settings = JSON.parse(readFileSync(globalSettingsPath, 'utf8'));
374
+ if (settings?.mcp_servers?.vectorforce) {
375
+ delete settings.mcp_servers.vectorforce;
376
+ writeFileSync(globalSettingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
377
+ }
378
+ }
379
+ } catch {}
380
+ }
381
+
382
+ return removed;
383
+ }
384
+
385
+ /**
386
+ * Generate configuration object for Codex.
387
+ */
388
+ static getCodexServerEntry() {
389
+ return {
390
+ command: 'node',
391
+ args: [HarnessConfig.getBinPath(), 'mcp'],
392
+ env: {
393
+ VF_HARNESS: 'codex'
394
+ }
395
+ };
396
+ }
397
+
398
+ /**
399
+ * Format or merge TOML configuration for Codex MCP server.
400
+ * @param {string} existingToml
401
+ * @returns {string} merged TOML
402
+ */
403
+ static _mergeCodexToml(existingToml) {
404
+ const binPath = HarnessConfig.getBinPath().replace(/\\/g, '\\\\');
405
+ const serverBlock = `[mcp_servers.vectorforce]\ncommand = "node"\nargs = ["${binPath}", "mcp"]\n[mcp_servers.vectorforce.env]\nVF_HARNESS = "codex"\n`;
406
+
407
+ if (!existingToml || !existingToml.trim()) {
408
+ return serverBlock;
409
+ }
410
+
411
+ const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
412
+ if (sectionRegex.test(existingToml)) {
413
+ return existingToml.replace(sectionRegex, serverBlock.trimEnd()) + '\n';
414
+ }
415
+
416
+ return existingToml.trimEnd() + '\n\n' + serverBlock;
417
+ }
418
+
419
+ /**
420
+ * Preview changes to Codex configuration.
421
+ * @param {string} workspacePath
422
+ */
423
+ static previewCodex(workspacePath) {
424
+ const dir = join(workspacePath, '.codex');
425
+ const filePath = join(dir, 'config.toml');
426
+ const codexJsonPath = join(workspacePath, 'codex.json');
427
+
428
+ let currentToml = null;
429
+ if (existsSync(filePath)) {
430
+ try {
431
+ currentToml = readFileSync(filePath, 'utf8');
432
+ } catch {
433
+ currentToml = null;
434
+ }
435
+ }
436
+
437
+ let currentJson = null;
438
+ if (existsSync(codexJsonPath)) {
439
+ try {
440
+ currentJson = JSON.parse(readFileSync(codexJsonPath, 'utf8'));
441
+ } catch {
442
+ currentJson = {};
443
+ }
444
+ }
445
+
446
+ const mergedToml = HarnessConfig._mergeCodexToml(currentToml);
447
+ const mergedJson = {
448
+ ...currentJson,
449
+ mcpServers: {
450
+ ...(currentJson?.mcpServers || {}),
451
+ vectorforce: HarnessConfig.getCodexServerEntry()
452
+ }
453
+ };
454
+
455
+ const cliCommand = `codex mcp add vectorforce -- node ${HarnessConfig.getBinPath()} mcp`;
456
+
457
+ return {
458
+ filePath,
459
+ codexJsonPath,
460
+ exists: existsSync(filePath) || existsSync(codexJsonPath),
461
+ cliCommand,
462
+ currentContent: currentToml,
463
+ mergedContent: mergedToml,
464
+ mergedJson
465
+ };
466
+ }
467
+
468
+ /**
469
+ * Install Codex MCP configuration reversibly.
470
+ * Writes both .codex/config.toml (standard Codex CLI) and codex.json.
471
+ * @param {string} workspacePath
472
+ * @returns {string} filePath
473
+ */
474
+ static installCodex(workspacePath) {
475
+ const { filePath, codexJsonPath, mergedContent, mergedJson } = HarnessConfig.previewCodex(workspacePath);
476
+ const dir = join(workspacePath, '.codex');
477
+ if (!existsSync(dir)) {
478
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
479
+ }
480
+ writeFileSync(filePath, mergedContent, 'utf8');
481
+ writeFileSync(codexJsonPath, JSON.stringify(mergedJson, null, 2) + '\n', 'utf8');
482
+
483
+ if (!isTempWorkspace(workspacePath)) {
484
+ try {
485
+ const globalCodex = join(homedir(), '.codex');
486
+ if (existsSync(globalCodex)) {
487
+ const globalTomlPath = join(globalCodex, 'config.toml');
488
+ const existingGlobal = existsSync(globalTomlPath) ? readFileSync(globalTomlPath, 'utf8') : '';
489
+ const updatedGlobal = HarnessConfig._mergeCodexToml(existingGlobal);
490
+ writeFileSync(globalTomlPath, updatedGlobal, 'utf8');
491
+ }
492
+ } catch {}
493
+ }
494
+
495
+ return filePath;
496
+ }
497
+
498
+ /**
499
+ * Uninstall VectorForce from Codex configuration reversibly.
500
+ * @param {string} workspacePath
501
+ * @returns {boolean} whether removed
502
+ */
503
+ static uninstallCodex(workspacePath) {
504
+ const filePath = join(workspacePath, '.codex', 'config.toml');
505
+ const codexJsonPath = join(workspacePath, 'codex.json');
506
+ let removed = false;
507
+
508
+ if (existsSync(filePath)) {
509
+ try {
510
+ const toml = readFileSync(filePath, 'utf8');
511
+ const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
512
+ if (sectionRegex.test(toml)) {
513
+ const cleaned = toml.replace(sectionRegex, '').trim();
514
+ writeFileSync(filePath, cleaned ? cleaned + '\n' : '', 'utf8');
515
+ removed = true;
516
+ }
517
+ } catch {}
518
+ }
519
+
520
+ if (existsSync(codexJsonPath)) {
521
+ try {
522
+ const config = JSON.parse(readFileSync(codexJsonPath, 'utf8'));
523
+ if (config?.mcpServers?.vectorforce) {
524
+ delete config.mcpServers.vectorforce;
525
+ writeFileSync(codexJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
526
+ removed = true;
527
+ }
528
+ } catch {}
529
+ }
530
+
531
+ try {
532
+ const globalTomlPath = join(homedir(), '.codex', 'config.toml');
533
+ if (existsSync(globalTomlPath)) {
534
+ const toml = readFileSync(globalTomlPath, 'utf8');
535
+ const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
536
+ if (sectionRegex.test(toml)) {
537
+ const cleaned = toml.replace(sectionRegex, '').trim();
538
+ writeFileSync(globalTomlPath, cleaned ? cleaned + '\n' : '', 'utf8');
539
+ removed = true;
540
+ }
541
+ }
542
+ } catch {}
543
+
544
+ return removed;
545
+ }
546
+ }