Skip to content

KB remote --source ignored when cache dir already exists (~/.pair/kb/<cliVersion>) #189

@omar-diop

Description

@omar-diop

Story Statement

As a CLI user or automation script wrapping pair install/pair update
I want --source <url> to always download and apply the specified KB archive
So that I can reliably switch between KB sources without manually deleting cache directories

Where: pair install --source <url> and pair update --source <url> CLI commands

Epic Context

Parent Epic: N/A (standalone bug fix)
Status: Done
Priority: P0 (Must-Have) — silent data staleness affects all --source users

Status Workflow

  • Refined: Story is detailed, estimated, and ready for development
  • In Progress: Story is actively being developed
  • Done: Story delivered and accepted

Acceptance Criteria

Functional Requirements

Given-When-Then Format:

  1. Given a cached KB exists at ~/.pair/kb/<version> (from a previous default download)
    When the user runs pair update --source https://example.com/other-kb.zip
    Then the CLI downloads and extracts the new archive, replacing the cached KB for that version

  2. Given a cached KB exists at ~/.pair/kb/<version> (from a previous --source A URL)
    When the user runs pair update --source <URL-B> (a different URL)
    Then the CLI downloads from URL-B and replaces the cached KB

  3. Given a cached KB exists at ~/.pair/kb/<version> and no --source is provided
    When the user runs pair update (default)
    Then the CLI returns the cached path immediately (existing cache-hit behavior preserved)

  4. Given a cached KB exists at ~/.pair/kb/<version>
    When the user runs pair install --source /local/path/to/kb
    Then the CLI installs from the local path, replacing the cached KB

  5. Given a cached KB exists and --source points to a URL that fails (404, network error)
    When the download fails
    Then the existing cached KB is NOT deleted and an actionable error is shown

Business Rules

  • Cache bypass applies only when customUrl / --source is explicitly provided
  • Default behavior (no --source) must remain unchanged: cache-hit returns immediately
  • Failed re-downloads must not corrupt or delete the existing cache (atomic replacement)
  • The fix must work for all source types: remote URL, local ZIP, local directory

Edge Cases and Error Handling

  • Same URL re-download: When --source points to the same URL as the cached version, the download still occurs (no URL comparison optimization needed for v1 — simplicity over optimization)
  • Concurrent access: Not in scope — CLI is single-process per invocation
  • Disk space: If extraction fails mid-way, cleanup must remove partial files (existing cleanup logic applies)
  • Version with v prefix: v0.4.2 and 0.4.2 must both work correctly (existing normalization in cache-manager.ts handles this)

Definition of Done Checklist

Development Completion

  • All 5 acceptance criteria implemented and verified
  • Code follows project coding standards (TypeScript, ESLint, Prettier)
  • Code review completed and approved
  • Unit tests written and passing for cache-bypass logic
  • Integration tests cover "cache exists + new URL" scenario
  • Existing tests remain green (no regressions)

Quality Assurance

  • All acceptance criteria tested via automated tests
  • Edge cases tested (same URL, failed download, local paths)
  • pnpm quality-gate passes
  • Existing E2E tests in cli.e2e.test.ts still pass

Deployment and Release

  • Changeset entry created for the fix
  • No database migrations needed
  • No feature flags needed (behavioral fix)

Story Sizing and Sprint Readiness

Refined Story Points

Final Story Points: S(2)
Confidence Level: High
Sizing Justification: The fix is a small, well-scoped change to ensureKBAvailable in kb-availability.ts — bypass cache early-return when customUrl is provided, plus delete-and-redownload logic. Affected surface is narrow (one function + one cache utility). Tests are straightforward using existing InMemoryFileSystemService and MockHttpClientService patterns.

Sprint Capacity Validation

Sprint Fit Assessment: Easily fits in a single sprint
Development Time Estimate: 0.5 days
Testing Time Estimate: 0.5 days
Total Effort Assessment: Fits within sprint capacity: Yes

Story Splitting Recommendations

No split needed — story is already XS/S sized.

