Backend (Spring Boot 3.2 / Java 21 / PostgreSQL): - JWT auth with BCrypt password hashing - User profile + Mifflin-St Jeor BMR calculator - Food search + barcode via OpenFoodFacts API with local cache - Meal CRUD with user data isolation and ownership checks - AI photo analysis (OpenAI Vision) with confidence intervals - AI correction feedback loop for personalisation - Flyway DB migrations + RFC-7807 error responses Mobile (React Native / TypeScript): - Full navigation stack (Auth → Tabs → Home stack) - Design tokens (WCAG 2.2 AA colours, 8px grid, 48px touch targets) - 10 screens: Login, Register, Home, Search, Camera, AI Result, Edit Meal, Daily Details, History, Profile - Confidence-aware calorie display (kcal ± range) - Repeat last meal shortcut + macro tracking Docs: - docs/PLAN-AND-REQUIREMENTS.md - docs/traceability.csv (35 requirements, all Implemented)
14 KiB
Extension intercepts user actions before they reach the filesystem. Governance validation is mandatory.
Extension Standards - Layer 3
Module: Extension Standards Component: Layer 3 (VS Code Extension) Load: When working on virsaitis-development/virsaitis-extension/ Version: 3.0.0 Updated: 2026-04-20
🎯 Purpose
Defines VS Code Extension API standards, TypeScript conventions, and packaging workflow for Virsaitis Extension (Layer 3 user action interception).
🤖 Machine Policy
[TECHNOLOGY_STACK]
LANGUAGE=TypeScript 5.0+
FRAMEWORK=VS Code Extension API 1.85+
BUILD=webpack
PACKAGE=vsce
TEST=@vscode/test-electron
[CODE_STANDARDS]
INDENTATION=2_spaces
LINE_LENGTH=100_chars
API_VERSION=1.85.0
ACTIVATION=lazy_load
[QUALITY_GATES]
COMPILE=must_succeed
TESTS=must_pass
PACKAGE_SIZE=< 5MB
ACTIVATION_TIME=<200ms
📐 TypeScript Standards
Same as MCP layer: 2-space indentation, 100-char line length, single quotes, semicolons required. See MCP Standards for details.
🔧 VS Code Extension Architecture
Project Structure
virsaitis-development/virsaitis-extension/
├── src/
│ ├── extension.ts (entry point, activate/deactivate)
│ ├── governance/
│ │ ├── file-interceptor.ts (intercept file operations)
│ │ ├── mcp-client.ts (communicate with MCP server)
│ │ └── shield-decorator.ts (🛡️ UI indicator)
│ ├── commands/
│ │ ├── request-override.ts
│ │ └── show-governance-status.ts
│ ├── ui/
│ │ ├── status-bar.ts
│ │ ├── notifications.ts
│ │ └── webview-provider.ts
│ └── utils/
│ └── config.ts
├── test/
│ ├── suite/
│ │ ├── extension.test.ts
│ │ └── governance.test.ts
│ └── runTest.ts
├── resources/
│ └── icons/
│ └── shield.svg
├── package.json (extension manifest)
├── tsconfig.json
├── webpack.config.js
├── .vscodeignore
└── README.md
📦 Extension Manifest (package.json)
Essential Fields
{
"name": "virsaitis-extension",
"displayName": "Virsaitis Governance",
"description": "AI governance enforcement for VS Code",
"version": "2.0.0",
"publisher": "virsaitis",
"engines": {
"vscode": "^1.85.0"
},
"categories": ["Other"],
"activationEvents": [
"onStartupFinished",
"onCommand:virsaitis.requestOverride"
],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "virsaitis.requestOverride",
"title": "Virsaitis: Request Override"
},
{
"command": "virsaitis.showGovernanceStatus",
"title": "Virsaitis: Show Governance Status"
}
],
"configuration": {
"title": "Virsaitis",
"properties": {
"virsaitis.enabled": {
"type": "boolean",
"default": true,
"description": "Enable Virsaitis governance enforcement"
},
"virsaitis.mcpServerCommand": {
"type": "string",
"default": "node",
"description": "Command to start Virsaitis MCP server"
},
"virsaitis.mcpServerArgs": {
"type": "array",
"default": ["build/index.js"],
"description": "Arguments for Virsaitis MCP server process"
}
}
}
}
}
🚀 Activation (TIER-2)
Lazy Activation
PATTERN:
// extension.ts
export function activate(context: vscode.ExtensionContext) {
console.log('Virsaitis extension activating...');
// Register commands
context.subscriptions.push(
vscode.commands.registerCommand(
'virsaitis.requestOverride',
() => requestOverride()
)
);
// Initialize governance interceptor (lazy)
const interceptor = new FileInterceptor();
context.subscriptions.push(interceptor);
// Start MCP client connection
const mcpClient = new MCPClient();
context.subscriptions.push(mcpClient);
console.log('Virsaitis extension activated');
}
export function deactivate() {
console.log('Virsaitis extension deactivated');
}
Activation Events
RECOMMENDED:
onStartupFinished- Start when VS Code ready (lazy)onCommand:virsaitis.*- Activate on command- NOT
*- Don't activate on every event (performance)
TARGET: Activation time <200ms
⚡ CHECKPOINT — Does this file operation go through MCP validation first? Extension must not bypass governance.
🛡️ File Operation Interception (TIER-1)
Intercept File Save
export class FileInterceptor implements vscode.Disposable {
private _disposables: vscode.Disposable[] = [];
private _mcpClient: MCPClient;
constructor() {
this._mcpClient = new MCPClient();
// Intercept file save
this._disposables.push(
vscode.workspace.onWillSaveTextDocument(async (e) => {
const validation = await this.validateOperation(
'write',
e.document.uri.fsPath
);
if (!validation.allowed && validation.tier === 'TIER-0') {
// Block save for TIER-0 violation
e.waitUntil(this.blockSave(validation));
} else if (!validation.allowed && validation.tier === 'TIER-1') {
// Warn for TIER-1
await this.warnUser(validation);
}
})
);
}
private async validateOperation(
operation: string,
filePath: string
): Promise<ValidationResult> {
return await this._mcpClient.validateOperation(operation, filePath);
}
private async blockSave(validation: ValidationResult): Promise<void> {
const message = `TIER-0 VIOLATION: ${validation.reason}`;
await vscode.window.showErrorMessage(message, { modal: true });
throw new Error(message); // Prevents save
}
dispose() {
this._disposables.forEach(d => d.dispose());
}
}
🎨 UI Components
Status Bar Item
export class GovernanceStatusBar implements vscode.Disposable {
private _statusBarItem: vscode.StatusBarItem;
constructor() {
this._statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
this._statusBarItem.command = 'virsaitis.showGovernanceStatus';
this.update StatusBarItem.text = '$(shield) Virsaitis: Active';
this._statusBarItem.tooltip = 'Governance enforcement active';
this._statusBarItem.show();
}
public setStatus(status: 'active' | 'warning' | 'error'): void {
switch (status) {
case 'active':
this._statusBarItem.text = '$(shield) Virsaitis: Active';
this._statusBarItem.backgroundColor = undefined;
break;
case 'warning':
this._statusBarItem.text = '$(warning) Virsaitis: Warning';
this._statusBarItem.backgroundColor = new vscode.ThemeColor(
'statusBarItem.warningBackground'
);
break;
case 'error':
this._statusBarItem.text = '$(error) Virsaitis: Error';
this._statusBarItem.backgroundColor = new vscode.ThemeColor(
'statusBarItem.errorBackground'
);
break;
}
}
dispose() {
this._statusBarItem.dispose();
}
}
File Decoration (Shield Icon)
⚡ CHECKPOINT — MCP client uses stdio transport (StdioClientTransport), not HTTP fetch. Verify.
export class ShieldDecorator implements vscode.Disposable {
private _disposables: vscode.Disposable[] = [];
constructor() {
const decorationType = vscode.window.createTextEditorDecorationType({
gutterIconPath: vscode.Uri.file('resources/icons/shield.svg'),
gutterIconSize: '80%',
});
// Apply to protected files
this._disposables.push(
vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor && this.isProtectedFile(editor.document.uri)) {
const range = new vscode.Range(0, 0, 0, 0);
editor.setDecorations(decorationType, [{ range }]);
}
})
);
}
private isProtectedFile(uri: vscode.Uri): boolean {
const path = uri.fsPath;
const protectedPatterns = [
'.github/copilot-instructions.md',
'.github/copilot-modules/',
'.github/agents/',
'virsaitis-development/virsaitis-requirements/',
];
return protectedPatterns.some(pattern => path.includes(pattern));
}
dispose() {
this._disposables.forEach(d => d.dispose());
}
}
🔌 MCP Client Communication
Virsaitis MCP uses stdio transport (not HTTP). The extension spawns the MCP server as a child process and communicates via stdin/stdout.
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
export class MCPClient {
private _client: Client;
private _transport: StdioClientTransport;
constructor() {
const config = vscode.workspace.getConfiguration('virsaitis');
const command = config.get<string>('mcpServerCommand', 'node');
const args = config.get<string[]>('mcpServerArgs', ['build/index.js']);
this._transport = new StdioClientTransport({ command, args });
this._client = new Client({ name: 'virsaitis-extension', version: '3.0.0' });
}
async connect(): Promise<void> {
await this._client.connect(this._transport);
}
async validateOperation(
operation: string,
filePath: string
): Promise<ValidationResult> {
try {
const result = await this._client.callTool({
name: 'validate_operation',
arguments: { operation, filePath },
});
return result.content[0].text as unknown as ValidationResult;
} catch (error) {
console.error('MCP client error:', error);
// Fail open (allow operation if MCP unavailable)
return { allowed: true, reason: 'MCP server unavailable' };
}
}
async dispose(): Promise<void> {
await this._transport.close();
}
}
⚡ CHECKPOINT — All UI accessible? Keyboard navigation, focus indicators, WCAG 2.2 AA.
🧪 Testing
Extension Test Setup
// test/suite/extension.test.ts
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Extension should activate', async () => {
const extension = vscode.extensions.getExtension('virsaitis.virsaitis-extension');
assert.ok(extension);
await extension.activate();
assert.strictEqual(extension.isActive, true);
});
test('Should register commands', async () => {
const commands = await vscode.commands.getCommands();
assert.ok(commands.includes('virsaitis.requestOverride'));
assert.ok(commands.includes('virsaitis.showGovernanceStatus'));
});
});
Run Tests
npm test
Tests run in Extension Development Host (isolated VS Code instance).
📦 Packaging & Distribution
Build Extension
# Compile TypeScript + webpack bundle
npm run compile
# Run tests
npm test
# Package extension (.vsix file)
vsce package
OUTPUT: virsaitis-extension-2.0.0.vsix
Package Size
TARGET: <5MB
CHECK:
ls -lh *.vsix
Key Rules From This Module
- Extension validates every file operation through MCP before allowing it.
- MCP client uses stdio transport via
@modelcontextprotocol/sdk, not HTTP. - All UI must meet WCAG 2.2 AA. Keyboard navigation mandatory.
- Extension must degrade gracefully if MCP server is unavailable.
- Definitions:
.github/virsaitis-definition-library.md
Return to hub: .github/copilot-instructions.md
REDUCE SIZE:
- Exclude test files (
.vscodeignore) - Exclude source maps in production
- Minimize dependencies
- Use webpack production mode
.vscodeignore
.vscode/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts
src/**
test/**
node_modules/**
!node_modules/@modelcontextprotocol/**
webpack.config.js
🔧 Configuration
Extension Settings
Users can configure via VS Code settings:
{
"virsaitis.enabled": true,
"virsaitis.mcpServerCommand": "node",
"virsaitis.mcpServerArgs": ["build/index.js"],
"virsaitis.showShieldIcons": true,
"virsaitis.blockTier0": true
}
Reading Configuration
const config = vscode.workspace.getConfiguration('virsaitis');
const enabled = config.get<boolean>('enabled', true);
const mcpCommand = config.get<string>('mcpServerCommand', 'node');
💡 Best Practices
Disposal Pattern
Always implement vscode.Disposable:
export class MyComponent implements vscode.Disposable {
private _disposables: vscode.Disposable[] = [];
constructor() {
this._disposables.push(
// Register event handlers, commands, etc.
);
}
dispose() {
this._disposables.forEach(d => d.dispose());
}
}
Error Handling
try {
await riskyOperation();
} catch (error) {
// Log for debugging
console.error('Operation failed:', error);
// Show user-friendly message
vscode.window.showErrorMessage(
'Operation failed. Please check Virsaitis logs.'
);
}
Performance
- Use lazy loading
- Debounce frequent events
- Cache expensive operations
- Minimize synchronous work on activation
📚 Quick Reference
| Aspect | Standard | Command |
|---|---|---|
| Build | Webpack | npm run compile |
| Test | @vscode/test-electron | npm test |
| Package | vsce | vsce package |
| Size | <5MB | Check .vsix |
| Activation | <200ms | Lazy load |
Extension Standards Module v3.0.0 VS Code user action interception layer