update the repo

This commit is contained in:
DatTT127
2026-06-23 13:39:43 +07:00
parent 3283a8f8af
commit fdb384434d
61 changed files with 1450 additions and 226 deletions

View File

@@ -0,0 +1,19 @@
# VKIST MSK Pilot — Shared Agent Enforcement Skills
## Goal
Create up to 5 shareable agent skills under `PILOT_PROJECT/AGENT_SKILL/` that automatically guide coding agents (Claude Code, Gemini CLI, KILO) to follow project conventions from `README.md` and architecture specs.
## How to add a new skill?
1. Create a new folder under AGENT_SKILL/<skill_name>/
2. Add SKILL.md, EXAMPLES.md, CHECKLIST.md
3. Update this INTRODUCTION.md index
4. Open a PR with at least one EXAMPLES.md entry per rule
## Skills Index
| Skill | Covers |
|-------|--------|
| file_naming_convention | File/dir names, forbidden extensions, LEGACY/ policy |
| coding_convention | Python PEP8, TS Airbnb, docstrings, error handling, RBAC |
| interface_contract_hygiene | Contract files, versioning, breaking-change policy |
| secrets_and_phi_safety | Secrets loading, PHI exclusion, CI safety, gitignore |
| design_pattern_compliance | Layered arch, DI, Zustand, Terraform IaC, pinning |

View File

@@ -0,0 +1,45 @@
# Checklist: coding_convention
Instructions: For each item below, mark whether the change complies with the rule.
## Python Conventions
- [ ] File names use snake_case (e.g., `patient_service.py`)
- [ ] Class names use PascalCase (e.g., `PatientService`)
- [ ] Function and variable names use snake_case (e.g., `get_patient_by_id`)
- [ ] Constants use UPPER_SNAKE_CASE (e.g., `MAX_PATIENTS_PER_PAGE`)
- [ ] Line length does not exceed 100 characters
- [ ] Every public function, class, and module has a docstring
- [ ] Docstrings follow Google/NumPy style (Args, Returns, Side Effects)
- [ ] No `print()` statements; logging is used instead
- [ ] Exceptions are typed and not overly broad (avoid bare `except:`)
- [ ] FastAPI routers use `APIRouter(prefix=...)`
## TypeScript/React Conventions
- [ ] File names use snake_case (e.g., `patient_list.tsx`)
- [ ] Component names use PascalCase (e.g., `PatientList`)
- [ ] Hook names use `useSnakeCase` (e.g., `usePatientData`)
- [ ] Follows Airbnb JavaScript/TypeScript style guide
- [ ] No `console.log()` in production code (use proper logging)
## Error Handling
- [ ] No `print()` or `console.log()` for error reporting
- [ ] Errors are logged at appropriate level (debug/info/warn/error)
- [ ] Custom exceptions are used where appropriate
- [ ] Exceptions are not swallowed without handling
## RBAC
- [ ] Only use role constants: `RO_RADIOLOGIST`, `RO_THERAPIST`
- [ ] No hard-coded role strings in conditionals
- [ ] Role checks are centralized where possible
## Configuration
- [ ] No hard-coded endpoints, API keys, or configuration values
- [ ] Configuration loaded via environment variables or secrets/
- [ ] `secrets/` directory is properly gitignored
- [ ] No secrets in code, logs, or comments
## Commit Messages
- [ ] Commit message follows Conventional Commits format
- [ ] Type is one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
- [ ] Scope is provided when applicable
- [ ] Subject is imperative, lowercase, no trailing period

View File

@@ -0,0 +1,139 @@
# Examples: coding_convention
## Correct Examples
### Python
```python
"""
Module: patient_service
Purpose: Handle patient-related business logic.
"""
from typing import List, Optional
from fastapi import APIRouter, HTTPException
from models.patient import PatientModel
from services.database import DatabaseService
router = APIRouter(prefix="/patients", tags=["patients"])
class PatientService:
"""Service for managing patient records.
Attributes:
db: Database service instance
"""
def __init__(self, db: DatabaseService):
self.db = db
def get_patient_by_id(self, patient_id: str) -> Optional[PatientModel]:
"""Retrieve a patient by their ID.
Args:
patient_id: Unique identifier for the patient
Returns:
PatientModel if found, None otherwise
Side Effects:
None
"""
try:
return self.db.get_patient(patient_id)
except DatabaseError as e:
logger.error(f"Failed to retrieve patient {patient_id}: {e}")
raise
```
### TypeScript/React
```typescript
import { create } from 'zustand';
import { useEffect } from 'react';
// Hook name follows useSnakeCase
const usePatientStore = create<PatientState>((set, get) => ({
patients: [],
loading: false,
error: null,
fetchPatients: async () => {
set({ loading: true, error: null });
try {
const response = await api.getPatients();
set({ patients: response.data, loading: false });
} catch (err) {
set({ error: err.message, loading: false });
}
},
}));
export const PatientList: React.FC = () => {
const { patients, loading, error } = usePatientStore();
// Component name: PascalCase
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{patients.map(patient => (
<li key={patient.id}>{patient.name}</li>
))}
</ul>
);
};
```
### Error Handling
```python
# Good: Using logging instead of print
import logging
logger = logging.getLogger(__name__)
def process_data(data: dict) -> dict:
try:
# Process data
result = transform_data(data)
return result
except ValidationError as e:
logger.warning(f"Validation failed: {e}")
raise InvalidInputError(str(e)) from e
except Exception as e:
logger.error(f"Unexpected error in process_data: {e}")
raise
```
### RBAC
```python
# Good: Using role constants
from auth.roles import RO_RADIOLOGIST, RO_THERAPIST
def can_read_patient_records(user_role: str) -> bool:
return user_role in [RO_RADIOLOGIST, RO_THERAPIST]
# Bad: Hard-coded role strings
def can_read_patient_records_bad(user_role: str) -> bool:
return user_role in ["radiologist", "therapist"] # VIOLATION
```
### Configuration
```python
# Good: Loading from environment
import os
API_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8000")
MAX_UPLOAD_SIZE = int(os.environ.get("MAX_UPLOAD_SIZE", "10485760")) # 10MB
# Bad: Hard-coded values
API_BASE_URL = "http://localhost:8000" # VIOLATION
```
### Commit Messages
```
feat(auth): add JWT refresh token endpoint
fix(patient-api): fix patient ID validation typo
docs(api): update patient endpoint documentation
refactor(core): extract database connection logic
```

