@claudemini/shit-cli 1.4.0 → 1.6.0

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/README.md CHANGED
@@ -2,69 +2,73 @@
2
2
 
3
3
  **S**ession-based **H**ook **I**ntelligence **T**racker
4
4
 
5
- A memory system for human-AI interactions, designed to provide reliable data support for code review automation.
5
+ A zero-dependency memory system for human-AI coding sessions. Tracks what happened, classifies intent and risk, and provides structured data for code review automation.
6
6
 
7
- ## Design Vision
7
+ Supports **Claude Code**, **Gemini CLI**, **Cursor**, and **OpenCode**.
8
8
 
9
- 1. **Human-AI Interaction Memory System** - Long-term memory, not temporary logs
10
- 2. **Code Review Bot Data Support** - Structured semantic data for intelligent code review
11
-
12
- See [DESIGN_PHILOSOPHY.md](./DESIGN_PHILOSOPHY.md) for detailed design rationale.
13
-
14
- ## Installation
9
+ ## Quick Start
15
10
 
16
11
  ```bash
17
- cd shit-cli
18
- npm link
19
- ```
12
+ npm install -g @claudemini/shit-cli
20
13
 
21
- Or use directly:
22
- ```bash
23
- node bin/shit.js <command>
14
+ cd /path/to/your/project
15
+ shit enable # Setup hooks + .shit-logs
16
+ # ... use Claude Code normally ...
17
+ shit list # See sessions
18
+ shit status # Check current session
24
19
  ```
25
20
 
26
- ## Usage
27
-
28
- ### Initialize hooks
21
+ ## Installation
29
22
 
30
23
  ```bash
31
- cd /path/to/your/project
32
- shit init
24
+ npm install -g @claudemini/shit-cli
33
25
  ```
34
26
 
35
- Registers all hooks in `.claude/settings.json` automatically.
36
-
37
- ### List sessions
27
+ Or use directly without installing:
38
28
 
39
29
  ```bash
40
- shit list
30
+ npx @claudemini/shit-cli <command>
41
31
  ```
42
32
 
43
- Output:
44
- ```
45
- 3 session(s):
33
+ ## Commands
46
34
 
47
- 1. f608c31e [bugfix] risk:medium
48
- Fix auth timeout by adjusting retry logic
49
- 45min | 42 events | 28 tools | 3 files | 0 errors
50
- 2/27/2026, 3:15:00 PM
35
+ ### Setup
51
36
 
52
- 2. a1b2c3d4 [feature] risk:low
53
- Add user profile endpoint
54
- 30min | 28 events | 15 tools | 2 files | 0 errors
55
- 2/26/2026, 10:30:00 AM
37
+ ```bash
38
+ shit enable # Enable for Claude Code (default)
39
+ shit enable gemini-cli # Enable for Gemini CLI
40
+ shit enable --all # Enable for all supported agents
41
+ shit enable --checkpoint # Also create checkpoints on git commit
42
+ shit disable # Remove hooks (keep data)
43
+ shit disable --clean # Remove hooks and all data
44
+ shit init # Low-level: register hooks in .claude/settings.json
56
45
  ```
57
46
 
58
- ### View session details
47
+ ### Session Tracking
59
48
 
60
49
  ```bash
