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.
Files changed (47) hide show
  1. package/install.js +4 -2
  2. package/package.json +1 -1
  3. package/src/hooks/context-staleness.sh +46 -46
  4. package/src/hooks/dirty-tree-guard.sh +33 -33
  5. package/src/hooks/engine-protection.sh +43 -43
  6. package/src/hooks/git-safety.sh +47 -47
  7. package/src/hooks/pre-compact-backup.sh +177 -177
  8. package/src/hooks/session-log-reminder.sh +135 -73
  9. package/src/skills/systematic-debugging/CREATION-LOG.md +119 -119
  10. package/src/skills/systematic-debugging/SKILL.md +296 -296
  11. package/src/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  12. package/src/skills/systematic-debugging/condition-based-waiting.md +115 -115
  13. package/src/skills/systematic-debugging/defense-in-depth.md +122 -122
  14. package/src/skills/systematic-debugging/find-polluter.sh +63 -63
  15. package/src/skills/systematic-debugging/root-cause-tracing.md +169 -169
  16. package/src/skills/systematic-debugging/test-academic.md +14 -14
  17. package/src/skills/systematic-debugging/test-pressure-1.md +58 -58
  18. package/src/skills/systematic-debugging/test-pressure-2.md +68 -68
  19. package/src/skills/systematic-debugging/test-pressure-3.md +69 -69
  20. package/src/structure/01_Projects/_Template Project/plan.md +55 -55
  21. package/src/structure/01_Projects/_Template Project/project_brief.md +45 -45
  22. package/src/structure/02_Areas/Engine/Active_Context.md +146 -146
  23. package/src/structure/02_Areas/Engine/Auto_Learnings.md +36 -36
  24. package/src/structure/02_Areas/Engine/Daily_Template.md +133 -133
  25. package/src/structure/02_Areas/Engine/Identity_Prime_template.md +86 -86
  26. package/src/structure/02_Areas/Engine/Mover_Dossier.md +120 -120
  27. package/src/structure/02_Areas/Engine/Strategy_template.md +65 -65
  28. package/src/structure/03_Library/SOPs/Tech_Stack.md +55 -55
  29. package/src/structure/03_Library/SOPs/Zone_Operating.md +57 -57
  30. package/src/system/V4_CONTEXT.md +262 -262
  31. package/src/theme/minimal-theme.css +271 -271
  32. package/src/workflows/analyse-day.md +401 -401
  33. package/src/workflows/debug-resistance.md +180 -180
  34. package/src/workflows/harvest.md +239 -239
  35. package/src/workflows/ignite.md +720 -720
  36. package/src/workflows/init-plan.md +16 -16
  37. package/src/workflows/morning.md +222 -222
  38. package/src/workflows/overview.md +203 -203
  39. package/src/workflows/pivot-strategy.md +218 -218
  40. package/src/workflows/plan-tomorrow.md +308 -308
  41. package/src/workflows/primer.md +207 -207
  42. package/src/workflows/reboot.md +201 -201
  43. package/src/workflows/refactor-plan.md +135 -135
  44. package/src/workflows/review-week.md +558 -558
  45. package/src/workflows/setup.md +388 -388
  46. package/src/workflows/update.md +10 -13
  47. 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 AUTO-LOG: If PreCompact left a pending-log flag, auto-execute
6
- # /log from the saved summary. Fires once (flag is consumed).
7
- # 2. SESSION REMINDER: After 7+ exchanges, remind user to run /log. Fires once
8
- # per session (mkdir gate). For sessions that never reach compaction.
9
-
10
- INPUT=$(cat)
11
-
12
- SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
13
- TRANSCRIPT=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')
14
-
15
- SESSION_KEY="${SESSION_ID:-global}"
16
-
17
- # --- Check if /log already ran this session ---
18
- LOG_DONE_FILE="/tmp/mover_logdone_${SESSION_KEY}"
19
- if [ -f "$LOG_DONE_FILE" ]; then
20
- # /log already executed consume any stale pending flag and exit silently
21
- rm -f "/tmp/mover_pendinglog_${SESSION_KEY}"
22
- exit 0
23
- fi
24
-
25
- # --- MODE 1: Post-compaction auto-log ---
26
- PENDING_FILE="/tmp/mover_pendinglog_${SESSION_KEY}"
27
- if [ -f "$PENDING_FILE" ]; then
28
- SUMMARY_PATH=$(cat "$PENDING_FILE")
29
- rm -f "$PENDING_FILE"
30
- if [ -n "$SUMMARY_PATH" ] && [ -f "$SUMMARY_PATH" ]; then
31
- # Extract pre-compaction timestamp from summary file
32
- COMPACT_TS=$(grep -oP '^\*\*Timestamp:\*\* \K.*' "$SUMMARY_PATH" 2>/dev/null)
33
- # Fallback: try without -P (macOS/busybox)
34
- if [ -z "$COMPACT_TS" ]; then
35
- COMPACT_TS=$(grep '^\*\*Timestamp:\*\*' "$SUMMARY_PATH" 2>/dev/null | sed 's/\*\*Timestamp:\*\* //')
36
- fi
37
- # Current time is the definitive post-compaction anchor
38
- CURRENT_TS=$(date '+%Y-%m-%dT%H:%M')
39
- echo "{\"decision\":\"block\",\"reason\":\"POST-COMPACTION AUTO-LOG: Context was just compacted. You MUST run the /log workflow now. Use the Skill tool to invoke skill 'log'. CONTEXT FOR /log: Current time: ${CURRENT_TS}. Pre-compaction ended: ${COMPACT_TS:-unknown}. Use these as timestamp anchors — do NOT estimate or project timestamps beyond ${CURRENT_TS}. Read the session summary at: ${SUMMARY_PATH} for what happened before compaction. Mark session log entries as [COMPACTED]. Skip Steps 3.7-3.13 (commitments, waiting-for, decisions, inputs) — these require full conversation context that may be lost in summarization. KEY STEPS the /log workflow will handle: (1) resolve vault root, (2) write session log to Daily Note, (3) update plan.md execution log, (4) update project_state.md (Solutions Ledger, Changelog, Snapshot), (5) update Active_Context sessions buffer + log_last_run, (6) git commit. If the Skill tool is unavailable, execute these steps manually.\"}"
40
- exit 0
41
- fi
42
- fi
43
-
44
- # --- MODE 2: Session reminder (once per session) ---
45
- MARKER_DIR="/tmp/mover_logremind_${SESSION_KEY}"
46
-
47
- # Atomic once-per-session gate. mkdir succeeds for exactly one process.
48
- if ! mkdir "$MARKER_DIR" 2>/dev/null; then
49
- exit 0
50
- fi
51
-
52
- # Only for substantial sessions (count USER messages, not assistant — tool calls inflate assistant count)
53
- MSG_COUNT=0
54
- if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
55
- MSG_COUNT=$(grep -cE '"role":"user"|"type":"user"' "$TRANSCRIPT" 2>/dev/null)
56
- MSG_COUNT=${MSG_COUNT:-0}
57
- fi
58
- if [ "$MSG_COUNT" -lt 7 ]; then
59
- rmdir "$MARKER_DIR" 2>/dev/null
60
- exit 0
61
- fi
62
-
63
- # Skip reminder for system maintenance workflows (update, setup, walkthrough)
64
- if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
65
- SYSTEM_WF=$(grep -cE '/update|/setup|/walkthrough' "$TRANSCRIPT" 2>/dev/null)
66
- if [ "${SYSTEM_WF:-0}" -gt 0 ]; then
67
- rmdir "$MARKER_DIR" 2>/dev/null
68
- exit 0
69
- fi
70
- fi
71
-
72
- 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."}'
73
- exit 0
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