Skip to main content
Glama

analyze_adr_timeline

Analyze architectural decision timelines to detect staleness, implementation lag, and technical debt, then generate prioritized work items with effort estimates.

Instructions

Analyze ADR timeline with smart time tracking, adaptive thresholds, and actionable recommendations. Auto-detects project context (startup/growth/mature) and generates prioritized work queue based on staleness, implementation lag, and technical debt.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectPathNoPath to the project directory.
adrDirectoryNoDirectory containing ADR filesdocs/adrs
generateActionsNoGenerate actionable work items with priority and effort estimates
thresholdProfileNoThreshold profile for action generation (auto-detected if not specified)
autoDetectContextNoAuto-detect project phase from git activity and ADR patterns
includeContentNoInclude ADR content for better analysis
forceExtractNoForce timeline extraction even if ADRs have dates

Implementation Reference

  • Registration and input schema definition for the analyze_adr_timeline tool in the central tool catalog. Includes metadata, description, token cost estimates, related tools, and input schema.
    TOOL_CATALOG.set('analyze_adr_timeline', {
      name: 'analyze_adr_timeline',
      shortDescription: 'Analyze ADR evolution over time',
      fullDescription: 'Analyzes the timeline of ADR decisions to understand architectural evolution.',
      category: 'adr',
      complexity: 'moderate',
      tokenCost: { min: 1500, max: 3000 },
      hasCEMCPDirective: true, // Phase 4.3: Moderate tool - timeline analysis
      relatedTools: ['discover_existing_adrs', 'compare_adr_progress'],
      keywords: ['adr', 'timeline', 'history', 'evolution'],
      requiresAI: true,
      inputSchema: {
        type: 'object',
        properties: {
          startDate: { type: 'string', description: 'Start date for analysis' },
          endDate: { type: 'string', description: 'End date for analysis' },
        },
      },
    });
  • Core timeline extraction logic that analyzes ADR creation/update dates using git history, content parsing, or filesystem metadata with intelligent caching and conditional extraction. This is the primary helper function for ADR timeline analysis.
    export async function extractBasicTimeline(
      adrPath: string,
      adrContent: string,
      options: TimelineExtractionOptions = {}
    ): Promise<BasicTimeline> {
      const {
        useCache = true,
        cacheTTL = 3600, // 1 hour default
        forceExtract = false,
      } = options;
    
      // Check if extraction can be skipped (unless forced)
      if (!forceExtract) {
        const decision = await shouldExtractTimeline(adrPath, adrContent);
    
        if (!decision.shouldExtract && decision.useExisting) {
          // Use existing date from content
          return createTimelineFromContent(adrContent, decision.useExisting, decision.reason);
        }
    
        if (!decision.shouldExtract && !decision.useExisting) {
          // Use cached data
          const cached = getCachedTimeline(`timeline:${adrPath}`);
          if (cached) {
            return cached as BasicTimeline;
          }
        }
    
        // Log extraction reason for debugging
        // NOTE: All console output goes to stderr to preserve stdout for MCP JSON-RPC
        if (decision.shouldExtract) {
          console.error(`[Timeline] Extracting for ${adrPath}: ${decision.reason}`);
        }
      }
    
      // Perform extraction using priority order: git → content → filesystem
      let timeline: Partial<BasicTimeline> | null = null;
    
      // Try git extraction first
      timeline = await tryGitExtraction(adrPath);
    
      // Fallback to content parsing
      if (!timeline || !timeline.created_at) {
        const contentDate = extractDateFromContent(adrContent);
        if (contentDate) {
          timeline = {
            created_at: contentDate,
            updated_at: contentDate,
            extraction_method: 'content',
          };
        }
      }
    
      // Final fallback to filesystem
      if (!timeline || !timeline.created_at) {
        timeline = await fallbackToFilesystem(adrPath);
      }
    
      // Calculate derived fields
      const stats = await stat(adrPath);
      const ageDays = calculateAgeDays(timeline.created_at!);
      const daysSinceUpdate = calculateAgeDays(timeline.updated_at!);
    
      const fullTimeline: BasicTimeline = {
        created_at: timeline.created_at!,
        updated_at: timeline.updated_at!,
        age_days: ageDays,
        days_since_update: daysSinceUpdate,
        staleness_warnings: generateBasicWarnings(ageDays, daysSinceUpdate),
        extraction_method: timeline.extraction_method as 'git' | 'content' | 'filesystem',
      };
    
      // Cache result with file modification time for future checks
      if (useCache) {
        setCachedTimeline(
          `timeline:${adrPath}`,
          {
            ...fullTimeline,
            _fileModifiedAt: stats.mtime.toISOString(),
            _cachedAt: new Date().toISOString(),
          },
          cacheTTL
        );
      }
    
      return fullTimeline;
    }
    
    /**
     * Determine if timeline extraction should run
     */
    async function shouldExtractTimeline(
      adrPath: string,
      adrContent: string
    ): Promise<{
      shouldExtract: boolean;
      reason: string;
      useExisting?: string;
    }> {
      // Check if ADR content has a date field
      const dateInContent = extractDateFromContent(adrContent);
    
      // Check file modification time
      const stats = await stat(adrPath);
      const fileModifiedAt = stats.mtime;
    
      // Check cache
      const cacheKey = `timeline:${adrPath}`;
      const cached = getCachedTimeline(cacheKey);
    
      // CONDITION 1: No date in content → MUST extract
      if (!dateInContent) {
        return {
          shouldExtract: true,
          reason: 'No timestamp found in ADR content',
        };
      }
    
      // CONDITION 2: Has date + cached + file unchanged → SKIP
      if (cached && cached._fileModifiedAt) {
        const cachedModTime = new Date(cached._fileModifiedAt);
        if (fileModifiedAt <= cachedModTime) {
          return {
            shouldExtract: false,
            reason: 'File unchanged since last extraction',
            useExisting: dateInContent,
          };
        }
      }
    
      // CONDITION 3: Has date + file modified recently → RE-EXTRACT
      const daysSinceModification = (Date.now() - fileModifiedAt.getTime()) / (1000 * 60 * 60 * 24);
    
      if (daysSinceModification < 7) {
        // Modified within last week
        return {
          shouldExtract: true,
          reason: `File modified ${Math.floor(daysSinceModification)} days ago`,
        };
      }
    
      // CONDITION 4: Has date + old file → USE CONTENT DATE
      return {
        shouldExtract: false,
        reason: 'File has valid date and is stable (not recently modified)',
        useExisting: dateInContent,
      };
    }
    
    /**
     * Extract date from ADR content using regex
     */
    function extractDateFromContent(content: string): string | null {
      const dateMatch = content.match(/(?:##?\s*date|date:|\*\*date\*\*:)\s*(.+?)(?:\n|$)/i);
      if (dateMatch && dateMatch[1]) {
        const dateStr = dateMatch[1].trim();
        try {
          // Validate it's a parseable date
          const date = new Date(dateStr);
          if (!isNaN(date.getTime())) {
            return date.toISOString();
          }
        } catch {
          // Invalid date format
        }
      }
      return null;
    }
    
    /**
     * Create timeline from ADR content date (no git extraction needed)
     */
    function createTimelineFromContent(
      _content: string,
      dateStr: string,
      reason: string
    ): BasicTimeline {
      let decisionDate: Date;
      try {
        decisionDate = new Date(dateStr);
      } catch {
        // Fallback to current time if parsing fails
        decisionDate = new Date();
      }
    
      const isoDate = decisionDate.toISOString();
      const ageDays = calculateAgeDays(isoDate);
    
      return {
        created_at: isoDate,
        updated_at: isoDate, // Assume same as creation
        age_days: ageDays,
        days_since_update: ageDays,
        staleness_warnings: generateBasicWarnings(ageDays, ageDays),
        extraction_method: 'content',
        extraction_skipped: true,
        skip_reason: reason,
      };
    }
    
    /**
     * Try to extract timeline from git history
     */
    async function tryGitExtraction(adrPath: string): Promise<Partial<BasicTimeline> | null> {
      try {
        // Creation date (first commit)
        const { stdout: created } = await execAsync(
          `git log --diff-filter=A --follow --format=%aI -- "${adrPath}" | tail -1`,
          { timeout: 5000 }
        );
    
        // Last modification date
        const { stdout: updated } = await execAsync(`git log -1 --format=%aI -- "${adrPath}"`, {
          timeout: 5000,
        });
    
        if (!created.trim() || !updated.trim()) {
          return null;
        }
    
        return {
          created_at: created.trim(),
          updated_at: updated.trim(),
          extraction_method: 'git',
        };
      } catch (error) {
        // Git not available, not a git repo, or command failed
        console.warn(`[Timeline] Git extraction failed for ${adrPath}:`, error);
        return null;
      }
    }
    
    /**
     * Fallback to filesystem timestamps
     */
    async function fallbackToFilesystem(adrPath: string): Promise<Partial<BasicTimeline>> {
      try {
        const stats = await stat(adrPath);
    
        return {
          created_at: stats.birthtime.toISOString(),
          updated_at: stats.mtime.toISOString(),
          extraction_method: 'filesystem',
        };
      } catch {
        // Last resort: use current time
        const now = new Date().toISOString();
        return {
          created_at: now,
          updated_at: now,
          extraction_method: 'filesystem',
        };
      }
    }
    
    /**
     * Calculate age in days from ISO timestamp
     */
    function calculateAgeDays(isoTimestamp: string): number {
      const date = new Date(isoTimestamp);
      const now = new Date();
      const diffMs = now.getTime() - date.getTime();
      return Math.floor(diffMs / (1000 * 60 * 60 * 24));
    }
    
    /**
     * Generate basic staleness warnings
     */
    function generateBasicWarnings(ageDays: number, daysSinceUpdate: number): string[] {
      const warnings: string[] = [];
    
      // Warning: Old ADR
      if (ageDays > 180) {
        warnings.push(`ADR is ${ageDays} days old (>6 months)`);
      }
    
      // Warning: No recent updates
      if (daysSinceUpdate > 365) {
        warnings.push(`No updates in ${daysSinceUpdate} days (>1 year)`);
      }
    
      // Warning: Very old dormant ADR
      if (daysSinceUpdate > 730) {
        warnings.push(`Dormant for ${Math.floor(daysSinceUpdate / 365)} years - may be obsolete`);
      }
    
      return warnings;
    }
    
    /**
     * Helper to extract keywords from ADR content for implementation tracking
     */
    export function extractKeywordsFromAdr(title: string, decision?: string): string[] {
      const text = `${title} ${decision || ''}`.toLowerCase();
      const keywords: string[] = [];
    
      // Extract technology names
      const techPatterns = [
        /\b(redis|postgres|mongodb|mysql|kafka|rabbitmq|docker|kubernetes|aws|gcp|azure)\b/gi,
        /\b(react|vue|angular|node|express|fastapi|django|flask|spring)\b/gi,
        /\b(graphql|rest|grpc|websocket|oauth|jwt|saml)\b/gi,
      ];
    
      techPatterns.forEach(pattern => {
        const matches = text.match(pattern);
        if (matches) {
          keywords.push(...matches.map(m => m.toLowerCase()));
        }
      });
    
      // Extract quoted terms (likely important concepts)
      const quoted = text.match(/"([^"]+)"/g);
      if (quoted) {
        keywords.push(...quoted.map(q => q.replace(/"/g, '').toLowerCase()));
      }
    
      // Remove duplicates
      return [...new Set(keywords)];
    }
  • Type definitions for ADR timeline data structures used by the timeline extractor and analyzer.
     */
    
    /**
     * Basic timeline information for an ADR
     * Extracted automatically from git history, content, or filesystem
     */
    export interface BasicTimeline {
      /** When the ADR was created (ISO timestamp) */
      created_at: string;
      /** When the ADR was last updated (ISO timestamp) */
      updated_at: string;
      /** Age of the ADR in days */
      age_days: number;
      /** Days since last update */
      days_since_update: number;
      /** Staleness warnings based on basic timeline analysis */
      staleness_warnings: string[];
      /** How the timeline was extracted */
      extraction_method: 'git' | 'content' | 'filesystem';
      /** Whether extraction was skipped due to smart detection */
      extraction_skipped?: boolean;
  • Usage of the timeline extractor in ADR discovery utility, demonstrating integration point for timeline analysis.
            const { extractBasicTimeline } = await import('./adr-timeline-extractor.js');
            adr.timeline = await extractBasicTimeline(filePath, content, timelineOptions);
          } catch (error) {
            console.warn(`[Timeline] Failed to extract timeline for ${filename}:`, error);
            // Continue without timeline data
          }
        }
    
        discoveredAdrs.push(adr);
      }
    } catch (error) {
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions capabilities like 'smart time tracking' and 'adaptive thresholds', it doesn't clarify what these mean operationally, what data is analyzed, how recommendations are generated, or what the output format looks like. The description is more feature-focused than behaviorally transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured as a single sentence that packs substantial information about capabilities. It's appropriately sized for a complex tool with 7 parameters, though could potentially benefit from breaking into multiple sentences for better readability of the different capability clusters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex analysis tool with 7 parameters and no annotations or output schema, the description is insufficient. It doesn't explain what 'analyze' means operationally, what constitutes 'actionable recommendations', how the prioritized work queue is structured, or what the tool actually returns. The gap is significant given the tool's complexity and lack of structured output documentation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already documents all 7 parameters thoroughly. The description mentions 'auto-detects project context' which relates to 'autoDetectContext' and 'thresholdProfile', and 'actionable recommendations' which relates to 'generateActions', but adds minimal semantic value beyond what's already in the parameter descriptions. Baseline 3 is appropriate given the comprehensive schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool analyzes ADR timelines with specific capabilities like smart time tracking, adaptive thresholds, and actionable recommendations. It distinguishes from siblings like 'compare_adr_progress' or 'review_existing_adrs' by emphasizing analysis and work queue generation. However, it doesn't explicitly differentiate from all ADR-related siblings in the list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description mentions auto-detecting project context and generating prioritized work queues, but provides no explicit guidance on when to use this tool versus alternatives like 'compare_adr_progress' or 'validate_adr'. There's no mention of prerequisites, typical use cases, or when other tools might be more appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/mcp-adr-analysis-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server