61
- shit view f608c31e-453c-435a-b0e2-3116dc56ad71
62
- shit view f608c31e-453c-435a-b0e2-3116dc56ad71 --json # Include raw JSON
50
+ shit status # Show current session + git info
51
+ shit list # List all sessions with type, risk, intent
52
+ shit view <session-id> # View semantic session report
53
+ shit view <session-id> --json # Include raw JSON data
54
+ shit review [session-id] # Run structured code review from session data
55
+ shit review --json # Machine-readable findings (structured schema)
56
+ shit review --recent=3 --md # Aggregated Markdown report for PR comments
57
+ shit review --strict --fail-on=medium # CI gate by severity threshold
58
+ shit explain <session-id> # Human-friendly explanation of a session
59
+ shit explain <commit-sha> # Explain a commit via its checkpoint
63
60
  ```
64
61
 
65
- Output includes: intent, changes by category, tools, commands, review hints (tests run, build verified, config changed, etc.).
62
+ `shit review` options:
63
+ - `--recent=<n>` review latest `n` sessions (default `1`)
64
+ - `--all` review all sessions in `.shit-logs`
65
+ - `--min-severity=<info|low|medium|high|critical>` filter findings
66
+ - `--fail-on=<info|low|medium|high|critical>` strict-mode failure threshold (default `high`)
67
+ - `--strict` exit code `1` when findings reach `--fail-on`
68
+ - `--json` output structured JSON
69
+ - `--markdown` / `--md` output Markdown
66
70
 
67
- ### Query session memory
71
+ ### Cross-Session Queries
68
72
 
69
73
  ```bash
70
74
  shit query --recent=5 # Recent 5 sessions
@@ -74,70 +78,93 @@ shit query --risk=high # High-risk sessions
74
78
  shit query --type=feature --json # JSON output for bot consumption
75
79
  ```
76
80
 
77
- ### Shadow branches
81
+ ### Checkpoints & Recovery
78
82
 
79
83
  ```bash
80
- shit shadow # List shadow branches
81
- shit shadow info <branch> # Show branch details
84
+ shit checkpoints # List all checkpoints
85
+ shit commit # Manually create checkpoint for current HEAD
86
+ shit rewind <checkpoint> # Rollback to a checkpoint (git reset --hard)
87
+ shit rewind --interactive # Choose from available checkpoints
88
+ shit resume <checkpoint> # Restore session data from a checkpoint
89
+ shit reset --force # Delete checkpoint for current HEAD
82
90
  ```
83
91
 
84
- ### Clean old sessions
92
+ ### Maintenance
85
93
 
86
94
  ```bash
87
- shit clean --days=7 --dry-run # Preview
88
- shit clean --days=7 # Delete sessions older than 7 days
95
+ shit doctor # Diagnose issues (corrupted state, stuck sessions)
96
+ shit doctor --fix # Auto-fix detected issues
97
+ shit shadow # List shadow branches
98
+ shit shadow info <branch> # Show branch details
99
+ shit clean --days=7 --dry-run # Preview cleanup
100
+ shit clean --days=7 # Delete sessions older than 7 days
101
+ shit summarize <session-id> # Generate AI summary (requires API key)
89
102
  ```
90
103
 
91
- ## Commands
104
+ ## Command Reference
92
105
 
93
106
  | Command | Description |
94
107
  |---------|-------------|
95
- | `init` | Initialize hooks in .claude/settings.json |
96
- | `log <hook-type>` | Log a hook event from stdin (called by hooks) |
108
+ | `enable` | Enable shit-cli in repository (multi-agent support) |
109
+ | `disable` | Remove hooks, optionally clean data |
110
+ | `status` | Show current session and git info |
111
+ | `init` | Register hooks in .claude/settings.json |
112
+ | `log <type>` | Log a hook event from stdin (called by hooks) |
97
113
  | `list` | List all sessions with type, intent, risk |
98
- | `view <session-id> [--json]` | View semantic session report |
99
- | `query [options]` | Query session memory across sessions |
100
- | `shadow [info <branch>]` | List or inspect shadow branches |
101
- | `clean [--days=N] [--dry-run]` | Clean old sessions |
102
- | `help` | Show help |
103
-
104
- ## Architecture
114
+ | `view <id>` | View semantic session report |
115
+ | `review [id]` | Run structured code review (single or multi-session) |
116
+ | `query` | Query session memory across sessions |
117
+ | `explain <id>` | Human-friendly explanation of a session or commit |
118
+ | `commit` | Create checkpoint on git commit |
119
+ | `checkpoints` | List all checkpoints |
120
+ | `rewind <cp>` | Rollback to a checkpoint |
121
+ | `resume <cp>` | Resume session from a checkpoint |
122
+ | `reset` | Delete checkpoint for current HEAD |
123
+ | `summarize <id>` | Generate AI summary for a session |
124
+ | `doctor` | Diagnose and fix issues |
125
+ | `shadow` | List/inspect shadow branches |
126
+ | `clean` | Clean old sessions |
127
+
128
+ ## How It Works
105
129
 
106
130
  ```
107
- shit-cli/
108
- ├── bin/shit.js # CLI entry point
109
- ├── lib/
110
- │ ├── config.js # Shared config: getProjectRoot(), getLogDir(), toRelative()
111
- │ ├── extract.js # Semantic extraction: intent, changes, classification
112
- │ ├── report.js # Report generation: summary.json v2, summary.txt, metadata
113
- │ ├── session.js # Session state management + cross-session index
114
- │ ├── log.js # Event ingestion dispatcher (stdin → parse → extract → save)
115
- │ ├── init.js # shit init (hook registration)
116
- │ ├── list.js # shit list (semantic session listing)
117
- │ ├── view.js # shit view (semantic report display)
118
- │ ├── query.js # shit query (cross-session memory queries)
119
- │ ├── clean.js # shit clean (session cleanup)
120
- │ ├── shadow.js # shit shadow (branch listing)
121
- │ └── git-shadow.js # Git plumbing for shadow branches
131
+ Human <-> AI Agent (Claude Code, Gemini CLI, ...)
132
+ | (hooks)
133
+ Event Ingestion (log.js)
134
+ |
135
+ Semantic Extraction (extract.js)
136
+ |
137
+ Session State (session.js) + Reports (report.js)
138
+ |
139
+ Memory System (.shit-logs/ + index.json)
140
+ |
141
+ Code Review Bot / Human Queries
122
142
  ```
123
143
 
144
+ 1. **Ingestion** - Hooks fire on every agent event (tool use, prompts, session start/end). Events are appended to `events.jsonl`.
145
+ 2. **Extraction** - Each event updates incremental state. Intent, change categories, and risk are computed using rule-based pattern matching (zero latency, zero cost, fully offline).
146
+ 3. **Reports** - `summary.json` (bot-readable), `summary.txt` (human-readable), `context.md`, `metadata.json`, and `prompts.txt` are regenerated on every event.
147
+ 4. **Checkpoints** - On session end or git commit, session data is committed to an orphan git branch using plumbing commands (no working tree impact).
148
+
124
149
  ## Data Model
125
150
 
126
151
  ### Session Directory
127
152
 
128
153
  ```
129
154
  .shit-logs/
130
- ├── index.json # Cross-session index (file history, types)
155
+ ├── index.json # Cross-session index
131
156
  └── <session-id>/
132
157
  ├── events.jsonl # Raw hook events
133
158
  ├── state.json # Incremental processing state
134
- ├── summary.json # Bot data interface (v2 schema)
135
- ├── summary.txt # Human-readable semantic report
159
+ ├── summary.json # Bot data interface (v2)
160
+ ├── summary.txt # Human-readable report
161
+ ├── context.md # Session context (Entire-style)
136
162
  ├── prompts.txt # User prompts with timestamps
137
- └── metadata.json # Lightweight session metadata
163
+ ├── metadata.json # Lightweight session metadata
164
+ └── ai-summary.md # AI-generated summary (optional)
138
165
  ```
139
166
 
140
- ### summary.json v2 Schema
167
+ ### summary.json v2
141
168
 
142
169
  ```json
143
170
  {
@@ -153,56 +180,27 @@ shit-cli/
153
180
  "summary": "Fixed: Fix authentication timeout issue"
154
181
  },
155
182
  "changes": {
156
- "files": [{
157
- "path": "src/auth/auth.service.ts",
158
- "category": "source",
159
- "operations": ["edit"],
160
- "editCount": 2,
161
- "editSummary": "Modified timeout logic"
162
- }],
183
+ "files": [{ "path": "src/auth.ts", "category": "source", "operations": ["edit"] }],
163
184
  "summary": { "source": 3, "test": 1 }
164
185
  },
165
186
  "activity": {
166
187
  "tools": { "Read": 15, "Edit": 3, "Bash": 5 },
167
- "commands": {
168
- "test": ["npm run test"],
169
- "git": ["git status"]
170
- },
188
+ "commands": { "test": ["npm run test"], "git": ["git status"] },
171
189
  "errors": []
172
190
  },
173
191
  "review_hints": {
174
192
  "tests_run": true,
175
193
  "build_verified": false,
176
- "files_without_tests": ["src/auth/auth.service.ts"],
194
+ "files_without_tests": ["src/auth.ts"],
177
195
  "large_change": false,
178
196
  "config_changed": false,
179
197
  "migration_added": false
180
198
  },
181
- "prompts": ["Fix the auth timeout bug", "Run the tests"],
199
+ "prompts": [{ "time": "...", "text": "Fix the auth timeout bug" }],
182
200
  "scope": ["auth"]
183
201
  }
184
202
  ```
185
203
 
186
- ### Cross-Session Index
187
-
188
- ```json
189
- {
190
- "project": "my-project",
191
- "sessions": [{
192
- "id": "f608c31e...",
193
- "date": "2026-02-27",
194
- "type": "bugfix",
195
- "intent": "Fix auth timeout",
196
- "files": ["src/auth/auth.service.ts"],
197
- "duration": 45,
198
- "risk": "medium"
199
- }],
200
- "file_history": {
201
- "src/auth/auth.service.ts": ["f608c31e...", "a1b2c3d4..."]
202
- }
203
- }
204
- ```
205
-
206
204
  ## Session Types
207
205
 
208
206
  | Type | Description |
@@ -219,19 +217,20 @@ shit-cli/
219
217
  | `style` | Formatting, UI |
220
218
  | `security` | Security-related |
221
219
  | `perf` | Performance optimization |
222
- | `unknown` | Unclassified |
223
220
 
224
221
  ## Risk Levels
225
222
 
226
- - **low**: Few files, no config/migration changes, tests run
227
- - **medium**: Multiple files, some config changes
228
- - **high**: Many files (>10), migration changes, infra changes without tests
223
+ - **low** - Few files, no config/migration changes, tests run
224
+ - **medium** - Multiple files, some config changes
225
+ - **high** - Many files (>10), migration changes, infra changes without tests
229
226
 
230
227
  ## Bot Integration
231
228
 
232
229
  ```javascript
230
+ import { readFileSync } from 'fs';
231
+
233
232
  // Read session data
234
- const summary = JSON.parse(fs.readFileSync('.shit-logs/<id>/summary.json'));
233
+ const summary = JSON.parse(readFileSync('.shit-logs/<id>/summary.json', 'utf-8'));
235
234
 
236
235
  // Check review hints
237
236
  if (!summary.review_hints.tests_run && summary.changes.files.length > 0) {
@@ -242,16 +241,40 @@ if (summary.review_hints.migration_added) {
242
241
  }
243
242
 
244
243
  // Query file history via index
245
- const index = JSON.parse(fs.readFileSync('.shit-logs/index.json'));
244
+ const index = JSON.parse(readFileSync('.shit-logs/index.json', 'utf-8'));
246
245
  const history = index.file_history['src/auth/auth.service.ts'];
247
- if (history.length > 3) {
246
+ if (history && history.length > 3) {
248
247
  review.note('This file has been modified frequently');
249
248
  }
250
249
  ```
251
250
 
251
+ ## AI Summary
252
+
253
+ Set one of these environment variables to enable AI-powered session summaries:
254
+
255
+ ```bash
256
+ export OPENAI_API_KEY=sk-... # Uses gpt-4o-mini by default
257
+ export ANTHROPIC_API_KEY=sk-... # Uses claude-3-haiku by default
258
+ ```
259
+
260
+ Then run:
261
+ ```bash
262
+ shit summarize <session-id>
263
+ ```
264
+
252
265
  ## Environment Variables
253
266
 
254
- - `SHIT_LOG_DIR`: Custom log directory (default: `./.shit-logs` in project root)
267
+ | Variable | Description |
268
+ |----------|-------------|
269
+ | `SHIT_LOG_DIR` | Custom log directory (default: `.shit-logs` in project root) |
270
+ | `OPENAI_API_KEY` | Enable AI summaries via OpenAI |
271
+ | `ANTHROPIC_API_KEY` | Enable AI summaries via Anthropic |
272
+
273
+ ## Security
274
+
275
+ - Session logs are stored locally in `.shit-logs/` (added to `.gitignore` automatically)
276
+ - Secrets (API keys, tokens, passwords) are automatically redacted when writing to shadow branches
277
+ - Checkpoint data uses git plumbing commands — no impact on your working tree
255
278
 
256
279
  ## License
257
280
 
package/bin/shit.js CHANGED
@@ -13,6 +13,7 @@ const commands = {
13
13
  list: 'List all sessions',
14
14
  checkpoints: 'List all checkpoints',
15
15
  view: 'View session details',
16
+ review: 'Run structured code review for a session',
16
17
  query: 'Query session memory (cross-session)',
17
18
  explain: 'Explain a session or commit',
18
19
  summarize: 'Generate AI summary for a session',
@@ -37,6 +38,8 @@ function showHelp() {
37
38
  console.log(' shit status # Show current session');
38
39
  console.log(' shit list # List sessions');
39
40
  console.log(' shit view <session-id> # View session');
41
+ console.log(' shit review [session-id] # Structured code review');
42
+ console.log(' shit review --recent=3 --md # Markdown review for latest 3 sessions');
40
43
  console.log(' shit rewind <checkpoint> # Rollback to checkpoint');
41
44
  console.log(' shit resume <checkpoint> # Resume from checkpoint');
42
45
  console.log(' shit doctor --fix # Fix stuck sessions');
@@ -61,13 +64,21 @@ if (command === '--version' || command === '-v') {
61
64
  process.exit(0);
62
65
  }
63
66
 
67
+ if (!Object.prototype.hasOwnProperty.call(commands, command)) {
68
+ console.error(`Unknown command: ${command}`);
69
+ process.exit(1);
70
+ }
71
+
64
72
  try {
65
73
  const mod = await import(`../lib/${command}.js`);
66
- await mod.default(args.slice(1));
67
- } catch (error) {
68
- if (error.code === 'ERR_MODULE_NOT_FOUND') {
69
- console.error(`Unknown command: ${command}`);
70
- process.exit(1);
74
+ if (typeof mod.default !== 'function') {
75
+ throw new Error(`Command module "${command}" has no default function export`);
71
76
  }
72
- throw error;
77
+ const exitCode = await mod.default(args.slice(1));
78
+ if (Number.isInteger(exitCode) && exitCode !== 0) {
79
+ process.exitCode = exitCode;
80
+ }
81
+ } catch (error) {
82
+ console.error(`Failed to run command "${command}": ${error.message}`);
83
+ process.exit(1);
73
84
  }
package/lib/checkpoint.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * - Supports multiple agents (Claude Code, Gemini CLI, Cursor, etc.)
9
9
  */
10
10
 
11
- import { readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
11
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
12
12
  import { execSync } from 'child_process';
13
13
  import { join, dirname } from 'path';
14
14
  import { redactSecrets } from './redact.js';
@@ -149,9 +149,10 @@ export async function commitCheckpoint(projectRoot, sessionDir, sessionId, commi
149
149
  return { success: false, reason: 'not a git repo' };
150
150
  }
151
151
 
152
- // Branch naming: shit/checkpoints/v1/YYYY-MM-DD-<short-uuid>
153
- const datePart = sessionId.split('-').slice(0, 3).join('-');
154
- const uuidPart = sessionId.split('-').slice(3).join('-').slice(0, 8);
152
+ // Branch naming: shit/checkpoints/v1/YYYY-MM-DD-<session-short>
153
+ const datePart = new Date().toISOString().slice(0, 10);
154
+ const sessionCompact = sessionId.toLowerCase().replace(/[^a-f0-9]/g, '');
155
+ const uuidPart = (sessionCompact.slice(-8) || sessionCompact.slice(0, 8) || 'unknown00').padEnd(8, '0');
155
156
  const branchName = `shit/checkpoints/v1/${datePart}-${uuidPart}`;
156
157
  const refPath = `refs/heads/${branchName}`;
157
158
 
@@ -208,13 +209,6 @@ export async function commitCheckpoint(projectRoot, sessionDir, sessionId, commi
208
209
  // best effort
209
210
  }
210
211
 
211
- return {
212
- success: true,
213
- branch: branchName,
214
- commit: commitHash,
215
- linked_commit: linkedCommit,
216
- };
217
-
218
212
  // Auto-summarize if enabled
219
213
  if (autoSummarize) {
220
214
  try {
@@ -227,6 +221,13 @@ export async function commitCheckpoint(projectRoot, sessionDir, sessionId, commi
227
221
  // Best effort - summarize is optional
228
222
  }
229
223
  }
224
+
225
+ return {
226
+ success: true,
227
+ branch: branchName,
228
+ commit: commitHash,
229
+ linked_commit: linkedCommit,
230
+ };
230
231
  }
231
232
 
232
233
  /**
@@ -241,29 +242,35 @@ export function listCheckpoints(projectRoot) {
241
242
 
242
243
  for (const branch of branches) {
243
244
  try {
244
- // Extract session info from branch name
245
+ // Extract session info from branch name (supports current and legacy formats)
245
246
  const match = branch.match(/^shit\/checkpoints\/v1\/(\d{4}-\d{2}-\d{2})-([a-f0-9]+)$/);
246
- if (match) {
247
- const [, date, uuidShort] = match;
248
-
249
- // Get commit info
250
- const log = git(`log ${branch} --oneline -1`, projectRoot);
251
- const commitMatch = log.match(/^([a-f0-9]+)\s+checkpoint/);
252
- const commit = commitMatch ? commitMatch[1] : log.split(' ')[0];
253
-
254
- // Get linked commit from message
255
- const fullLog = git(`log ${branch} --format=%B -1`, projectRoot);
256
- const linkedMatch = fullLog.match(/@ ([a-f0-9]+)/);
257
- const linkedCommit = linkedMatch ? linkedMatch[1] : null;
258
-
259
- checkpoints.push({
260
- branch,
261
- commit: commit.slice(0, 12),
262
- linked_commit: linkedCommit,
263
- date,
264
- uuid: uuidShort,
265
- });
247
+ const legacyMatch = branch.match(/^shit\/checkpoints\/v1\/([a-f0-9-]+)$/);
248
+ if (!match && !legacyMatch) {
249
+ continue;
266
250
  }
251
+
252
+ const date = match ? match[1] : git(`log ${branch} --format=%cs -1`, projectRoot);
253
+ const uuidShort = match
254
+ ? match[2]
255
+ : ((legacyMatch[1].replace(/[^a-f0-9]/g, '').slice(-8) || legacyMatch[1].slice(0, 8)).toLowerCase());
256
+
257
+ // Get commit info
258
+ const log = git(`log ${branch} --oneline -1`, projectRoot);
259
+ const commitMatch = log.match(/^([a-f0-9]+)\s+checkpoint/);
260
+ const commit = commitMatch ? commitMatch[1] : log.split(' ')[0];
261
+
262
+ // Get linked commit from message
263
+ const fullLog = git(`log ${branch} --format=%B -1`, projectRoot);
264
+ const linkedMatch = fullLog.match(/@ ([a-f0-9]+)/);
265
+ const linkedCommit = linkedMatch ? linkedMatch[1] : null;
266
+
267
+ checkpoints.push({
268
+ branch,
269
+ commit: commit.slice(0, 12),
270
+ linked_commit: linkedCommit,
271
+ date,
272
+ uuid: uuidShort,
273
+ });
267
274
  } catch {
268
275
  // Skip invalid branches
269
276
  }
@@ -5,24 +5,12 @@
5
5
  * Inspired by Entire's checkpoint system
6
6
  */
7
7
 
8
- import { existsSync } from 'fs';
9
- import { join } from 'path';
10
- import { listCheckpoints, getCheckpoint } from './checkpoint.js';
11
-
12
- function findProjectRoot() {
13
- let dir = process.cwd();
14
- while (dir !== '/') {
15
- if (existsSync(join(dir, '.git'))) {
16
- return dir;
17
- }
18
- dir = join(dir, '..');
19
- }
20
- throw new Error('Not in a git repository');
21
- }
8
+ import { listCheckpoints } from './checkpoint.js';
9
+ import { getProjectRoot } from './config.js';
22
10
 
23
11
  export default async function checkpoints(args) {
24
12
  try {
25
- const projectRoot = findProjectRoot();
13
+ const projectRoot = getProjectRoot();
26
14
  const verbose = args.includes('--verbose') || args.includes('-v');
27
15
  const json = args.includes('--json');
28
16
 
package/lib/commit.js CHANGED
@@ -5,25 +5,15 @@
5
5
  * Similar to Entire's approach: checkpoint created on git commit
6
6
  */
7
7
 
8
- import { existsSync, readFileSync, writeFileSync } from 'fs';
8
+ import { existsSync, readdirSync, statSync } from 'fs';
9
9
  import { join } from 'path';
10
10
  import { execSync } from 'child_process';
11
11
  import { commitCheckpoint } from './checkpoint.js';
12
-
13
- function findProjectRoot() {
14
- let dir = process.cwd();
15
- while (dir !== '/') {
16
- if (existsSync(join(dir, '.git'))) {
17
- return dir;
18
- }
19
- dir = join(dir, '..');
20
- }
21
- throw new Error('Not in a git repository');
22
- }
12
+ import { getProjectRoot, SESSION_ID_REGEX } from './config.js';
23
13
 
24
14
  export default async function commitHook(args) {
25
15
  try {
26
- const projectRoot = findProjectRoot();
16
+ const projectRoot = getProjectRoot();
27
17
 
28
18
  // Get the commit that was just created
29
19
  const commitSha = args[0] || execSync('git rev-parse HEAD', { cwd: projectRoot, encoding: 'utf-8' }).trim();
@@ -39,9 +29,11 @@ export default async function commitHook(args) {
39
29
  }
40
30
 
41
31
  // Find the most recent session
42
- const { readdirSync, statSync } = await import('fs');
43
32
  const sessions = readdirSync(shitLogsDir)
44
- .filter(name => name.match(/^\d{4}-\d{2}-\d{2}-[a-f0-9-]+$/))
33
+ .filter(name => {
34
+ const fullPath = join(shitLogsDir, name);
35
+ return SESSION_ID_REGEX.test(name) && statSync(fullPath).isDirectory();
36
+ })
45
37
  .map(name => ({
46
38
  name,
47
39
  path: join(shitLogsDir, name),
package/lib/config.js CHANGED
@@ -3,7 +3,9 @@
3
3
  import { join, relative } from 'path';
4
4
  import { execSync } from 'child_process';
5
5
 
6
- export const SESSION_ID_REGEX = /^[a-f0-9-]{36}$/;
6
+ export const UUID_SESSION_ID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
7
+ export const LEGACY_SESSION_ID_REGEX = /^\d{4}-\d{2}-\d{2}-[a-f0-9-]+$/;
8
+ export const SESSION_ID_REGEX = /^(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|\d{4}-\d{2}-\d{2}-[a-f0-9-]+)$/;
7
9
 
8
10
  export function getProjectRoot() {
9
11
  try {