{
  "markdown": "<p align=\"center\">\n  <img src=\"assets/epoch-hero.webp\" alt=\"Epoch — estimation MCP server for AI agents: PERT, COCOMO II, Monte Carlo, schedule-risk tools\" width=\"100%\">\n</p>\n\n# Epoch -- Time Estimation MCP Server\n\n> Epoch is a time estimation MCP server that helps engineering leads, agents, and planners who need calibrated duration estimates produce PERT/reference-class estimates and record actuals.\n\n**TL;DR:** Epoch — time estimation MCP server. Best for engineering leads, agents, and planners who need calibrated duration estimates.\n\n[![CI](https://github.com/KyaniteLabs/Epoch/actions/workflows/ci.yml/badge.svg)](https://github.com/KyaniteLabs/Epoch/actions/workflows/ci.yml) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/KyaniteLabs/Epoch/blob/main/LICENSE) [![MCP](https://img.shields.io/badge/MCP-Server-green.svg)](https://modelcontextprotocol.io) [![npm version](https://img.shields.io/npm/v/@kyanitelabs/epoch.svg)](https://www.npmjs.com/package/@kyanitelabs/epoch) [![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue.svg)](https://registry.modelcontextprotocol.io/servers/io.github.KyaniteLabs/Epoch)\n\n**Epoch helps AI agents understand time.**\n\nAI can write code, analyze data, and build apps -- but it can't tell you how long something will take, what it'll cost, or whether a deadline is realistic. Epoch fixes that.\n\n---\n\n## Show Me\n\n```\nYou: \"How long will a 15,000 line project take with a small team?\"\n\nClaude (using Epoch):\n  COCOMO II nominal: 100 person-months (traditional team)\n  AI-adjusted: 9 person-months (LLM-assisted workflow)\n  Schedule risk: medium (MAPE 15%, based on 28 historical features)\n  Cost estimate (Claude Sonnet): ~$340 in API tokens\n```\n\n```\nYou: \"How many working days between now and launch?\"\n\nClaude (using Epoch):\n  Start: May 1, 2026\n  End: July 15, 2026\n  Business days: 54\n  Weekend days: 16\n  Holidays: 1 (Independence Day)\n  Total calendar days: 75\n```\n\n```\nYou: \"Which AI model should we use for a 50k-token job?\"\n\nClaude (using Epoch):\n  gemini-2.0-flash:  $0.02 -- fast, budget-friendly\n  gpt-4o-mini:       $0.02 -- fast, budget-friendly\n  gemini-2.5-pro:    $0.37 -- standard, high quality\n  claude-sonnet-5:    $0.57 -- standard, high quality\n  Recommendation: gemini-2.0-flash for cost, claude-sonnet-5 for quality\n```\n\n## Why Epoch?\n\nEvery AI agent hallucinates timelines. \"This should take about 2 hours\" becomes 2 days. Epoch gives AI grounded, data-driven estimates instead of guesses. It packages established estimation methods (PERT, COCOMO II, Monte Carlo, reference class forecasting) into 25 tools any AI can call -- so your assistant stops guessing and starts calculating.\n\n**Works out of the box.** Epoch ships with a bundled reference database built from 117,791 real data points across task types, complexity levels, and estimation tools. You get accurate estimates from day one — no data collection or account setup required. If you choose to record your actuals, Epoch's self-improvement engine learns your patterns and gets even more precise over time.\n\n## What is MCP?\n\nMCP (Model Context Protocol) is how AI assistants like Claude connect to external tools. Think of it like a plugin system -- you add Epoch with one command, and suddenly your AI assistant can estimate timelines, calculate business days, compare model costs, and predict whether your project will finish on time.\n\n## Quick Start\n\n**30-second setup -- works in Claude Code, Cursor, VS Code, and Windsurf:**\n\n```bash\nclaude mcp add epoch -- npx @kyanitelabs/epoch\n```\n\nThat's it. Your AI assistant now has 25 time estimation tools.\n\nOr add it to your project's `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"epoch\": {\n      \"command\": \"npx\",\n      \"args\": [\"@kyanitelabs/epoch\"]\n    }\n  }\n}\n```\n\n## Agent Skill\n\nEpoch also ships a public agent skill at [`skills/epoch/SKILL.md`](skills/epoch/SKILL.md). Use `$epoch` in compatible agent hosts when you want the agent to choose the right Epoch MCP or CLI workflow for time estimates, business-day math, model-cost comparison, schedule risk, and estimate-vs-actual feedback.\n\n## What Can Epoch Do?\n\n| What you want | What Epoch does | No jargon |\n|---|---|---|\n| \"How long will this take?\" | Gives you a realistic estimate with best/worst case ranges | Estimates |\n| \"Can we hit this deadline?\" | Tells you if your timeline is realistic or risky | Schedule risk |\n| \"How much will the AI calls cost?\" | Calculates token costs across 16 AI models side-by-side | Cost comparison |\n| \"How many business days between now and launch?\" | Counts days excluding weekends and holidays (5 countries) | Calendar math |\n| \"Are our estimates getting better?\" | Tracks your accuracy over time and auto-corrects | Self-improving |\n| \"What model should we use?\" | Compares speed, cost, and quality across all major AI models | Model comparison |\n\n---\n\n# Technical Reference\n\nEverything below is for developers who want to understand the internals, use the CLI or REST API, or contribute to Epoch.\n\n## Architecture\n\nSix-layer design with 25 tools for time estimation, scheduling, cost analysis, and feedback:\n\n| Layer | Purpose | Tools |\n|-------|---------|-------|\n| **1. Core Temporal** | Time, timezones, duration, date math | `get_current_time`, `convert_timezone`, `parse_duration`, `time_math` |\n| **2. Calendar Math** | Business days, holidays (US/UK/FR/DE/JP) | `add_business_days`, `count_business_days` |\n| **3. Estimation** | PERT, COCOMO II, sprint, CPM, Monte Carlo | `pert_estimate`, `cocomo_estimate`, `sprint_forecast`, `critical_path`, `monte_carlo_schedule` |\n| **4. Analytics** | Reference class, context classification, calibration, token-time bridge | `reference_class_estimate`, `estimate_from_context`, `calibrate_estimates`, `token_time_bridge` |\n| **5. Cost & Risk** | Token cost, model comparison, accuracy trends, risk, COCOMO validation | `token_cost_estimate`, `compare_models`, `accuracy_trend`, `schedule_risk`, `cocomo_validate`, `cocomo_ground_truth` |\n| **6. Feedback** | Record actuals, track pending estimates, batch operations, health checks | `record_actual`, `get_pending_estimates`, `batch_record_actuals`, `feedback_health` |\n\n## Tool Reference\n\n### Layer 1 -- Core Temporal\n\n**`get_current_time`** -- Current wall-clock time in any IANA timezone\n\n```\nInput:  { timezone: \"America/New_York\" }\nOutput: {\n  iso: \"2026-05-01T08:30:00.000-04:00\",\n  humanReadable: \"Fri, May 1, 2026, 8:30 AM EDT\",\n  timezone: \"America/New_York\",\n  utcOffset: \"-04:00\"\n}\n```\n\n**`convert_timezone`** -- Convert a timestamp between IANA timezones\n\n```\nInput:  { timestamp: \"2026-05-01T12:00:00Z\", target_tz: \"Asia/Tokyo\" }\nOutput: {\n  iso: \"2026-05-01T21:00:00.000+09:00\",\n  timezone: \"Asia/Tokyo\",\n  utcOffset: \"+09:00\",\n  humanReadable: \"Fri, May 1, 2026, 9:00 PM JST\"\n}\n```\n\n**`parse_duration`** -- Parse human-readable duration strings\n\n```\nInput:  { duration_string: \"2h30m\" }\nOutput: {\n  input: \"2h30m\",\n  totalSeconds: 9000,\n  humanReadable: \"2 hours 30 minutes\"\n}\n```\n\n**`time_math`** -- Date arithmetic operations\n\n```\nInput:  { operation: \"add_days\", date: \"2026-05-01\", value: 7 }\nOutput: {\n  result: \"2026-05-08T00:00:00.000Z\",\n  operation: \"add_days\",\n  input: \"2026-05-01\"\n}\n```\n\nSupported operations: `add_days`, `add_business_days`, `diff`, `convert_tz`, `parse_nl`, `format_duration`\n\n### Layer 2 -- Calendar Math\n\n**`add_business_days`** -- Add N business days with holiday awareness (US, UK, FR, DE, JP)\n\n```\nInput:  { start_date: \"2026-05-01\", days: 5, country: \"US\" }\nOutput: {\n  startDate: \"2026-05-01\",\n  endDate: \"2026-05-08\",\n  businessDays: 5,\n  countryCode: \"US\",\n  holidaySupport: \"holiday_calendar\",\n  humanReadable: \"5 business days from 2026-05-01 to 2026-05-08 (US).\"\n}\n```\n\n**`count_business_days`** -- Count business days between two dates\n\n```\nInput:  { start_date: \"2026-05-01\", end_date: \"2026-05-15\", country: \"US\" }\nOutput: {\n  startDate: \"2026-05-01\",\n  endDate: \"2026-05-15\",\n  businessDays: 10,\n  countryCode: \"US\",\n  holidaySupport: \"holiday_calendar\",\n  humanReadable: \"10 business days between 2026-05-01 and 2026-05-15 (US).\"\n}\n```\n\nBoth tools always include `holidaySupport` (`\"holiday_calendar\"` when the country has a bundled holiday set — US/UK/FR/DE/JP — otherwise `\"weekends_only\"`) in successful output; `country` must match `^[A-Za-z]{2}$` — \"USA\"-style codes are rejected with a readable error.\n\n### Layer 3 -- Estimation\n\n**`pert_estimate`** -- PERT three-point estimation with confidence intervals and urgency scoring\n\n```\nInput:  {\n  optimistic: 2,\n  most_likely: 4,\n  pessimistic: 12,\n  unit: \"hours\"\n}\nOutput: {\n  expected: 5,\n  variance: 2.78,\n  stdDeviation: 1.67,\n  confidence95: [1.67, 8.33],\n  confidence99: [0, 10],\n  unit: \"hours\",\n  urgencyCategory: \"medium\",\n  riskLevel: \"high\",\n  humanReadable: \"Expected 2.86–7.14 hours (80% confidence interval); point estimate 5 hours (ledger-recorded basis; adjustedEstimate 5 applies the correction factor). No task_type was supplied, so this interval is derived from the PERT variance (optimistic/most_likely/pessimistic spread) instead of empirical data.\",\n  developerProfile: { mode: \"ai_native\", correctionFactor: 1 },\n  adjustedEstimate: 5,\n  rawEstimate: 5,\n  correctionFactor: 1,\n  n: 0,\n  interval: { p50: { lower: 3.87, upper: 6.13 }, p80: { lower: 2.86, upper: 7.14 }, p90: { lower: 2.25, upper: 7.75 }, source: \"pert_variance\" },\n  intervalNote: \"No task_type was supplied, so this interval is derived from the PERT variance (optimistic/most_likely/pessimistic spread) instead of empirical data.\",\n  basisNote: \"Interval and point estimate are on the ledger-recorded basis (raw PERT expected × unit factor). adjustedEstimate (5 hours) additionally applies the correction factor (1) and is display-only — it is never recorded or calibrated against.\"\n}\n```\n\n(Example from a fresh install with no accumulated feedback; with ≥5 exclusion-filtered matched pairs for the task type, `interval.source` becomes `\"empirical_ratio_quantile\"`, `n` reports the sample size, and `intervalPopulation` names the ratio population used.)\n\n**`cocomo_estimate`** -- COCOMO II software sizing with LLM-adapted cost drivers\n\n```\nInput:  {\n  kloc: 15,\n  reasoning_complexity: 1.2,\n  context_completeness: 1.0,\n  transformation_impact: 0.8,\n  iterative_cycles: 1.5,\n  human_oversight: 1.2\n}\nOutput: {\n  kloc: 15,\n  personMonthsNominal: 99.9,\n  personMonthsLlmAdjusted: 8.9,\n  effortMultipliers: {\n    reasoning_complexity: 1.2,\n    context_completeness: 1.0,\n    transformation_impact: 0.8,\n    iterative_cycles: 1.5,\n    human_oversight: 1.2,\n    product: 1.728\n  },\n  developerProfile: { mode: \"ai_native\", correctionFactor: 1.45 }\n}\n```\n\nLLM-adapted cost drivers include reasoning complexity, context completeness, transformation impact, iterative cycles, and human oversight requirements.\n\n**`sprint_forecast`** -- Sprint velocity forecasting from historical data\n\n```\nInput:  {\n  backlog_points: 100,\n  velocity_history: [20, 25, 22, 23],\n  sprint_length_days: 14,\n  hours_per_sprint: 80\n}\nOutput: {\n  backlogPoints: 100,\n  averageVelocity: 22.5,\n  requiredSprints: 4.4,\n  pessimisticSprints: 4.9,\n  hoursPerPoint: 3.56,\n  totalHours: 355.6,\n  completionDays: 62,\n  sprintLengthDays: 14,\n  developerProfile: { mode: \"ai_native\", sprintVelocityPoints: 80, correctionFactor: 1.45 }\n}\n```\n\n**`critical_path`** -- Critical Path Method with merge-bias adjustment for parallel tasks\n\n```\nInput:  {\n  tasks: [\n    { name: \"A\", duration: 5, predecessors: [] },\n    { name: \"B\", duration: 3, predecessors: [\"A\"] },\n    { name: \"C\", duration: 4, predecessors: [\"A\"] }\n  ]\n}\nOutput: {\n  critical_path: [\"A\", \"C\"],\n  total_duration: 9,\n  slack_per_task: { A: 0, B: 1, C: 0 },\n  merge_bias_adjustment: 0\n}\n```\n\n**`monte_carlo_schedule`** -- Monte Carlo simulation with seeded PRNG for deterministic, reproducible results\n\n```\nInput:  {\n  tasks: [\n    { name: \"A\", optimistic: 2, most_likely: 4, pessimistic: 8 },\n    { name: \"B\", optimistic: 1, most_likely: 3, pessimistic: 6 }\n  ],\n  iterations: 10000\n}\nOutput: {\n  p10: \"5.9\",\n  p50: \"7.91\",\n  p80: \"9.39\",\n  p95: \"10.75\",\n  riskEvents: [\n    { description: \"Task \\\"A\\\" exceeded 1.5x PERT expected in 10% of simulations\", probability: 0.1, impactDays: 0.05 },\n    { description: \"Task \\\"B\\\" exceeded 1.5x PERT expected in 10% of simulations\", probability: 0.1, impactDays: 0.04 }\n  ],\n  criticalPathProbability: null\n}\n```\n\n(`criticalPathProbability` is `null` unless a `target_hours` deadline is supplied — then it is the real P(total ≤ target); `riskEvents[].impactDays` is per-task expected overrun, sorted by impact.)\n\n### Layer 4 -- Analytics\n\n**`reference_class_estimate`** -- Reference class forecasting with planning fallacy correction\n\n```\nInput:  {\n  task_type: \"feature\",\n  complexity: 3\n}\nOutput: {\n  rawEstimate: 2,\n  correctedEstimate: 2,\n  correctionFactor: 1,\n  sampleSize: 0,\n  baselineSource: \"inferred_scope_medium_real_tasks\",\n  scopeUsed: \"medium\",\n  scopeInferred: true,\n  confidence: \"pessimistic\",\n  developerProfile: { mode: \"ai_native\", estimationMape: 15, underestimationBias: 0.2, correctionFactor: 1 },\n  adjustedEstimate: 2,\n  basisNote: \"correctedEstimate (2 hours) is the ledger-recorded and displayed basis (rawEstimate × correctionFactor). adjustedEstimate (2 hours) additionally applies the developerProfile factor (1) and is display-only — it is never recorded or calibrated against.\",\n  intervalNote: \"Fewer than 5 exclusion-filtered historical \\\"feature\\\" reference_class_estimate pairs are available yet, so no empirical confidence interval could be computed.\"\n}\n```\n\n(Fresh-install output; with accumulated feedback the correction factor and sample size come from your own matched pairs and the empirical interval is populated — `correctedEstimate` is always the recorded basis.)\n\nValid `task_type` values: `feature`, `bugfix`, `refactor`, `migration`, `infrastructure`, `documentation`, `testing`, `design`.\n\n**`estimate_from_context`** -- Classify a free-text task description and delegate to reference class estimation\n\n```\nInput:  {\n  context: \"Add OAuth2 login support to the API, including refresh token rotation and a new /auth/callback endpoint\"\n}\nOutput: {\n  tool: \"estimate_from_context\",\n  rawEstimate: 2,\n  correctedEstimate: 2,\n  correctionFactor: 1,\n  sampleSize: 0,\n  baselineSource: \"inferred_scope_medium_real_tasks\",\n  scopeUsed: \"medium\",\n  scopeGuide: \"For feature tasks: small=~2.3h, medium=~6h, large=~10.6h, xl=~17h\",\n  classification: {\n    classified_task_type: \"feature\",\n    classified_complexity: 3,\n    confidence: \"medium\",\n    signals: [\"task_type_matched:feature\"],\n    task_type_from_hint: false,\n    complexity_from_hint: false\n  },\n  note: \"Using reference database correction factors. Submit actuals via record_actual to improve accuracy.\"\n}\n```\n\nClassifies `task_type` and `complexity` from free text (an issue body, PR/diff description, or task summary) using a local, deterministic keyword/signal heuristic -- no LLM call is made. Caller-supplied `task_type`/`complexity` hints always override the classification. The resolved inputs are then delegated to the same reference-class-forecasting path used by `reference_class_estimate`, so the response carries the same estimate fields plus a `classification` provenance block explaining how the tool read the context. When classification confidence is low, an additional `lowConfidenceNote` field is returned rather than silently guessing.\n\n**`calibrate_estimates`** -- Team-specific accuracy calibration from historical estimated vs actual data\n\n```\nInput:  {\n  task_type: \"feature\",\n  team_id: \"backend\"\n}\nOutput: {\n  correctionFactor: 1.45,\n  accuracyTrend: \"stable\",\n  velocityTrend: \"stable\",\n  recommendations: [\n    \"Using reference database correction factor (1.45x) — personalized from 117,791 samples.\",\n    \"Record actuals via POST /v1/feedback/record-actual to refine for your team's patterns.\"\n  ]\n}\n```\n\n**`token_time_bridge`** -- Map LLM token budgets to wall-clock time for 16 model families\n\n```\nInput:  {\n  tokens: 50000,\n  model: \"claude-sonnet-4-20250514\",\n  tool_calls: 10,\n  reasoning_depth: \"deep\"\n}\nOutput: {\n  estimatedSeconds: 697,\n  estimatedMinutes: 11.6,\n  confidence: \"likely\",\n  urgency: \"short\",\n  breakdown: {\n    promptTokens: 15000,\n    completionTokens: 35000,\n    toolOverheadSeconds: 2\n  }\n}\n```\n\n### Layer 5 -- Cost & Risk\n\n**`token_cost_estimate`** -- Token cost estimation for LLM API calls\n\n```\nInput:  {\n  tokens: 50000,\n  model: \"claude-sonnet-5\"\n}\nOutput: {\n  tokens: 50000,\n  model: \"claude-sonnet-5\",\n  estimatedSeconds: 695,\n  estimatedMinutes: 11.6,\n  estimatedCost: 0.57,\n  costBreakdown: { inputCost: 0.045, outputCost: 0.525, toolCallOverheadCost: 0 },\n  confidence: \"likely\"\n}\n```\n\n**`compare_models`** -- Side-by-side cost and capability comparison across LLM models\n\n```\nInput:  {\n  tokens: 50000,\n  sort_by: \"cost\"\n}\nOutput: {\n  tokens: 50000,\n  models: [\n    { model: \"gemini-2.0-flash\", estimatedCost: 0.0155, qualityTier: \"fast\", tokensPerSecond: 230 },\n    { model: \"deepseek-v3\", estimatedCost: 0.0189, qualityTier: \"standard\", tokensPerSecond: 97 },\n    { model: \"gpt-4o-mini\", estimatedCost: 0.0233, qualityTier: \"fast\", tokensPerSecond: 180 }\n  ],\n  sortBy: \"cost\"\n}\n```\n\n**`accuracy_trend`** -- Track estimation accuracy over time from recorded feedback data\n\n```\nInput:  { team_id: \"backend\", window_size: 50 }\nOutput: {\n  overallTrend: \"improving\",\n  currentMape: 26.5,\n  industryBaselineMape: 25,\n  totalEstimates: 1049,\n  totalWithActuals: 1049,\n  windows: [{ period: \"Window 1 (estimates 1-50)\", mape: 32, bias: 5.3, sampleSize: 50 }]\n}\n```\n\n**`schedule_risk`** -- Schedule risk scoring for project timelines\n\n```\nInput:  {\n  estimated_hours: 40,\n  task_type: \"feature\"\n}\nOutput: {\n  estimatedHours: 40,\n  riskLevel: \"low\",\n  confidenceIntervals: { p50: 40, p80: 45.1, p95: 49.9 },\n  historicalAccuracy: { mape: 15, sampleSize: 117791 },\n  recommendation: \"Low risk. Estimate is within normal variance.\",\n  humanReadable: \"Schedule risk: low. MAPE: 15% (based on 0 historical records). Confidence intervals: p50=40h, p80=45.1h, p95=49.9h.\"\n}\n```\n\n**`cocomo_validate`** -- Validate COCOMO II estimates against reference data\n\n```\nInput:  {}\nOutput: {\n  projectsEvaluated: 182,\n  mape: 85.55,\n  bias: 53.5,\n  byProjectType: {\n    organic: { mape: 86.57, count: 22 },\n    semidetached: { mape: 84.75, count: 106 },\n    embedded: { mape: 86.71, count: 54 }\n  },\n  recommendedAdjustments: []\n}\n```\n\n**`cocomo_ground_truth`** -- Benchmark all COCOMO variants (Basic, COCOMO II nominal, AI 12x speedup, AI + developer-profile gradients) against the same real historical projects, with per-dataset and per-type breakdowns\n\n```\nInput:  {}\nOutput: {\n  projectsEvaluated: 182,\n  models: [\n    { name: \"COCOMO Basic\", mape: 85.55, mmre: 0.856, pred25: 0.313, pred50: 0.544, bias: 53.5, count: 182 },\n    ...\n  ],\n  byDataset: { ... },\n  byType: { ... },\n  winner: \"AI + Profile (human)\",\n  conclusion: \"Best model: AI + Profile (human) (MAPE=79.66%). ...\",\n  humanReadable: \"...\"\n}\n```\n\n## ai_native Mode\n\nEpoch tools support dual estimation modes to account for the fundamentally different velocity of AI-assisted vs human-only development.\n\nWhen `ai_native=true` (default), tools use Epoch's reference database with tool-aware correction factors. These baselines reflect AI agent workflows: faster iteration, higher output volume, and different error profiles.\n\nWhen `ai_native=false`, tools apply human developer baselines:\n\n| Parameter | Human Baseline | AI-Native Baseline |\n|-----------|---------------|-------------------|\n| Feature development | 14 calendar days (industry data) | 5.7h median (126K+ real tasks) |\n| Bug fix turnaround | 72 hours (industry data) | 6.2h median (139 matched estimate-actual pairs; source: `src/lib/supplementary-data.ts`) |\n| Sprint velocity | 35 story points (industry data) | 80 story points |\n| Estimation accuracy (MAPE) | 25% (Jorgensen 2004) | 15% (from AI-native profiles) |\n| Correction factor | 1.8x (industry standard) | 1.07-1.45x (from reference DB) |\n\nTools that support `ai_native`: `pert_estimate`, `cocomo_estimate`, `sprint_forecast`, `reference_class_estimate`, `schedule_risk`.\n\n**Hybrid workflows:** `ai_native` accepts a float from 0.0 (fully human) to 1.0 (fully AI-native). Values like 0.5 produce interpolated profiles for mixed AI/human workflows. Boolean values (`true`/`false`) remain supported for backward compatibility.\n\n## Self-Improvement Engine\n\nEpoch learns your patterns the more you use it. The bundled reference database already contains 117,791 data points with correction factors tuned from real estimate-vs-actual pairs across 8 task types — **it works accurately on day one.**\n\nIf you record your actuals, Epoch personalizes further:\n\n1. **Estimate** -- Generate an initial estimate with any estimation tool\n2. **Record** -- Track the actual outcome (`record_actual`)\n3. **Learn** -- Self-improvement computes personalized correction factors from your data\n4. **Improve** -- Future estimates apply your team's actual patterns\n5. **Trend** -- `accuracy_trend` tracks whether your accuracy is improving over time\n\n```\nYour estimates + your actuals -> Your correction factors -> Better estimates -> Repeat\n```\n\n**The loop can close itself.** Recording actuals is the step everyone forgets, so Epoch can do it for you: `epoch auto-actuals --session <id>` records wall-clock-derived actuals for a session's unfinished estimates (agent hosts can wire it into a session-end hook). Auto-recorded actuals are sanity-bounded (0.05–12h, <10x the estimate), provenance-labeled `auto_wallclock`, never overwrite a real actual, and `feedback_health` reports them separately (`byProvenance`) so automated data can't silently skew your calibration.\n\n**Estimates lead with honest ranges.** When at least 5 matched pairs exist for a task type, `pert_estimate` and `reference_class_estimate` open with a calibrated 80% interval (\"Expected 1.6–4.2 hours (80% confidence interval); point estimate 2.5 hours\") derived from your own historical estimate-vs-actual ratios — and say plainly when there isn't enough data yet.\n\nThe engine detects systematic biases (chronic under-estimation, accuracy degradation) and surfaces actionable recommendations.\n\n**You do not need to share data with anyone for this to work.** Self-improvement runs entirely locally using your own `~/.epoch/` data.\n\n### The correction loop, measured\n\nThe self-improvement claim above isn't marketing copy -- it's backed by a runnable receipt. `scripts/backtest-pert-correction.mjs` makes a read-only temp copy of your `~/.epoch` ledger, chronologically splits matched `pert_estimate` (estimate, actual) pairs 80/20, trains the learned per-(tool, task_type) correction factor on the training split only, and reports MdAPE on the held-out test split it never trained on:\n\n```bash\nnpx tsx scripts/backtest-pert-correction.mjs\n```\n\nMeasured on the maintainers' production ledger (697 held-out matched pairs at time of writing): MdAPE improved from 105.2% (uncorrected) to 80.5% (learned correction) on data the correction factor never saw during training. This is the mechanism `EPOCH_PERT_LEARNED_CORRECTION` gates behind before it's recommended on by default -- the script also checks that the corrected median actual/predicted ratio lands in [0.7, 1.3], and reports `HOLD` (not recommended yet) when that second guard hasn't cleared, so the flag doesn't ship as \"on\" until both hold. Run the script against your own ledger for your own numbers; they move as more actuals get recorded, which is the point.\n\n`reference_class_estimate`'s correction factors are the same learned mechanism applied to a different tool. Track its current calibration with `epoch data status` or `feedback_health` (per-tool MAPE/MdAPE, bias, and trend), or generate a full calibration decision-surface report with `node scripts/build-calibration-dashboard.mjs` -- also strictly read-only against your ledger.\n\n## Data Pipeline\n\nEpoch uses a three-layer data strategy so it's accurate from the start and gets better over time:\n\n**1. Bundled reference database (works immediately, no setup):**\nEpoch ships with a pre-built reference database containing 117,791 data points across 8 task types and 5 complexity levels. Correction factors are computed from real estimate-vs-actual pairs. You get accurate estimates the moment you install it.\n\n**2. Local self-improvement (automatic, private):**\nAs you use Epoch and record actuals, the self-improvement engine recalibrates correction factors from *your* data. This runs entirely locally in `~/.epoch/` — nothing leaves your machine. The engine triggers automatically every 100 tool calls or 24 hours.\n\n- **Auto-recording:** Use `scripts/auto-record-actual.mjs` to automatically record actual time against pending estimates.\n- **Source tagging:** Set `EPOCH_SOURCE=<project-name>` to tag estimates by project.\n- **Inspect your data:** `epoch data where` and `epoch data status` show what's stored locally.\n\n**3. Community contributions (optional, opt-in):**\nYou can optionally share anonymized data to help improve baselines for all users. Community data is stripped of all identifying information — only task type, complexity, estimated hours, actual hours, and date remain. See [CONTRIBUTING-data.md](./CONTRIBUTING-data.md) for format and privacy requirements.\n\n```bash\nepoch share-data --validate --description \"My anonymized estimation data\"\n```\n\nThis is completely optional. Epoch works great without it.\n\n## Surfaces\n\nEpoch exposes the same 25 tools through three interfaces:\n\n| Surface | Transport | Use Case |\n|---------|-----------|----------|\n| **MCP Server** | stdio | Claude Code, Cursor, VS Code, Windsurf |\n| **CLI** | Direct invocation | Scripts, CI/CD, quick lookups |\n| **REST API** | HTTP (Hono) | Web apps, AI agents, integrations |\n\nDefault behavior: running `epoch` with no arguments starts the MCP stdio server.\n\n### CLI\n\n```bash\n# PERT estimate\nepoch pert-estimate --optimistic 2 --most-likely 4 --pessimistic 12 --unit hours\n\n# Token-to-time bridge\nepoch token-time-bridge --tokens 50000 --model claude-sonnet-4-20250514\n\n# Monte Carlo simulation\nepoch monte-carlo-schedule --tasks '[{\"name\":\"A\",\"optimistic\":2,\"most_likely\":4,\"pessimistic\":8}]'\n\n# COCOMO II estimate\nepoch cocomo-estimate --kloc 15 --project-type organic\n\n# Schedule risk score\nepoch schedule-risk --tasks '[{\"name\":\"A\",\"duration\":5,\"risk_level\":\"high\"},{\"name\":\"B\",\"duration\":3,\"risk_level\":\"low\"}]'\n\n# List all tools\nepoch list-tools\n\n# Pretty table output\nepoch pert-estimate --optimistic 2 --most-likely 4 --pessimistic 12 --pretty\n```\n\n### REST API\n\n```bash\n# Start the server\nepoch serve --port 3099\n# or: EPOCH_TRANSPORT=http EPOCH_PORT=3099 epoch\n\n# Call any tool\ncurl -X POST http://localhost:3099/v1/tools/pert_estimate \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"optimistic\": 2, \"most_likely\": 4, \"pessimistic\": 12, \"unit\": \"hours\"}'\n\n# Health check\ncurl http://localhost:3099/health\n\n# OpenAPI spec\ncurl http://localhost:3099/openapi.json\n```\n\n## Agent-First\n\nEpoch is built for agents as first-class callers, not humans typing in a terminal as an afterthought.\n\n**Why agents need time-sense.** An LLM has no grounded sense of duration or cost -- it will say \"quick fix\" for a two-day migration and \"big project\" for a two-hour config change with equal confidence, because it has no feedback loop telling it otherwise. That's fine for a chat answer; it breaks down the moment an agent is planning multi-step work, sequencing a sprint, or deciding whether a deadline is realistic. Epoch gives the agent a calculator instead of a guess: PERT/COCOMO/Monte Carlo math, a reference-class baseline built from real task data, and a feedback loop that corrects itself as the agent (or its operator) records actuals.\n\n**`EPOCH_TELEMETRY=1` for headless/agent operators.** Telemetry is off by default and requires informed consent. For a human at a terminal, that consent is `epoch telemetry enable`, which shows the data and asks for confirmation. An agent should never be the one clicking \"yes\" to that prompt on its own behalf -- there is deliberately no MCP tool that enables telemetry, so an agent cannot self-consent. For headless or agent-operated deployments, the operator opts in out-of-band by setting `EPOCH_TELEMETRY=1` in the server's environment (for example, the `env` block of the MCP server config) before the agent ever starts. Consent stays with the human who configures the deployment, not the agent that runs inside it.\n\n**MCP client qualification.** Epoch's telemetry schema (v2) records `client_name`/`client_version` from the MCP `clientInfo` your host reports at connection time, plus `transport` (`stdio`/`http`). This is agent *qualification*, not agent *identification*: it lets aggregate accuracy stats count \"5.7h median across N agent-driven feature estimates\" as first-class agent data rather than lumping it in with anonymous CLI usage, without adding any new per-user identifying signal. MCP clients that report `clientInfo` (Claude Code, Cursor, and most current hosts do) get this for free; clients that don't are still fully functional, they just show up as `client_name: null`.\n\nEpoch also provides built-in discoverability endpoints so agents can find and use the HTTP API without prior configuration:\n\n| Endpoint | Description |\n|----------|-------------|\n| `GET /.well-known/ai-plugin.json` | OpenAI plugin manifest |\n| `GET /llms.txt` | LLM-consumable documentation |\n| `GET /openapi.json` | OpenAPI 3.1 specification |\n| `GET /health` | Service health and version |\n\n## Installation\n\n```bash\ngit clone https://github.com/KyaniteLabs/Epoch.git\ncd Epoch\npnpm install\npnpm run build\n```\n\n## Development\n\n```bash\npnpm test          # Run the Vitest suite\npnpm run build     # Build with tsup\npnpm run typecheck # TypeScript strict mode check\npnpm run dev       # Run development server\npnpm run inspector # Open MCP Inspector for interactive testing\n```\n\n## Tech Stack\n\n- **Runtime**: Node.js 22+ (ESM; `engines.node >=22` — Node 20 reached EOL April 2026)\n- **Language**: TypeScript 6 (strict mode, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`)\n- **Validation**: Zod 4 with `.describe()` on every field\n- **MCP SDK**: `@modelcontextprotocol/sdk` 1.12+\n- **HTTP**: Hono (lightweight, multi-runtime)\n- **CLI**: Commander.js\n- **Date Handling**: `date-fns` 4.x + `date-fns-tz` 3.x\n- **Build**: `tsup` (ESM output)\n- **Testing**: `vitest` 4.x with v8 coverage (97% statements, 88% branches)\n\n## Configuration\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `EPOCH_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |\n| `EPOCH_PORT` | `3000` | HTTP server port |\n| `EPOCH_HOST` | `127.0.0.1` | HTTP server bind address |\n| `EPOCH_DATA_DIR` | `~/.epoch/` | Data directory for feedback and self-improvement |\n| `EPOCH_COMMUNITY_DIR` | `data/community/` | Community data directory |\n| `EPOCH_RATE_LIMIT` | `100` | Max requests per minute per client (HTTP only). `0` disables limiting; invalid or negative values fall back to `100` with a warning. 429 responses carry a `Retry-After` header. |\n| `EPOCH_TRUST_PROXY` | `0` | Set to `1` only when running behind a trusted reverse proxy: rate limiting then keys on `X-Forwarded-For`/`X-Real-IP` instead of the connection address (those headers are client-spoofable, so they are ignored by default). |\n| `EPOCH_CORS_ORIGINS` | _(none)_ | Comma-separated origins allowed by the HTTP API's CORS handling (e.g. `https://app.example.com,http://localhost:5173`), or `*` to allow any origin. Default: no CORS headers at all — same-origin tools, curl, and MCP clients are unaffected; cross-origin browser requests fail. Preflight `OPTIONS` requests are always answered. |\n| `EPOCH_SOURCE` | _(none)_ | Project/source tag attached to estimate records |\n| `EPOCH_TELEMETRY` | `0` | Set to `1` to enable anonymous telemetry. See [Telemetry & Privacy](#telemetry--privacy). |\n| `EPOCH_TELEMETRY_ENDPOINT` | _(none)_ | Override the configured telemetry receiver endpoint for status/submission. |\n\n## Telemetry & Privacy\n\nEpoch can share **anonymized** estimate/actual pairs to improve accuracy for all users. This is **off by default** and requires explicit opt-in.\n\n**Agent-operator consent model:** there is deliberately no MCP tool that enables telemetry -- an agent must not be able to self-consent on a human's behalf. Humans opt in interactively with `epoch telemetry enable`. Agent/headless operators opt in out-of-band by setting `EPOCH_TELEMETRY=1` in the server's environment before the agent starts (see [Agent-First](#agent-first)). Either way, consent belongs to the person who configures the deployment.\n\n```bash\nepoch telemetry enable     # Opt in (shows exactly what will be shared)\nepoch telemetry preview    # Preview anonymized data before enabling\nepoch telemetry status     # Show current settings\nepoch telemetry set-endpoint --endpoint https://your-server.example.com/v1/telemetry\nepoch telemetry submit     # Submit queued anonymized records to the configured endpoint\nepoch telemetry disable    # Opt out\nepoch telemetry export     # Export all local data as anonymized JSON\n```\n\n**What is shared:** task type, complexity, tool name, estimated hours, actual hours, ratio, date (YYYY-MM-DD only).\n\n**What is NEVER shared:** project names, notes, team IDs, IP addresses, timestamps with time-of-day, source code, descriptions.\n\nSee [Privacy Policy](docs/PRIVACY.md) and [Telemetry Documentation](docs/TELEMETRY.md) for full details.\n\n## Where Your Data Lives\n\nBy default, Epoch stores local data under `~/.epoch/` or `EPOCH_DATA_DIR`. Your local usage data is not automatically committed to GitHub and is not automatically submitted anywhere.\n\n```bash\nepoch data where     # Show local data file locations\nepoch data status    # Show data file counts, feedback health, telemetry config\n```\n\n## Sharing Data\n\nUse `epoch share-data --validate` to create a community-data JSON file suitable for `data/community/`. Review the file before opening a PR.\n\n```bash\nepoch share-data --description \"Anonymized Epoch usage export\" --validate\n```\n\n## Machine Labels\n\nFleet host inventories are not published in this repository. [docs/ops/machines.md](docs/ops/machines.md) documents the schema used to track machines internally; actual hostnames, addresses, and SSH users are supplied at runtime via environment variables (see `scripts/` and `docs/ops/epoch-fleet-audit.md`). `windows-receiver` is a historical label only.\n\n## License\n\nApache License 2.0. See [LICENSE](./LICENSE) for full terms.\n\n---\n\n## Part of KyaniteLabs\n\nMore from [KyaniteLabs](https://kyanitelabs.tech). Related projects:\n\n- **[mcp-video](https://github.com/KyaniteLabs/mcp-video)** — guardrailed video-editing MCP server for AI agents\n- **[DialectOS](https://github.com/KyaniteLabs/DialectOS)** — Spanish dialect localization MCP server & CLI\n- **[checkyourself](https://github.com/KyaniteLabs/checkyourself)** — local-first production-readiness checks for AI-built code\n\n→ More at **[kyanitelabs.tech](https://kyanitelabs.tech)**\n\n<!-- s-plus-geo:start -->\n\n## What is Epoch?\n\n**Epoch** is a **time estimation MCP server** that helps **engineering leads, agents, and planners who need calibrated duration estimates** **produce PERT/reference-class estimates and record actuals**.\n\n| | |\n| --- | --- |\n| **Product** | Epoch |\n| **Category** | time estimation MCP server |\n| **Best for** | engineering leads, agents, and planners who need calibrated duration estimates |\n| **Not** | a calendar or project tracker |\n| **Source** | [GitHub](https://github.com/KyaniteLabs/Epoch) · [Forgejo](https://git.kyanitelabs.tech/KyaniteLabs/Epoch) |\n| **Keywords** | time estimation MCP, PERT, reference class forecasting |\n\n## Who it's for\n\n- Primary: engineering leads, agents, and planners who need calibrated duration estimates\n- Use when you need to produce PERT/reference-class estimates and record actuals\n- Skip if you need a calendar or project tracker\n\n## FAQ\n\n### What is Epoch?\n\nEpoch is a time estimation MCP server. It helps engineering leads, agents, and planners who need calibrated duration estimates produce PERT/reference-class estimates and record actuals.\n\n### Who should use Epoch?\n\nengineering leads, agents, and planners who need calibrated duration estimates.\n\n### How is Epoch different?\n\nUnlike vibes-based hour guesses, Epoch forces structured estimate + actual feedback.\n\n### Is Epoch production software?\n\nTreat the README status and release tags as source of truth for maturity. Validate against your own requirements before production use.\n\n## Status\n\n- Maintained as of 2026 on the default branch\n- Prefer release tags when pinning dependencies\n- Report issues on the canonical remote listed above\n\n## Agent surface\n\n- Coding agents: read this README first, then repo docs/`AGENTS.md` if present\n- Prefer machine-readable briefs (`llms.txt`) when the repo ships one\n- MCP or skill entrypoints are documented in-repo when applicable\n\n## Contributing\n\nIssues and PRs welcome on the canonical remote. Keep public docs free of secrets and machine-local paths.\n\n## License\n\nSee [LICENSE](LICENSE) in this repository (or package metadata if license is package-only).\n\n<!-- s-plus-geo:end -->\n",
  "bytes": 37339,
  "sha": "2f599247ce9f669ed6b1581d5325bf71462e69d8dbfe34b0bc4d01a3b9c6d0e4",
  "repo_slug": "kyanitelabs/epoch",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kyanitelabs_epoch_2dfdadb5/readme"
}