diff --git a/skills/continuous-learning-v2/scripts/instinct-cli.py b/skills/continuous-learning-v2/scripts/instinct-cli.py index 710f4c69..8d87513a 100755 --- a/skills/continuous-learning-v2/scripts/instinct-cli.py +++ b/skills/continuous-learning-v2/scripts/instinct-cli.py @@ -703,10 +703,47 @@ def cmd_status(args) -> int: days_left = max(0, PENDING_TTL_DAYS - item["age_days"]) print(f" - {item['name']} ({days_left}d remaining)") + # Legacy data warning + _warn_legacy_data() + print(f"\n{'='*60}\n") return 0 +def _warn_legacy_data() -> None: + """Warn if legacy ~/.claude/homunculus/ contains data while the active + path has moved to the XDG directory.""" + legacy_dir = Path.home() / ".claude" / "homunculus" + if legacy_dir == HOMUNCULUS_DIR: + return # CLV2_HOMUNCULUS_DIR explicitly points at the legacy path + if not legacy_dir.is_dir(): + return + + # Count substantive files (skip empty dirs and the directory itself) + try: + legacy_files = [f for f in legacy_dir.rglob("*") if f.is_file()] + except (PermissionError, OSError): + print(f"\n Note: legacy directory exists but cannot be read: {legacy_dir}", file=sys.stderr) + return + if not legacy_files: + return + + migrate_script = Path(__file__).resolve().parent / "migrate-homunculus.sh" + + print(f"\n{'!'*60}") + print(" LEGACY DATA DETECTED") + print(f"{'!'*60}") + print(f" Found {len(legacy_files)} file(s) in legacy path:") + print(f" {legacy_dir}") + print(" Active data directory:") + print(f" {HOMUNCULUS_DIR}") + print() + print(" Run the migration script to move your data:") + print(f' bash "{migrate_script}"') + print(f" Or set CLV2_HOMUNCULUS_DIR={legacy_dir} to use the legacy path.") + print(f"{'!'*60}\n") + + def _print_instincts_by_domain(instincts: list[dict]) -> None: """Helper to print instincts grouped by domain.""" by_domain = defaultdict(list) diff --git a/tests/scripts/instinct-cli-projects.test.js b/tests/scripts/instinct-cli-projects.test.js index 3ebd8651..d2d95161 100644 --- a/tests/scripts/instinct-cli-projects.test.js +++ b/tests/scripts/instinct-cli-projects.test.js @@ -219,6 +219,65 @@ test('projects merge deduplicates instincts, appends observations, and removes s } }); +test('status warns when legacy ~/.claude/homunculus contains files', () => { + const root = createTempDir(); + try { + const legacyDir = path.join(root, 'home', '.claude', 'homunculus', 'instincts', 'personal'); + fs.mkdirSync(legacyDir, { recursive: true }); + fs.writeFileSync(path.join(legacyDir, 'old-instinct.yaml'), '---\nid: old\n---\nOld instinct.\n'); + + const result = runCli(root, ['status']); + assert.strictEqual(result.status, 0, result.stderr); + assert.match(result.stdout, /LEGACY DATA DETECTED/); + assert.match(result.stdout, /legacy path/i); + assert.match(result.stdout, /migration script/i); + } finally { + cleanupDir(root); + } +}); + +test('status does not warn when legacy dir is empty', () => { + const root = createTempDir(); + try { + const legacyDir = path.join(root, 'home', '.claude', 'homunculus'); + fs.mkdirSync(legacyDir, { recursive: true }); + + const result = runCli(root, ['status']); + assert.strictEqual(result.status, 0, result.stderr); + assert.doesNotMatch(result.stdout, /LEGACY DATA DETECTED/); + } finally { + cleanupDir(root); + } +}); + +test('status does not warn when no legacy dir exists', () => { + const root = createTempDir(); + try { + const result = runCli(root, ['status']); + assert.strictEqual(result.status, 0, result.stderr); + assert.doesNotMatch(result.stdout, /LEGACY DATA DETECTED/); + } finally { + cleanupDir(root); + } +}); + +test('status does not warn when CLV2_HOMUNCULUS_DIR points at legacy path', () => { + const root = createTempDir(); + try { + const legacyDir = path.join(root, 'home', '.claude', 'homunculus', 'instincts', 'personal'); + fs.mkdirSync(legacyDir, { recursive: true }); + fs.writeFileSync(path.join(legacyDir, 'active.yaml'), '---\nid: active\n---\nActive.\n'); + + const result = runCli(root, ['status'], { + env: { CLV2_HOMUNCULUS_DIR: path.join(root, 'home', '.claude', 'homunculus') }, + }); + assert.strictEqual(result.status, 0, result.stderr); + assert.doesNotMatch(result.stdout, /LEGACY DATA DETECTED/); + } finally { + cleanupDir(root); + } +}); + test('status migrates legacy no-remote linked worktree project dirs to main worktree id', () => { const root = createTempDir(); const repoParent = createTempDir();