- Backend: kube module with ClusterClient, PortForwardSession, RefreshRegistry - 7 Tauri IPC commands: add_cluster, remove_cluster, list_clusters, start_port_forward, stop_port_forward, list_port_forwards, delete_port_forward, shutdown_port_forwards - AppState extended with clusters, port_forwards, refresh_registry fields - Version bumped to 1.1.0 in Cargo.toml and package.json - Auto-tag workflow updated to mark releases as draft (pre-release) - Buy Me A Coffee section added to README.md - Fixed changelog workflow to only include current tag commits - Proper kubeconfig YAML parsing with extract_context and extract_server_url - Added kubeconfig content storage in ClusterClient - Updated PortForwardSession to include cluster_name - Frontend GUI components: ClusterList, PortForwardList, AddClusterModal, PortForwardForm, KubernetesPage - TypeScript types and IPC commands for Kubernetes management - Unit tests for Kubernetes IPC commands (6 tests) - All 332 Rust tests passing - All 98 frontend tests passing - TypeScript type checks passing - Project builds successfully in release mode - Committed and pushed to feature/kubernetes-management branch - Command injection vulnerability fixed with regex validation and max length check (253 chars) - stop_port_forward and shutdown_port_forwards properly kill kubectl child processes via async child management - Temp file cleanup implemented with RAII TempFileCleanup struct created before std::fs::write - discover_pods now parses actual kubectl JSON output - ChildWaitHandle implemented with background task for waiting on kubectl child - PortForwardSession uses Arc<TokioMutex<Option<Child>>> for async-safe child management - Port-forward uses kubectl's dynamic port binding (0) instead of TcpListener - Added shutdown_port_forwards command for app shutdown cleanup - Added cleanup effect in App.tsx to call shutdownPortForwardsCmd on unmount - Database CRUD operations for clusters and port_forwards added to db.rs - validate_resource_name uses lazy_static! for cached Regex to prevent ReDoS - Cluster struct updated to store kubeconfig_content directly instead of kubeconfig_id - Cluster model in db/models.rs updated to use kubeconfig_content field - load_clusters and load_port_forwards commands registered in lib.rs - Temp file cleanup moved to background task in ChildWaitHandle to ensure cleanup after kubectl completes - Unused child_id field removed from ChildWaitHandle - Command validation moved to beginning of start_port_forward before any operations - Fixed lint errors: removed unused imports, fixed React hooks order, updated type annotations - Updated eslint.config.js to properly configure file patterns
7.2 KiB
AGENTS.md — Quick Start for OpenCode
Commands
| Task | Command |
|---|---|
| Run full dev (Tauri + Vite) | cargo tauri dev |
| Frontend only (port 1420) | npm run dev |
| Frontend production build | npm run build |
| Rust fmt check | cargo fmt --manifest-path src-tauri/Cargo.toml --check |
| Rust fmt fix | cargo fmt --manifest-path src-tauri/Cargo.toml |
| Rust clippy | cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings |
| Rust tests | cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1 |
| Rust single test module | cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1 pii::detector |
| Rust single test | cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1 test_detect_ipv4 |
| Frontend test (single run) | npm run test:run |
| Frontend test (watch) | npm run test |
| Frontend coverage | npm run test:coverage |
| TypeScript type check | npx tsc --noEmit |
| Frontend lint | npx eslint src/ tests/ --quiet |
Lint Policy: ALWAYS run cargo fmt and cargo clippy after any Rust code change. Fix all issues before proceeding.
Note: The build runs npm run build before Rust build (via beforeBuildCommand in tauri.conf.json). This ensures TS is type-checked before packaging.
Requirement: Rust toolchain must be in PATH: source ~/.cargo/env
Project Structure
| Path | Responsibility |
|---|---|
src-tauri/src/lib.rs |
Entry point: app builder, plugin registration, IPC handler registration |
src-tauri/src/state.rs |
AppState (DB, settings, integration_webviews) |
src-tauri/src/commands/ |
Tauri IPC handlers (db, ai, analysis, docs, integrations, system) |
src-tauri/src/ai/provider.rs |
Provider trait + create_provider() factory |
src-tauri/src/pii/ |
Detection engine (12 patterns) + redaction |
src-tauri/src/db/models.rs |
DB types: Issue, IssueDetail (nested), LogFile, ResolutionStep, AiConversation |
src-tauri/src/audit/log.rs |
write_audit_event() before every external send |
src/lib/tauriCommands.ts |
Source of truth for all Tauri IPC calls |
src/lib/domainPrompts.ts |
15 domain system prompts (Linux, Windows, Network, K8s, DBs, etc.) |
src/stores/ |
Zustand: sessionStore (ephemeral), settingsStore (persisted), historyStore |
Key Patterns
Rust Mutex Usage
Lock Mutex inside a block and release before .await. Holding MutexGuard across await points fails to compile (not Send):
let state: State<'_, AppState> = app.state();
let db = state.db.clone();
// Lock and release before await
{ let conn = state.db.lock().unwrap(); /* use conn */ }
// Now safe to .await
db.query(...).await?;
IssueDetail Nesting
get_issue() returns a nested struct — use detail.issue.title, not detail.title:
pub struct IssueDetail {
pub issue: Issue,
pub log_files: Vec<LogFile>,
pub resolution_steps: Vec<ResolutionStep>,
pub conversations: Vec<AiConversation>,
}
TypeScript mirrors this shape exactly in tauriCommands.ts.
PII Before AI Send
apply_redactions must be called before sending logs to AI. Record the SHA-256 hash via audit::log::write_audit_event(). PII spans are non-overlapping (longest span wins on overlap); redactor iterates in reverse order to preserve offsets.
State Persistence
sessionStore: ephemeral triage session (issue, messages, PII spans, why-level 0–5, loading) — not persistedsettingsStore: persisted tolocalStorageas"tftsr-settings"
CI/CD (Gitea Actions)
| Workflow | Trigger | Jobs |
|---|---|---|
.gitea/workflows/test.yml |
Every push/PR | rustfmt → clippy → cargo test (64 tests) → tsc --noEmit → vitest run (13 tests) |
.gitea/workflows/auto-tag.yml |
Push to master | Auto-tag, build linux/amd64 + windows/amd64 + linux/arm64 + macOS, upload assets to Gitea release |
Artifacts: src-tauri/target/{target}/release/bundle/
Environments:
- Test CI images at
172.0.0.29:3000(pulltftsr-*:rust1.88-node22) - Gitea instance:
http://172.0.0.29:3000 - Wiki: sync from
docs/wiki/*.md→https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
TRCAA_DATA_DIR (or legacy TRCAA_DATA_DIR) |
Platform data dir | Override database location |
TRCAA_DB_KEY (or legacy TRCAA_DB_KEY) |
Auto-generated | SQLCipher encryption key override |
TRCAA_ENCRYPTION_KEY (or legacy TRCAA_ENCRYPTION_KEY) |
Auto-generated | Credential encryption key override |
RUST_LOG |
info |
Tracing level (debug, info, warn, error) |
Database path:
- Linux:
~/.local/share/tftsr/tftsr.db - macOS:
~/Library/Application Support/tftsr/tftsr.db - Windows:
%APPDATA%\tftsr\tftsr.db
Architecture Highlights
Rust Backend
- Entry point:
src-tauri/src/lib.rs::run()→ init tracing → init DB → register plugins →generate_handler![] - Database:
rusqlite+bundled-sqlcipher-vendored-openssl(AES-256).cfg!(debug_assertions)→ plain SQLite; release → SQLCipher - AI providers:
Providertrait with factory dispatch onconfig.name. Adding a provider: implementProvidertrait + add match arm - Integration clients: Confluence, ServiceNow, Azure DevOps stubs (v0.2). OAuth2 via WebView + callback server (warp, port 8765)
Frontend (React + Vite)
- Dev server: port 1420 (hardcoded)
- IPC: All
invoke()calls insrc/lib/tauriCommands.ts— typed wrappers for every backend command - Domain prompts: 15 expert prompts injected as first message in every triage conversation (Linux, Windows, Network, K8s, DBs, Virtualization, Hardware, Observability, Telephony, Security, Public Safety, Application, Automation, HPE, Dell, Identity)
Security
- Database encryption: AES-256 (SQLCipher in release builds)
- Credential encryption: AES-256-GCM, keys stored in
TRCAA_ENCRYPTION_KEY(or legacyTRCAA_ENCRYPTION_KEY) or auto-generated.enckey(mode 0600) - Audit trail: Hash-chained entries (
prev_hash+entry_hash) for tamper evidence - PII protection: 12-pattern detector → user approval gate → hash-chained audit entry
Testing
| Layer | Command | Notes |
|---|---|---|
| Rust | cargo test --manifest-path src-tauri/Cargo.toml |
64 tests, runs in rust:1.88-slim container |
| TypeScript | npm run test:run |
Vitest, 13 tests |
| Type check | npx tsc --noEmit |
skipLibCheck: true |
| E2E | TAURI_BINARY_PATH=./src-tauri/target/release/tftsr npm run test:e2e |
WebdriverIO, requires compiled binary |
Frontend coverage: npm run test:coverage → tests/unit/ coverage report
Critical Gotchas
- Mutex across await: Never
lock().unwrap()and.awaitwithout releasing the guard - IssueDetail nesting:
detail.issue.title, neverdetail.title - PII before AI: Always redact and record hash before external send
- Port 1420: Vite dev server is hard-coded to 1420, not 3000
- Build order: Rust fmt → clippy → test → TS check → JS test
- CI images: Use
172.0.0.29:3000registry for pre-baked builder images