View File

@@ -0,0 +1,59 @@
# Skill: coding_convention
## Trigger Conditions
Any code edit or creation (files with extensions: .py, .js, .ts, .tsx, .java, .cpp, .h, etc.)
## Rules
### 1. Python Conventions
- Follow PEP 8 with line length ≤ 100 characters
- Use `ruff`-compatible formatting
- Class names: `PascalCase`
- Function and variable names: `snake_case`
- Constants: `UPPER_SNAKE_CASE`
- FastAPI routers should be grouped under `APIRouter(prefix=...)`
- Every public function, class, and module must have a docstring:
- Purpose: brief description
- Parameters: list with types and descriptions
- Return: return type and description
- Side-effects: note any side effects (if applicable)
- Severity: `[ERROR]`
### 2. TypeScript/JavaScript Conventions
- Follow Airbnb JavaScript Style Guide
- Use ESLint with `eslint-config-airbnb` as base
- Component names: `PascalCase`
- Hook names: `useSnakeCase` (e.g., `useUserData`)
- File names: `snake_case.tsx` or `snake_case.ts`
- Use TypeScript strict mode
- Prefer interfaces over types for object shapes
- Export constants as `const` in UPPER_SNAKE_CASE
- Severity: `[ERROR]`
### 3. Error Handling
- Never use `print()` for logging or debugging
- Use appropriate logging levels: `debug`, `info`, `warn`, `error`
- Raise typed exceptions (custom exceptions preferred over generic Exception)
- Handle exceptions at appropriate levels; do not swallow exceptions silently
- Severity: `[ERROR]`
### 4. RBAC (Role-Based Access Control)
- Use only declared role constants for permission checks:
- `RO_RADIOLOGIST` (Read-Only Radiologist)
- `RO_THERAPIST` (Read-Only Therapist)
- Do not hard-code role strings in conditionals
- Severity: `[ERROR]`
### 5. Configuration Management
- No hard-coded endpoints, API keys, or configuration values
- Load configuration from environment variables via `os.environ.get()` (Python) or `process.env` (Node.js)
- Secrets must be loaded from the `secrets/` directory or environment variables only
- Severity: `[ERROR]`
### 6. Commit Messages
- Follow Conventional Commits format: `<type>(<scope>): <subject>`
- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
- Scope should be the module or component affected (optional but recommended)
- Subject should be imperative, lowercase, no trailing period
- Body and footer optional but recommended for complex changes
- Severity: `[WARN]`

View File

@@ -0,0 +1,51 @@
# Checklist: design_pattern_compliance
## Layered Architecture (Backend)
- [ ] No direct calls from presentation layer to data access layer
- [ ] Controllers only handle HTTP concerns (validation, serialization, routing)
- [ ] Business logic resides in service layer
- [ ] Data access layer handles only persistence concerns
- [ ] No upward dependencies (data layer does not depend on service layer)
## Dependency Injection
- [ ] No direct instantiation of infrastructure dependencies (e.g., `new MongoClient()`)
- [ ] Dependencies are injected (constructor, setter, or framework DI)
- [ ] Classes depend on interfaces/protocols, not concrete implementations
- [ ] Framework-appropriate DI mechanisms are used (e.g., FastAPI Depends, Spring @Autowired)
## Interface-Driven Development
- [ ] Business logic depends on abstractions (interfaces/repositories)
- [ ] No direct infrastructure calls in business logic (no `boto3`, `psycopg2.connect()`, etc. in services)
- [ ] Concrete implementations exist for interfaces but are injected, not instantiated internally
## Frontend State Management (Zustand)
- [ ] Global shared state is managed in Zustand stores
- [ ] Component state (`useState`) is used only for:
- Form inputs and UI controls
- Temporary UI state (loading states for local effects, animation states)
- Data that is truly isolated to a single component
- [ ] Store follows naming convention: `use[Domain]Store`
- [ ] Store actions are named as verbs
- [ ] No business logic in components that should be in store actions
## Infrastructure as Code
- [ ] Terraform configuration uses a remote backend (GCS, S3, etc.)
- [ ] No local `terraform.tfstate` files committed
- [ ] Resources are named with environment identifier (e.g., `${var.environment}`)
- [ ] IAM roles follow least privilege principle (specific permissions, no wildcards)
- [ ] Separate configurations or workspaces for different environments
- [ ] All providers and modules have explicit version pins
- [ ] Terraform files are formatted (`terraform fmt`)
## Dependency Pinning
- [ ] Python: `requirements.txt` or `pyproject.toml` contains exact versions (no `~`, `>`, `<` without corresponding lock file)
- [ ] Python: If using `pyproject.toml` with poetry or pipenv, lock file (`poetry.lock` or `Pipfile.lock`) is present
- [ ] Node.js: `package-lock.json` or `yarn.lock` is present and matches `package.json`
- [ ] Other languages: Equivalent lock files are present and committed
- [ ] No unpinned dependencies that could cause non-reproducible builds
## Code Quality
- [ ] Duplicated code has been considered for extraction
- [ ] Preference given to composition over inheritance where applicable
- [ ] Utility functions are placed in appropriate shared locations
- [ ] No "God classes" or objects with too many responsibilities

View File

