Files
calorie-counter/.github/copilot-modules/extension-standards.md
Andris Enins 91cd18aec6 feat: initial implementation — all 35 requirements across phases 1-3
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)
2026-05-18 21:56:13 +03:00

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