Skip to content

Bug: pair update ignores --config (always uses base config, can’t disable skills flatten) #186

@omar-diop

Description

@omar-diop

Story Statement

As a CLI user running pair update in a consumer repo
I want pair update to honor -c/--config <path> and auto-load project pair.config.json
So that I can override registry behavior (e.g. skills.flatten=false) per-project, just like pair install and pair package already do

Where: pair CLI — update command

Epic Context

Parent Epic: Standalone bug (no parent epic)
Status: In Progress
Priority: P0 (Must-Have)

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 consumer repo with a pair.config.json containing skills.flatten: false
    When I run pair update --source <path>
    Then the update uses the project config and produces nested (non-flattened) skill folders

  2. Given a consumer repo with a pair.config.json containing skills.flatten: false
    When I run pair update --source <path> -c ./pair.config.json
    Then the update uses the explicitly-specified config and produces nested (non-flattened) skill folders

  3. Given a consumer repo with a custom config at ./custom-config.json setting different registry targets
    When I run pair update -c ./custom-config.json
    Then the update resolves registries from the custom config, not the packaged base config

  4. Given no project config exists and no -c flag is provided
    When I run pair update
    Then the update uses the packaged base config (backward-compatible default behavior)

  5. Given a -c flag pointing to a non-existent file
    When I run pair update -c ./missing.json
    Then the update fails with a clear error message referencing the missing config file

Business Rules

  • Config resolution must match pair install and pair package behavior exactly: base config -> project pair.config.json merge -> custom config merge
  • --config flag must be wired through the full dispatch path: CLI -> parser -> dispatcher -> handler -> loadConfigWithOverrides
  • Backward compatibility: no behavior change when --config is not provided and no project config exists

Edge Cases and Error Handling

  • Invalid config JSON: loadConfigWithOverrides already throws; error should propagate with clear message
  • Config file not found: loadConfigWithOverrides already throws; no new error handling needed
  • Empty config file: merged config should equal base config (no override)
  • Conflicting flags: --config takes precedence over auto-detected pair.config.json (existing loadConfigWithOverrides merge order)

Definition of Done Checklist

Development Completion

  • All acceptance criteria implemented and verified
  • Code follows project coding standards and conventions
  • Code review completed and approved by team member
  • Unit tests written and passing
  • Integration tests implemented for config override path
  • Quality gate passes (pnpm quality-gate)

Quality Assurance

  • All acceptance criteria tested and verified
  • Edge cases and error conditions tested
  • Regression: existing update tests still pass
  • Config parity with pair install verified

Deployment and Release

  • Feature deployed via normal release pipeline
  • No database migrations needed
  • No feature flags needed

Story Sizing and Sprint Readiness

Refined Story Points

Final Story Points: S(2)
Confidence Level: High
Sizing Justification: The fix is well-scoped — 2 files need structural changes (dispatcher.ts, update/parser.ts) and the handler already supports the config option. The install command provides an exact reference implementation. Testing is straightforward with existing test patterns.

Sprint Capacity Validation

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

Story Splitting Recommendations

Not needed — story is already small.

Dependencies and Coordination

Story Dependencies

Prerequisite Stories: None
Dependent Stories: None
Shared Components: #config module (loadConfigWithOverrides), commands/dispatcher.ts

Team Coordination

Development Roles Involved:

  • Backend/CLI: Parser, dispatcher, and handler changes

External Dependencies

None.

Validation and Testing Strategy

Acceptance Testing Approach

Testing Methods: Unit tests for parser, unit test for dispatcher config forwarding, integration test (handler with custom config)
Test Data Requirements: Mock FileSystemService with custom config JSON
Environment Requirements: Standard vitest test environment

User Validation

Success Metrics: pair update -c ./pair.config.json uses the custom config; pair update in a directory with pair.config.json auto-loads it
Rollback Plan: Revert PR

Notes and Additional Context

Refinement Session Insights: The bug root cause is confirmed via code analysis. Two gaps exist:

  1. dispatchCommand in dispatcher.ts does not forward the config option from CLI args to the handler options
  2. parseUpdateCommand in update/parser.ts does not include config in its ParseUpdateOptions interface (though the parser itself doesn't need it — it's the dispatch path that matters)

The install command works because its setupInstallContext receives options.config from the handler options, and the registerCommandFromMetadata function in cli.ts does pass config in the normalized options. However, dispatchCommand strips it out in resolveOptions.

NOTE: The bug also affected pair install — the dispatcher stripped config for ALL commands. Fix covers both.

Documentation Links:

  • apps/pair-cli/src/commands/update/parser.ts — missing config in ParseUpdateOptions
  • apps/pair-cli/src/commands/update/handler.ts — handler already supports options.config
  • apps/pair-cli/src/commands/dispatcher.tsresolveOptions strips config
  • apps/pair-cli/src/commands/install/handler.ts — reference implementation (working)
  • apps/pair-cli/src/config/loader.tsloadConfigWithOverrides (shared config resolution)

