Skip to content

Commit cf907ab

Browse files
cahaselerclaude
andauthored
feat: make autoflow configurable and opt-in (#184)
* feat: make autoflow configurable and opt-in - Add autoflow config to features section with enabled: false by default - Autoflow now requires explicit enabling in track.config.json - Add configurable throttle_limit and window_duration_minutes options - Update all autoflow hooks to check config before running: - user-message.ts: exits early if not configured/enabled - permission-request.ts: exits early if not configured/enabled - stop.ts: exits early if not configured/enabled, uses config for throttle values - Update config-track.md documentation to accurately reflect implementation BREAKING CHANGE: Autoflow is now disabled by default and must be explicitly enabled in track.config.json with `"autoflow": { "enabled": true }` Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: simplify autoflow config access with typed helpers - Add getAutoflowConfig() and isAutoflowEnabled() helper functions - Simplify hook config checks to single isAutoflowEnabled() call - Use typed accessor for throttle config instead of manual casts - Follow established pattern for other feature configs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Craig Haseler <cahaseler@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dc26f4f commit cf907ab

File tree

5 files changed

+65
-9
lines changed

5 files changed

+65
-9
lines changed

commands/config-track.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ The config file has three main sections: `hooks`, `features`, and `logging`.
4040
}
4141
```
4242

43+
### Features Section
44+
45+
**Autonomous Operation:**
46+
4347
- `autoflow` - Autonomous operation mode for unattended task completion
44-
- Per-message opt-in: include "autoflow" in your message to activate
48+
- **Disabled by default** - must be explicitly enabled
49+
- Per-message opt-in: include "autoflow" in your message to activate for that task
4550
- Permission requests denied with "find safe alternative" guidance
4651
- Throttle detection prevents runaway sessions
4752
- Sub-options:
@@ -56,8 +61,6 @@ The config file has three main sections: `hooks`, `features`, and `logging`.
5661
}
5762
```
5863

59-
### Features Section
60-
6164
**Git & GitHub Integration:**
6265

6366
- `git_branching` - Automatic feature branch creation from tasks

hooks/permission-request.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#!/usr/bin/env bun
22
/**
33
* PermissionRequest Hook - Denies permissions in autoflow mode with helpful message
4+
*
5+
* IMPORTANT: Autoflow must be enabled in track.config.json to function.
46
*/
57

68
import { existsSync, readFileSync } from 'node:fs';
79
import { join } from 'node:path';
10+
import { isAutoflowEnabled } from '../skills/cc-track-tools/lib/config';
811

