Skip to content

CLAUDE_CONFIG_DIR + PAI_DIR two-root split (v4.0.3+)#103

Closed
virtualian wants to merge 8 commits intomainfrom
100-claude-config-dir-pai-dir-separation
Closed

CLAUDE_CONFIG_DIR + PAI_DIR two-root split (v4.0.3+)#103
virtualian wants to merge 8 commits intomainfrom
100-claude-config-dir-pai-dir-separation

Conversation

@virtualian
Copy link
Copy Markdown
Owner

Summary

  • Introduces CLAUDE_CONFIG_DIR / PAI_DIR two-root separation in the Releases/v4.0.3+/ release template
  • CLAUDE_CONFIG_DIR (~/.claude) holds Claude Code's own config — settings.json, sessions, projects, CC's CLAUDE.md
  • PAI_DIR (~/.pai) holds the PAI install — hooks, skills, agents, PAI code, Algorithm, VoiceServer, MEMORY, USER
  • New paths.ts primitives: getConfigDir(), getPaiDir(), configPath(), codePath(); ~20 hooks migrated to use them
  • MEMORY path construction fully centralized via codePath('MEMORY', ...) across LoadContext, prd-utils, UpdateCounts, RelationshipMemory, change-detection, LastResponseCache, learning-readback, tab-setter, VoiceNotification

Corrections made during branch development

  • ebc8130 initially misplaced PAI hooks under CLAUDE_CONFIG_DIR. Reverted in 39ea6b1 — PAI hooks belong under PAI_DIR (settings.json hook commands, getHooksDir(), UpdateCounts countHooks caller, DocCrossRefIntegrity HOOKS_DIR, .pai-protected.json allow-list).
  • ebc8130 also bundled unannounced deletions of ~606 lines: CorrectionMode fast-path + BehavioralSignal capture subsystem in RatingCapture.hook.ts (-446), loadLatestSynthesis in learning-readback.ts (-160). Restored in 42f70b4.
  • Bug fixes in 42f70b4: missing `mkdirSync` in LastResponseCache (silent ENOENT on fresh install); broken `.replace()` prefix in prd-utils after the split; MEMORY-wrong-root bugs in UpdateCounts (`countHooks`, `refreshUsageCache`) and RelationshipMemory (`ensureRelationshipDir`); hardcoded `~/.claude/settings.json` path in VoiceCompletion; stale docs in paths.ts and settings.json `_env`.
  • c7f304c eliminates the residual `paiDir` parameter chain in LoadContext and learning-readback — 7 helper functions now use `codePath()` directly, removing the last legacy pattern.

Test plan

  • `bun build` clean on all touched TypeScript hook files (verified locally)
  • Run a session under non-default env vars (`CLAUDE_CONFIG_DIR=/tmp/ctest PAI_DIR=/tmp/ptest`) and confirm writes land in the correct roots
  • Verify every `settings.json → hooks.command` entry references `${PAI_DIR}/hooks/*`
  • Confirm MEMORY writes land in `$PAI_DIR/MEMORY/` across all writing hooks (WorkCompletion, RatingCapture, RelationshipMemory, VoiceNotification, UpdateCounts, LoadContext)
  • Verify dashboard `prd` field in `work.json` parses correctly (relative-path format preserved)

Known deferred

  • MEMORY migration for existing v4.0.2 → v4.0.3+ upgraders — not addressed here; needs separate installer logic to detect and move `/.claude/MEMORY` → `/.pai/MEMORY` for anyone upgrading from a pre-split release
  • `statusLine.command` — still `$CLAUDE_CONFIG_DIR/statusline-command.sh` because the physical file currently lives at `~/.claude/statusline-command.sh`; whether to move it under PAI_DIR is a separate decision
  • `ebc8130` commit message — still describes only the path-separation intent, not the unannounced deletions it also made; left as-is (no force-push / history rewrite); `42f70b4` serves as the narrative record for the restoration

…template

