This file provides guidance for AI coding agents working on this codebase.
OpenCode Team Sync (oct) is a CLI tool for synchronizing OpenCode configurations (agents and skills) across teams via Git repositories.
Tech Stack: TypeScript 5.x, Node.js 18+, Commander.js, Vitest, tsup, minimatch
npm install # Install dependencies
npm run build # Build the project
npm test # Run all tests
npx vitest run path/to/file.test.ts # Run a single test file
npx vitest run --testNamePattern "pattern" # Run tests matching a pattern
npx vitest watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage
npx tsc --noEmit # Type checking
npm run lint # Linting
npm run format # Formattingsrc/
├── cli/commands/ # init, sync, status, list, validate, update, rollback, remove, clean, info
├── core/ # discovery, git, sync, validator, namespace, lockfile, tags
├── schemas/ # Zod schemas for agent, skill, manifest, lockfile
├── types/ # TypeScript interfaces
└── utils/ # fs-utils, path-utils, hash-utils, error-utils, logger, yaml-utils
tests/
├── unit/ # Fast, isolated tests (~70%)
├── integration/ # Multi-module tests (~25%)
├── fixtures/ # Test data
└── helpers/ # Test utilities
- Use strict mode (
"strict": truein tsconfig.json) - Prefer
interfaceovertypefor object shapes - Use explicit return types on public functions
- Avoid
any- useunknownwith type guards - Use
readonlyfor immutable properties
- Files: kebab-case (
git-manager.ts,sync-engine.ts) - Classes: PascalCase (
GitManager,SyncEngine) - Interfaces: PascalCase, no
Iprefix (ConfigEntry, notIConfigEntry) - Functions/Methods: camelCase (
discoverConfigs,validateSchema) - Constants: UPPER_SNAKE_CASE (
DEFAULT_TIMEOUT,ERROR_CODES)
Group imports: external deps, internal modules, types. Use ES modules exclusively.
import { Command } from 'commander';
import { z } from 'zod';
import { GitManager } from '@/core/git/git-manager';
import type { ConfigEntry } from '@/types';Use custom error classes extending OpenCodeTeamError with error codes:
export class GitCloneError extends GitError {
constructor(url: string, cause?: Error) {
super(`Failed to clone repository: ${url}`, 'GIT_CLONE_ERROR', { url, cause: cause?.message });
}
}- Always use async/await over raw Promises
- Handle errors with try-catch, not .catch()
- Use Promise.all() for parallel independent operations
Define schemas in src/schemas/, export inferred types alongside:
export const AgentFrontmatterSchema = z.object({
description: z.string().min(1).max(1024),
mode: z.enum(['primary', 'subagent', 'all']).optional(),
tags: z.array(z.string().regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/)).optional(),
});
export type AgentFrontmatter = z.infer<typeof AgentFrontmatterSchema>;- Test files:
*.test.ts- use descriptive names, Arrange-Act-Assert pattern - Mock external deps (Git, file system), target 80%+ coverage
- Separation of Concerns: CLI, Business Logic, Data Access separated
- Dependency Injection: Services injected, not hard-coded
- Fail-Fast: Validate early, fail with clear messages
- Idempotency: Operations can be safely retried
- Atomicity: Changes are all-or-nothing where possible
interface ConfigEntry {
path: string; // Relative path in repo
type: ConfigType; // 'agent' | 'skill'
name: string; // Derived from filename/directory
hash: string; // SHA-256 of content
tags: string[];
description?: string; // Optional metadata
}
interface SyncResult {
added: ConfigEntry[];
updated: ConfigEntry[];
removed: string[];
conflicts: Conflict[];
errors: SyncError[];
}- Agents: Markdown files with YAML frontmatter (
.md)- Patterns:
agents/**/*.md,*.agent.md - Validated by:
AgentValidator
- Patterns:
- Skills: SKILL.md files in skill directories
- Patterns:
skills/**/SKILL.md,skill/**/SKILL.md - Validated by:
SkillValidator
- Patterns:
Note: MCP servers are NOT distributed as files. They are configured in
opencode.json and should be documented in team repos but not synced.
Team configs: {config_dir}/{type}/team/, Personal: {config_dir}/{type}/personal/
Personal always takes precedence over team configs.