Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions askcc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path
from string import Template

from . import __version__
from . import __version__, settings
from .definitions import AgentAction, AgentConfig, SupportedLanguage
from .functions import (
CheckResult,
Expand All @@ -26,7 +26,7 @@
write_prompt_content,
)
from .runners import DEFAULT_RUNNER, RUNNER_REGISTRY, get_runner
from .settings import configure_logging
from .settings import VALID_EFFORT_LEVELS, configure_logging

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -105,6 +105,35 @@ def main() -> None: # noqa: PLR0912, PLR0915, C901
default=DEFAULT_RUNNER,
help=f"Runner to execute the task (default: {DEFAULT_RUNNER}).",
)
parser.add_argument(
"--effort",
choices=VALID_EFFORT_LEVELS,
default=settings.ASKCC_CLAUDE_EFFORT_LEVEL,
help=f"Claude thinking effort level (default: {settings.ASKCC_CLAUDE_EFFORT_LEVEL}). "
"Env: ASKCC_CLAUDE_EFFORT_LEVEL.",
)
parser.add_argument(
"--max-thinking-tokens",
type=int,
default=settings.ASKCC_CLAUDE_MAX_THINKING_TOKENS,
help=f"Thinking token budget (default: {settings.ASKCC_CLAUDE_MAX_THINKING_TOKENS}). "
"Env: ASKCC_CLAUDE_MAX_THINKING_TOKENS.",
)
parser.add_argument(
"--disable-thinking",
action="store_true",
default=settings.ASKCC_CLAUDE_DISABLE_THINKING,
help=f"Force-disable extended thinking (default: {settings.ASKCC_CLAUDE_DISABLE_THINKING}). "
"Env: ASKCC_CLAUDE_DISABLE_THINKING.",
)
parser.add_argument(
"--disable-adaptive-thinking",
action=argparse.BooleanOptionalAction,
default=settings.ASKCC_CLAUDE_DISABLE_ADAPTIVE_THINKING,
help=f"Disable adaptive reasoning (Opus 4.6, Sonnet 4.6) "
f"(default: {settings.ASKCC_CLAUDE_DISABLE_ADAPTIVE_THINKING}). "
"Env: ASKCC_CLAUDE_DISABLE_ADAPTIVE_THINKING.",
)

subparsers = parser.add_subparsers(dest="command", required=True)

Expand Down Expand Up @@ -222,7 +251,16 @@ def main() -> None: # noqa: PLR0912, PLR0915, C901
prompt += f"\nOutput all comments in {args.language}."
runner = get_runner(args.runner)
try:
return_code, usage = runner.run(prompt, config=config, issue_url=issue_url, cwd=cwd)
return_code, usage = runner.run(
prompt,
config=config,
issue_url=issue_url,
cwd=cwd,
effort_level=args.effort,
max_thinking_tokens=args.max_thinking_tokens,
disable_thinking=args.disable_thinking,
disable_adaptive_thinking=args.disable_adaptive_thinking,
)
finally:
# Clean up /tmp files created by _build_prompt (not user templates)
for f in prompt_tempfiles:
Expand Down
20 changes: 20 additions & 0 deletions askcc/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

from .settings import CLAUDE_ENV_DISABLE_ADAPTIVE_THINKING, CLAUDE_ENV_DISABLE_THINKING, CLAUDE_ENV_MAX_THINKING_TOKENS

if TYPE_CHECKING:
from pathlib import Path

Expand All @@ -28,6 +30,10 @@ def run(
*,
issue_url: str,
cwd: Path,
effort_level: str | None = None,
max_thinking_tokens: int | None = None,
disable_thinking: bool = False,
disable_adaptive_thinking: bool = False,
) -> tuple[int, dict | None]:
"""Execute a prompt and return (exit_code, usage_dict_or_none)."""

Expand All @@ -42,6 +48,10 @@ def run(
*,
issue_url: str,
cwd: Path,
effort_level: str | None = None,
max_thinking_tokens: int | None = None,
disable_thinking: bool = False,
disable_adaptive_thinking: bool = False,
) -> tuple[int, dict | None]:
agent_definition = {config.action_name: {"description": config.description, "prompt": config.system_prompt}}

Expand All @@ -57,10 +67,20 @@ def run(
json.dumps(agent_definition),
]

if effort_level:
cmd.extend(["--effort", effort_level])

