Story Statement
As a CLI user
I want .pair/working/ recognized as the operational area: excluded from KB asset registries (never touched by install/update) with its path overridable in adoption
So that checkpoints and reports survive every KB install/update untouched (D14)
Where: pair-cli install/update behavior + adoption override + KB docs
Epic Context
Parent Epic: Split implement: checkpoint + publish-pr #206
Status: Refined
Priority: P1 (Should-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 project with files under .pair/working/ (checkpoints, reports)
When pair install or pair update runs
Then nothing under the working area is created, modified or deleted — mirror/registry behaviors skip it entirely
-
Given an adoption override declaring a custom working path
When skills and CLI resolve the working area
Then the override path is used consistently (checkpoints, reports) and equally excluded from registry operations
-
Given a fresh project without a working area
When a skill first needs it (checkpoint, report)
Then the directory is created on demand by the skill — install does NOT scaffold it
Business Rules
- Working area is operational data, not knowledge: outside every asset registry by design (D14)
- Default path
.pair/working/ with checkpoints/ and reports/<category>/ subdirs; override in adoption (WoW)
- Convention documented in KB so external KBs inherit the rule
Edge Cases and Error Handling
- Registry config accidentally includes the working path: validate-config flags it as an error
- Override points inside a registry-managed dir: validation error with clear message
Definition of Done Checklist
Development Completion
Quality Assurance
Story Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 3 (M)
Confidence Level: High
Sizing Justification: bounded CLI change (exclusion rule + validation) + docs; no skill logic
Sprint Capacity Validation
Sprint Fit Assessment: yes
Total Effort Assessment: fits — Yes
Dependencies and Coordination
Story Dependencies
Prerequisite Stories: none (checkpoint story benefits but does not block)
Dependent Stories: report-producing stories (#226 monitoring, #228) rely on the exclusion guarantee
Shared Components: pair.config.json registry schema, validate-config
Validation and Testing Strategy
Acceptance Testing Approach
Testing Methods: CLI integration tests with fixture repos (populated working area, override variant)
Test Data Requirements: fixture checkpoints/reports files
Notes and Additional Context
Documentation Links: D14, D21 · Spec G5/G6 (reports) · working-area listed in the ".pair structure after" map
Technical Analysis
Implementation Approach
Technical Strategy: exclusion implemented in apps/pair-cli registry resolution (hard rule + config validation); adoption override read from WoW; KB doc for the convention
Key Components: registry walker exclusion, validate-config rule, docs
Integration Points: install/update/update-link commands, kb-validate
Technical Risks and Mitigation
| Risk |
Impact |
Probability |
Mitigation Strategy |
Existing configs already mirroring .pair root |
Medium |
Low |
hard exclusion wins over config; validation warns on overlap |
Task Breakdown
Checklist
Dependency Graph
T1 → T2 → T3
AC Coverage
| Task |
AC1 |
AC2 |
AC3 |
| T1 |
✓ |
|
|
| T2 |
|
✓ |
|
| T3 |
|
|
✓ |
T-1: Hard exclusion of working path in registry operations + unit tests
Priority: P0 | Estimated Hours: 4h | Bounded Context: pair-cli KB Distribution (Registry Operations)
Summary: Introduce a hard, config-independent exclusion primitive (isPathExcluded) in content-ops and thread it through every copy/mirror traversal used by pair install/pair update, so the working area can never be touched regardless of registry configuration.
Type: Feature Implementation
Description: Adds isPathExcluded(candidate, excludePaths, platform) to packages/content-ops/src/file-system/file-operations.ts as the single exclusion primitive: a path matches if it equals or lies under one of excludePaths, with case-folded comparison on darwin/win32 (platform injectable for tests) so an override differing only in case still matches. excludePaths is added to SyncOptions (packages/content-ops/src/ops/SyncOptions.ts) as an optional string[]. It is consumed at three traversal points: copyDirEntry (per-entry hard-skip during recursive directory copy, file-operations.ts), prepareCopyPathOperation (whole-operation hard-skip when the resolved destination itself is excluded, copyPathOps.ts), and handleMirrorCleanup (skips excluded destination-only entries instead of deleting them during mirror cleanup, path-operation-helpers.ts; forwarded from both copyPathOps.ts and movePathOps.ts). At the pair-cli registry layer, apps/pair-cli/src/registry/operations.ts applies the same guard at two more points: doCopyAndUpdateLinks short-circuits the entire registry copy with { skipped: true, reason } when the resolved target is (or lies within) an excluded path, and distributeToSecondaryTargets skips any secondary (symlink/copy/transform) target that resolves into an excluded path before writing it. buildCopyOptions injects excludePaths into the built SyncOptions unconditionally whenever the caller supplies it — a registry's own config cannot opt out. install/handler.ts and update/handler.ts resolve the working path once (resolveWorkingPath) and pass [workingPath] into buildCopyOptions and postCopyOps for every registry, so the exclusion applies uniformly across all registries without per-registry configuration.
Acceptance Criteria:
- Primary deliverable:
isPathExcluded exported from content-ops and applied at every copy/mirror/secondary-distribution entry point reachable from pair install/pair update.
- Quality standard: case-insensitive comparison only on
darwin/win32; linux remains case-sensitive; platform is injectable for deterministic tests.
- Integration requirement: the exclusion is unconditional — no registry
behavior or include setting can cause content to be written into an excluded path.
- Verification method: round-trip install/update against a fixture with a populated working area, assert byte-identical content before/after.
Implementation Approach:
- Technical Design: single boundary primitive (
isPathExcluded) reused at every traversal layer instead of re-implementing exclusion logic per call site; excludePaths flows through SyncOptions rather than a separate parameter.
- Files to Modify/Create:
packages/content-ops/src/file-system/file-operations.ts — isPathExcluded, copyDirEntry hard-skip
packages/content-ops/src/ops/SyncOptions.ts — excludePaths?: string[] field
packages/content-ops/src/ops/copyPathOps.ts — prepareCopyPathOperation whole-op hard-skip
packages/content-ops/src/ops/path-operation-helpers.ts — handleMirrorCleanup excludePaths param
packages/content-ops/src/ops/movePathOps.ts — forwards options?.excludePaths to handleMirrorCleanup
apps/pair-cli/src/registry/operations.ts — doCopyAndUpdateLinks, distributeToSecondaryTargets, buildCopyOptions, postCopyOps
apps/pair-cli/src/commands/install/handler.ts, apps/pair-cli/src/commands/update/handler.ts — resolve workingPath and pass as excludePaths to every registry's copy + post-copy step
Dependencies:
- Tasks: none (root of the dependency graph)
Implementation Steps:
- Add
isPathExcluded to file-operations.ts with case-fold-by-platform normalization; add excludePaths to SyncOptions.
- Wire the per-entry hard-skip into
copyDirEntry, and the whole-operation hard-skip into copyPathOps.ts's prepareCopyPathOperation.
- Extend
handleMirrorCleanup to accept excludePaths and skip removal of excluded destination-only entries; forward it from both copyPathOps.ts and movePathOps.ts.
- Add the registry-layer guards in
operations.ts (doCopyAndUpdateLinks short-circuit, distributeToSecondaryTargets per-target skip, unconditional injection in buildCopyOptions).
- Resolve
workingPath once per command in install/handler.ts and update/handler.ts and pass it through to every registry's copy and post-copy call.
Testing Strategy:
- Unit Tests:
file-operations.test.ts (isPathExcluded exact/nested/sibling/unrelated matches, case-sensitivity by platform, copyDirHelper excludePaths never entering an excluded subtree); path-operation-helpers.test.ts (handleMirrorCleanup never removes an excluded path); movePathOps.test.ts (excluded destination-only entries survive a mirror move); operations.test.ts (doCopyAndUpdateLinks skips the whole op when target is/contains the working area, distributeToSecondaryTargets skips excluded secondary targets while still distributing to non-excluded ones, buildCopyOptions omits excludePaths when none given and injects unconditionally when given, postCopyOps forwards excludePaths).
- Manual Testing:
install/handler.test.ts #257: working area exclusion on install (D14) — fresh install does not scaffold .pair/working; update/handler.test.ts #257: working area excluded from update (D14) — populated working area survives update byte-identical, and an update whose registry accidentally covers the working area is rejected before any file is touched.
Notes: Verified genuinely present on this branch: case-fold comparison in isPathExcluded (darwin/win32, tested per-platform) and the distributeToSecondaryTargets hard guard (operations.ts:279-293, tested at operations.test.ts "skips a secondary target excluded by excludePaths (D14)" / "still distributes to non-excluded secondary targets"). One residual gap: movePathOps.ts forwards excludePaths only into handleMirrorCleanup (mirror-cleanup of destination-only entries) — the per-entry move loop itself (moveDirectoryContents/moveDirectoryEntry) never calls isPathExcluded, so a direct move of an entry into an excluded path wouldn't be blocked at that layer. This is currently inert risk, not a live bug: movePathOps is exported from content-ops but not called anywhere in apps/pair-cli's install/update/update-link flows (grep confirms zero non-test call sites), so no code path reachable from the CLI can hit it today. Flagging for awareness if movePathOps is ever wired into a registry-driven flow — the guard would need to be added to the per-entry loop, not just the cleanup path.
T-2: Adoption override resolution + validation rules (overlap errors)
Priority: P0 | Estimated Hours: 3h | Bounded Context: pair-cli KB Distribution (Configuration & Validation)
Summary: Resolve the working-area path (default or working_path override) consistently across every command that needs it, and validate that no registry ever overlaps with it, in either direction, with a clear error message.
Type: Feature Implementation
Description: apps/pair-cli/src/registry/working-area.ts is the new module owning this: DEFAULT_WORKING_PATH = '.pair/working'; resolveWorkingPathOverride(config) reads a top-level working_path string from raw config (falling back to the default for anything not a non-empty string); resolveWorkingPath(config, baseTarget, fs) resolves that override to an absolute path against a given base target; isWithinPath/pathsOverlap do bidirectional overlap checks with the same darwin/win32 case-folding used in isPathExcluded; detectWorkingPathOverlap(registries, workingPath) walks every registry's targets and flags any whose path overlaps the working path in either direction (registry-covers-working-area, or override-lands-inside-registry), returning human-readable error strings referencing D14. apps/pair-cli/src/registry/validation.ts's validateAllRegistries takes an optional workingPath parameter and appends detectWorkingPathOverlap results to the existing overlap/config errors, only when there are no other errors already (so registry-shape errors surface first). resolver.ts's Config type gains an optional working_path field. Every entry point resolves the override consistently: config/loader.ts's validateConfig, install/handler.ts and update/handler.ts's setup functions (resolveWorkingPathOverride → validateAllRegistries before any copy runs, and resolveWorkingPath for the actual exclusion), and validate-config/handler.ts (resolving from either the merged config or, when --config is passed, the raw custom config file directly — validating only that file without merging, to catch errors in the user's config in isolation). update-link/handler.ts's verifyKB adds a hard guard: it best-effort loads the project config (falling back to the default path if loading fails, so the command never blocks on an unrelated config error), resolves the working path, and refuses to process a kbPath (whether the default .pair/knowledge or an explicit --target) that overlaps the working area.
Acceptance Criteria:
- Primary deliverable:
working_path override is read from one place (resolveWorkingPathOverride) and consumed consistently by install, update, update-link, and validate-config — no command has its own divergent resolution logic.
- Quality standard: overlap detection is bidirectional (registry-into-working-area and override-into-registry) and case-normalized per platform, matching the
isPathExcluded normalization semantics from T1.
- Integration requirement:
validateAllRegistries runs and throws before any copy operation starts, for install, update, and validate-config.
- Verification method: fixture-driven unit tests covering default path, override path, both overlap directions, and case-differing overrides on darwin/win32 vs linux.
Implementation Approach:
- Technical Design: a small, dependency-free module (
working-area.ts) as the single source of truth for path resolution and overlap detection, imported by validation, install/update setup, and update-link — avoiding drift between "what gets excluded" (T1) and "what gets validated" (T2).
- Files to Modify/Create:
apps/pair-cli/src/registry/working-area.ts — new: DEFAULT_WORKING_PATH, resolveWorkingPathOverride, resolveWorkingPath, isWithinPath, pathsOverlap, detectWorkingPathOverlap
apps/pair-cli/src/registry/validation.ts — validateAllRegistries(registries, workingPath) appends overlap errors
apps/pair-cli/src/registry/resolver.ts — Config.working_path?: string
apps/pair-cli/src/registry/index.ts — re-export the new module's public API
apps/pair-cli/src/config/loader.ts — validateConfig resolves and passes workingPath
apps/pair-cli/src/commands/validate-config/handler.ts — resolves workingPath from merged or raw custom config
apps/pair-cli/src/commands/install/handler.ts, apps/pair-cli/src/commands/update/handler.ts — setupInstallContext/setupUpdateContext validate before executing
apps/pair-cli/src/commands/update-link/handler.ts — verifyKB hard guard against kbPath/--target overlap
Dependencies:
- Tasks: T1 (reuses the same case-fold overlap semantics as
isPathExcluded; T2's validation and T1's runtime exclusion must agree on what counts as "the working area")
Implementation Steps:
- Add
working-area.ts with path resolution (resolveWorkingPathOverride, resolveWorkingPath) and overlap primitives (isWithinPath, pathsOverlap, detectWorkingPathOverlap), case-folding on darwin/win32.
- Extend
validateAllRegistries to accept workingPath and append detectWorkingPathOverlap errors after existing registry/overlap errors.
- Add
working_path to the Config type and wire resolveWorkingPathOverride/resolveWorkingPath into install/update setup (validate-before-copy) and validate-config (both merged-config and raw-custom-config paths).
- Add the
update-link hard guard in verifyKB: best-effort config load, resolve working path, pathsOverlap(kbPath, workingPath) check, throw with a D14-referencing message on overlap.
Testing Strategy:
- Unit Tests:
working-area.test.ts (override default/explicit/invalid, resolveWorkingPath resolution, isWithinPath/pathsOverlap exact/nested/sibling/unrelated, case-sensitivity by platform for both, detectWorkingPathOverlap no-overlap/exact-match/ancestor-mirror/override-inside-registry/secondary-targets); validation.test.ts "working area overlap (D14)" block (passes for defaults, fails on registry-overlap, fails on override-inside-registry).
- Manual Testing:
validate-config/handler.test.ts "working area overlap (D14)" (flags a custom-config registry accidentally targeting the working path; flags an override landing inside a registry-managed directory); update-link/handler.test.ts "working area hard exclusion (D14)" (refuses a --target overlapping the working area; refuses when a configured working_path override lands inside --target); update/handler.test.ts "respects an overridden working_path, excluding it from update instead of the default path" and "rejects an update whose registry accidentally covers the working area, leaving files untouched" (confirms validation runs, and blocks, before any copy).
Notes: Verified genuinely present and tested on this branch: case-fold path comparison in working-area.ts's normalize() (darwin/win32 folded, linux not — working-area.test.ts "case sensitivity by platform (D14)" blocks for both isWithinPath and pathsOverlap), and the update-link handler's overlap guard in verifyKB (lines 110-126 of update-link/handler.ts), exercised by both cases in the "working area hard exclusion (D14)" describe block. No discrepancy found between the AC/edge cases in the story and the actual validation behavior — both overlap directions are implemented, tested, and wired into all four commands (install, update, update-link, validate-config) that need to know where the working area is.
T-3: KB convention doc + docs site + release note
Priority: P1 | Estimated Hours: 2h | Bounded Context: Documentation (Knowledge Base Authoring)
Summary: Document the working-area convention in the KB itself (so downstream projects inherit it via pair update) and in the public docs site, and prepare a release note describing the behavior change.
Type: Documentation
Description: Adds packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/working-area.md — the canonical KB doc: overview distinguishing .pair/working/ from .pair/knowledge/ and .pair/adoption/, the on-demand-creation rule (checkpoints/, reports/<category>/, neither scaffolded by pair install), the two-level exclusion rule (convention + hard rule in pair-cli, referencing T1's defense-in-depth framing), the working_path override mechanism and its validation guarantee, an explicit statement that the rule applies to any KB dataset consumed by pair-cli (not foomakers/pair-specific, since enforcement lives in pair-cli itself), and a skill-integration table. Links it from packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/README.md under a new "Operational Conventions" section. On the docs site (apps/website): content/docs/concepts/knowledge-base.mdx gains a "A Third Area: Working (Operational, Not Knowledge)" section describing the third area alongside knowledge/adoption; content/docs/reference/kb-structure.mdx adds working/ to the directory tree and a working/ — Operational Area (D14) subsection plus a "Never touched (excluded)" row in the mutation-rules table; content/docs/reference/configuration.mdx adds a "Working Area Override" section with the working_path JSON example and a validation bullet cross-referencing it.
Acceptance Criteria:
- Primary deliverable: KB doc (
working-area.md) exists, is linked from the collaboration README, and matches the actual T1/T2 behavior (no aspirational claims beyond what's implemented).
- Quality standard: docs site pages (concepts, kb-structure, configuration references) are updated consistently with the KB doc — same terminology (D14,
working_path, checkpoints/, reports/<category>/).
- Integration requirement: because the KB doc lives under
packages/knowledge-hub/dataset/.pair/..., it is itself distributed to consuming projects via the knowledge registry on pair update — the convention becomes visible externally, not just in this repo.
- Verification method: manual read-through cross-checking each doc claim against the T1/T2 implementation; changelog/release note communicates the behavior change to consumers.
Implementation Approach:
- Technical Design: documentation-only change, no code; content is derived from and must stay consistent with T1 (exclusion mechanics) and T2 (override/validation mechanics).
- Files to Modify/Create:
packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/working-area.md — new KB doc
packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/README.md — link under "Operational Conventions"
apps/website/content/docs/concepts/knowledge-base.mdx — third-area section
apps/website/content/docs/reference/kb-structure.mdx — directory tree entry, subsection, mutation-rules table row
apps/website/content/docs/reference/configuration.mdx — "Working Area Override" section
.changeset/working-area-exclusion.md — drafted, then removed (see Notes)
Dependencies:
- Tasks: T1, T2 (documentation must describe behavior that already exists and is already validated/tested)
Implementation Steps:
- Write
working-area.md covering overview, structure, exclusion rule (referencing T1), override mechanism and validation (referencing T2), external-KB applicability, and skill integration table.
- Link the new doc from
collaboration/README.md's new "Operational Conventions" section.
- Update the three
apps/website doc pages (concepts, kb-structure, configuration) to reflect the same convention for the public docs site.
- Draft a changeset describing the behavior change for consumers of
@pair/pair-cli, @pair/content-ops, and @pair/knowledge-hub.
Testing Strategy:
- Unit Tests: none (documentation-only; no code paths introduced).
- Manual Testing: cross-read each doc file against T1/T2 code to confirm no drift (e.g. confirmed
working-area.md's exclusion/override claims match isPathExcluded, detectWorkingPathOverlap, and the install/update wiring verified in T1/T2).
Notes: Discrepancy found — the DoD item "Documentation updated" is satisfied for the KB doc and docs site, but the release note is not actually present on this branch: commit 68b2a7a "[#257] chore: remove changeset (deferred to release batch)" deletes the drafted .changeset/working-area-exclusion.md that commit 1a374c3 had added, with the explicit message that it's deferred to a release batch rather than shipped with this story. This is a deliberate, logged deferral (not an oversight), but the checklist item T3 as titled ("KB convention doc + docs site + release note") is only two-thirds done as of this branch — the release note will need to be re-added in whichever PR/batch actually cuts the release. No other gap found: the KB doc and both docs-site updates are genuinely present and consistent with the T1/T2 implementation.
Story Statement
As a CLI user
I want
.pair/working/recognized as the operational area: excluded from KB asset registries (never touched by install/update) with its path overridable in adoptionSo that checkpoints and reports survive every KB install/update untouched (D14)
Where:
pair-cliinstall/update behavior + adoption override + KB docsEpic Context
Parent Epic: Split implement: checkpoint + publish-pr #206
Status: Refined
Priority: P1 (Should-Have)
Status Workflow
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given a project with files under
.pair/working/(checkpoints, reports)When
pair installorpair updaterunsThen nothing under the working area is created, modified or deleted — mirror/registry behaviors skip it entirely
Given an adoption override declaring a custom working path
When skills and CLI resolve the working area
Then the override path is used consistently (checkpoints, reports) and equally excluded from registry operations
Given a fresh project without a working area
When a skill first needs it (checkpoint, report)
Then the directory is created on demand by the skill — install does NOT scaffold it
Business Rules
.pair/working/withcheckpoints/andreports/<category>/subdirs; override in adoption (WoW)Edge Cases and Error Handling
Definition of Done Checklist
Development Completion
apps/website) working-area pageapps/pair-cli) updated — this story is CLI work by definitionQuality Assurance
Story Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 3 (M)
Confidence Level: High
Sizing Justification: bounded CLI change (exclusion rule + validation) + docs; no skill logic
Sprint Capacity Validation
Sprint Fit Assessment: yes
Total Effort Assessment: fits — Yes
Dependencies and Coordination
Story Dependencies
Prerequisite Stories: none (checkpoint story benefits but does not block)
Dependent Stories: report-producing stories (#226 monitoring, #228) rely on the exclusion guarantee
Shared Components:
pair.config.jsonregistry schema, validate-configValidation and Testing Strategy
Acceptance Testing Approach
Testing Methods: CLI integration tests with fixture repos (populated working area, override variant)
Test Data Requirements: fixture checkpoints/reports files
Notes and Additional Context
Documentation Links: D14, D21 · Spec G5/G6 (reports) · working-area listed in the ".pair structure after" map
Technical Analysis
Implementation Approach
Technical Strategy: exclusion implemented in
apps/pair-cliregistry resolution (hard rule + config validation); adoption override read from WoW; KB doc for the conventionKey Components: registry walker exclusion, validate-config rule, docs
Integration Points: install/update/update-link commands, kb-validate
Technical Risks and Mitigation
.pairrootTask Breakdown
Checklist
Dependency Graph
T1 → T2 → T3
AC Coverage
T-1: Hard exclusion of working path in registry operations + unit tests
Priority: P0 | Estimated Hours: 4h | Bounded Context: pair-cli KB Distribution (Registry Operations)
Summary: Introduce a hard, config-independent exclusion primitive (
isPathExcluded) incontent-opsand thread it through every copy/mirror traversal used bypair install/pair update, so the working area can never be touched regardless of registry configuration.Type: Feature Implementation
Description: Adds
isPathExcluded(candidate, excludePaths, platform)topackages/content-ops/src/file-system/file-operations.tsas the single exclusion primitive: a path matches if it equals or lies under one ofexcludePaths, with case-folded comparison ondarwin/win32(platform injectable for tests) so an override differing only in case still matches.excludePathsis added toSyncOptions(packages/content-ops/src/ops/SyncOptions.ts) as an optionalstring[]. It is consumed at three traversal points:copyDirEntry(per-entry hard-skip during recursive directory copy,file-operations.ts),prepareCopyPathOperation(whole-operation hard-skip when the resolved destination itself is excluded,copyPathOps.ts), andhandleMirrorCleanup(skips excluded destination-only entries instead of deleting them during mirror cleanup,path-operation-helpers.ts; forwarded from bothcopyPathOps.tsandmovePathOps.ts). At thepair-cliregistry layer,apps/pair-cli/src/registry/operations.tsapplies the same guard at two more points:doCopyAndUpdateLinksshort-circuits the entire registry copy with{ skipped: true, reason }when the resolved target is (or lies within) an excluded path, anddistributeToSecondaryTargetsskips any secondary (symlink/copy/transform) target that resolves into an excluded path before writing it.buildCopyOptionsinjectsexcludePathsinto the builtSyncOptionsunconditionally whenever the caller supplies it — a registry's own config cannot opt out.install/handler.tsandupdate/handler.tsresolve the working path once (resolveWorkingPath) and pass[workingPath]intobuildCopyOptionsandpostCopyOpsfor every registry, so the exclusion applies uniformly across all registries without per-registry configuration.Acceptance Criteria:
isPathExcludedexported fromcontent-opsand applied at every copy/mirror/secondary-distribution entry point reachable frompair install/pair update.darwin/win32;linuxremains case-sensitive; platform is injectable for deterministic tests.behaviororincludesetting can cause content to be written into an excluded path.Implementation Approach:
isPathExcluded) reused at every traversal layer instead of re-implementing exclusion logic per call site;excludePathsflows throughSyncOptionsrather than a separate parameter.packages/content-ops/src/file-system/file-operations.ts—isPathExcluded,copyDirEntryhard-skippackages/content-ops/src/ops/SyncOptions.ts—excludePaths?: string[]fieldpackages/content-ops/src/ops/copyPathOps.ts—prepareCopyPathOperationwhole-op hard-skippackages/content-ops/src/ops/path-operation-helpers.ts—handleMirrorCleanupexcludePaths parampackages/content-ops/src/ops/movePathOps.ts— forwardsoptions?.excludePathstohandleMirrorCleanupapps/pair-cli/src/registry/operations.ts—doCopyAndUpdateLinks,distributeToSecondaryTargets,buildCopyOptions,postCopyOpsapps/pair-cli/src/commands/install/handler.ts,apps/pair-cli/src/commands/update/handler.ts— resolveworkingPathand pass asexcludePathsto every registry's copy + post-copy stepDependencies:
Implementation Steps:
isPathExcludedtofile-operations.tswith case-fold-by-platform normalization; addexcludePathstoSyncOptions.copyDirEntry, and the whole-operation hard-skip intocopyPathOps.ts'sprepareCopyPathOperation.handleMirrorCleanupto acceptexcludePathsand skip removal of excluded destination-only entries; forward it from bothcopyPathOps.tsandmovePathOps.ts.operations.ts(doCopyAndUpdateLinksshort-circuit,distributeToSecondaryTargetsper-target skip, unconditional injection inbuildCopyOptions).workingPathonce per command ininstall/handler.tsandupdate/handler.tsand pass it through to every registry's copy and post-copy call.Testing Strategy:
file-operations.test.ts(isPathExcludedexact/nested/sibling/unrelated matches, case-sensitivity by platform,copyDirHelperexcludePaths never entering an excluded subtree);path-operation-helpers.test.ts(handleMirrorCleanupnever removes an excluded path);movePathOps.test.ts(excluded destination-only entries survive a mirror move);operations.test.ts(doCopyAndUpdateLinksskips the whole op when target is/contains the working area,distributeToSecondaryTargetsskips excluded secondary targets while still distributing to non-excluded ones,buildCopyOptionsomitsexcludePathswhen none given and injects unconditionally when given,postCopyOpsforwardsexcludePaths).install/handler.test.ts#257: working area exclusion on install (D14)— fresh install does not scaffold.pair/working;update/handler.test.ts#257: working area excluded from update (D14)— populated working area survives update byte-identical, and an update whose registry accidentally covers the working area is rejected before any file is touched.Notes: Verified genuinely present on this branch: case-fold comparison in
isPathExcluded(darwin/win32, tested per-platform) and thedistributeToSecondaryTargetshard guard (operations.ts:279-293, tested at operations.test.ts "skips a secondary target excluded by excludePaths (D14)" / "still distributes to non-excluded secondary targets"). One residual gap:movePathOps.tsforwardsexcludePathsonly intohandleMirrorCleanup(mirror-cleanup of destination-only entries) — the per-entry move loop itself (moveDirectoryContents/moveDirectoryEntry) never callsisPathExcluded, so a direct move of an entry into an excluded path wouldn't be blocked at that layer. This is currently inert risk, not a live bug:movePathOpsis exported fromcontent-opsbut not called anywhere inapps/pair-cli's install/update/update-link flows (grep confirms zero non-test call sites), so no code path reachable from the CLI can hit it today. Flagging for awareness ifmovePathOpsis ever wired into a registry-driven flow — the guard would need to be added to the per-entry loop, not just the cleanup path.T-2: Adoption override resolution + validation rules (overlap errors)
Priority: P0 | Estimated Hours: 3h | Bounded Context: pair-cli KB Distribution (Configuration & Validation)
Summary: Resolve the working-area path (default or
working_pathoverride) consistently across every command that needs it, and validate that no registry ever overlaps with it, in either direction, with a clear error message.Type: Feature Implementation
Description:
apps/pair-cli/src/registry/working-area.tsis the new module owning this:DEFAULT_WORKING_PATH = '.pair/working';resolveWorkingPathOverride(config)reads a top-levelworking_pathstring from raw config (falling back to the default for anything not a non-empty string);resolveWorkingPath(config, baseTarget, fs)resolves that override to an absolute path against a given base target;isWithinPath/pathsOverlapdo bidirectional overlap checks with the same darwin/win32 case-folding used inisPathExcluded;detectWorkingPathOverlap(registries, workingPath)walks every registry's targets and flags any whose path overlaps the working path in either direction (registry-covers-working-area, or override-lands-inside-registry), returning human-readable error strings referencing D14.apps/pair-cli/src/registry/validation.ts'svalidateAllRegistriestakes an optionalworkingPathparameter and appendsdetectWorkingPathOverlapresults to the existing overlap/config errors, only when there are no other errors already (so registry-shape errors surface first).resolver.ts'sConfigtype gains an optionalworking_pathfield. Every entry point resolves the override consistently:config/loader.ts'svalidateConfig,install/handler.tsandupdate/handler.ts's setup functions (resolveWorkingPathOverride→validateAllRegistriesbefore any copy runs, andresolveWorkingPathfor the actual exclusion), andvalidate-config/handler.ts(resolving from either the merged config or, when--configis passed, the raw custom config file directly — validating only that file without merging, to catch errors in the user's config in isolation).update-link/handler.ts'sverifyKBadds a hard guard: it best-effort loads the project config (falling back to the default path if loading fails, so the command never blocks on an unrelated config error), resolves the working path, and refuses to process akbPath(whether the default.pair/knowledgeor an explicit--target) that overlaps the working area.Acceptance Criteria:
working_pathoverride is read from one place (resolveWorkingPathOverride) and consumed consistently by install, update, update-link, and validate-config — no command has its own divergent resolution logic.isPathExcludednormalization semantics from T1.validateAllRegistriesruns and throws before any copy operation starts, for install, update, and validate-config.Implementation Approach:
working-area.ts) as the single source of truth for path resolution and overlap detection, imported by validation, install/update setup, and update-link — avoiding drift between "what gets excluded" (T1) and "what gets validated" (T2).apps/pair-cli/src/registry/working-area.ts— new:DEFAULT_WORKING_PATH,resolveWorkingPathOverride,resolveWorkingPath,isWithinPath,pathsOverlap,detectWorkingPathOverlapapps/pair-cli/src/registry/validation.ts—validateAllRegistries(registries, workingPath)appends overlap errorsapps/pair-cli/src/registry/resolver.ts—Config.working_path?: stringapps/pair-cli/src/registry/index.ts— re-export the new module's public APIapps/pair-cli/src/config/loader.ts—validateConfigresolves and passesworkingPathapps/pair-cli/src/commands/validate-config/handler.ts— resolvesworkingPathfrom merged or raw custom configapps/pair-cli/src/commands/install/handler.ts,apps/pair-cli/src/commands/update/handler.ts—setupInstallContext/setupUpdateContextvalidate before executingapps/pair-cli/src/commands/update-link/handler.ts—verifyKBhard guard againstkbPath/--targetoverlapDependencies:
isPathExcluded; T2's validation and T1's runtime exclusion must agree on what counts as "the working area")Implementation Steps:
working-area.tswith path resolution (resolveWorkingPathOverride,resolveWorkingPath) and overlap primitives (isWithinPath,pathsOverlap,detectWorkingPathOverlap), case-folding on darwin/win32.validateAllRegistriesto acceptworkingPathand appenddetectWorkingPathOverlaperrors after existing registry/overlap errors.working_pathto theConfigtype and wireresolveWorkingPathOverride/resolveWorkingPathintoinstall/updatesetup (validate-before-copy) andvalidate-config(both merged-config and raw-custom-config paths).update-linkhard guard inverifyKB: best-effort config load, resolve working path,pathsOverlap(kbPath, workingPath)check, throw with a D14-referencing message on overlap.Testing Strategy:
working-area.test.ts(override default/explicit/invalid,resolveWorkingPathresolution,isWithinPath/pathsOverlapexact/nested/sibling/unrelated, case-sensitivity by platform for both,detectWorkingPathOverlapno-overlap/exact-match/ancestor-mirror/override-inside-registry/secondary-targets);validation.test.ts"working area overlap (D14)" block (passes for defaults, fails on registry-overlap, fails on override-inside-registry).validate-config/handler.test.ts"working area overlap (D14)" (flags a custom-config registry accidentally targeting the working path; flags an override landing inside a registry-managed directory);update-link/handler.test.ts"working area hard exclusion (D14)" (refuses a--targetoverlapping the working area; refuses when a configuredworking_pathoverride lands inside--target);update/handler.test.ts"respects an overridden working_path, excluding it from update instead of the default path" and "rejects an update whose registry accidentally covers the working area, leaving files untouched" (confirms validation runs, and blocks, before any copy).Notes: Verified genuinely present and tested on this branch: case-fold path comparison in
working-area.ts'snormalize()(darwin/win32 folded, linux not —working-area.test.ts"case sensitivity by platform (D14)" blocks for bothisWithinPathandpathsOverlap), and the update-link handler's overlap guard inverifyKB(lines 110-126 ofupdate-link/handler.ts), exercised by both cases in the "working area hard exclusion (D14)" describe block. No discrepancy found between the AC/edge cases in the story and the actual validation behavior — both overlap directions are implemented, tested, and wired into all four commands (install, update, update-link, validate-config) that need to know where the working area is.T-3: KB convention doc + docs site + release note
Priority: P1 | Estimated Hours: 2h | Bounded Context: Documentation (Knowledge Base Authoring)
Summary: Document the working-area convention in the KB itself (so downstream projects inherit it via
pair update) and in the public docs site, and prepare a release note describing the behavior change.Type: Documentation
Description: Adds
packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/working-area.md— the canonical KB doc: overview distinguishing.pair/working/from.pair/knowledge/and.pair/adoption/, the on-demand-creation rule (checkpoints/,reports/<category>/, neither scaffolded bypair install), the two-level exclusion rule (convention + hard rule inpair-cli, referencing T1's defense-in-depth framing), theworking_pathoverride mechanism and its validation guarantee, an explicit statement that the rule applies to any KB dataset consumed bypair-cli(notfoomakers/pair-specific, since enforcement lives inpair-cliitself), and a skill-integration table. Links it frompackages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/README.mdunder a new "Operational Conventions" section. On the docs site (apps/website):content/docs/concepts/knowledge-base.mdxgains a "A Third Area: Working (Operational, Not Knowledge)" section describing the third area alongside knowledge/adoption;content/docs/reference/kb-structure.mdxaddsworking/to the directory tree and aworking/— Operational Area (D14) subsection plus a "Never touched (excluded)" row in the mutation-rules table;content/docs/reference/configuration.mdxadds a "Working Area Override" section with theworking_pathJSON example and a validation bullet cross-referencing it.Acceptance Criteria:
working-area.md) exists, is linked from the collaboration README, and matches the actual T1/T2 behavior (no aspirational claims beyond what's implemented).working_path,checkpoints/,reports/<category>/).packages/knowledge-hub/dataset/.pair/..., it is itself distributed to consuming projects via theknowledgeregistry onpair update— the convention becomes visible externally, not just in this repo.Implementation Approach:
packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/working-area.md— new KB docpackages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/README.md— link under "Operational Conventions"apps/website/content/docs/concepts/knowledge-base.mdx— third-area sectionapps/website/content/docs/reference/kb-structure.mdx— directory tree entry, subsection, mutation-rules table rowapps/website/content/docs/reference/configuration.mdx— "Working Area Override" section.changeset/working-area-exclusion.md— drafted, then removed (see Notes)Dependencies:
Implementation Steps:
working-area.mdcovering overview, structure, exclusion rule (referencing T1), override mechanism and validation (referencing T2), external-KB applicability, and skill integration table.collaboration/README.md's new "Operational Conventions" section.apps/websitedoc pages (concepts, kb-structure, configuration) to reflect the same convention for the public docs site.@pair/pair-cli,@pair/content-ops, and@pair/knowledge-hub.Testing Strategy:
working-area.md's exclusion/override claims matchisPathExcluded,detectWorkingPathOverlap, and the install/update wiring verified in T1/T2).Notes: Discrepancy found — the DoD item "Documentation updated" is satisfied for the KB doc and docs site, but the release note is not actually present on this branch: commit
68b2a7a "[#257] chore: remove changeset (deferred to release batch)"deletes the drafted.changeset/working-area-exclusion.mdthat commit1a374c3had added, with the explicit message that it's deferred to a release batch rather than shipped with this story. This is a deliberate, logged deferral (not an oversight), but the checklist item T3 as titled ("KB convention doc + docs site + release note") is only two-thirds done as of this branch — the release note will need to be re-added in whichever PR/batch actually cuts the release. No other gap found: the KB doc and both docs-site updates are genuinely present and consistent with the T1/T2 implementation.