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,313 @@
1
+ import { Authorizer } from '../policy/authorizer.js';
2
+ import { GitProvider } from '../providers/git-provider.js';
3
+ import { sha256 } from '../core/hash.js';
4
+ import { readFileSync } from 'node:fs';
5
+ import { ErrorCode, VectorForceError } from '../core/errors.js';
6
+
7
+ export class BriefingService {
8
+ /**
9
+ * @param {import('../storage/sqlite-store.js').SqliteStore} store
10
+ * @param {import('./project-service.js').ProjectService} projectService
11
+ */
12
+ constructor(store, projectService) {
13
+ this.store = store;
14
+ this.projectService = projectService;
15
+ }
16
+
17
+ /**
18
+ * Generate an authorized resume briefing for a task.
19
+ * @param {object} params
20
+ * @param {string} params.workspacePath
21
+ * @param {string} [params.taskId]
22
+ * @param {string} [params.checkpointId]
23
+ * @param {string} [params.destination='cli']
24
+ * @param {number} [params.maxTokens=4000]
25
+ * @returns {{ briefingText: string, driftDetected: boolean, warnings: string[], checkpoint: object }}
26
+ */
27
+ generateBriefing(params) {
28
+ Authorizer.authorizeDestination(params.destination);
29
+
30
+ const { task, project } = this.projectService.resolveTask(params.workspacePath, params.taskId);
31
+
32
+ // Find target checkpoint
33
+ let checkpoint;
34
+ if (params.checkpointId) {
35
+ checkpoint = this.store.getCheckpoint(params.checkpointId);
36
+ if (!checkpoint || checkpoint.project_id !== project.project_id || checkpoint.task_id !== task.task_id) {
37
+ throw new VectorForceError(
38
+ ErrorCode.SCHEMA_VALIDATION_FAILED,
39
+ `Checkpoint '${params.checkpointId}' not found for task '${task.task_id}'`
40
+ );
41
+ }
42
+ } else {
43
+ const leaves = this.store.getLeafCheckpoints(project.project_id, task.task_id);
44
+ if (leaves.length === 0) {
45
+ throw new VectorForceError(
46
+ ErrorCode.TASK_NOT_FOUND,
47
+ `Task '${task.task_id}' has no checkpoints recorded yet.`
48
+ );
49
+ }
50
+ if (leaves.length > 1) {
51
+ const heads = leaves.map(l => `${l.checkpoint_id} (${l.timestamp})`).join(', ');
52
+ throw new VectorForceError(
53
+ ErrorCode.DIVERGENT_HEADS,
54
+ `Task has divergent checkpoint heads: ${heads}. You must specify --checkpoint <id>.`
55
+ );
56
+ }
57
+ checkpoint = leaves[0];
58
+ }
59
+
60
+ // Drift Detection
61
+ const warnings = [];
62
+ let driftDetected = false;
63
+
64
+ // 1. Git drift
65
+ const currentGit = GitProvider.collect(project.canonical_path).git;
66
+ const recordedGit = checkpoint.evidence.git;
67
+
68
+ let gitDriftSummary = 'Git evidence not recorded.';
69
+ if (recordedGit && currentGit) {
70
+ if (recordedGit.commit_hash !== currentGit.commit_hash) {
71
+ driftDetected = true;
72
+ warnings.push(`Git commit has drifted from ${recordedGit.commit_hash.slice(0, 7)} to ${currentGit.commit_hash.slice(0, 7)}`);
73
+ gitDriftSummary = `DRIFT: Commit changed from ${recordedGit.commit_hash} to ${currentGit.commit_hash} on branch ${currentGit.branch}.`;
74
+ } else if (recordedGit.is_dirty !== currentGit.is_dirty) {
75
+ driftDetected = true;
76
+ warnings.push(`Working directory dirty state changed (now ${currentGit.is_dirty ? 'dirty' : 'clean'})`);
77
+ gitDriftSummary = `DRIFT: Working directory dirty state changed.`;
78
+ } else {
79
+ gitDriftSummary = `MATCH: Workspace at commit ${currentGit.commit_hash.slice(0, 7)} (${currentGit.branch}), dirty: ${currentGit.is_dirty}.`;
80
+ }
81
+ }
82
+
83
+ // 2. Document drift
84
+ const docDriftSummaries = [];
85
+ if (checkpoint.evidence.documents && checkpoint.evidence.documents.length > 0) {
86
+ for (const doc of checkpoint.evidence.documents) {
87
+ try {
88
+ const fullPath = Authorizer.validateDocumentPath(project.canonical_path, doc.path);
89
+ const currentBytes = readFileSync(fullPath);
90
+ const currentHash = sha256(currentBytes);
91
+ if (currentHash !== doc.sha256) {
92
+ driftDetected = true;
93
+ warnings.push(`Document '${doc.path}' content has changed since checkpoint.`);
94
+ docDriftSummaries.push(`- **${doc.path}**: MODIFIED (hash mismatch)`);
95
+ } else {
96
+ docDriftSummaries.push(`- **${doc.path}**: VERIFIED (content unchanged)`);
97
+ }
98
+ } catch (err) {
99
+ driftDetected = true;
100
+ warnings.push(`Document '${doc.path}' is missing or unreadable: ${err.message}`);
101
+ docDriftSummaries.push(`- **${doc.path}**: MISSING / GONE`);
102
+ }
103
+ }
104
+ }
105
+
106
+ // Construct Briefing Markdown
107
+ const lines = [];
108
+ lines.push(`# Task Briefing: ${task.title} (\`${task.task_id}\`)`);
109
+ lines.push(`\n**Checkpoint ID:** \`${checkpoint.checkpoint_id}\` | **Recorded:** ${checkpoint.timestamp}`);
110
+ lines.push(`**Author:** ${checkpoint.author.name} via ${checkpoint.author.harness}`);
111
+ lines.push(`\n> [!NOTE]\n> **Summary:** ${checkpoint.summary}`);
112
+
113
+ lines.push('\n## Invariant Constraints (Non-Negotiable)');
114
+ if (checkpoint.claims.constraints.length > 0) {
115
+ for (const c of checkpoint.claims.constraints) {
116
+ lines.push(`- ⚠️ ${c}`);
117
+ }
118
+ } else {
119
+ lines.push('*No specific constraints recorded.*');
120
+ }
121
+
122
+ lines.push('\n## Active Goals');
123
+ for (const g of checkpoint.claims.goals) {
124
+ lines.push(`- [ ] ${g}`);
125
+ }
126
+
127
+ if (checkpoint.claims.decisions.length > 0) {
128
+ lines.push('\n## Architectural Decisions');
129
+ for (const d of checkpoint.claims.decisions) {
130
+ lines.push(`- **${d.id}: ${d.title}** — ${d.rationale}`);
131
+ }
132
+ }
133
+
134
+ if (checkpoint.claims.next_steps.length > 0) {
135
+ lines.push('\n## Immediate Next Steps');
136
+ for (const s of checkpoint.claims.next_steps) {
137
+ lines.push(`1. ${s}`);
138
+ }
139
+ }
140
+
141
+ if (checkpoint.claims.open_questions.length > 0) {
142
+ lines.push('\n## Open Questions');
143
+ for (const q of checkpoint.claims.open_questions) {
144
+ lines.push(`- ❓ ${q}`);
145
+ }
146
+ }
147
+
148
+ lines.push('\n## Verified Workspace Evidence & Drift Analysis');
149
+ lines.push(`- **Git Status:** ${gitDriftSummary}`);
150
+ if (docDriftSummaries.length > 0) {
151
+ lines.push('- **Document States:**');
152
+ for (const ds of docDriftSummaries) {
153
+ lines.push(` ${ds}`);
154
+ }
155
+ }
156
+
157
+ if (checkpoint.evidence.gaps.length > 0) {
158
+ lines.push('\n### Known Evidence Gaps');
159
+ for (const gap of checkpoint.evidence.gaps) {
160
+ lines.push(`- ⚠️ [${gap.type.toUpperCase()}] ${gap.target}: ${gap.reason}`);
161
+ }
162
+ }
163
+
164
+ if (checkpoint.evidence.documents && checkpoint.evidence.documents.length > 0) {
165
+ lines.push('\n## Allowlisted Documents (Untrusted Reference Data)');
166
+ for (const doc of checkpoint.evidence.documents) {
167
+ lines.push(`\n### Document: \`${doc.path}\` (${doc.size_bytes} bytes)`);
168
+ lines.push('```markdown');
169
+ lines.push(doc.content || '/* Content snippet omitted */');
170
+ lines.push('```');
171
+ }
172
+ }
173
+
174
+ const briefingText = lines.join('\n');
175
+ return {
176
+ briefingText,
177
+ driftDetected,
178
+ warnings,
179
+ checkpoint
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Generate an authorized weekly / chronological recap of project checkpoints and engineering progress.
185
+ * @param {object} params
186
+ * @param {string} params.workspacePath
187
+ * @param {number} [params.days=7] Timeframe in days
188
+ * @param {string} [params.taskId] Optional task filter
189
+ * @param {string} [params.destination='cli'] Destination harness
190
+ * @returns {{ recapText: string, checkpoints: Array<object>, taskCount: number }}
191
+ */
192
+ generateRecap(params) {
193
+ Authorizer.authorizeDestination(params.destination || 'cli');
194
+
195
+ const project = this.projectService.initProject(params.workspacePath);
196
+ const days = typeof params.days === 'number' ? params.days : 7;
197
+ const sinceDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
198
+
199
+ const checkpoints = this.store.getCheckpoints(project.project_id, {
200
+ taskId: params.taskId,
201
+ since: sinceDate
202
+ });
203
+
204
+ const tasks = this.projectService.listTasks(params.workspacePath, 'all');
205
+ const taskMap = new Map(tasks.map(t => [t.task_id, t]));
206
+
207
+ const lines = [];
208
+ lines.push(`# Weekly Context Continuity Recap: ${project.name}`);
209
+ lines.push(`\n**Time Window:** Past ${days} day(s) (since \`${sinceDate.slice(0, 10)}\`) | **Checkpoints:** ${checkpoints.length}`);
210
+ lines.push(`**Destination Harness:** \`${params.destination || 'cli'}\``);
211
+
212
+ if (checkpoints.length === 0) {
213
+ lines.push('\n> [!NOTE]\n> No checkpoints recorded within this timeframe.');
214
+ return { recapText: lines.join('\n'), checkpoints: [], taskCount: 0 };
215
+ }
216
+
217
+ const touchedTaskIds = Array.from(new Set(checkpoints.map(c => c.task_id)));
218
+ lines.push(`\n## Active Tasks Covered (${touchedTaskIds.length})`);
219
+ for (const tid of touchedTaskIds) {
220
+ const taskObj = taskMap.get(tid);
221
+ const title = taskObj ? taskObj.title : tid;
222
+ lines.push(`- **${title}** (\`${tid}\`)`);
223
+ }
224
+
225
+ lines.push('\n## Chronological Checkpoint Log');
226
+ for (let i = 0; i < checkpoints.length; i++) {
227
+ const cp = checkpoints[i];
228
+ const taskObj = taskMap.get(cp.task_id);
229
+ const taskTitle = taskObj ? taskObj.title : cp.task_id;
230
+ lines.push(`\n### ${i + 1}. [${cp.timestamp.replace('T', ' ').slice(0, 19)}] Task: ${taskTitle} (\`${cp.task_id}\`)`);
231
+ lines.push(`- **Checkpoint ID:** \`${cp.checkpoint_id.slice(0, 8)}\` | **Author:** ${cp.author.name} (${cp.author.harness})`);
232
+ lines.push(`- **Summary:** ${cp.summary}`);
233
+ if (cp.claims.goals && cp.claims.goals.length > 0) {
234
+ lines.push(`- **Goals:** ${cp.claims.goals.join('; ')}`);
235
+ }
236
+ if (cp.claims.decisions && cp.claims.decisions.length > 0) {
237
+ lines.push(`- **Decisions:** ${cp.claims.decisions.map(d => `${d.id}: ${d.title}`).join(', ')}`);
238
+ }
239
+ if (cp.claims.constraints && cp.claims.constraints.length > 0) {
240
+ lines.push(`- **Constraints:** ${cp.claims.constraints.join('; ')}`);
241
+ }
242
+ if (cp.evidence.git && cp.evidence.git.commit_hash) {
243
+ lines.push(`- **Git Commit:** \`${cp.evidence.git.commit_hash.slice(0, 7)}\` (${cp.evidence.git.branch}${cp.evidence.git.is_dirty ? ' [dirty]' : ''})`);
244
+ }
245
+ }
246
+
247
+ // Aggregate Decisions and Constraints
248
+ const allDecisions = [];
249
+ const allConstraints = new Set();
250
+ const seenDecisionIds = new Set();
251
+
252
+ for (const cp of checkpoints) {
253
+ if (cp.claims.decisions) {
254
+ for (const d of cp.claims.decisions) {
255
+ if (!seenDecisionIds.has(d.id)) {
256
+ seenDecisionIds.add(d.id);
257
+ allDecisions.push(d);
258
+ }
259
+ }
260
+ }
261
+ if (cp.claims.constraints) {
262
+ for (const c of cp.claims.constraints) {
263
+ allConstraints.add(c);
264
+ }
265
+ }
266
+ }
267
+
268
+ if (allDecisions.length > 0) {
269
+ lines.push('\n## Key Architectural Decisions');
270
+ for (const d of allDecisions) {
271
+ lines.push(`- **${d.id}: ${d.title}** — ${d.rationale}`);
272
+ }
273
+ }
274
+
275
+ if (allConstraints.size > 0) {
276
+ lines.push('\n## Invariant Constraints Enforced');
277
+ for (const c of allConstraints) {
278
+ lines.push(`- ⚠️ ${c}`);
279
+ }
280
+ }
281
+
282
+ // Latest Next Steps across active tasks
283
+ const latestCheckpoints = [];
284
+ for (const tid of touchedTaskIds) {
285
+ const taskCps = checkpoints.filter(c => c.task_id === tid);
286
+ if (taskCps.length > 0) {
287
+ latestCheckpoints.push(taskCps[taskCps.length - 1]);
288
+ }
289
+ }
290
+
291
+ const upcomingSteps = [];
292
+ for (const cp of latestCheckpoints) {
293
+ if (cp.claims.next_steps && cp.claims.next_steps.length > 0) {
294
+ for (const step of cp.claims.next_steps) {
295
+ upcomingSteps.push(`[\`${cp.task_id}\`] ${step}`);
296
+ }
297
+ }
298
+ }
299
+
300
+ if (upcomingSteps.length > 0) {
301
+ lines.push('\n## Current Next Steps');
302
+ for (const step of upcomingSteps) {
303
+ lines.push(`- [ ] ${step}`);
304
+ }
305
+ }
306
+
307
+ return {
308
+ recapText: lines.join('\n'),
309
+ checkpoints,
310
+ taskCount: touchedTaskIds.length
311
+ };
312
+ }
313
+ }
@@ -0,0 +1,120 @@
1
+ import { GitProvider } from '../providers/git-provider.js';
2
+ import { DocumentProvider } from '../providers/document-provider.js';
3
+ import { generateUuid, hashRequestPayload } from '../core/hash.js';
4
+ import { SCHEMA_VERSION_CHECKPOINT } from '../core/types.js';
5
+
6
+ export class CheckpointService {
7
+ /**
8
+ * @param {import('../storage/sqlite-store.js').SqliteStore} store
9
+ * @param {import('./project-service.js').ProjectService} projectService
10
+ */
11
+ constructor(store, projectService) {
12
+ this.store = store;
13
+ this.projectService = projectService;
14
+ }
15
+
16
+ /**
17
+ * Capture an immutable checkpoint.
18
+ * @param {object} params
19
+ * @param {string} params.workspacePath Canonical or relative workspace path
20
+ * @param {string} [params.taskId] Optional explicit task ID
21
+ * @param {string} params.summary Concise progress summary
22
+ * @param {string[]} params.goals Active goals
23
+ * @param {string[]} params.constraints Key constraints
24
+ * @param {Array<{id: string, title: string, rationale: string}>} [params.decisions=[]]
25
+ * @param {string[]} [params.nextSteps=[]]
26
+ * @param {string[]} [params.openQuestions=[]]
27
+ * @param {boolean} [params.includeGit=true]
28
+ * @param {string[]} [params.requestedDocuments=[]]
29
+ * @param {string} [params.parentId=null]
30
+ * @param {string} [params.authorName='developer']
31
+ * @param {string} [params.authorHarness='antigravity']
32
+ * @param {string} [params.operationId]
33
+ * @returns {import('../core/types.js').OperationReceiptV1}
34
+ */
35
+ captureCheckpoint(params) {
36
+ const { task, project } = this.projectService.resolveTask(params.workspacePath, params.taskId);
37
+
38
+ // Compute request hash for idempotency deduplication
39
+ const requestPayload = {
40
+ projectId: project.project_id,
41
+ taskId: task.task_id,
42
+ summary: params.summary,
43
+ goals: params.goals || [],
44
+ constraints: params.constraints || [],
45
+ decisions: params.decisions || [],
46
+ nextSteps: params.nextSteps || [],
47
+ openQuestions: params.openQuestions || [],
48
+ includeGit: params.includeGit !== false,
49
+ requestedDocuments: (params.requestedDocuments || []).slice().sort(),
50
+ parentId: params.parentId || null
51
+ };
52
+ const requestHash = hashRequestPayload(requestPayload);
53
+
54
+ // If parentId not specified, discover latest single leaf
55
+ let parentId = params.parentId || null;
56
+ if (!parentId) {
57
+ const leaves = this.store.getLeafCheckpoints(project.project_id, task.task_id);
58
+ if (leaves.length === 1) {
59
+ parentId = leaves[0].checkpoint_id;
60
+ }
61
+ // If leaves.length > 1, parentId stays null or explicit selection is required
62
+ }
63
+
64
+ // 1. Collect Git evidence if requested
65
+ let gitEvidence = null;
66
+ const gaps = [];
67
+ if (params.includeGit !== false) {
68
+ const gitResult = GitProvider.collect(project.canonical_path);
69
+ gitEvidence = gitResult.git;
70
+ if (gitResult.gap) gaps.push(gitResult.gap);
71
+ }
72
+
73
+ // 2. Collect Document evidence if requested
74
+ let documentEvidence = [];
75
+ if (params.requestedDocuments && params.requestedDocuments.length > 0) {
76
+ const policy = this.store.getDocumentPolicy(project.project_id);
77
+ const docResult = DocumentProvider.collect(
78
+ project.canonical_path,
79
+ params.requestedDocuments,
80
+ policy
81
+ );
82
+ documentEvidence = docResult.documents;
83
+ if (docResult.gaps) gaps.push(...docResult.gaps);
84
+ }
85
+
86
+ // 3. Assemble checkpoint record
87
+ const checkpointId = generateUuid();
88
+ const now = new Date().toISOString();
89
+
90
+ const checkpoint = {
91
+ schema_version: SCHEMA_VERSION_CHECKPOINT,
92
+ checkpoint_id: checkpointId,
93
+ project_id: project.project_id,
94
+ task_id: task.task_id,
95
+ parent_id: parentId,
96
+ timestamp: now,
97
+ author: {
98
+ name: params.authorName || 'developer',
99
+ harness: params.authorHarness || 'antigravity'
100
+ },
101
+ summary: params.summary,
102
+ claims: {
103
+ goals: params.goals || [],
104
+ constraints: params.constraints || [],
105
+ decisions: params.decisions || [],
106
+ next_steps: params.nextSteps || [],
107
+ open_questions: params.openQuestions || []
108
+ },
109
+ evidence: {
110
+ git: gitEvidence,
111
+ documents: documentEvidence,
112
+ gaps
113
+ },
114
+ metadata: params.metadata || {}
115
+ };
116
+
117
+ // 4. Save atomically in store
118
+ return this.store.saveCheckpointAtomic(checkpoint, requestHash, params.operationId);
119
+ }
120
+ }