Split single PAI_DIR root into CONFIG (hooks, MEMORY, settings) and
CODE (PAI Tools, Algorithm, skills, agents) roots. Updates paths.ts,
settings.json, 20 hooks, and 170+ docs/skills with new path scheme.
MEMORY is PAI-specific state, not CC config. Reclassify all
configPath('MEMORY') to codePath('MEMORY') and update docs
from ~/.claude/MEMORY to ~/.pai/MEMORY.
VoiceNotification, tab-setter, and prd-utils only imported
configPath — add codePath to their import statements.
Phase 4 of issue #100. Updates 749 path references across 185 files
to reflect the CLAUDE_CONFIG_DIR + PAI_DIR two-root separation.
CC-native paths (history.jsonl, commands/, projects/) preserved.
After ebc8130 (two-root separation) and 5cc0656 (MEMORY to PAI root),
eight hook files still bypassed paths.ts, using ad-hoc fallbacks like
"process.env.PAI_DIR || join(HOME, '.claude')" or hardcoded
"$HOME/.claude/settings.json". With defaults they read/wrote MEMORY
under ~/.claude while migrated hooks used ~/.pai, producing a silent
split-brain where neither half saw the other's data.

All eight now route through codePath/getSettingsPath:

- LastResponseCache.hook.ts: last-response.txt via codePath
- RatingCapture.hook.ts: SIGNALS/RATINGS/LAST_RESPONSE and learningsDir
- SessionCleanup.hook.ts: STATE_DIR, WORK_DIR
- WorkCompletionLearning.hook.ts: STATE_DIR, WORK_DIR, LEARNING_DIR
- lib/notifications.ts: loadNtfyConfig reads getSettingsPath()
- lib/identity.ts: SETTINGS_PATH = getSettingsPath()
- KittyEnvPersist.hook.ts: kitty-env stateDir via codePath
- LoadContext.hook.ts: MEMORY/RELATIONSHIP lookup, loadLearningDigest,
  loadWisdomFrames, loadFailurePatterns, loadSignalTrends, and
  checkActiveProgress now all receive paiDir (not configDir); dropped
  now-dead configDir param from loadRelationshipContext
