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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 87 additions & 2 deletions scripts/ai_tools/finish_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re
import shutil
import sys
from pathlib import Path

from scripts.ai_tools.utils import (
format_timestamp,
Expand Down Expand Up @@ -69,6 +70,28 @@ def extract_decisions(execution_content: str) -> list[str]:
return decisions


def extract_task_name_from_filename(plan_file_path: Path) -> str:
"""Extract task name from PLAN filename as fallback.

Args:
plan_file_path: Path to PLAN file

Returns:
Task name extracted from filename slug
"""
# Filename format: YYYYMMDDHHMMSS-PLAN-slug.md
filename = plan_file_path.name
parts = filename.replace(".md", "").split("-", 2)

if len(parts) >= 3:
# Extract slug (third part), convert hyphens to spaces, title case
slug = parts[2]
task_name = slug.replace("-", " ").title()
return task_name

return "Untitled Task"


def check_plan_completion(plan_content: str) -> tuple[bool, int, int]:
"""Check if plan is complete.

Expand Down Expand Up @@ -106,6 +129,47 @@ def check_make_check_ran(execution_content: str) -> bool:
return bool(re.search(r"make\s+check", execution_content, re.IGNORECASE))


def verify_plan_execution(plan_content: str, execution_content: str) -> list[str]:
"""Verify that checked plan items are mentioned in execution log.

Args:
plan_content: Content of PLAN file
execution_content: Content of EXECUTION file

Returns:
List of checked plan items that are not mentioned in execution
"""
lines = plan_content.split("\n")
unverified_items = []

for line in lines:
# Check for checked checkboxes
match = re.match(r"^\s*-\s*\[x\]\s*(.+)$", line, re.IGNORECASE)
if match:
item_text = match.group(1).strip()

# Check if item text (or key parts of it) appear in execution log
# Extract key terms (words longer than 3 chars) from item
key_terms = [
word for word in re.findall(r"\b\w+\b", item_text) if len(word) > 3
]

# If at least 2 key terms are mentioned, consider it verified
mentioned_count = sum(
1
for term in key_terms
if re.search(
r"\b" + re.escape(term) + r"\b", execution_content, re.IGNORECASE
)
)

# If less than half of key terms mentioned, mark as unverified
if key_terms and mentioned_count < len(key_terms) / 2:
unverified_items.append(item_text)

return unverified_items


def update_last_session_summary(
session_id: str,
task_name: str,
Expand Down Expand Up @@ -254,9 +318,14 @@ def finish_task(
summary_file.read_text()
execution_content = execution_file.read_text()

# Extract task name from file
# Extract task name from file header
task_name_match = re.search(r"# Task (?:Plan|Summary): (.+)", plan_content)
task_name = task_name_match.group(1) if task_name_match else "Unknown Task"

if task_name_match:
task_name = task_name_match.group(1)
else:
# Fallback: extract from filename instead of using "Unknown Task"
task_name = extract_task_name_from_filename(plan_file)

print_header(f"✅ Finishing Task: {task_name}")

Expand All @@ -279,6 +348,22 @@ def finish_task(
print("Task not finished. Run 'make check' first.")
sys.exit(0)

# Verify plan execution
unverified_items = verify_plan_execution(plan_content, execution_content)
if unverified_items:
print_warning(
f"Some checked plan items may not have been executed ({len(unverified_items)} items)"
)
print("\n⚠️ Plan items not clearly mentioned in execution log:")
for item in unverified_items[:5]: # Show first 5
print(f" • {item}")
if len(unverified_items) > 5:
print(f" ... and {len(unverified_items) - 5} more")
print(
"\nTip: Use 'ai-log' with specific details (file names, test names) "
"for better verification.\n"
)

# Get summary if not provided
if summary is None:
print("\n📝 Enter task summary:")
Expand Down
74 changes: 72 additions & 2 deletions scripts/ai_tools/log_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,75 @@ def get_emoji_for_level(level: str) -> str:
return emoji_map.get(level, "📝")


def check_log_specificity(message: str) -> list[str]:
"""Check if log message includes specific details.

Args:
message: Log message to check

Returns:
List of suggestions for improving specificity
"""
suggestions = []

# Check for vague phrases
vague_phrases = [
(
"write test",
"Which test file? Example: 'Wrote tests in tests/test_feature.py'",
),
(
"update file",
"Which file? Example: 'Updated src/module.py with new function'",
),
(
"add feature",
"Which feature file? Example: 'Added validation to src/auth.py'",
),
(
"fix bug",
"Which file/function? Example: 'Fixed validation bug in src/auth.py:validate_email()'",
),
(
"implement",
"Which file/module? Be specific about location and what was implemented",
),
]

message_lower = message.lower()
for vague_phrase, suggestion in vague_phrases:
if vague_phrase in message_lower and not any(
ext in message_lower for ext in [".py", ".md", ".txt", ".yml", ".toml"]
):
suggestions.append(suggestion)
break # Only show one suggestion

return suggestions


def log_execution(
message: str,
level: str = "info",
session_id: str | None = None,
) -> None:
"""Log execution progress to EXECUTION file.
"""Log execution progress to EXECUTION file with specific details.

IMPORTANT: Include specific file names, test names, or function names in your log messages.
This helps with execution verification and provides clear audit trail.

Good examples:
- "Wrote tests in tests/ai_tools/test_feature.py (3 test cases)"
- "Implemented validate_email() in src/auth.py"
- "Updated User model in src/models/user.py to add email field"
- "Fixed bug in calculate_total() at src/billing.py:45"

Bad examples (too vague):
- "Wrote some tests"
- "Updated a file"
- "Fixed a bug"

Args:
message: Log message
message: Log message with specific details (file names, function names, etc.)
level: Log level (info, warning, error, success)
session_id: Session ID (default: most recent)
"""
Expand All @@ -62,6 +122,9 @@ def log_execution(
print_error(f"Execution file not found for session {session_id}")
sys.exit(1)

# Check log specificity
suggestions = check_log_specificity(message)

# Format log entry
timestamp = format_timestamp()
emoji = get_emoji_for_level(level)
Expand All @@ -74,6 +137,13 @@ def log_execution(
print_success(f"Logged to session {session_id}:")
print(f" {log_entry.strip()}")

# Show suggestions if log is vague
if suggestions:
print("\n💡 Tip: Consider being more specific in your log messages:")
for suggestion in suggestions:
print(f" {suggestion}")
print()


def main() -> None:
"""Main entry point for ai-log command."""
Expand Down
91 changes: 89 additions & 2 deletions template/scripts/ai_tools/finish_task.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import argparse
import re
import shutil
import sys
from pathlib import Path

from scripts.ai_tools.utils import (
format_timestamp,
Expand Down Expand Up @@ -69,6 +70,28 @@ def extract_decisions(execution_content: str) -> list[str]:
return decisions


def extract_task_name_from_filename(plan_file_path: Path) -> str:
"""Extract task name from PLAN filename as fallback.

Args:
plan_file_path: Path to PLAN file

Returns:
Task name extracted from filename slug
"""
# Filename format: YYYYMMDDHHMMSS-PLAN-slug.md
filename = plan_file_path.name
parts = filename.replace(".md", "").split("-", 2)

if len(parts) >= 3:
# Extract slug (third part), convert hyphens to spaces, title case
slug = parts[2]
task_name = slug.replace("-", " ").title()
return task_name

return "Untitled Task"


def check_plan_completion(plan_content: str) -> tuple[bool, int, int]:
"""Check if plan is complete.

Expand Down Expand Up @@ -106,6 +129,49 @@ def check_make_check_ran(execution_content: str) -> bool:
return bool(re.search(r"make\s+check", execution_content, re.IGNORECASE))


def verify_plan_execution(plan_content: str, execution_content: str) -> list[str]:
"""Verify that checked plan items are mentioned in execution log.

Args:
plan_content: Content of PLAN file
execution_content: Content of EXECUTION file

Returns:
List of checked plan items that are not mentioned in execution
"""
lines = plan_content.split("\n")
unverified_items = []

for line in lines:
# Check for checked checkboxes
match = re.match(r"^\s*-\s*\[x\]\s*(.+)$", line, re.IGNORECASE)
if match:
item_text = match.group(1).strip()

# Check if item text (or key parts of it) appear in execution log
# Extract key terms (words longer than 3 chars) from item
key_terms = [
word
for word in re.findall(r"\b\w+\b", item_text)
if len(word) > 3
]

# If at least 2 key terms are mentioned, consider it verified
mentioned_count = sum(
1
for term in key_terms
if re.search(
r"\b" + re.escape(term) + r"\b", execution_content, re.IGNORECASE
)
)

# If less than half of key terms mentioned, mark as unverified
if key_terms and mentioned_count < len(key_terms) / 2:
unverified_items.append(item_text)

return unverified_items


def update_last_session_summary(
session_id: str,
task_name: str,
Expand Down Expand Up @@ -254,9 +320,14 @@ def finish_task(
summary_file.read_text()
execution_content = execution_file.read_text()

# Extract task name from file
# Extract task name from file header
task_name_match = re.search(r"# Task (?:Plan|Summary): (.+)", plan_content)
task_name = task_name_match.group(1) if task_name_match else "Unknown Task"

if task_name_match:
task_name = task_name_match.group(1)
else:
# Fallback: extract from filename instead of using "Unknown Task"
task_name = extract_task_name_from_filename(plan_file)

print_header(f"✅ Finishing Task: {task_name}")

Expand All @@ -279,6 +350,22 @@ def finish_task(
print("Task not finished. Run 'make check' first.")
sys.exit(0)

# Verify plan execution
unverified_items = verify_plan_execution(plan_content, execution_content)
if unverified_items:
print_warning(
f"Some checked plan items may not have been executed ({len(unverified_items)} items)"
)
print("\n⚠️ Plan items not clearly mentioned in execution log:")
for item in unverified_items[:5]: # Show first 5
print(f" • {item}")
if len(unverified_items) > 5:
print(f" ... and {len(unverified_items) - 5} more")
print(
"\nTip: Use 'ai-log' with specific details (file names, test names) "
"for better verification.\n"
)

# Get summary if not provided
if summary is None:
print("\n📝 Enter task summary:")
Expand Down
Loading
Loading