912
export interface HookInput {
1013
cwd?: string;
@@ -56,6 +59,12 @@ function createDefaultDeps(cwd: string): PermissionRequestDeps {
5659

5760
export async function permissionRequestHook(input: HookInput, deps?: PermissionRequestDeps): Promise<HookOutput> {
5861
const cwd = input.cwd || process.cwd();
62+
63+
// Exit early if cc-track not configured or autoflow not enabled
64+
if (!isAutoflowEnabled()) {
65+
return { continue: true };
66+
}
67+
5968
const { readState } = deps || createDefaultDeps(cwd);
6069
const state = readState();
6170

hooks/stop.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
* Stop Hook - Evaluates if Claude should continue in autoflow mode
44
*
55
* Uses battle-tested evaluation prompt from double-shot-latte project
6-
* with 3 continuations per 5-minute window throttling.
6+
* with configurable throttling (default: 3 continuations per 5-minute window).
7+
*
8+
* IMPORTANT: Autoflow must be enabled in track.config.json to function.
79
*/
810

911
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
1012
import { join } from 'node:path';
13+
import { getAutoflowConfig, isAutoflowEnabled } from '../skills/cc-track-tools/lib/config';
1114
import { createLogger } from '../skills/cc-track-tools/lib/logger';
1215

1316
export interface HookInput {
@@ -197,6 +200,12 @@ function createDefaultDeps(cwd: string): StopHookDeps {
197200

198201
export async function stopHook(input: HookInput, deps?: StopHookDeps): Promise<StopHookResult> {
199202
const cwd = input.cwd || process.cwd();
203+
204+
// Exit early if cc-track not configured or autoflow not enabled
205+
if (!isAutoflowEnabled()) {
206+
return { action: 'allow' };
207+
}
208+
200209
const { readState, writeState, readTranscript, evaluate, now } = deps || createDefaultDeps(cwd);
201210

202211
const state = readState();
@@ -206,27 +215,33 @@ export async function stopHook(input: HookInput, deps?: StopHookDeps): Promise<S
206215
return { action: 'allow' };
207216
}
208217

209-
// Autoflow is active - check throttling (3 per 5-minute window)
218+
// Get throttle config (with defaults)
219+
const autoflowConfig = getAutoflowConfig();
220+
const throttleLimit = autoflowConfig?.throttle_limit ?? MAX_CONTINUES_PER_WINDOW;
221+
const windowDurationMinutes = autoflowConfig?.window_duration_minutes ?? 5;
222+
const windowDurationMs = windowDurationMinutes * 60 * 1000;
223+
224+
// Autoflow is active - check throttling
210225
const currentTime = now();
211226
const windowStart = state.windowStart ? new Date(state.windowStart).getTime() : currentTime;
212227
const windowElapsed = currentTime - windowStart;
213228

214229
let continueCount = state.continueCount || 0;
215230

216231
// Reset window if expired
217-
if (windowElapsed >= WINDOW_DURATION_MS) {
232+
if (windowElapsed >= windowDurationMs) {
218233
continueCount = 0;
219234
}
220235

221236
// Check if we've exceeded the limit
222-
if (continueCount >= MAX_CONTINUES_PER_WINDOW) {
237+
if (continueCount >= throttleLimit) {
223238
// Deactivate autoflow due to throttle limit
224239
const updatedState = { ...state, active: false };
225240
writeState(updatedState);
226241

227242
return {
228243
action: 'throttle',
229-
message: `⚠️ Autoflow throttle limit reached\n\nClaude has continued ${MAX_CONTINUES_PER_WINDOW} times in the last 5 minutes.\nThis suggests it may be stuck in a loop.\n\nAutoflow mode has been deactivated. Please review the situation.`,
244+
message: `⚠️ Autoflow throttle limit reached\n\nClaude has continued ${throttleLimit} times in the last ${windowDurationMinutes} minutes.\nThis suggests it may be stuck in a loop.\n\nAutoflow mode has been deactivated. Please review the situation.`,
230245
updatedState,
231246
};
232247
}

hooks/user-message.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
*
88
* Activation: Include "autoflow" in message (not negated)
99
* Deactivation: Any message without "autoflow" (or with negation)
10+
*
11+
* IMPORTANT: Autoflow must be enabled in track.config.json to function.
1012
*/
1113

1214
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
1315
import { join } from 'node:path';
16+
import { isAutoflowEnabled } from '../skills/cc-track-tools/lib/config';
1417

1518
export interface HookInput {
1619
prompt?: string;
@@ -87,6 +90,12 @@ export function shouldActivate(message: string): boolean {
8790
export async function userMessageHook(input: HookInput, deps?: UserMessageDeps): Promise<HookOutput> {
8891
const message = input.prompt || '';
8992
const cwd = input.cwd || process.cwd();
93+
94+
// Exit early if cc-track not configured or autoflow not enabled
95+
if (!isAutoflowEnabled()) {
96+
return { continue: true };
97+
}
98+
9099
const { readState, writeState } = deps || createDefaultDeps(cwd);
91100

92101
const currentState = readState();

skills/cc-track-tools/lib/config.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export interface StatuslineConfig extends HookConfig {
4141
show_repo?: boolean;
4242
}
4343

44+
export interface AutoflowConfig extends HookConfig {
45+
throttle_limit?: number;
46+
window_duration_minutes?: number;
47+
}
48+
4449
interface GitConfig {
4550
defaultBranch?: string;
4651
description?: string;
@@ -60,7 +65,7 @@ interface InternalConfig {
6065
[key: string]: HookConfig | EditValidationConfig;
6166
};
6267
features: {
63-
[key: string]: HookConfig | CodeReviewConfig | StatuslineConfig;
68+
[key: string]: HookConfig | CodeReviewConfig | StatuslineConfig | AutoflowConfig;
6469
};
6570
git?: GitConfig;
6671
logging?: LoggingConfig;
@@ -149,6 +154,12 @@ const DEFAULT_CONFIG: InternalConfig = {
149154
description: 'Run comprehensive code review before task completion',
150155
tool: 'claude' as const,
151156
},
157+
autoflow: {
158+
enabled: false,
159+
description: 'Autonomous operation mode - activates per-message when "autoflow" keyword is used',
160+
throttle_limit: 3,
161+
window_duration_minutes: 5,
162+
},
152163
},
153164
};
154165

@@ -357,3 +368,12 @@ export function getTestConfig(configPath?: string): ValidationConfig | null {
357368

358369
return testConfig;
359370
}
371+
372+
export function getAutoflowConfig(configPath?: string): AutoflowConfig | null {
373+
const config = getConfig(configPath);
374+
return (config.features?.autoflow as AutoflowConfig) || null;
375+
}
376+
377+
export function isAutoflowEnabled(configPath?: string): boolean {
378+
return isCcTrackConfigured() && isHookEnabled('autoflow', configPath);
379+
}

0 commit comments

Comments
 (0)