Path-separation fixes:
- LastResponseCache: mkdirSync before writeFileSync (ENOENT on fresh install)
- prd-utils: swap broken configPath() replace prefix to codePath()
- paths.ts: fix stale docstring; deprecate paiPath() (semantic flip to ~/.pai)
- settings.json _env: correct stale MEMORY-under-CONFIG claim
- UpdateCounts: route MEMORY paths to PAI root; simplify refreshUsageCache
  (was reading/writing ~/.claude/MEMORY/* after 5cc0656 moved MEMORY)
- VoiceCompletion: replace hardcoded settings path with getSettingsPath()
- LoadContext: five raw join(paiDir, ...) swapped to codePath(); rename
  misleading loadSettings parameter; extract sessionProgressPath
- VoiceNotification, tab-setter: drop dead configPath imports
- .pai-protected.json: fix stale Kai workflow comment

Revert of unannounced deletions from ebc8130:
- RatingCapture: restore 446 lines (CorrectionMode fast-path, BehavioralSignal,
  BEHAVIORAL_FEEDBACK_STATE subsystem); keep only the load-bearing dynamic
  import swap for inference/captureFailure.
- learning-readback: restore 160 lines (loadLatestSynthesis). Identical to
  main - ebc8130 made no legitimate path-sep changes to this file.

Scoped branch delta: -538 -> +59 net.
ebc8130 rewrote 25 hook command paths in settings.json from
${PAI_DIR}/hooks/* to ${CLAUDE_CONFIG_DIR}/hooks/*, and rewired
paths.ts getHooksDir() and several path constructions in hooks to
use configPath('hooks'). This was the wrong model: CLAUDE_CONFIG_DIR
is Claude Code's own config root (sessions, projects, CC's own
settings.json), while PAI's runtime (hooks, skills, agents, MEMORY,
PAI code) lives under PAI_DIR. Runtime install at ~/.pai/hooks/
confirms the correct placement.

42f70b4 inherited the wrong model in two doc strings; corrected here.

settings.json:
- 25 hook command paths: ${CLAUDE_CONFIG_DIR}/hooks/* -> ${PAI_DIR}/hooks/*
- _env.CLAUDE_CONFIG_DIR description corrected to show CC's own
  files in CONFIG root and PAI install in PAI root
- statusLine line 250 left alone (physical file at ~/.claude/;
  ambiguous whether to move it, separate decision)

paths.ts:
- getHooksDir(): configPath('hooks') -> codePath('hooks')
- Module docstring: hooks moved to PAI root listing

DocCrossRefIntegrity.ts:
- HOOKS_DIR: configPath('hooks') -> codePath('hooks')

UpdateCounts.ts:
- countHooks(configDir) -> countHooks(paiDir)
- Detached spawn scriptPath: join(configDir, 'hooks', ...) ->
  join(paiDir, 'hooks', ...)

RelationshipMemory.hook.ts:
- ensureRelationshipDir(configDir) -> ensureRelationshipDir(getPaiDir())
  (was writing daily notes to ~/.claude/MEMORY/RELATIONSHIP/ which
  doesn't exist after 5cc0656 moved MEMORY to PAI root)

change-detection.ts:
- STATE_FILE: join(CONFIG_DIR, 'MEMORY', 'STATE', ...) ->
  join(PAI_DIR, 'MEMORY', 'STATE', ...)
- Remove dead getConfigDir import and CONFIG_DIR constant

.pai-protected.json:
- Remove stale allow-list entries ~/.claude/skills and ~/.claude/hooks
  (both moved to PAI root in the corrected model)
…pers

After 42f70b4 modernized LoadContext.hook.ts to use codePath() for MEMORY
path construction, the paiDir variable in main() existed only to pass
through to helper functions that still took it as a param for raw
join(paiDir, 'MEMORY', ...) calls. Rewriting those helpers to call
codePath() directly eliminates the whole chain - the param, the variable,
and the getPaiDir import.

LoadContext.hook.ts:
- loadStartupFiles(paiDir, settings) -> loadStartupFiles(settings);
  body: join(paiDir, relPath) -> codePath(relPath)
- Delete const paiDir = getPaiDir(); from main()
- Drop paiDir arg from 5 call sites (loadStartupFiles +
  loadLearningDigest + loadWisdomFrames + loadFailurePatterns +
  loadSignalTrends)
- Remove getPaiDir from paths import

learning-readback.ts:
- Add codePath import
- 6 exported functions (loadLearningDigest, loadWisdomFrames,
  loadFailurePatterns, loadSignalTrends, loadLatestSynthesis,
  loadBehavioralTrends) lose their paiDir parameter
- Internal helper getRecentLearnings(baseDir, subdir, count) ->
  getRecentLearnings(subdir, count); updated 2 internal call sites
- 8 join(paiDir, 'MEMORY', ...) sites rewritten as codePath()

Symmetric +33/-33. Pure pattern modernization - no behavior change.
virtualian added a commit that referenced this pull request Apr 13, 2026
Supersedes PR #103. Incorporates the 4 correction commits from
branch 100-claude-config-dir-pai-dir-separation (b3e4e49, 42f70b4,
39ea6b1, c7f304c) as a single focused refactor on top of the
CLAUDE_CONFIG_DIR + PAI_DIR split that landed via #101 and the
CorrectionMode + loadLatestSynthesis restore that landed via #104.

Path separation fixes
---------------------
- settings.json: revert 13 hook commands from CLAUDE_CONFIG_DIR back
  to PAI_DIR (the hook-root inversion originally introduced by ebc8130)
- paths.ts: getHooksDir() returns codePath('hooks') not configPath('hooks'),
  docstring rewrites for the two-root contract
- LoadContext.hook.ts: eliminate paiDir parameter chain across 5 helpers,
  route 5 raw join(paiDir, ...) calls through codePath(), rename
  misleading loadSettings param, extract sessionProgressPath
- UpdateCounts.ts: countHooks and refreshUsageCache now read MEMORY from
  the PAI root instead of the CONFIG root
- RelationshipMemory.hook.ts: ensureRelationshipDir fixed for the split
- LastResponseCache.hook.ts: mkdirSync before writeFileSync (silent
  ENOENT on fresh install)
- prd-utils.ts: correct replace() prefix from configPath to codePath
- VoiceCompletion.hook.ts: replace hardcoded ~/.claude/settings.json
  with getSettingsPath()
- VoiceNotification.ts, tab-setter.ts: drop dead configPath imports
- DocCrossRefIntegrity.ts, change-detection.ts, identity.ts,
  notifications.ts, SessionCleanup.hook.ts, KittyEnvPersist.hook.ts,
  WorkCompletionLearning.hook.ts: small codePath / configPath cleanups
- .pai-protected.json: correct stale comment

Deprecated alias deletion
-------------------------
- paths.ts: remove the paiPath() function entirely. The semantic flip
  in v4.0.3 (pre-split ~/.claude/x -> post-split ~/.pai/x) is exactly
  the class of change where a silent alias is a landmine, not a safety
  net. The compiler now catches any revenant caller. Zero code callers
  existed when this commit was written.
- SYSTEM_USER_EXTENDABILITY.md: update 4 code examples from paiPath()
  to codePath() so the documentation matches the public API.

MEMORY migration release note (manual upgrade path)
---------------------------------------------------
- Releases/v4.0.3+/README.md: add a "Breaking Changes" subsection
  explaining the two-root split and add the required MEMORY migration
  step (`mv ~/.claude/MEMORY ~/.pai/MEMORY`) to the "Upgrading from
  v4.0.x" recipe. An automated migrator is a follow-up - see the
  tracking issue.

Deferred (intentional)
----------------------
- statusLine.command still points at ${CLAUDE_CONFIG_DIR}/statusline-command.sh
  because the physical file currently lives at ~/.claude/statusline-command.sh.
  Relocating the script is a separate decision that does not belong in
  this refactor.

Verification
------------
- bun build --target=bun clean on all 17 touched hook TypeScript files
- settings.json parses as valid JSON
- env-var resolution tested under CLAUDE_CONFIG_DIR=/tmp/ctest PAI_DIR=/tmp/ptest:
  10/10 path primitives resolve to the expected roots
- settings.json hook-command audit: 0 references to CLAUDE_CONFIG_DIR/hooks,
  24 references to PAI_DIR/hooks
- MEMORY routing audit: 0 configPath('MEMORY'), 45 codePath('MEMORY'),
  0 raw ~/.claude/MEMORY references in hook sources
- CI symbol guard (merged in #105) passes all 13 required-symbol checks
  against this commit
@virtualian
Copy link
Copy Markdown
Owner Author

Superseded by #106.

Summary of what happened to this PR:

  1. Its first four commits (ebc8130, 5cc0656, e51ec55, ff466e3) landed on main via the squash-merge in Implement CLAUDE_CONFIG_DIR + PAI_DIR two-root separation #101, which unfortunately also shipped the 606-line CorrectionMode / BehavioralSignal / loadLatestSynthesis deletion that ebc8130 silently bundled in.
  2. PR Restore CorrectionMode fast-path and loadLatestSynthesis (hotfix) #104 restored those 606 lines as a narrow 2-file hotfix.
  3. PR Add CI symbol guard for critical hook files #105 added a CI symbol-presence guard so the same class of failure cannot recur.
  4. After Implement CLAUDE_CONFIG_DIR + PAI_DIR two-root separation #101, Restore CorrectionMode fast-path and loadLatestSynthesis (hotfix) #104, and Add CI symbol guard for critical hook files #105 landed, this PR's true semantic delta collapsed from 403 files to 18 files. Because this branch was never rebased, GitHub's merge-base-relative diff kept showing the old 403-file view and the merge state stayed CONFLICTING — a rebase was tried in a scratch worktree and produced phantom conflicts (git's patch-ID matching cannot resolve individual commits against Implement CLAUDE_CONFIG_DIR + PAI_DIR two-root separation #101's squash).
  5. PR Complete two-root path separation (supersedes #103) #106 is the rebased outcome: same 4 correction commits (b3e4e49, 42f70b4, 39ea6b1, c7f304c) applied via file-copy reconstruction, plus three architectural decisions that were pending here: delete paiPath() outright (was deprecated), defer statusLine.command relocation, and ship the MEMORY migration as a documented manual step with a tracking issue follow-up.

Closing this PR in favour of #106.

@virtualian virtualian closed this Apr 13, 2026
virtualian added a commit that referenced this pull request Apr 13, 2026
)

Supersedes PR #103. Incorporates the 4 correction commits from
branch 100-claude-config-dir-pai-dir-separation (b3e4e49, 42f70b4,
39ea6b1, c7f304c) as a single focused refactor on top of the
CLAUDE_CONFIG_DIR + PAI_DIR split that landed via #101 and the
CorrectionMode + loadLatestSynthesis restore that landed via #104.

Path separation fixes
---------------------
- settings.json: revert 13 hook commands from CLAUDE_CONFIG_DIR back
  to PAI_DIR (the hook-root inversion originally introduced by ebc8130)
- paths.ts: getHooksDir() returns codePath('hooks') not configPath('hooks'),
  docstring rewrites for the two-root contract
- LoadContext.hook.ts: eliminate paiDir parameter chain across 5 helpers,
  route 5 raw join(paiDir, ...) calls through codePath(), rename
  misleading loadSettings param, extract sessionProgressPath
- UpdateCounts.ts: countHooks and refreshUsageCache now read MEMORY from
  the PAI root instead of the CONFIG root
- RelationshipMemory.hook.ts: ensureRelationshipDir fixed for the split
- LastResponseCache.hook.ts: mkdirSync before writeFileSync (silent
  ENOENT on fresh install)
- prd-utils.ts: correct replace() prefix from configPath to codePath
- VoiceCompletion.hook.ts: replace hardcoded ~/.claude/settings.json
  with getSettingsPath()
- VoiceNotification.ts, tab-setter.ts: drop dead configPath imports
- DocCrossRefIntegrity.ts, change-detection.ts, identity.ts,
  notifications.ts, SessionCleanup.hook.ts, KittyEnvPersist.hook.ts,
  WorkCompletionLearning.hook.ts: small codePath / configPath cleanups
- .pai-protected.json: correct stale comment

Deprecated alias deletion
-------------------------
- paths.ts: remove the paiPath() function entirely. The semantic flip
  in v4.0.3 (pre-split ~/.claude/x -> post-split ~/.pai/x) is exactly
  the class of change where a silent alias is a landmine, not a safety
  net. The compiler now catches any revenant caller. Zero code callers
  existed when this commit was written.
- SYSTEM_USER_EXTENDABILITY.md: update 4 code examples from paiPath()
  to codePath() so the documentation matches the public API.

MEMORY migration release note (manual upgrade path)
---------------------------------------------------
- Releases/v4.0.3+/README.md: add a "Breaking Changes" subsection
  explaining the two-root split and add the required MEMORY migration
  step (`mv ~/.claude/MEMORY ~/.pai/MEMORY`) to the "Upgrading from
  v4.0.x" recipe. An automated migrator is a follow-up - see the
  tracking issue.

Deferred (intentional)
----------------------
- statusLine.command still points at ${CLAUDE_CONFIG_DIR}/statusline-command.sh
  because the physical file currently lives at ~/.claude/statusline-command.sh.
  Relocating the script is a separate decision that does not belong in
  this refactor.

Verification
------------
- bun build --target=bun clean on all 17 touched hook TypeScript files
- settings.json parses as valid JSON
- env-var resolution tested under CLAUDE_CONFIG_DIR=/tmp/ctest PAI_DIR=/tmp/ptest:
  10/10 path primitives resolve to the expected roots
- settings.json hook-command audit: 0 references to CLAUDE_CONFIG_DIR/hooks,
  24 references to PAI_DIR/hooks
- MEMORY routing audit: 0 configPath('MEMORY'), 45 codePath('MEMORY'),
  0 raw ~/.claude/MEMORY references in hook sources
- CI symbol guard (merged in #105) passes all 13 required-symbol checks
  against this commit
@virtualian virtualian deleted the 100-claude-config-dir-pai-dir-separation branch April 14, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant