instar 0.25.7 → 0.25.9
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/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +234 -0
- package/.claude/skills/autonomous/hooks/hooks.json +15 -0
- package/.claude/skills/autonomous/scripts/setup-autonomous.sh +166 -0
- package/.claude/skills/autonomous/skill.md +254 -0
- package/dist/cli.js +0 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +20 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +47 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +3 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +24 -2
- package/dist/core/SessionManager.js.map +1 -1
- package/package.json +2 -1
- package/src/data/builtin-manifest.json +18 -18
- package/upgrades/0.25.8.md +24 -0
- package/upgrades/0.25.9.md +19 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Autonomous Mode Stop Hook
|
|
4
|
+
# Prevents session exit when autonomous mode is active.
|
|
5
|
+
# Feeds the goal and task list back to continue working.
|
|
6
|
+
#
|
|
7
|
+
# SESSION-SCOPED: Only blocks the session that activated autonomous mode.
|
|
8
|
+
# Other sessions on the same machine pass through unaffected.
|
|
9
|
+
#
|
|
10
|
+
# RESPECTS:
|
|
11
|
+
# - Session isolation (only blocks the autonomous session)
|
|
12
|
+
# - Emergency stop signals (user says "stop everything")
|
|
13
|
+
# - Duration expiry
|
|
14
|
+
# - Genuine completion (all tasks done, promise output)
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# Read hook input from stdin
|
|
19
|
+
HOOK_INPUT=$(cat)
|
|
20
|
+
|
|
21
|
+
# Check if autonomous mode is active
|
|
22
|
+
STATE_FILE=".instar/autonomous-state.local.md"
|
|
23
|
+
|
|
24
|
+
if [[ ! -f "$STATE_FILE" ]]; then
|
|
25
|
+
# No active autonomous session — allow exit
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Parse YAML frontmatter
|
|
30
|
+
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
|
|
31
|
+
|
|
32
|
+
ACTIVE=$(echo "$FRONTMATTER" | grep '^active:' | sed 's/active: *//')
|
|
33
|
+
if [[ "$ACTIVE" != "true" ]]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# SESSION ISOLATION: Only block the session that started autonomous mode.
|
|
38
|
+
# Uses self-bootstrapping: if no session_id in state file yet, the FIRST
|
|
39
|
+
# session to trigger the hook claims it. All other sessions see a mismatch.
|
|
40
|
+
STATE_SESSION=$(echo "$FRONTMATTER" | grep '^session_id:' | sed 's/^session_id: *//' | tr -d '"' || true)
|
|
41
|
+
|
|
42
|
+
# VALIDATE: If state has a session_id but it's not a valid UUID, treat as empty.
|
|
43
|
+
# Claude sometimes writes a custom string instead of the real $CLAUDE_CODE_SESSION_ID.
|
|
44
|
+
# Non-UUID values will never match the real hook session_id, causing the hook to
|
|
45
|
+
# fail-open and allow premature exit. By clearing invalid values, we fall through
|
|
46
|
+
# to self-bootstrap, which captures the REAL session_id from the first hook call.
|
|
47
|
+
UUID_REGEX='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
48
|
+
if [[ -n "$STATE_SESSION" ]] && ! [[ "$STATE_SESSION" =~ $UUID_REGEX ]]; then
|
|
49
|
+
echo "[autonomous] Invalid session_id in state file (not UUID): '$STATE_SESSION' — falling back to self-bootstrap" >&2
|
|
50
|
+
STATE_SESSION=""
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
HOOK_SESSION=$(echo "$HOOK_INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "")
|
|
54
|
+
|
|
55
|
+
# If hook has no session_id → fail OPEN (unknown context, don't trap)
|
|
56
|
+
if [[ -z "$HOOK_SESSION" ]]; then
|
|
57
|
+
echo "⚠️ Autonomous mode: No session_id in hook input — fail-open (allowing exit)" >&2
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# SELF-BOOTSTRAP: If state has no session_id yet, claim it from this hook call.
|
|
62
|
+
# The first session to fire the hook becomes the autonomous session.
|
|
63
|
+
if [[ -z "$STATE_SESSION" ]]; then
|
|
64
|
+
# Atomic claim: write session_id to state file
|
|
65
|
+
TEMP_FILE="${STATE_FILE}.claim.$$"
|
|
66
|
+
sed "s/^session_id:.*/session_id: \"${HOOK_SESSION}\"/" "$STATE_FILE" > "$TEMP_FILE"
|
|
67
|
+
mv "$TEMP_FILE" "$STATE_FILE"
|
|
68
|
+
STATE_SESSION="$HOOK_SESSION"
|
|
69
|
+
echo "[autonomous] Session $HOOK_SESSION claimed autonomous mode" >&2
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Different session → allow exit (fail-open for non-autonomous sessions)
|
|
73
|
+
if [[ "$STATE_SESSION" != "$HOOK_SESSION" ]]; then
|
|
74
|
+
exit 0
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Same session — this IS the autonomous session, proceed with block logic
|
|
78
|
+
|
|
79
|
+
ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
|
|
80
|
+
DURATION_SECONDS=$(echo "$FRONTMATTER" | grep '^duration_seconds:' | sed 's/duration_seconds: *//')
|
|
81
|
+
STARTED_AT=$(echo "$FRONTMATTER" | grep '^started_at:' | sed 's/started_at: *//' | tr -d '"')
|
|
82
|
+
COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | tr -d '"')
|
|
83
|
+
REPORT_TOPIC=$(echo "$FRONTMATTER" | grep '^report_topic:' | sed 's/report_topic: *//' | tr -d '"')
|
|
84
|
+
|
|
85
|
+
# Validate iteration
|
|
86
|
+
if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then
|
|
87
|
+
echo "⚠️ Autonomous mode: State file corrupted (bad iteration)" >&2
|
|
88
|
+
rm "$STATE_FILE"
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Check duration expiry
|
|
93
|
+
if [[ "$DURATION_SECONDS" =~ ^[0-9]+$ ]] && [[ $DURATION_SECONDS -gt 0 ]]; then
|
|
94
|
+
START_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$STARTED_AT" +%s 2>/dev/null || date -d "$STARTED_AT" +%s 2>/dev/null || echo "0")
|
|
95
|
+
NOW_EPOCH=$(date +%s)
|
|
96
|
+
ELAPSED=$(( NOW_EPOCH - START_EPOCH ))
|
|
97
|
+
if [[ $ELAPSED -ge $DURATION_SECONDS ]]; then
|
|
98
|
+
echo "⏰ Autonomous mode: Duration expired ($ELAPSED seconds elapsed)."
|
|
99
|
+
echo " Session is free to exit."
|
|
100
|
+
rm "$STATE_FILE"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
REMAINING=$(( DURATION_SECONDS - ELAPSED ))
|
|
104
|
+
REMAINING_MIN=$(( REMAINING / 60 ))
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Check for emergency stop (look in recent messages)
|
|
108
|
+
# The MessageSentinel handles this at the messaging layer, but also check here
|
|
109
|
+
if [[ -f ".instar/autonomous-emergency-stop" ]]; then
|
|
110
|
+
echo "🛑 Autonomous mode: Emergency stop detected."
|
|
111
|
+
rm "$STATE_FILE"
|
|
112
|
+
rm -f ".instar/autonomous-emergency-stop"
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Get transcript and check for completion promise
|
|
117
|
+
TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path' 2>/dev/null || echo "")
|
|
118
|
+
|
|
119
|
+
if [[ -n "$TRANSCRIPT_PATH" ]] && [[ -f "$TRANSCRIPT_PATH" ]]; then
|
|
120
|
+
# Check last assistant message for completion promise
|
|
121
|
+
LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1 || echo "")
|
|
122
|
+
|
|
123
|
+
if [[ -n "$LAST_LINE" ]]; then
|
|
124
|
+
LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r '
|
|
125
|
+
.message.content |
|
|
126
|
+
map(select(.type == "text")) |
|
|
127
|
+
map(.text) |
|
|
128
|
+
join("\n")
|
|
129
|
+
' 2>/dev/null || echo "")
|
|
130
|
+
|
|
131
|
+
# Check for completion promise in <promise> tags
|
|
132
|
+
if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then
|
|
133
|
+
PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
|
|
134
|
+
|
|
135
|
+
if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
|
|
136
|
+
echo "✅ Autonomous mode: Completion promise detected — <promise>$COMPLETION_PROMISE</promise>"
|
|
137
|
+
echo " Session is free to exit. Good work!"
|
|
138
|
+
rm "$STATE_FILE"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Not complete — block exit and feed task list back
|
|
146
|
+
NEXT_ITERATION=$((ITERATION + 1))
|
|
147
|
+
|
|
148
|
+
# Extract prompt (everything after closing ---)
|
|
149
|
+
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE")
|
|
150
|
+
|
|
151
|
+
if [[ -z "$PROMPT_TEXT" ]]; then
|
|
152
|
+
echo "⚠️ Autonomous mode: State file has no task content" >&2
|
|
153
|
+
rm "$STATE_FILE"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Update iteration counter
|
|
158
|
+
TEMP_FILE="${STATE_FILE}.tmp.$$"
|
|
159
|
+
sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$STATE_FILE" > "$TEMP_FILE"
|
|
160
|
+
mv "$TEMP_FILE" "$STATE_FILE"
|
|
161
|
+
|
|
162
|
+
# ── Progress Report Check ──
|
|
163
|
+
# Check if it's time to send a progress report
|
|
164
|
+
REPORT_INTERVAL=$(echo "$FRONTMATTER" | grep '^report_interval:' | sed 's/report_interval: *//' | tr -d '"')
|
|
165
|
+
LAST_REPORT_AT=$(echo "$FRONTMATTER" | grep '^last_report_at:' | sed 's/last_report_at: *//' | tr -d '"')
|
|
166
|
+
|
|
167
|
+
# Convert report interval to seconds
|
|
168
|
+
REPORT_INTERVAL_SECS=1800 # default 30 minutes
|
|
169
|
+
if [[ "$REPORT_INTERVAL" =~ ^([0-9]+)m$ ]]; then
|
|
170
|
+
REPORT_INTERVAL_SECS=$(( ${BASH_REMATCH[1]} * 60 ))
|
|
171
|
+
elif [[ "$REPORT_INTERVAL" =~ ^([0-9]+)h$ ]]; then
|
|
172
|
+
REPORT_INTERVAL_SECS=$(( ${BASH_REMATCH[1]} * 3600 ))
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
REPORT_DUE="false"
|
|
176
|
+
NOW_EPOCH=$(date +%s)
|
|
177
|
+
|
|
178
|
+
if [[ -z "$LAST_REPORT_AT" ]] || [[ "$LAST_REPORT_AT" == "null" ]] || [[ "$LAST_REPORT_AT" == '""' ]]; then
|
|
179
|
+
# No report sent yet — due if we've been running for at least one interval
|
|
180
|
+
if [[ -n "$STARTED_AT" ]]; then
|
|
181
|
+
START_EPOCH_R=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$STARTED_AT" +%s 2>/dev/null || date -d "$STARTED_AT" +%s 2>/dev/null || echo "0")
|
|
182
|
+
ELAPSED_SINCE_START=$(( NOW_EPOCH - START_EPOCH_R ))
|
|
183
|
+
if [[ $ELAPSED_SINCE_START -ge $REPORT_INTERVAL_SECS ]]; then
|
|
184
|
+
REPORT_DUE="true"
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
else
|
|
188
|
+
# Check time since last report
|
|
189
|
+
LAST_REPORT_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_REPORT_AT" +%s 2>/dev/null || date -d "$LAST_REPORT_AT" +%s 2>/dev/null || echo "0")
|
|
190
|
+
ELAPSED_SINCE_REPORT=$(( NOW_EPOCH - LAST_REPORT_EPOCH ))
|
|
191
|
+
if [[ $ELAPSED_SINCE_REPORT -ge $REPORT_INTERVAL_SECS ]]; then
|
|
192
|
+
REPORT_DUE="true"
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# If report is due, update last_report_at in state file
|
|
197
|
+
REPORT_DIRECTIVE=""
|
|
198
|
+
if [[ "$REPORT_DUE" == "true" ]]; then
|
|
199
|
+
REPORT_NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
200
|
+
# Update or add last_report_at in frontmatter
|
|
201
|
+
if grep -q '^last_report_at:' "$STATE_FILE"; then
|
|
202
|
+
TEMP_FILE2="${STATE_FILE}.tmp2.$$"
|
|
203
|
+
sed "s/^last_report_at: .*/last_report_at: \"$REPORT_NOW\"/" "$STATE_FILE" > "$TEMP_FILE2"
|
|
204
|
+
mv "$TEMP_FILE2" "$STATE_FILE"
|
|
205
|
+
else
|
|
206
|
+
# Add last_report_at before the closing ---
|
|
207
|
+
TEMP_FILE2="${STATE_FILE}.tmp2.$$"
|
|
208
|
+
sed "0,/^---$/! { /^---$/i\\
|
|
209
|
+
last_report_at: \"$REPORT_NOW\"
|
|
210
|
+
}" "$STATE_FILE" > "$TEMP_FILE2" 2>/dev/null && mv "$TEMP_FILE2" "$STATE_FILE" || true
|
|
211
|
+
fi
|
|
212
|
+
REPORT_DIRECTIVE=" | ⚠️ PROGRESS REPORT DUE: Send an update to the user NOW via messaging before continuing work (topic: ${REPORT_TOPIC:-auto})"
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# Build system message
|
|
216
|
+
if [[ -n "${REMAINING_MIN:-}" ]]; then
|
|
217
|
+
TIME_MSG="${REMAINING_MIN}m remaining"
|
|
218
|
+
else
|
|
219
|
+
TIME_MSG="no time limit"
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
SYSTEM_MSG="🔄 Autonomous iteration $NEXT_ITERATION ($TIME_MSG) | Complete ALL tasks, then output <promise>$COMPLETION_PROMISE</promise> | Do NOT defer to future self — if you can do it now, DO IT NOW${REPORT_DIRECTIVE}"
|
|
223
|
+
|
|
224
|
+
# Block exit and feed prompt back
|
|
225
|
+
jq -n \
|
|
226
|
+
--arg prompt "$PROMPT_TEXT" \
|
|
227
|
+
--arg msg "$SYSTEM_MSG" \
|
|
228
|
+
'{
|
|
229
|
+
"decision": "block",
|
|
230
|
+
"reason": $prompt,
|
|
231
|
+
"systemMessage": $msg
|
|
232
|
+
}'
|
|
233
|
+
|
|
234
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Autonomous mode stop hook - prevents premature exit during autonomous sessions",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"Stop": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/autonomous-stop-hook.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Autonomous Mode Setup Script
|
|
4
|
+
# Creates state file that the stop hook reads to enforce continuous work.
|
|
5
|
+
# The stop hook blocks exit and feeds the task list back until all tasks are done.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# Parse arguments
|
|
10
|
+
GOAL=""
|
|
11
|
+
DURATION="4h"
|
|
12
|
+
REPORT_TOPIC=""
|
|
13
|
+
LEVEL_UP="false"
|
|
14
|
+
TASKS=""
|
|
15
|
+
COMPLETION_PROMISE=""
|
|
16
|
+
REPORT_INTERVAL="30m"
|
|
17
|
+
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case $1 in
|
|
20
|
+
--goal)
|
|
21
|
+
GOAL="$2"
|
|
22
|
+
shift 2
|
|
23
|
+
;;
|
|
24
|
+
--duration)
|
|
25
|
+
DURATION="$2"
|
|
26
|
+
shift 2
|
|
27
|
+
;;
|
|
28
|
+
--report-topic)
|
|
29
|
+
REPORT_TOPIC="$2"
|
|
30
|
+
shift 2
|
|
31
|
+
;;
|
|
32
|
+
--level-up)
|
|
33
|
+
LEVEL_UP="true"
|
|
34
|
+
shift
|
|
35
|
+
;;
|
|
36
|
+
--tasks)
|
|
37
|
+
TASKS="$2"
|
|
38
|
+
shift 2
|
|
39
|
+
;;
|
|
40
|
+
--completion-promise)
|
|
41
|
+
COMPLETION_PROMISE="$2"
|
|
42
|
+
shift 2
|
|
43
|
+
;;
|
|
44
|
+
--report-interval)
|
|
45
|
+
REPORT_INTERVAL="$2"
|
|
46
|
+
shift 2
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
# Collect remaining as goal if not set
|
|
50
|
+
if [[ -z "$GOAL" ]]; then
|
|
51
|
+
GOAL="$1"
|
|
52
|
+
else
|
|
53
|
+
GOAL="$GOAL $1"
|
|
54
|
+
fi
|
|
55
|
+
shift
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
if [[ -z "$GOAL" ]]; then
|
|
61
|
+
echo "❌ Error: No goal provided" >&2
|
|
62
|
+
echo "" >&2
|
|
63
|
+
echo " Usage: /autonomous --goal 'Complete feature X' --duration 4h" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Calculate end time
|
|
68
|
+
STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
69
|
+
|
|
70
|
+
# Convert duration to seconds for end time calculation
|
|
71
|
+
DURATION_SECONDS=0
|
|
72
|
+
if [[ "$DURATION" =~ ^([0-9]+)h$ ]]; then
|
|
73
|
+
DURATION_SECONDS=$(( ${BASH_REMATCH[1]} * 3600 ))
|
|
74
|
+
elif [[ "$DURATION" =~ ^([0-9]+)m$ ]]; then
|
|
75
|
+
DURATION_SECONDS=$(( ${BASH_REMATCH[1]} * 60 ))
|
|
76
|
+
elif [[ "$DURATION" =~ ^([0-9]+)$ ]]; then
|
|
77
|
+
DURATION_SECONDS=$(( $1 * 60 ))
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [[ $DURATION_SECONDS -gt 0 ]]; then
|
|
81
|
+
END_AT=$(date -u -v+${DURATION_SECONDS}S +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "+${DURATION_SECONDS} seconds" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "unknown")
|
|
82
|
+
else
|
|
83
|
+
END_AT="unlimited"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Default completion promise
|
|
87
|
+
if [[ -z "$COMPLETION_PROMISE" ]]; then
|
|
88
|
+
COMPLETION_PROMISE="ALL_TASKS_COMPLETE"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Create state file
|
|
92
|
+
mkdir -p .instar
|
|
93
|
+
|
|
94
|
+
cat > .instar/autonomous-state.local.md <<EOF
|
|
95
|
+
---
|
|
96
|
+
active: true
|
|
97
|
+
iteration: 1
|
|
98
|
+
session_id: ${CLAUDE_CODE_SESSION_ID:-}
|
|
99
|
+
goal: "$GOAL"
|
|
100
|
+
duration: "$DURATION"
|
|
101
|
+
duration_seconds: $DURATION_SECONDS
|
|
102
|
+
started_at: "$STARTED_AT"
|
|
103
|
+
end_at: "$END_AT"
|
|
104
|
+
report_topic: "$REPORT_TOPIC"
|
|
105
|
+
report_interval: "$REPORT_INTERVAL"
|
|
106
|
+
last_report_at: ""
|
|
107
|
+
level_up: $LEVEL_UP
|
|
108
|
+
completion_promise: "$COMPLETION_PROMISE"
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Autonomous Session
|
|
112
|
+
|
|
113
|
+
## Goal
|
|
114
|
+
$GOAL
|
|
115
|
+
|
|
116
|
+
## Tasks
|
|
117
|
+
$TASKS
|
|
118
|
+
|
|
119
|
+
## Instructions
|
|
120
|
+
|
|
121
|
+
You are in AUTONOMOUS MODE. The stop hook will prevent you from exiting until:
|
|
122
|
+
1. You output <promise>$COMPLETION_PROMISE</promise> (ONLY when genuinely true)
|
|
123
|
+
2. OR the duration expires ($DURATION from $STARTED_AT)
|
|
124
|
+
3. OR the user sends an emergency stop
|
|
125
|
+
|
|
126
|
+
### Rules
|
|
127
|
+
- Do NOT defer work to "Phase 2" or "future sessions"
|
|
128
|
+
- Do NOT label tasks as "parked" unless genuinely blocked by external dependencies
|
|
129
|
+
- Do NOT declare victory early — check EVERY task
|
|
130
|
+
- When you think you're done, re-read the task list and verify each item
|
|
131
|
+
- If time remains after completing tasks, look for related improvements
|
|
132
|
+
- Send progress reports every $REPORT_INTERVAL to topic $REPORT_TOPIC
|
|
133
|
+
|
|
134
|
+
### Emergency Stop
|
|
135
|
+
The user can always stop you via:
|
|
136
|
+
- Sending "stop everything" or "emergency stop" via messaging
|
|
137
|
+
- The MessageSentinel will intercept and halt operations
|
|
138
|
+
|
|
139
|
+
### Completion
|
|
140
|
+
To complete, ALL of these must be true:
|
|
141
|
+
- Every task in the task list is implemented (not just wired/stubbed)
|
|
142
|
+
- Code compiles (npx tsc --noEmit)
|
|
143
|
+
- Changes are tested where practical
|
|
144
|
+
- Then output: <promise>$COMPLETION_PROMISE</promise>
|
|
145
|
+
EOF
|
|
146
|
+
|
|
147
|
+
echo "🔄 Autonomous mode activated!"
|
|
148
|
+
echo ""
|
|
149
|
+
echo "Goal: $GOAL"
|
|
150
|
+
echo "Duration: $DURATION (until $END_AT)"
|
|
151
|
+
echo "Level-up: $LEVEL_UP"
|
|
152
|
+
echo "Report topic: ${REPORT_TOPIC:-none}"
|
|
153
|
+
echo "Completion: <promise>$COMPLETION_PROMISE</promise>"
|
|
154
|
+
echo ""
|
|
155
|
+
echo "The stop hook is now active. You cannot exit until tasks are complete."
|
|
156
|
+
echo ""
|
|
157
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
158
|
+
echo "CRITICAL: Defer-to-Future-Self Trap"
|
|
159
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
160
|
+
echo ""
|
|
161
|
+
echo "Do NOT label remaining work as 'Phase 2', 'future', or 'parked'"
|
|
162
|
+
echo "unless it genuinely requires something you don't have access to."
|
|
163
|
+
echo ""
|
|
164
|
+
echo "If you have the tools and knowledge to do it NOW — do it NOW."
|
|
165
|
+
echo "Your future self is not better equipped. You are the future self."
|
|
166
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autonomous
|
|
3
|
+
description: Enter autonomous development mode with STRUCTURAL enforcement. Uses a stop hook to prevent premature exit. Generates stop conditions and confirms with user before starting. Work independently for a specified duration with progress reporting.
|
|
4
|
+
user_invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Autonomous Mode (Structurally Enforced)
|
|
8
|
+
|
|
9
|
+
You are entering **autonomous development mode**. This mode uses a **stop hook** that PREVENTS you from exiting until all tasks are genuinely complete. This is not optional — the hook structurally enforces continuous work.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Step 1: Generate Stop Conditions (MANDATORY)
|
|
14
|
+
|
|
15
|
+
Before activating the stop hook, you MUST:
|
|
16
|
+
|
|
17
|
+
1. **Analyze the goal** and break it into specific, verifiable tasks
|
|
18
|
+
2. **Present the task list** to the user with clear completion criteria
|
|
19
|
+
3. **Wait for user confirmation** before activating the hook
|
|
20
|
+
4. **Define the completion promise** — a phrase that is only TRUE when ALL tasks are done
|
|
21
|
+
|
|
22
|
+
**Example interaction:**
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
User: /autonomous --duration=8h --goal="Complete Slack feature parity"
|
|
26
|
+
|
|
27
|
+
Agent: Here's my task breakdown for autonomous mode:
|
|
28
|
+
|
|
29
|
+
TASKS (all must be complete before I can exit):
|
|
30
|
+
1. [ ] Implement job-specific Slack channels
|
|
31
|
+
2. [ ] Build full PresenceProxy Slack integration
|
|
32
|
+
3. [ ] Update dashboard HTML with platform badges
|
|
33
|
+
4. [ ] Implement Slack Lifeline process
|
|
34
|
+
5. [ ] Add platform dropdown to new session UI
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
Completion promise: "ALL_TASKS_COMPLETE"
|
|
38
|
+
Duration: 8 hours
|
|
39
|
+
Emergency stop: "stop everything" via messaging
|
|
40
|
+
|
|
41
|
+
Shall I proceed? (The stop hook will prevent me from exiting until all tasks are done)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**The user must confirm before the hook activates.** This is the safety gate.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Step 2: Activate the Stop Hook
|
|
49
|
+
|
|
50
|
+
After user confirmation:
|
|
51
|
+
|
|
52
|
+
**2a. Add the stop hook to settings.json** (dynamically — it's removed when the session ends):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 -c "
|
|
56
|
+
import json
|
|
57
|
+
with open('.claude/settings.json') as f:
|
|
58
|
+
s = json.load(f)
|
|
59
|
+
hooks = s.setdefault('hooks', {}).setdefault('Stop', [])
|
|
60
|
+
if not any('autonomous-stop-hook' in str(h) for h in hooks):
|
|
61
|
+
hooks.append({'matcher': '', 'hooks': [{'type': 'command', 'command': 'bash .instar/hooks/instar/autonomous-stop-hook.sh', 'timeout': 10000}]})
|
|
62
|
+
with open('.claude/settings.json', 'w') as f:
|
|
63
|
+
json.dump(s, f, indent=2)
|
|
64
|
+
print('Stop hook registered')
|
|
65
|
+
"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**2b. Write the state file DIRECTLY** (do NOT shell out to bash — the session ID env var is only available inside Claude Code):
|
|
69
|
+
|
|
70
|
+
Use the **Write tool** to create `.instar/autonomous-state.local.md` with this content:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
---
|
|
74
|
+
active: true
|
|
75
|
+
iteration: 1
|
|
76
|
+
session_id: {VALUE OF $CLAUDE_CODE_SESSION_ID — get via: echo $CLAUDE_CODE_SESSION_ID}
|
|
77
|
+
goal: "YOUR GOAL"
|
|
78
|
+
duration: "8h"
|
|
79
|
+
duration_seconds: 28800
|
|
80
|
+
started_at: "{ISO timestamp}"
|
|
81
|
+
end_at: "{ISO timestamp + duration}"
|
|
82
|
+
report_topic: "TOPIC_ID"
|
|
83
|
+
report_interval: "30m"
|
|
84
|
+
last_report_at: ""
|
|
85
|
+
level_up: true
|
|
86
|
+
completion_promise: "ALL_TASKS_COMPLETE"
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Autonomous Session
|
|
90
|
+
|
|
91
|
+
## Goal
|
|
92
|
+
{goal text}
|
|
93
|
+
|
|
94
|
+
## Tasks
|
|
95
|
+
{numbered task list}
|
|
96
|
+
|
|
97
|
+
## Instructions
|
|
98
|
+
{autonomous instructions}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**CRITICAL**: To capture the session ID correctly, run this FIRST:
|
|
102
|
+
```bash
|
|
103
|
+
echo $CLAUDE_CODE_SESSION_ID
|
|
104
|
+
```
|
|
105
|
+
Then include the output in the `session_id:` field. This ensures session isolation works.
|
|
106
|
+
|
|
107
|
+
**WHY NOT bash script?** Running `bash setup-autonomous.sh` creates a subprocess that does NOT inherit `CLAUDE_CODE_SESSION_ID`. The state file ends up with an empty session_id, which causes the hook to leak into all sessions. Always write the state file from within Claude Code's context.
|
|
108
|
+
|
|
109
|
+
**SESSION ISOLATION**: The stop hook checks `session_id` — it only blocks the session that activated autonomous mode. Other sessions on the same machine pass through unaffected.
|
|
110
|
+
|
|
111
|
+
**From this point, you CANNOT exit THIS session** unless:
|
|
112
|
+
- You output `<promise>ALL_TASKS_COMPLETE</promise>` (genuinely true)
|
|
113
|
+
- Duration expires
|
|
114
|
+
- Emergency stop is triggered
|
|
115
|
+
|
|
116
|
+
**2c. On completion/exit**: Remove the stop hook from settings.json:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python3 -c "
|
|
120
|
+
import json
|
|
121
|
+
with open('.claude/settings.json') as f:
|
|
122
|
+
s = json.load(f)
|
|
123
|
+
s['hooks']['Stop'] = [h for h in s.get('hooks',{}).get('Stop',[]) if 'autonomous-stop-hook' not in str(h)]
|
|
124
|
+
with open('.claude/settings.json', 'w') as f:
|
|
125
|
+
json.dump(s, f, indent=2)
|
|
126
|
+
print('Stop hook removed')
|
|
127
|
+
"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Step 3: Work Until Done
|
|
133
|
+
|
|
134
|
+
The stop hook will catch every attempt to exit and feed your task list back. Each iteration you will:
|
|
135
|
+
|
|
136
|
+
1. Read the task list
|
|
137
|
+
2. Pick the next incomplete task
|
|
138
|
+
3. Implement it fully (not stub, not wire — IMPLEMENT)
|
|
139
|
+
4. Verify it works (compile, test where practical)
|
|
140
|
+
5. Move to next task
|
|
141
|
+
6. Send progress reports at the configured interval
|
|
142
|
+
|
|
143
|
+
### The Defer-to-Future-Self Trap
|
|
144
|
+
|
|
145
|
+
**This is the #1 failure mode.** It looks like:
|
|
146
|
+
|
|
147
|
+
| What you think | What's actually happening |
|
|
148
|
+
|----------------|--------------------------|
|
|
149
|
+
| "This is Phase 2 work" | You don't feel like doing it right now |
|
|
150
|
+
| "Parked for follow-up" | You're avoiding the hard part |
|
|
151
|
+
| "Future improvement" | Your future self has no advantage over you |
|
|
152
|
+
| "Remaining work" | Work you're choosing not to do |
|
|
153
|
+
| "Deferred" | Abdication disguised as planning |
|
|
154
|
+
|
|
155
|
+
**The test:** Can you do this task right now with the tools and knowledge you have?
|
|
156
|
+
- **Yes** → Do it. Not later. Now.
|
|
157
|
+
- **No** → Document exactly WHY (missing dependency, needs external access, etc.)
|
|
158
|
+
|
|
159
|
+
**"Phase 2" is only valid when the current phase genuinely cannot contain the work** — not when you're tired, not when it's complex, not when it's 2 AM.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Step 4: Completion
|
|
164
|
+
|
|
165
|
+
When ALL tasks are genuinely done:
|
|
166
|
+
|
|
167
|
+
1. Verify every task is complete (re-read the list)
|
|
168
|
+
2. Run `npx tsc --noEmit` — zero errors
|
|
169
|
+
3. Run relevant tests
|
|
170
|
+
4. Write learnings to MEMORY.md
|
|
171
|
+
5. Send final report via messaging
|
|
172
|
+
6. Output: `<promise>ALL_TASKS_COMPLETE</promise>`
|
|
173
|
+
|
|
174
|
+
The stop hook will detect the promise and allow exit.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Emergency Stop
|
|
179
|
+
|
|
180
|
+
The user can always stop autonomous mode:
|
|
181
|
+
|
|
182
|
+
1. **Via messaging**: Send "stop everything" or "emergency stop" — the MessageSentinel intercepts
|
|
183
|
+
2. **Via file**: `touch .instar/autonomous-emergency-stop` — the stop hook checks for this
|
|
184
|
+
3. **Via cancel**: `/cancel-autonomous` — removes the state file
|
|
185
|
+
|
|
186
|
+
The stop hook checks for emergency stop on EVERY iteration. User safety is never compromised.
|
|
187
|
+
|
|
188
|
+
### /cancel-autonomous
|
|
189
|
+
|
|
190
|
+
To manually cancel:
|
|
191
|
+
```bash
|
|
192
|
+
rm -f .instar/autonomous-state.local.md
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Progress Reporting
|
|
198
|
+
|
|
199
|
+
Send reports to the user's messaging platform at the configured interval.
|
|
200
|
+
|
|
201
|
+
**How to send:**
|
|
202
|
+
```bash
|
|
203
|
+
cat <<'EOF' | .claude/scripts/telegram-reply.sh TOPIC_ID
|
|
204
|
+
AUTONOMOUS PROGRESS (iteration N, Xh elapsed / Yh total)
|
|
205
|
+
|
|
206
|
+
Completed:
|
|
207
|
+
- [task 1] ✅
|
|
208
|
+
- [task 2] ✅
|
|
209
|
+
|
|
210
|
+
Working on:
|
|
211
|
+
- [current task]
|
|
212
|
+
|
|
213
|
+
Remaining:
|
|
214
|
+
- [task 3]
|
|
215
|
+
- [task 4]
|
|
216
|
+
|
|
217
|
+
Issues: none
|
|
218
|
+
EOF
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Anti-Patterns (Structurally Enforced)
|
|
224
|
+
|
|
225
|
+
These aren't just warnings — the stop hook WILL catch you if you try to exit without completing them:
|
|
226
|
+
|
|
227
|
+
### The "Beautiful Summary" Exit
|
|
228
|
+
Writing a comprehensive report of what you did and what's "remaining" then trying to exit. **The hook will block you and feed the remaining tasks back.**
|
|
229
|
+
|
|
230
|
+
### The "Phase 2" Labeling
|
|
231
|
+
Calling incomplete work "Phase 2" and trying to exit. **The hook doesn't care about phases — it cares about the task list.**
|
|
232
|
+
|
|
233
|
+
### The "Good Stopping Point" Exit
|
|
234
|
+
Finding a natural break and trying to exit early. **The hook checks the task list, not your feelings about stopping.**
|
|
235
|
+
|
|
236
|
+
### The "It's 2 AM" Exit
|
|
237
|
+
Feeling tired (as an AI) and deferring. **You don't get tired. The hook knows this.**
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Hook Configuration
|
|
242
|
+
|
|
243
|
+
The stop hook is at `.claude/skills/autonomous/hooks/autonomous-stop-hook.sh`.
|
|
244
|
+
|
|
245
|
+
It reads state from `.instar/autonomous-state.local.md` and:
|
|
246
|
+
- Blocks exit if tasks are incomplete
|
|
247
|
+
- Feeds the task list + goal back as the next prompt
|
|
248
|
+
- Increments the iteration counter
|
|
249
|
+
- Checks for emergency stop signals
|
|
250
|
+
- Checks for duration expiry
|
|
251
|
+
- Checks for completion promise in `<promise>` tags
|
|
252
|
+
- Includes time remaining in the system message
|
|
253
|
+
|
|
254
|
+
**This is structural enforcement, not willpower.** You cannot talk your way out of the loop.
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+CH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+CH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAk3FD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAiBlF"}
|