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.
Files changed (90) hide show
  1. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  2. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  3. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  4. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  5. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  6. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  7. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  8. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  9. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  10. package/dist/templates/_shared/hooks/archive_plan.py +28 -38
  11. package/dist/templates/_shared/hooks/context_enforcer.py +71 -21
  12. package/dist/templates/_shared/hooks/context_monitor.py +4 -8
  13. package/dist/templates/_shared/hooks/file-suggestion.py +4 -10
  14. package/dist/templates/_shared/hooks/session_start.py +103 -0
  15. package/dist/templates/_shared/hooks/task_create_atomicity.py +205 -0
  16. package/dist/templates/_shared/hooks/task_create_capture.py +83 -146
  17. package/dist/templates/_shared/hooks/task_update_capture.py +116 -167
  18. package/dist/templates/_shared/hooks/user_prompt_submit.py +62 -22
  19. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  21. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  22. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  23. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  24. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  25. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  26. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  27. package/dist/templates/_shared/lib/base/hook_utils.py +169 -0
  28. package/dist/templates/_shared/lib/base/inference.py +20 -35
  29. package/dist/templates/_shared/lib/base/stop_words.py +158 -0
  30. package/dist/templates/_shared/lib/base/utils.py +3 -2
  31. package/dist/templates/_shared/lib/context/__init__.py +9 -2
  32. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  33. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  34. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  35. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  36. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  37. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  38. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  39. package/dist/templates/_shared/lib/context/context_extractor.py +115 -0
  40. package/dist/templates/_shared/lib/context/context_manager.py +2 -2
  41. package/dist/templates/_shared/lib/context/discovery.py +4 -4
  42. package/dist/templates/_shared/lib/context/task_sync.py +5 -82
  43. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  44. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  45. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  46. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  47. package/dist/templates/_shared/lib/templates/persona_questions.py +113 -0
  48. package/dist/templates/_shared/lib/templates/plan_context.py +13 -27
  49. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +1 -1
  50. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +21 -48
  51. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +26 -204
  52. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +25 -76
  53. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +1 -1
  54. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +32 -77
  55. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +1 -1
  56. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +26 -189
  57. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +30 -52
  58. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +27 -63
  59. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +32 -81
  60. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +25 -106
  61. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +24 -209
  62. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +26 -200
  63. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +1 -1
  64. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +1 -1
  65. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +1 -1
  66. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +36 -206
  67. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +27 -177
  68. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +23 -66
  69. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +26 -162
  70. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +29 -59
  71. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +28 -312
  72. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +23 -74
  73. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +1 -1
  74. package/dist/templates/cc-native/.claude/settings.json +21 -0
  75. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +211 -0
  76. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  77. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  78. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  79. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +65 -12
  80. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +240 -0
  81. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  82. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  83. package/dist/templates/cc-native/_cc-native/lib/debug.py +124 -0
  84. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +1 -0
  85. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  86. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +34 -1
  87. package/dist/templates/cc-native/_cc-native/plan-review.config.json +7 -1
  88. package/oclif.manifest.json +1 -1
  89. package/package.json +1 -1
  90. 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.context.task_sync import record_task_created, generate_next_task_id
37
- from lib.context.context_manager import get_all_contexts, get_context_by_session_id
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
- def extract_context_id(
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
- try:
111
- # Parse hook input
112
- payload = json.load(sys.stdin)
113
-
114
- # Validate hook type
115
- if payload.get("hook_event_name") != "PostToolUse":
116
- return 0
117
-
118
- # Validate tool name
119
- if payload.get("tool_name") != "TaskCreate":
120
- return 0
121
-
122
- # Extract tool input
123
- tool_input = payload.get("tool_input", {})
124
- if not isinstance(tool_input, dict):
125
- eprint("[task_create_capture] Invalid tool_input: not a dict")
126
- return 0
127
-
128
- # Check for skip_persistence flag (used during hydration to avoid duplicates)
129
- metadata = tool_input.get("metadata", {})
130
- if isinstance(metadata, dict) and metadata.get("skip_persistence"):
131
- eprint("[task_create_capture] Skipping persistence (hydration mode)")
132
- return 0
133
-
134
- # Extract tool response (contains task ID assigned by Claude)
135
- tool_response = payload.get("tool_response", {})
136
- if not isinstance(tool_response, dict):
137
- eprint("[task_create_capture] Invalid tool_response: not a dict")
138
- return 0
139
-
140
- # Get project root and session ID
141
- project_root = project_dir(payload)
142
- session_id = payload.get("session_id")
143
-
144
- # Extract context ID
145
- context_id = extract_context_id(tool_input, project_root, session_id)
146
- if not context_id:
147
- eprint("[task_create_capture] No context available - skipping persistence")
148
- eprint("[task_create_capture] Task will be ephemeral until context is created")
149
- return 0
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
- except json.JSONDecodeError as e:
184
- eprint(f"[task_create_capture] JSON decode error: {e}")
185
- return 0 # Non-blocking
186
- except Exception as e:
187
- eprint(f"[task_create_capture] Unexpected error: {e}")
188
- import traceback
189
- eprint(traceback.format_exc())
190
- return 0 # Non-blocking
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
- raise SystemExit(main())
131
+ run_hook(main)