@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/lib/rewind.js
CHANGED
|
@@ -5,54 +5,71 @@
|
|
|
5
5
|
* Similar to 'entire rewind' - rollback to known good state
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
10
8
|
import { execSync } from 'child_process';
|
|
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
|
-
}
|
|
9
|
+
import { getProjectRoot } from './config.js';
|
|
22
10
|
|
|
23
11
|
function git(cmd, cwd) {
|
|
24
12
|
return execSync(`git ${cmd}`, { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
25
13
|
}
|
|
26
14
|
|
|
15
|
+
function parseCheckpointRef(projectRoot, branch) {
|
|
16
|
+
const shadowMatch = branch.match(/^shit\/([a-f0-9]+)-([a-f0-9]+)$/);
|
|
17
|
+
if (shadowMatch) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'shadow',
|
|
20
|
+
baseCommit: shadowMatch[1],
|
|
21
|
+
sessionShort: shadowMatch[2],
|
|
22
|
+
lookupKey: shadowMatch[2],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const checkpointMatch = branch.match(/^shit\/checkpoints\/v1\/(\d{4}-\d{2}-\d{2})-([a-f0-9]+)$/);
|
|
27
|
+
if (checkpointMatch) {
|
|
28
|
+
const message = git(`log ${branch} --format=%B -1`, projectRoot);
|
|
29
|
+
const linkedMatch = message.match(/@ ([a-f0-9]+)/);
|
|
30
|
+
const date = checkpointMatch[1];
|
|
31
|
+
const sessionShort = checkpointMatch[2];
|
|
32
|
+
return {
|
|
33
|
+
type: 'checkpoint',
|
|
34
|
+
baseCommit: linkedMatch ? linkedMatch[1] : null,
|
|
35
|
+
sessionShort,
|
|
36
|
+
lookupKey: `${date}-${sessionShort}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
function listCheckpoints(projectRoot) {
|
|
28
44
|
try {
|
|
29
|
-
|
|
30
|
-
const branches = git('branch --list "shit/*"', projectRoot)
|
|
45
|
+
const branches = git('branch --list "shit/checkpoints/v1/*" "shit/*"', projectRoot)
|
|
31
46
|
.split('\n')
|
|
32
47
|
.map(b => b.trim().replace(/^\*?\s*/, ''))
|
|
33
48
|
.filter(Boolean);
|
|
49
|
+
const uniqueBranches = [...new Set(branches)];
|
|
34
50
|
|
|
35
51
|
const checkpoints = [];
|
|
36
52
|
|
|
37
|
-
for (const branch of
|
|
53
|
+
for (const branch of uniqueBranches) {
|
|
38
54
|
try {
|
|
55
|
+
const parsed = parseCheckpointRef(projectRoot, branch);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
const log = git(`log ${branch} --oneline -1`, projectRoot);
|
|
40
61
|
const [commit, ...messageParts] = log.split(' ');
|
|
41
62
|
const message = messageParts.join(' ');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
message,
|
|
53
|
-
timestamp: git(`log ${branch} --format=%ci -1`, projectRoot)
|
|
54
|
-
});
|
|
55
|
-
}
|
|
63
|
+
checkpoints.push({
|
|
64
|
+
branch,
|
|
65
|
+
commit,
|
|
66
|
+
baseCommit: parsed.baseCommit,
|
|
67
|
+
sessionShort: parsed.sessionShort,
|
|
68
|
+
lookupKey: parsed.lookupKey,
|
|
69
|
+
type: parsed.type,
|
|
70
|
+
message,
|
|
71
|
+
timestamp: git(`log ${branch} --format=%ci -1`, projectRoot)
|
|
72
|
+
});
|
|
56
73
|
} catch {
|
|
57
74
|
// Skip invalid branches
|
|
58
75
|
}
|
|
@@ -84,6 +101,10 @@ function hasUncommittedChanges(projectRoot) {
|
|
|
84
101
|
function rewindToCheckpoint(projectRoot, checkpoint, force = false) {
|
|
85
102
|
const currentCommit = getCurrentCommit(projectRoot);
|
|
86
103
|
|
|
104
|
+
if (!checkpoint.baseCommit) {
|
|
105
|
+
throw new Error(`Checkpoint ${checkpoint.lookupKey || checkpoint.sessionShort} is missing a linked base commit`);
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
if (!force && hasUncommittedChanges(projectRoot)) {
|
|
88
109
|
throw new Error('Working directory has uncommitted changes. Use --force to override or commit changes first.');
|
|
89
110
|
}
|
|
@@ -100,7 +121,7 @@ function rewindToCheckpoint(projectRoot, checkpoint, force = false) {
|
|
|
100
121
|
|
|
101
122
|
export default async function rewind(args) {
|
|
102
123
|
try {
|
|
103
|
-
const projectRoot =
|
|
124
|
+
const projectRoot = getProjectRoot();
|
|
104
125
|
const force = args.includes('--force') || args.includes('-f');
|
|
105
126
|
const interactive = args.includes('--interactive') || args.includes('-i');
|
|
106
127
|
|
|
@@ -112,7 +133,7 @@ export default async function rewind(args) {
|
|
|
112
133
|
|
|
113
134
|
if (checkpoints.length === 0) {
|
|
114
135
|
console.log('❌ No checkpoints found');
|
|
115
|
-
console.log(' Checkpoints are created
|
|
136
|
+
console.log(' Checkpoints are created when you run "shit commit".');
|
|
116
137
|
process.exit(1);
|
|
117
138
|
}
|
|
118
139
|
|
|
@@ -120,7 +141,8 @@ export default async function rewind(args) {
|
|
|
120
141
|
// Find specific checkpoint
|
|
121
142
|
targetCheckpoint = checkpoints.find(cp =>
|
|
122
143
|
cp.sessionShort.startsWith(checkpointArg) ||
|
|
123
|
-
cp.
|
|
144
|
+
cp.lookupKey.startsWith(checkpointArg) ||
|
|
145
|
+
(cp.baseCommit && cp.baseCommit.startsWith(checkpointArg))
|
|
124
146
|
);
|
|
125
147
|
|
|
126
148
|
if (!targetCheckpoint) {
|
|
@@ -132,22 +154,25 @@ export default async function rewind(args) {
|
|
|
132
154
|
console.log('📋 Available checkpoints:\n');
|
|
133
155
|
checkpoints.forEach((cp, i) => {
|
|
134
156
|
const date = new Date(cp.timestamp).toLocaleString();
|
|
135
|
-
|
|
157
|
+
const base = cp.baseCommit ? cp.baseCommit.slice(0, 7) : 'unknown';
|
|
158
|
+
const key = cp.lookupKey || cp.sessionShort;
|
|
159
|
+
console.log(`${i + 1}. ${key} (${base}) - ${date}`);
|
|
136
160
|
console.log(` ${cp.message}`);
|
|
137
161
|
console.log();
|
|
138
162
|
});
|
|
139
163
|
|
|
140
164
|
// For now, just use the most recent
|
|
141
165
|
targetCheckpoint = checkpoints[0];
|
|
142
|
-
console.log(`Using most recent checkpoint: ${targetCheckpoint.sessionShort}`);
|
|
166
|
+
console.log(`Using most recent checkpoint: ${targetCheckpoint.lookupKey || targetCheckpoint.sessionShort}`);
|
|
143
167
|
} else {
|
|
144
168
|
// Use most recent checkpoint
|
|
145
169
|
targetCheckpoint = checkpoints[0];
|
|
146
170
|
}
|
|
147
171
|
|
|
148
|
-
|
|
172
|
+
const selectedKey = targetCheckpoint.lookupKey || targetCheckpoint.sessionShort;
|
|
173
|
+
console.log(`🔄 Rewinding to checkpoint: ${selectedKey}`);
|
|
149
174
|
console.log(` Branch: ${targetCheckpoint.branch}`);
|
|
150
|
-
console.log(` Base commit: ${targetCheckpoint.baseCommit}`);
|
|
175
|
+
console.log(` Base commit: ${targetCheckpoint.baseCommit || 'unknown'}`);
|
|
151
176
|
console.log(` Created: ${new Date(targetCheckpoint.timestamp).toLocaleString()}`);
|
|
152
177
|
console.log();
|
|
153
178
|
|
|
@@ -164,7 +189,7 @@ export default async function rewind(args) {
|
|
|
164
189
|
console.log(` git reset --hard ${previousCommit}`);
|
|
165
190
|
console.log();
|
|
166
191
|
console.log('💡 To resume from this checkpoint:');
|
|
167
|
-
console.log(` shit resume ${
|
|
192
|
+
console.log(` shit resume ${selectedKey}`);
|
|
168
193
|
|
|
169
194
|
} catch (error) {
|
|
170
195
|
console.error('❌ Failed to rewind:', error.message);
|
package/lib/session.js
CHANGED
|
@@ -105,6 +105,12 @@ export function processEvent(state, event, hookType, projectRoot) {
|
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Mark session as ended for end/stop hooks.
|
|
109
|
+
if (hookType === 'session-end' || hookType === 'SessionEnd' || hookType === 'stop' || hookType === 'session_end' || hookType === 'end') {
|
|
110
|
+
state.end_time = now;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
108
114
|
// Tool events
|
|
109
115
|
if (hookType === 'post-tool-use' && toolName) {
|
|
110
116
|
state.tool_counts[toolName] = (state.tool_counts[toolName] || 0) + 1;
|
package/lib/status.js
CHANGED
|
@@ -5,20 +5,10 @@
|
|
|
5
5
|
* Similar to 'entire status' - displays active session info
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync } from 'fs';
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import { execSync } from 'child_process';
|
|
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
|
-
}
|
|
11
|
+
import { getProjectRoot, SESSION_ID_REGEX } from './config.js';
|
|
22
12
|
|
|
23
13
|
function getCurrentSession(projectRoot) {
|
|
24
14
|
const shitLogsDir = join(projectRoot, '.shit-logs');
|
|
@@ -27,9 +17,11 @@ function getCurrentSession(projectRoot) {
|
|
|
27
17
|
}
|
|
28
18
|
|
|
29
19
|
// Find the most recent session directory
|
|
30
|
-
const { readdirSync, statSync } = await import('fs');
|
|
31
20
|
const sessions = readdirSync(shitLogsDir)
|
|
32
|
-
.filter(name =>
|
|
21
|
+
.filter(name => {
|
|
22
|
+
const fullPath = join(shitLogsDir, name);
|
|
23
|
+
return SESSION_ID_REGEX.test(name) && statSync(fullPath).isDirectory();
|
|
24
|
+
})
|
|
33
25
|
.map(name => ({
|
|
34
26
|
name,
|
|
35
27
|
path: join(shitLogsDir, name),
|
|
@@ -90,11 +82,45 @@ function formatDuration(startTime) {
|
|
|
90
82
|
}
|
|
91
83
|
}
|
|
92
84
|
|
|
85
|
+
function getTouchedFileCount(state) {
|
|
86
|
+
const ops = state?.file_ops;
|
|
87
|
+
if (!ops || typeof ops !== 'object') {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const touched = new Set([
|
|
92
|
+
...(Array.isArray(ops.write) ? ops.write : []),
|
|
93
|
+
...(Array.isArray(ops.edit) ? ops.edit : []),
|
|
94
|
+
...(Array.isArray(ops.read) ? ops.read : []),
|
|
95
|
+
].filter(Boolean));
|
|
96
|
+
|
|
97
|
+
return touched.size;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hasShitHooks(settings) {
|
|
101
|
+
if (!settings?.hooks || typeof settings.hooks !== 'object') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return Object.values(settings.hooks).some(value => {
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
return value.includes('shit log');
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(value)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return value.some(entry =>
|
|
113
|
+
Array.isArray(entry?.hooks) &&
|
|
114
|
+
entry.hooks.some(hook => typeof hook?.command === 'string' && hook.command.includes('shit log'))
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
93
119
|
export default async function status(args) {
|
|
94
120
|
try {
|
|
95
|
-
const projectRoot =
|
|
121
|
+
const projectRoot = getProjectRoot();
|
|
96
122
|
const gitInfo = getGitInfo(projectRoot);
|
|
97
|
-
const currentSession =
|
|
123
|
+
const currentSession = getCurrentSession(projectRoot);
|
|
98
124
|
|
|
99
125
|
console.log('📊 shit-cli Status\n');
|
|
100
126
|
|
|
@@ -121,7 +147,7 @@ export default async function status(args) {
|
|
|
121
147
|
console.log(` Started: ${new Date(state.start_time).toLocaleString()}`);
|
|
122
148
|
console.log(` Duration: ${formatDuration(state.start_time)}`);
|
|
123
149
|
console.log(` Events: ${state.event_count || 0}`);
|
|
124
|
-
console.log(` Files: ${
|
|
150
|
+
console.log(` Files: ${getTouchedFileCount(state)}`);
|
|
125
151
|
|
|
126
152
|
if (state.shadow_branch) {
|
|
127
153
|
console.log(` Shadow: ${state.shadow_branch}`);
|
|
@@ -149,8 +175,7 @@ export default async function status(args) {
|
|
|
149
175
|
if (existsSync(claudeSettings)) {
|
|
150
176
|
try {
|
|
151
177
|
const settings = JSON.parse(readFileSync(claudeSettings, 'utf-8'));
|
|
152
|
-
const hasHooks = settings
|
|
153
|
-
(settings.hooks.session_start || settings.hooks.session_end);
|
|
178
|
+
const hasHooks = hasShitHooks(settings);
|
|
154
179
|
|
|
155
180
|
console.log();
|
|
156
181
|
if (hasHooks) {
|
package/lib/summarize.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
-
import {
|
|
11
|
+
import { getProjectRoot } from './config.js';
|
|
12
12
|
|
|
13
13
|
// Default configuration
|
|
14
14
|
const DEFAULT_CONFIG = {
|
|
@@ -294,7 +294,7 @@ export async function summarizeSession(projectRoot, sessionId, sessionDir) {
|
|
|
294
294
|
* CLI command for manual summarization
|
|
295
295
|
*/
|
|
296
296
|
export default async function summarize(args) {
|
|
297
|
-
const projectRoot =
|
|
297
|
+
const projectRoot = getProjectRoot();
|
|
298
298
|
const sessionId = args[0];
|
|
299
299
|
|
|
300
300
|
if (!sessionId) {
|
|
@@ -329,14 +329,3 @@ export default async function summarize(args) {
|
|
|
329
329
|
process.exit(1);
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
|
-
|
|
333
|
-
function findProjectRoot() {
|
|
334
|
-
let dir = process.cwd();
|
|
335
|
-
while (dir !== '/') {
|
|
336
|
-
if (existsSync(join(dir, '.git'))) {
|
|
337
|
-
return dir;
|
|
338
|
-
}
|
|
339
|
-
dir = join(dir, '..');
|
|
340
|
-
}
|
|
341
|
-
throw new Error('Not in a git repository');
|
|
342
|
-
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudemini/shit-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Session-based Hook Intelligence Tracker -
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Session-based Hook Intelligence Tracker - Zero-dependency memory system for human-AI coding sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"shit": "./bin/shit.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
9
17
|
"scripts": {
|
|
10
18
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
19
|
},
|
|
12
20
|
"keywords": [
|
|
13
21
|
"claude-code",
|
|
22
|
+
"gemini-cli",
|
|
23
|
+
"cursor",
|
|
24
|
+
"ai-coding",
|
|
14
25
|
"hooks",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
26
|
+
"session-tracking",
|
|
27
|
+
"code-review",
|
|
28
|
+
"checkpoint"
|
|
17
29
|
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/anthropics/shit-cli.git"
|
|
33
|
+
},
|
|
18
34
|
"author": "",
|
|
19
|
-
"license": "
|
|
35
|
+
"license": "UNLICENSED",
|
|
20
36
|
"dependencies": {},
|
|
21
37
|
"devDependencies": {}
|
|
22
38
|
}
|
package/.claude/settings.json
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"SessionStart": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "shit log session-start"
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
|
-
"SessionEnd": [
|
|
15
|
-
{
|
|
16
|
-
"matcher": "",
|
|
17
|
-
"hooks": [
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "shit log session-end"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"UserPromptSubmit": [
|
|
26
|
-
{
|
|
27
|
-
"matcher": "",
|
|
28
|
-
"hooks": [
|
|
29
|
-
{
|
|
30
|
-
"type": "command",
|
|
31
|
-
"command": "shit log user-prompt-submit"
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"PreToolUse": [
|
|
37
|
-
{
|
|
38
|
-
"matcher": "",
|
|
39
|
-
"hooks": [
|
|
40
|
-
{
|
|
41
|
-
"type": "command",
|
|
42
|
-
"command": "shit log pre-tool-use"
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"PostToolUse": [
|
|
48
|
-
{
|
|
49
|
-
"matcher": "",
|
|
50
|
-
"hooks": [
|
|
51
|
-
{
|
|
52
|
-
"type": "command",
|
|
53
|
-
"command": "shit log post-tool-use"
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
"Notification": [
|
|
59
|
-
{
|
|
60
|
-
"matcher": "",
|
|
61
|
-
"hooks": [
|
|
62
|
-
{
|
|
63
|
-
"type": "command",
|
|
64
|
-
"command": "shit log notification"
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
"Stop": [
|
|
70
|
-
{
|
|
71
|
-
"matcher": "",
|
|
72
|
-
"hooks": [
|
|
73
|
-
{
|
|
74
|
-
"type": "command",
|
|
75
|
-
"command": "shit log stop"
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(git remote add:*)",
|
|
5
|
-
"Bash(git push:*)",
|
|
6
|
-
"Bash(npm publish:*)",
|
|
7
|
-
"Bash(npm whoami:*)",
|
|
8
|
-
"WebFetch(domain:www.npmjs.com)",
|
|
9
|
-
"Bash(npm config delete:*)",
|
|
10
|
-
"Bash(sudo chown:*)",
|
|
11
|
-
"Bash(npm cache clean:*)",
|
|
12
|
-
"Bash(npm install:*)",
|
|
13
|
-
"Bash(git add:*)",
|
|
14
|
-
"Bash(git commit -m \"$\\(cat <<''EOF''\n更新包名和版本号\n\n- 包名改为 @cluademini/shit-cli\n- 版本号升至 1.0.3\nEOF\n\\)\")",
|
|
15
|
-
"Bash(git commit:*)",
|
|
16
|
-
"Bash(npm config set:*)",
|
|
17
|
-
"WebFetch(domain:github.com)"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
}
|
package/COMPARISON.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Project Structure Comparison
|
|
2
|
-
|
|
3
|
-
## Directory Layout
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
your-project/
|
|
7
|
-
├── .claude/ # Claude Code configuration
|
|
8
|
-
│ └── settings.json # Hook configurations
|
|
9
|
-
├── .entire/ # Entire tool (session management)
|
|
10
|
-
│ ├── metadata/
|
|
11
|
-
│ │ └── <session-id>/
|
|
12
|
-
│ │ ├── full.jsonl # Complete transcript
|
|
13
|
-
│ │ ├── prompt.txt # User prompts
|
|
14
|
-
│ │ ├── context.md # Session context
|
|
15
|
-
│ │ └── summary.txt # Session summary
|
|
16
|
-
│ ├── logs/
|
|
17
|
-
│ └── settings.json
|
|
18
|
-
└── .shit-logs/ # shit-cli (hook event logging)
|
|
19
|
-
├── <session-id>/
|
|
20
|
-
│ ├── events.jsonl # Hook events only
|
|
21
|
-
│ ├── prompts.txt # User prompts
|
|
22
|
-
│ ├── context.md # Session context
|
|
23
|
-
│ ├── summary.txt # Session summary
|
|
24
|
-
│ └── metadata.json # Session metadata
|
|
25
|
-
└── index.txt # Global index
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Feature Comparison
|
|
29
|
-
|
|
30
|
-
| Feature | `.entire` | `.shit-logs` |
|
|
31
|
-
|---------|-----------|--------------|
|
|
32
|
-
| **Scope** | Complete session transcript | Hook events only |
|
|
33
|
-
| **Location** | Project root | Project root ✓ |
|
|
34
|
-
| **Session-based** | ✓ | ✓ |
|
|
35
|
-
| **Transcript** | `full.jsonl` (all messages) | `events.jsonl` (hooks only) |
|
|
36
|
-
| **Prompts** | `prompt.txt` | `prompts.txt` |
|
|
37
|
-
| **Context** | `context.md` | `context.md` |
|
|
38
|
-
| **Summary** | `summary.txt` | `summary.txt` |
|
|
39
|
-
| **Checkpoints** | Git shadow branches | Not implemented |
|
|
40
|
-
| **CLI** | `entire` commands | `shit` commands |
|
|
41
|
-
| **Auto-init** | `entire enable` | `shit init` |
|
|
42
|
-
|
|
43
|
-
## Key Differences
|
|
44
|
-
|
|
45
|
-
### `.entire` (Entire Tool)
|
|
46
|
-
- **Purpose**: Complete session management and checkpointing
|
|
47
|
-
- **Data**: Full conversation transcript (user + assistant messages)
|
|
48
|
-
- **Features**: Git integration, checkpoints, session replay
|
|
49
|
-
- **Trigger**: Automatic (entire daemon)
|
|
50
|
-
|
|
51
|
-
### `.shit-logs` (shit-cli)
|
|
52
|
-
- **Purpose**: Hook event logging and analysis
|
|
53
|
-
- **Data**: Hook events only (tool calls, session events)
|
|
54
|
-
- **Features**: Session aggregation, event filtering, cleanup
|
|
55
|
-
- **Trigger**: Hook-based (Claude Code hooks)
|
|
56
|
-
|
|
57
|
-
## Use Cases
|
|
58
|
-
|
|
59
|
-
### Use `.entire` when you need:
|
|
60
|
-
- Complete session history
|
|
61
|
-
- Git-based checkpointing
|
|
62
|
-
- Session replay and analysis
|
|
63
|
-
- Cross-session context
|
|
64
|
-
|
|
65
|
-
### Use `.shit-logs` when you need:
|
|
66
|
-
- Hook event debugging
|
|
67
|
-
- Tool usage analysis
|
|
68
|
-
- Session statistics
|
|
69
|
-
- Lightweight logging
|
|
70
|
-
|
|
71
|
-
## Both Together
|
|
72
|
-
|
|
73
|
-
Running both systems provides:
|
|
74
|
-
- **Complete coverage**: Full transcript + hook events
|
|
75
|
-
- **Different perspectives**: Conversation flow + tool execution
|
|
76
|
-
- **Complementary data**: `.entire` for context, `.shit-logs` for debugging
|
|
77
|
-
|
|
78
|
-
## .gitignore
|
|
79
|
-
|
|
80
|
-
Both directories are typically excluded from git:
|
|
81
|
-
|
|
82
|
-
```gitignore
|
|
83
|
-
# Entire tool
|
|
84
|
-
.entire/store/
|
|
85
|
-
.entire/monitor.pid
|
|
86
|
-
.entire/monitor.sessions.json
|
|
87
|
-
|
|
88
|
-
# shit-cli logs
|
|
89
|
-
.shit-logs/
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Note: `.entire/settings.json` and `.entire/.gitignore` are usually committed.
|