aiag-cli 2.2.2 → 2.3.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/README.md +72 -37
- package/dist/cli.js +30 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto.js +45 -41
- package/dist/commands/auto.js.map +1 -1
- package/dist/commands/feature.d.ts +11 -0
- package/dist/commands/feature.d.ts.map +1 -0
- package/dist/commands/feature.js +153 -0
- package/dist/commands/feature.js.map +1 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +29 -78
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/prd.d.ts +12 -0
- package/dist/commands/prd.d.ts.map +1 -0
- package/dist/commands/prd.js +179 -0
- package/dist/commands/prd.js.map +1 -0
- package/dist/prompts/coding.d.ts.map +1 -1
- package/dist/prompts/coding.js +12 -0
- package/dist/prompts/coding.js.map +1 -1
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +2 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/initializer.d.ts.map +1 -1
- package/dist/prompts/initializer.js +6 -0
- package/dist/prompts/initializer.js.map +1 -1
- package/dist/prompts/prd.d.ts +28 -0
- package/dist/prompts/prd.d.ts.map +1 -0
- package/dist/prompts/prd.js +105 -0
- package/dist/prompts/prd.js.map +1 -0
- package/dist/skills/index.d.ts +12 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +12 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/installer.d.ts +38 -0
- package/dist/skills/installer.d.ts.map +1 -0
- package/dist/skills/installer.js +153 -0
- package/dist/skills/installer.js.map +1 -0
- package/dist/skills/loader.d.ts +34 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +134 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/runner.d.ts +14 -0
- package/dist/skills/runner.d.ts.map +1 -0
- package/dist/skills/runner.js +238 -0
- package/dist/skills/runner.js.map +1 -0
- package/dist/types.d.ts +127 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/prd.d.ts +21 -0
- package/dist/utils/prd.d.ts.map +1 -1
- package/dist/utils/prd.js +69 -0
- package/dist/utils/prd.js.map +1 -1
- package/dist/utils/taskmasterConverter.d.ts +72 -0
- package/dist/utils/taskmasterConverter.d.ts.map +1 -0
- package/dist/utils/taskmasterConverter.js +401 -0
- package/dist/utils/taskmasterConverter.js.map +1 -0
- package/dist/utils/taskmasterParser.d.ts +35 -0
- package/dist/utils/taskmasterParser.d.ts.map +1 -0
- package/dist/utils/taskmasterParser.js +259 -0
- package/dist/utils/taskmasterParser.js.map +1 -0
- package/package.json +1 -1
- package/templates/skills/prd-taskmaster/.taskmaster/docs/prd.md +2571 -0
- package/templates/skills/prd-taskmaster/.taskmaster/scripts/execution-state.py +87 -0
- package/templates/skills/prd-taskmaster/.taskmaster/scripts/learn-accuracy.py +113 -0
- package/templates/skills/prd-taskmaster/.taskmaster/scripts/rollback.sh +71 -0
- package/templates/skills/prd-taskmaster/.taskmaster/scripts/security-audit.py +130 -0
- package/templates/skills/prd-taskmaster/.taskmaster/scripts/track-time.py +133 -0
- package/templates/skills/prd-taskmaster/LICENSE +21 -0
- package/templates/skills/prd-taskmaster/README.md +608 -0
- package/templates/skills/prd-taskmaster/SKILL.md +1258 -0
- package/templates/skills/prd-taskmaster/reference/taskmaster-integration-guide.md +645 -0
- package/templates/skills/prd-taskmaster/reference/validation-checklist.md +394 -0
- package/templates/skills/prd-taskmaster/scripts/setup-taskmaster.sh +112 -0
- package/templates/skills/prd-taskmaster/templates/CLAUDE.md.template +635 -0
- package/templates/skills/prd-taskmaster/templates/taskmaster-prd-comprehensive.md +983 -0
- package/templates/skills/prd-taskmaster/templates/taskmaster-prd-minimal.md +103 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Task execution state manager for crash recovery.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
class ExecutionState:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.state_file = Path(".taskmaster/state/execution-state.json")
|
|
13
|
+
self.state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
self.load_state()
|
|
15
|
+
|
|
16
|
+
def load_state(self):
|
|
17
|
+
"""Load current execution state."""
|
|
18
|
+
if self.state_file.exists():
|
|
19
|
+
with open(self.state_file, 'r') as f:
|
|
20
|
+
self.state = json.load(f)
|
|
21
|
+
else:
|
|
22
|
+
self.state = {
|
|
23
|
+
"mode": None,
|
|
24
|
+
"current_task": None,
|
|
25
|
+
"current_subtask": None,
|
|
26
|
+
"tasks_completed": [],
|
|
27
|
+
"last_update": None
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def save_state(self):
|
|
31
|
+
"""Persist current state to disk."""
|
|
32
|
+
self.state["last_update"] = datetime.now(timezone.utc).isoformat()
|
|
33
|
+
with open(self.state_file, 'w') as f:
|
|
34
|
+
json.dump(self.state, f, indent=2)
|
|
35
|
+
|
|
36
|
+
def set_mode(self, mode):
|
|
37
|
+
"""Set execution mode (sequential, parallel, etc.)."""
|
|
38
|
+
self.state["mode"] = mode
|
|
39
|
+
self.save_state()
|
|
40
|
+
|
|
41
|
+
def start_task(self, task_id):
|
|
42
|
+
"""Mark task as started."""
|
|
43
|
+
self.state["current_task"] = task_id
|
|
44
|
+
self.state["current_subtask"] = None
|
|
45
|
+
self.save_state()
|
|
46
|
+
|
|
47
|
+
def start_subtask(self, task_id, subtask_id):
|
|
48
|
+
"""Mark subtask as started."""
|
|
49
|
+
self.state["current_task"] = task_id
|
|
50
|
+
self.state["current_subtask"] = subtask_id
|
|
51
|
+
self.save_state()
|
|
52
|
+
|
|
53
|
+
def complete_task(self, task_id):
|
|
54
|
+
"""Mark task as completed."""
|
|
55
|
+
if task_id not in self.state["tasks_completed"]:
|
|
56
|
+
self.state["tasks_completed"].append(task_id)
|
|
57
|
+
self.state["current_task"] = None
|
|
58
|
+
self.state["current_subtask"] = None
|
|
59
|
+
self.save_state()
|
|
60
|
+
|
|
61
|
+
def has_incomplete_work(self):
|
|
62
|
+
"""Check if there's incomplete work from previous session."""
|
|
63
|
+
return self.state["current_task"] is not None
|
|
64
|
+
|
|
65
|
+
def get_resume_point(self):
|
|
66
|
+
"""Get information about where to resume."""
|
|
67
|
+
if not self.has_incomplete_work():
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"mode": self.state["mode"],
|
|
72
|
+
"task": self.state["current_task"],
|
|
73
|
+
"subtask": self.state["current_subtask"],
|
|
74
|
+
"last_update": self.state["last_update"],
|
|
75
|
+
"completed_tasks": self.state["tasks_completed"]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
state = ExecutionState()
|
|
80
|
+
if state.has_incomplete_work():
|
|
81
|
+
resume = state.get_resume_point()
|
|
82
|
+
print(f"Incomplete work detected:")
|
|
83
|
+
print(f" Task: {resume['task']}")
|
|
84
|
+
print(f" Subtask: {resume['subtask']}")
|
|
85
|
+
print(f" Last update: {resume['last_update']}")
|
|
86
|
+
else:
|
|
87
|
+
print("No incomplete work found.")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Estimation accuracy learning system.
|
|
4
|
+
Analyzes actual vs estimated times and improves future estimates.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
11
|
+
from track_time import get_all_task_times, calculate_accuracy
|
|
12
|
+
|
|
13
|
+
class AccuracyLearner:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.reports_dir = Path(".taskmaster/reports")
|
|
16
|
+
self.reports_dir.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
self.accuracy_file = self.reports_dir / "estimation-accuracy.json"
|
|
18
|
+
self.load_accuracy_data()
|
|
19
|
+
|
|
20
|
+
def load_accuracy_data(self):
|
|
21
|
+
"""Load historical accuracy data."""
|
|
22
|
+
if self.accuracy_file.exists():
|
|
23
|
+
with open(self.accuracy_file, 'r') as f:
|
|
24
|
+
self.data = json.load(f)
|
|
25
|
+
else:
|
|
26
|
+
self.data = {
|
|
27
|
+
"tasks_analyzed": 0,
|
|
28
|
+
"average_accuracy": 100.0,
|
|
29
|
+
"adjustment_factor": 1.0,
|
|
30
|
+
"history": []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def save_accuracy_data(self):
|
|
34
|
+
"""Save accuracy data."""
|
|
35
|
+
with open(self.accuracy_file, 'w') as f:
|
|
36
|
+
json.dump(self.data, f, indent=2)
|
|
37
|
+
|
|
38
|
+
def analyze_task(self, task_id, estimated_minutes, actual_minutes):
|
|
39
|
+
"""Analyze a single task's accuracy."""
|
|
40
|
+
accuracy = calculate_accuracy(estimated_minutes, actual_minutes)
|
|
41
|
+
|
|
42
|
+
entry = {
|
|
43
|
+
"task_id": task_id,
|
|
44
|
+
"estimated_minutes": estimated_minutes,
|
|
45
|
+
"actual_minutes": actual_minutes,
|
|
46
|
+
"accuracy_percent": accuracy,
|
|
47
|
+
"variance_percent": accuracy - 100
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
self.data["history"].append(entry)
|
|
51
|
+
self.data["tasks_analyzed"] += 1
|
|
52
|
+
|
|
53
|
+
# Recalculate average
|
|
54
|
+
total_accuracy = sum(h["accuracy_percent"] for h in self.data["history"])
|
|
55
|
+
self.data["average_accuracy"] = round(total_accuracy / len(self.data["history"]), 1)
|
|
56
|
+
|
|
57
|
+
# Update adjustment factor
|
|
58
|
+
self.data["adjustment_factor"] = round(self.data["average_accuracy"] / 100, 2)
|
|
59
|
+
|
|
60
|
+
self.save_accuracy_data()
|
|
61
|
+
|
|
62
|
+
return entry
|
|
63
|
+
|
|
64
|
+
def get_adjusted_estimate(self, base_estimate_minutes):
|
|
65
|
+
"""Apply learned adjustment to new estimate."""
|
|
66
|
+
adjusted = base_estimate_minutes * self.data["adjustment_factor"]
|
|
67
|
+
return round(adjusted, 0)
|
|
68
|
+
|
|
69
|
+
def generate_report(self):
|
|
70
|
+
"""Generate human-readable accuracy report."""
|
|
71
|
+
if self.data["tasks_analyzed"] < 3:
|
|
72
|
+
return "❌ Need at least 3 completed tasks for accuracy analysis."
|
|
73
|
+
|
|
74
|
+
history = self.data["history"]
|
|
75
|
+
avg_accuracy = self.data["average_accuracy"]
|
|
76
|
+
adjustment = self.data["adjustment_factor"]
|
|
77
|
+
|
|
78
|
+
# Find outliers
|
|
79
|
+
outliers = [h for h in history if abs(h["variance_percent"]) > 50]
|
|
80
|
+
|
|
81
|
+
report = f"""
|
|
82
|
+
📊 Estimation Accuracy Report
|
|
83
|
+
|
|
84
|
+
Tasks Analyzed: {self.data["tasks_analyzed"]}
|
|
85
|
+
Average Accuracy: {avg_accuracy}%
|
|
86
|
+
Adjustment Factor: {adjustment}x
|
|
87
|
+
|
|
88
|
+
Recent Tasks:
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
for h in history[-10:]: # Last 10 tasks
|
|
92
|
+
status = "✅" if 90 <= h["accuracy_percent"] <= 110 else "⚠️"
|
|
93
|
+
report += f" Task {h['task_id']}: {h['actual_minutes']}min (est: {h['estimated_minutes']}min) - {h['accuracy_percent']}% {status}\n"
|
|
94
|
+
|
|
95
|
+
if avg_accuracy < 90:
|
|
96
|
+
report += f"\n⚠️ You're completing tasks {100 - avg_accuracy:.0f}% faster than estimated.\n"
|
|
97
|
+
report += f"🎯 Recommendation: Reduce future estimates by {(1 - adjustment) * 100:.0f}%\n"
|
|
98
|
+
elif avg_accuracy > 110:
|
|
99
|
+
report += f"\n⚠️ You're taking {avg_accuracy - 100:.0f}% longer than estimated.\n"
|
|
100
|
+
report += f"🎯 Recommendation: Increase future estimates by {(adjustment - 1) * 100:.0f}%\n"
|
|
101
|
+
else:
|
|
102
|
+
report += f"\n✅ Your estimates are accurate! Keep it up.\n"
|
|
103
|
+
|
|
104
|
+
if outliers:
|
|
105
|
+
report += f"\n⚠️ {len(outliers)} outlier tasks found (>50% variance):\n"
|
|
106
|
+
for o in outliers:
|
|
107
|
+
report += f" Task {o['task_id']}: {o['variance_percent']:+.0f}% variance\n"
|
|
108
|
+
|
|
109
|
+
return report
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
learner = AccuracyLearner()
|
|
113
|
+
print(learner.generate_report())
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# .taskmaster/scripts/rollback.sh
|
|
3
|
+
# Rollback to any task checkpoint
|
|
4
|
+
|
|
5
|
+
TASK_ID=$1
|
|
6
|
+
|
|
7
|
+
if [ -z "$TASK_ID" ]; then
|
|
8
|
+
echo "❌ Error: Task ID required"
|
|
9
|
+
echo "Usage: rollback.sh <task_id>"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
TAG="checkpoint-task-${TASK_ID}"
|
|
14
|
+
|
|
15
|
+
# Check if tag exists
|
|
16
|
+
if ! git tag -l | grep -q "^${TAG}$"; then
|
|
17
|
+
echo "❌ Error: Checkpoint tag '${TAG}' not found"
|
|
18
|
+
echo "Available checkpoints:"
|
|
19
|
+
git tag -l "checkpoint-task-*" | sort -V
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "🔄 Rolling back to ${TAG}..."
|
|
24
|
+
|
|
25
|
+
# Safety check
|
|
26
|
+
echo "⚠️ This will:"
|
|
27
|
+
echo " - Discard all changes after Task ${TASK_ID}"
|
|
28
|
+
echo " - Reset to checkpoint-task-${TASK_ID}"
|
|
29
|
+
echo " - Preserve current work in rollback-backup branch"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "Continue? (yes/no)"
|
|
32
|
+
read -r CONFIRM
|
|
33
|
+
|
|
34
|
+
if [ "$CONFIRM" != "yes" ]; then
|
|
35
|
+
echo "❌ Rollback cancelled"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Create backup branch of current state
|
|
40
|
+
BACKUP_BRANCH="rollback-backup-$(date +%Y%m%d-%H%M%S)"
|
|
41
|
+
git checkout -b "$BACKUP_BRANCH"
|
|
42
|
+
git checkout main
|
|
43
|
+
|
|
44
|
+
echo "💾 Backed up current state to: ${BACKUP_BRANCH}"
|
|
45
|
+
|
|
46
|
+
# Reset to checkpoint
|
|
47
|
+
git reset --hard "${TAG}"
|
|
48
|
+
|
|
49
|
+
echo "✅ Rolled back to Task ${TASK_ID} completion state"
|
|
50
|
+
echo "📝 Updating progress.md..."
|
|
51
|
+
|
|
52
|
+
# Log rollback
|
|
53
|
+
cat >> .taskmaster/docs/progress.md <<EOF
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
## ROLLBACK PERFORMED
|
|
57
|
+
**Timestamp**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
58
|
+
**Rolled back to**: Task ${TASK_ID} (${TAG})
|
|
59
|
+
**Backup branch**: ${BACKUP_BRANCH}
|
|
60
|
+
**Reason**: User-initiated rollback
|
|
61
|
+
|
|
62
|
+
Tasks after ${TASK_ID} discarded. Ready to resume from Task ${TASK_ID}.
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
EOF
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
echo "🎯 Next steps:"
|
|
69
|
+
echo " 1. Resume from Task $((TASK_ID + 1))"
|
|
70
|
+
echo " 2. Redo Task ${TASK_ID} differently"
|
|
71
|
+
echo " 3. Review backup: git checkout ${BACKUP_BRANCH}"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Auto-generate security audit checklist based on implemented features.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
def detect_security_concerns(codebase_path="."):
|
|
10
|
+
"""Scan code for security-relevant features."""
|
|
11
|
+
concerns = []
|
|
12
|
+
|
|
13
|
+
# Scan for authentication-related code
|
|
14
|
+
for file_path in Path(codebase_path).rglob("*.{js,ts,py,go,java}"):
|
|
15
|
+
try:
|
|
16
|
+
content = file_path.read_text()
|
|
17
|
+
|
|
18
|
+
# Check for password handling
|
|
19
|
+
if re.search(r'password|passwd|pwd', content, re.I):
|
|
20
|
+
concerns.append({
|
|
21
|
+
"category": "Authentication",
|
|
22
|
+
"item": "Password hashing",
|
|
23
|
+
"check": "Passwords hashed with bcrypt/argon2 (cost ≥ 10)",
|
|
24
|
+
"file": str(file_path)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
# Check for OAuth
|
|
28
|
+
if re.search(r'oauth|openid', content, re.I):
|
|
29
|
+
concerns.append({
|
|
30
|
+
"category": "OAuth",
|
|
31
|
+
"item": "OAuth token security",
|
|
32
|
+
"check": "OAuth tokens encrypted at rest and in transit",
|
|
33
|
+
"file": str(file_path)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
# Check for database queries
|
|
37
|
+
if re.search(r'SELECT.*FROM|INSERT INTO|UPDATE.*SET', content, re.I):
|
|
38
|
+
concerns.append({
|
|
39
|
+
"category": "Database",
|
|
40
|
+
"item": "SQL injection prevention",
|
|
41
|
+
"check": "All queries use parameterized statements (no string concatenation)",
|
|
42
|
+
"file": str(file_path)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
# Check for user input handling
|
|
46
|
+
if re.search(r'req\.body|request\.form|input\(', content):
|
|
47
|
+
concerns.append({
|
|
48
|
+
"category": "Input Validation",
|
|
49
|
+
"item": "XSS prevention",
|
|
50
|
+
"check": "All user input sanitized before rendering",
|
|
51
|
+
"file": str(file_path)
|
|
52
|
+
})
|
|
53
|
+
except:
|
|
54
|
+
pass # Skip files that can't be read
|
|
55
|
+
|
|
56
|
+
# Deduplicate
|
|
57
|
+
unique_concerns = []
|
|
58
|
+
seen = set()
|
|
59
|
+
for c in concerns:
|
|
60
|
+
key = (c["category"], c["item"])
|
|
61
|
+
if key not in seen:
|
|
62
|
+
unique_concerns.append(c)
|
|
63
|
+
seen.add(key)
|
|
64
|
+
|
|
65
|
+
return unique_concerns
|
|
66
|
+
|
|
67
|
+
def generate_security_checklist():
|
|
68
|
+
"""Generate comprehensive security audit checklist."""
|
|
69
|
+
concerns = detect_security_concerns()
|
|
70
|
+
|
|
71
|
+
checklist = """
|
|
72
|
+
🔒 SECURITY AUDIT CHECKLIST
|
|
73
|
+
|
|
74
|
+
Auto-generated based on your implementation.
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Group by category
|
|
79
|
+
by_category = {}
|
|
80
|
+
for c in concerns:
|
|
81
|
+
cat = c["category"]
|
|
82
|
+
if cat not in by_category:
|
|
83
|
+
by_category[cat] = []
|
|
84
|
+
by_category[cat].append(c)
|
|
85
|
+
|
|
86
|
+
for category, items in sorted(by_category.items()):
|
|
87
|
+
checklist += f"\n### {category}\n"
|
|
88
|
+
for item in items:
|
|
89
|
+
checklist += f"- [ ] {item['check']}\n"
|
|
90
|
+
checklist += f" File: {item['file']}\n"
|
|
91
|
+
|
|
92
|
+
# Add standard checks
|
|
93
|
+
checklist += """
|
|
94
|
+
|
|
95
|
+
### General Security
|
|
96
|
+
- [ ] HTTPS enforced in production
|
|
97
|
+
- [ ] CSRF protection enabled
|
|
98
|
+
- [ ] Rate limiting on sensitive endpoints
|
|
99
|
+
- [ ] Security headers set (CSP, X-Frame-Options, etc.)
|
|
100
|
+
- [ ] Dependencies audited (npm audit / pip-audit)
|
|
101
|
+
- [ ] Secrets not committed to git (.env in .gitignore)
|
|
102
|
+
- [ ] Error messages don't leak sensitive info
|
|
103
|
+
- [ ] Logging doesn't include passwords/tokens
|
|
104
|
+
|
|
105
|
+
### Compliance (if applicable)
|
|
106
|
+
- [ ] GDPR: User data deletion implemented
|
|
107
|
+
- [ ] SOC2: Audit logs for all auth events
|
|
108
|
+
- [ ] PCI-DSS: No credit card data stored
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
checklist += """
|
|
113
|
+
🧪 Automated Security Scans Available:
|
|
114
|
+
|
|
115
|
+
1. npm audit (Node.js dependencies)
|
|
116
|
+
2. pip-audit (Python dependencies)
|
|
117
|
+
3. OWASP ZAP (web application scan)
|
|
118
|
+
4. Snyk (vulnerability scanning)
|
|
119
|
+
|
|
120
|
+
Run automated scans?
|
|
121
|
+
1. Yes, run npm audit + recommended scans
|
|
122
|
+
2. Manual review only
|
|
123
|
+
3. Skip for now
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
return checklist
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
print(generate_security_checklist())
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Task time tracking with real datetime stamps.
|
|
4
|
+
Auto-generated by PRD-Taskmaster skill.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
class TaskTimer:
|
|
13
|
+
"""Track task execution time with precise datetime stamps."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, task_id, subtask_id=None):
|
|
16
|
+
self.task_id = task_id
|
|
17
|
+
self.subtask_id = subtask_id
|
|
18
|
+
self.state_dir = Path(".taskmaster/state")
|
|
19
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
if subtask_id:
|
|
22
|
+
self.state_file = self.state_dir / f"task-{task_id}-subtask-{subtask_id}.json"
|
|
23
|
+
else:
|
|
24
|
+
self.state_file = self.state_dir / f"task-{task_id}.json"
|
|
25
|
+
|
|
26
|
+
def start(self):
|
|
27
|
+
"""Mark task/subtask as started with current UTC timestamp."""
|
|
28
|
+
data = {
|
|
29
|
+
"task_id": self.task_id,
|
|
30
|
+
"subtask_id": self.subtask_id,
|
|
31
|
+
"start_time": datetime.now(timezone.utc).isoformat(),
|
|
32
|
+
"status": "in_progress"
|
|
33
|
+
}
|
|
34
|
+
with open(self.state_file, 'w') as f:
|
|
35
|
+
json.dump(data, f, indent=2)
|
|
36
|
+
|
|
37
|
+
start_time = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
38
|
+
print(f"📅 Started: {start_time}")
|
|
39
|
+
|
|
40
|
+
def complete(self, notes=""):
|
|
41
|
+
"""Mark task/subtask as complete and calculate actual duration."""
|
|
42
|
+
with open(self.state_file, 'r') as f:
|
|
43
|
+
data = json.load(f)
|
|
44
|
+
|
|
45
|
+
start = datetime.fromisoformat(data['start_time'])
|
|
46
|
+
end = datetime.now(timezone.utc)
|
|
47
|
+
|
|
48
|
+
duration_seconds = (end - start).total_seconds()
|
|
49
|
+
duration_minutes = duration_seconds / 60
|
|
50
|
+
duration_hours = duration_minutes / 60
|
|
51
|
+
|
|
52
|
+
data.update({
|
|
53
|
+
"end_time": end.isoformat(),
|
|
54
|
+
"status": "completed",
|
|
55
|
+
"actual_duration_seconds": int(duration_seconds),
|
|
56
|
+
"actual_duration_minutes": round(duration_minutes, 2),
|
|
57
|
+
"actual_duration_hours": round(duration_hours, 2),
|
|
58
|
+
"notes": notes
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
with open(self.state_file, 'w') as f:
|
|
62
|
+
json.dump(data, f, indent=2)
|
|
63
|
+
|
|
64
|
+
# Format output
|
|
65
|
+
if duration_hours >= 1:
|
|
66
|
+
duration_str = f"{int(duration_hours)}h {int(duration_minutes % 60)}min"
|
|
67
|
+
else:
|
|
68
|
+
duration_str = f"{int(duration_minutes)} min"
|
|
69
|
+
|
|
70
|
+
end_time = end.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
71
|
+
print(f"✅ COMPLETED")
|
|
72
|
+
print(f"📅 Ended: {end_time}")
|
|
73
|
+
print(f"⏱️ Actual: {duration_str} ({duration_minutes:.1f} min)")
|
|
74
|
+
|
|
75
|
+
return duration_minutes
|
|
76
|
+
|
|
77
|
+
def get_duration(self):
|
|
78
|
+
"""Get current duration for in-progress task."""
|
|
79
|
+
if not self.state_file.exists():
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
with open(self.state_file, 'r') as f:
|
|
83
|
+
data = json.load(f)
|
|
84
|
+
|
|
85
|
+
if data['status'] == 'completed':
|
|
86
|
+
return data['actual_duration_minutes']
|
|
87
|
+
|
|
88
|
+
start = datetime.fromisoformat(data['start_time'])
|
|
89
|
+
now = datetime.now(timezone.utc)
|
|
90
|
+
duration_minutes = (now - start).total_seconds() / 60
|
|
91
|
+
return round(duration_minutes, 2)
|
|
92
|
+
|
|
93
|
+
def get_all_task_times():
|
|
94
|
+
"""Get timing data for all tasks."""
|
|
95
|
+
state_dir = Path(".taskmaster/state")
|
|
96
|
+
if not state_dir.exists():
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
times = []
|
|
100
|
+
for state_file in state_dir.glob("task-*.json"):
|
|
101
|
+
with open(state_file, 'r') as f:
|
|
102
|
+
data = json.load(f)
|
|
103
|
+
if data['status'] == 'completed':
|
|
104
|
+
times.append(data)
|
|
105
|
+
|
|
106
|
+
return times
|
|
107
|
+
|
|
108
|
+
def calculate_accuracy(estimated_minutes, actual_minutes):
|
|
109
|
+
"""Calculate estimation accuracy percentage."""
|
|
110
|
+
if estimated_minutes == 0:
|
|
111
|
+
return 0
|
|
112
|
+
accuracy = (actual_minutes / estimated_minutes) * 100
|
|
113
|
+
return round(accuracy, 1)
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
# CLI usage
|
|
117
|
+
import sys
|
|
118
|
+
|
|
119
|
+
if len(sys.argv) < 3:
|
|
120
|
+
print("Usage: python track-time.py <command> <task_id> [subtask_id]")
|
|
121
|
+
print("Commands: start, complete")
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
command = sys.argv[1]
|
|
125
|
+
task_id = sys.argv[2]
|
|
126
|
+
subtask_id = sys.argv[3] if len(sys.argv) > 3 else None
|
|
127
|
+
|
|
128
|
+
timer = TaskTimer(task_id, subtask_id)
|
|
129
|
+
|
|
130
|
+
if command == "start":
|
|
131
|
+
timer.start()
|
|
132
|
+
elif command == "complete":
|
|
133
|
+
timer.complete()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 anombyte93
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|