projecta-rrr 1.18.0 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to RRR will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.18.1] - 2026-01-30
8
+
9
+ ### Fixed
10
+
11
+ - **Drift detection now updates HUD state** - PostToolUse hook was writing to `.planning/.drift-status.json` but statusline reads from `~/.claude/rrr/hud-state.json`. Now updates both.
12
+ - **Cache stats hook missing from SessionStart** - Added `rrr-update-cache-stats.sh` to SessionStart hooks for IDX % refresh
13
+ - **IDX showing file count instead of hit rate** - Changed to show cache hit rate percentage
14
+ - **Index path detection** - Fixed to find LanceDB at actual location (`~/.claude/rrr/lib/search/.rrr/search/lancedb/`)
15
+
16
+ ### Added
17
+
18
+ - **Scheduled indexing** (macOS) - Launchd agent runs semantic + session indexing every 4 hours
19
+ - **Test results capture hook** - `rrr-capture-test-results.sh` updates HUD with test pass rate after `npm test`
20
+
7
21
  ## [1.18.0] - 2026-01-30
8
22
 
9
23
  ### Added
package/bin/install.js CHANGED
@@ -1359,6 +1359,27 @@ function install(isGlobal) {
1359
1359
  });
1360
1360
  console.log(` ${green}✓${reset} Configured HUD state sync hook`);
1361
1361
  }
1362
+
1363
+ // Add cache stats update hook (updates IDX % in statusline)
1364
+ const cacheStatsCommand = isGlobal
1365
+ ? '$HOME/.claude/hooks/rrr-update-cache-stats.sh'
1366
+ : `${localDirName}/hooks/rrr-update-cache-stats.sh`;
1367
+
1368
+ const hasCacheStatsHook = settings.hooks.SessionStart.some(entry =>
1369
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('rrr-update-cache-stats'))
1370
+ );
1371
+
1372
+ if (!hasCacheStatsHook && hookFileExists(claudeDir, 'rrr-update-cache-stats.sh')) {
1373
+ settings.hooks.SessionStart.push({
1374
+ hooks: [
1375
+ {
1376
+ type: 'command',
1377
+ command: cacheStatsCommand
1378
+ }
1379
+ ]
1380
+ });
1381
+ console.log(` ${green}✓${reset} Configured cache stats hook`);
1382
+ }
1362
1383
  }
1363
1384
 
1364
1385
  // PATCH-01: Configure PostToolUse hook for drift detection
