evil-omo 3.12.0 → 3.12.3

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/dist/cli/index.js CHANGED
@@ -5903,6 +5903,12 @@ var init_snake_case = __esm(() => {
5903
5903
 
5904
5904
  // src/shared/tool-name.ts
5905
5905
  var init_tool_name = () => {};
5906
+
5907
+ // src/shared/pattern-matcher.ts
5908
+ var regexCache;
5909
+ var init_pattern_matcher = __esm(() => {
5910
+ regexCache = new Map;
5911
+ });
5906
5912
  // src/shared/file-utils.ts
5907
5913
  var init_file_utils = () => {};
5908
5914
 
@@ -6660,85 +6666,160 @@ var init_agent_tool_restrictions = () => {};
6660
6666
  // src/shared/connected-providers-cache.ts
6661
6667
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
6662
6668
  import { join as join5 } from "path";
6663
- function getCacheFilePath(filename) {
6664
- return join5(getOmoOpenCodeCacheDir(), filename);
6665
- }
6666
- function ensureCacheDir() {
6667
- const cacheDir = getOmoOpenCodeCacheDir();
6668
- if (!existsSync3(cacheDir)) {
6669
- mkdirSync(cacheDir, { recursive: true });
6669
+ function createConnectedProvidersCacheStore(getCacheDir2 = getOmoOpenCodeCacheDir) {
6670
+ function getCacheFilePath(filename) {
6671
+ return join5(getCacheDir2(), filename);
6672
+ }
6673
+ let memConnected;
6674
+ let memProviderModels;
6675
+ function ensureCacheDir() {
6676
+ const cacheDir = getCacheDir2();
6677
+ if (!existsSync3(cacheDir)) {
6678
+ mkdirSync(cacheDir, { recursive: true });
6679
+ }
6680
+ }
6681
+ function readConnectedProvidersCache() {
6682
+ if (memConnected !== undefined)
6683
+ return memConnected;
6684
+ const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
6685
+ if (!existsSync3(cacheFile)) {
6686
+ log("[connected-providers-cache] Cache file not found", { cacheFile });
6687
+ memConnected = null;
6688
+ return null;
6689
+ }
6690
+ try {
6691
+ const content = readFileSync2(cacheFile, "utf-8");
6692
+ const data = JSON.parse(content);
6693
+ log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
6694
+ memConnected = data.connected;
6695
+ return data.connected;
6696
+ } catch (err) {
6697
+ log("[connected-providers-cache] Error reading cache", { error: String(err) });
6698
+ memConnected = null;
6699
+ return null;
6700
+ }
6670
6701
  }
6671
- }
6672
- function writeConnectedProvidersCache(connected) {
6673
- ensureCacheDir();
6674
- const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
6675
- const data = {
6676
- connected,
6677
- updatedAt: new Date().toISOString()
6678
- };
6679
- try {
6680
- writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
6681
- log("[connected-providers-cache] Cache written", { count: connected.length });
6682
- } catch (err) {
6683
- log("[connected-providers-cache] Error writing cache", { error: String(err) });
6702
+ function hasConnectedProvidersCache() {
6703
+ const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
6704
+ return existsSync3(cacheFile);
6684
6705
  }
6685
- }
6686
- function hasProviderModelsCache() {
6687
- const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
6688
- return existsSync3(cacheFile);
6689
- }
6690
- function writeProviderModelsCache(data) {
6691
- ensureCacheDir();
6692
- const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
6693
- const cacheData = {
6694
- ...data,
6695
- updatedAt: new Date().toISOString()
6696
- };
6697
- try {
6698
- writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
6699
- log("[connected-providers-cache] Provider-models cache written", {
6700
- providerCount: Object.keys(data.models).length
6701
- });
6702
- } catch (err) {
6703
- log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
6706
+ function writeConnectedProvidersCache(connected) {
6707
+ ensureCacheDir();
6708
+ const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
6709
+ const data = {
6710
+ connected,
6711
+ updatedAt: new Date().toISOString()
6712
+ };
6713
+ try {
6714
+ writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
6715
+ memConnected = connected;
6716
+ log("[connected-providers-cache] Cache written", { count: connected.length });
6717
+ } catch (err) {
6718
+ log("[connected-providers-cache] Error writing cache", { error: String(err) });
6719
+ }
6704
6720
  }
6705
- }
6706
- async function updateConnectedProvidersCache(client) {
6707
- if (!client?.provider?.list) {
6708
- log("[connected-providers-cache] client.provider.list not available");
6709
- return;
6721
+ function readProviderModelsCache() {
6722
+ if (memProviderModels !== undefined)
6723
+ return memProviderModels;
6724
+ const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
6725
+ if (!existsSync3(cacheFile)) {
6726
+ log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
6727
+ memProviderModels = null;
6728
+ return null;
6729
+ }
6730
+ try {
6731
+ const content = readFileSync2(cacheFile, "utf-8");
6732
+ const data = JSON.parse(content);
6733
+ log("[connected-providers-cache] Read provider-models cache", {
6734
+ providerCount: Object.keys(data.models).length,
6735
+ updatedAt: data.updatedAt
6736
+ });
6737
+ memProviderModels = data;
6738
+ return data;
6739
+ } catch (err) {
6740
+ log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) });
6741
+ memProviderModels = null;
6742
+ return null;
6743
+ }
6710
6744
  }
6711
- try {
6712
- const result = await client.provider.list();
6713
- const connected = result.data?.connected ?? [];
6714
- log("[connected-providers-cache] Fetched connected providers", { count: connected.length, providers: connected });
6715
- writeConnectedProvidersCache(connected);
6716
- const modelsByProvider = {};
6717
- const allProviders = result.data?.all ?? [];
6718
- for (const provider of allProviders) {
6719
- if (provider.models) {
6720
- const modelIds = Object.keys(provider.models);
6721
- if (modelIds.length > 0) {
6722
- modelsByProvider[provider.id] = modelIds;
6723
- }
6724
- }
6725
- }
6726
- log("[connected-providers-cache] Extracted models from provider list", {
6727
- providerCount: Object.keys(modelsByProvider).length,
6728
- totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
6729
- });
6730
- writeProviderModelsCache({
6731
- models: modelsByProvider,
6732
- connected
6733
- });
6734
- } catch (err) {
6735
- log("[connected-providers-cache] Error updating cache", { error: String(err) });
6745
+ function hasProviderModelsCache() {
6746
+ const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
6747
+ return existsSync3(cacheFile);
6748
+ }
6749
+ function writeProviderModelsCache(data) {
6750
+ ensureCacheDir();
6751
+ const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
6752
+ const cacheData = {
6753
+ ...data,
6754
+ updatedAt: new Date().toISOString()
6755
+ };
6756
+ try {
6757
+ writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
6758
+ memProviderModels = cacheData;
6759
+ log("[connected-providers-cache] Provider-models cache written", {
6760
+ providerCount: Object.keys(data.models).length
6761
+ });
6762
+ } catch (err) {
6763
+ log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
6764
+ }
6765
+ }
6766
+ async function updateConnectedProvidersCache(client) {
6767
+ if (!client?.provider?.list) {
6768
+ log("[connected-providers-cache] client.provider.list not available");
6769
+ return;
6770
+ }
6771
+ try {
6772
+ const result = await client.provider.list();
6773
+ const connected = result.data?.connected ?? [];
6774
+ log("[connected-providers-cache] Fetched connected providers", {
6775
+ count: connected.length,
6776
+ providers: connected
6777
+ });
6778
+ writeConnectedProvidersCache(connected);
6779
+ const modelsByProvider = {};
6780
+ const allProviders = result.data?.all ?? [];
6781
+ for (const provider of allProviders) {
6782
+ if (provider.models) {
6783
+ const modelIds = Object.keys(provider.models);
6784
+ if (modelIds.length > 0) {
6785
+ modelsByProvider[provider.id] = modelIds;
6786
+ }
6787
+ }
6788
+ }
6789
+ log("[connected-providers-cache] Extracted models from provider list", {
6790
+ providerCount: Object.keys(modelsByProvider).length,
6791
+ totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
6792
+ });
6793
+ writeProviderModelsCache({
6794
+ models: modelsByProvider,
6795
+ connected
6796
+ });
6797
+ } catch (err) {
6798
+ log("[connected-providers-cache] Error updating cache", { error: String(err) });
6799
+ }
6736
6800
  }
6801
+ return {
6802
+ readConnectedProvidersCache,
6803
+ hasConnectedProvidersCache,
6804
+ readProviderModelsCache,
6805
+ hasProviderModelsCache,
6806
+ writeProviderModelsCache,
6807
+ updateConnectedProvidersCache
6808
+ };
6737
6809
  }
6738
- var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
6810
+ var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json", defaultConnectedProvidersCacheStore, readConnectedProvidersCache, hasConnectedProvidersCache, readProviderModelsCache, hasProviderModelsCache, writeProviderModelsCache, updateConnectedProvidersCache;
6739
6811
  var init_connected_providers_cache = __esm(() => {
6740
6812
  init_logger();
6741
6813
  init_data_path();
6814
+ defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(() => getOmoOpenCodeCacheDir());
6815
+ ({
6816
+ readConnectedProvidersCache,
6817
+ hasConnectedProvidersCache,
6818
+ readProviderModelsCache,
6819
+ hasProviderModelsCache,
6820
+ writeProviderModelsCache,
6821
+ updateConnectedProvidersCache
6822
+ } = defaultConnectedProvidersCacheStore);
6742
6823
  });
6743
6824
 
6744
6825
  // src/shared/model-availability.ts
@@ -7104,6 +7185,7 @@ var init_shared = __esm(() => {
7104
7185
  init_logger();
7105
7186
  init_snake_case();
7106
7187
  init_tool_name();
7188
+ init_pattern_matcher();
7107
7189
  init_deep_merge();
7108
7190
  init_file_utils();
7109
7191
  init_dynamic_truncator();
@@ -8885,7 +8967,7 @@ var {
8885
8967
  // package.json
8886
8968
  var package_default = {
8887
8969
  name: "evil-omo",
8888
- version: "3.12.0",
8970
+ version: "3.12.3",
8889
8971
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
8890
8972
  main: "dist/index.js",
8891
8973
  types: "dist/index.d.ts",
@@ -8961,17 +9043,17 @@ var package_default = {
8961
9043
  typescript: "^5.7.3"
8962
9044
  },
8963
9045
  optionalDependencies: {
8964
- "evil-omo-darwin-arm64": "3.12.0",
8965
- "evil-omo-darwin-x64": "3.12.0",
8966
- "evil-omo-darwin-x64-baseline": "3.12.0",
8967
- "evil-omo-linux-x64": "3.12.0",
8968
- "evil-omo-linux-x64-baseline": "3.12.0",
8969
- "evil-omo-linux-arm64": "3.12.0",
8970
- "evil-omo-linux-x64-musl": "3.12.0",
8971
- "evil-omo-linux-x64-musl-baseline": "3.12.0",
8972
- "evil-omo-linux-arm64-musl": "3.12.0",
8973
- "evil-omo-windows-x64": "3.12.0",
8974
- "evil-omo-windows-x64-baseline": "3.12.0"
9046
+ "evil-omo-darwin-arm64": "3.12.3",
9047
+ "evil-omo-darwin-x64": "3.12.3",
9048
+ "evil-omo-darwin-x64-baseline": "3.12.3",
9049
+ "evil-omo-linux-x64": "3.12.3",
9050
+ "evil-omo-linux-x64-baseline": "3.12.3",
9051
+ "evil-omo-linux-arm64": "3.12.3",
9052
+ "evil-omo-linux-x64-musl": "3.12.3",
9053
+ "evil-omo-linux-x64-musl-baseline": "3.12.3",
9054
+ "evil-omo-linux-arm64-musl": "3.12.3",
9055
+ "evil-omo-windows-x64": "3.12.3",
9056
+ "evil-omo-windows-x64-baseline": "3.12.3"
8975
9057
  },
8976
9058
  overrides: {
8977
9059
  "@opencode-ai/sdk": "^1.2.24"
@@ -10340,24 +10422,24 @@ function writePaddedText(text, atLineStart) {
10340
10422
  return { output: text, atLineStart: text.endsWith(`
10341
10423
  `) };
10342
10424
  }
10343
- let output = "";
10425
+ const parts = [];
10344
10426
  let lineStart = atLineStart;
10345
10427
  for (let i2 = 0;i2 < text.length; i2++) {
10346
10428
  const ch = text[i2];
10347
10429
  if (lineStart) {
10348
- output += " ";
10430
+ parts.push(" ");
10349
10431
  lineStart = false;
10350
10432
  }
10351
10433
  if (ch === `
10352
10434
  `) {
10353
- output += `
10354
- `;
10435
+ parts.push(`
10436
+ `);
10355
10437
  lineStart = true;
10356
10438
  continue;
10357
10439
  }
10358
- output += ch;
10440
+ parts.push(ch);
10359
10441
  }
10360
- return { output, atLineStart: lineStart };
10442
+ return { output: parts.join(""), atLineStart: lineStart };
10361
10443
  }
10362
10444
  function colorizeWithProfileColor(text, hexColor) {
10363
10445
  if (!hexColor)
@@ -24350,9 +24432,9 @@ var BabysittingConfigSchema = exports_external.object({
24350
24432
  });
24351
24433
  // src/config/schema/background-task.ts
24352
24434
  var CircuitBreakerConfigSchema = exports_external.object({
24435
+ enabled: exports_external.boolean().optional(),
24353
24436
  maxToolCalls: exports_external.number().int().min(10).optional(),
24354
- windowSize: exports_external.number().int().min(5).optional(),
24355
- repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
24437
+ consecutiveThreshold: exports_external.number().int().min(5).optional()
24356
24438
  });
24357
24439
  var BackgroundTaskConfigSchema = exports_external.object({
24358
24440
  defaultConcurrency: exports_external.number().min(1).optional(),
@@ -24542,7 +24624,8 @@ var HookNameSchema = exports_external.enum([
24542
24624
  "write-existing-file-guard",
24543
24625
  "anthropic-effort",
24544
24626
  "hashline-read-enhancer",
24545
- "read-image-resizer"
24627
+ "read-image-resizer",
24628
+ "todo-description-override"
24546
24629
  ]);
24547
24630
  // src/config/schema/notification.ts
24548
24631
  var NotificationConfigSchema = exports_external.object({
@@ -10,9 +10,9 @@ export declare const BackgroundTaskConfigSchema: z.ZodObject<{
10
10
  syncPollTimeoutMs: z.ZodOptional<z.ZodNumber>;
11
11
  maxToolCalls: z.ZodOptional<z.ZodNumber>;
12
12
  circuitBreaker: z.ZodOptional<z.ZodObject<{
13
+ enabled: z.ZodOptional<z.ZodBoolean>;
13
14
  maxToolCalls: z.ZodOptional<z.ZodNumber>;
14
- windowSize: z.ZodOptional<z.ZodNumber>;
15
- repetitionThresholdPercent: z.ZodOptional<z.ZodNumber>;
15
+ consecutiveThreshold: z.ZodOptional<z.ZodNumber>;
16
16
  }, z.core.$strip>>;
17
17
  }, z.core.$strip>;
18
18
  export type BackgroundTaskConfig = z.infer<typeof BackgroundTaskConfigSchema>;
@@ -1343,9 +1343,9 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
1343
1343
  syncPollTimeoutMs: z.ZodOptional<z.ZodNumber>;
1344
1344
  maxToolCalls: z.ZodOptional<z.ZodNumber>;
1345
1345
  circuitBreaker: z.ZodOptional<z.ZodObject<{
1346
+ enabled: z.ZodOptional<z.ZodBoolean>;
1346
1347
  maxToolCalls: z.ZodOptional<z.ZodNumber>;
1347
- windowSize: z.ZodOptional<z.ZodNumber>;
1348
- repetitionThresholdPercent: z.ZodOptional<z.ZodNumber>;
1348
+ consecutiveThreshold: z.ZodOptional<z.ZodNumber>;
1349
1349
  }, z.core.$strip>>;
1350
1350
  }, z.core.$strip>>;
1351
1351
  notification: z.ZodOptional<z.ZodObject<{
@@ -48,5 +48,6 @@ export declare const HookNameSchema: z.ZodEnum<{
48
48
  "anthropic-effort": "anthropic-effort";
49
49
  "hashline-read-enhancer": "hashline-read-enhancer";
50
50
  "read-image-resizer": "read-image-resizer";
51
+ "todo-description-override": "todo-description-override";
51
52
  }>;
52
53
  export type HookName = z.infer<typeof HookNameSchema>;
@@ -50,6 +50,7 @@ export declare function createHooks(args: {
50
50
  hashlineReadEnhancer: ReturnType<typeof import("./hooks").createHashlineReadEnhancerHook> | null;
51
51
  jsonErrorRecovery: ReturnType<typeof import("./hooks").createJsonErrorRecoveryHook> | null;
52
52
  readImageResizer: ReturnType<typeof import("./hooks").createReadImageResizerHook> | null;
53
+ todoDescriptionOverride: ReturnType<typeof import("./hooks").createTodoDescriptionOverrideHook> | null;
53
54
  contextWindowMonitor: ReturnType<typeof import("./hooks").createContextWindowMonitorHook> | null;
54
55
  preemptiveCompaction: ReturnType<typeof import("./hooks").createPreemptiveCompactionHook> | null;
55
56
  sessionRecovery: ReturnType<typeof import("./hooks").createSessionRecoveryHook> | null;
@@ -3708,20 +3708,18 @@
3708
3708
  "circuitBreaker": {
3709
3709
  "type": "object",
3710
3710
  "properties": {
3711
+ "enabled": {
3712
+ "type": "boolean"
3713
+ },
3711
3714
  "maxToolCalls": {
3712
3715
  "type": "integer",
3713
3716
  "minimum": 10,
3714
3717
  "maximum": 9007199254740991
3715
3718
  },
3716
- "windowSize": {
3719
+ "consecutiveThreshold": {
3717
3720
  "type": "integer",
3718
3721
  "minimum": 5,
3719
3722
  "maximum": 9007199254740991
3720
- },
3721
- "repetitionThresholdPercent": {
3722
- "type": "number",
3723
- "exclusiveMinimum": 0,
3724
- "maximum": 100
3725
3723
  }
3726
3724
  },
3727
3725
  "additionalProperties": false
@@ -6,8 +6,8 @@ export declare const MIN_STABILITY_TIME_MS: number;
6
6
  export declare const DEFAULT_STALE_TIMEOUT_MS = 1200000;
7
7
  export declare const DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
8
8
  export declare const DEFAULT_MAX_TOOL_CALLS = 200;
9
- export declare const DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE = 20;
10
- export declare const DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT = 80;
9
+ export declare const DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
10
+ export declare const DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
11
11
  export declare const MIN_RUNTIME_BEFORE_STALE_MS = 30000;
12
12
  export declare const MIN_IDLE_TIME_MS = 5000;
13
13
  export declare const POLLING_INTERVAL_MS = 3000;
@@ -1,17 +1,16 @@
1
1
  import type { BackgroundTaskConfig } from "../../config/schema";
2
2
  import type { ToolCallWindow } from "./types";
3
3
  export interface CircuitBreakerSettings {
4
+ enabled: boolean;
4
5
  maxToolCalls: number;
5
- windowSize: number;
6
- repetitionThresholdPercent: number;
6
+ consecutiveThreshold: number;
7
7
  }
8
8
  export interface ToolLoopDetectionResult {
9
9
  triggered: boolean;
10
10
  toolName?: string;
11
11
  repeatedCount?: number;
12
- sampleSize?: number;
13
- thresholdPercent?: number;
14
12
  }
15
13
  export declare function resolveCircuitBreakerSettings(config?: BackgroundTaskConfig): CircuitBreakerSettings;
16
- export declare function recordToolCall(window: ToolCallWindow | undefined, toolName: string, settings: CircuitBreakerSettings): ToolCallWindow;
14
+ export declare function recordToolCall(window: ToolCallWindow | undefined, toolName: string, settings: CircuitBreakerSettings, toolInput?: Record<string, unknown> | null): ToolCallWindow;
15
+ export declare function createToolCallSignature(toolName: string, toolInput?: Record<string, unknown> | null): string;
17
16
  export declare function detectRepetitiveToolUse(window: ToolCallWindow | undefined): ToolLoopDetectionResult;
@@ -45,6 +45,7 @@ export declare class BackgroundManager {
45
45
  private preStartDescendantReservations;
46
46
  private enableParentSessionNotifications;
47
47
  readonly taskHistory: TaskHistory;
48
+ private cachedCircuitBreakerSettings?;
48
49
  constructor(ctx: PluginInput, config?: BackgroundTaskConfig, options?: {
49
50
  tmuxConfig?: TmuxConfig;
50
51
  onSubagentSessionCreated?: OnSubagentSessionCreated;
@@ -0,0 +1,2 @@
1
+ export declare function isActiveSessionStatus(type: string): boolean;
2
+ export declare function isTerminalSessionStatus(type: string): boolean;
@@ -2,15 +2,15 @@ import type { FallbackEntry } from "../../shared/model-requirements";
2
2
  import type { SessionPermissionRule } from "../../shared/question-denied-session-permission";
3
3
  export type BackgroundTaskStatus = "pending" | "running" | "completed" | "error" | "cancelled" | "interrupt";
4
4
  export interface ToolCallWindow {
5
- toolNames: string[];
6
- windowSize: number;
7
- thresholdPercent: number;
5
+ lastSignature: string;
6
+ consecutiveCount: number;
7
+ threshold: number;
8
8
  }
9
9
  export interface TaskProgress {
10
10
  toolCalls: number;
11
11
  lastTool?: string;
12
12
  toolCallWindow?: ToolCallWindow;
13
- countedToolPartIDs?: string[];
13
+ countedToolPartIDs?: Set<string>;
14
14
  lastUpdate: Date;
15
15
  lastMessage?: string;
16
16
  lastMessageAt?: Date;
@@ -1 +1 @@
1
- export declare const START_WORK_TEMPLATE = "You are starting a Sisyphus work session.\n\n## ARGUMENTS\n\n- `/start-work [plan-name] [--worktree <path>]`\n - `plan-name` (optional): name or partial match of the plan to start\n - `--worktree <path>` (optional): absolute path to an existing git worktree to work in\n - If specified and valid: hook pre-sets worktree_path in boulder.json\n - If specified but invalid: you must run `git worktree add <path> <branch>` first\n - If omitted: you MUST choose or create a worktree (see Worktree Setup below)\n\n## WHAT TO DO\n\n1. **Find available plans**: Search for Prometheus-generated plan files at `.sisyphus/plans/`\n\n2. **Check for active boulder state**: Read `.sisyphus/boulder.json` if it exists\n\n3. **Decision logic**:\n - If `.sisyphus/boulder.json` exists AND plan is NOT complete (has unchecked boxes):\n - **APPEND** current session to session_ids\n - Continue work on existing plan\n - If no active plan OR plan is complete:\n - List available plan files\n - If ONE plan: auto-select it\n - If MULTIPLE plans: show list with timestamps, ask user to select\n\n4. **Worktree Setup** (when `worktree_path` not already set in boulder.json):\n 1. `git worktree list --porcelain` \u2014 see available worktrees\n 2. Create: `git worktree add <absolute-path> <branch-or-HEAD>`\n 3. Update boulder.json to add `\"worktree_path\": \"<absolute-path>\"`\n 4. All work happens inside that worktree directory\n\n5. **Create/Update boulder.json**:\n ```json\n {\n \"active_plan\": \"/absolute/path/to/plan.md\",\n \"started_at\": \"ISO_TIMESTAMP\",\n \"session_ids\": [\"session_id_1\", \"session_id_2\"],\n \"plan_name\": \"plan-name\",\n \"worktree_path\": \"/absolute/path/to/git/worktree\"\n }\n ```\n\n6. **Read the plan file** and start executing tasks according to atlas workflow\n\n## OUTPUT FORMAT\n\nWhen listing plans for selection:\n```\nAvailable Work Plans\n\nCurrent Time: {ISO timestamp}\nSession ID: {current session id}\n\n1. [plan-name-1.md] - Modified: {date} - Progress: 3/10 tasks\n2. [plan-name-2.md] - Modified: {date} - Progress: 0/5 tasks\n\nWhich plan would you like to work on? (Enter number or plan name)\n```\n\nWhen resuming existing work:\n```\nResuming Work Session\n\nActive Plan: {plan-name}\nProgress: {completed}/{total} tasks\nSessions: {count} (appending current session)\nWorktree: {worktree_path}\n\nReading plan and continuing from last incomplete task...\n```\n\nWhen auto-selecting single plan:\n```\nStarting Work Session\n\nPlan: {plan-name}\nSession ID: {session_id}\nStarted: {timestamp}\nWorktree: {worktree_path}\n\nReading plan and beginning execution...\n```\n\n## CRITICAL\n\n- The session_id is injected by the hook - use it directly\n- Always update boulder.json BEFORE starting work\n- Always set worktree_path in boulder.json before executing any tasks\n- Read the FULL plan file before delegating any tasks\n- Follow atlas delegation protocols (7-section format)";
1
+ export declare const START_WORK_TEMPLATE = "You are starting a Sisyphus work session.\n\n## ARGUMENTS\n\n- `/start-work [plan-name] [--worktree <path>]`\n - `plan-name` (optional): name or partial match of the plan to start\n - `--worktree <path>` (optional): absolute path to an existing git worktree to work in\n - If specified and valid: hook pre-sets worktree_path in boulder.json\n - If specified but invalid: you must run `git worktree add <path> <branch>` first\n - If omitted: work directly in the current project directory (no worktree)\n\n## WHAT TO DO\n\n1. **Find available plans**: Search for Prometheus-generated plan files at `.sisyphus/plans/`\n\n2. **Check for active boulder state**: Read `.sisyphus/boulder.json` if it exists\n\n3. **Decision logic**:\n - If `.sisyphus/boulder.json` exists AND plan is NOT complete (has unchecked boxes):\n - **APPEND** current session to session_ids\n - Continue work on existing plan\n - If no active plan OR plan is complete:\n - List available plan files\n - If ONE plan: auto-select it\n - If MULTIPLE plans: show list with timestamps, ask user to select\n\n4. **Worktree Setup** (ONLY when `--worktree` was explicitly specified and `worktree_path` not already set in boulder.json):\n 1. `git worktree list --porcelain` \u2014 see available worktrees\n 2. Create: `git worktree add <absolute-path> <branch-or-HEAD>`\n 3. Update boulder.json to add `\"worktree_path\": \"<absolute-path>\"`\n 4. All work happens inside that worktree directory\n\n5. **Create/Update boulder.json**:\n ```json\n {\n \"active_plan\": \"/absolute/path/to/plan.md\",\n \"started_at\": \"ISO_TIMESTAMP\",\n \"session_ids\": [\"session_id_1\", \"session_id_2\"],\n \"plan_name\": \"plan-name\",\n \"worktree_path\": \"/absolute/path/to/git/worktree\"\n }\n ```\n\n6. **Read the plan file** and start executing tasks according to atlas workflow\n\n## OUTPUT FORMAT\n\nWhen listing plans for selection:\n```\nAvailable Work Plans\n\nCurrent Time: {ISO timestamp}\nSession ID: {current session id}\n\n1. [plan-name-1.md] - Modified: {date} - Progress: 3/10 tasks\n2. [plan-name-2.md] - Modified: {date} - Progress: 0/5 tasks\n\nWhich plan would you like to work on? (Enter number or plan name)\n```\n\nWhen resuming existing work:\n```\nResuming Work Session\n\nActive Plan: {plan-name}\nProgress: {completed}/{total} tasks\nSessions: {count} (appending current session)\nWorktree: {worktree_path}\n\nReading plan and continuing from last incomplete task...\n```\n\nWhen auto-selecting single plan:\n```\nStarting Work Session\n\nPlan: {plan-name}\nSession ID: {session_id}\nStarted: {timestamp}\nWorktree: {worktree_path}\n\nReading plan and beginning execution...\n```\n\n## CRITICAL\n\n- The session_id is injected by the hook - use it directly\n- Always update boulder.json BEFORE starting work\n- If worktree_path is set in boulder.json, all work happens inside that worktree directory\n- Read the FULL plan file before delegating any tasks\n- Follow atlas delegation protocols (7-section format)\n\n## TASK BREAKDOWN (MANDATORY)\n\nAfter reading the plan file, you MUST decompose every plan task into granular, implementation-level sub-steps and register ALL of them as task/todo items BEFORE starting any work.\n\n**How to break down**:\n- Each plan checkbox item (e.g., `- [ ] Add user authentication`) must be split into concrete, actionable sub-tasks\n- Sub-tasks should be specific enough that each one touches a clear set of files/functions\n- Include: file to modify, what to change, expected behavior, and how to verify\n- Do NOT leave any task vague \u2014 \"implement feature X\" is NOT acceptable; \"add validateToken() to src/auth/middleware.ts that checks JWT expiry and returns 401\" IS acceptable\n\n**Example breakdown**:\nPlan task: `- [ ] Add rate limiting to API`\n\u2192 Todo items:\n 1. Create `src/middleware/rate-limiter.ts` with sliding window algorithm (max 100 req/min per IP)\n 2. Add RateLimiter middleware to `src/app.ts` router chain, before auth middleware\n 3. Add rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) to response in `rate-limiter.ts`\n 4. Add test: verify 429 response after exceeding limit in `src/middleware/rate-limiter.test.ts`\n 5. Add test: verify headers are present on normal responses\n\nRegister these as task/todo items so progress is tracked and visible throughout the session.\n\n## WORKTREE COMPLETION\n\nWhen working in a worktree (`worktree_path` is set in boulder.json) and ALL plan tasks are complete:\n1. Commit all remaining changes in the worktree\n2. Switch to the main working directory (the original repo, NOT the worktree)\n3. Merge the worktree branch into the current branch: `git merge <worktree-branch>`\n4. If merge succeeds, clean up: `git worktree remove <worktree-path>`\n5. Remove the boulder.json state\n\nThis is the DEFAULT behavior when `--worktree` was used. Skip merge only if the user explicitly instructs otherwise (e.g., asks to create a PR instead).";
@@ -48,3 +48,4 @@ export { createWriteExistingFileGuardHook } from "./write-existing-file-guard";
48
48
  export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer";
49
49
  export { createJsonErrorRecoveryHook, JSON_ERROR_TOOL_EXCLUDE_LIST, JSON_ERROR_PATTERNS, JSON_ERROR_REMINDER } from "./json-error-recovery";
50
50
  export { createReadImageResizerHook } from "./read-image-resizer";
51
+ export { createTodoDescriptionOverrideHook } from "./todo-description-override";
@@ -0,0 +1 @@
1
+ export declare const TODOWRITE_DESCRIPTION = "Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] \u2014 expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization \u2014 returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert \u2014 rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign \u2014 expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (this is a feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- Skip this tool for single trivial tasks (one-step, obvious action).";
@@ -0,0 +1,8 @@
1
+ export declare function createTodoDescriptionOverrideHook(): {
2
+ "tool.definition": (input: {
3
+ toolID: string;
4
+ }, output: {
5
+ description: string;
6
+ parameters: unknown;
7
+ }) => Promise<void>;
8
+ };
@@ -0,0 +1 @@
1
+ export { createTodoDescriptionOverrideHook } from "./hook";