# Remove CLAUDECODE env var so the child claude process doesn't think it's nested inside Claude Code
env = os.environ.copy()
env.pop("CLAUDECODE", None)

if max_thinking_tokens is not None:
env[CLAUDE_ENV_MAX_THINKING_TOKENS] = str(max_thinking_tokens)
if disable_thinking:
env[CLAUDE_ENV_DISABLE_THINKING] = "1"
if disable_adaptive_thinking:
env[CLAUDE_ENV_DISABLE_ADAPTIVE_THINKING] = "1"

logger.info("[%s] Requesting '%s' from Claude Code ...", issue_url, config.action_name)
logger.info("[%s] Working directory: %s", issue_url, cwd)
logger.debug("[%s] Command: %s", issue_url, " ".join("<prompt>" if arg is prompt else arg for arg in cmd))
Expand Down
54 changes: 54 additions & 0 deletions askcc/settings.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import enum
import logging
import os
import sys
from logging.handlers import RotatingFileHandler
from pathlib import Path

logger = logging.getLogger(__name__)

DEFAULT_LOG_LEVEL = "INFO"
LOG_LEVEL = os.getenv("LOG_LEVEL", DEFAULT_LOG_LEVEL).upper()

Expand All @@ -28,6 +31,57 @@
# Project field transition
REVIEW_STATUS_OPTIONS: tuple[str, ...] = ("in-internal-review", "in-review")

# -- Claude thinking/reasoning controls --
# NOTE: VALID_EFFORT_LEVELS lives here (not definitions.py) to avoid a circular import;
# definitions.py already imports from settings.py.


class VALID_EFFORT_LEVELS(enum.StrEnum): # noqa: N801
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
MAX = "max"


DEFAULT_EFFORT_LEVEL = VALID_EFFORT_LEVELS.MAX


def _resolve_effort_level() -> VALID_EFFORT_LEVELS:
"""Resolve ASKCC_CLAUDE_EFFORT_LEVEL, warning on invalid values."""
raw = os.getenv("ASKCC_CLAUDE_EFFORT_LEVEL") or None
if raw is None:
return DEFAULT_EFFORT_LEVEL
try:
return VALID_EFFORT_LEVELS(raw)
except ValueError:
logger.warning(
"Invalid ASKCC_CLAUDE_EFFORT_LEVEL=%r (valid: %s). Ignoring.",
raw,
", ".join(VALID_EFFORT_LEVELS),
)
return DEFAULT_EFFORT_LEVEL


ASKCC_CLAUDE_EFFORT_LEVEL: VALID_EFFORT_LEVELS = _resolve_effort_level()

DEFAULT_MAX_THINKING_TOKENS = 21000 # ~5% of Max5 plan daily token budget (~422K tokens/day)

_raw_max_thinking = os.getenv("ASKCC_CLAUDE_MAX_THINKING_TOKENS", "")
ASKCC_CLAUDE_MAX_THINKING_TOKENS: int = (
int(_raw_max_thinking) if _raw_max_thinking.isdigit() else DEFAULT_MAX_THINKING_TOKENS
)

ASKCC_CLAUDE_DISABLE_THINKING: bool = os.getenv("ASKCC_CLAUDE_DISABLE_THINKING", "").lower() in ("1", "true")

ASKCC_CLAUDE_DISABLE_ADAPTIVE_THINKING: bool = os.getenv(
"ASKCC_CLAUDE_DISABLE_ADAPTIVE_THINKING", "true"
).lower() not in ("0", "false")

# Claude Code subprocess env var names
CLAUDE_ENV_MAX_THINKING_TOKENS = "MAX_THINKING_TOKENS"
CLAUDE_ENV_DISABLE_THINKING = "CLAUDE_CODE_DISABLE_THINKING"
CLAUDE_ENV_DISABLE_ADAPTIVE_THINKING = "CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING"

ASKCC_HOME: Path = Path(os.getenv("ASKCC_HOME") or str(Path.home() / ".askcc")).expanduser().resolve()
TEMPLATES_DIR: Path = ASKCC_HOME / "templates"
LOG_DIR: Path = ASKCC_HOME / "logs"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "askcc"
version = "0.2.3"
version = "0.2.4"
description = "A one-shot cc cli executor"
authors = [{ name = "mknt", email = "shane.cousins@gmail.com" }]
readme = "README.md"
Expand Down
Loading
Loading