aiwcli 0.9.1 → 0.9.4
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/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__/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 +28 -38
- package/dist/templates/_shared/hooks/context_enforcer.py +71 -21
- package/dist/templates/_shared/hooks/context_monitor.py +4 -8
- package/dist/templates/_shared/hooks/file-suggestion.py +4 -10
- package/dist/templates/_shared/hooks/session_start.py +103 -0
- package/dist/templates/_shared/hooks/task_create_atomicity.py +205 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +83 -146
- package/dist/templates/_shared/hooks/task_update_capture.py +116 -167
- package/dist/templates/_shared/hooks/user_prompt_submit.py +62 -22
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- 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__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/hook_utils.py +169 -0
- package/dist/templates/_shared/lib/base/inference.py +20 -35
- package/dist/templates/_shared/lib/base/stop_words.py +158 -0
- package/dist/templates/_shared/lib/base/utils.py +3 -2
- package/dist/templates/_shared/lib/context/__init__.py +9 -2
- 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_manager.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__/plan_archive.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/context_extractor.py +115 -0
- package/dist/templates/_shared/lib/context/context_manager.py +2 -2
- package/dist/templates/_shared/lib/context/discovery.py +4 -4
- package/dist/templates/_shared/lib/context/task_sync.py +5 -82
- 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/persona_questions.py +113 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +13 -27
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +21 -48
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +26 -204
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +25 -76
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +32 -77
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +26 -189
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +30 -52
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +27 -63
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +32 -81
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +25 -106
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +24 -209
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +26 -200
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +36 -206
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +27 -177
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +23 -66
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +26 -162
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +29 -59
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +28 -312
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +23 -74
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +21 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +211 -0
- 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__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +65 -12
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +240 -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/debug.py +124 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +1 -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/agent.py +34 -1
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +7 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +0 -147
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook for TaskCreate - assesses atomicity and forkability via inference.
|
|
3
|
+
|
|
4
|
+
Ensures tasks contain sufficient self-contained context for independent execution,
|
|
5
|
+
especially when delegated to subagents with zero conversation history.
|
|
6
|
+
|
|
7
|
+
Non-blocking: Warns but allows creation even if atomicity is poor.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Path setup
|
|
15
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
16
|
+
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
17
|
+
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
18
|
+
|
|
19
|
+
from lib.base.hook_utils import (
|
|
20
|
+
load_hook_input,
|
|
21
|
+
validate_hook_event,
|
|
22
|
+
get_tool_input,
|
|
23
|
+
safe_hook_main,
|
|
24
|
+
run_hook,
|
|
25
|
+
)
|
|
26
|
+
from lib.base.utils import eprint
|
|
27
|
+
from lib.base.subprocess_utils import is_internal_call
|
|
28
|
+
from lib.base.inference import inference
|
|
29
|
+
|
|
30
|
+
# Prompt engineered per Prompting/Standards.md:
|
|
31
|
+
# - Markdown-only (no XML)
|
|
32
|
+
# - Positive framing (tell what TO do)
|
|
33
|
+
# - 1-3 clear examples matching desired output
|
|
34
|
+
# - Direct imperative instructions
|
|
35
|
+
# - Explicit JSON output format
|
|
36
|
+
|
|
37
|
+
ASSESSMENT_SYSTEM_PROMPT = """You assess task descriptions for atomicity and forkability.
|
|
38
|
+
|
|
39
|
+
## Definitions
|
|
40
|
+
|
|
41
|
+
**Atomic Task:** Contains ALL context needed for independent execution without reading prior conversation.
|
|
42
|
+
|
|
43
|
+
**Forkable Task:** Can be delegated to a subagent with ZERO conversation history and still be completed successfully.
|
|
44
|
+
|
|
45
|
+
## Signs of Non-Atomic Tasks
|
|
46
|
+
|
|
47
|
+
Look for these indicators:
|
|
48
|
+
- Contextual references: "the file above", "as discussed", "the mentioned function", "this bug"
|
|
49
|
+
- Vague descriptions assuming prior knowledge: "fix the bug", "update it", "finish the work"
|
|
50
|
+
- Missing specifics: which file? what function? what expected behavior? what error?
|
|
51
|
+
- Pronouns without antecedents: "it", "they", "the issue" without explicit definition
|
|
52
|
+
|
|
53
|
+
## Signs of Atomic Tasks
|
|
54
|
+
|
|
55
|
+
Well-specified tasks include:
|
|
56
|
+
- Explicit file paths: "Edit src/utils/parser.py"
|
|
57
|
+
- Specific function names: "Modify the validate_input() function"
|
|
58
|
+
- Clear expected behavior: "Should return 404 when user not found"
|
|
59
|
+
- Complete error context: "TypeError on line 45 when input is None"
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
**Example 1: Non-Atomic Task**
|
|
64
|
+
Subject: "Fix the bug"
|
|
65
|
+
Description: "The issue we discussed earlier needs to be resolved"
|
|
66
|
+
Assessment: NOT atomic (no file, no function, no error details, references "discussed earlier")
|
|
67
|
+
|
|
68
|
+
**Example 2: Atomic Task**
|
|
69
|
+
Subject: "Fix null pointer in user lookup"
|
|
70
|
+
Description: "In src/services/user.py, the get_user_by_id() function raises TypeError when user_id is None. Add null check at line 23 that returns None early instead of calling database.query()."
|
|
71
|
+
Assessment: Atomic (file path, function name, specific error, exact fix location, expected behavior)
|
|
72
|
+
|
|
73
|
+
**Example 3: Partially Atomic Task**
|
|
74
|
+
Subject: "Add validation to form"
|
|
75
|
+
Description: "Add email validation to the signup form. Return error message if invalid."
|
|
76
|
+
Assessment: NOT fully atomic (missing: which file contains the form? what validation rules? where to display error?)
|
|
77
|
+
|
|
78
|
+
## Output Format
|
|
79
|
+
|
|
80
|
+
Respond with valid JSON only:
|
|
81
|
+
{
|
|
82
|
+
"atomic": true/false,
|
|
83
|
+
"forkable": true/false,
|
|
84
|
+
"issues": ["specific issue 1", "specific issue 2"],
|
|
85
|
+
"recommendation": "brief actionable suggestion if issues exist, or 'Task is well-specified' if good"
|
|
86
|
+
}"""
|
|
87
|
+
|
|
88
|
+
ASSESSMENT_USER_TEMPLATE = """Assess this task for atomicity and forkability:
|
|
89
|
+
|
|
90
|
+
**Subject:** {subject}
|
|
91
|
+
|
|
92
|
+
**Description:** {description}
|
|
93
|
+
|
|
94
|
+
Evaluate whether a subagent with zero prior context could execute this task successfully."""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@safe_hook_main("task_create_atomicity")
|
|
98
|
+
def main() -> int:
|
|
99
|
+
# Skip internal calls (prevents recursion from orchestrator/inference)
|
|
100
|
+
if is_internal_call():
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
# Load and validate hook input
|
|
104
|
+
payload = load_hook_input()
|
|
105
|
+
if not payload:
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
# Only process TaskCreate
|
|
109
|
+
if not validate_hook_event(payload, "PreToolUse", "TaskCreate"):
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
tool_input = get_tool_input(payload)
|
|
113
|
+
if not tool_input:
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
subject = tool_input.get("subject", "")
|
|
117
|
+
description = tool_input.get("description", "")
|
|
118
|
+
|
|
119
|
+
# Skip very short tasks (likely intentionally brief or simple acknowledgments)
|
|
120
|
+
if len(description.strip()) < 15:
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
# Call inference to assess atomicity and forkability
|
|
124
|
+
result = inference(
|
|
125
|
+
system_prompt=ASSESSMENT_SYSTEM_PROMPT,
|
|
126
|
+
user_prompt=ASSESSMENT_USER_TEMPLATE.format(
|
|
127
|
+
subject=subject,
|
|
128
|
+
description=description
|
|
129
|
+
),
|
|
130
|
+
level="fast", # Use Haiku for minimal latency (~1-2s)
|
|
131
|
+
timeout=12, # Allow up to 12s for inference
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if not result.success:
|
|
135
|
+
eprint(f"[task_create_atomicity] Inference failed: {result.error}")
|
|
136
|
+
return 0 # Non-blocking on failure
|
|
137
|
+
|
|
138
|
+
# Parse JSON response
|
|
139
|
+
try:
|
|
140
|
+
# Handle potential markdown code blocks in response
|
|
141
|
+
output = result.output.strip()
|
|
142
|
+
if output.startswith("```"):
|
|
143
|
+
# Extract JSON from code block
|
|
144
|
+
lines = output.split("\n")
|
|
145
|
+
json_lines = []
|
|
146
|
+
in_block = False
|
|
147
|
+
for line in lines:
|
|
148
|
+
if line.startswith("```") and not in_block:
|
|
149
|
+
in_block = True
|
|
150
|
+
continue
|
|
151
|
+
elif line.startswith("```") and in_block:
|
|
152
|
+
break
|
|
153
|
+
elif in_block:
|
|
154
|
+
json_lines.append(line)
|
|
155
|
+
output = "\n".join(json_lines)
|
|
156
|
+
|
|
157
|
+
assessment = json.loads(output)
|
|
158
|
+
except json.JSONDecodeError:
|
|
159
|
+
eprint(f"[task_create_atomicity] Failed to parse inference response: {result.output[:100]}")
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
# Extract assessment fields
|
|
163
|
+
atomic = assessment.get("atomic", True)
|
|
164
|
+
forkable = assessment.get("forkable", True)
|
|
165
|
+
issues = assessment.get("issues", [])
|
|
166
|
+
recommendation = assessment.get("recommendation", "")
|
|
167
|
+
|
|
168
|
+
# Build context message based on assessment
|
|
169
|
+
if atomic and forkable:
|
|
170
|
+
# Task is good - minimal positive feedback
|
|
171
|
+
context_msg = "Task Assessment: Well-specified and forkable."
|
|
172
|
+
else:
|
|
173
|
+
# Task has issues - inject detailed warning
|
|
174
|
+
status_parts = []
|
|
175
|
+
if not atomic:
|
|
176
|
+
status_parts.append("NOT ATOMIC")
|
|
177
|
+
if not forkable:
|
|
178
|
+
status_parts.append("NOT FORKABLE")
|
|
179
|
+
|
|
180
|
+
issues_text = "\n".join(f"- {issue}" for issue in issues) if issues else "- See recommendation below"
|
|
181
|
+
|
|
182
|
+
context_msg = f"""**TASK ATOMICITY WARNING** ({', '.join(status_parts)})
|
|
183
|
+
|
|
184
|
+
This task may lack sufficient context for independent execution by a subagent.
|
|
185
|
+
|
|
186
|
+
**Issues detected:**
|
|
187
|
+
{issues_text}
|
|
188
|
+
|
|
189
|
+
**Recommendation:** {recommendation}
|
|
190
|
+
|
|
191
|
+
Consider adding specific file paths, function names, expected behaviors, or error details before creating this task."""
|
|
192
|
+
|
|
193
|
+
# Output hook response with additionalContext
|
|
194
|
+
out = {
|
|
195
|
+
"hookSpecificOutput": {
|
|
196
|
+
"hookEventName": "PreToolUse",
|
|
197
|
+
"additionalContext": context_msg
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
print(json.dumps(out, ensure_ascii=False))
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
run_hook(main)
|
|
@@ -23,83 +23,28 @@ Hook output:
|
|
|
23
23
|
- Silent on success (no stdout output)
|
|
24
24
|
- Logs to stderr for debugging
|
|
25
25
|
"""
|
|
26
|
-
import json
|
|
27
26
|
import sys
|
|
28
27
|
from pathlib import Path
|
|
29
|
-
from typing import Optional, Dict, Any
|
|
30
28
|
|
|
31
29
|
# Add parent directories to path for imports
|
|
32
30
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
33
31
|
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
34
32
|
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
35
33
|
|
|
36
|
-
from lib.
|
|
37
|
-
|
|
34
|
+
from lib.base.hook_utils import (
|
|
35
|
+
load_hook_input,
|
|
36
|
+
validate_hook_event,
|
|
37
|
+
get_tool_input,
|
|
38
|
+
check_skip_persistence,
|
|
39
|
+
safe_hook_main,
|
|
40
|
+
run_hook,
|
|
41
|
+
)
|
|
38
42
|
from lib.base.utils import eprint, project_dir
|
|
43
|
+
from lib.context.context_extractor import extract_context_id
|
|
44
|
+
from lib.context.task_sync import record_task_created, generate_next_task_id
|
|
39
45
|
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
tool_input: Dict[str, Any],
|
|
43
|
-
project_root: Path,
|
|
44
|
-
session_id: Optional[str] = None
|
|
45
|
-
) -> Optional[str]:
|
|
46
|
-
"""
|
|
47
|
-
Extract context ID from tool input metadata, session, or active contexts.
|
|
48
|
-
|
|
49
|
-
Priority:
|
|
50
|
-
1. metadata.context field
|
|
51
|
-
2. Session ID lookup (session bound to context)
|
|
52
|
-
3. metadata.persistent_id prefix (e.g., "ctx-123-task-1" -> "ctx-123")
|
|
53
|
-
4. Single active context
|
|
54
|
-
5. None (will trigger auto-creation)
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
tool_input: Tool input from TaskCreate
|
|
58
|
-
project_root: Project root directory
|
|
59
|
-
session_id: Session ID from hook payload
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
Context ID or None if cannot determine
|
|
63
|
-
"""
|
|
64
|
-
# Check metadata.context field
|
|
65
|
-
metadata = tool_input.get("metadata", {})
|
|
66
|
-
if isinstance(metadata, dict):
|
|
67
|
-
context = metadata.get("context")
|
|
68
|
-
if context:
|
|
69
|
-
return context
|
|
70
|
-
|
|
71
|
-
# Check session ID - session may be bound to a context
|
|
72
|
-
if session_id:
|
|
73
|
-
try:
|
|
74
|
-
session_context = get_context_by_session_id(session_id, project_root)
|
|
75
|
-
if session_context:
|
|
76
|
-
eprint(f"[task_create_capture] Found context via session_id: {session_context.id}")
|
|
77
|
-
return session_context.id
|
|
78
|
-
except Exception as e:
|
|
79
|
-
eprint(f"[task_create_capture] Failed to lookup context by session: {e}")
|
|
80
|
-
|
|
81
|
-
# Check persistent_id for context hint
|
|
82
|
-
if isinstance(metadata, dict):
|
|
83
|
-
persistent_id = metadata.get("persistent_id", "")
|
|
84
|
-
if persistent_id and "-" in persistent_id:
|
|
85
|
-
# Format: "context-id-task-1" or similar
|
|
86
|
-
parts = persistent_id.split("-")
|
|
87
|
-
if len(parts) >= 2:
|
|
88
|
-
# Reconstruct context ID (everything before last two parts)
|
|
89
|
-
context_parts = parts[:-2] if len(parts) > 2 else parts[:1]
|
|
90
|
-
return "-".join(context_parts)
|
|
91
|
-
|
|
92
|
-
# Check for single active context
|
|
93
|
-
try:
|
|
94
|
-
contexts = get_all_contexts(status="active", project_root=project_root)
|
|
95
|
-
if len(contexts) == 1:
|
|
96
|
-
return contexts[0].id
|
|
97
|
-
except Exception as e:
|
|
98
|
-
eprint(f"[task_create_capture] Failed to get active contexts: {e}")
|
|
99
|
-
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
|
|
47
|
+
@safe_hook_main("task_create_capture")
|
|
103
48
|
def main() -> int:
|
|
104
49
|
"""
|
|
105
50
|
Main hook entry point.
|
|
@@ -107,88 +52,80 @@ def main() -> int:
|
|
|
107
52
|
Returns:
|
|
108
53
|
0 on success, non-zero on failure (but hook is non-blocking)
|
|
109
54
|
"""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# Extract task data
|
|
152
|
-
subject = tool_input.get("subject", "")
|
|
153
|
-
if not subject:
|
|
154
|
-
eprint("[task_create_capture] Missing required field: subject")
|
|
155
|
-
return 0
|
|
156
|
-
|
|
157
|
-
description = tool_input.get("description", "")
|
|
158
|
-
active_form = tool_input.get("activeForm", "")
|
|
159
|
-
|
|
160
|
-
# Generate persistent task ID
|
|
161
|
-
# Claude's native ID is ephemeral (1, 2, 3...)
|
|
162
|
-
# We need a persistent ID that survives sessions
|
|
163
|
-
persistent_task_id = generate_next_task_id(context_id, project_root)
|
|
164
|
-
|
|
165
|
-
# Record the task creation event
|
|
166
|
-
success = record_task_created(
|
|
167
|
-
context_id=context_id,
|
|
168
|
-
task_id=persistent_task_id,
|
|
169
|
-
subject=subject,
|
|
170
|
-
description=description,
|
|
171
|
-
active_form=active_form,
|
|
172
|
-
project_root=project_root
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if success:
|
|
176
|
-
eprint(f"[task_create_capture] Recorded task_added: {persistent_task_id} in {context_id}")
|
|
177
|
-
else:
|
|
178
|
-
eprint(f"[task_create_capture] Failed to record task_added: {persistent_task_id}")
|
|
179
|
-
|
|
180
|
-
# Silent success (no stdout output)
|
|
55
|
+
# Parse hook input
|
|
56
|
+
payload = load_hook_input()
|
|
57
|
+
if not payload:
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
# Validate hook type and tool name
|
|
61
|
+
if not validate_hook_event(payload, "PostToolUse", "TaskCreate"):
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
# Extract tool input
|
|
65
|
+
tool_input = get_tool_input(payload)
|
|
66
|
+
if not tool_input:
|
|
67
|
+
eprint("[task_create_capture] Invalid tool_input: not a dict")
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
# Check for skip_persistence flag
|
|
71
|
+
if check_skip_persistence(payload, "task_create_capture"):
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
# Extract tool response (contains task ID assigned by Claude)
|
|
75
|
+
tool_response = payload.get("tool_response", {})
|
|
76
|
+
if not isinstance(tool_response, dict):
|
|
77
|
+
eprint("[task_create_capture] Invalid tool_response: not a dict")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
# Get project root and session ID
|
|
81
|
+
project_root = project_dir(payload)
|
|
82
|
+
session_id = payload.get("session_id")
|
|
83
|
+
|
|
84
|
+
# Extract context ID using unified extractor
|
|
85
|
+
context_id = extract_context_id(
|
|
86
|
+
tool_input,
|
|
87
|
+
project_root,
|
|
88
|
+
session_id=session_id,
|
|
89
|
+
hook_name="task_create_capture",
|
|
90
|
+
check_persistent_id=True # TaskCreate uses persistent_id for context hints
|
|
91
|
+
)
|
|
92
|
+
if not context_id:
|
|
93
|
+
eprint("[task_create_capture] No context available - skipping persistence")
|
|
94
|
+
eprint("[task_create_capture] Task will be ephemeral until context is created")
|
|
181
95
|
return 0
|
|
182
96
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
97
|
+
# Extract task data
|
|
98
|
+
subject = tool_input.get("subject", "")
|
|
99
|
+
if not subject:
|
|
100
|
+
eprint("[task_create_capture] Missing required field: subject")
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
description = tool_input.get("description", "")
|
|
104
|
+
active_form = tool_input.get("activeForm", "")
|
|
105
|
+
|
|
106
|
+
# Generate persistent task ID
|
|
107
|
+
# Claude's native ID is ephemeral (1, 2, 3...)
|
|
108
|
+
# We need a persistent ID that survives sessions
|
|
109
|
+
persistent_task_id = generate_next_task_id(context_id, project_root)
|
|
110
|
+
|
|
111
|
+
# Record the task creation event
|
|
112
|
+
success = record_task_created(
|
|
113
|
+
context_id=context_id,
|
|
114
|
+
task_id=persistent_task_id,
|
|
115
|
+
subject=subject,
|
|
116
|
+
description=description,
|
|
117
|
+
active_form=active_form,
|
|
118
|
+
project_root=project_root
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if success:
|
|
122
|
+
eprint(f"[task_create_capture] Recorded task_added: {persistent_task_id} in {context_id}")
|
|
123
|
+
else:
|
|
124
|
+
eprint(f"[task_create_capture] Failed to record task_added: {persistent_task_id}")
|
|
125
|
+
|
|
126
|
+
# Silent success (no stdout output)
|
|
127
|
+
return 0
|
|
191
128
|
|
|
192
129
|
|
|
193
130
|
if __name__ == "__main__":
|
|
194
|
-
|
|
131
|
+
run_hook(main)
|