vectorforce 0.3.4 → 0.3.7

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/CHANGELOG.md CHANGED
@@ -7,7 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
- ## [Unreleased]
10
+ ## [0.3.5] - 2026-11-04
11
+
12
+ ### Added
13
+ - Dynamic project semantic topology layout and AST import dependency edge mapping in Studio `/api/graph`.
14
+ - Live workspace AST entity symbols extraction and allowlisted document evidence collection (SHA-256 digests and file sizes) in `/api/memory`.
15
+ - Task scope selector and live Git branch/commit/dirty badges in the Studio header.
16
+
17
+ ### Fixed
18
+ - Studio Project Graph and Memory views now dynamically reflect the active workspace repository rather than fallback mock structures.
19
+ - Dashboard metric cards (Health Score, AI Reasoning, Ontology & Memory, Provenance Chain) and recent checkpoint cards are now interactive and route to their respective views (`timeline`, `settings`, `memory`, `graph`).
20
+
21
+ ## [0.3.4] - 2026-11-04
11
22
 
12
23
  ### Fixed
13
24
  - Cross-platform MCP availability: generated harness configs now use the absolute Node.js runtime (`process.execPath`) with `"type": "stdio"` instead of bare `node`, so GUI-launched clients on Windows, macOS, and Linux can spawn `vf mcp` even when `node` is not on their PATH.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vectorforce",
3
- "version": "0.3.4",
3
+ "version": "0.3.7",
4
4
  "description": "Portable engineering context platform - Git for AI context",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -13,6 +13,7 @@ import { ReasoningService } from '../operations/reasoning-service.js';
13
13
  import { CodeIndexProvider } from '../providers/code-index-provider.js';
14
14
  import { LlmProvider, resolveLlmConfig } from '../providers/llm-provider.js';
15
15
  import { GitProvider } from '../providers/git-provider.js';
16
+ import { DocumentProvider } from '../providers/document-provider.js';
16
17
  import { HarnessConfig } from '../mcp/harness-config.js';
17
18
 
