moveros 4.0.8 → 4.1.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/install.js +4 -2
- package/package.json +1 -1
- package/src/hooks/context-staleness.sh +46 -46
- package/src/hooks/dirty-tree-guard.sh +33 -33
- package/src/hooks/engine-protection.sh +43 -43
- package/src/hooks/git-safety.sh +47 -47
- package/src/hooks/pre-compact-backup.sh +177 -177
- package/src/hooks/session-log-reminder.sh +135 -73
- package/src/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/src/skills/systematic-debugging/SKILL.md +296 -296
- package/src/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/src/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/src/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/src/skills/systematic-debugging/find-polluter.sh +63 -63
- package/src/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/src/skills/systematic-debugging/test-academic.md +14 -14
- package/src/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/src/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/src/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/src/structure/01_Projects/_Template Project/plan.md +55 -55
- package/src/structure/01_Projects/_Template Project/project_brief.md +45 -45
- package/src/structure/02_Areas/Engine/Active_Context.md +146 -146
- package/src/structure/02_Areas/Engine/Auto_Learnings.md +36 -36
- package/src/structure/02_Areas/Engine/Daily_Template.md +133 -133
- package/src/structure/02_Areas/Engine/Identity_Prime_template.md +86 -86
- package/src/structure/02_Areas/Engine/Mover_Dossier.md +120 -120
- package/src/structure/02_Areas/Engine/Strategy_template.md +65 -65
- package/src/structure/03_Library/SOPs/Tech_Stack.md +55 -55
- package/src/structure/03_Library/SOPs/Zone_Operating.md +57 -57
- package/src/system/V4_CONTEXT.md +262 -262
- package/src/theme/minimal-theme.css +271 -271
- package/src/workflows/analyse-day.md +401 -401
- package/src/workflows/debug-resistance.md +180 -180
- package/src/workflows/harvest.md +239 -239
- package/src/workflows/ignite.md +720 -720
- package/src/workflows/init-plan.md +16 -16
- package/src/workflows/morning.md +222 -222
- package/src/workflows/overview.md +203 -203
- package/src/workflows/pivot-strategy.md +218 -218
- package/src/workflows/plan-tomorrow.md +308 -308
- package/src/workflows/primer.md +207 -207
- package/src/workflows/reboot.md +201 -201
- package/src/workflows/refactor-plan.md +135 -135
- package/src/workflows/review-week.md +558 -558
- package/src/workflows/setup.md +388 -388
- package/src/workflows/update.md +10 -13
- package/src/workflows/walkthrough.md +523 -523
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# pre-compact-backup.sh
|
|
3
|
-
# Mover OS V4 - PreCompact Hook
|
|
4
|
-
# Two jobs:
|
|
5
|
-
# 1. Extracts a human-readable session summary BEFORE compaction.
|
|
6
|
-
# 2. Writes a "pending-log" flag so the Stop hook can auto-log post-compaction.
|
|
7
|
-
# PreCompact CANNOT block (Claude Code limitation). The Stop hook reads the flag
|
|
8
|
-
# after compaction and triggers /log from the saved summary.
|
|
9
|
-
|
|
10
|
-
INPUT=$(cat)
|
|
11
|
-
|
|
12
|
-
# Extract fields
|
|
13
|
-
SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
14
|
-
TRANSCRIPT=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
15
|
-
TRIGGER=$(echo "$INPUT" | grep -o '"trigger"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
16
|
-
|
|
17
|
-
if [ "$TRIGGER" != "auto" ] && [ "$TRIGGER" != "manual" ]; then
|
|
18
|
-
exit 0
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
|
|
22
|
-
exit 0
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
BACKUP_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/session-backups"
|
|
26
|
-
mkdir -p "$BACKUP_DIR"
|
|
27
|
-
|
|
28
|
-
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
29
|
-
SUMMARY_FILE="$BACKUP_DIR/session_${TIMESTAMP}_summary.md"
|
|
30
|
-
|
|
31
|
-
# Count messages — grep -c returns "0" and exits 1 on no match, so suppress error, don't use ||
|
|
32
|
-
ASSISTANT_COUNT=$(grep -c '"type":"assistant"' "$TRANSCRIPT" 2>/dev/null); ASSISTANT_COUNT=${ASSISTANT_COUNT:-0}
|
|
33
|
-
USER_COUNT=$(grep -c '"type":"user"' "$TRANSCRIPT" 2>/dev/null); USER_COUNT=${USER_COUNT:-0}
|
|
34
|
-
TOOL_COUNT=$(grep -c '"tool_use"' "$TRANSCRIPT" 2>/dev/null); TOOL_COUNT=${TOOL_COUNT:-0}
|
|
35
|
-
|
|
36
|
-
# Extract skill invocations
|
|
37
|
-
SKILLS=$(grep -o '"skill":"[^"]*"' "$TRANSCRIPT" 2>/dev/null | sort -u | sed 's/"skill":"//;s/"//' | tr '\n' ', ')
|
|
38
|
-
SKILLS=${SKILLS:-none}
|
|
39
|
-
|
|
40
|
-
# Extract last 15 user messages AND last 10 assistant messages — try python3, fall back to node
|
|
41
|
-
USER_MESSAGES=""
|
|
42
|
-
ASSISTANT_MESSAGES=""
|
|
43
|
-
|
|
44
|
-
# Detect python: actually exec it, don't just check path (Windows has a stub that fails)
|
|
45
|
-
PYTHON_CMD=""
|
|
46
|
-
if python3 -c "print(1)" >/dev/null 2>&1; then
|
|
47
|
-
PYTHON_CMD="python3"
|
|
48
|
-
elif python -c "print(1)" >/dev/null 2>&1; then
|
|
49
|
-
PYTHON_CMD="python"
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
if [ -n "$PYTHON_CMD" ]; then
|
|
53
|
-
EXTRACTED=$("$PYTHON_CMD" - "$TRANSCRIPT" << 'PYEOF'
|
|
54
|
-
import json, sys
|
|
55
|
-
transcript_path = sys.argv[1]
|
|
56
|
-
user_msgs = []
|
|
57
|
-
asst_msgs = []
|
|
58
|
-
with open(transcript_path, encoding='utf-8', errors='replace') as f:
|
|
59
|
-
for line in f:
|
|
60
|
-
line = line.strip()
|
|
61
|
-
if not line:
|
|
62
|
-
continue
|
|
63
|
-
try:
|
|
64
|
-
obj = json.loads(line)
|
|
65
|
-
msg = obj.get('message', obj)
|
|
66
|
-
role = msg.get('role') or obj.get('type', '')
|
|
67
|
-
# Extract timestamp: try common field names in Claude JSONL format
|
|
68
|
-
ts = obj.get('timestamp') or obj.get('created_at') or msg.get('timestamp') or ''
|
|
69
|
-
content = msg.get('content', '')
|
|
70
|
-
if isinstance(content, list):
|
|
71
|
-
text = ' '.join(c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text')
|
|
72
|
-
elif isinstance(content, str):
|
|
73
|
-
text = content
|
|
74
|
-
else:
|
|
75
|
-
text = ''
|
|
76
|
-
text = text.strip()
|
|
77
|
-
if not text:
|
|
78
|
-
continue
|
|
79
|
-
if role == 'user':
|
|
80
|
-
user_msgs.append((ts, text))
|
|
81
|
-
elif role == 'assistant':
|
|
82
|
-
asst_msgs.append((ts, text))
|
|
83
|
-
except Exception:
|
|
84
|
-
continue
|
|
85
|
-
print("===USER_MESSAGES===")
|
|
86
|
-
for i, (ts, msg) in enumerate(user_msgs[-15:], 1):
|
|
87
|
-
prefix = f"[{ts[:16]}] " if ts else ""
|
|
88
|
-
print(f"{i}. {prefix}{msg[:380]}{'...' if len(msg) > 380 else ''}")
|
|
89
|
-
print("===ASSISTANT_MESSAGES===")
|
|
90
|
-
for i, (ts, msg) in enumerate(asst_msgs[-10:], 1):
|
|
91
|
-
prefix = f"[{ts[:16]}] " if ts else ""
|
|
92
|
-
print(f"{i}. {prefix}{msg[:280]}{'...' if len(msg) > 280 else ''}")
|
|
93
|
-
PYEOF
|
|
94
|
-
)
|
|
95
|
-
USER_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===USER_MESSAGES===/,/===ASSISTANT_MESSAGES===/{ /===/d; p; }')
|
|
96
|
-
ASSISTANT_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===ASSISTANT_MESSAGES===/,$ { /===/d; p; }')
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
# Fall back to node if python produced nothing
|
|
100
|
-
if [ -z "$USER_MESSAGES" ]; then
|
|
101
|
-
NODE_CMD=$(command -v node 2>/dev/null)
|
|
102
|
-
if [ -n "$NODE_CMD" ]; then
|
|
103
|
-
EXTRACTED=$("$NODE_CMD" -e "
|
|
104
|
-
const fs = require('fs');
|
|
105
|
-
const lines = fs.readFileSync(process.argv[1], 'utf8').split('\n');
|
|
106
|
-
const userMsgs = [], asstMsgs = [];
|
|
107
|
-
for (const line of lines) {
|
|
108
|
-
if (!line.trim()) continue;
|
|
109
|
-
try {
|
|
110
|
-
const obj = JSON.parse(line);
|
|
111
|
-
const msg = obj.message || obj;
|
|
112
|
-
const role = msg.role || obj.type || '';
|
|
113
|
-
const ts = obj.timestamp || obj.created_at || msg.timestamp || '';
|
|
114
|
-
const c = msg.content;
|
|
115
|
-
let text = Array.isArray(c)
|
|
116
|
-
? c.filter(x => x.type === 'text').map(x => x.text).join(' ')
|
|
117
|
-
: (typeof c === 'string' ? c : '');
|
|
118
|
-
text = text.trim();
|
|
119
|
-
if (!text) continue;
|
|
120
|
-
if (role === 'user') userMsgs.push([ts, text]);
|
|
121
|
-
else if (role === 'assistant') asstMsgs.push([ts, text]);
|
|
122
|
-
} catch(e) {}
|
|
123
|
-
}
|
|
124
|
-
console.log('===USER_MESSAGES===');
|
|
125
|
-
userMsgs.slice(-15).forEach(([ts, m], i) => {
|
|
126
|
-
const pre = ts ? '[' + ts.slice(0,16) + '] ' : '';
|
|
127
|
-
console.log((i+1) + '. ' + pre + m.slice(0,380) + (m.length>380?'...':''));
|
|
128
|
-
});
|
|
129
|
-
console.log('===ASSISTANT_MESSAGES===');
|
|
130
|
-
asstMsgs.slice(-10).forEach(([ts, m], i) => {
|
|
131
|
-
const pre = ts ? '[' + ts.slice(0,16) + '] ' : '';
|
|
132
|
-
console.log((i+1) + '. ' + pre + m.slice(0,280) + (m.length>280?'...':''));
|
|
133
|
-
});
|
|
134
|
-
" "$TRANSCRIPT" 2>/dev/null)
|
|
135
|
-
USER_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===USER_MESSAGES===/,/===ASSISTANT_MESSAGES===/{ /===/d; p; }')
|
|
136
|
-
ASSISTANT_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===ASSISTANT_MESSAGES===/,$ { /===/d; p; }')
|
|
137
|
-
fi
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
if [ -z "$USER_MESSAGES" ]; then
|
|
141
|
-
USER_MESSAGES="(no messages extracted — install python3 or node)"
|
|
142
|
-
fi
|
|
143
|
-
if [ -z "$ASSISTANT_MESSAGES" ]; then
|
|
144
|
-
ASSISTANT_MESSAGES="(no assistant messages extracted)"
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
# Write summary
|
|
148
|
-
cat > "$SUMMARY_FILE" << SUMMARY
|
|
149
|
-
# Pre-Compaction Session Summary
|
|
150
|
-
**Timestamp:** $(date +%Y-%m-%d\ %H:%M)
|
|
151
|
-
**Session ID:** ${SESSION_ID}
|
|
152
|
-
**Trigger:** ${TRIGGER}
|
|
153
|
-
|
|
154
|
-
## Stats
|
|
155
|
-
- User messages: ${USER_COUNT}
|
|
156
|
-
- Assistant messages: ${ASSISTANT_COUNT}
|
|
157
|
-
- Tool uses: ${TOOL_COUNT}
|
|
158
|
-
- Skills invoked: ${SKILLS}
|
|
159
|
-
|
|
160
|
-
## Conversation (Last 15 User Messages)
|
|
161
|
-
${USER_MESSAGES}
|
|
162
|
-
|
|
163
|
-
## Agent Context (Last 10 Assistant Messages)
|
|
164
|
-
${ASSISTANT_MESSAGES}
|
|
165
|
-
SUMMARY
|
|
166
|
-
|
|
167
|
-
# Keep last 10 summaries
|
|
168
|
-
ls -t "$BACKUP_DIR"/session_*_summary.md 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null
|
|
169
|
-
|
|
170
|
-
# Write pending-log flag for post-compaction auto-log via Stop hook
|
|
171
|
-
# Only for substantial sessions (5+ assistant messages)
|
|
172
|
-
if [ "$ASSISTANT_COUNT" -ge 5 ]; then
|
|
173
|
-
SESSION_KEY="${SESSION_ID:-global}"
|
|
174
|
-
echo "$SUMMARY_FILE" > "/tmp/mover_pendinglog_${SESSION_KEY}"
|
|
175
|
-
fi
|
|
176
|
-
|
|
177
|
-
exit 0
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# pre-compact-backup.sh
|
|
3
|
+
# Mover OS V4 - PreCompact Hook
|
|
4
|
+
# Two jobs:
|
|
5
|
+
# 1. Extracts a human-readable session summary BEFORE compaction.
|
|
6
|
+
# 2. Writes a "pending-log" flag so the Stop hook can auto-log post-compaction.
|
|
7
|
+
# PreCompact CANNOT block (Claude Code limitation). The Stop hook reads the flag
|
|
8
|
+
# after compaction and triggers /log from the saved summary.
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract fields
|
|
13
|
+
SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
14
|
+
TRANSCRIPT=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
15
|
+
TRIGGER=$(echo "$INPUT" | grep -o '"trigger"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
16
|
+
|
|
17
|
+
if [ "$TRIGGER" != "auto" ] && [ "$TRIGGER" != "manual" ]; then
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
BACKUP_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/session-backups"
|
|
26
|
+
mkdir -p "$BACKUP_DIR"
|
|
27
|
+
|
|
28
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
29
|
+
SUMMARY_FILE="$BACKUP_DIR/session_${TIMESTAMP}_summary.md"
|
|
30
|
+
|
|
31
|
+
# Count messages — grep -c returns "0" and exits 1 on no match, so suppress error, don't use ||
|
|
32
|
+
ASSISTANT_COUNT=$(grep -c '"type":"assistant"' "$TRANSCRIPT" 2>/dev/null); ASSISTANT_COUNT=${ASSISTANT_COUNT:-0}
|
|
33
|
+
USER_COUNT=$(grep -c '"type":"user"' "$TRANSCRIPT" 2>/dev/null); USER_COUNT=${USER_COUNT:-0}
|
|
34
|
+
TOOL_COUNT=$(grep -c '"tool_use"' "$TRANSCRIPT" 2>/dev/null); TOOL_COUNT=${TOOL_COUNT:-0}
|
|
35
|
+
|
|
36
|
+
# Extract skill invocations
|
|
37
|
+
SKILLS=$(grep -o '"skill":"[^"]*"' "$TRANSCRIPT" 2>/dev/null | sort -u | sed 's/"skill":"//;s/"//' | tr '\n' ', ')
|
|
38
|
+
SKILLS=${SKILLS:-none}
|
|
39
|
+
|
|
40
|
+
# Extract last 15 user messages AND last 10 assistant messages — try python3, fall back to node
|
|
41
|
+
USER_MESSAGES=""
|
|
42
|
+
ASSISTANT_MESSAGES=""
|
|
43
|
+
|
|
44
|
+
# Detect python: actually exec it, don't just check path (Windows has a stub that fails)
|
|
45
|
+
PYTHON_CMD=""
|
|
46
|
+
if python3 -c "print(1)" >/dev/null 2>&1; then
|
|
47
|
+
PYTHON_CMD="python3"
|
|
48
|
+
elif python -c "print(1)" >/dev/null 2>&1; then
|
|
49
|
+
PYTHON_CMD="python"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ -n "$PYTHON_CMD" ]; then
|
|
53
|
+
EXTRACTED=$("$PYTHON_CMD" - "$TRANSCRIPT" << 'PYEOF'
|
|
54
|
+
import json, sys
|
|
55
|
+
transcript_path = sys.argv[1]
|
|
56
|
+
user_msgs = []
|
|
57
|
+
asst_msgs = []
|
|
58
|
+
with open(transcript_path, encoding='utf-8', errors='replace') as f:
|
|
59
|
+
for line in f:
|
|
60
|
+
line = line.strip()
|
|
61
|
+
if not line:
|
|
62
|
+
continue
|
|
63
|
+
try:
|
|
64
|
+
obj = json.loads(line)
|
|
65
|
+
msg = obj.get('message', obj)
|
|
66
|
+
role = msg.get('role') or obj.get('type', '')
|
|
67
|
+
# Extract timestamp: try common field names in Claude JSONL format
|
|
68
|
+
ts = obj.get('timestamp') or obj.get('created_at') or msg.get('timestamp') or ''
|
|
69
|
+
content = msg.get('content', '')
|
|
70
|
+
if isinstance(content, list):
|
|
71
|
+
text = ' '.join(c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text')
|
|
72
|
+
elif isinstance(content, str):
|
|
73
|
+
text = content
|
|
74
|
+
else:
|
|
75
|
+
text = ''
|
|
76
|
+
text = text.strip()
|
|
77
|
+
if not text:
|
|
78
|
+
continue
|
|
79
|
+
if role == 'user':
|
|
80
|
+
user_msgs.append((ts, text))
|
|
81
|
+
elif role == 'assistant':
|
|
82
|
+
asst_msgs.append((ts, text))
|
|
83
|
+
except Exception:
|
|
84
|
+
continue
|
|
85
|
+
print("===USER_MESSAGES===")
|
|
86
|
+
for i, (ts, msg) in enumerate(user_msgs[-15:], 1):
|
|
87
|
+
prefix = f"[{ts[:16]}] " if ts else ""
|
|
88
|
+
print(f"{i}. {prefix}{msg[:380]}{'...' if len(msg) > 380 else ''}")
|
|
89
|
+
print("===ASSISTANT_MESSAGES===")
|
|
90
|
+
for i, (ts, msg) in enumerate(asst_msgs[-10:], 1):
|
|
91
|
+
prefix = f"[{ts[:16]}] " if ts else ""
|
|
92
|
+
print(f"{i}. {prefix}{msg[:280]}{'...' if len(msg) > 280 else ''}")
|
|
93
|
+
PYEOF
|
|
94
|
+
)
|
|
95
|
+
USER_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===USER_MESSAGES===/,/===ASSISTANT_MESSAGES===/{ /===/d; p; }')
|
|
96
|
+
ASSISTANT_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===ASSISTANT_MESSAGES===/,$ { /===/d; p; }')
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Fall back to node if python produced nothing
|
|
100
|
+
if [ -z "$USER_MESSAGES" ]; then
|
|
101
|
+
NODE_CMD=$(command -v node 2>/dev/null)
|
|
102
|
+
if [ -n "$NODE_CMD" ]; then
|
|
103
|
+
EXTRACTED=$("$NODE_CMD" -e "
|
|
104
|
+
const fs = require('fs');
|
|
105
|
+
const lines = fs.readFileSync(process.argv[1], 'utf8').split('\n');
|
|
106
|
+
const userMsgs = [], asstMsgs = [];
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (!line.trim()) continue;
|
|
109
|
+
try {
|
|
110
|
+
const obj = JSON.parse(line);
|
|
111
|
+
const msg = obj.message || obj;
|
|
112
|
+
const role = msg.role || obj.type || '';
|
|
113
|
+
const ts = obj.timestamp || obj.created_at || msg.timestamp || '';
|
|
114
|
+
const c = msg.content;
|
|
115
|
+
let text = Array.isArray(c)
|
|
116
|
+
? c.filter(x => x.type === 'text').map(x => x.text).join(' ')
|
|
117
|
+
: (typeof c === 'string' ? c : '');
|
|
118
|
+
text = text.trim();
|
|
119
|
+
if (!text) continue;
|
|
120
|
+
if (role === 'user') userMsgs.push([ts, text]);
|
|
121
|
+
else if (role === 'assistant') asstMsgs.push([ts, text]);
|
|
122
|
+
} catch(e) {}
|
|
123
|
+
}
|
|
124
|
+
console.log('===USER_MESSAGES===');
|
|
125
|
+
userMsgs.slice(-15).forEach(([ts, m], i) => {
|
|
126
|
+
const pre = ts ? '[' + ts.slice(0,16) + '] ' : '';
|
|
127
|
+
console.log((i+1) + '. ' + pre + m.slice(0,380) + (m.length>380?'...':''));
|
|
128
|
+
});
|
|
129
|
+
console.log('===ASSISTANT_MESSAGES===');
|
|
130
|
+
asstMsgs.slice(-10).forEach(([ts, m], i) => {
|
|
131
|
+
const pre = ts ? '[' + ts.slice(0,16) + '] ' : '';
|
|
132
|
+
console.log((i+1) + '. ' + pre + m.slice(0,280) + (m.length>280?'...':''));
|
|
133
|
+
});
|
|
134
|
+
" "$TRANSCRIPT" 2>/dev/null)
|
|
135
|
+
USER_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===USER_MESSAGES===/,/===ASSISTANT_MESSAGES===/{ /===/d; p; }')
|
|
136
|
+
ASSISTANT_MESSAGES=$(echo "$EXTRACTED" | sed -n '/===ASSISTANT_MESSAGES===/,$ { /===/d; p; }')
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [ -z "$USER_MESSAGES" ]; then
|
|
141
|
+
USER_MESSAGES="(no messages extracted — install python3 or node)"
|
|
142
|
+
fi
|
|
143
|
+
if [ -z "$ASSISTANT_MESSAGES" ]; then
|
|
144
|
+
ASSISTANT_MESSAGES="(no assistant messages extracted)"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Write summary
|
|
148
|
+
cat > "$SUMMARY_FILE" << SUMMARY
|
|
149
|
+
# Pre-Compaction Session Summary
|
|
150
|
+
**Timestamp:** $(date +%Y-%m-%d\ %H:%M)
|
|
151
|
+
**Session ID:** ${SESSION_ID}
|
|
152
|
+
**Trigger:** ${TRIGGER}
|
|
153
|
+
|
|
154
|
+
## Stats
|
|
155
|
+
- User messages: ${USER_COUNT}
|
|
156
|
+
- Assistant messages: ${ASSISTANT_COUNT}
|
|
157
|
+
- Tool uses: ${TOOL_COUNT}
|
|
158
|
+
- Skills invoked: ${SKILLS}
|
|
159
|
+
|
|
160
|
+
## Conversation (Last 15 User Messages)
|
|
161
|
+
${USER_MESSAGES}
|
|
162
|
+
|
|
163
|
+
## Agent Context (Last 10 Assistant Messages)
|
|
164
|
+
${ASSISTANT_MESSAGES}
|
|
165
|
+
SUMMARY
|
|
166
|
+
|
|
167
|
+
# Keep last 10 summaries
|
|
168
|
+
ls -t "$BACKUP_DIR"/session_*_summary.md 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null
|
|
169
|
+
|
|
170
|
+
# Write pending-log flag for post-compaction auto-log via Stop hook
|
|
171
|
+
# Only for substantial sessions (5+ assistant messages)
|
|
172
|
+
if [ "$ASSISTANT_COUNT" -ge 5 ]; then
|
|
173
|
+
SESSION_KEY="${SESSION_ID:-global}"
|
|
174
|
+
echo "$SUMMARY_FILE" > "/tmp/mover_pendinglog_${SESSION_KEY}"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
exit 0
|
|
@@ -1,73 +1,135 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# session-log-reminder.sh
|
|
3
|
-
# Mover OS V4 - Stop Hook
|
|
4
|
-
# Two modes:
|
|
5
|
-
# 1. POST-COMPACTION
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-log-reminder.sh
|
|
3
|
+
# Mover OS V4 - Stop Hook
|
|
4
|
+
# Two modes:
|
|
5
|
+
# 1. POST-COMPACTION BACKGROUND LOG: If PreCompact left a pending-log flag, instruct
|
|
6
|
+
# the main agent to spawn a background Sonnet agent to handle session logging.
|
|
7
|
+
# This avoids filling the main context with /log work (which accelerates compaction).
|
|
8
|
+
# 2. SESSION REMINDER: After 7+ exchanges, remind user to run /log. Fires once
|
|
9
|
+
# per session (mkdir gate). For sessions that never reach compaction.
|
|
10
|
+
|
|
11
|
+
INPUT=$(cat)
|
|
12
|
+
|
|
13
|
+
SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
14
|
+
TRANSCRIPT=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
|
|
15
|
+
|
|
16
|
+
SESSION_KEY="${SESSION_ID:-global}"
|
|
17
|
+
|
|
18
|
+
# --- Check if /log already ran this session ---
|
|
19
|
+
LOG_DONE_FILE="/tmp/mover_logdone_${SESSION_KEY}"
|
|
20
|
+
if [ -f "$LOG_DONE_FILE" ]; then
|
|
21
|
+
# /log already executed — but if session continued significantly past logging,
|
|
22
|
+
# clear the marker so Mode 2 can re-fire a reminder for the tail end.
|
|
23
|
+
MSG_COUNT_CHECK=0
|
|
24
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
25
|
+
MSG_COUNT_CHECK=$(grep -cE '"role":"user"|"type":"user"' "$TRANSCRIPT" 2>/dev/null)
|
|
26
|
+
MSG_COUNT_CHECK=${MSG_COUNT_CHECK:-0}
|
|
27
|
+
fi
|
|
28
|
+
LOG_DONE_AGE=0
|
|
29
|
+
if command -v stat > /dev/null 2>&1; then
|
|
30
|
+
LOG_DONE_MTIME=$(stat -c %Y "$LOG_DONE_FILE" 2>/dev/null || stat -f %m "$LOG_DONE_FILE" 2>/dev/null)
|
|
31
|
+
NOW_EPOCH=$(date +%s)
|
|
32
|
+
if [ -n "$LOG_DONE_MTIME" ]; then
|
|
33
|
+
LOG_DONE_AGE=$(( NOW_EPOCH - LOG_DONE_MTIME ))
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
# If 15+ user messages AND 10+ minutes since last log: allow re-reminder
|
|
37
|
+
if [ "$MSG_COUNT_CHECK" -gt 15 ] && [ "$LOG_DONE_AGE" -gt 600 ]; then
|
|
38
|
+
rm -f "$LOG_DONE_FILE"
|
|
39
|
+
rm -f "/tmp/mover_logremind_${SESSION_KEY}" 2>/dev/null
|
|
40
|
+
rmdir "/tmp/mover_logremind_${SESSION_KEY}" 2>/dev/null
|
|
41
|
+
# Fall through to Mode 1/2 checks below
|
|
42
|
+
else
|
|
43
|
+
rm -f "/tmp/mover_pendinglog_${SESSION_KEY}"
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# --- MODE 1: Post-compaction background log ---
|
|
49
|
+
PENDING_FILE="/tmp/mover_pendinglog_${SESSION_KEY}"
|
|
50
|
+
if [ -f "$PENDING_FILE" ]; then
|
|
51
|
+
SUMMARY_PATH=$(cat "$PENDING_FILE")
|
|
52
|
+
rm -f "$PENDING_FILE"
|
|
53
|
+
if [ -n "$SUMMARY_PATH" ] && [ -f "$SUMMARY_PATH" ]; then
|
|
54
|
+
# Extract pre-compaction timestamp from summary file
|
|
55
|
+
COMPACT_TS=$(grep -oP '^\*\*Timestamp:\*\* \K.*' "$SUMMARY_PATH" 2>/dev/null)
|
|
56
|
+
# Fallback: try without -P (macOS/busybox)
|
|
57
|
+
if [ -z "$COMPACT_TS" ]; then
|
|
58
|
+
COMPACT_TS=$(grep '^\*\*Timestamp:\*\*' "$SUMMARY_PATH" 2>/dev/null | sed 's/\*\*Timestamp:\*\* //')
|
|
59
|
+
fi
|
|
60
|
+
CURRENT_TS=$(date '+%Y-%m-%dT%H:%M')
|
|
61
|
+
CURRENT_DATE=$(date '+%Y-%m-%d')
|
|
62
|
+
CURRENT_MONTH=$(date '+%Y-%m')
|
|
63
|
+
WORKING_DIR=$(pwd)
|
|
64
|
+
|
|
65
|
+
# Resolve vault root: walk up from working dir looking for 02_Areas/Engine/
|
|
66
|
+
VAULT_ROOT=""
|
|
67
|
+
CHECK_DIR="$WORKING_DIR"
|
|
68
|
+
for i in 1 2 3 4 5; do
|
|
69
|
+
if [ -d "$CHECK_DIR/02_Areas/Engine" ]; then
|
|
70
|
+
VAULT_ROOT="$CHECK_DIR"
|
|
71
|
+
break
|
|
72
|
+
fi
|
|
73
|
+
CHECK_DIR=$(dirname "$CHECK_DIR")
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
# Derive key file paths
|
|
77
|
+
DAILY_NOTE="${VAULT_ROOT}/02_Areas/Engine/Dailies/${CURRENT_MONTH}/Daily - ${CURRENT_DATE}.md"
|
|
78
|
+
ACTIVE_CONTEXT="${VAULT_ROOT}/02_Areas/Engine/Active_Context.md"
|
|
79
|
+
|
|
80
|
+
# Detect project context (if inside 01_Projects/[Name]/)
|
|
81
|
+
PROJECT_NAME=""
|
|
82
|
+
PLAN_MD=""
|
|
83
|
+
PROJECT_STATE=""
|
|
84
|
+
case "$WORKING_DIR" in
|
|
85
|
+
*01_Projects/*)
|
|
86
|
+
PROJECT_NAME=$(echo "$WORKING_DIR" | sed 's|.*01_Projects/||' | sed 's|/.*||')
|
|
87
|
+
PROJECT_DIR="${VAULT_ROOT}/01_Projects/${PROJECT_NAME}"
|
|
88
|
+
if [ -f "$PROJECT_DIR/dev/plan.md" ]; then
|
|
89
|
+
PLAN_MD="$PROJECT_DIR/dev/plan.md"
|
|
90
|
+
elif [ -f "$PROJECT_DIR/plan.md" ]; then
|
|
91
|
+
PLAN_MD="$PROJECT_DIR/plan.md"
|
|
92
|
+
fi
|
|
93
|
+
if [ -f "$PROJECT_DIR/dev/project_state.md" ]; then
|
|
94
|
+
PROJECT_STATE="$PROJECT_DIR/dev/project_state.md"
|
|
95
|
+
elif [ -f "$PROJECT_DIR/project_state.md" ]; then
|
|
96
|
+
PROJECT_STATE="$PROJECT_DIR/project_state.md"
|
|
97
|
+
fi
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
echo "{\"decision\":\"block\",\"reason\":\"POST-COMPACTION BACKGROUND LOG: Context was just compacted. Instead of running the full /log workflow (which fills your context and causes more compactions), spawn a BACKGROUND Task agent to handle it. Do this: 1. Write a brief summary of what was worked on this session (from your compacted context — you have the conversation summary). 2. Spawn: Task tool with subagent_type='general-purpose', model='sonnet', run_in_background=true. 3. In the Task prompt, include: (a) your session context summary, (b) instructions below. 4. Mark session as logged: echo done > /tmp/mover_logdone_${SESSION_KEY}. 5. Continue with whatever you were doing. INSTRUCTIONS FOR THE BACKGROUND AGENT: You are a session logger for Mover OS. Process this session log entry. Timestamps: current=${CURRENT_TS}, pre-compaction=${COMPACT_TS:-unknown}. Session backup summary: ${SUMMARY_PATH}. Working directory: ${WORKING_DIR}. Project: ${PROJECT_NAME:-none}. STEPS: (1) Read the session backup summary at the path above. (2) Read the Daily Note at: ${DAILY_NOTE} — if it does not exist, create it with a basic template. Append a session log entry under '## Session Log' marked [COMPACTED]. Format: '### Session Log [ATTENDED] — ~HH:MM-HH:MM [Claude Code] [${PROJECT_NAME:-Unknown}] [COMPACTED]' then subsections with ~timestamps and 3-4 line descriptions. (3) If plan.md exists at: ${PLAN_MD:-N/A} — read it, mark completed tasks [x], append to EXECUTION LOG. (4) If project_state.md exists at: ${PROJECT_STATE:-N/A} — append new Solutions Ledger entries (deduplicate first), Changelog entry, System State Snapshot. (5) Read Active_Context at: ${ACTIVE_CONTEXT} — prepend 1-line summary to Active Sessions (keep 5 max, remove oldest), update log_last_run to ${CURRENT_TS}. (6) Git commit changed files if inside a repo (git add specific files only, never git add . at root, never commit Dailies). CONSTRAINTS: append-only (never delete), cite sources, mark entries [COMPACTED], Windows=no && in commands.\"}"
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# --- MODE 2: Session reminder (once per session) ---
|
|
107
|
+
MARKER_DIR="/tmp/mover_logremind_${SESSION_KEY}"
|
|
108
|
+
|
|
109
|
+
# Atomic once-per-session gate. mkdir succeeds for exactly one process.
|
|
110
|
+
if ! mkdir "$MARKER_DIR" 2>/dev/null; then
|
|
111
|
+
exit 0
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Only for substantial sessions (count USER messages, not assistant — tool calls inflate assistant count)
|
|
115
|
+
MSG_COUNT=0
|
|
116
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
117
|
+
MSG_COUNT=$(grep -cE '"role":"user"|"type":"user"' "$TRANSCRIPT" 2>/dev/null)
|
|
118
|
+
MSG_COUNT=${MSG_COUNT:-0}
|
|
119
|
+
fi
|
|
120
|
+
if [ "$MSG_COUNT" -lt 7 ]; then
|
|
121
|
+
rmdir "$MARKER_DIR" 2>/dev/null
|
|
122
|
+
exit 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Skip reminder for system maintenance workflows (update, setup, walkthrough)
|
|
126
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
127
|
+
SYSTEM_WF=$(grep -cE '/update|/setup|/walkthrough' "$TRANSCRIPT" 2>/dev/null)
|
|
128
|
+
if [ "${SYSTEM_WF:-0}" -gt 0 ]; then
|
|
129
|
+
rmdir "$MARKER_DIR" 2>/dev/null
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
echo '{"decision":"block","reason":"SESSION REMINDER: Ask the user (one sentence) if they want you to run /log to capture the session. Then continue with your response."}'
|
|
135
|
+
exit 0
|