Technical Analysis

Implementation Approach

Technical Strategy: Wire the --config CLI option through the dispatch path so it reaches handleUpdateCommand's options.config. The handler (setupUpdateContext) already calls loadConfigWithOverrides(fs, configOptions) with configOptions.customConfigPath = options.config — it just never receives a non-undefined value today.

Key Components:

  1. dispatcher.tsresolveOptions must include config from the dispatch context
  2. cli.tsregisterCommandFromMetadata already passes config in normalized options; the gap is that dispatchCommand's DispatchContext interface lacks config
  3. update/handler.ts — already wired, no changes needed

Data Flow:

CLI args → Commander → normalizedOptions (has config) → cmdConfig.parse() → dispatchCommand(config, fs, ctx)
                                                                              ↓
                                                                 ctx currently missing config
                                                                              ↓
                                                                 resolveOptions(ctx) → handler options
                                                                              ↓
                                                                 handleUpdateCommand(config, fs, { config: ... })
                                                                              ↓
                                                                 setupUpdateContext → loadConfigWithOverrides(fs, { customConfigPath })

Integration Points: loadConfigWithOverrides (shared config module) — no changes needed there.

Technical Requirements

  • DispatchContext interface in dispatcher.ts must add config?: string
  • resolveOptions in dispatcher.ts must spread config into returned options
  • cli.ts registerCommandFromMetadata must pass config from normalized options into dispatchCommand context
  • Unit tests for parser (config option passthrough), dispatcher (config forwarding), handler (integration with custom config)

Technical Risks and Mitigation

Risk Impact Probability Mitigation Strategy
Breaking existing update tests Medium Low Run full test suite before/after; handler behavior unchanged when config is undefined
Config merge order surprise Low Low Match exact loadConfigWithOverrides merge semantics already used by install/package

Spike Requirements

Required Spikes: None — implementation path is fully understood from the install command reference.


Refinement Completed By: AI agent (automated refinement)
Refinement Date: 2026-04-11
Review and Approval: Pending product owner review

Task Breakdown

  • T-1: Add config to dispatch path (dispatcher.ts + cli.ts)
  • T-2: Add failing tests reproducing the bug (update + install)
  • T-3: Wire config through update and install parser interfaces
  • T-4: Verify parity with install command and run quality gate

Dependency Graph

T-2 (failing tests) ── T-1 (dispatch fix) ── T-3 (parser interface) ── T-4 (parity + QG)

AC Coverage

AC Tasks
AC-1 (project config auto-load) T-1, T-2
AC-2 (explicit -c flag) T-1, T-2, T-3
AC-3 (custom config targets) T-1, T-2
AC-4 (backward compat default) T-2, T-4
AC-5 (missing config error) T-2

T-1: Add config to dispatch path (dispatcher.ts + cli.ts)

Priority: P0 | Estimated Hours: 1.5h | Bounded Context: CLI dispatch

Summary: Wire the --config CLI option from Commander through dispatchCommand to the handler options.

Type: Bug Fix

Description: The DispatchContext interface in dispatcher.ts lacks a config field, and resolveOptions does not include it. The registerCommandFromMetadata function in cli.ts does not pass config from normalized CLI options into the dispatch context. This task adds the field to the interface, spreads it in resolveOptions, and passes it from cli.ts.

Acceptance Criteria:

  • Primary deliverable: dispatchCommand forwards config to handler options
  • Quality standard: Type-safe — DispatchContext interface updated, no any casts
  • Integration requirement: handleUpdateCommand receives options.config when -c is passed
  • Verification method: Unit test on dispatcher; integration test with update handler

T-2: Add failing tests reproducing the bug

Priority: P0 | Estimated Hours: 2h | Bounded Context: CLI testing

Summary: Write tests that reproduce the bug: handleUpdateCommand and handleInstallCommand ignoring --config option.

Type: Testing

Description: Per the bug-fix workflow, create tests that demonstrate the config option is ignored. Tests cover both update and install commands: (1) dispatcher-level config forwarding, (2) handler-level custom config, (3) backward compat, (4) missing config error.


T-3: Wire config through update and install parser interfaces

Priority: P1 | Estimated Hours: 0.5h | Bounded Context: CLI parsing

Summary: Add config to ParseUpdateOptions and ParseInstallOptions for interface completeness.

Type: Bug Fix


T-4: Verify parity with install command and run quality gate

Priority: P1 | Estimated Hours: 1h | Bounded Context: CLI quality

Summary: Verify that pair update config handling matches pair install exactly. Run full quality gate.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions