@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 +139 -116
- package/bin/shit.js +17 -6
- package/lib/checkpoint.js +39 -32
- package/lib/checkpoints.js +3 -15
- package/lib/commit.js +7 -15
- package/lib/config.js +3 -1
- package/lib/disable.js +54 -18
- package/lib/doctor.js +17 -24
- package/lib/enable.js +24 -13
- package/lib/explain.js +43 -38
- package/lib/reset.js +8 -16
- package/lib/resume.js +32 -27
- package/lib/review.js +728 -0
- package/lib/rewind.js +63 -38
- package/lib/session.js +6 -0
- package/lib/status.js +44 -19
- package/lib/summarize.js +2 -13
- package/package.json +21 -5
- package/.claude/settings.json +0 -81
- package/.claude/settings.local.json +0 -20
- package/COMPARISON.md +0 -92
- package/DESIGN_PHILOSOPHY.md +0 -138
- package/QUICKSTART.md +0 -109
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
|
|
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
|
-
|
|
7
|
+
Supports **Claude Code**, **Gemini CLI**, **Cursor**, and **OpenCode**.
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
npm link
|
|
19
|
-
```
|
|
12
|
+
npm install -g @claudemini/shit-cli
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
##
|
|
27
|
-
|
|
28
|
-
### Initialize hooks
|
|
21
|
+
## Installation
|
|
29
22
|
|
|
30
23
|
```bash
|
|
31
|
-
|
|
32
|
-
shit init
|
|
24
|
+
npm install -g @claudemini/shit-cli
|
|
33
25
|
```
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
### List sessions
|
|
27
|
+
Or use directly without installing:
|
|
38
28
|
|
|
39
29
|
```bash
|
|
40
|
-
shit
|
|
30
|
+
npx @claudemini/shit-cli <command>
|
|
41
31
|
```
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
3 session(s):
|
|
33
|
+
## Commands
|
|
46
34
|
|
|
47
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
###
|
|
47
|
+
### Session Tracking
|
|
59
48
|
|
|
60
49
|
```bash
|
|
61
|
-
shit
|
|
62
|
-
shit
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
81
|
+
### Checkpoints & Recovery
|
|
78
82
|
|
|
79
83
|
```bash
|
|
80
|
-
shit
|
|
81
|
-
shit
|
|
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
|
-
###
|
|
92
|
+
### Maintenance
|
|
85
93
|
|
|
86
94
|
```bash
|
|
87
|
-
shit
|
|
88
|
-
shit
|
|
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
|
-
##
|
|
104
|
+
## Command Reference
|
|
92
105
|
|
|
93
106
|
| Command | Description |
|
|
94
107
|
|---------|-------------|
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
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 <
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
|
135
|
-
├── summary.txt # Human-readable
|
|
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
|
-
|
|
163
|
+
├── metadata.json # Lightweight session metadata
|
|
164
|
+
└── ai-summary.md # AI-generated summary (optional)
|
|
138
165
|
```
|
|
139
166
|
|
|
140
|
-
### summary.json v2
|
|
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
|
|
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"
|
|
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
|
|
227
|
-
- **medium
|
|
228
|
-
- **high
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
153
|
-
const datePart =
|
|
154
|
-
const
|
|
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
|
-
|
|
247
|
-
|
|
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
|
}
|
package/lib/checkpoints.js
CHANGED
|
@@ -5,24 +5,12 @@
|
|
|
5
5
|
* Inspired by Entire's checkpoint system
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 =>
|
|
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
|
|
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 {
|