@@ -1590,6 +1611,86 @@ function install(isGlobal) {
1590
1611
  /**
1591
1612
  * Print doctor summary showing where hooks were installed and configured paths
1592
1613
  */
1614
+ /**
1615
+ * Setup scheduled indexing via macOS launchd
1616
+ * Runs semantic index and session indexing every 4 hours
1617
+ */
1618
+ function setupScheduledIndexing() {
1619
+ const launchAgentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
1620
+ const plistPath = path.join(launchAgentsDir, 'com.projecta.rrr-index.plist');
1621
+
1622
+ // Only setup if not already configured
1623
+ if (fs.existsSync(plistPath)) {
1624
+ return;
1625
+ }
1626
+
1627
+ try {
1628
+ // Ensure LaunchAgents directory exists
1629
+ if (!fs.existsSync(launchAgentsDir)) {
1630
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
1631
+ }
1632
+
1633
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1634
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1635
+ <plist version="1.0">
1636
+ <dict>
1637
+ <key>Label</key>
1638
+ <string>com.projecta.rrr-index</string>
1639
+ <key>ProgramArguments</key>
1640
+ <array>
1641
+ <string>/bin/bash</string>
1642
+ <string>-c</string>
1643
+ <string>
1644
+ # Run semantic index if grepai available
1645
+ if command -v grepai &amp;>/dev/null; then
1646
+ grepai index --quiet 2>/dev/null
1647
+ fi
1648
+
1649
+ # Run session indexing (Datasphere)
1650
+ INSTALLED_RRR=$(npm root -g 2>/dev/null)/projecta-rrr
1651
+ if [[ -f "$INSTALLED_RRR/scripts/index-sessions.sh" ]]; then
1652
+ bash "$INSTALLED_RRR/scripts/index-sessions.sh" --all --quiet 2>/dev/null
1653
+ fi
1654
+
1655
+ # Update cache stats for HUD
1656
+ ~/.claude/hooks/rrr-update-cache-stats.sh 2>/dev/null
1657
+ </string>
1658
+ </array>
1659
+ <key>StartInterval</key>
1660
+ <integer>14400</integer>
1661
+ <key>RunAtLoad</key>
1662
+ <true/>
1663
+ <key>StandardOutPath</key>
1664
+ <string>/tmp/rrr-index.log</string>
1665
+ <key>StandardErrorPath</key>
1666
+ <string>/tmp/rrr-index.err</string>
1667
+ <key>LowPriorityIO</key>
1668
+ <true/>
1669
+ <key>ProcessType</key>
1670
+ <string>Background</string>
1671
+ <key>EnvironmentVariables</key>
1672
+ <dict>
1673
+ <key>PATH</key>
1674
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
1675
+ </dict>
1676
+ </dict>
1677
+ </plist>`;
1678
+
1679
+ fs.writeFileSync(plistPath, plistContent);
1680
+
1681
+ // Load the agent
1682
+ const { execSync } = require('child_process');
1683
+ try {
1684
+ execSync(`launchctl load "${plistPath}"`, { stdio: 'pipe' });
1685
+ console.log(` ${green}✓${reset} Configured scheduled indexing (every 4 hours)`);
1686
+ } catch (e) {
1687
+ // Agent might already be loaded
1688
+ }
1689
+ } catch (e) {
1690
+ // Silent fail - scheduled indexing is optional
1691
+ }
1692
+ }
1693
+
1593
1694
  function printDoctorSummary(claudeDir, settings, localDirName, isGlobal) {
1594
1695
  console.log(`\n ${cyan}Hook Configuration Summary:${reset}`);
1595
1696
 
@@ -1727,6 +1828,11 @@ function finishInstall(settingsPath, settings, statuslineCommand, notifyCommand,
1727
1828
  }
1728
1829
  }
1729
1830
 
1831
+ // Setup scheduled indexing (macOS launchd) - runs every 4 hours
1832
+ if (process.platform === 'darwin' && isGlobal) {
1833
+ setupScheduledIndexing();
1834
+ }
1835
+
1730
1836
  console.log(`
1731
1837
  ${green}Done!${reset}
1732
1838
 
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # rrr-capture-test-results.sh - Capture test results and update HUD
4
+ #
5
+ # Usage: npm test 2>&1 | tee /tmp/test-output.txt; rrr-capture-test-results.sh
6
+ #
7
+
8
+ set +e
9
+
10
+ HUD_FILE="$HOME/.claude/rrr/hud-state.json"
11
+ TEST_OUTPUT="${1:-/tmp/test-output.txt}"
12
+
13
+ if [[ ! -f "$TEST_OUTPUT" ]]; then
14
+ exit 0
15
+ fi
16
+
17
+ # Parse Jest output
18
+ if grep -q "Tests:" "$TEST_OUTPUT"; then
19
+ # Jest format: Tests: X failed, Y passed, Z total
20
+ passed=$(grep "Tests:" "$TEST_OUTPUT" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' || echo "0")
21
+ failed=$(grep "Tests:" "$TEST_OUTPUT" | grep -oE '[0-9]+ failed' | grep -oE '[0-9]+' || echo "0")
22
+ total=$((passed + failed))
23
+
24
+ if [[ "$total" -gt 0 ]]; then
25
+ pass_rate=$((passed * 100 / total))
26
+
27
+ # Update HUD state
28
+ if [[ -f "$HUD_FILE" ]] && command -v jq &>/dev/null; then
29
+ jq --argjson passed "$passed" \
30
+ --argjson failed "$failed" \
31
+ --argjson total "$total" \
32
+ --argjson rate "$pass_rate" '
33
+ .tests.passed = $passed |
34
+ .tests.failed = $failed |
35
+ .tests.total = $total |
36
+ .tests.pass_rate = $rate
37
+ ' "$HUD_FILE" > "${HUD_FILE}.tmp" && mv "${HUD_FILE}.tmp" "$HUD_FILE"
38
+
39
+ echo "Updated HUD: ${passed}/${total} tests passed (${pass_rate}%)"
40
+ fi
41
+ fi
42
+ fi
43
+
44
+ exit 0
@@ -116,6 +116,21 @@ if [ -n "$DRIFT_RESULT" ]; then
116
116
  echo "$DRIFT_RESULT" > "$DRIFT_STATUS_FILE" 2>/dev/null
117
117
  fi
118
118
 
119
+ # Update HUD state with drift info (so statusline can read it)
120
+ HUD_STATE_FILE="${CLAUDE_DIR}/rrr/hud-state.json"
121
+ if [ -f "$HUD_STATE_FILE" ] && command -v jq &>/dev/null; then
122
+ # Count changed files
123
+ FILE_COUNT=$(echo "$FILES" | tr ',' '\n' | wc -l | tr -d ' ')
124
+
125
+ # Update drift section in hud-state.json
126
+ jq --argjson count "$FILE_COUNT" --arg files "$FILES" '
127
+ .drift.detected = ($count > 0) |
128
+ .drift.files_changed = $count |
129
+ .drift.details = (if $files != "" then ($files | split(",")[0:5]) else [] end) |
130
+ .status = (if $count > 5 then "drifting" else .status end)
131
+ ' "$HUD_STATE_FILE" > "${HUD_STATE_FILE}.tmp" && mv "${HUD_STATE_FILE}.tmp" "$HUD_STATE_FILE"
132
+ fi
133
+
119
134
  # ============================================================================
120
135
  # Episode Emission
121
136
  # ============================================================================
@@ -21,11 +21,16 @@ if [[ -f "$MCP_SERVER" ]]; then
21
21
  const path = require('path');
22
22
  const fs = require('fs');
23
23
 
24
- // Find the index database
25
- const indexDir = path.join(process.env.HOME, '.claude', 'rrr', '.semantic-index');
26
- const dbPath = path.join(indexDir, 'rrr-search.lance');
24
+ // Find the index database (check multiple locations)
25
+ const possiblePaths = [
26
+ path.join(process.env.HOME, '.claude', 'rrr', 'lib', 'search', '.rrr', 'search', 'lancedb', 'file_index.lance'),
27
+ path.join(process.env.HOME, '.claude', 'rrr', '.semantic-index', 'rrr-search.lance'),
28
+ path.join(process.env.HOME, '.grepai', 'index.lance')
29
+ ];
30
+ const dbPath = possiblePaths.find(p => fs.existsSync(p));
31
+ const indexDir = dbPath ? path.dirname(dbPath) : null;
27
32
 
28
- if (!fs.existsSync(dbPath)) {
33
+ if (!dbPath || !indexDir) {
29
34
  // No index yet
30
35
  console.log(JSON.stringify({
31
36
  fileCount: 0,
@@ -47,11 +52,27 @@ const lastUpdated = stats.mtime.toISOString();
47
52
  let fileCount = 0;
48
53
  let chunkCount = 0;
49
54
  try {
50
- const manifestPath = path.join(indexDir, 'manifest.json');
51
- if (fs.existsSync(manifestPath)) {
52
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
53
- fileCount = manifest.files ? Object.keys(manifest.files).length : 0;
54
- chunkCount = manifest.totalChunks || fileCount * 5; // estimate
55
+ // Try manifest.json in various locations
56
+ const manifestPaths = [
57
+ path.join(indexDir, 'manifest.json'),
58
+ path.join(indexDir, '..', 'manifest.json'),
59
+ path.join(indexDir, '..', '..', 'manifest.json')
60
+ ];
61
+ for (const manifestPath of manifestPaths) {
62
+ if (fs.existsSync(manifestPath)) {
63
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
64
+ fileCount = manifest.files ? Object.keys(manifest.files).length : 0;
65
+ chunkCount = manifest.totalChunks || fileCount * 5;
66
+ break;
67
+ }
68
+ }
69
+ // Fallback: count .lance data files
70
+ if (fileCount === 0) {
71
+ const dataDir = path.join(dbPath, 'data');
72
+ if (fs.existsSync(dataDir)) {
73
+ fileCount = fs.readdirSync(dataDir).filter(f => f.endsWith('.lance')).length * 100; // estimate
74
+ chunkCount = fileCount * 5;
75
+ }
55
76
  }
56
77
  } catch (e) {}
57
78
 
@@ -82,27 +82,18 @@ if [[ -f "$HUD_FILE" ]]; then
82
82
  fi
83
83
  fi
84
84
 
85
- # Show index health: [IDX 2.2k OK] (ASCII only for Claude Code compatibility)
86
- file_count=$(jq -r '.fileCount // 0' "$CACHE_FILE" 2>/dev/null)
85
+ # Show index health as hit rate percentage (ASCII only for Claude Code compatibility)
87
86
  healthy=$(jq -r '.healthy // false' "$CACHE_FILE" 2>/dev/null)
88
87
 
89
- if [[ "$file_count" -gt 0 ]]; then
90
- # Format file count nicely (2207 -> 2.2k)
91
- if [[ "$file_count" -gt 1000 ]]; then
92
- file_display=$(echo "scale=1; $file_count / 1000" | bc 2>/dev/null || echo "$file_count")
93
- file_display="${file_display}k"
94
- else
95
- file_display="$file_count"
96
- fi
97
-
98
- # Show health indicator (ASCII)
88
+ if [[ "$cache_rate" -gt 0 ]]; then
89
+ # Show hit rate percentage
99
90
  if [[ "$healthy" == "true" ]]; then
100
- brain="IDX:${file_display}"
91
+ brain="IDX:${cache_rate}%"
101
92
  else
102
- brain="IDX:${file_display}!"
93
+ brain="IDX:${cache_rate}%!"
103
94
  fi
104
95
  else
105
- brain="IDX:--"
96
+ brain="IDX:--%"
106
97
  fi
107
98
 
108
99
  # ────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projecta-rrr",
3
- "version": "1.18.0",
3
+ "version": "1.18.1",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
5
5
  "bin": {
6
6
  "projecta-rrr": "bin/install.js"