Deckent exposes a programmatic TypeScript API. Import from the main entry point or from individual submodule paths.
import { loadConfig, runSprint, readTask } from 'deckent';
// or granular imports:
import { loadConfig } from 'deckent/core';
import { runSprint } from 'deckent/orchestra';
import { readTask } from 'deckent/agents';
import { scanHeartbeats } from 'deckent/monitor';- Core — Types
- Core — Constants
- Core — Config
- Core — Analyzer
- Orchestra — Brain
- Orchestra — Planner
- Orchestra — Tmux
- Agents — Worker
- Monitor — Auditor
- MCP Server
- HTTP API
- CLI Commands
Source: src/core/types.ts
Exports: src/core/index.ts
type ModelType = 'opus' | 'sonnet' | 'haiku';
type TaskEffort = 'low' | 'normal' | 'high';
type TaskPriority = 'CRITICAL' | 'HIGH' | 'NORMAL' | 'LOW';
type AgentRole = 'brain' | 'auditor' | 'worker';
type PlanMode = 'performance' | 'balanced' | 'economic' | 'api';
// Legacy aliases (still accepted): max_plan → performance, max5x_plan → balanced, pro_plan → economic
type SelfAssessment = 'DONE' | 'GO_WITH_TECH_DEBT' | 'NO_GO';
type BoundaryViolationType =
| 'file_outside_scope'
| 'stale_heartbeat'
| 'stale_lock'
| 'circular_dependency'
| 'memory_budget_exceeded';Task lifecycle states.
| Value | Description |
|---|---|
PENDING |
Task created, not yet claimed |
CLAIMED |
Worker has claimed the task |
EXECUTING |
Worker is actively coding |
TESTING |
Worker is running tests |
DOCUMENTING |
Worker is writing docs |
DONE |
Task completed successfully |
NO_GO |
Task failed evaluation |
PAUSED |
Task is paused |
Brain's final verdict on a completed task.
| Value | Description |
|---|---|
DONE |
Fully accepted |
GO_WITH_TECH_DEBT |
Accepted with known tech debt |
NO_GO |
Rejected; fix task will be created |
Real-time agent state written to heartbeat files.
Values: IDLE, PLANNING, EXECUTING, EVALUATING, SCANNING, CODING, TESTING, DOCUMENTING, DONE, ERROR, PAUSED
Values: INFO, WARNING, CRITICAL
Phases the brain moves through during a sprint.
Values: DIRECTIVE, PLAN, SPAWN, EXECUTE, EVALUATE, FIX, RETRO, DECAY, TRANSITION, COMPLETE
Values: PLANNING, ACTIVE, EVALUATING, FIXING, RETROSPECTIVE, COMPLETE, PAUSED
Values: NORMAL, HIGH, CRITICAL
Defines the file-system boundary a worker may touch.
interface TaskScope {
directories: string[]; // Permitted directories (e.g. "src/core/")
filesRead: string[]; // Files the worker may read (informational)
filesWrite: string[]; // Specific files the worker may write
}Acceptance criteria embedded in every task.
interface GoNoGoCriteria {
goCriteria: string; // What constitutes success
noGoCriteria: string; // What constitutes failure
techDebtAcceptable: string; // Acceptable tech debt conditions
}The central unit of work.
interface Task {
id: string; // Format: "{sprintNumber}-{sequence}"
title: string;
description: string;
model: ModelType;
effort: TaskEffort;
priority: TaskPriority;
reason: string; // Why this task exists
scope: TaskScope;
dependencies: string[]; // Task IDs that must complete first
goNogo: GoNoGoCriteria;
status: TaskStatus;
sprintId?: string;
assignedWorker?: string;
isPriorityFix?: boolean; // True for auto-generated fix tasks
fixForTaskId?: string; // Points to the NO_GO task being fixed
createdAt?: string; // ISO 8601
updatedAt?: string; // ISO 8601
}Written by the worker agent on task completion.
interface TaskResult {
taskId: string;
workerId: string;
filesChanged: string[];
linesAdded: number;
linesRemoved: number;
testsPassed: boolean;
coverage: number; // Percentage, 0–100
selfAssessment: SelfAssessment;
notes: string;
completedAt?: string; // ISO 8601
durationMs?: number;
}Execution plan written before coding begins.
interface TaskPlan {
taskId: string;
workerId: string;
filesToCreate: string[];
filesToModify: string[];
executionSteps: string[];
testStrategy: string;
documentationPlan: string;
estimatedDurationMin?: number;
}interface Sprint {
id: string; // Format: "sprint-{number}"
number: number;
status: SprintStatus;
phase: SprintPhase;
tasks: Task[];
workers: string[]; // Worker IDs
metrics?: SprintMetrics;
reasoning?: string; // AI planner reasoning (when brain_planning = 'ai')
planningMode?: BrainPlanningMode; // Which planning mode was used
startedAt?: string; // ISO 8601
completedAt?: string; // ISO 8601
}interface SprintMetrics {
totalTasks: number;
completedTasks: number;
techDebtTasks: number;
noGoTasks: number;
durationMs: number;
coveragePercent: number;
noGoRate: number; // 0.0–1.0
newDebtCount: number;
resolvedDebtCount: number;
totalOpenDebt: number;
boundaryViolations: number;
crossAssignments: number;
contextLinesUsed: number;
}interface DebtItem {
id: string;
description: string;
originTaskId: string;
originSprintId: string;
priority: DebtPriority;
sprintsOpen: number; // Auto-incremented each sprint
resolved: boolean;
resolvedInSprintId?: string;
createdAt: string; // ISO 8601
}Written by workers every ~15 seconds to signal liveness.
interface Heartbeat {
workerId: string;
taskId: string;
status: AgentStatus;
currentAction: string;
currentFile?: string;
timestamp: string; // ISO 8601
filesChangedCount: number;
sequence: number;
}Snapshot of a running agent's state.
interface AgentInfo {
id: string;
role: AgentRole;
status: AgentStatus;
model: ModelType;
tmuxWindow: string;
taskId?: string;
currentAction?: string;
spawnedAt?: string;
}interface Alert {
level: AlertLevel;
message: string;
source?: string;
timestamp: string;
acknowledged?: boolean;
}interface BoundaryViolation {
type: BoundaryViolationType;
agentId: string;
detail: string;
timestamp: string;
}interface DashboardState {
sprint: {
id: string;
number: number;
phase: SprintPhase;
status: SprintStatus;
};
agents: AgentInfo[];
progress: { done: number; active: number; blocked: number; total: number };
alerts: Alert[];
auditorLastScan?: string; // ISO 8601 — when last scan cycle completed
violations?: number; // Total boundary violation count
updatedAt: string;
}interface LockInfo {
filePath: string;
ownerWorkerId: string;
acquiredAt: string; // ISO 8601
taskId: string;
}type BrainPlanningMode = 'ai' | 'structured' | 'auto';Brain planning strategy: 'ai' uses AI planner with Zod validation, 'structured' parses DIRECTIVES.md ## Task N: blocks, 'auto' (default) tries AI first then falls back to structured.
interface PlanModeConfig {
max_workers: number;
brain_model: ModelType;
default_model: ModelType;
haiku_allowed: boolean;
budget_per_sprint?: number; // USD, api mode only
requires?: string; // Env var name, api mode only
brain_planning?: BrainPlanningMode; // Default: 'auto'
}interface DeckentConfig {
mode: PlanMode;
modes: Record<PlanMode, PlanModeConfig>;
language?: string; // 'en' | 'tr'
projectName?: string;
version?: string;
}Fully resolved config with all defaults applied.
interface ResolvedConfig {
mode: PlanMode;
activeModeConfig: PlanModeConfig;
modes: Record<PlanMode, PlanModeConfig>;
language: string;
projectName: string;
projectRoot: string; // Absolute path
version: string;
}interface DecayResult {
linesBefore: number;
linesAfter: number;
archivedSprints: string[];
removedDebtCount: number;
removedPatternCount: number;
}interface DoctorResult {
ok: boolean; // true only if all required checks pass
checks: {
name: string;
passed: boolean;
message: string;
required: boolean;
}[];
}interface StartOptions {
autoApprove?: boolean; // Pass --dangerously-skip-permissions to workers
sandboxMode?: boolean; // Reserved for Docker sandbox (not yet implemented)
}Source: src/core/constants.ts
Exports: src/core/index.ts
const DECKENT_DIR = '.deckent';
const BRAIN_DIR = '.brain';
const TASKS_DIR = '.tasks';
const LOCKS_DIR = '.locks';
const DASHBOARD_FILE = '.dashboard';
const PROJECT_CONFIG_PATH = '.deckent/config.json';
const GLOBAL_CONFIG_PATH = '~/.deckent/config.json'; // resolved via homedir()const MEMORY_FILE = 'MEMORY.md';
const DECISIONS_FILE = 'DECISIONS.md';
const DEBT_FILE = 'DEBT.md';
const PATTERNS_FILE = 'PATTERNS.md';
const RETRO_FILE = 'RETRO.md';
const SPRINTS_DIR = 'sprints';
const ARCHIVE_DIR = 'archive';const AUDITOR_SCAN_INTERVAL_MS = 30_000; // 30 seconds
const HEARTBEAT_STALE_THRESHOLD_MS = 120_000; // 2 minutes
const HEARTBEAT_WRITE_INTERVAL_MS = 15_000; // 15 seconds
const LOCK_TIMEOUT_MS = 30_000; // 30 seconds
const LOCK_STALE_THRESHOLD_MS = 300_000; // 5 minutesconst MEMORY_MAX_LINES = 200;
const PATTERNS_MAX_LINES = 80;
const RETRO_MAX_LINES = 100;
const SPRINT_LOG_MAX_LINES = 80;
const BRAIN_TOTAL_LINE_BUDGET = 600;
const MEMORY_DECAY_SPRINTS = 5; // Unused entries decay after 5 sprints
const PATTERN_DECAY_SPRINTS = 8;const DEBT_HIGH_PRIORITY_SPRINTS = 2; // Escalate to HIGH after 2 sprints open
const DEBT_CRITICAL_SPRINTS = 3; // Escalate to CRITICAL after 3 sprints openconst DEFAULT_LANGUAGE = 'en';
const DEFAULT_MODE = 'performance'; // Legacy alias: 'max_plan'
const DECKENT_VERSION = '0.1.0';
const SUPPORTED_LANGUAGES = ['en', 'tr'];const TMUX_SESSION_NAME = 'deckent';
const TMUX_BRAIN_WINDOW = 'brain';
const TMUX_AUDITOR_WINDOW = 'auditor';
const TMUX_DASHBOARD_WINDOW = 'dashboard';
const TMUX_WORKER_PREFIX = 'w-'; // Worker windows: w-{taskId}Source: src/core/config.ts
Exports: src/core/index.ts
async function loadConfig(projectRoot?: string): Promise<ResolvedConfig>Loads and merges global config (~/.deckent/config.json) and project config (.deckent/config.json), then validates the result.
Parameters:
projectRoot— Project directory. Defaults toprocess.cwd().
Throws: ConfigValidationError if the merged config is invalid, or if api mode is active but ANTHROPIC_API_KEY is not set.
Example:
import { loadConfig } from 'deckent';
const config = await loadConfig('/path/to/project');
console.log(config.mode); // 'performance'
console.log(config.activeModeConfig.max_workers); // 8function validatePartialConfig(partial: Partial<DeckentConfig>): voidValidates a partial config object by merging it with defaults before validation. Useful for validating user-supplied overrides before writing to disk.
Parameters:
partial— Partial config to validate.
Throws: ConfigValidationError with an errors: string[] field listing all validation failures.
Example:
import { validatePartialConfig } from 'deckent';
validatePartialConfig({ mode: 'economic', language: 'en' }); // OK
validatePartialConfig({ mode: 'invalid' as any }); // throws ConfigValidationErrorfunction getDefaultConfig(): DeckentConfigReturns a fresh copy of the factory default config (mode: performance, all mode configs at defaults).
function getDefaultModes(): Record<PlanMode, PlanModeConfig>Returns a deep clone of the default mode configuration map. Safe to mutate.
class ConfigValidationError extends Error {
readonly errors: string[];
}Thrown when config validation fails. The errors array contains one entry per violation.
Source: src/core/analyzer.ts
Exports: src/core/index.ts
function analyzeProject(root: string): ProjectAnalysisAnalyzes a project directory and returns detected stack, size classification, and methodology recommendation.
Returns: ProjectAnalysis:
interface ProjectAnalysis {
framework: string | null; // 'react' | 'next' | 'express' | 'nestjs' | 'vue' | 'angular' | 'svelte' | null
language: string; // 'typescript' | 'javascript' | 'python' | 'rust' | 'mixed'
testFramework: string | null; // 'vitest' | 'jest' | 'mocha' | 'pytest' | null
buildTool: string | null; // 'tsc' | 'vite' | 'webpack' | 'esbuild' | 'turbo' | null
ci: string | null; // 'github-actions' | 'gitlab-ci' | 'circleci' | null
size: ProjectSize;
methodology: MethodologyRecommendation;
}
interface ProjectSize {
files: number;
lines: number;
classification: 'small' | 'medium' | 'large' | 'enterprise';
}
interface MethodologyRecommendation {
maxWorkers: number;
defaultModel: ModelType;
brainModel: ModelType;
reasoning: string;
}Source: src/orchestra/brain.ts
Exports: src/orchestra/index.ts
async function runSprint(
projectRoot: string,
config: ResolvedConfig,
opts?: { autoApprove?: boolean },
): Promise<Sprint>Master orchestrator. Executes the full sprint lifecycle:
PLAN → SPAWN → EXECUTE → EVALUATE → FIX → RETRO → DECAY → CLEANUP
Each phase is wrapped in a try/catch so the sprint always reaches COMPLETE, even on partial failure.
Parameters:
projectRoot— Absolute path to the project root.config— Resolved config (fromloadConfig).opts.autoApprove— Pass--dangerously-skip-permissionsto worker claude processes.
Example:
import { loadConfig, runSprint } from 'deckent';
const config = await loadConfig();
const sprint = await runSprint(process.cwd(), config, { autoApprove: true });
console.log(sprint.metrics?.noGoRate); // 0.0 if all tasks passedfunction readContext(projectRoot: string): BrainContextReads all brain files and the current task state into a single context object.
Returns: BrainContext with fields: directives, memory, retro, debt, patterns, decisions, existingTasks, projectState.
Example:
const ctx = readContext('/my/project');
console.log(ctx.debt.length); // number of open debt itemsfunction planSprint(
projectRoot: string,
config: ResolvedConfig,
context: BrainContext,
recommendation: SprintSizeRecommendation,
options?: { mode?: BrainPlanningMode; asDraft?: boolean },
): SprintReads DIRECTIVES.md, creates Task objects, and writes .tasks/task-{id}.json files. Handles CRITICAL debt priority fixes by prepending fix tasks.
Parameters:
options.mode— Planning mode override ('ai','structured','auto'). Default from config.options.asDraft— Iftrue, tasks are created inDRAFTstatus (requiresconfirmDraftTasksbefore spawning).
Returns: A Sprint in PLANNING status with all tasks populated.
function confirmDraftTasks(projectRoot: string, sprint: Sprint): voidTransitions all DRAFT tasks in a sprint to PENDING status. Called after operator confirms the sprint plan when asDraft: true was used.
function resolveDebt(
projectRoot: string,
debtId: string,
resolvedInSprintId: string,
): booleanMarks a debt item as resolved. Returns true if the debt was found and updated, false otherwise.
function evaluateResult(result: TaskResult, task: Task): TaskEvaluationPure function. Evaluates a worker's result against go/no-go criteria.
Rules:
NO_GOifselfAssessment === 'NO_GO'ortestsPassed === falseGO_WITH_TECH_DEBTifcoverage < 90DONEotherwise
Example:
const evaluation = evaluateResult(result, task);
if (evaluation === TaskEvaluation.NO_GO) {
console.log('Fix task will be auto-created');
}function runDecay(
projectRoot: string,
sprintId: string,
opts?: { force?: boolean },
): DecayResultCompresses brain memory: removes resolved debt and patterns, archives old sprint logs, trims MEMORY.md and RETRO.md to their line budgets.
Parameters:
force— Run decay even if the brain is under its line budget.
Returns: DecayResult with before/after line counts and removed item counts.
function cleanup(projectRoot: string, sprint: Sprint): voidPost-sprint cleanup: kills all tmux worker windows, releases all file locks, and removes .hb heartbeat files.
function calculateMetrics(
sprint: Sprint,
evaluations: Map<string, TaskEvaluation>,
results: TaskResult[],
debt?: DebtItem[],
): SprintMetricsPure function. Computes all sprint metrics from evaluation results and task data.
function escalateDebt(projectRoot: string): voidIncrements sprintsOpen on all unresolved debt items. Auto-escalates priority based on DEBT_HIGH_PRIORITY_SPRINTS and DEBT_CRITICAL_SPRINTS thresholds.
function writeRetrospective(
projectRoot: string,
sprint: Sprint,
evaluations: Map<string, TaskEvaluation>,
metrics: SprintMetrics,
): voidWrites .brain/RETRO.md (overwrite) and appends a summary to .brain/MEMORY.md.
function writeSprintLog(
projectRoot: string,
sprint: Sprint,
metrics: SprintMetrics,
): voidWrites .brain/sprints/sprint-{id}.md with the full sprint metrics and per-task summaries.
function spawnWorkers(
projectRoot: string,
sprint: Sprint,
config: ResolvedConfig,
spawnOpts?: { autoApprove?: boolean },
): voidEnsures the tmux session exists and creates one tmux window per task. Each window runs claude with the generated worker prompt.
Note: As of Sprint 14, spawnWorkers no longer calls startAuditor(). The auditor scan loop runs in-process within runSprint. The worker prompt (via buildWorkerPrompt) includes instructions for creating and updating .tasks/task-{id}.hb heartbeat files.
async function waitForResults(
projectRoot: string,
sprint: Sprint,
timeoutMs?: number,
): Promise<TaskResult[]>Polls .tasks/task-{id}.result files until all tasks complete or the timeout is reached. Missing results are synthesized as NO_GO.
Parameters:
timeoutMs— Default: 30 minutes (1,800,000 ms).
function createTask(params: CreateTaskParams, sequence: number): TaskPure function. Creates a Task object with ID {sprintNumber}-{sequence:03}.
Type CreateTaskParams:
interface CreateTaskParams {
title: string;
description: string;
model: ModelType;
effort: TaskEffort;
priority: TaskPriority;
reason: string;
scope: TaskScope;
dependencies: string[];
goNogo: GoNoGoCriteria;
sprintId: string;
isPriorityFix?: boolean;
fixForTaskId?: string;
}function parseStructuredDirectives(content: string): ParsedDirectiveTask[]Parses DIRECTIVES.md content that uses ## Task N: or ## Görev N: section headers into structured task objects.
Type ParsedDirectiveTask:
interface ParsedDirectiveTask {
title: string;
description: string;
scope: TaskScope;
testTarget?: string;
}function extractScopeFromDirective(line: string): TaskScopePure function. Extracts src/ and tests/ directory paths and .ts/.js file paths from a single directive line.
class BrainError extends Error {
readonly phase?: SprintPhase;
}Thrown when a sprint phase fails unrecoverably. The phase field identifies which phase failed.
Source: src/orchestra/planner.ts
Exports: src/orchestra/index.ts
Planner imports ONLY from core/ (types, constants) — never from brain.ts (ADR-008).
function buildPlanPrompt(
context: BrainContext,
recommendation: SprintSizeRecommendation,
projectName: string,
): stringConstructs the AI prompt sent to the LLM for task planning. Includes context from directives, memory, debt, patterns, and the sprint size recommendation.
function parsePlannerResponse(raw: string): PlannerResult | nullParses and validates a raw AI response string into a PlannerResult using Zod schemas. Returns null if the response is invalid or cannot be parsed.
Type PlannerResult:
interface PlannerResult {
tasks: PlannerTask[];
reasoning: string;
}
interface PlannerTask {
title: string;
description: string;
model: ModelType;
effort: TaskEffort;
priority: TaskPriority;
reason: string;
scope: TaskScope;
dependencies: string[];
goNogo: GoNoGoCriteria;
}function callBrainPlanner(
context: BrainContext,
recommendation: SprintSizeRecommendation,
model: ModelType,
projectName: string,
): PlannerResult | nullSpawns claude CLI with the plan prompt, parses the response, and returns a validated PlannerResult. Returns null on any failure (timeout, invalid response, etc.).
Parameters:
model— Which model to use for planning (typicallyopusorsonnet).
Source: src/orchestra/tmux.ts
Exports: src/orchestra/index.ts
All tmux functions use spawnSync('tmux', [...]) — safe against shell injection.
function isSessionActive(): booleanReturns true if the deckent tmux session exists.
function ensureSession(): voidCreates the deckent tmux session if it does not already exist. No-op if already active.
function spawnWorker(
taskId: string,
model: ModelType,
prompt: string,
projectDir: string,
opts?: SpawnOptions,
): voidCreates a new tmux window named w-{taskId} inside the deckent session and sends the claude command.
Type SpawnOptions:
interface SpawnOptions {
allowedTools?: string; // Comma-separated list for --allowedTools
autoApprove?: boolean; // Adds --dangerously-skip-permissions
}Example:
spawnWorker('006-001', 'sonnet', 'Your task prompt...', '/project', {
autoApprove: true,
});function killWorker(taskId: string): voidKills the tmux window w-{taskId}. Throws TmuxError if the window does not exist.
function listWorkers(): string[]Returns an array of active task IDs (window names with the w- prefix stripped). Returns [] if the session does not exist.
function startAuditor(projectDir: string, opts?: SpawnOptions): voidCreates (or reuses) the auditor tmux window and starts the auditor claude process.
function attach(): voidAttaches the current terminal to the deckent tmux session (stdio inheritance).
function destroy(): voidKills the entire deckent tmux session. Silent no-op if the session does not exist.
function sendKeys(target: string, keys: string): voidSends a key sequence to a specific window within the deckent session.
Parameters:
target— Window name (e.g.'w-006-001'or'auditor').keys— Key string sent totmux send-keysfollowed byEnter.
class TmuxError extends Error {
readonly command?: string; // The failing tmux command string
}Source: src/agents/worker.ts
Exports: src/agents/index.ts
function readTask(projectRoot: string, taskId: string): TaskReads .tasks/task-{taskId}.json and returns the parsed Task.
Throws: Error if the file does not exist or contains invalid JSON.
function claimTask(
projectRoot: string,
taskId: string,
workerId: string,
): TaskAtomically claims a task: sets status = CLAIMED and assignedWorker = workerId, then writes back to disk.
Throws: TaskClaimError if the task is not PENDING or is already assigned.
Example:
import { claimTask } from 'deckent';
const task = claimTask('/project', '006-001', 'worker-abc');
console.log(task.assignedWorker); // 'worker-abc'function acquireLock(
projectRoot: string,
filePath: string,
workerId: string,
taskId: string,
): LockInfoCreates a lock file at .locks/{filePath-as-lockname}.lock. Idempotent if the same worker already holds the lock (returns existing LockInfo).
Parameters:
filePath— Path to the file to lock (separators replaced with__in lock filename).
Throws: LockError if a different worker holds the lock.
Example:
const lock = acquireLock('/project', 'src/core/types.ts', 'worker-abc', '006-001');function releaseLock(
projectRoot: string,
filePath: string,
workerId: string,
): voidDeletes the lock file for filePath. No-op if no lock exists.
Throws: LockError if the lock is held by a different worker.
function releaseAllLocks(projectRoot: string, workerId: string): numberReleases all lock files owned by workerId. Returns the count of locks released.
function checkLock(projectRoot: string, filePath: string): LockInfo | nullReturns the current LockInfo for filePath, or null if no lock exists or the lock file is corrupt.
function createHeartbeat(
workerId: string,
taskId: string,
status: AgentStatus,
action: string,
file?: string,
sequence?: number,
): HeartbeatConstructs a Heartbeat object with the current ISO timestamp. Does not write to disk.
function writeHeartbeat(projectRoot: string, heartbeat: Heartbeat): voidWrites the heartbeat to .tasks/task-{taskId}.hb (JSON format).
Example:
const hb = createHeartbeat('worker-abc', '006-001', AgentStatus.CODING, 'Writing types', 'src/core/types.ts', 1);
writeHeartbeat('/project', hb);function writeResult(projectRoot: string, result: TaskResult): voidWrites the result to .tasks/task-{taskId}.result and updates the task status to DONE or NO_GO based on result.selfAssessment.
function updateTaskStatus(
projectRoot: string,
taskId: string,
status: TaskStatus,
): TaskRead-modify-write: loads the task JSON, sets status and updatedAt, and writes back. Returns the updated Task.
function isWithinScope(filePath: string, scope: TaskScope): booleanReturns true if filePath is within one of the scope's permitted directories or matches one of its filesWrite entries.
Path separators are normalized; trailing-slash prefix overlap is handled (e.g. src/core-extra/ is not within src/core/).
Example:
const scope: TaskScope = { directories: ['src/core/'], filesRead: [], filesWrite: [] };
isWithinScope('src/core/types.ts', scope); // true
isWithinScope('src/core-extra/foo.ts', scope); // falseclass TaskClaimError extends Error {}
class LockError extends Error {
readonly filePath: string;
}
class ScopeViolationError extends Error {
readonly filePath: string;
readonly scope: TaskScope;
}Source: src/monitor/auditor.ts
Exports: src/monitor/index.ts
function scanHeartbeats(projectRoot: string): {
heartbeats: Heartbeat[];
staleAgents: BoundaryViolation[];
alerts: Alert[];
}Reads all .tasks/*.hb files and flags heartbeats older than HEARTBEAT_STALE_THRESHOLD_MS (120 s) as stale. Resilient to corrupt files (skips silently).
function checkBoundaryViolations(
projectRoot: string,
workerScopes: Map<string, TaskScope>,
): BoundaryViolation[]Runs git diff --stat and cross-checks modified files against each worker's assigned scope. Files outside scope produce a file_outside_scope violation.
Parameters:
workerScopes— Map ofworkerId → TaskScope. Build withbuildWorkerScopeMap.
function checkStaleLocks(projectRoot: string): {
locks: LockInfo[];
staleLocks: BoundaryViolation[];
alerts: Alert[];
}Reads all .locks/*.lock files and flags locks held longer than LOCK_STALE_THRESHOLD_MS (300 s).
function detectDeadlocks(tasks: Task[]): BoundaryViolation[]Uses Kahn's topological-sort algorithm to detect circular task dependencies. Returns a single circular_dependency violation listing all tasks in the cycle.
function updateDashboard(projectRoot: string, state: DashboardState): voidWrites the dashboard state to .dashboard (JSON format). The status CLI command reads this file.
Example:
updateDashboard('/project', {
sprint: { id: 'sprint-1', number: 1, phase: SprintPhase.EXECUTE, status: SprintStatus.ACTIVE },
agents: [],
progress: { done: 2, active: 3, blocked: 0, total: 5 },
alerts: [],
updatedAt: new Date().toISOString(),
});function detectPatterns(
projectRoot: string,
violations: BoundaryViolation[],
currentSprintId: string,
): voidGroups violations by type, updates or creates PatternEntry records in .brain/PATTERNS.md. Truncates the file if it exceeds PATTERNS_MAX_LINES.
function buildWorkerScopeMap(projectRoot: string): Map<string, TaskScope>Reads all .tasks/task-*.json files and returns a map of assignedWorker → scope for tasks with an assigned worker.
function runScanCycle(
projectRoot: string,
currentSprintId: string,
): {
heartbeats: Heartbeat[];
violations: BoundaryViolation[];
alerts: Alert[];
locks: LockInfo[];
}Runs a complete scan: heartbeats + boundary violations + stale locks + deadlock detection + pattern detection. Resilient — returns empty arrays on any error.
function startScanLoop(
projectRoot: string,
currentSprintId: string,
intervalMs?: number,
onScanComplete?: (result: ScanResult) => void,
): ReturnType<typeof setInterval>Starts an interval that calls runScanCycle repeatedly. Never throws. Called by Brain in Phase 2.5 of runSprint.
Parameters:
intervalMs— Default:AUDITOR_SCAN_INTERVAL_MS(30,000 ms).onScanComplete— Optional callback invoked after each scan cycle. Errors in the callback do not kill the loop.
Returns: The interval handle. Call clearInterval to stop (Brain does this in Phase 3.5).
function writeScanToDashboard(
projectRoot: string,
sprintInfo: { id: string; number: number; phase: SprintPhase; status: SprintStatus },
scanResult: ScanResult,
): voidMerges scan results into the existing dashboard state. Reads the current .dashboard file, merges new alerts (keeps last 50), updates agent statuses from heartbeats, and overwrites. Used as the onScanComplete callback in startScanLoop.
function createAlert(level: AlertLevel, message: string, source?: string): AlertFactory function for Alert objects. Stamps the current ISO timestamp automatically.
Source: src/mcp/server.ts
function createServer(): McpServerCreates and configures an MCP server named 'deckent' (version from DECKENT_VERSION). Registers all 19 tools and 8 resources.
Example:
import { createServer } from 'deckent/mcp/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = createServer();
await server.connect(new StdioServerTransport());| Tool name | Description |
|---|---|
deckent_init |
Initialize .deckent/ project structure, brain files, and MCP registration |
deckent_set_directives |
Set or replace the contents of DIRECTIVES.md |
deckent_plan |
Plan a sprint (read directives, write task files) without spawning workers |
deckent_start |
Run a full sprint lifecycle (plan → spawn → execute → evaluate → retro → cleanup) |
deckent_status |
Return the current dashboard state as JSON |
deckent_doctor |
Run all health checks (Node, git, tmux, Claude CLI, workspace, budget) |
deckent_retro |
Return the latest sprint retrospective (RETRO.md) |
deckent_history |
Return recent sprint log summaries |
deckent_analyze_project |
Analyze project stack, size, and methodology recommendation |
deckent_sync |
Sync adapter files (CLAUDE.md, AGENTS.md) with @DECKENT.md reference |
deckent_config |
Read or update project configuration |
deckent_review |
Evaluate sprint results: GO / NO_GO / GO_WITH_TECH_DEBT |
deckent_run |
Run a single task in the background |
deckent_kill |
Kill active sprint or specific workers |
deckent_cleanup |
Archive task files, release locks, close sessions |
deckent_help |
Show runtime capabilities, project status, and usage guide |
deckent_agent_list |
List registered agents (built-in and temp) |
deckent_skill_list |
List registered skills (manifest and AST sandbox info) |
deckent_checkpoint |
Approve or reject human checkpoints |
deckent_docs |
Manage user-defined documents in sprint lifecycle |
deckent_explain |
Explain sprint history and results |
{
dryRun?: boolean; // default: false
mode?: 'ai' | 'structured' | 'auto'; // default: from config (brain_planning)
}{
projectName: string;
mode?: 'performance' | 'balanced' | 'economic' | 'api'; // default: 'performance'
// Legacy aliases: max_plan → performance, max5x_plan → balanced, pro_plan → economic
language?: 'en' | 'tr'; // default: 'en'
}{
autoApprove?: boolean; // default: false
}Resources provide read-only context that MCP hosts can inject into the AI's context window.
| Resource URI | MIME Type | Description |
|---|---|---|
deckent://dashboard |
application/json | Current DashboardState with sprint progress, agents, alerts |
deckent://directives |
text/markdown | Current contents of DIRECTIVES.md |
deckent://memory |
text/markdown | Current contents of .brain/MEMORY.md (learned patterns) |
deckent://debt |
text/markdown | Current contents of .brain/DEBT.md (technical debt items) |
deckent://config |
application/json | Current project config from .deckent/config.json |
deckent://retro |
text/markdown | Latest sprint retrospective report |
deckent://tasks |
application/json | Current sprint task list and statuses |
deckent://agents |
application/json | Registered agent pool, stats, usage rates |
Register the Deckent MCP server with Claude Code:
claude mcp add deckent -- npx deckent mcpOr let deckent init handle registration automatically. The MCP server is registered in .claude/settings.json:
{
"mcpServers": {
"deckent": {
"command": "deckent-mcp",
"args": []
}
}
}No authentication is required for the MCP server when running locally. It is intended for local development use only. The HTTP API supports optional Bearer token authentication for POST endpoints — see Section 11 for details.
Source: src/api/server.ts, src/api/watcher.ts
Start the server with deckent serve (API only) or deckent web (API + web dashboard). Default port: 3100. The server binds to 127.0.0.1 (localhost only) and is intended for local development use.
POST endpoints are protected by an optional Bearer token. GET endpoints (including /api/events) do not require authentication.
Enabling auth: Set the token via environment variable or project config:
# Environment variable (takes precedence)
export DECKENT_API_TOKEN=your-secret-token
# Or in .deckent/config.json
{ "api_token": "your-secret-token" }If no token is configured, auth is disabled and a warning is printed to stderr:
[deckent:warn] API server running without authentication. Set DECKENT_API_TOKEN or config.api_token to enable auth.
Request header for protected routes:
Authorization: Bearer <token>
Auth error response (401):
{ "error": "Unauthorized — provide Authorization: Bearer <token>" }Token comparison uses SHA-256 + timingSafeEqual to prevent timing side-channel attacks. Tokens with incorrect scheme (not Bearer) are rejected.
function createHttpServer(projectRoot: string, opts?: HttpServerOptions): HttpApi
interface HttpServerOptions {
port?: number; // Default: 3100
staticDir?: string; // Serve static files from this directory (deckent web)
apiToken?: string; // Bearer token for POST endpoints
host?: string; // Bind address. Default: '127.0.0.1'
}
interface HttpApi {
server: Server;
close(): Promise<void>;
}Creates an HTTP server with all API routes and optional static file serving for the web dashboard. Returns an object with the raw Node.js Server and a close() method that shuts down the server and terminates all active SSE connections.
All responses include Access-Control-Allow-Origin restricted to http://localhost:* and http://127.0.0.1:*. Cross-origin requests from external hosts are blocked. Preflight (OPTIONS) requests are handled automatically.
All error responses use HTTP status codes and return JSON:
{ "error": "Human-readable error message" }| Status | Meaning |
|---|---|
400 |
Bad request — invalid JSON body or failed schema validation |
401 |
Unauthorized — missing or invalid Bearer token on a POST route |
403 |
Forbidden — path traversal attempt on static file serving |
404 |
Not found — resource does not exist (no active sprint, missing file, etc.) |
405 |
Method not allowed — HTTP method not supported for this route |
409 |
Conflict — sprint already running when POST /api/start is called |
500 |
Internal server error — unexpected failure |
Returns the current dashboard state from the .dashboard file.
Authentication: Not required.
Response 200:
{
"sprint": {
"id": "sprint-037",
"number": 37,
"phase": "EXECUTE",
"status": "ACTIVE"
},
"agents": [...],
"alerts": [...],
"metrics": { ... }
}Error 404: { "error": "No active sprint" } — returned when no .dashboard file exists.
curl http://localhost:3100/api/statusReturns the latest sprint log parsed from .brain/sprints/sprint-NNN.md.
Authentication: Not required.
Response 200:
{
"id": "sprint-037",
"metrics": {
"tasks": "11",
"completed": "11",
"noGoRate": "0%",
"coverage": "92%",
"duration": "47m"
},
"tasks": [
"001 Extract sprint-controller.ts — DONE",
"002 Extract result-evaluator.ts — DONE"
]
}Error 404: { "error": "No sprint logs found" } — returned when no sprint log files exist.
curl http://localhost:3100/api/sprintReturns all sprint logs as an array, sorted oldest-first.
Authentication: Not required.
Response 200: Array of sprint log objects (same shape as /api/sprint metrics, plus id field). Returns [] if no sprint logs exist.
curl http://localhost:3100/api/historyReturns the project configuration from .deckent/config.json.
Authentication: Not required.
Response 200:
{
"mode": "performance",
"language": "en",
"brain_planning": "ai",
"performance": { "max_workers": 8, "model": "opus" },
"economic": { "max_workers": 4, "model": "sonnet" }
}Error 404: { "error": "Config not found" } — returned when .deckent/config.json does not exist.
curl http://localhost:3100/api/configRuns system health checks and returns results.
Authentication: Not required.
Response 200:
{
"ok": true,
"checks": [
{ "name": "tmux", "passed": true, "message": "tmux 3.3a found", "required": true },
{ "name": "claude", "passed": true, "message": "claude CLI found", "required": true },
{ "name": "node", "passed": true, "message": "Node.js v20.11.0 found", "required": true }
]
}ok is true only when all checks with required: true pass.
curl http://localhost:3100/api/doctorReturns the content of .brain/MEMORY.md as a JSON-wrapped string.
Authentication: Not required.
Response 200:
{ "content": "## Sprint 036 Learnings\n- brain.ts split: 1312 → 58 lines\n..." }Error 404: { "error": "Memory file not found" }.
curl http://localhost:3100/api/memoryReturns the content of .brain/DEBT.md as a JSON-wrapped string.
Authentication: Not required.
Response 200:
{ "content": "| ID | Description | Priority | Sprint |\n|---|---|---|---|\n| D-001 | ... |" }Error 404: { "error": "Debt file not found" }.
curl http://localhost:3100/api/debtReturns the status of an async sprint job started via POST /api/start.
Authentication: Not required.
Path parameter: jobId — the job ID returned by POST /api/start (format: job-<timestamp>).
Response 200:
{
"id": "job-1710768000000",
"status": "running"
}{
"id": "job-1710768000000",
"status": "completed",
"result": { ... }
}{
"id": "job-1710768000000",
"status": "failed",
"error": "Sprint failed: no tasks found in DIRECTIVES.md"
}status values: "running" | "completed" | "failed"
Error 404: { "error": "Job not found" } — returned when the job ID does not match the current active job.
curl http://localhost:3100/api/job/job-1710768000000Returns the task JSON and terminal log output for a specific worker.
Authentication: Not required.
Path parameter: taskId — task identifier (e.g. 001-001).
Response 200:
{
"taskId": "001-001",
"log": "Worker started...\ntsc --noEmit passed\nvitest run: 42 tests passed",
"task": {
"id": "001-001",
"title": "Extract sprint-controller.ts",
"status": "DONE",
"model": "opus",
"effort": "high"
}
}log may be null if no log file exists yet. task is the parsed .tasks/task-{taskId}.json.
Error 400: { "error": "Missing taskId" }.
Error 404: { "error": "Task not found" } — task JSON file does not exist.
curl http://localhost:3100/api/worker/001-001/logOpens a Server-Sent Events (SSE) stream. The server pushes a data: line containing the full dashboard state as JSON whenever the .dashboard file changes. Changes are debounced at 500ms.
Authentication: Not required.
Response headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Event format:
Each event is a bare data: line (no named event type) followed by a blank line:
data: {"sprint":{"id":"sprint-037","number":37,"phase":"EXECUTE","status":"ACTIVE"},...}
data: {"sprint":{"id":"sprint-037","number":37,"phase":"EVALUATE","status":"EVALUATING"},...}
The watcher is initialized lazily on the first SSE client connection. When a client connects, any existing dashboard state is sent immediately. When the client disconnects, it is removed from the broadcast set.
Connection setup (JavaScript):
const es = new EventSource('http://localhost:3100/api/events');
es.onmessage = (event) => {
const state = JSON.parse(event.data);
console.log(state.sprint.phase);
};
es.onerror = () => console.error('SSE connection lost');# Stream events (Ctrl-C to stop)
curl -N http://localhost:3100/api/eventsAll POST endpoints require Content-Type: application/json. If an api_token is configured, all POST endpoints also require Authorization: Bearer <token>.
Starts a sprint asynchronously. Returns a jobId immediately (HTTP 202). Poll GET /api/job/:jobId to track progress.
Authentication: Required if api_token is configured.
Request body:
{ "autoApprove": true }| Field | Type | Required | Description |
|---|---|---|---|
autoApprove |
boolean |
No | Pass --dangerously-skip-permissions to workers |
Response 202:
{ "jobId": "job-1710768000000", "status": "started" }Error 400: Body failed schema validation.
Error 409: { "error": "Sprint already running" } — a job with status: "running" already exists.
# Start with manual approval (default)
curl -X POST http://localhost:3100/api/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-token" \
-d '{}'
# Start with auto-approve
curl -X POST http://localhost:3100/api/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-token" \
-d '{"autoApprove": true}'Generates a sprint plan from DIRECTIVES.md and returns the task list synchronously. Does not spawn workers.
Authentication: Required if api_token is configured.
Request body:
{ "mode": "ai" }| Field | Type | Required | Description |
|---|---|---|---|
mode |
"ai" | "structured" | "auto" |
No | Planning mode override. Defaults to the project config value. |
Response 200: The generated sprint plan object (array of task drafts with id, title, model, effort, scope, go/no-go criteria).
Error 400: Body failed schema validation (e.g. mode value not in allowed enum).
Error 500: Planning failed (e.g. AI planner returned invalid output).
curl -X POST http://localhost:3100/api/plan \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-token" \
-d '{"mode": "ai"}'Kills a running worker tmux window by worker ID.
Authentication: Required if api_token is configured.
Path parameter: workerId — must match [a-zA-Z0-9-]+.
Response 200:
{ "success": true }Error 400: Missing or invalid workerId format.
Error 500: Kill command failed.
curl -X POST http://localhost:3100/api/kill/001-002 \
-H "Authorization: Bearer your-secret-token"Overwrites DIRECTIVES.md with the provided content. Also returns a count of ## Task blocks found in the content.
Authentication: Required if api_token is configured.
Request body:
{ "content": "# DIRECTIVES — Sprint 038\n\n## Task 1: Fix login bug\n..." }| Field | Type | Required | Description |
|---|---|---|---|
content |
string |
Yes | Full file content. Must be non-empty. |
Response 200:
{ "success": true, "taskCount": 5 }taskCount is the number of ## Task headings found (lines matching ^## Task).
Error 400: { "error": "Missing content field" } — content is missing or empty.
Error 500: File write failed.
curl -X POST http://localhost:3100/api/set-directives \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-token" \
-d '{"content": "# DIRECTIVES — Sprint 038\n\n## Task 1: Fix the login bug\n- Description: ...\n"}'Merges the provided fields into the project config (/.deckent/config.json) and returns the full merged result. Unknown keys are preserved.
Authentication: Required if api_token is configured.
Request body: Any JSON object. Keys are merged with the existing config using shallow merge ({ ...existing, ...body }).
{ "mode": "economic", "language": "en" }Response 200: The full merged config object after writing to disk.
{
"mode": "economic",
"language": "en",
"brain_planning": "ai",
"performance": { "max_workers": 8, "model": "opus" }
}Error 400: Body is not a valid JSON object.
Error 500: File write failed.
curl -X POST http://localhost:3100/api/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-token" \
-d '{"mode": "economic"}'function watchDashboard(
filePath: string,
onChange: () => void,
): DashboardWatcher
interface DashboardWatcher {
close(): void;
}Watches the .dashboard file for changes using fs.watch and calls onChange with a 500ms debounce. The returned object has a close() method that cancels any pending debounce timer and closes the underlying file watcher.
Run via deckent <command> (globally installed) or npx deckent <command>.
| Command | Description | Key Options |
|---|---|---|
init |
Initialize a new Deckent project (interactive wizard) | — |
start |
Start a new sprint (full lifecycle) | --auto-approve, --sandbox-mode, --dry-run, --force, --watch |
plan |
Plan a sprint without executing it | — |
status |
Show the current sprint dashboard | --watch (auto-refresh 2s), --json |
doctor |
Check system dependencies and health | — |
retro |
Show the latest sprint retrospective | — |
history |
Show recent sprint history | — |
spawn <taskId> |
Manually spawn a worker for a specific task | — |
attach |
Attach terminal to the tmux orchestra session | — |
kill <taskId> |
Kill a running worker window | — |
cleanup |
Clean up locks and heartbeats after a sprint | --decay (force memory decay) |
config |
Show project configuration | — |
config set <key> <value> |
Set a configuration value | — |
upgrade |
Self-update deckent to the latest version | — |
plugin |
Manage plugins | — |
plugin install <name> |
Install a plugin | — |
plugin list |
List installed plugins | — |
onboard |
Run the first-time onboarding wizard | — |
analyze |
Analyze project stack, size, methodology | — |
archive-debt |
Archive resolved technical debt | — |
dashboard |
Terminal TUI dashboard (rich mode) | — |
serve |
Start HTTP API server (SSE) | --port |
web |
Web dashboard + API server | --port |
sync |
Sync adapter files (CLAUDE.md, AGENTS.md) with DECKENT.md reference | — |
watch |
Live tmux split view: dashboard + worker panes | --follow <taskId> |
# Initialize a project
deckent init
# Run a sprint with auto-approved workers
deckent start --auto-approve
# Dry-run: plan tasks but don't spawn workers
deckent start --dry-run
# Watch the live dashboard
deckent status --watch
# Get raw JSON dashboard
deckent status --json
# Run health checks
deckent doctor
# Clean up and force memory decay
deckent cleanup --decay