aiwcli 0.10.2 → 0.11.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.
- package/bin/run.js +1 -1
- package/dist/commands/clear.d.ts +11 -6
- package/dist/commands/clear.js +229 -381
- package/dist/commands/init/index.d.ts +1 -17
- package/dist/commands/init/index.js +22 -107
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/lib/template-installer.d.ts +7 -12
- package/dist/lib/template-installer.js +69 -193
- package/dist/lib/template-settings-reconstructor.d.ts +35 -0
- package/dist/lib/template-settings-reconstructor.js +130 -0
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
- package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
- package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
- package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
- package/dist/templates/_shared/lib-ts/package.json +21 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
- package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
- package/dist/templates/_shared/lib-ts/types.ts +164 -0
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
- package/dist/templates/_shared/scripts/status_line.ts +733 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
- package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
- package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
- package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
- package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/lib/template-merger.d.ts +0 -47
- package/dist/lib/template-merger.js +0 -162
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -169
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
- package/dist/templates/_shared/lib/base/inference.py +0 -318
- package/dist/templates/_shared/lib/base/logger.py +0 -291
- package/dist/templates/_shared/lib/base/stop_words.py +0 -213
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -242
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -701
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
- package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
- package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
- package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
- package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
- package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
- package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
- package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
- package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
"""Inference utility for AI-powered text processing.
|
|
2
|
-
|
|
3
|
-
Provides a unified interface for Claude API calls using the claude CLI.
|
|
4
|
-
Supports multiple model tiers: fast (Haiku), standard (Sonnet), smart (Opus).
|
|
5
|
-
"""
|
|
6
|
-
import re
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
import os
|
|
10
|
-
from typing import Optional
|
|
11
|
-
|
|
12
|
-
from .logger import log_debug, log_info, log_warn, log_error
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class InferenceResult:
|
|
18
|
-
"""Result from an inference call."""
|
|
19
|
-
success: bool
|
|
20
|
-
output: str
|
|
21
|
-
error: Optional[str] = None
|
|
22
|
-
latency_ms: int = 0
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Model configurations
|
|
26
|
-
MODELS = {
|
|
27
|
-
"fast": "claude-3-haiku-20240307",
|
|
28
|
-
"standard": "claude-sonnet-4-20250514",
|
|
29
|
-
"smart": "claude-opus-4-20250514",
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
TIMEOUTS = {
|
|
33
|
-
"fast": 15, # 15 seconds
|
|
34
|
-
"standard": 30, # 30 seconds
|
|
35
|
-
"smart": 90, # 90 seconds
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def inference(
|
|
40
|
-
system_prompt: str,
|
|
41
|
-
user_prompt: str,
|
|
42
|
-
level: str = "fast",
|
|
43
|
-
timeout: Optional[int] = None,
|
|
44
|
-
) -> InferenceResult:
|
|
45
|
-
"""
|
|
46
|
-
Run inference using the claude CLI.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
system_prompt: System instructions for the model
|
|
50
|
-
user_prompt: User message to process
|
|
51
|
-
level: Model level - "fast" (Haiku), "standard" (Sonnet), "smart" (Opus)
|
|
52
|
-
timeout: Custom timeout in seconds (uses level default if not specified)
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
InferenceResult with success status, output, and any error
|
|
56
|
-
"""
|
|
57
|
-
import time
|
|
58
|
-
start_time = time.time()
|
|
59
|
-
|
|
60
|
-
model = MODELS.get(level, MODELS["fast"])
|
|
61
|
-
timeout_sec = timeout or TIMEOUTS.get(level, TIMEOUTS["fast"])
|
|
62
|
-
|
|
63
|
-
# Combine prompts
|
|
64
|
-
full_prompt = f"{system_prompt}\n\n{user_prompt}"
|
|
65
|
-
|
|
66
|
-
# Build command
|
|
67
|
-
cmd = [
|
|
68
|
-
"claude",
|
|
69
|
-
"--model", model,
|
|
70
|
-
"--print",
|
|
71
|
-
"--no-hooks",
|
|
72
|
-
"-p", full_prompt,
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
# Remove ANTHROPIC_API_KEY to force subscription auth
|
|
76
|
-
env = os.environ.copy()
|
|
77
|
-
env.pop("ANTHROPIC_API_KEY", None)
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
result = subprocess.run(
|
|
81
|
-
cmd,
|
|
82
|
-
capture_output=True,
|
|
83
|
-
text=True,
|
|
84
|
-
timeout=timeout_sec,
|
|
85
|
-
env=env,
|
|
86
|
-
# Windows needs shell=True for command resolution
|
|
87
|
-
shell=(sys.platform == "win32"),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
latency_ms = int((time.time() - start_time) * 1000)
|
|
91
|
-
|
|
92
|
-
if result.returncode != 0:
|
|
93
|
-
return InferenceResult(
|
|
94
|
-
success=False,
|
|
95
|
-
output=result.stdout.strip() if result.stdout else "",
|
|
96
|
-
error=result.stderr.strip() if result.stderr else f"Exit code: {result.returncode}",
|
|
97
|
-
latency_ms=latency_ms,
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
return InferenceResult(
|
|
101
|
-
success=True,
|
|
102
|
-
output=result.stdout.strip(),
|
|
103
|
-
latency_ms=latency_ms,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
except subprocess.TimeoutExpired:
|
|
107
|
-
latency_ms = int((time.time() - start_time) * 1000)
|
|
108
|
-
return InferenceResult(
|
|
109
|
-
success=False,
|
|
110
|
-
output="",
|
|
111
|
-
error=f"Timeout after {timeout_sec}s",
|
|
112
|
-
latency_ms=latency_ms,
|
|
113
|
-
)
|
|
114
|
-
except FileNotFoundError:
|
|
115
|
-
latency_ms = int((time.time() - start_time) * 1000)
|
|
116
|
-
return InferenceResult(
|
|
117
|
-
success=False,
|
|
118
|
-
output="",
|
|
119
|
-
error="claude CLI not found",
|
|
120
|
-
latency_ms=latency_ms,
|
|
121
|
-
)
|
|
122
|
-
except Exception as e:
|
|
123
|
-
latency_ms = int((time.time() - start_time) * 1000)
|
|
124
|
-
return InferenceResult(
|
|
125
|
-
success=False,
|
|
126
|
-
output="",
|
|
127
|
-
error=str(e),
|
|
128
|
-
latency_ms=latency_ms,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# Stop words for filtering (from corpus analysis of 1,424 documents)
|
|
133
|
-
from .stop_words import STOP_WORDS
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def filter_stop_words(text: str) -> str:
|
|
137
|
-
"""Remove stop words from text, keeping only content keywords."""
|
|
138
|
-
words = text.lower().split()
|
|
139
|
-
filtered = [w for w in words if w not in STOP_WORDS and len(w) > 1]
|
|
140
|
-
return ' '.join(filtered)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# System prompt for generating context ID summaries (keyword extraction for recognition)
|
|
144
|
-
CONTEXT_ID_SYSTEM_PROMPT = """Extract 6-12 keywords from what the user wants to do.
|
|
145
|
-
|
|
146
|
-
Rules:
|
|
147
|
-
- Output 6-12 keywords only
|
|
148
|
-
- Keywords: nouns, verbs, adjectives, technical terms, proper names
|
|
149
|
-
- NO function words: the, to, with, for, in, a, an, of, on, is, it, and, or, that, this, be, as, at, by, from
|
|
150
|
-
- Most important/specific words preferred
|
|
151
|
-
- No punctuation, no quotes
|
|
152
|
-
|
|
153
|
-
Examples:
|
|
154
|
-
- "I want to add user authentication" -> "add user authentication login security JWT tokens webapp service"
|
|
155
|
-
- "Fix the bug in the login flow" -> "fix bug login flow validation error redirect session auth handler"
|
|
156
|
-
- "Can you help me refactor this code" -> "refactor code cleanup architecture maintainability legacy modules structure patterns"
|
|
157
|
-
- "Update the README with new instructions" -> "update README documentation instructions setup configuration install guide steps"
|
|
158
|
-
|
|
159
|
-
Output ONLY the keywords separated by spaces, nothing else."""
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def generate_semantic_summary(prompt: str, timeout: int = 15) -> Optional[str]:
|
|
163
|
-
"""
|
|
164
|
-
Generate a keyword summary of a user prompt.
|
|
165
|
-
|
|
166
|
-
Uses Sonnet for quality inference. Returns None if inference fails.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
prompt: User prompt to summarize
|
|
170
|
-
timeout: Timeout in seconds (default 15)
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
Keyword summary string (5-10 words) or None if failed
|
|
174
|
-
"""
|
|
175
|
-
result = inference(
|
|
176
|
-
system_prompt=CONTEXT_ID_SYSTEM_PROMPT,
|
|
177
|
-
user_prompt=prompt,
|
|
178
|
-
level="standard",
|
|
179
|
-
timeout=timeout,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
if not result.success or not result.output:
|
|
183
|
-
return None
|
|
184
|
-
|
|
185
|
-
# Clean up the output
|
|
186
|
-
summary = result.output.strip()
|
|
187
|
-
# Remove any quotes
|
|
188
|
-
summary = summary.strip('"\'')
|
|
189
|
-
# Remove trailing punctuation
|
|
190
|
-
summary = summary.rstrip('.!?')
|
|
191
|
-
|
|
192
|
-
# Filter stop words
|
|
193
|
-
summary = filter_stop_words(summary)
|
|
194
|
-
|
|
195
|
-
# Validate 6-12 words for sufficient context
|
|
196
|
-
words = summary.split()
|
|
197
|
-
if len(words) < 6 or len(words) > 12:
|
|
198
|
-
return None
|
|
199
|
-
|
|
200
|
-
return summary
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
# System prompt for generating context ID slugs (3-12 keyword tags for folder names)
|
|
204
|
-
CONTEXT_ID_SLUG_PROMPT = """You are extracting keyword tags from a user's request to create a **folder name** for a work session.
|
|
205
|
-
|
|
206
|
-
## Why This Matters
|
|
207
|
-
|
|
208
|
-
These tags become part of a context ID like `260206-1959-refactor-webhook-retry-logic`. Users scan lists of 50-100+ such folder names to find past work sessions. Your job is to extract the 3-12 words that make THIS session instantly recognizable among hundreds of others.
|
|
209
|
-
|
|
210
|
-
Think: "If someone had 100 folders and needed to find this one by scanning names, which words would make it jump out?"
|
|
211
|
-
|
|
212
|
-
## First Word: Action Verb (REQUIRED)
|
|
213
|
-
|
|
214
|
-
The first word MUST be a specific action verb. Choose the most precise verb available:
|
|
215
|
-
|
|
216
|
-
Common: fix, add, implement, refactor, update, create, remove, replace, optimize, debug
|
|
217
|
-
Specific (preferred when they fit): scaffold, instrument, serialize, throttle, migrate, integrate, extract, redesign, restructure, decouple, consolidate, parallelize, configure, deploy, benchmark, normalize, validate, document, deprecate, upstream
|
|
218
|
-
|
|
219
|
-
## Word Selection: Rarity = Quality
|
|
220
|
-
|
|
221
|
-
The less common a word is in everyday language, the better it is as an identifier. Apply this mental filter:
|
|
222
|
-
|
|
223
|
-
BEST (+3): Proper nouns, brand names, unique identifiers (PostgreSQL, Webpack, OAuth, JWT, CICD)
|
|
224
|
-
GOOD (+2): Domain-specific technical terms (middleware, pooling, webhook, serialization, throttle)
|
|
225
|
-
OKAY (+1): Specific common nouns (authentication, redirect, navbar, pagination, dropdown)
|
|
226
|
-
WEAK (-1): Generic nouns that appear in most prompts (code, file, app, feature, issue, project, stuff, thing)
|
|
227
|
-
BANNED: See banned list below — these must NEVER appear in output
|
|
228
|
-
|
|
229
|
-
Select the 3-12 highest-value words. When choosing between two words that mean similar things, always pick the rarer/more specific one.
|
|
230
|
-
|
|
231
|
-
## Banned Words (NEVER include these)
|
|
232
|
-
|
|
233
|
-
Greetings/social: sure, okay, ok, hi, hello, hey, thanks, yeah, yes, no, well, right, um, uh
|
|
234
|
-
Pronouns/articles: I, my, me, we, our, you, your, he, she, it, they, the, a, an, this, that, these, those
|
|
235
|
-
Auxiliaries/modals: is, are, was, were, be, been, being, can, could, would, should, will, shall, may, might, must, do, does, did, have, has, had
|
|
236
|
-
Prepositions: to, with, for, in, of, on, at, by, from, into, about, through, between, after, before, during
|
|
237
|
-
Conjunctions: and, or, but, so, because, if, when, while, although, since
|
|
238
|
-
Filler/hedging: want, need, help, try, think, know, look, going, trying, looking, basically, actually, really, just, some, also, please, maybe, probably, kind, sort, pretty, very, quite, like
|
|
239
|
-
Generic: code, file, app, stuff, thing, feature, issue, problem, way, part, bit, lot, something
|
|
240
|
-
|
|
241
|
-
## Bad vs Good Examples
|
|
242
|
-
|
|
243
|
-
BAD: sure help refactor code cleanup -> starts with filler, "code" is generic
|
|
244
|
-
GOOD: refactor database queries connection pooling
|
|
245
|
-
|
|
246
|
-
BAD: want add feature authentication -> "want" is filler, "feature" is noise
|
|
247
|
-
GOOD: add authentication Express JWT middleware
|
|
248
|
-
|
|
249
|
-
BAD: looking fix issue login page -> gerund opener, "issue" is generic
|
|
250
|
-
GOOD: fix login redirect blank page session
|
|
251
|
-
|
|
252
|
-
BAD: improve things make better setup -> vague nouns, no technical specificity
|
|
253
|
-
GOOD: improve CI pipeline GitHub Actions caching
|
|
254
|
-
|
|
255
|
-
BAD: help update documentation readme stuff -> "help" opener, "stuff" is noise
|
|
256
|
-
GOOD: update README API endpoints documentation
|
|
257
|
-
|
|
258
|
-
BAD: thinking about maybe changing prompt -> hedging words, no specificity
|
|
259
|
-
GOOD: optimize context ID extraction prompting Opus
|
|
260
|
-
|
|
261
|
-
## Output
|
|
262
|
-
|
|
263
|
-
Output ONLY the keyword tags separated by spaces. Nothing else. No reasoning, no labels, no punctuation, no quotes, no hyphens, no markdown."""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def generate_context_id_slug(prompt: str, timeout: int = 3) -> Optional[str]:
|
|
267
|
-
"""
|
|
268
|
-
Generate a 3-12 word context ID slug from a user prompt using AI inference.
|
|
269
|
-
|
|
270
|
-
Uses Haiku (fast tier) for low-latency keyword extraction within hook timeout budgets.
|
|
271
|
-
Returns a cleaned, validated slug or None on failure.
|
|
272
|
-
|
|
273
|
-
Args:
|
|
274
|
-
prompt: Raw user prompt to extract keywords from
|
|
275
|
-
timeout: Timeout in seconds (default 3, fits within 5-10s hook budget)
|
|
276
|
-
|
|
277
|
-
Returns:
|
|
278
|
-
Space-separated keyword slug (3-12 words) or None if failed
|
|
279
|
-
"""
|
|
280
|
-
# Truncate input to 500 chars to keep inference fast
|
|
281
|
-
truncated = prompt[:500] if len(prompt) > 500 else prompt
|
|
282
|
-
|
|
283
|
-
result = inference(
|
|
284
|
-
system_prompt=CONTEXT_ID_SLUG_PROMPT,
|
|
285
|
-
user_prompt=truncated,
|
|
286
|
-
level="fast",
|
|
287
|
-
timeout=timeout,
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
if not result.success or not result.output:
|
|
291
|
-
log_warn("inference", f"Context ID slug inference failed: {result.error}")
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
slug = result.output.strip()
|
|
295
|
-
|
|
296
|
-
# Clean: strip quotes, punctuation, hyphens
|
|
297
|
-
slug = slug.strip('"\'`')
|
|
298
|
-
slug = slug.rstrip('.!?')
|
|
299
|
-
slug = slug.replace('-', ' ')
|
|
300
|
-
|
|
301
|
-
# Remove non-alphanumeric chars (except spaces)
|
|
302
|
-
slug = re.sub(r'[^a-zA-Z0-9 ]', '', slug)
|
|
303
|
-
|
|
304
|
-
# Normalize whitespace
|
|
305
|
-
slug = re.sub(r'\s+', ' ', slug).strip()
|
|
306
|
-
|
|
307
|
-
words = slug.split()
|
|
308
|
-
|
|
309
|
-
# Validate word count: truncate if over 12, reject if under 3
|
|
310
|
-
if len(words) > 12:
|
|
311
|
-
words = words[:12]
|
|
312
|
-
if len(words) < 3:
|
|
313
|
-
log_debug("inference", f"Context ID slug too short ({len(words)} words): '{slug}'")
|
|
314
|
-
return None
|
|
315
|
-
|
|
316
|
-
result_slug = ' '.join(words)
|
|
317
|
-
log_debug("inference", f"Generated context ID slug: '{result_slug}' ({result.latency_ms}ms)")
|
|
318
|
-
return result_slug
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
"""Unified logging for all hooks and libraries.
|
|
2
|
-
|
|
3
|
-
Provides a single logging interface that replaces:
|
|
4
|
-
- log_hook_error() from hook_utils.py (error-only, plain text)
|
|
5
|
-
- debug.py from cc-native (per-context, plain text)
|
|
6
|
-
- eprint() for diagnostic output (stderr-only, no persistence)
|
|
7
|
-
|
|
8
|
-
Log format: JSONL (one JSON object per line)
|
|
9
|
-
Log locations:
|
|
10
|
-
- With context: _output/contexts/<context-id>/debug/hook-log.jsonl
|
|
11
|
-
- Without context (fallback): _output/hook-log.jsonl
|
|
12
|
-
|
|
13
|
-
Environment variables:
|
|
14
|
-
- HOOK_LOG_DISABLE=1: Disable all file logging
|
|
15
|
-
- HOOK_LOG_LEVEL=warn: Minimum level to log (default: debug)
|
|
16
|
-
- HOOK_ERROR_LOG_DISABLE=1: Legacy alias for HOOK_LOG_DISABLE
|
|
17
|
-
|
|
18
|
-
Never raises — all errors silently swallowed.
|
|
19
|
-
No buffering — each call is one open+write+close.
|
|
20
|
-
Stdlib only — json, os, sys, datetime, pathlib.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import json
|
|
24
|
-
import os
|
|
25
|
-
import sys
|
|
26
|
-
from datetime import datetime, timezone
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from typing import Any, Dict, Optional
|
|
29
|
-
|
|
30
|
-
_LEVELS = {"debug": 0, "info": 1, "warn": 2, "error": 3}
|
|
31
|
-
|
|
32
|
-
_MAX_LOG_SIZE = 1024 * 1024 # 1MB
|
|
33
|
-
_TRUNCATE_TO = 512 * 1024 # 512KB
|
|
34
|
-
|
|
35
|
-
# Module-level context path cache.
|
|
36
|
-
# Set once per hook process via set_context_path() or auto-resolved on first use.
|
|
37
|
-
_cached_context_path: Optional[Path] = None
|
|
38
|
-
_context_resolved: bool = False
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def set_context_path(path: Optional[Path]) -> None:
|
|
42
|
-
"""Set the context path for this process. All subsequent log calls use it.
|
|
43
|
-
|
|
44
|
-
Call this once in your hook after resolving the context:
|
|
45
|
-
from lib.base.logger import set_context_path
|
|
46
|
-
set_context_path(get_context_dir(context_id, project_root))
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
path: Path to context folder (e.g., _output/contexts/<context-id>/)
|
|
50
|
-
or None to force global-only logging.
|
|
51
|
-
"""
|
|
52
|
-
global _cached_context_path, _context_resolved
|
|
53
|
-
_cached_context_path = path
|
|
54
|
-
_context_resolved = True
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _auto_resolve_context_path() -> Optional[Path]:
|
|
58
|
-
"""Try to auto-resolve context path from session_id. Called once per process.
|
|
59
|
-
|
|
60
|
-
Uses the context store to look up which context owns this session,
|
|
61
|
-
then returns its directory path. Falls back to None (global log).
|
|
62
|
-
"""
|
|
63
|
-
global _cached_context_path, _context_resolved
|
|
64
|
-
_context_resolved = True # Don't retry on failure
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
from ..context.context_store import get_context_by_session_id
|
|
68
|
-
from .constants import get_context_dir
|
|
69
|
-
|
|
70
|
-
# Hook input isn't available here, but we can check if a recent
|
|
71
|
-
# context dir exists by scanning _output/contexts/ for one that
|
|
72
|
-
# has a state.json with a matching session
|
|
73
|
-
# This is too expensive for a logger. Instead, rely on set_context_path().
|
|
74
|
-
except Exception:
|
|
75
|
-
pass
|
|
76
|
-
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _get_context_path() -> Optional[Path]:
|
|
81
|
-
"""Get the cached context path, auto-resolving on first call."""
|
|
82
|
-
global _context_resolved
|
|
83
|
-
if not _context_resolved:
|
|
84
|
-
_auto_resolve_context_path()
|
|
85
|
-
return _cached_context_path
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _get_min_level() -> int:
|
|
89
|
-
"""Get minimum log level from environment."""
|
|
90
|
-
env = os.environ.get("HOOK_LOG_LEVEL", "debug").lower()
|
|
91
|
-
return _LEVELS.get(env, 0)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _is_disabled() -> bool:
|
|
95
|
-
"""Check if file logging is disabled."""
|
|
96
|
-
if os.environ.get("HOOK_LOG_DISABLE") == "1":
|
|
97
|
-
return True
|
|
98
|
-
if os.environ.get("HOOK_ERROR_LOG_DISABLE") == "1":
|
|
99
|
-
return True
|
|
100
|
-
return False
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _get_project_root() -> Path:
|
|
104
|
-
"""Get project root from environment or cwd."""
|
|
105
|
-
env_dir = os.environ.get("CLAUDE_PROJECT_DIR", "")
|
|
106
|
-
return Path(env_dir) if env_dir else Path.cwd()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def hook_log(
|
|
110
|
-
level: str,
|
|
111
|
-
hook_name: str,
|
|
112
|
-
message: str,
|
|
113
|
-
*,
|
|
114
|
-
component: str = "",
|
|
115
|
-
data: Any = None,
|
|
116
|
-
traceback_str: str = "",
|
|
117
|
-
context_path: Optional[Path] = None,
|
|
118
|
-
stderr: bool = True,
|
|
119
|
-
) -> None:
|
|
120
|
-
"""Write a structured log entry.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
level: "debug" | "info" | "warn" | "error"
|
|
124
|
-
hook_name: Hook or module name (e.g., "session_end")
|
|
125
|
-
message: Log message
|
|
126
|
-
component: Sub-component (e.g., "git", "parse")
|
|
127
|
-
data: Optional structured data (must be JSON-serializable)
|
|
128
|
-
traceback_str: Optional traceback string
|
|
129
|
-
context_path: If provided, logs to context debug dir
|
|
130
|
-
stderr: Also write to stderr (default: True)
|
|
131
|
-
"""
|
|
132
|
-
try:
|
|
133
|
-
level_lower = level.lower()
|
|
134
|
-
level_num = _LEVELS.get(level_lower, 0)
|
|
135
|
-
|
|
136
|
-
# Write to stderr if requested
|
|
137
|
-
if stderr:
|
|
138
|
-
prefix = f"[{hook_name}]"
|
|
139
|
-
if component:
|
|
140
|
-
prefix = f"[{hook_name}:{component}]"
|
|
141
|
-
print(f"{prefix} {message}", file=sys.stderr)
|
|
142
|
-
if traceback_str:
|
|
143
|
-
print(traceback_str, file=sys.stderr)
|
|
144
|
-
|
|
145
|
-
# Check if file logging is enabled
|
|
146
|
-
if _is_disabled():
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
# Check minimum level
|
|
150
|
-
if level_num < _get_min_level():
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
# Build JSONL entry
|
|
154
|
-
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
|
155
|
-
entry = {
|
|
156
|
-
"ts": ts,
|
|
157
|
-
"level": level_lower,
|
|
158
|
-
"hook": hook_name,
|
|
159
|
-
"msg": message,
|
|
160
|
-
}
|
|
161
|
-
if component:
|
|
162
|
-
entry["component"] = component
|
|
163
|
-
if data is not None:
|
|
164
|
-
try:
|
|
165
|
-
json.dumps(data, default=str) # Validate serializable
|
|
166
|
-
entry["data"] = data
|
|
167
|
-
except (TypeError, ValueError):
|
|
168
|
-
entry["data"] = str(data)
|
|
169
|
-
if traceback_str:
|
|
170
|
-
entry["tb"] = traceback_str.rstrip()
|
|
171
|
-
|
|
172
|
-
line = json.dumps(entry, ensure_ascii=True, default=str) + "\n"
|
|
173
|
-
|
|
174
|
-
# Determine log path: explicit > cached > global
|
|
175
|
-
resolved_ctx = context_path or _get_context_path()
|
|
176
|
-
if resolved_ctx and resolved_ctx.exists():
|
|
177
|
-
log_path = resolved_ctx / "debug" / "hook-log.jsonl"
|
|
178
|
-
else:
|
|
179
|
-
project_root = _get_project_root()
|
|
180
|
-
log_path = project_root / "_output" / "hook-log.jsonl"
|
|
181
|
-
|
|
182
|
-
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
-
|
|
184
|
-
# Size guard (global log only, not per-context)
|
|
185
|
-
if not resolved_ctx and log_path.exists():
|
|
186
|
-
try:
|
|
187
|
-
if log_path.stat().st_size > _MAX_LOG_SIZE:
|
|
188
|
-
file_data = log_path.read_bytes()
|
|
189
|
-
log_path.write_bytes(file_data[-_TRUNCATE_TO:])
|
|
190
|
-
except OSError:
|
|
191
|
-
pass
|
|
192
|
-
|
|
193
|
-
with open(log_path, "a", encoding="utf-8") as f:
|
|
194
|
-
f.write(line)
|
|
195
|
-
|
|
196
|
-
except Exception:
|
|
197
|
-
pass # Never crash
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def log_debug(hook_name: str, message: str, **kwargs: Any) -> None:
|
|
201
|
-
"""Log a debug-level message."""
|
|
202
|
-
hook_log("debug", hook_name, message, **kwargs)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def log_info(hook_name: str, message: str, **kwargs: Any) -> None:
|
|
206
|
-
"""Log an info-level message."""
|
|
207
|
-
hook_log("info", hook_name, message, **kwargs)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def log_warn(hook_name: str, message: str, **kwargs: Any) -> None:
|
|
211
|
-
"""Log a warn-level message."""
|
|
212
|
-
hook_log("warn", hook_name, message, **kwargs)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def log_error(hook_name: str, message: str, **kwargs: Any) -> None:
|
|
216
|
-
"""Log an error-level message."""
|
|
217
|
-
hook_log("error", hook_name, message, **kwargs)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def log_diagnostic(
|
|
221
|
-
hook_name: str,
|
|
222
|
-
phase: str,
|
|
223
|
-
summary: str,
|
|
224
|
-
*,
|
|
225
|
-
inputs: Any = None,
|
|
226
|
-
decision: Any = None,
|
|
227
|
-
reasoning: Any = None,
|
|
228
|
-
component: str = "diag",
|
|
229
|
-
data: Any = None,
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Log a structured diagnostic entry at a hook decision point.
|
|
232
|
-
|
|
233
|
-
Emits a debug-level JSONL entry with tagged, filterable data.
|
|
234
|
-
Use at key decision points: receive (what came in), decide (what was chosen),
|
|
235
|
-
result (what happened).
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
hook_name: Hook or module name (e.g., "session_start")
|
|
239
|
-
phase: Decision phase — "receive", "decide", or "result"
|
|
240
|
-
summary: One-line description (e.g., "source=clear, session=a1b2c3d4")
|
|
241
|
-
inputs: Input data relevant to this phase
|
|
242
|
-
decision: The decision made (for "decide" phase)
|
|
243
|
-
reasoning: Why this decision was made
|
|
244
|
-
component: Log component tag (default: "diag")
|
|
245
|
-
data: Extra data to merge into the structured entry
|
|
246
|
-
"""
|
|
247
|
-
diag_data: Dict[str, Any] = {"phase": phase}
|
|
248
|
-
if inputs is not None:
|
|
249
|
-
diag_data["inputs"] = inputs
|
|
250
|
-
if decision is not None:
|
|
251
|
-
diag_data["decision"] = decision
|
|
252
|
-
if reasoning is not None:
|
|
253
|
-
diag_data["reasoning"] = reasoning
|
|
254
|
-
if data is not None and isinstance(data, dict):
|
|
255
|
-
diag_data.update(data)
|
|
256
|
-
hook_log(
|
|
257
|
-
"debug",
|
|
258
|
-
hook_name,
|
|
259
|
-
f"[DIAG:{phase}] {summary}",
|
|
260
|
-
component=component,
|
|
261
|
-
data=diag_data,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def log_hook_error(
|
|
266
|
-
hook_name: str,
|
|
267
|
-
error: Exception,
|
|
268
|
-
hook_event: str = "unknown",
|
|
269
|
-
traceback_str: str = "",
|
|
270
|
-
) -> None:
|
|
271
|
-
"""Backward-compatible wrapper matching the old hook_utils.log_hook_error signature.
|
|
272
|
-
|
|
273
|
-
Delegates to hook_log("error", ...) with the same behavior:
|
|
274
|
-
- Message capped at 200 chars, newlines stripped
|
|
275
|
-
- Never raises
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
hook_name: Name of the hook
|
|
279
|
-
error: The exception that occurred
|
|
280
|
-
hook_event: Hook event type (e.g., "PreToolUse")
|
|
281
|
-
traceback_str: Optional formatted traceback
|
|
282
|
-
"""
|
|
283
|
-
msg = str(error).replace("\n", " ").replace("\r", "")[:200]
|
|
284
|
-
err_type = type(error).__name__
|
|
285
|
-
hook_log(
|
|
286
|
-
"error",
|
|
287
|
-
hook_name,
|
|
288
|
-
f"[{hook_event}] {err_type}: {msg}",
|
|
289
|
-
traceback_str=traceback_str,
|
|
290
|
-
stderr=True,
|
|
291
|
-
)
|