Skip to content

.pair/working/ excluded from KB registries + adoption path override #257

Description

@rucka

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:

  1. 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

  2. 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

  3. 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

  • All acceptance criteria implemented and verified
  • Code follows project coding standards
  • Code review completed and approved
  • Unit tests written and passing (registry exclusion fixtures)
  • Documentation updated — docs site (apps/website) working-area page
  • CLI (apps/pair-cli) updated — this story is CLI work by definition

Quality Assurance

  • Round-trip install/update with populated working area (byte-identical after)
  • Override fixture tested (custom path respected and excluded)

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

  • T1 — Hard exclusion of working path in registry operations + unit tests
  • T2 — Adoption override resolution + validation rules (overlap errors)
  • T3 — KB convention doc + docs site + release note

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.tsisPathExcluded, copyDirEntry hard-skip
    • packages/content-ops/src/ops/SyncOptions.tsexcludePaths?: string[] field
    • packages/content-ops/src/ops/copyPathOps.tsprepareCopyPathOperation whole-op hard-skip
    • packages/content-ops/src/ops/path-operation-helpers.tshandleMirrorCleanup excludePaths param
    • packages/content-ops/src/ops/movePathOps.ts — forwards options?.excludePaths to handleMirrorCleanup
    • apps/pair-cli/src/registry/operations.tsdoCopyAndUpdateLinks, 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:

  1. Add isPathExcluded to file-operations.ts with case-fold-by-platform normalization; add excludePaths to SyncOptions.
  2. Wire the per-entry hard-skip into copyDirEntry, and the whole-operation hard-skip into copyPathOps.ts's prepareCopyPathOperation.
  3. Extend handleMirrorCleanup to accept excludePaths and skip removal of excluded destination-only entries; forward it from both copyPathOps.ts and movePathOps.ts.
  4. Add the registry-layer guards in operations.ts (doCopyAndUpdateLinks short-circuit, distributeToSecondaryTargets per-target skip, unconditional injection in buildCopyOptions).
  5. 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 (resolveWorkingPathOverridevalidateAllRegistries 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.tsvalidateAllRegistries(registries, workingPath) appends overlap errors
    • apps/pair-cli/src/registry/resolver.tsConfig.working_path?: string
    • apps/pair-cli/src/registry/index.ts — re-export the new module's public API
    • apps/pair-cli/src/config/loader.tsvalidateConfig 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.tssetupInstallContext/setupUpdateContext validate before executing
    • apps/pair-cli/src/commands/update-link/handler.tsverifyKB 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:

  1. Add working-area.ts with path resolution (resolveWorkingPathOverride, resolveWorkingPath) and overlap primitives (isWithinPath, pathsOverlap, detectWorkingPathOverlap), case-folding on darwin/win32.
  2. Extend validateAllRegistries to accept workingPath and append detectWorkingPathOverlap errors after existing registry/overlap errors.
  3. 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).
  4. 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:

  1. Write working-area.md covering overview, structure, exclusion rule (referencing T1), override mechanism and validation (referencing T2), external-KB applicability, and skill integration table.
  2. Link the new doc from collaboration/README.md's new "Operational Conventions" section.
  3. Update the three apps/website doc pages (concepts, kb-structure, configuration) to reflect the same convention for the public docs site.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    user storyWork item representing a user story

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions