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:
-
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
-
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
-
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
-
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)
-
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
Quality Assurance
Deployment and Release
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:
dispatchCommand in dispatcher.ts does not forward the config option from CLI args to the handler options
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.ts — resolveOptions strips config
apps/pair-cli/src/commands/install/handler.ts — reference implementation (working)
apps/pair-cli/src/config/loader.ts — loadConfigWithOverrides (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:
dispatcher.ts — resolveOptions must include config from the dispatch context
cli.ts — registerCommandFromMetadata already passes config in normalized options; the gap is that dispatchCommand's DispatchContext interface lacks config
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
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.
Story Statement
As a CLI user running
pair updatein a consumer repoI want
pair updateto honor-c/--config <path>and auto-load projectpair.config.jsonSo that I can override registry behavior (e.g.
skills.flatten=false) per-project, just likepair installandpair packagealready doWhere:
pairCLI —updatecommandEpic Context
Parent Epic: Standalone bug (no parent epic)
Status: In Progress
Priority: P0 (Must-Have)
Status Workflow
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given a consumer repo with a
pair.config.jsoncontainingskills.flatten: falseWhen I run
pair update --source <path>Then the update uses the project config and produces nested (non-flattened) skill folders
Given a consumer repo with a
pair.config.jsoncontainingskills.flatten: falseWhen I run
pair update --source <path> -c ./pair.config.jsonThen the update uses the explicitly-specified config and produces nested (non-flattened) skill folders
Given a consumer repo with a custom config at
./custom-config.jsonsetting different registry targetsWhen I run
pair update -c ./custom-config.jsonThen the update resolves registries from the custom config, not the packaged base config
Given no project config exists and no
-cflag is providedWhen I run
pair updateThen the update uses the packaged base config (backward-compatible default behavior)
Given a
-cflag pointing to a non-existent fileWhen I run
pair update -c ./missing.jsonThen the update fails with a clear error message referencing the missing config file
Business Rules
pair installandpair packagebehavior exactly: base config -> projectpair.config.jsonmerge -> custom config merge--configflag must be wired through the full dispatch path: CLI -> parser -> dispatcher -> handler ->loadConfigWithOverrides--configis not provided and no project config existsEdge Cases and Error Handling
loadConfigWithOverridesalready throws; error should propagate with clear messageloadConfigWithOverridesalready throws; no new error handling needed--configtakes precedence over auto-detectedpair.config.json(existingloadConfigWithOverridesmerge order)Definition of Done Checklist
Development Completion
pnpm quality-gate)Quality Assurance
pair installverifiedDeployment and Release
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
configoption. Theinstallcommand 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:
#configmodule (loadConfigWithOverrides),commands/dispatcher.tsTeam Coordination
Development Roles Involved:
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
FileSystemServicewith custom config JSONEnvironment Requirements: Standard vitest test environment
User Validation
Success Metrics:
pair update -c ./pair.config.jsonuses the custom config;pair updatein a directory withpair.config.jsonauto-loads itRollback Plan: Revert PR
Notes and Additional Context
Refinement Session Insights: The bug root cause is confirmed via code analysis. Two gaps exist:
dispatchCommandindispatcher.tsdoes not forward theconfigoption from CLI args to the handler optionsparseUpdateCommandinupdate/parser.tsdoes not includeconfigin itsParseUpdateOptionsinterface (though the parser itself doesn't need it — it's the dispatch path that matters)The
installcommand works because itssetupInstallContextreceivesoptions.configfrom the handler options, and theregisterCommandFromMetadatafunction incli.tsdoes passconfigin the normalized options. However,dispatchCommandstrips it out inresolveOptions.NOTE: The bug also affected
pair install— the dispatcher strippedconfigfor ALL commands. Fix covers both.Documentation Links:
apps/pair-cli/src/commands/update/parser.ts— missing config in ParseUpdateOptionsapps/pair-cli/src/commands/update/handler.ts— handler already supportsoptions.configapps/pair-cli/src/commands/dispatcher.ts—resolveOptionsstripsconfigapps/pair-cli/src/commands/install/handler.ts— reference implementation (working)apps/pair-cli/src/config/loader.ts—loadConfigWithOverrides(shared config resolution)Technical Analysis
Implementation Approach
Technical Strategy: Wire the
--configCLI option through the dispatch path so it reacheshandleUpdateCommand'soptions.config. The handler (setupUpdateContext) already callsloadConfigWithOverrides(fs, configOptions)withconfigOptions.customConfigPath = options.config— it just never receives a non-undefined value today.Key Components:
dispatcher.ts—resolveOptionsmust includeconfigfrom the dispatch contextcli.ts—registerCommandFromMetadataalready passesconfigin normalized options; the gap is thatdispatchCommand'sDispatchContextinterface lacksconfigupdate/handler.ts— already wired, no changes neededData Flow:
Integration Points:
loadConfigWithOverrides(shared config module) — no changes needed there.Technical Requirements
DispatchContextinterface indispatcher.tsmust addconfig?: stringresolveOptionsindispatcher.tsmust spreadconfiginto returned optionscli.tsregisterCommandFromMetadatamust passconfigfrom normalized options intodispatchCommandcontextTechnical Risks and Mitigation
loadConfigWithOverridesmerge semantics already used by install/packageSpike 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
configto dispatch path (dispatcher.ts + cli.ts)configthrough update and install parser interfacesDependency Graph
AC Coverage
T-1: Add
configto dispatch path (dispatcher.ts + cli.ts)Priority: P0 | Estimated Hours: 1.5h | Bounded Context: CLI dispatch
Summary: Wire the
--configCLI option from Commander throughdispatchCommandto the handler options.Type: Bug Fix
Description: The
DispatchContextinterface indispatcher.tslacks aconfigfield, andresolveOptionsdoes not include it. TheregisterCommandFromMetadatafunction incli.tsdoes not passconfigfrom normalized CLI options into the dispatch context. This task adds the field to the interface, spreads it inresolveOptions, and passes it fromcli.ts.Acceptance Criteria:
dispatchCommandforwardsconfigto handler optionsDispatchContextinterface updated, noanycastshandleUpdateCommandreceivesoptions.configwhen-cis passedT-2: Add failing tests reproducing the bug
Priority: P0 | Estimated Hours: 2h | Bounded Context: CLI testing
Summary: Write tests that reproduce the bug:
handleUpdateCommandandhandleInstallCommandignoring--configoption.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
configthrough update and install parser interfacesPriority: P1 | Estimated Hours: 0.5h | Bounded Context: CLI parsing
Summary: Add
configtoParseUpdateOptionsandParseInstallOptionsfor 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 updateconfig handling matchespair installexactly. Run full quality gate.