@@ -0,0 +1,363 @@
# Examples: design_pattern_compliance
## Correct Examples
### Layered Architecture (Python/FastAPI)
```python
# GOOD: Proper layer separation
# presentation layer (api/patients.py)
from fastapi import APIRouter, Depends, HTTPException
from services.patient_service import PatientService
from models.patient import PatientResponse
router = APIRouter(prefix="/patients", tags=["patients"])
def get_patient_service() -> PatientService:
# Dependency injection - could come from a DI container
return PatientService(
repository=PatientRepository() # In practice, this would be injected
)
@router.get("/{patient_id}", response_model=PatientResponse)
def get_patient(
patient_id: str,
service: PatientService = Depends(get_patient_service)
):
"""Controller handles only HTTP concerns."""
patient = service.get_patient_by_id(patient_id)
if not patient:
raise HTTPException(status_code=404, detail="Patient not found")
return patient
# service layer (services/patient_service.py)
from repositories.patient_repository import PatientRepository
from models.patient import PatientModel
class PatientService:
def __init__(self, repository: PatientRepository):
self.repository = repository # Dependency injected
def get_patient_by_id(self, patient_id: str) -> Optional[PatientModel]:
# Business logic lives here
return self.repository.get_by_id(patient_id)
# data access layer (repositories/patient_repository.py)
from models.patient import PatientModel
class PatientRepository:
def get_by_id(self, patient_id: str) -> Optional[PatientModel]:
# Data access concerns only
# In real implementation, this would query a database
pass
```
### Dependency Injection with Interfaces
```python
# GOOD: Interface-based dependencies
from abc import ABC, abstractmethod
from typing import Protocol
# Define interface (protocol)
class PatientRepository(Protocol):
def get_by_id(self, patient_id: str) -> Optional[PatientModel]: ...
def save(self, patient: PatientModel) -> bool: ...
# Concrete implementation
class PostgreSQLPatientRepository:
def __init__(self, connection_string: str):
self.connection_string = connection_string
def get_by_id(self, patient_id: str) -> Optional[PatientModel]:
# Implementation details
pass
# Service depends on interface, not concrete class
class PatientService:
def __init__(self, repository: PatientRepository): # Accepts any implementation
self.repository = repository
def get_patient(self, patient_id: str) -> Optional[PatientModel]:
return self.repository.get_by_id(patient_id)
# Usage with dependency injection
def get_patient_service() -> PatientService:
repo = PostgreSQLPatientRepository(os.getenv("DB_CONNECTION"))
return PatientService(repository)
```
### Frontend Zustand Store
```javascript
// GOOD: Proper Zustand store usage
import { create } from 'zustand';
// Patient store slice
const usePatientStore = create((set, get) => ({
// State
patients: [],
loading: false,
error: null,
// Actions
setPatients: (patients) => set({ patients }),
fetchPatients: async () => {
set({ loading: true, error: null });
try {
const response = await api.getPatients();
set({ patients: response.data, loading: false });
} catch (err) {
set({ error: err.message, loading: false });
}
},
addPatient: (patient) => set((state) => ({
patients: [...state.patients, patient],
loading: false,
error: null
})),
}));
// Component using store - ONLY for UI state
import { useState } from 'react';
import { usePatientStore } from '../store/patientStore';
export const PatientForm: React.FC = () => {
// LOCAL UI state only - correct use of useState
const [name, setName] = useState('');
const [dob, setDob] = useState('');
const [submitting, setSubmitting] = useState(false);
// GLOBAL state from store
const { patients, loading, error, addPatient } = usePatientStore();
const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
try {
await addPatient({ id: Date.now(), name, dob });
setName('');
setDob('');
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields using local state */}
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
<input value={dob} onChange={(e) => setDob(e.target.value)} placeholder="Date of Birth" />
<button type="submit" disabled={submitting}>
{submitting ? 'Adding...' : 'Add Patient'}
</button>
{/* Displaying global state */}
{loading && <p>Saving...</p>}
{error && <p>Error: {error}</p>}
</form>
);
};
```
### Infrastructure as Code (Terraform)
```hcl
# GOOD: Proper Terraform practices
terraform {
# REQUIRED: Remote backend
backend "gcs" {
bucket = "vkist-terraform-state"
prefix = "env/${var.environment}"
}
# REQUIRED: Versioned providers
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
}
}
variable "environment" {
description = "Deployment environment"
type = string
validation {
condition = ["dev", "staging", "prod"].contains(var.upper)
error_message = "Environment must be either dev, staging, or prod."
}
}
# RESOURCE NAMING with environment
resource "google_compute_instance" "vm" {
name = "vkist-${var.environment}-backend"
machine_type = "e2-medium"
# ... other configuration
}
# LEAST PRIVILEGE IAM
resource "google_service_account" "backend" {
account_id = "vkist-backend-${var.environment}"
display_name = "VKIST Backend Service Account"
}
# Grant only necessary roles
resource "google_project_iam_member" "storage_reader" {
role = "roles/storage.objectViewer"
member = "serviceAccount:${google_service_account.backend.email}"
}
# VERSIONED MODULES
module "network" {
source = "terraform-google-modules/network/google"
version = "~> 5.0"
project_id = var.project_id
network_name = "vkist-${var.environment}-net"
# ... other parameters
}
```
### Dependency Pinning
```txt
# GOOD: requirements.txt with exact versions
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
sqlalchemy==2.0.23
alembic==1.13.1
python-multipart==0.0.6
```
```json
// GOOD: package.json with package-lock.json
{
"name": "vkist-frontend",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zustand": "^4.4.1",
"axios": "^1.6.2"
}
}
// package-lock.json ensures exact versions are installed
```
## Incorrect Examples (to avoid)
### Layer Violation
```python
# BAD: Controller directly accessing repository (skipping service layer)
from fastapi import APIRouter, Depends
from repositories.patient_repository import PatientRepository
router = APIRouter()
@router.get("/patients/{id}")
def get_patient(patient_id: str, repo: PatientRepository = Depends()):
# VIOLATION: Presentation layer accessing data layer directly
return repo.get_by_id(patient_id)
```
### Direct Infrastructure Instantiation
```python
# BAD: Service creating its own database connection
import psycopg2
import os
class PatientService:
def __init__(self):
# VIOLATION: Direct instantiation of infrastructure
self.conn = psycopg2.connect(
host=os.getenv("DB_HOST"),
database=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD")
)
def get_patient(self, patient_id: str):
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM patients WHERE id = %s", (patient_id,))
return cursor.fetchone()
```
### Frontend Anti-pattern: State in Components
```javascript
// BAD: Using useState for shared data that should be in store
import { useState, useEffect } from 'react';
import { fetchPatients } from '../api/patientApi';
export const PatientList: React.FC = () => {
// WRONG: This state is shared and should be in a store
const [patients, setPatients] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadPatients = async () => {
setLoading(true);
try {
const data = await fetchPatients();
setPatients(data); // This state might be needed by other components
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
};
loadPatients();
}, []);
// ... render logic
};
// If another component needs patients data, it would have to fetch again
// or receive it via props - inefficient and not scalable
```
### Infrastructure Mistakes
```hcl
# BAD: Local state storage (should be remote)
terraform {
# MISSING: backend configuration
# state will be stored locally - VIOLATION
}
# BAD: Hard-coded environment (should be variable)
resource "google_compute_network" "vpc" {
name = "vkist-prod-network" # What if we want to deploy to staging?
}
# BAD: Overly permissive IAM
resource "google_project_iam_member" "admin" {
role = "roles/editor" # Too permissive - VIOLATION of least privilege
member = "user:admin@example.com"
}
# BAD: Unpinned module version
module "network" {
source = "terraform-google-modules/network/google"
# MISSING: version parameter - could get breaking changes on next run
}
```
### Dependency Issues
```txt
# BAD: Unpinned dependencies in requirements.txt
fastapi
uvicorn
pydantic
# Any of these could break on next pip install
```
```json
// BAD: Missing or outdated lock file
{
"name": "my-app",
"dependencies": {
"lodash": "^4.17.21"
// No package-lock.json - versions not locked
}
}
```