18
19
  export function createStudioServer({
@@ -136,7 +137,7 @@ export function createStudioServer({
136
137
  const allConstraints = new Set();
137
138
 
138
139
  for (const cp of checkpoints) {
139
- if (cp.claims.decisions) {
140
+ if (cp.claims?.decisions) {
140
141
  for (const d of cp.claims.decisions) {
141
142
  if (!seenDecisions.has(d.id)) {
142
143
  seenDecisions.add(d.id);
@@ -144,18 +145,58 @@ export function createStudioServer({
144
145
  }
145
146
  }
146
147
  }
147
- if (cp.claims.constraints) {
148
+ if (cp.claims?.constraints) {
148
149
  for (const c of cp.claims.constraints) {
149
150
  allConstraints.add(c);
150
151
  }
151
152
  }
152
153
  }
153
154
 
155
+ // Collect allowlisted docs details (sizes, hashes) safely
156
+ let docDetails = [];
157
+ try {
158
+ const collected = DocumentProvider.collect(workspacePath, allowedDocs, allowedDocs);
159
+ docDetails = (collected.documents || []).map(d => ({
160
+ path: d.relative_path,
161
+ size: d.size_bytes ? (d.size_bytes < 1024 ? `${d.size_bytes} B` : `${(d.size_bytes / 1024).toFixed(1)} kB`) : '0 B',
162
+ hash: d.sha256_hash ? d.sha256_hash.slice(0, 10) + '...' : 'tracked',
163
+ status: 'Verified'
164
+ }));
165
+ for (const g of (collected.gaps || [])) {
166
+ docDetails.push({
167
+ path: g.relative_path,
168
+ size: 'Missing',
169
+ hash: 'N/A',
170
+ status: 'File Missing'
171
+ });
172
+ }
173
+ } catch {
174
+ docDetails = (allowedDocs || []).map(p => ({ path: p, size: 'Tracked', hash: 'allowlisted', status: 'Verified' }));
175
+ }
176
+
177
+ // Extract real top AST symbols from code index
178
+ const topSymbols = [];
179
+ for (const f of codeIndex.files) {
180
+ if (f.symbols) {
181
+ for (const c of (f.symbols.classes || [])) {
182
+ topSymbols.push({ name: c, type: 'Class', file: f.path });
183
+ }
184
+ for (const fn of (f.symbols.functions || [])) {
185
+ topSymbols.push({ name: fn, type: 'Function', file: f.path });
186
+ }
187
+ for (const exp of (f.symbols.exports || [])) {
188
+ topSymbols.push({ name: exp, type: 'Export', file: f.path });
189
+ }
190
+ }
191
+ }
192
+
154
193
  res.writeHead(200, { 'Content-Type': 'application/json' });
155
194
  res.end(JSON.stringify({
156
195
  filesIndexed: codeIndex.files.length,
157
196
  symbolsIndexed: codeIndex.files.reduce((acc, f) => acc + ((f.symbols?.functions?.length || 0) + (f.symbols?.classes?.length || 0) + (f.symbols?.exports?.length || 0)), 0),
158
197
  allowedDocuments: allowedDocs,
198
+ allowedDocsList: docDetails,
199
+ topSymbols: topSymbols.slice(0, 30),
159
200
  decisions: allDecisions,
160
201
  constraints: Array.from(allConstraints),
161
202
  totalCheckpoints: checkpoints.length
@@ -188,6 +229,12 @@ export function createStudioServer({
188
229
  const codeIndex = CodeIndexProvider.build(workspacePath);
189
230
  const checkpoints = store.getCheckpoints(project.project_id);
190
231
  const changedFiles = new Set(GitProvider.collectChangedFiles(workspacePath).files || []);
232
+ const requestedTaskId = url.searchParams.get('taskId') || null;
233
+
234
+ // If a task is selected, get its allowlisted docs or touched files
235
+ const taskCheckpoints = requestedTaskId
236
+ ? checkpoints.filter(cp => cp.task_id === requestedTaskId)
237
+ : checkpoints;
191
238
 
192
239
  // Dynamically cluster code files by architectural module
193
240
  const moduleMap = new Map();
@@ -207,7 +254,7 @@ export function createStudioServer({
207
254
  } else if (file.path.startsWith('bin/')) {
208
255
  modId = 'cli';
209
256
  } else {
210
- modId = 'project-root';
257
+ modId = parts.length > 1 ? parts[0] : 'root';
211
258
  }
212
259
 
213
260
  if (!moduleMap.has(modId)) {
@@ -221,15 +268,26 @@ export function createStudioServer({
221
268
  'core': 'Core Hashes & Errors',
222
269
  'cli': 'Unified CLI Interface',
223
270
  'studio-ui': 'React Studio Frontend',
224
- 'tests': 'Automated Test Suite (62 Tests)',
225
- 'wiki': 'Architecture Wiki (ADR 001-014)',
226
- 'project-root': 'Project Root & Config'
271
+ 'tests': 'Automated Test Suite',
272
+ 'wiki': 'Architecture Wiki',
273
+ 'root': 'Project Root & Config',
274
+ 'backend': 'Backend Services',
275
+ 'frontend': 'Frontend Application',
276
+ 'api': 'API Routes & Handlers',
277
+ 'models': 'Data Models & Schemas',
278
+ 'controllers': 'Controllers',
279
+ 'routes': 'Router Layer',
280
+ 'services': 'Domain Services',
281
+ 'utils': 'Utility Helpers'
227
282
  };
283
+ const capitalizedMod = modId.charAt(0).toUpperCase() + modId.slice(1);
228
284
  moduleMap.set(modId, {
229
285
  id: modId,
230
- name: prettyNames[modId] || modId.toUpperCase(),
231
- category: (modId === 'policy' || modId === 'storage') ? 'Security & Data' : modId === 'providers' ? 'AI / LLM' : modId === 'mcp' ? 'Integration' : modId === 'wiki' ? 'Governance' : 'Core',
232
- owner: 'VectorForce',
286
+ name: prettyNames[modId] || capitalizedMod,
287
+ category: (modId === 'policy' || modId === 'storage' || modId === 'auth' || modId === 'security') ? 'Security' :
288
+ (modId === 'providers' || modId === 'ai' || modId === 'llm') ? 'AI / Copilot' :
289
+ (modId === 'mcp' || modId === 'api' || modId === 'routes') ? 'Integration' : 'Core',
290
+ owner: project.name || 'Workspace',
233
291
  files: [],
234
292
  drift: false,
235
293
  dependencies: new Set(),
@@ -259,23 +317,51 @@ export function createStudioServer({
259
317
 
260
318
  // Link real ADR decisions from checkpoints
261
319
  const allDecisions = [];
262
- for (const cp of checkpoints) {
320
+ for (const cp of taskCheckpoints) {
263
321
  if (cp.claims?.decisions) {
264
322
  for (const d of cp.claims.decisions) {
265
- allDecisions.push(`${d.id}: ${d.title}`);
323
+ allDecisions.push(`${d.id}: ${d.title || d.rationale || ''}`);
266
324
  }
267
325
  }
268
326
  }
269
327
 
270
- const modules = Array.from(moduleMap.values()).map(m => ({
271
- ...m,
272
- status: m.drift ? 'Drift Detected' : 'Verified',
273
- dependencies: Array.from(m.dependencies),
274
- decisions: allDecisions.slice(0, 3)
275
- }));
328
+ // Compute clean 2D grid coordinates for modules
329
+ const modArray = Array.from(moduleMap.values());
330
+ const cols = Math.min(3, Math.max(2, Math.ceil(Math.sqrt(modArray.length))));
331
+ const colWidth = 270;
332
+ const rowHeight = 160;
333
+ const startX = 40;
334
+ const startY = 40;
335
+
336
+ const connections = [];
337
+ for (const [fromMod, mod] of moduleMap.entries()) {
338
+ for (const toMod of mod.dependencies) {
339
+ connections.push({ from: fromMod, to: toMod });
340
+ }
341
+ }
342
+
343
+ const modules = modArray.map((m, idx) => {
344
+ const col = idx % cols;
345
+ const row = Math.floor(idx / cols);
346
+ return {
347
+ ...m,
348
+ x: startX + col * colWidth,
349
+ y: startY + row * rowHeight,
350
+ status: m.drift ? 'Drift Detected' : 'Verified',
351
+ dependencies: Array.from(m.dependencies),
352
+ decisions: allDecisions.slice(0, 3),
353
+ files: m.files.length,
354
+ description: `${m.files.length} file${m.files.length === 1 ? '' : 's'} · ${m.symbolsCount} symbols indexed.`
355
+ };
356
+ });
276
357
 
277
358
  res.writeHead(200, { 'Content-Type': 'application/json' });
278
- res.end(JSON.stringify({ modules }));
359
+ res.end(JSON.stringify({
360
+ modules,
361
+ connections,
362
+ activeTask: requestedTaskId,
363
+ totalFiles: codeIndex.files.length
364
+ }));
279
365
  return;
280
366
  }
281
367