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)
575 lines
14 KiB
Markdown
575 lines
14 KiB
Markdown
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](mcp-standards.md) 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
|
|
|
|
```json
|
|
{
|
|
"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**:
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
Tests run in Extension Development Host (isolated VS Code instance).
|
|
|
|
---
|
|
|
|
## 📦 Packaging & Distribution
|
|
|
|
### Build Extension
|
|
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"virsaitis.enabled": true,
|
|
"virsaitis.mcpServerCommand": "node",
|
|
"virsaitis.mcpServerArgs": ["build/index.js"],
|
|
"virsaitis.showShieldIcons": true,
|
|
"virsaitis.blockTier0": true
|
|
}
|
|
```
|
|
|
|
### Reading Configuration
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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*
|