View File

@@ -0,0 +1,96 @@
# Skill: design_pattern_compliance
## Trigger Conditions
- Creation of a new class, function, or module
- Significant refactoring of existing code (architectural changes)
- Adding new infrastructure (Terraform modules)
- Creating new state slices
)
## Rules
### 1. Layered Architecture (Backend)
- **Direction of Dependencies**:
- Presentation layer (API controllers) → Service layer → Data Access layer
- No skipping layers (e.g., controller directly accessing repository)
- No upward dependencies (data layer should not depend on service layer)
- **Separation of Concerns**:
- Controllers handle HTTP concerns only (validation, serialization, routing)
- Services contain business logic and orchestration
- Data Access/Repositories handle persistence concerns only
- Severity: `[ERROR]`
### 2. Dependency Injection and Inversion of Control
- **No Direct Instantiation**:
- Classes should not instantiate their dependencies directly (especially infrastructure clients)
- Dependencies should be injected via constructor or setter
- **Interface-Based Dependencies**:
- Depend on abstractions (interfaces/protocols) rather than concrete implementations
- Example: Service depends on `DatabaseRepository` interface, not `PostgreSQLRepository` directly
- **Framework-Compatible DI**:
- Use framework-provided dependency injection (e.g., FastAPI's `Depends`, Spring `@Autowired`)
- Avoid service locator pattern
- Severity: `[ERROR]`
### 3. Interface-Driven Development
- **Program to Interfaces**:
- Concrete classes should implement defined interfaces
- Clients should depend on interfaces, not implementations
- **No Concrete Infra in Business Logic**:
- Business logic should not contain direct calls to infrastructure (e.g., `boto3.client()`, `psycopg2.connect()`)
- Infrastructure access should be abstracted behind repository/service interfaces
- Severity: `[ERROR]`
### 4. Frontend State Management (Zustand)
- **Store Slices**:
- Global state should be split into logical slices using Zustand
- Each slice corresponds to a domain (e.g., `usePatientStore`, `useAppointmentStore`)
- **No Inline State for Shared Data**:
- Component state (`useState`) should only be used for:
- UI-specific state (form inputs, toggles, animation states)
- Data that is not shared across components
- Any data that is shared across components or needed by multiple hooks should be in a store
- **Store Structure**:
- Follow consistent naming: `use[Domain]Store`
- Actions should be named as verbs (e.g., `setPatients`, `fetchAppointments`)
- Selectors should be derived and memoized where appropriate
- Severity: `[ERROR]`
### 5. Infrastructure as Code (IaC) Best Practices
- **Terraform State**:
- All Terraform configurations must use a remote backend (GCS recommended for this project)
- No local `terraform.tfstate` files in the repository
- **Resource Naming**:
- Use consistent naming conventions that include environment (e.g., `vkist-${var.environment}-service`)
- **Least Privilege IAM**:
- IAM roles and policies must grant only the permissions necessary
- Avoid using wildcard permissions (`*`) or overly broad roles
- **Environment Separation**:
- Use separate workspaces or directories for different environments (e.g., `env/staging`, `env/prod`)
- Do not use the same resources for different environments
- **Version Pinning**:
- All external dependencies must be version-pinned:
- Terraform modules: `source = "terraform-google-modules/network/google//version"`
- Provider versions explicitly set
- Severity: `[ERROR]`
### 6. Dependency Pinning
- **Python**:
- `requirements.txt` or `pyproject.toml` must contain exact versions (no wildcards)
- Use `pip freeze > requirements.txt` for deterministic builds
- **Node.js**:
- `package-lock.json` or `yarn.lock` must be committed and up to date
- Dependencies in `package.json` should use caret (`^`) or tilde (`~`) ranges appropriately, but lock file ensures reproducibility
- **Other Ecosystems**:
- Equivalent lock files must be present and committed (e.g., `Pipfile.lock`, `poetry.lock`, `Cargo.lock`)
- Severity: `[ERROR]`
### 7. Code Reusability and Abstraction
- **Avoid Duplication**:
- Similar code in two or more places should be extracted to a shared utility or component
- **Prefer Composition Over Inheritance**:
- Especially in complex domain models, favor composing behavior over deep inheritance hierarchies
- **Utility Functions**:
- Place truly generic utilities in shared locations (e.g., `utils/`, `lib/`)
- Avoid creating utility classes with only static methods if not needed in the language
- Severity: `[WARN]`

View File

@@ -0,0 +1,18 @@
# Checklist: file_naming_convention
## File/Directory Creation/Rename/Move
- [ ] All new files and directories use `snake_case` (lowercase, digits, underscores only)
- [ ] No spaces or special characters in file/directory names
- [ ] Hyphens (`-`) used only in `docker-compose.yaml`, `docker-compose.yml`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
- [ ] `UPPER_SNAKE_CASE` used only for root-level configuration constants (e.g., `CONFIG.json`, `Dockerfile`)
- [ ] Forbidden extensions (`.pdf`, `.pptx`, `.docx`, `.xlsx`, `.csv`, `.zip`, `.pth`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.mp4`, `.mov`, `.avi`, `.dcm`, `.nii`, `.nii.gz`) are not present in the repository
- [ ] Any file under `LEGACY/` that is modified includes an inline comment `# LEGACY:` explaining the change
- [ ] Test files follow the pattern: `tests/<same_path_as_source>/test_<source_filename>`
## Verification Steps
1. Check all created/renamed/moved files for snake_case naming
2. Verify hyphen usage is limited to allowed files: docker-compose.yaml/.yml, package-lock.json, yarn.lock, pnpm-lock.yaml
3. Confirm UPPER_SNAKE_CASE appears only for root config files
4. Scan for forbidden file extensions
5. Inspect any changes in LEGACY/ directory for required comment
6. Validate test file placement matches source file structure

View File

@@ -0,0 +1,74 @@
# Examples: file_naming_convention
## Correct Examples
### File Naming
- `patient_data.csv` → ❌ (forbidden extension, should be external)
- `patient_data.json` → ✅ (allowed extension, snake_case)
- `Dockerfile` → ✅ (UPPER_SNAKE_CASE allowed for root config)
- `docker-compose.yaml` → ✅ (hyphen allowed exception)
- `docker-compose.yml` → ✅ (hyphen allowed exception)
- `package-lock.json` → ✅ (hyphen allowed exception)
- `yarn.lock` → ✅ (hyphen allowed exception)
- `pnpm-lock.yaml` → ✅ (hyphen allowed exception)
- `config.json` → ✅ (snake_case, allowed extension)
- `api/v1/patient_routes.py` → ✅ (snake_case in path)
### Directory Naming
- `data_models/` → ✅
- `api_v2/` → ✅
- `legacy_data/` → ✅ (but see LEGACY/ rule below)
### LEGACY/ Directory
```python
# LEGACY: Temporary fix for migration issue #123
# TODO: Remove after migration complete
legacy_code_here()
```
→ ✅ (includes required comment)
### Test Files
```
src/
models/
patient_model.py
tests/
models/
test_patient_model.py
```
→ ✅ (test file mirrors source path)
## Incorrect Examples
### File Naming
- `PatientData.json` → ❌ (PascalCase, should be snake_case)
- `patient-data.json` → ❌ (hyphen not allowed except docker-compose and lockfiles)
- `patient data.json` → ❌ (spaces not allowed)
- `patient.PDF` → ❌ (forbidden extension)
- `secret_key.pem` → ❌ (forbidden extension)
- `package_lock.json` → ❌ (should be package-lock.json with hyphens)
- `yarn_lock` → ❌ (should be yarn.lock with hyphen and extension)
- `docker_compose.yaml` → ❌ (should be docker-compose.yaml with hyphens)
### Directory Naming
- `DataModels/` → ❌ (PascalCase)
- `data-models/` → ❌ (hyphens not allowed)
- `data models/` → ❌ (spaces)
### LEGACY/ Directory
```python
# Modified legacy function
def old_function():
pass
```
→ ❌ (missing `# LEGACY:` comment)
### Test Files
```
src/
utils/
helper.py
tests/
test_helper.py # ❌ Incorrect: should be tests/utils/test_helper.py
```
→ ❌ (test file does not mirror source path)

View File

@@ -0,0 +1,40 @@
# Skill: file_naming_convention
## Trigger Conditions
Any file or directory create, rename, or move operation.
## Rules
### 1. File and Directory Naming
- All files and directories must use `snake_case` (lowercase letters, digits, underscores only).
- No spaces or special characters (except hyphens in specific cases).
- Severity: `[ERROR]`
### 2. Hyphen Usage Exception
- Hyphens (`-`) are allowed ONLY in the following specific filenames (where changing the name would break standard tooling):
- `docker-compose.yaml`, `docker-compose.yml`
- `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
- Files beginning with '.' (like `.gitignore`, `.env`, `.npmrc`) are exempt from snake_case requirements for the prefix portion, but the suffix should follow naming conventions where practical.
- Any other use of hyphens in file or directory names is prohibited.
- Severity: `[ERROR]`
### 3. Uppercase Reserved for Root Constants
- `UPPER_SNAKE_CASE` is reserved only for root-level configuration constants (e.g., `CONFIG.json`, `Dockerfile`).
- Using `UPPER_SNAKE_CASE` for regular files or directories is prohibited.
- Severity: `[ERROR]`
### 4. Forbidden Extensions
The following file extensions are forbidden in the repository (must be stored externally and referenced by URL):
- `.pdf`, `.pptx`, `.docx`, `.xlsx`, `.csv`, `.zip`, `.pth`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.mp4`, `.mov`, `.avi`, `.dcm`, `.nii`, `.nii.gz`
- Severity: `[ERROR]`
### 5. LEGACY/ Directory Policy
- The `LEGACY/` directory is read-only.
- Any modification to files under `LEGACY/` must include an inline comment `# LEGACY:` explaining why the change is necessary.
- Failure to include this comment when modifying LEGACY/ files is a violation.
- Severity: `[ERROR]`
### 6. Test File Naming
- Test files must mirror the source file path in a parallel `tests/` directory.
- Example: `src/module.py``tests/test_module.py`
- Severity: `[WARN]`

View File

@@ -0,0 +1,29 @@
# Checklist: interface_contract_hygiene
## New Module/Package Creation
- [ ] New module/package includes a `spec/` directory
- [ ] `spec/interface-contract.md` file exists in the new module
- [ ] Contract file is located at `<module_path>/spec/interface-contract.md`
## Contract Content Verification
- [ ] Contract includes a clear **Purpose** section
- [ ] Contract identifies an **Owner** (team or individual)
- [ ] Contract defines the **Boundary** (includes/excludes)
- [ ] Contract specifies a **Breaking-change Policy**
- [ ] Contract lists **Consumers** (other modules/systems that use this)
- [ ] Contract provides **References** to related documentation
- [ ] Contract follows the same format/structure as existing contracts in the same domain
## Contract Modification (Existing Modules)
When modifying a public API (changing function signatures, adding/removing endpoints, etc.):
- [ ] Breaking changes follow the versioning policy:
- Adding features: MINOR version bump
- Removing/changing types: MAJOR version with migration path and 60-day notice
- [ ] Deprecation warnings are added for removed/changed interfaces
- [ ] Migration documentation is provided for breaking changes
## Template Consistency Check
Before creating a new contract:
- [ ] Reviewed at least two existing interface-contract.md files in the same domain (backend/frontend/infra)
- [ ] Matched section headings and formatting conventions
- [ ] Used similar language and level of detail

View File

@@ -0,0 +1,161 @@
# Examples: interface_contract_hygiene
## Correct Examples
### Backend Service Contract Example
File: `backend/services/patient_service/spec/interface_contract.md`
```markdown
# Patient Service Interface Contract
## Purpose
Handles all patient-related business operations including registration, updates, and retrieval of patient records. Provides a clean API for frontend and other backend services.
## Owner
Platform Team (platform-team@vkist.example.com)
## Boundary
**Includes:**
- Patient data validation
- Appointment scheduling logic
- Insurance verification workflows
- HL7 message parsing for patient demographics
**Excludes:**
- Image storage and retrieval (handled by Imaging Service)
- Payment processing (handled by Billing Service)
- User authentication (handled by Auth Service)
## Breaking-change Policy
- **MINOR version**: Adding new optional fields to patient schema, new endpoints for non-core features
- **MAJOR version**: Removing fields, changing data types of existing fields, removing endpoints
- Requires 60-day deprecation period
- Migration guide provided in release notes
- Deprecation warnings added 30 days prior to removal
## Consumers
- Frontend Patient Portal (`frontend/modules/patient_portal`)
- Appointment Scheduling Service (`backend/services/scheduling`)
- Billing Service (`backend/services/billing`)
- Mobile App API (`backend/services/mobile-api`)
## References
- [Patient Data Model](../models/patient_model.md)
- [API Specification](api-spec.yaml)
- [HL7 Interface Specification](../integrations/hl7-spec.md)
```
### Frontend Module Contract Example
File: `frontend/modules/appointment_scheduler/spec/interface_contract.md`
```markdown
# Appointment Scheduler Module Contract
## Purpose
Provides UI components and state management for scheduling, rescheduling, and cancelling patient appointments. Integrates with calendar services and sends notifications.
## Owner
Frontend Team (frontend-team@vkist.example.com)
## Boundary
**Includes:**
- Appointment booking form components
- Calendar view widgets
- Conflict detection logic
- Notification scheduling (email/SMS)
- Form validation for appointment constraints
**Excludes:**
- Patient identity verification (handled by Auth Module)
- Payment collection for appointments (handled by Billing Module)
- Actual calendar sync with external systems (handled by Calendar Integration Service)
## Breaking-change Policy
- **MINOR**: Adding new optional props to components, new utility functions
- **MAJOR**: Removing props, changing prop types, removing components
- Requires minor version bump in package.json
- Migration guide in CHANGELOG.md
- Deprecated props logged as warnings for 2 minor versions
## Consumers
- Main App Layout (`frontend/layout/MainLayout`)
- Patient Dashboard (`frontend/modules/patient_dashboard`)
- Provider Portal (`frontend/modules/provider_portal`)
## References
- [Component Library Guidelines](../../shared/components/README.md)
- [API Contract with Backend](../services/appointment_service/spec/interface-contract.md)
- [Accessibility Requirements](../../docs/accessibility.md)
```
### Infrastructure Module Contract Example
File: `infra/modules/networking/spec/interface_contract.md`
```markdown
# Networking Module Contract
## Purpose
Defines AWS/VPC networking infrastructure including subnets, route tables, security groups, and DNS configuration for the VKIST deployment.
## Owner
Infrastructure Team (infra-team@vkist.example.com)
## Boundary
**Includes:**
- VPC creation and CIDR allocation
- Public and private subnet configuration
- Internet gateway and NAT gateway setup
- Route table association
- Security group baseline rules
- DNS zone and record creation
**Excludes:**
- Compute resources (EC2/EKS) (handled by compute modules)
- Database infrastructure (handled by database module)
- Application-level security (handled by respective service modules)
## Breaking-change Policy
- **MINOR**: Adding new subnets, adding non-restrictive security group rules
- **MAJOR**: Changing VPC CIDR, removing subnets, altering route tables that break connectivity
- Requires terraform state migration plan
- 30-day review period with stakeholders
- Backward compatibility maintained via Terraform state import where possible
## Consumers
- Compute Module (`infra/modules/compute`)
- Database Module (`infra/modules/database`)
- EKS Cluster Module (`infra/modules/eks`)
- All environment-specific configurations (staging, production)
## References
- [AWS VPC Best Practices](../docs/aws_vpc_best practices.md)
- [Network Security Policy](../../docs/security/network_policy.md)
- [Terraform Module Guidelines](../../modules/README.md)
```
## Incorrect Examples (to avoid)
### Missing Contract
```diff
- // No spec/interface-contract.md exists for this new service
+ mkdir -p backend/services/new_service
+ touch backend/services/new_service/main.py
```
### Incomplete Contract
```markdown
# Incomplete Service
## Purpose
Handles user notifications.
// Missing: Owner, Boundary, Breaking-change Policy, Consumers, References
```
### Wrong Location
```diff
- // Contract should be in spec/ but is placed in root
+ NEW SERVICE/
+ interface_contract.md // WRONG LOCATION
+ main.py
+ requirements.txt
```

View File

@@ -0,0 +1,41 @@
# Skill: interface_contract_hygiene
## Trigger Conditions
- Creation of a new module, package, or component
- Any modification to a public API (function signatures, class interfaces, endpoints)
- Creation of a new directory that represents a logical boundary (e.g., new service, new frontend module)
## Rules
### 1. Interface Contract Requirement
- Every module boundary MUST have an `interface_contract.md` file in its `spec/` directory.
- Applies to: backend services, frontend modules, data access layers, infrastructure components.
- Severity: `[ERROR]`
### 2. Contract Content Requirements
Each `interface_contract.md` must include:
- **Purpose**: Brief description of what the module does
- **Owner**: Team or person responsible for the module
- **Boundary**: Clear definition of what is inside and outside the module
- **Breaking-change Policy**: How breaking changes are handled (versioning, deprecation)
- **Consumers**: List of other modules or systems that use this module
- **References**: Links to related documentation, diagrams, or external specifications
- Severity: `[ERROR]`
### 3. Versioning and Backward Compatibility
- Adding new fields, endpoints, or optional parameters: MINOR version bump (backward compatible)
- Removing or changing types of existing fields/endpoints: MAJOR version bump with:
- Migration path documentation
- 60-day deprecation notice minimum
- Deprecation warnings in code
- Severity: `[ERROR]`
### 4. Template Consistency
- When creating a new `interface_contract.md`, the author MUST review existing contracts in the same domain for naming and template consistency.
- Use the same section headings and format as neighboring contracts.
- Severity: `[WARN]`
### 5. Contract Location
- Contract must be located at `<module_path>/spec/interface_contract.md`
- If `spec/` directory does not exist, it must be created.
- Severity: `[ERROR]`

View File

@@ -0,0 +1,43 @@
# Checklist: secrets_and_phi_safety
## Secret Handling
- [ ] No hard-coded secrets (API keys, passwords, tokens, keys) in source code
- [ ] All secrets loaded via environment variables (`os.environ.get`, `process.env`, etc.)
- [ ] No `.env` files containing secrets in the repository
- [ ] No `secrets/` directory contents tracked by Git
- [ ] No private keys (`*.pem`, `*.key`) or certificates in the repository
- [ ] No model weights or training data files in the repository
## PHI Protection
- [ ] No patient names, IDs, MRNs, SSNs, DOBs, addresses, or phone numbers in:
- Console output (`print`, `console.log`)
- Application logs (any logging level)
- Error messages or exception text
- HTTP response bodies (including error responses)
- Debug output or test output
- [ ] Any identifiers used in logs/tests are clearly synthetic (e.g., `SYNTH-*` prefix)
- [ ] Image file paths in logs or responses do not contain actual patient identifiers
## Test Data and Fixtures
- [ ] All test data uses synthetic identifiers:
- Patient IDs start with `SYNTH-`
- Medical record numbers are clearly fake
- No real patient data or realistic facsimiles
- [ ] Test fixtures do not contain actual PHI
## Secrets Directory Protection
- [ ] The `secrets/` directory is listed in `.gitignore`
- [ ] Before writing to `secrets/`, verified that `.gitignore` includes `secrets/`
- [ ] No actual secrets stored in `secrets/` directory (use for non-sensitive configs only if at all)
## Configuration Safety
- [ ] Configuration files do not contain plain-text secrets
- [ ] Secrets in configs use environment variable substitution (e.g., `${VAR}` or `env.VAR`)
- [ ] TLS certificate paths are configured via environment variables, not hard-coded
- [ ] No connection strings with embedded credentials in config files
## Pre-commit Verification
- [ ] Reviewed git diff for accidental secret inclusion
- [ ] Checked for strings that resemble keys/tokens (long alphanumeric strings)
- [ ] Verified no patient-like identifiers appear in changed files
- [ ] Ran secret scanning tool if available (e.g., git-secrets, truffleHog)

View File

@@ -0,0 +1,191 @@
# Examples: secrets_and_phi_safety
## Correct Examples
### Secret Loading
```python
# Good: Loading from environment variables
import os
API_KEY = os.environ.get("EXTERNAL_API_KEY")
if not API_KEY:
raise ValueError("EXTERNAL_API_KEY environment variable not set")
DATABASE_URL = os.environ.get("DATABASE_URL")
SECRET_KEY = os.environ.get("SECRET_KEY", "default-dev-key-change-in-prod")
```
```javascript
// Good: Node.js environment variables
const apiKey = process.env.API_KEY;
const dbPassword = process.env.DB_PASSWORD;
if (!apiKey || !dbPassword) {
throw new Error('Missing required environment variables');
}
```
### PHI Protection in Logs
```python
# Good: Logging without PHI
import logging
import hashlib
logger = logging.getLogger(__name__)
def process_patient_record(patient_record):
# Log only non-identifying information
logger.info(
f"Processing record for patient age group: {get_age_group(patient_record.age)}"
)
# If logging an ID is absolutely necessary, use a hash
patient_hash = hashlib.sha256(
patient_record.medical_record_number.encode()
).hexdigest()[:8]
logger.debug(f"Processing hashed MRN: {patient_hash}...")
# Never do this:
# logger.info(f"Processing patient {patient_record.name} (MRN: {patient_record.mrn})")
# logger.error(f"Failed to process {patient_record.id}: {str(e)}") # if id is PHI
```
### Test Data with Synthetic Identifiers
```python
# Good: Test fixtures use SYNTH-* prefix
TEST_PATIENTS = [
{
"patient_id": "SYNTH-PAT-0001",
"name": "John Doe", # Note: In real tests, even names should be fake/synthetic
"mrn": "SYNTH-MRN-0001",
"date_of_birth": "1980-01-01",
# ... other test fields
},
{
"patient_id": "SYNTH-PAT-0002",
"name": "Jane Smith",
"mrn": "SYNTH-MRN-0002",
"date_of_birth": "1992-05-15",
}
]
```
### Configuration Files
```yaml
# Good: docker-compose.yml using environment variables
version: '3.8'
services:
api:
image: medical-api:latest
environment:
- DATABASE_URL=${DATABASE_URL}
- API_KEY=${API_KEY}
- LOG_LEVEL=${LOG_LEVEL:-info}
secrets:
- db_cert # Reference to external secret store, not file in repo
# Secrets are NOT stored in the repo
# Use Docker secrets or Kubernetes secrets or similar external secret management
```
```ini
# Good: config.ini with environment variable substitution
[database]
url = ${DATABASE_URL}
# or using os.path.expandvars equivalent
```
### .gitignore Protection
```gitignore
# .gitignore at repository root
# ... other entries
secrets/
*.env
*.pem
*.key
```
### Safe Error Messages
```python
# Good: Generic error messages that don't leak PHI
try:
process_claim(claim_data)
except ValidationError as e:
# Log detailed error internally (without PHI)
logger.warning(f"Claim validation failed: {e}")
# Return generic message to user
raise HTTPException(
status_code=400,
detail="Claim validation failed. Please check the submitted information."
)
except Exception as e:
logger.error(f"Unexpected error processing claim: {e}")
raise HTTPException(
status_code=500,
detail="Internal server error. Please contact support."
)
```
## Incorrect Examples
### Secret Exposure
```python
# Bad: Hard-coded secret
API_KEY = "sk_live_1234567890abcdef" # VIOLATION: exposed in code
# Bad: Secret in config file committed to repo
# config.yaml
# api_key: "actual_secret_key_here" # VIOLATION
# Bad: Using .env file that gets committed
# .env (committed to git)
# DATABASE_URL=postgres://user:password@localhost/db
```
### PHI in Logs and Errors
```python
# Bad: Logging full patient identifier
logger.error(f"Failed to update patient {patient.patient_id}: {str(e)}")
# If patient_id is MRN or similar, this leaks PHI
# Bad: Error message with PHI
raise ValueError(f"Invalid DOB for patient {patient.name}: {patient.dob}")
# Bad: Including PHI in HTTP response
return {
"error": f"Patient {patient.mrn} not found", # MRN is PHI
"code": "PATIENT_NOT_FOUND"
}
```
### Real Data in Tests
```python
# Bad: Test using real patient-like data
TEST_PATIENT = {
"patient_id": "12345678", # Looks like real MRN
"name": "John Doe", # Realistic name
"ssn": "123-45-6789", # Actual SSN format
"dob": "1980-01-01",
}
```
### Missing .gitignore Protection
```gitignore
# .gitmissing: secrets/ directory NOT ignored
# This would allow secrets to be committed
# (no entry for secrets/ here)
```
### Insecure Configuration
```yaml
# Bad: Hard-coded secrets in config
database:
host: localhost
port: 5432
username: admin
password: "super_secret_password" # VIOLATION
tls:
cert_file: "/etc/ssl/certs/real-cert.pem" # Hard-coded path, should be env var
key_file: "/etc/ssl/private/real-key.pem"
```

View File

@@ -0,0 +1,72 @@
# Skill: secrets_and_phi_safety
## Trigger Conditions
Any code or configuration change involving:
- Environment variable access
- File I/O operations
- Logging statements
- HTTP request/response handling
- Exception handling
- Configuration loading
- Secret management systems
## Rules
### 1. Secret Storage and Access
- **Never commit** secrets to version control:
- `.env` files
- `secrets/` directory contents
- Private keys (`*.pem`, `*.key`)
- API keys, passwords, tokens
- Model weights or training data
- All secrets must be loaded via environment variables using:
- `os.environ.get("KEY_NAME")` in Python
- `process.env.KEY_NAME` in Node.js
- Equivalent secure methods in other languages
- Hard-coded literal secrets in source code are strictly prohibited.
- Severity: `[ERROR]`
### 2. PHI (Protected Health Information) Protection
- **Never log, print, or expose** PHI in any form:
- Patient names, IDs, medical record numbers
- Dates of birth, ages, genders
- Addresses, phone numbers, email addresses
- Image file paths or URLs that could identify patients
- Diagnosis codes, procedure codes, or treatment details
- This applies to:
- Console logs (`print()`, `console.log()`)
- Application logs (any logging framework)
- Exception messages and stack traces
- HTTP response bodies (including error responses)
- Debug output or test output
- CI/CD pipeline artifacts and logs
- Use only synthetic/test data with `SYNTH-*` prefix for development and testing.
- Severity: `[ERROR]`
### 3. Test Data and Fixtures
- All test fixtures, mock data, and sample data must use synthetic identifiers:
- Patient IDs must start with `SYNTH-` (e.g., `SYNTH-PAT-0001`)
- Medical record numbers must be clearly fake
- No real patient data or derivations thereof
- Severity: `[ERROR]`
### 4. Secrets Directory Protection
- The `secrets/` directory must be ignored by Git:
- `secrets/` must be listed in `.gitignore` at the repository root
- Any attempt to write to `secrets/` directory must first verify that `.gitignore` contains `secrets/` (or `/*` negations, but effectively using a `secrets/` directory, verify `.gitignore` inclusion before first use
- Never store actual secrets in the repository, even if encrypted or obfuscated.
- Severity: `[ERROR]`
### 5. Configuration and Secrets Injection
- Configuration files (e.g., `.yaml`, `.json`, `.ini`) must not contain secrets.
- Use environment variable substitution in config files:
- Example: `password: ${DB_PASSWORD}` or `password: env.DB_PASSWORD`
- TLS/SSL certificate paths must come from environment variables, not be hardcoded.
- Severity: `[ERROR]`
### 6. Audit and Detection
- Before committing, run secret scanning tools if available (e.g., git-secrets, truffleHog, git-leaks)
- Review diff for accidental inclusion of:
- Strings resembling passwords, keys, or tokens
- File paths that look like they could contain PHI
- Any literal that looks like an ID number, SSN, MRN, etc.