You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a CLI user I want flexible KB source installation from remote URLs, local ZIPs, local directories, or default sources So that I can install/update KB content from any location without workarounds and work offline when network unavailable
Refined: Story is detailed, estimated, and ready for development ✅ Current
In Progress: Story is actively being developed
Done: Story delivered and accepted
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given developer in monorepo development mode runs pair install When no flags provided Then CLI installs KB from packages/knowledge-hub/dataset/ with success message
Given developer with released CLI runs pair install When no flags provided Then CLI downloads KB from GitHub release ZIP to ~/.pair/kb/{version}/ and installs with success message
Given developer runs pair install --source https://example.com/releases/kb-v1.0.0.zip When network available Then CLI downloads ZIP from URL, extracts to temp location, validates, installs to target with progress indicator
Given developer runs pair install --source /absolute/path/to/kb-dataset When directory exists and valid Then CLI validates KB structure, copies content to target, displays success message
Given developer runs pair install --source ./relative/path/to/kb.zip When ZIP file exists and valid Then CLI extracts ZIP, validates KB structure, installs to target
Given developer runs pair install --source ../outside-repo/shared-kb/ When directory exists outside current repo Then CLI validates and installs KB from external directory
Given developer runs pair install --offline When no --source provided Then CLI exits with error "Offline mode requires explicit --source with local path"
Given developer runs pair install --offline --source /local/kb-dataset When directory exists Then CLI installs from local source without network access, skips download attempts
Given developer runs pair update --source https://example.com/kb-v2.0.0.zip When target already has KB installed Then CLI downloads new KB version, validates, replaces existing KB content, preserves target config
Given developer runs pair install --source https://example.com/kb.zip When network unavailable Then CLI displays error "Failed to download KB from URL: network unavailable. Try --offline with local --source"
Given developer runs pair install --source /non-existent/path When path doesn't exist Then CLI exits with error "KB source path not found: /non-existent/path"
Given developer runs pair install --source /path/with/invalid-kb-structure When directory missing required .pair/ structure Then CLI exits with error "Invalid KB structure: missing required directories/files" with validation details
Business Rules
Dataset root concept ELIMINATED - no mandatory dataset root configuration
Default source (monorepo): packages/knowledge-hub/dataset/ relative to repo root
Default source (release/npm): GitHub release ZIP URL from package metadata
Demo-ready: show install from URL, local directory, offline mode
Story Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 8 Confidence Level: High Sizing Justification:
Source resolution implementation (~1 pt)
Download manager enhancement (~1.5 pt)
Extraction service (~1 pt)
Validator integration (~0.5 pt)
Installation service refactor (~1.5 pt)
Progress indicators and UX (~0.5 pt)
Comprehensive testing (~2 pt)
Well-defined scope with clear specification, builds on existing infrastructure
Sprint Capacity Validation
Sprint Fit Assessment: Yes - fits in single sprint with focused effort Development Time Estimate: 4-5 days (implementation across modules) Testing Time Estimate: 2-3 days (unit, integration, E2E, regression) Total Effort Assessment: 6-8 days total - fits within 2-week sprint if focused, may benefit from pairing
Story Splitting Recommendations
If needed, split into:
Story A (P0): Core flexible source support (URL + local paths, validation, basic offline)
Acceptance Criteria: AC1-6, AC11-12
Size Estimate: 5 pts
Story B (P1): Enhanced UX (progress indicators, retry logic, advanced offline)
Existing kb-manager.ts has download logic to enhance
Path resolution uses Node.js path.resolve with process.cwd()
Future Considerations:
Resume support for interrupted downloads (not in this story)
Advanced progress indicators (fancy spinners, ETA) (not in this story)
Multiple KB version management (future epic)
KB source caching beyond current version (future enhancement)
Technical Analysis
Implementation Strategy
Current State: Stories #90 (KB Packaging) and #91 (CommandConfig Parser) delivered the foundational infrastructure. The codebase has been significantly refactored since the original task breakdown — command pattern (commands/{command}/parser.ts+handler.ts+metadata.ts), content-ops service layer (FileSystemService, HttpClientService), and smoke test framework (scripts/smoke-tests/) are now in place.
Approach: Fix gaps in existing pipeline rather than creating new services. Move generic logic to content-ops, fix local resolution bugs, thread download progress end-to-end, add retry logic, extend smoke tests.
Existing Infrastructure (already implemented)
Component
Location
Status
CommandConfig discriminated union (install + update)
Summary: Refactor detectSourceType in content-ops to accept an optional FileSystemService parameter instead of using bare fs module and process.cwd(), enabling testability with InMemoryFileSystemService.
Type: Refactoring
Description: detectSourceType in packages/content-ops/src/path-resolution/source-detector.ts currently imports fs and path directly and uses process.cwd() for relative path resolution. This prevents testing with InMemoryFileSystemService. Refactor to accept an optional FileSystemService parameter (defaulting to real FS for backward compatibility). The function must preserve its current behavior for all callers.
Technical Design: Add optional FileSystemService param. Use fs.existsSync/fs.stat from service instead of bare fs module. Use fs.currentWorkingDirectory() instead of process.cwd() for relative path resolution. Default param to production fileSystemService for backward compat.
packages/content-ops/src/path-resolution/source-detector.test.ts — rewrite tests with InMemoryFileSystemService (remove real FS tmpdir setup)
Dependencies:
Technical: FileSystemService interface (already in content-ops)
Tasks: None (independent)
Implementation Steps:
RED: Write tests using InMemoryFileSystemService for all source types (URL, ZIP, directory, invalid, unsafe protocols)
GREEN: Refactor detectSourceType signature to (source: string, fsService?: FileSystemService)
Replace fs.existsSync/fs.statSync with service calls, process.cwd() with fsService.currentWorkingDirectory()
Default fsService to production fileSystemService singleton
Verify all existing callers still work (no signature break)
REFACTOR: Remove beforeAll/afterAll tmpdir setup from test file
Testing Strategy:
Unit Tests: 8+ tests with InMemoryFS — remote URL, unsafe protocols, local ZIP (abs+rel), local directory (abs+rel), non-existent path, INVALID fallback
Integration Tests: Verify parseInstallCommand and parseUpdateCommand still work (existing tests pass)
Notes: Backward-compatible change. Optional param means zero impact on existing callers.
T-2: Extract KB validation utilities to content-ops
Summary: Move generic KB validation functions (validateKBStructure, normalizeExtractedKB, helpers) from CLI's kb-installer.ts to content-ops package, making them reusable and testable with InMemoryFileSystemService.
Type: Refactoring
Description: validateKBStructure, normalizeExtractedKB, findKBStructureInSubdirectories, moveDirectoryContents, and copyDirectoryInMemory in apps/pair-cli/src/kb-manager/kb-installer.ts are generic file operations with no CLI dependency. They should live in content-ops for reuse (e.g., by resolveDatasetRoot for local directory validation in T-4). CLI's kb-installer.ts will import from content-ops instead.
Acceptance Criteria:
Primary deliverable: New content-ops/src/file-system/kb-validation.ts module with exported functions
Quality standard: 1:1 test file with InMemoryFS, all existing kb-installer tests still pass
Integration requirement: CLI's kb-installer.ts imports from @pair/content-ops instead of local definitions
Verification method: pnpm quality-gate passes across both packages
Technical Requirements:
Functionality: validateKBStructure(path, fs), normalizeExtractedKB(path, fs) available from content-ops
Performance: No change (same logic, different location)
Security: No change
Implementation Approach:
Technical Design: Create packages/content-ops/src/file-system/kb-validation.ts. Move functions. Export from content-ops public API. Update kb-installer.ts imports.
Regression: All existing kb-installer.test.ts tests pass unchanged
Notes:logDirEntriesIfDebug is a CLI debug concern — keep it in CLI, don't move. The moved functions accept FileSystemService already (no signature change needed).
T-3: Thread download progress + retry with backoff
Summary: Wire download progress reporting through the full resolution pipeline (handler → resolveDatasetRoot → download) and add retry with exponential backoff for transient network failures in content-ops downloadFile.
Type: Feature Implementation
Description:
Two gaps: (1) ProgressReporter infrastructure exists in content-ops downloadFile but DatasetResolveOptions and getKnowledgeHubDatasetPathWithFallback don't accept progressWriter/isTTY, so download progress is invisible to users. (2) downloadFile has resume support but no retry — transient network errors (ECONNRESET, ETIMEDOUT) are fatal. Fix both by threading progress options through the pipeline and adding a retry wrapper.
Acceptance Criteria:
Primary deliverable: (1) Progress visible during pair install --source <url>. (2) Transient failures retried 3 times with backoff.
Quality standard: Tests use MockHttpClientService, no real network calls
Integration requirement: Existing download behavior preserved, progress/retry are additive
Verification method: Unit tests simulating progress and transient failures
Technical Requirements:
Functionality: Progress bar during remote downloads, retry 3x with 1s/2s/4s backoff
Performance: No overhead for local/offline operations
Security: No change
Implementation Approach:
Technical Design:
Progress threading: Add progressWriter? and isTTY? to DatasetResolveOptions in config/kb-resolver.ts. Thread through getKnowledgeHubDatasetPathWithFallback → downloadKBIfNeeded → ensureKBAvailable → installKB → downloadFile. Install/update handlers pass process.stdout as progressWriter.
Retry logic: Add downloadWithRetry wrapper in content-ops download-manager.ts (or retryable-download.ts). Wraps downloadFile with configurable maxRetries (default 3) and delays (default [1000, 2000, 4000]). Only retries on transient errors (isRetryableError check: ECONNRESET, ETIMEDOUT, ECONNREFUSED — NOT 404/403).
Manual Testing: pair install --source <url> shows progress bar, retry on simulated failure
Notes:isRetryableError checks error message for econnreset, etimedout, econnrefused, socket hang up. HTTP 404/403 are NOT retried. Resume (byte-level) is orthogonal to retry (request-level) — both can coexist.
T-4: Harden local resolution, validation & error handling
Summary: Fix local directory resolution bugs in resolveDatasetRoot (relative paths, existence validation, KB structure validation) and add INVALID source type error in parsers. Covers AC-5, AC-6, AC-9, AC-11, AC-12.
Type: Bug Fix + Feature Implementation
Description:
Three related bugs/gaps: (1) resolveDatasetRoot case 'local' for directories returns config.path as-is without resolving relative paths or validating existence. (2) parseInstallCommand and parseUpdateCommand don't throw when detectSourceType returns INVALID — non-existent paths silently become local config. (3) Local directory sources skip KB structure validation (ZIP goes through normalizeExtractedKB but directory doesn't). Also fix installKBFromLocalDirectory using process.cwd() instead of fs.currentWorkingDirectory().
Acceptance Criteria:
Primary deliverable: Relative paths resolved, non-existent paths error early, KB structure validated for all source types
Quality standard: Tests use InMemoryFileSystemService for all FS operations
Integration requirement: Existing behavior for valid paths unchanged
Verification method: Unit tests covering AC-5, AC-11, AC-12 scenarios
Security: Path traversal prevented by detectSourceType INVALID check
Implementation Approach:
Technical Design:
Parser fix: After detectSourceType call, if result is INVALID, throw Error('KB source path not found: <source>'). Both install and update parsers.
resolveDatasetRoot fix: For local non-ZIP: resolve relative path via path.resolve(fs.currentWorkingDirectory(), config.path), validate existence with fs.exists(), validate KB structure via validateKBStructure (from content-ops after T-2), throw clear errors.
installKBFromLocalDirectory fix: Replace process.cwd() with fs.currentWorkingDirectory().
Technical: validateKBStructure from content-ops (T-2)
Tasks: T-1 (detectSourceType with FileSystemService for testable parser), T-2 (KB validation in content-ops), T-3 (progress threading through resolveDatasetRoot options)
Regression: All existing kb-resolver.test.ts and parser.test.ts tests pass
Notes: The parser INVALID check uses the current (non-injected) detectSourceType call — this is fine for production but parser tests may need to create real tmp dirs OR rely on the refactored detectSourceType from T-1 with InMemoryFS. Prefer InMemoryFS approach after T-1.
T-5: Source resolution smoke tests + --offline-only flag
Summary: Add new smoke test scenario source-resolution.sh covering --source (local dir, local zip) and --offline modes. Add --offline-only flag to run-all.sh for CI filtering.
Type: Testing
Description:
The smoke test framework has 6 scenarios but none test --source or --offline flags. Add source-resolution.sh scenario testing local directory source, local ZIP source, offline mode, and error cases (non-existent path, invalid structure). Also add --offline-only flag to run-all.sh that filters to scenarios tagged as offline-safe (all current scenarios are offline, future remote-URL scenarios would be online-only).
Acceptance Criteria:
Primary deliverable: source-resolution.sh scenario with 6+ test cases, --offline-only flag in run-all.sh
Quality standard: All tests pass in CI mode, follow existing scenario patterns (lib/utils.sh helpers)
Integration requirement: pnpm smoke-tests includes new scenario, pnpm smoke-tests -- --offline-only filters correctly
Verification method: Manual run of pnpm smoke-tests
Technical Requirements:
Functionality: Test --source (dir, zip), --offline, error cases; offline filtering
Performance: New scenario <30s
Compatibility: Bash, Linux/macOS, CI-safe (no network)
Offline flag: Add --offline-only flag to run-all.sh. Each scenario file exports an OFFLINE_SAFE=true|false variable (default true). When --offline-only passed, skip scenarios with OFFLINE_SAFE=false.
Bounded Context & Modules: scripts/smoke-tests/
Files to Modify/Create:
scripts/smoke-tests/scenarios/source-resolution.sh — NEW scenario
scripts/smoke-tests/run-all.sh — add --offline-only flag and filtering logic
scripts/smoke-tests/scenarios/install-basic.sh — add OFFLINE_SAFE=true tag (example for pattern)
Dependencies:
Technical: Smoke test framework (lib/utils.sh), packaged CLI from 00-create-install-package.sh
Tasks: T-4 (source resolution bugs fixed)
Implementation Steps:
Add OFFLINE_SAFE=true variable convention to existing scenarios (all are offline)
Add --offline-only flag parsing to run-all.sh
Add filtering logic: when --offline-only, check scenario's OFFLINE_SAFE before running
Create source-resolution.sh scenario with test cases
Add to CI_TESTS list in run-all.sh
Run pnpm smoke-tests to verify all pass
Run pnpm smoke-tests -- --offline-only to verify filtering
Testing Strategy:
Manual Testing: Run full smoke suite, verify new scenario passes
CI Validation: Verify --offline-only flag works correctly
Notes: All current scenarios are offline-safe. The --offline-only flag is forward-looking — when remote URL smoke tests are added later, they'll be tagged OFFLINE_SAFE=false and skipped in offline-only mode.
T-6: CLI UX improvements — CliPresenter + help formatting
Summary: Create a CliPresenter module for structured CLI output with progress tracking, operation summaries, and improved help formatting using chalk.
Type: Feature Implementation
Description:
The CLI currently has minimal user feedback: sparse pushLog('info', ...) messages, no progress tracking (e.g., "Registry 2/5"), no summary at completion, and basic Commander.js help output. chalk is already a dependency but barely used. Create a CliPresenter abstraction that wraps pushLog with chalk-formatted console output, preserving backward compatibility with existing log accumulation and test patterns. Integrate into install/update handlers and enhance Commander.js help formatting.
Acceptance Criteria:
Primary deliverable: CliPresenter module with createCliPresenter(pushLog) and createSilentPresenter(pushLog) factories
Quality standard: Unit tests for formatting logic, existing handler tests unbroken
Integration requirement: Presenter wraps existing pushLog — all log accumulation continues working
Verification method: pnpm quality-gate passes, manual verification of install/update/help output
Technical Requirements:
Functionality: Progress per registry ([1/4] name source → target), operation summary with timing, phase messages for backup/rollback, improved help with chalk
Performance: No overhead — chalk formatting is synchronous string operations
Security: No change
Implementation Approach:
Technical Design: Create CliPresenter interface with startOperation, registryStart, registryDone, registryError, phase, summary methods. Two factories: createCliPresenter(pushLog) (chalk + console + pushLog) and createSilentPresenter(pushLog) (pushLog only). Integrate into install/update handlers via injectable presenter option. Enhance Commander.js help via addHelpText and configureHelp.
Resolution
Delivered by PR #133 (merged). Board: Done.
feat: flexible KB source resolution.
Story Statement
As a CLI user
I want flexible KB source installation from remote URLs, local ZIPs, local directories, or default sources
So that I can install/update KB content from any location without workarounds and work offline when network unavailable
Where: Command-line interface (pair install, pair update commands)
Epic Context
Parent Epic: #65 Enhanced CLI Packaging & Distribution
Status: Refined
Priority: P1 (Should-Have)
Status Workflow
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given developer in monorepo development mode runs
pair installWhen no flags provided
Then CLI installs KB from
packages/knowledge-hub/dataset/with success messageGiven developer with released CLI runs
pair installWhen no flags provided
Then CLI downloads KB from GitHub release ZIP to
~/.pair/kb/{version}/and installs with success messageGiven developer runs
pair install --source https://example.com/releases/kb-v1.0.0.zipWhen network available
Then CLI downloads ZIP from URL, extracts to temp location, validates, installs to target with progress indicator
Given developer runs
pair install --source /absolute/path/to/kb-datasetWhen directory exists and valid
Then CLI validates KB structure, copies content to target, displays success message
Given developer runs
pair install --source ./relative/path/to/kb.zipWhen ZIP file exists and valid
Then CLI extracts ZIP, validates KB structure, installs to target
Given developer runs
pair install --source ../outside-repo/shared-kb/When directory exists outside current repo
Then CLI validates and installs KB from external directory
Given developer runs
pair install --offlineWhen no --source provided
Then CLI exits with error "Offline mode requires explicit --source with local path"
Given developer runs
pair install --offline --source /local/kb-datasetWhen directory exists
Then CLI installs from local source without network access, skips download attempts
Given developer runs
pair update --source https://example.com/kb-v2.0.0.zipWhen target already has KB installed
Then CLI downloads new KB version, validates, replaces existing KB content, preserves target config
Given developer runs
pair install --source https://example.com/kb.zipWhen network unavailable
Then CLI displays error "Failed to download KB from URL: network unavailable. Try --offline with local --source"
Given developer runs
pair install --source /non-existent/pathWhen path doesn't exist
Then CLI exits with error "KB source path not found: /non-existent/path"
Given developer runs
pair install --source /path/with/invalid-kb-structureWhen directory missing required .pair/ structure
Then CLI exits with error "Invalid KB structure: missing required directories/files" with validation details
Business Rules
packages/knowledge-hub/dataset/relative to repo rootEdge Cases and Error Handling
Definition of Done Checklist
Development Completion
Quality Assurance
Deployment and Release
Story Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 8
Confidence Level: High
Sizing Justification:
Sprint Capacity Validation
Sprint Fit Assessment: Yes - fits in single sprint with focused effort
Development Time Estimate: 4-5 days (implementation across modules)
Testing Time Estimate: 2-3 days (unit, integration, E2E, regression)
Total Effort Assessment: 6-8 days total - fits within 2-week sprint if focused, may benefit from pairing
Story Splitting Recommendations
If needed, split into:
Split Rationale: Maintains incremental delivery - core functionality first, UX enhancements second
Dependencies and Coordination
Story Dependencies
Prerequisite Stories:
Dependent Stories: None (delivers complete user value)
Shared Components:
Team Coordination
Development Roles Involved:
External Dependencies
Infrastructure Requirements:
Validation and Testing Strategy
Acceptance Testing Approach
Testing Methods:
Test Data Requirements:
Environment Requirements:
User Validation
User Feedback Collection: Sprint review demo + early adopter beta testing
Success Metrics:
Rollback Plan: Feature behind flag (--source override) - existing default behavior unchanged, can disable --source flag if critical issues
Notes and Additional Context
Refinement Session Insights:
Technical Decisions:
.pair/.install-lockat targetImplementation Notes:
Future Considerations:
Technical Analysis
Implementation Strategy
Current State: Stories #90 (KB Packaging) and #91 (CommandConfig Parser) delivered the foundational infrastructure. The codebase has been significantly refactored since the original task breakdown — command pattern (
commands/{command}/parser.ts+handler.ts+metadata.ts), content-ops service layer (FileSystemService,HttpClientService), and smoke test framework (scripts/smoke-tests/) are now in place.Approach: Fix gaps in existing pipeline rather than creating new services. Move generic logic to
content-ops, fix local resolution bugs, thread download progress end-to-end, add retry logic, extend smoke tests.Existing Infrastructure (already implemented)
commands/install/parser.ts,commands/update/parser.tsresolveDatasetRoot(default/remote/local strategies)config/kb-resolver.tsdetectSourceType+SourceTypeenumcontent-ops/path-resolution/source-detector.tsfs)validateCommandOptions(offline validation)config/cli.tscontent-ops/http/download-manager.ts(ProgressReporter)kb-manager/kb-installer.tskb-manager/error-formatter.ts(KBDownloadError,formatDownloadError)scripts/smoke-tests/run-all.sh+ 6 scenariosGaps to Address
detectSourceTypeuses barefsmodule, notFileSystemServiceInMemoryFileSystemServicevalidateKBStructure,normalizeExtractedKBlive in CLI-onlykb-installer.tsProgressReporterexists in content-ops butDatasetResolveOptionsdoesn't acceptprogressWriter/isTTYdownloadFileresolveDatasetRootlocal directory returnsconfig.pathas-is (no relative resolution)INVALIDsource type fromdetectSourceTyperesolveDatasetRootinstallKBFromLocalDirectoryusesprocess.cwd()instead offs.currentWorkingDirectory()InMemoryFileSystemService--sourceor--offline--offline-onlyflag inrun-all.shData Flow (current pipeline)
Key Risks
detectSourceTypesignature change breaks consumersFileSystemServiceparam with default (backward compat)Task Breakdown
detectSourceTypeto acceptFileSystemService(2h)--offline-onlyflag (3h)Total Hours: 17h | Story Estimate: 8 pts
Dependency Graph
T-1, T-2, T-3 are independent (parallel). T-4 depends on all three. T-5 depends on T-4. T-6 is independent (UX track).
AC Coverage
pair installin monorepopair installfrom releaseT-1: Refactor
detectSourceTypeto acceptFileSystemServicePriority: P0 | Estimated Hours: 2h | Bounded Context: content-ops / path-resolution
Summary: Refactor
detectSourceTypein content-ops to accept an optionalFileSystemServiceparameter instead of using barefsmodule andprocess.cwd(), enabling testability withInMemoryFileSystemService.Type: Refactoring
Description:
detectSourceTypeinpackages/content-ops/src/path-resolution/source-detector.tscurrently importsfsandpathdirectly and usesprocess.cwd()for relative path resolution. This prevents testing withInMemoryFileSystemService. Refactor to accept an optionalFileSystemServiceparameter (defaulting to real FS for backward compatibility). The function must preserve its current behavior for all callers.Acceptance Criteria:
detectSourceType(source, fs?)accepting optionalFileSystemServiceInMemoryFileSystemServiceinstall/parser.ts,update/parser.ts,config/cli.ts) continue working without changesInMemoryFileSystemServicefor all source typesTechnical Requirements:
Implementation Approach:
FileSystemServiceparam. Usefs.existsSync/fs.statfrom service instead of barefsmodule. Usefs.currentWorkingDirectory()instead ofprocess.cwd()for relative path resolution. Default param to productionfileSystemServicefor backward compat.content-opspackage,path-resolutionmodulepackages/content-ops/src/path-resolution/source-detector.ts— add FileSystemService parampackages/content-ops/src/path-resolution/source-detector.test.ts— rewrite tests with InMemoryFileSystemService (remove real FS tmpdir setup)Dependencies:
FileSystemServiceinterface (already in content-ops)Implementation Steps:
InMemoryFileSystemServicefor all source types (URL, ZIP, directory, invalid, unsafe protocols)detectSourceTypesignature to(source: string, fsService?: FileSystemService)fs.existsSync/fs.statSyncwith service calls,process.cwd()withfsService.currentWorkingDirectory()fsServiceto productionfileSystemServicesingletonbeforeAll/afterAlltmpdir setup from test fileTesting Strategy:
parseInstallCommandandparseUpdateCommandstill work (existing tests pass)Notes: Backward-compatible change. Optional param means zero impact on existing callers.
T-2: Extract KB validation utilities to content-ops
Priority: P0 | Estimated Hours: 3h | Bounded Context: content-ops / file-system
Summary: Move generic KB validation functions (
validateKBStructure,normalizeExtractedKB, helpers) from CLI'skb-installer.tstocontent-opspackage, making them reusable and testable withInMemoryFileSystemService.Type: Refactoring
Description:
validateKBStructure,normalizeExtractedKB,findKBStructureInSubdirectories,moveDirectoryContents, andcopyDirectoryInMemoryinapps/pair-cli/src/kb-manager/kb-installer.tsare generic file operations with no CLI dependency. They should live incontent-opsfor reuse (e.g., byresolveDatasetRootfor local directory validation in T-4). CLI'skb-installer.tswill import fromcontent-opsinstead.Acceptance Criteria:
content-ops/src/file-system/kb-validation.tsmodule with exported functionskb-installer.tsimports from@pair/content-opsinstead of local definitionspnpm quality-gatepasses across both packagesTechnical Requirements:
validateKBStructure(path, fs),normalizeExtractedKB(path, fs)available from content-opsImplementation Approach:
packages/content-ops/src/file-system/kb-validation.ts. Move functions. Export fromcontent-opspublic API. Updatekb-installer.tsimports.content-opsfile-system module,pair-clikb-manager modulepackages/content-ops/src/file-system/kb-validation.ts— NEW: moved functionspackages/content-ops/src/file-system/kb-validation.test.ts— NEW: tests with InMemoryFSpackages/content-ops/src/file-system/index.ts— export new modulepackages/content-ops/src/index.ts— export new functionsapps/pair-cli/src/kb-manager/kb-installer.ts— replace local functions with imports from@pair/content-opsDependencies:
FileSystemService,InMemoryFileSystemService(already in content-ops)Implementation Steps:
validateKBStructureandnormalizeExtractedKBin content-ops using InMemoryFSkb-installer.tstokb-validation.tsfile-system/index.ts,src/index.ts)kb-installer.tsto import from@pair/content-opslogDirEntriesIfDebugfrom moved functions (debug concern stays in CLI)pnpm quality-gateacross both packagesTesting Strategy:
kb-installer.test.tstests pass unchangedNotes:
logDirEntriesIfDebugis a CLI debug concern — keep it in CLI, don't move. The moved functions acceptFileSystemServicealready (no signature change needed).T-3: Thread download progress + retry with backoff
Priority: P0 | Estimated Hours: 3h | Bounded Context: content-ops / http + pair-cli / config
Summary: Wire download progress reporting through the full resolution pipeline (handler →
resolveDatasetRoot→ download) and add retry with exponential backoff for transient network failures in content-opsdownloadFile.Type: Feature Implementation
Description:
Two gaps: (1)
ProgressReporterinfrastructure exists in content-opsdownloadFilebutDatasetResolveOptionsandgetKnowledgeHubDatasetPathWithFallbackdon't acceptprogressWriter/isTTY, so download progress is invisible to users. (2)downloadFilehas resume support but no retry — transient network errors (ECONNRESET, ETIMEDOUT) are fatal. Fix both by threading progress options through the pipeline and adding a retry wrapper.Acceptance Criteria:
pair install --source <url>. (2) Transient failures retried 3 times with backoff.MockHttpClientService, no real network callsTechnical Requirements:
Implementation Approach:
progressWriter?andisTTY?toDatasetResolveOptionsinconfig/kb-resolver.ts. Thread throughgetKnowledgeHubDatasetPathWithFallback→downloadKBIfNeeded→ensureKBAvailable→installKB→downloadFile. Install/update handlers passprocess.stdoutas progressWriter.downloadWithRetrywrapper in content-opsdownload-manager.ts(orretryable-download.ts). WrapsdownloadFilewith configurablemaxRetries(default 3) anddelays(default [1000, 2000, 4000]). Only retries on transient errors (isRetryableErrorcheck: ECONNRESET, ETIMEDOUT, ECONNREFUSED — NOT 404/403).content-opshttp module,pair-cliconfig modulepackages/content-ops/src/http/retryable-download.ts— NEW:downloadWithRetrywrapperpackages/content-ops/src/http/retryable-download.test.ts— NEW: tests with MockHttpClientpackages/content-ops/src/http/index.ts— export new moduleapps/pair-cli/src/config/kb-resolver.ts— addprogressWriter/isTTYtoDatasetResolveOptions, thread throughapps/pair-cli/src/kb-manager/kb-installer.ts— usedownloadWithRetryinstead ofdownloadFileapps/pair-cli/src/commands/install/handler.ts— passprogressWriter: process.stdoutin optionsapps/pair-cli/src/commands/update/handler.ts— sameDependencies:
MockHttpClientService,ProgressReporter(already in content-ops)Implementation Steps:
downloadWithRetry— success on first try, success on retry, failure after max retries, non-retryable error not retrieddownloadWithRetryin content-ops withisRetryableErrorcheckprogressWriterreachesdownloadFileprogressWriter/isTTYtoDatasetResolveOptions, thread throughgetKnowledgeHubDatasetPathWithFallbackkb-installer.tsinstallKBto calldownloadWithRetryprocess.stdoutasprogressWriterpnpm quality-gateTesting Strategy:
pair install --source <url>shows progress bar, retry on simulated failureNotes:
isRetryableErrorchecks error message foreconnreset,etimedout,econnrefused,socket hang up. HTTP 404/403 are NOT retried. Resume (byte-level) is orthogonal to retry (request-level) — both can coexist.T-4: Harden local resolution, validation & error handling
Priority: P0 | Estimated Hours: 3h | Bounded Context: pair-cli / config + commands
Summary: Fix local directory resolution bugs in
resolveDatasetRoot(relative paths, existence validation, KB structure validation) and add INVALID source type error in parsers. Covers AC-5, AC-6, AC-9, AC-11, AC-12.Type: Bug Fix + Feature Implementation
Description:
Three related bugs/gaps: (1)
resolveDatasetRootcase 'local' for directories returnsconfig.pathas-is without resolving relative paths or validating existence. (2)parseInstallCommandandparseUpdateCommanddon't throw whendetectSourceTypereturns INVALID — non-existent paths silently become local config. (3) Local directory sources skip KB structure validation (ZIP goes throughnormalizeExtractedKBbut directory doesn't). Also fixinstallKBFromLocalDirectoryusingprocess.cwd()instead offs.currentWorkingDirectory().Acceptance Criteria:
InMemoryFileSystemServicefor all FS operationsTechnical Requirements:
detectSourceTypeINVALID checkImplementation Approach:
detectSourceTypecall, if result isINVALID, throwError('KB source path not found: <source>'). Both install and update parsers.localnon-ZIP: resolve relative path viapath.resolve(fs.currentWorkingDirectory(), config.path), validate existence withfs.exists(), validate KB structure viavalidateKBStructure(from content-ops after T-2), throw clear errors.process.cwd()withfs.currentWorkingDirectory().pair-cliconfig + commands modulesapps/pair-cli/src/config/kb-resolver.ts— fix local directory resolution: resolve relative, validate existence, validate KB structureapps/pair-cli/src/config/kb-resolver.test.ts— add tests for relative paths, non-existent, invalid structureapps/pair-cli/src/commands/install/parser.ts— throw on INVALID source typeapps/pair-cli/src/commands/install/parser.test.ts— add INVALID source type testapps/pair-cli/src/commands/update/parser.ts— throw on INVALID source typeapps/pair-cli/src/commands/update/parser.test.ts— add INVALID source type testapps/pair-cli/src/kb-manager/kb-installer.ts— fixprocess.cwd()usageDependencies:
validateKBStructurefrom content-ops (T-2)Implementation Steps:
detectSourceTypereturns INVALID → parser throwsparseInstallCommandandparseUpdateCommandresolveDatasetRoottest — relative local dir path → resolved absolute pathpath.resolve(fs.currentWorkingDirectory(), config.path)fs.exists()check, throw if missingvalidateKBStructurefrom@pair/content-ops, call after existence checkinstallKBFromLocalDirectoryto usefs.currentWorkingDirectory()pnpm quality-gateTesting Strategy:
kb-resolver.test.tsandparser.test.tstests passNotes: The parser INVALID check uses the current (non-injected)
detectSourceTypecall — this is fine for production but parser tests may need to create real tmp dirs OR rely on the refactoreddetectSourceTypefrom T-1 with InMemoryFS. Prefer InMemoryFS approach after T-1.T-5: Source resolution smoke tests +
--offline-onlyflagPriority: P0 | Estimated Hours: 3h | Bounded Context: scripts / smoke-tests
Summary: Add new smoke test scenario
source-resolution.shcovering--source(local dir, local zip) and--offlinemodes. Add--offline-onlyflag torun-all.shfor CI filtering.Type: Testing
Description:
The smoke test framework has 6 scenarios but none test
--sourceor--offlineflags. Addsource-resolution.shscenario testing local directory source, local ZIP source, offline mode, and error cases (non-existent path, invalid structure). Also add--offline-onlyflag torun-all.shthat filters to scenarios tagged as offline-safe (all current scenarios are offline, future remote-URL scenarios would be online-only).Acceptance Criteria:
source-resolution.shscenario with 6+ test cases,--offline-onlyflag inrun-all.shlib/utils.shhelpers)pnpm smoke-testsincludes new scenario,pnpm smoke-tests -- --offline-onlyfilters correctlypnpm smoke-testsTechnical Requirements:
Implementation Approach:
scripts/smoke-tests/scenarios/source-resolution.sh— usessetup_workspace,run_pair,assert_*helpers fromlib/utils.sh. Tests: (1)pair install --source <dataset-dir>(2)pair install --source <kb.zip>created by00-create-install-package.sh(3)pair install --offline --source <dataset-dir>(4)pair install --source /non-existent→ assert_failure (5)pair install --offlinewithout source → assert_failure (6)pair update --source <dataset-dir>--offline-onlyflag torun-all.sh. Each scenario file exports anOFFLINE_SAFE=true|falsevariable (default true). When--offline-onlypassed, skip scenarios withOFFLINE_SAFE=false.scripts/smoke-tests/scripts/smoke-tests/scenarios/source-resolution.sh— NEW scenarioscripts/smoke-tests/run-all.sh— add--offline-onlyflag and filtering logicscripts/smoke-tests/scenarios/install-basic.sh— addOFFLINE_SAFE=truetag (example for pattern)Dependencies:
lib/utils.sh), packaged CLI from00-create-install-package.shImplementation Steps:
OFFLINE_SAFE=truevariable convention to existing scenarios (all are offline)--offline-onlyflag parsing torun-all.sh--offline-only, check scenario'sOFFLINE_SAFEbefore runningsource-resolution.shscenario with test casesrun-all.shpnpm smoke-teststo verify all passpnpm smoke-tests -- --offline-onlyto verify filteringTesting Strategy:
--offline-onlyflag works correctlyNotes: All current scenarios are offline-safe. The
--offline-onlyflag is forward-looking — when remote URL smoke tests are added later, they'll be taggedOFFLINE_SAFE=falseand skipped in offline-only mode.T-6: CLI UX improvements — CliPresenter + help formatting
Priority: P0 | Estimated Hours: 3h | Bounded Context: pair-cli / ui + commands
Summary: Create a
CliPresentermodule for structured CLI output with progress tracking, operation summaries, and improved help formatting using chalk.Type: Feature Implementation
Description:
The CLI currently has minimal user feedback: sparse
pushLog('info', ...)messages, no progress tracking (e.g., "Registry 2/5"), no summary at completion, and basic Commander.js help output. chalk is already a dependency but barely used. Create aCliPresenterabstraction that wrapspushLogwith chalk-formatted console output, preserving backward compatibility with existing log accumulation and test patterns. Integrate into install/update handlers and enhance Commander.js help formatting.Acceptance Criteria:
CliPresentermodule withcreateCliPresenter(pushLog)andcreateSilentPresenter(pushLog)factoriespushLog— all log accumulation continues workingpnpm quality-gatepasses, manual verification of install/update/help outputTechnical Requirements:
[1/4] name source → target), operation summary with timing, phase messages for backup/rollback, improved help with chalkImplementation Approach:
CliPresenterinterface withstartOperation,registryStart,registryDone,registryError,phase,summarymethods. Two factories:createCliPresenter(pushLog)(chalk + console + pushLog) andcreateSilentPresenter(pushLog)(pushLog only). Integrate into install/update handlers via injectablepresenteroption. Enhance Commander.js help viaaddHelpTextandconfigureHelp.pair-cliui module (new), commands/install, commands/update, cli.tsapps/pair-cli/src/ui/presenter.ts— NEW: CliPresenter with chalk formattingapps/pair-cli/src/ui/index.ts— NEW: barrel exportapps/pair-cli/src/ui/presenter.test.ts— NEW: unit testsapps/pair-cli/src/commands/install/handler.ts— integrate presenterapps/pair-cli/src/commands/update/handler.ts— integrate presenterapps/pair-cli/src/cli.ts— improved help formattingapps/pair-cli/tsconfig.json— add#uipath aliasapps/pair-cli/package.json— add#uiimport mappingDependencies:
Implementation Steps:
#uipath alias in tsconfig.json and package.jsonpresenter.tswithCliPresenterinterface,createCliPresenter,createSilentPresenterpresenter.test.tswith unit tests for formattingpushLogcalls with presenter methodspnpm quality-gateTesting Strategy:
presenter.test.ts— verify formatted output for each method, verify pushLog still called, verify silent presenter behaviorpair install,pair update,pair --help,pair install --helpNotes:
forEachRegistryalready providesindexparameter — no signature change needed.createSilentPresenterallows tests to suppress console output while preserving pushLog accumulation.Refinement Completed By: Product Manager (AI-assisted)
Refinement Date: 2025-12-09
Task Breakdown Date: 2026-02-14
Task Breakdown Review: Pending developer confirmation