Dependencies and Coordination

Story Dependencies

Prerequisite Stories: None
Dependent Stories: None
Shared Components: @pair/content-ops (InMemoryFileSystemService, MockHttpClientService — test utilities only)

Team Coordination

Development Roles Involved:

  • Backend/CLI: Modify kb-availability.ts and cache-manager.ts, write tests

External Dependencies

Third-party Integrations: None
Infrastructure Requirements: None
Compliance Requirements: None

Validation and Testing Strategy

Acceptance Testing Approach

Testing Methods: Unit tests using existing in-memory FS and mock HTTP patterns from kb-availability.test.ts
Test Data Requirements: Pre-seeded InMemoryFileSystemService with cache directory, MockHttpClientService with zip responses
Environment Requirements: Standard vitest test runner

User Validation

Success Metrics: pair update --source <url> always applies the new URL when cache exists
Rollback Plan: Revert the commit — existing behavior (always cache-hit) is restored

Notes and Additional Context

Refinement Session Insights: The bug is a missing condition check — ensureKBAvailable does not distinguish between "no source specified (use cache)" and "explicit source specified (bypass cache)". The simplest fix is to skip the cache early-return when customUrl is truthy. For atomic safety, a backup/restore pattern ensures the existing cache is preserved on failed downloads.

Future Considerations: A more sophisticated approach (URL-keyed cache slots, metadata tracking) could be added later if multi-source caching becomes a requirement. For now, bypass-on-explicit-source is the right trade-off.

Original Bug Report: Observed with pair update / consumer scripts wrapping the CLI; cache path logged as ~/.pair/kb/0.4.2 while release asset version differed.

Technical Analysis

Implementation Approach

Technical Strategy: Bypass the isKBCached early-return in ensureKBAvailable when deps.customUrl is provided. When bypassing, use atomic backup/restore pattern: rename existing cache to .bak, attempt install, restore on failure, delete backup on success.

Key Components:

  • apps/pair-cli/src/kb-manager/kb-availability.ts — add customUrl check before cache early-return + installFromSource helper
  • apps/pair-cli/src/kb-manager/cache-manager.ts — add backupCachedKB, restoreCachedKB, removeBackupKB utilities

Data Flow:

  1. CLI parses --source <url> into customUrl
  2. ensureKBAvailable(version, { customUrl, ... }) is called
  3. If customUrl is truthy, backup existing cache to .bak
  4. Proceed to source detection and installation (via installFromSource)
  5. On success: remove backup, return installed path
  6. On failure: restore backup from .bak, rethrow error

Integration Points: No external integrations. Change is internal to kb-manager module.

Technical Requirements

  • ensureKBAvailable must bypass cache when customUrl is truthy
  • Atomic replacement via backup/restore ensures cache survives failed downloads
  • backupCachedKB must be safe to call when no cache exists (returns false)
  • All existing tests must remain green

Technical Risks and Mitigation

Risk Impact Probability Mitigation Strategy
Breaking default (no --source) cache behavior High Very Low AC #3 explicitly tests this. Existing cache-hit tests cover it.

Spike Requirements

Required Spikes: None — implementation approach is clear from code analysis.

Task Breakdown

  • T-1: Failing tests reproducing cache-bypass bug
  • T-2: Add backupCachedKB/restoreCachedKB/removeBackupKB + bypass cache in ensureKBAvailable
  • T-3: Regression tests for edge cases

Dependency Graph

T-1 (failing tests) ── T-2 (fix) ── T-3 (regression tests)

AC Coverage

AC Tasks
AC-1 (cache exists + remote --source re-downloads) T-1, T-2
AC-2 (different --source URL re-downloads) T-1, T-2
AC-3 (no --source preserves cache-hit) T-3
AC-4 (local --source path re-installs) T-3
AC-5 (failed --source preserves cache via backup/restore) T-2, T-3

Refinement Completed By: AI Agent (Claude)
Refinement Date: 2026-04-11
Delivered: 2026-04-11 — PR #194 merged (squash → 3467d17)

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions