vectorforce 0.0.0-stage ā 0.1.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/.agents/mcp_config.json +17 -0
- package/.codex/config.toml +8 -0
- package/AGENTS.md +43 -0
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/bin/vf.js +756 -0
- package/codex.json +14 -0
- package/commandcode.json +11 -0
- package/musecode.json +18 -0
- package/package.json +39 -4
- package/src/core/errors.js +45 -0
- package/src/core/hash.js +56 -0
- package/src/core/types.js +194 -0
- package/src/index.js +17 -0
- package/src/mcp/harness-config.js +498 -0
- package/src/mcp/mcp-server.js +507 -0
- package/src/operations/briefing-service.js +313 -0
- package/src/operations/checkpoint-service.js +120 -0
- package/src/operations/handoff-service.js +343 -0
- package/src/operations/project-service.js +132 -0
- package/src/operations/reasoning-service.js +356 -0
- package/src/policy/authorizer.js +147 -0
- package/src/providers/code-index-provider.js +285 -0
- package/src/providers/document-provider.js +105 -0
- package/src/providers/git-provider.js +98 -0
- package/src/providers/llm-provider.js +410 -0
- package/src/storage/sqlite-store.js +387 -0
- package/src/studio/server.js +456 -0
- package/studio/dist/assets/index-DTKNBk3V.css +2 -0
- package/studio/dist/assets/index-X-wHcJ6r.js +9 -0
- package/studio/dist/favicon.svg +1 -0
- package/studio/dist/icons.svg +24 -0
- package/studio/dist/index.html +17 -0
package/bin/vf.js
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { resolve, join, dirname } from 'node:path';
|
|
5
|
+
import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import {
|
|
7
|
+
SqliteStore,
|
|
8
|
+
ProjectService,
|
|
9
|
+
CheckpointService,
|
|
10
|
+
BriefingService,
|
|
11
|
+
HandoffService,
|
|
12
|
+
ReasoningService,
|
|
13
|
+
LlmProvider,
|
|
14
|
+
CodeIndexProvider,
|
|
15
|
+
resolveLlmConfig,
|
|
16
|
+
McpServer,
|
|
17
|
+
HarnessConfig,
|
|
18
|
+
createStudioServer,
|
|
19
|
+
VectorForceError
|
|
20
|
+
} from '../src/index.js';
|
|
21
|
+
|
|
22
|
+
function getScriptWorkspace() {
|
|
23
|
+
try {
|
|
24
|
+
const scriptDir = dirname(resolve(process.argv[1] || ''));
|
|
25
|
+
let curr = scriptDir;
|
|
26
|
+
while (curr && curr !== '/' && curr !== homedir()) {
|
|
27
|
+
if (existsSync(join(curr, '.vectorforce', 'vectorforce.db')) || existsSync(join(curr, '.git'))) {
|
|
28
|
+
return curr;
|
|
29
|
+
}
|
|
30
|
+
const parent = dirname(curr);
|
|
31
|
+
if (parent === curr) break;
|
|
32
|
+
curr = parent;
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getWorkspacePath(startDir = process.cwd()) {
|
|
39
|
+
if (process.env.VF_WORKSPACE) return resolve(process.env.VF_WORKSPACE);
|
|
40
|
+
|
|
41
|
+
// If startDir is root or homedir, check if the script location belongs to a VectorForce workspace first
|
|
42
|
+
if (startDir === '/' || startDir === homedir()) {
|
|
43
|
+
const scriptWs = getScriptWorkspace();
|
|
44
|
+
if (scriptWs) return scriptWs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let curr = resolve(startDir);
|
|
48
|
+
while (true) {
|
|
49
|
+
// homedir can contain ~/.vectorforce as a global fallback directory; do not treat homedir as a project workspace unless it is a git repo
|
|
50
|
+
if (curr !== homedir() && curr !== '/') {
|
|
51
|
+
if (existsSync(join(curr, '.vectorforce')) || existsSync(join(curr, '.git'))) {
|
|
52
|
+
return curr;
|
|
53
|
+
}
|
|
54
|
+
} else if (existsSync(join(curr, '.git'))) {
|
|
55
|
+
return curr;
|
|
56
|
+
}
|
|
57
|
+
const parent = dirname(curr);
|
|
58
|
+
if (parent === curr) break;
|
|
59
|
+
curr = parent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return resolve(startDir);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getDbPath(startDir = process.cwd()) {
|
|
66
|
+
if (process.env.VF_DB_PATH) return process.env.VF_DB_PATH;
|
|
67
|
+
|
|
68
|
+
// If startDir is root or homedir, check if the script location belongs to a VectorForce workspace first
|
|
69
|
+
if (startDir === '/' || startDir === homedir()) {
|
|
70
|
+
const scriptWs = getScriptWorkspace();
|
|
71
|
+
if (scriptWs) {
|
|
72
|
+
const dbCandidate = join(scriptWs, '.vectorforce', 'vectorforce.db');
|
|
73
|
+
if (existsSync(dbCandidate)) return dbCandidate;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 1. Search up from current directory to root: look for .vectorforce/vectorforce.db or .git
|
|
78
|
+
let curr = resolve(startDir);
|
|
79
|
+
while (true) {
|
|
80
|
+
if (curr !== homedir() && curr !== '/') {
|
|
81
|
+
const localDb = join(curr, '.vectorforce', 'vectorforce.db');
|
|
82
|
+
if (existsSync(localDb)) {
|
|
83
|
+
return localDb;
|
|
84
|
+
}
|
|
85
|
+
const localDir = join(curr, '.vectorforce');
|
|
86
|
+
if (existsSync(localDir)) {
|
|
87
|
+
return localDb;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (existsSync(join(curr, '.git'))) {
|
|
91
|
+
const gitLocalDir = join(curr, '.vectorforce');
|
|
92
|
+
mkdirSync(gitLocalDir, { recursive: true, mode: 0o700 });
|
|
93
|
+
return join(gitLocalDir, 'vectorforce.db');
|
|
94
|
+
}
|
|
95
|
+
const parent = dirname(curr);
|
|
96
|
+
if (parent === curr) break;
|
|
97
|
+
curr = parent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 2. Global fallback in ~/.vectorforce/vectorforce.db
|
|
101
|
+
try {
|
|
102
|
+
const homeDir = join(homedir(), '.vectorforce');
|
|
103
|
+
mkdirSync(homeDir, { recursive: true, mode: 0o700 });
|
|
104
|
+
return join(homeDir, 'vectorforce.db');
|
|
105
|
+
} catch {
|
|
106
|
+
const fallbackDir = join(startDir, '.vectorforce');
|
|
107
|
+
mkdirSync(fallbackDir, { recursive: true, mode: 0o700 });
|
|
108
|
+
return join(fallbackDir, 'vectorforce.db');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function createLlmProvider(workspacePath) {
|
|
113
|
+
try {
|
|
114
|
+
return { provider: new LlmProvider(resolveLlmConfig(process.env, {}, workspacePath)), error: null };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { provider: null, error: err.message };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getServices(explicitDbPath, workspacePath) {
|
|
121
|
+
const cwd = workspacePath || getWorkspacePath(process.cwd());
|
|
122
|
+
const dbPath = explicitDbPath || getDbPath(cwd);
|
|
123
|
+
const store = new SqliteStore(dbPath);
|
|
124
|
+
const projectService = new ProjectService(store);
|
|
125
|
+
const checkpointService = new CheckpointService(store, projectService);
|
|
126
|
+
const briefingService = new BriefingService(store, projectService);
|
|
127
|
+
const handoffService = new HandoffService(store, projectService, checkpointService);
|
|
128
|
+
const llm = createLlmProvider(cwd);
|
|
129
|
+
const reasoningService = new ReasoningService(store, projectService, briefingService, {
|
|
130
|
+
llmProvider: llm.provider,
|
|
131
|
+
codeIndexProvider: CodeIndexProvider
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
store,
|
|
135
|
+
projectService,
|
|
136
|
+
checkpointService,
|
|
137
|
+
briefingService,
|
|
138
|
+
handoffService,
|
|
139
|
+
reasoningService,
|
|
140
|
+
llmProviderError: llm.error
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printUsage() {
|
|
145
|
+
console.log(`
|
|
146
|
+
VectorForce (vf) - Git for AI Engineering Context
|
|
147
|
+
|
|
148
|
+
Usage:
|
|
149
|
+
vf init [project_name] Initialize current workspace
|
|
150
|
+
vf task <id> [title] Create or register a task
|
|
151
|
+
vf tasks [active|completed|all] List tasks in workspace
|
|
152
|
+
vf policy list List allowed document paths
|
|
153
|
+
vf policy add <relative_path> Allow document for checkpointing
|
|
154
|
+
vf checkpoint [options] Capture an immutable checkpoint
|
|
155
|
+
vf resume [options] Generate authorized resume briefing
|
|
156
|
+
vf reason [options] AI reasoning over memory + codebase (alias: continue)
|
|
157
|
+
vf drift [options] Check for workspace and document drift
|
|
158
|
+
vf export [options] Export selective handoff package
|
|
159
|
+
vf import <package_path> [options] Import handoff package into workspace
|
|
160
|
+
vf recap [options] Show weekly timeline & engineering recap
|
|
161
|
+
vf timeline [options] Alias for vf recap
|
|
162
|
+
vf studio [options] Launch VectorForce Studio web control center
|
|
163
|
+
vf ui [options] Alias for vf studio
|
|
164
|
+
vf mcp Start stdio MCP server for Antigravity / Command Code
|
|
165
|
+
vf config Display MCP configuration snippet for harnesses
|
|
166
|
+
vf help Show this help
|
|
167
|
+
|
|
168
|
+
Checkpoint options:
|
|
169
|
+
--task <id> Task identifier
|
|
170
|
+
--summary <text> Concise summary
|
|
171
|
+
--goal <text> Add a goal (can repeat)
|
|
172
|
+
--constraint <text> Add a constraint (can repeat)
|
|
173
|
+
--decision <text> Add decision format "ID:Title:Rationale" (can repeat)
|
|
174
|
+
--next <text> Add next step (can repeat)
|
|
175
|
+
--doc <path> Request allowlisted document (can repeat)
|
|
176
|
+
--no-git Skip Git status collection
|
|
177
|
+
|
|
178
|
+
Resume options:
|
|
179
|
+
--task <id> Task identifier
|
|
180
|
+
--checkpoint <id> Specific checkpoint ID (defaults to latest head)
|
|
181
|
+
--dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
|
|
182
|
+
|
|
183
|
+
Reason options:
|
|
184
|
+
--task <id> Task identifier
|
|
185
|
+
--checkpoint <id> Specific checkpoint ID (defaults to latest head)
|
|
186
|
+
--focus <text> Optional question or focus for the reasoning pass
|
|
187
|
+
--model <name> Override the model id (e.g. a Meta Llama or Muse model)
|
|
188
|
+
--no-code Reason over memory only (do not send code context)
|
|
189
|
+
--raw Print the deterministic briefing without calling the model
|
|
190
|
+
--dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
|
|
191
|
+
|
|
192
|
+
Recap / Timeline options:
|
|
193
|
+
--days <number> Number of days to include (default: 7)
|
|
194
|
+
--task <id> Filter by specific task
|
|
195
|
+
--dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
|
|
196
|
+
|
|
197
|
+
Handoff options:
|
|
198
|
+
--task <id> Task identifier
|
|
199
|
+
--recipient <name> Target recipient/audience
|
|
200
|
+
--checkpoint <id> Specific checkpoint ID (defaults to latest head)
|
|
201
|
+
--dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli, any
|
|
202
|
+
--preview Preview export bundle without writing file
|
|
203
|
+
--out <path> Target output path for handoff package
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseFlags(args) {
|
|
208
|
+
const flags = {
|
|
209
|
+
goals: [],
|
|
210
|
+
constraints: [],
|
|
211
|
+
decisions: [],
|
|
212
|
+
nextSteps: [],
|
|
213
|
+
docs: [],
|
|
214
|
+
includeGit: true
|
|
215
|
+
};
|
|
216
|
+
for (let i = 0; i < args.length; i++) {
|
|
217
|
+
const arg = args[i];
|
|
218
|
+
if (arg === '--task' && args[i + 1]) {
|
|
219
|
+
flags.task = args[++i];
|
|
220
|
+
} else if (arg === '--summary' && args[i + 1]) {
|
|
221
|
+
flags.summary = args[++i];
|
|
222
|
+
} else if (arg === '--checkpoint' && args[i + 1]) {
|
|
223
|
+
flags.checkpoint = args[++i];
|
|
224
|
+
} else if (arg === '--dest' && args[i + 1]) {
|
|
225
|
+
flags.dest = args[++i];
|
|
226
|
+
} else if (arg === '--goal' && args[i + 1]) {
|
|
227
|
+
flags.goals.push(args[++i]);
|
|
228
|
+
} else if (arg === '--constraint' && args[i + 1]) {
|
|
229
|
+
flags.constraints.push(args[++i]);
|
|
230
|
+
} else if (arg === '--decision' && args[i + 1]) {
|
|
231
|
+
const parts = args[++i].split(':');
|
|
232
|
+
flags.decisions.push({
|
|
233
|
+
id: parts[0] || 'DEC',
|
|
234
|
+
title: parts[1] || parts[0],
|
|
235
|
+
rationale: parts[2] || 'Approved architecture decision'
|
|
236
|
+
});
|
|
237
|
+
} else if (arg === '--next' && args[i + 1]) {
|
|
238
|
+
flags.nextSteps.push(args[++i]);
|
|
239
|
+
} else if (arg === '--doc' && args[i + 1]) {
|
|
240
|
+
flags.docs.push(args[++i]);
|
|
241
|
+
} else if (arg === '--model' && args[i + 1]) {
|
|
242
|
+
flags.model = args[++i];
|
|
243
|
+
} else if (arg === '--focus' && args[i + 1]) {
|
|
244
|
+
flags.focus = args[++i];
|
|
245
|
+
} else if (arg === '--no-code') {
|
|
246
|
+
flags.includeCode = false;
|
|
247
|
+
} else if (arg === '--raw') {
|
|
248
|
+
flags.raw = true;
|
|
249
|
+
} else if (arg === '--no-git') {
|
|
250
|
+
flags.includeGit = false;
|
|
251
|
+
} else if (arg === '--recipient' && args[i + 1]) {
|
|
252
|
+
flags.recipient = args[++i];
|
|
253
|
+
} else if (arg === '--preview') {
|
|
254
|
+
flags.preview = true;
|
|
255
|
+
} else if (arg === '--out' && args[i + 1]) {
|
|
256
|
+
flags.outPath = args[++i];
|
|
257
|
+
} else if (arg === '--port' && args[i + 1]) {
|
|
258
|
+
flags.port = parseInt(args[++i], 10);
|
|
259
|
+
} else if (arg === '--workspace' && args[i + 1]) {
|
|
260
|
+
flags.workspace = resolve(args[++i]);
|
|
261
|
+
} else if (arg === '--db' && args[i + 1]) {
|
|
262
|
+
flags.db = resolve(args[++i]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return flags;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function main() {
|
|
269
|
+
const args = process.argv.slice(2);
|
|
270
|
+
const command = args[0] || 'help';
|
|
271
|
+
|
|
272
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
273
|
+
printUsage();
|
|
274
|
+
process.exit(0);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const flags = parseFlags(args.slice(1));
|
|
278
|
+
const cwd = flags.workspace || getWorkspacePath(process.cwd());
|
|
279
|
+
const dbPath = flags.db || getDbPath(cwd);
|
|
280
|
+
const { store, projectService, checkpointService, briefingService, handoffService, reasoningService, llmProviderError } = getServices(dbPath, cwd);
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
switch (command) {
|
|
284
|
+
case 'init': {
|
|
285
|
+
const name = args[1] || null;
|
|
286
|
+
const project = projectService.initProject(cwd, name);
|
|
287
|
+
console.log(`Initialized VectorForce project: ${project.name} (${project.project_id})`);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'task': {
|
|
292
|
+
const sub = args[1];
|
|
293
|
+
if (sub === 'complete' || sub === 'done') {
|
|
294
|
+
const taskId = args[2];
|
|
295
|
+
if (!taskId) {
|
|
296
|
+
console.error('Error: task id required (e.g. vf task complete phase-5-pilot)');
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
const task = projectService.updateTaskStatus(cwd, taskId, 'completed');
|
|
300
|
+
console.log(`Task marked as completed: ${task.title} (${task.task_id}) [${task.status}]`);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
const taskId = sub;
|
|
304
|
+
if (!taskId) {
|
|
305
|
+
console.error('Error: task id required (e.g. vf task feat-sqlite "Implement SQLite store")');
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
const title = args.slice(2).join(' ') || taskId;
|
|
309
|
+
const task = projectService.createTask(cwd, taskId, title);
|
|
310
|
+
console.log(`Task registered: ${task.title} (${task.task_id}) [${task.status}]`);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case 'tasks': {
|
|
315
|
+
const filter = args[1] || 'active';
|
|
316
|
+
const project = projectService.getProjectForWorkspace(cwd);
|
|
317
|
+
const tasks = store.listTasks(project.project_id, filter);
|
|
318
|
+
console.log(`Tasks for ${project.name} [${filter}]:`);
|
|
319
|
+
for (const t of tasks) {
|
|
320
|
+
console.log(`- [${t.status}] ${t.task_id}: ${t.title}`);
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case 'policy': {
|
|
326
|
+
const sub = args[1];
|
|
327
|
+
const project = projectService.getProjectForWorkspace(cwd);
|
|
328
|
+
if (sub === 'list') {
|
|
329
|
+
const list = store.getDocumentPolicy(project.project_id);
|
|
330
|
+
console.log(`Document allowlist for ${project.name}:`);
|
|
331
|
+
if (list.length === 0) console.log(' (no documents allowlisted)');
|
|
332
|
+
for (const p of list) console.log(` - ${p}`);
|
|
333
|
+
} else if (sub === 'add') {
|
|
334
|
+
const docPath = args[2];
|
|
335
|
+
if (!docPath) {
|
|
336
|
+
console.error('Error: relative document path required');
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
const current = store.getDocumentPolicy(project.project_id);
|
|
340
|
+
const updated = store.setDocumentPolicy(project.project_id, [...current, docPath]);
|
|
341
|
+
console.log(`Updated allowlist: ${updated.join(', ')}`);
|
|
342
|
+
} else {
|
|
343
|
+
console.error('Unknown policy subcommand. Use "vf policy list" or "vf policy add <path>"');
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case 'checkpoint': {
|
|
350
|
+
const flags = parseFlags(args.slice(1));
|
|
351
|
+
if (!flags.summary) {
|
|
352
|
+
console.error('Error: --summary <text> is required');
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
const receipt = checkpointService.captureCheckpoint({
|
|
356
|
+
workspacePath: cwd,
|
|
357
|
+
taskId: flags.task,
|
|
358
|
+
summary: flags.summary,
|
|
359
|
+
goals: flags.goals,
|
|
360
|
+
constraints: flags.constraints,
|
|
361
|
+
decisions: flags.decisions,
|
|
362
|
+
nextSteps: flags.nextSteps,
|
|
363
|
+
requestedDocuments: flags.docs,
|
|
364
|
+
includeGit: flags.includeGit,
|
|
365
|
+
authorName: process.env.USER || 'developer',
|
|
366
|
+
authorHarness: 'cli'
|
|
367
|
+
});
|
|
368
|
+
console.log(`Checkpoint saved: ${receipt.reference_id} [${receipt.status}]`);
|
|
369
|
+
if (receipt.warnings.length > 0) {
|
|
370
|
+
for (const w of receipt.warnings) console.warn(`Warning: ${w}`);
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
case 'resume': {
|
|
376
|
+
const flags = parseFlags(args.slice(1));
|
|
377
|
+
const result = briefingService.generateBriefing({
|
|
378
|
+
workspacePath: cwd,
|
|
379
|
+
taskId: flags.task,
|
|
380
|
+
checkpointId: flags.checkpoint,
|
|
381
|
+
destination: flags.dest || 'cli'
|
|
382
|
+
});
|
|
383
|
+
console.log(result.briefingText);
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case 'drift': {
|
|
388
|
+
const flags = parseFlags(args.slice(1));
|
|
389
|
+
const result = briefingService.generateBriefing({
|
|
390
|
+
workspacePath: cwd,
|
|
391
|
+
taskId: flags.task,
|
|
392
|
+
checkpointId: flags.checkpoint,
|
|
393
|
+
destination: 'cli'
|
|
394
|
+
});
|
|
395
|
+
if (result.driftDetected) {
|
|
396
|
+
console.log('ā ļø DRIFT DETECTED:');
|
|
397
|
+
for (const w of result.warnings) console.log(`- ${w}`);
|
|
398
|
+
} else {
|
|
399
|
+
console.log('ā No drift detected: workspace and document state match recorded checkpoint.');
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
case 'reason':
|
|
405
|
+
case 'continue': {
|
|
406
|
+
const flags = parseFlags(args.slice(1));
|
|
407
|
+
if (flags.raw) {
|
|
408
|
+
const raw = briefingService.generateBriefing({
|
|
409
|
+
workspacePath: cwd,
|
|
410
|
+
taskId: flags.task,
|
|
411
|
+
checkpointId: flags.checkpoint,
|
|
412
|
+
destination: flags.dest || 'cli'
|
|
413
|
+
});
|
|
414
|
+
console.log(raw.briefingText);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
if (llmProviderError) {
|
|
418
|
+
console.warn(`Warning: LLM configuration issue: ${llmProviderError}`);
|
|
419
|
+
}
|
|
420
|
+
const result = await reasoningService.reasonAboutTask({
|
|
421
|
+
workspacePath: cwd,
|
|
422
|
+
taskId: flags.task,
|
|
423
|
+
checkpointId: flags.checkpoint,
|
|
424
|
+
destination: flags.dest || 'cli',
|
|
425
|
+
focus: flags.focus,
|
|
426
|
+
model: flags.model,
|
|
427
|
+
maxTokens: flags.maxTokens ? parseInt(flags.maxTokens, 10) : 4000,
|
|
428
|
+
includeCode: flags.includeCode !== false
|
|
429
|
+
});
|
|
430
|
+
console.log(result.narrativeText);
|
|
431
|
+
if (result.degraded) {
|
|
432
|
+
console.error(`\nā AI reasoning unavailable${result.error ? ` (${result.error})` : ''} ā showing deterministic briefing.`);
|
|
433
|
+
} else {
|
|
434
|
+
console.error(`\nā reasoned by ${result.provider}/${result.model} Ā· ${result.codeFilesConsidered} code file(s) considered`);
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
case 'export':
|
|
440
|
+
case 'export-handoff': {
|
|
441
|
+
const flags = parseFlags(args.slice(1));
|
|
442
|
+
if (!flags.task) {
|
|
443
|
+
console.error('Error: --task <id> is required for handoff export.');
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
if (!flags.recipient) {
|
|
447
|
+
console.error('Error: --recipient <name> is required for handoff export.');
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
const result = handoffService.exportHandoff({
|
|
451
|
+
workspacePath: cwd,
|
|
452
|
+
taskId: flags.task,
|
|
453
|
+
checkpointId: flags.checkpoint,
|
|
454
|
+
recipient: flags.recipient,
|
|
455
|
+
destinationHarness: flags.dest || 'any',
|
|
456
|
+
preview: flags.preview === true,
|
|
457
|
+
outPath: flags.outPath
|
|
458
|
+
});
|
|
459
|
+
if (result.preview) {
|
|
460
|
+
console.log(`\nā Handoff Preview for [${flags.task}] to ${flags.recipient}:`);
|
|
461
|
+
console.log(JSON.stringify(result.handoff, null, 2));
|
|
462
|
+
} else {
|
|
463
|
+
console.log(`\nā Handoff exported successfully: ${result.package_id}`);
|
|
464
|
+
console.log(` Bundle Path: ${result.bundlePath}`);
|
|
465
|
+
console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
case 'import':
|
|
471
|
+
case 'import-handoff': {
|
|
472
|
+
const bundlePath = args[1];
|
|
473
|
+
if (!bundlePath || bundlePath.startsWith('--')) {
|
|
474
|
+
console.error('Error: Package path required: vf import <package_path> [--task <id>]');
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
const flags = parseFlags(args.slice(2));
|
|
478
|
+
const result = handoffService.importHandoff({
|
|
479
|
+
workspacePath: cwd,
|
|
480
|
+
bundlePath,
|
|
481
|
+
targetTaskId: flags.task
|
|
482
|
+
});
|
|
483
|
+
console.log(`\nā Handoff imported successfully into task [${result.task.task_id}]`);
|
|
484
|
+
console.log(` Imported Checkpoint: ${result.importedCheckpointId}`);
|
|
485
|
+
console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case 'mcp': {
|
|
490
|
+
const server = new McpServer({ store, projectService, checkpointService, briefingService, handoffService, reasoningService }, cwd);
|
|
491
|
+
server.connect(process.stdin, process.stdout);
|
|
492
|
+
process.on('SIGINT', () => {
|
|
493
|
+
store.close();
|
|
494
|
+
process.exit(0);
|
|
495
|
+
});
|
|
496
|
+
process.on('SIGTERM', () => {
|
|
497
|
+
store.close();
|
|
498
|
+
process.exit(0);
|
|
499
|
+
});
|
|
500
|
+
// Keep process alive for stdio stream; do not execute finally store.close()
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case 'config': {
|
|
505
|
+
const sub = args[1];
|
|
506
|
+
if (sub === 'install' || sub === 'setup') {
|
|
507
|
+
const target = (args[2] || 'all').toLowerCase();
|
|
508
|
+
const installed = [];
|
|
509
|
+
if (target === 'antigravity' || target === 'all') {
|
|
510
|
+
const p = HarnessConfig.installAntigravity(cwd);
|
|
511
|
+
installed.push(`Antigravity -> ${p}`);
|
|
512
|
+
}
|
|
513
|
+
if (target === 'command-code' || target === 'cmd' || target === 'all') {
|
|
514
|
+
const p = HarnessConfig.installCommandCode(cwd);
|
|
515
|
+
installed.push(`Command Code -> ${p}`);
|
|
516
|
+
}
|
|
517
|
+
if (target === 'muse' || target === 'muse-code' || target === 'all') {
|
|
518
|
+
const p = HarnessConfig.installMuseCode(cwd);
|
|
519
|
+
installed.push(`Muse Code -> ${p}`);
|
|
520
|
+
}
|
|
521
|
+
if (target === 'codex' || target === 'all') {
|
|
522
|
+
const p = HarnessConfig.installCodex(cwd);
|
|
523
|
+
installed.push(`Codex -> ${p}`);
|
|
524
|
+
}
|
|
525
|
+
console.log(`\nā Configured harness integration(s):\n - ${installed.join('\n - ')}`);
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (sub === 'uninstall' || sub === 'remove') {
|
|
530
|
+
const target = (args[2] || 'all').toLowerCase();
|
|
531
|
+
const uninstalled = [];
|
|
532
|
+
if (target === 'antigravity' || target === 'all') {
|
|
533
|
+
if (HarnessConfig.uninstallAntigravity(cwd)) uninstalled.push('Antigravity');
|
|
534
|
+
}
|
|
535
|
+
if (target === 'command-code' || target === 'cmd' || target === 'all') {
|
|
536
|
+
if (HarnessConfig.uninstallCommandCode(cwd)) uninstalled.push('Command Code');
|
|
537
|
+
}
|
|
538
|
+
if (target === 'muse' || target === 'muse-code' || target === 'all') {
|
|
539
|
+
if (HarnessConfig.uninstallMuseCode(cwd)) uninstalled.push('Muse Code');
|
|
540
|
+
}
|
|
541
|
+
if (target === 'codex' || target === 'all') {
|
|
542
|
+
if (HarnessConfig.uninstallCodex(cwd)) uninstalled.push('Codex');
|
|
543
|
+
}
|
|
544
|
+
console.log(`\nā Removed harness integration(s): ${uninstalled.length ? uninstalled.join(', ') : 'none found'}`);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (sub === 'set') {
|
|
549
|
+
const key = args[2];
|
|
550
|
+
const val = args[3];
|
|
551
|
+
if (!key || val === undefined) {
|
|
552
|
+
console.error('Usage: node bin/vf.js config set <KEY> <VALUE>');
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
const vfDir = join(cwd, '.vectorforce');
|
|
556
|
+
mkdirSync(vfDir, { recursive: true, mode: 0o700 });
|
|
557
|
+
const cfgPath = join(vfDir, 'config.json');
|
|
558
|
+
let existing = {};
|
|
559
|
+
if (existsSync(cfgPath)) {
|
|
560
|
+
try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
|
|
561
|
+
}
|
|
562
|
+
existing[key] = val;
|
|
563
|
+
writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
564
|
+
console.log(`\nā Saved configuration [${key}] in ${cfgPath}`);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (sub === 'openrouter') {
|
|
569
|
+
const key = args[2];
|
|
570
|
+
const model = args[3] || 'deepseek/deepseek-v4.1-flash-1017';
|
|
571
|
+
if (!key) {
|
|
572
|
+
console.error('Usage: node bin/vf.js config openrouter <API_KEY> [MODEL]');
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
const vfDir = join(cwd, '.vectorforce');
|
|
576
|
+
mkdirSync(vfDir, { recursive: true, mode: 0o700 });
|
|
577
|
+
const cfgPath = join(vfDir, 'config.json');
|
|
578
|
+
let existing = {};
|
|
579
|
+
if (existsSync(cfgPath)) {
|
|
580
|
+
try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
|
|
581
|
+
}
|
|
582
|
+
existing.VF_LLM_PROVIDER = 'openrouter';
|
|
583
|
+
existing.OPENROUTER_API_KEY = key;
|
|
584
|
+
existing.VF_OPENROUTER_MODEL = model;
|
|
585
|
+
writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
586
|
+
console.log(`\nā Configured OpenRouter in ${cfgPath}`);
|
|
587
|
+
console.log(` Provider: openrouter`);
|
|
588
|
+
console.log(` Model: ${model}`);
|
|
589
|
+
console.log(` Base URL: https://openrouter.ai/api/v1`);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (sub === 'muse') {
|
|
594
|
+
const key = args[2];
|
|
595
|
+
const model = args[3] || 'muse-spark-1.3';
|
|
596
|
+
if (!key) {
|
|
597
|
+
console.error('Usage: node bin/vf.js config muse <API_KEY> [MODEL]');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
const vfDir = join(cwd, '.vectorforce');
|
|
601
|
+
mkdirSync(vfDir, { recursive: true, mode: 0o700 });
|
|
602
|
+
const cfgPath = join(vfDir, 'config.json');
|
|
603
|
+
let existing = {};
|
|
604
|
+
if (existsSync(cfgPath)) {
|
|
605
|
+
try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
|
|
606
|
+
}
|
|
607
|
+
existing.VF_LLM_PROVIDER = 'muse';
|
|
608
|
+
existing.MODEL_API_KEY = key;
|
|
609
|
+
existing.VF_MUSE_MODEL = model;
|
|
610
|
+
writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
611
|
+
console.log(`\nā Configured Muse in ${cfgPath}`);
|
|
612
|
+
console.log(` Provider: muse`);
|
|
613
|
+
console.log(` Model: ${model}`);
|
|
614
|
+
console.log(` Base URL: https://api.meta.ai/v1`);
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (sub === 'show' || sub === 'status') {
|
|
619
|
+
const cfg = resolveLlmConfig(process.env, {}, cwd);
|
|
620
|
+
console.log('\nVectorForce AI Reasoning Configuration:');
|
|
621
|
+
console.log(` Provider: ${cfg.provider}`);
|
|
622
|
+
console.log(` Model: ${cfg.model}`);
|
|
623
|
+
console.log(` Base URL: ${cfg.baseUrl}`);
|
|
624
|
+
console.log(` API Key: ${cfg.apiKey ? 'configured (hidden)' : 'none (falling back to local)'}`);
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const binPath = resolve(process.argv[1]);
|
|
629
|
+
console.log(`
|
|
630
|
+
VectorForce Harness Configuration:
|
|
631
|
+
|
|
632
|
+
1. Google Antigravity Configuration:
|
|
633
|
+
File: .agents/mcp_config.json (or ~/.gemini/config/mcp_config.json)
|
|
634
|
+
Content:
|
|
635
|
+
{
|
|
636
|
+
"mcpServers": {
|
|
637
|
+
"vectorforce": {
|
|
638
|
+
"command": "node",
|
|
639
|
+
"args": ["${binPath}", "mcp"]
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
2. Meta Muse Code (muse-code) Configuration:
|
|
645
|
+
File: .muse/mcp_config.json (or musecode.json)
|
|
646
|
+
Content:
|
|
647
|
+
{
|
|
648
|
+
"mcpServers": {
|
|
649
|
+
"vectorforce": {
|
|
650
|
+
"command": "node",
|
|
651
|
+
"args": ["${binPath}", "mcp"],
|
|
652
|
+
"env": { "VF_HARNESS": "muse-code" }
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
3. Command Code (command-code) Configuration:
|
|
658
|
+
Run command:
|
|
659
|
+
cmd mcp add --transport stdio vectorforce -- node ${binPath} mcp
|
|
660
|
+
|
|
661
|
+
4. OpenAI Codex (codex) Configuration:
|
|
662
|
+
File: .codex/config.toml (or codex.json)
|
|
663
|
+
Content:
|
|
664
|
+
[mcp_servers.vectorforce]
|
|
665
|
+
command = "node"
|
|
666
|
+
args = ["${binPath}", "mcp"]
|
|
667
|
+
[mcp_servers.vectorforce.env]
|
|
668
|
+
VF_HARNESS = "codex"
|
|
669
|
+
|
|
670
|
+
Or run command:
|
|
671
|
+
codex mcp add vectorforce -- node ${binPath} mcp
|
|
672
|
+
|
|
673
|
+
Automated Setup:
|
|
674
|
+
node bin/vf.js config install codex
|
|
675
|
+
node bin/vf.js config install all
|
|
676
|
+
|
|
677
|
+
5. AI Reasoning (vf reason / reason_about_task):
|
|
678
|
+
Local Ollama (default, 100% private):
|
|
679
|
+
VF_LLM_PROVIDER=ollama
|
|
680
|
+
VF_OLLAMA_BASE_URL=http://127.0.0.1:11434
|
|
681
|
+
VF_OLLAMA_MODEL=llama3.1:8b
|
|
682
|
+
|
|
683
|
+
Cloud Meta Muse (Responses API):
|
|
684
|
+
VF_LLM_PROVIDER=muse
|
|
685
|
+
MODEL_API_KEY=<your-key>
|
|
686
|
+
VF_MUSE_MODEL=muse-spark-1.3 # default
|
|
687
|
+
VF_MUSE_BASE_URL=https://api.meta.ai/v1 # default
|
|
688
|
+
# Or: node bin/vf.js config muse <API_KEY> [MODEL]
|
|
689
|
+
|
|
690
|
+
Cloud OpenAI Codex / GPT:
|
|
691
|
+
VF_LLM_PROVIDER=codex
|
|
692
|
+
VF_CODEX_BASE_URL=https://api.openai.com/v1
|
|
693
|
+
VF_CODEX_API_KEY=<your-key>
|
|
694
|
+
VF_CODEX_MODEL=o3-mini
|
|
695
|
+
|
|
696
|
+
Cloud OpenRouter (DeepSeek v4.1 Flash / Multi-Model):
|
|
697
|
+
VF_LLM_PROVIDER=openrouter
|
|
698
|
+
OPENROUTER_API_KEY=<your-key> # or VF_OPENROUTER_API_KEY
|
|
699
|
+
VF_OPENROUTER_MODEL=deepseek/deepseek-v4.1-flash-1017 # default
|
|
700
|
+
|
|
701
|
+
Privacy: set VF_LLM_ALLOW_CODE=false (or run with --no-code) to keep source out of the prompt.
|
|
702
|
+
`);
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
case 'studio':
|
|
707
|
+
case 'ui': {
|
|
708
|
+
const flags = parseFlags(args.slice(1));
|
|
709
|
+
const port = flags.port || 3210;
|
|
710
|
+
const studio = createStudioServer({
|
|
711
|
+
dbPath: getDbPath(),
|
|
712
|
+
workspacePath: cwd,
|
|
713
|
+
port
|
|
714
|
+
});
|
|
715
|
+
const url = await studio.start();
|
|
716
|
+
console.log(`\nš VectorForce Studio is running at: ${url}`);
|
|
717
|
+
console.log(` Connected to SQLite: ${getDbPath()}`);
|
|
718
|
+
console.log(` Interactive Topology & AI Memory: ${url}`);
|
|
719
|
+
console.log(` Press Ctrl+C to stop.\n`);
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
const { exec } = await import('node:child_process');
|
|
723
|
+
exec(`open ${url}`);
|
|
724
|
+
} catch {}
|
|
725
|
+
|
|
726
|
+
process.on('SIGINT', async () => {
|
|
727
|
+
await studio.stop();
|
|
728
|
+
process.exit(0);
|
|
729
|
+
});
|
|
730
|
+
process.on('SIGTERM', async () => {
|
|
731
|
+
await studio.stop();
|
|
732
|
+
process.exit(0);
|
|
733
|
+
});
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
default:
|
|
738
|
+
console.error(`Unknown command: ${command}`);
|
|
739
|
+
printUsage();
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
} catch (err) {
|
|
743
|
+
if (err instanceof VectorForceError) {
|
|
744
|
+
console.error(`VectorForce Error [${err.code}]: ${err.message}`);
|
|
745
|
+
} else {
|
|
746
|
+
console.error(`Error: ${err.message}`);
|
|
747
|
+
}
|
|
748
|
+
process.exit(1);
|
|
749
|
+
} finally {
|
|
750
|
+
if (command !== 'mcp' && command !== 'studio' && command !== 'ui') {
|
|
751
|
+
store.close();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
main();
|