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.
- package/.agents/agents/AGENTS.md +12 -0
- package/.agents/agents/vectorforce-architect.md +35 -0
- package/.agents/agents/vectorforce-auditor.md +25 -0
- package/.agents/agents/vectorforce-samples-builder.md +30 -0
- package/.agents/mcp_config.json +11 -0
- package/.codex/config.toml +5 -0
- package/AGENTS.md +115 -0
- package/LICENSE +21 -0
- package/README.md +198 -2
- package/bin/vf.js +1159 -0
- package/codex.json +14 -0
- package/commandcode.json +11 -0
- package/musecode.json +14 -0
- package/package.json +40 -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 +546 -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 +425 -0
- package/src/storage/sqlite-store.js +387 -0
- package/src/studio/server.js +764 -0
- package/studio/dist/assets/index-C0_eVpV1.js +9 -0
- package/studio/dist/assets/index-CxTK-2ty.css +2 -0
- package/studio/dist/favicon.svg +1 -0
- package/studio/dist/icons.svg +24 -0
- 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
|
+
}
|