claude-flow-novice 2.14.6 → 2.14.8

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 (66) hide show
  1. package/.claude/commands/cfn/run-tests.md +119 -0
  2. package/.claude/hooks/cfn-post-edit.config.json +11 -4
  3. package/.claude/skills/cfn-agent-selector/SKILL.md +3 -2
  4. package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +1 -1
  5. package/.claude/skills/cfn-product-owner-decision/execute-decision.sh +207 -113
  6. package/.claude/skills/cfn-product-owner-decision/test-backlog-integration.sh +148 -0
  7. package/.claude/skills/cfn-redis-coordination/report-completion.sh +86 -0
  8. package/.claude/skills/cfn-redis-coordination/store-context.sh +34 -0
  9. package/.claude/skills/pre-edit-backup/backup.sh +130 -0
  10. package/.claude/skills/pre-edit-backup/cleanup.sh +155 -0
  11. package/.claude/skills/pre-edit-backup/restore.sh +128 -0
  12. package/.claude/skills/pre-edit-backup/revert-file.sh +168 -0
  13. package/claude-assets/agents/README-AGENT_LIFECYCLE.md +522 -0
  14. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +13 -8
  15. package/claude-assets/agents/cfn-dev-team/product-owners/product-owner.md +1 -1
  16. package/claude-assets/agents/cfn-dev-team/test-agent.md +141 -0
  17. package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +35 -0
  18. package/claude-assets/commands/cfn/run-tests.md +119 -0
  19. package/claude-assets/hooks/cfn-post-edit.config.json +11 -4
  20. package/claude-assets/skills/agent-name-validation/README.md +28 -0
  21. package/claude-assets/skills/agent-name-validation/SKILL.md +168 -0
  22. package/claude-assets/skills/agent-name-validation/validate-agent-names.sh +47 -0
  23. package/claude-assets/skills/cfn-agent-selector/SKILL.md +3 -2
  24. package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +1 -1
  25. package/claude-assets/skills/cfn-product-owner-decision/execute-decision.sh +207 -113
  26. package/claude-assets/skills/cfn-product-owner-decision/test-backlog-integration.sh +148 -0
  27. package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +86 -0
  28. package/claude-assets/skills/cfn-redis-coordination/store-context.sh +34 -0
  29. package/claude-assets/skills/cfn-task-classifier/SKILL.md +1 -1
  30. package/claude-assets/skills/cfn-test-runner/SKILL.md +288 -0
  31. package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +55 -0
  32. package/claude-assets/skills/cfn-test-runner/init-benchmark-db.sh +48 -0
  33. package/claude-assets/skills/cfn-test-runner/run-all-tests.sh +222 -0
  34. package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +55 -0
  35. package/claude-assets/skills/cfn-test-runner/validate-redis-keys.sh +143 -0
  36. package/claude-assets/skills/hook-pipeline/bash-dependency-checker.sh +89 -0
  37. package/claude-assets/skills/hook-pipeline/bash-pipe-safety.sh +69 -0
  38. package/claude-assets/skills/hook-pipeline/enforce-lf.sh +36 -0
  39. package/claude-assets/skills/hook-pipeline/js-promise-safety.sh +110 -0
  40. package/claude-assets/skills/hook-pipeline/python-async-safety.py +124 -0
  41. package/claude-assets/skills/hook-pipeline/python-import-checker.py +114 -0
  42. package/claude-assets/skills/hook-pipeline/python-subprocess-safety.py +77 -0
  43. package/claude-assets/skills/hook-pipeline/rust-command-safety.sh +38 -0
  44. package/claude-assets/skills/hook-pipeline/rust-dependency-checker.sh +50 -0
  45. package/claude-assets/skills/hook-pipeline/rust-future-safety.sh +50 -0
  46. package/dist/agents/agent-loader.js +146 -165
  47. package/dist/agents/agent-loader.js.map +1 -1
  48. package/dist/cli/agent-executor.js +1 -1
  49. package/dist/cli/agent-executor.js.map +1 -1
  50. package/dist/cli/agent-prompt-builder.js +40 -30
  51. package/dist/cli/agent-prompt-builder.js.map +1 -1
  52. package/package.json +2 -1
  53. package/scripts/init-project.js +4 -1
  54. package/scripts/switch-api.sh +7 -7
  55. package/claude-assets/agents/cfn-dev-team/developers/dev-backend-api.md +0 -147
  56. package/claude-assets/agents/cfn-dev-team/developers/frontend/spec-mobile-react-native.md +0 -199
  57. package/claude-assets/agents/cfn-dev-team/documentation/docs-api-openapi.md +0 -98
  58. package/claude-assets/agents/cfn-dev-team/product-owners/product-owner-agent.md +0 -155
  59. package/claude-assets/agents/cfn-dev-team/reviewers/quality/analyze-code-quality.md +0 -141
  60. /package/claude-assets/agents/cfn-dev-team/developers/{backend-dev.md → backend-developer.md} +0 -0
  61. /package/claude-assets/agents/cfn-dev-team/documentation/{api-docs.md → api-documentation.md} +0 -0
  62. /package/claude-assets/agents/cfn-dev-team/documentation/{specification.md → specification-agent.md} +0 -0
  63. /package/claude-assets/agents/cfn-dev-team/reviewers/quality/{code-analyzer.md → code-quality-validator.md} +0 -0
  64. /package/claude-assets/agents/cfn-dev-team/testers/e2e/{playwright-agent.md → playwright-tester.md} +0 -0
  65. /package/claude-assets/agents/cfn-dev-team/testers/unit/{tdd-london-swarm.md → tdd-london-unit-swarm.md} +0 -0
  66. /package/claude-assets/agents/cfn-dev-team/testers/validation/{production-validator.md → validation-production-validator.md} +0 -0
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # Redis Key Validator
3
+ # Version: 1.0.0
4
+ # Purpose: Validate Redis key consistency across codebase
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m'
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
17
+
18
+ VIOLATIONS=0
19
+ WARNINGS=0
20
+
21
+ echo -e "${GREEN}=========================================="
22
+ echo "Redis Key Consistency Validator"
23
+ echo -e "==========================================${NC}"
24
+ echo ""
25
+
26
+ # Standard patterns (from audit)
27
+ VALID_PATTERNS=(
28
+ "swarm:\${TASK_ID}:\${AGENT_ID}:done"
29
+ "swarm:\${TASK_ID}:\${AGENT_ID}:confidence"
30
+ "swarm:\${TASK_ID}:\${AGENT_ID}:result"
31
+ "swarm:\${TASK_ID}:decision"
32
+ "swarm:\${TASK_ID}:gate-passed"
33
+ "swarm:\${TASK_ID}:gate-failed"
34
+ "swarm:\${TASK_ID}:loop2:consensus"
35
+ "swarm:metrics:decisions:*"
36
+ )
37
+
38
+ # Anti-patterns (non-standard)
39
+ ANTI_PATTERNS=(
40
+ "swarm:\${TASK_ID}:\${AGENT_ID}:decision" # Should be task-level
41
+ )
42
+
43
+ echo -e "${BLUE}[1/4] Checking for standard pattern usage...${NC}"
44
+
45
+ # Find all redis-cli commands
46
+ REDIS_FILES=$(grep -rl "redis-cli" "$PROJECT_ROOT/.claude" "$PROJECT_ROOT/tests" 2>/dev/null || true)
47
+
48
+ for file in $REDIS_FILES; do
49
+ # Skip backup files
50
+ if [[ "$file" =~ \.backup ]]; then
51
+ continue
52
+ fi
53
+
54
+ # Check for anti-patterns
55
+ for anti_pattern in "${ANTI_PATTERNS[@]}"; do
56
+ # Convert pattern to grep regex
57
+ grep_pattern=$(echo "$anti_pattern" | sed 's/\$/\\$/g' | sed 's/{[^}]*}/.*/g')
58
+
59
+ if grep -qE "$grep_pattern" "$file" 2>/dev/null; then
60
+ echo -e "${YELLOW}⚠️ WARNING: Non-standard pattern in $file${NC}"
61
+ echo " Found: $anti_pattern"
62
+ ((WARNINGS++))
63
+ fi
64
+ done
65
+ done
66
+
67
+ echo ""
68
+ echo -e "${BLUE}[2/4] Validating Product Owner decision keys...${NC}"
69
+
70
+ # Check execute-decision.sh uses correct pattern
71
+ PO_SCRIPT="$PROJECT_ROOT/.claude/skills/cfn-product-owner-decision/execute-decision.sh"
72
+ if [ -f "$PO_SCRIPT" ]; then
73
+ # Should have: SET "swarm:${TASK_ID}:decision"
74
+ if grep -q 'SET "swarm:${TASK_ID}:decision"' "$PO_SCRIPT"; then
75
+ echo -e "${GREEN}✅ Product Owner uses standard decision key${NC}"
76
+ else
77
+ echo -e "${RED}❌ VIOLATION: Product Owner decision key non-standard${NC}"
78
+ ((VIOLATIONS++))
79
+ fi
80
+
81
+ # Should have TTL (EX 3600)
82
+ if grep -q 'EX 3600' "$PO_SCRIPT"; then
83
+ echo -e "${GREEN}✅ Product Owner decision has TTL${NC}"
84
+ else
85
+ echo -e "${YELLOW}⚠️ WARNING: Product Owner decision missing TTL${NC}"
86
+ ((WARNINGS++))
87
+ fi
88
+ else
89
+ echo -e "${RED}❌ VIOLATION: Product Owner script not found${NC}"
90
+ ((VIOLATIONS++))
91
+ fi
92
+
93
+ echo ""
94
+ echo -e "${BLUE}[3/4] Checking key namespace consistency...${NC}"
95
+
96
+ # Find keys NOT starting with "swarm:"
97
+ NON_SWARM_KEYS=$(grep -rh "redis-cli.*[\"']" "$PROJECT_ROOT/.claude" "$PROJECT_ROOT/tests" 2>/dev/null | \
98
+ grep -oE '"[^"]*"' | \
99
+ grep -v "swarm:" | \
100
+ grep -v "complete" | \
101
+ grep -v "^\"$" | \
102
+ sort -u || true)
103
+
104
+ if [ -n "$NON_SWARM_KEYS" ]; then
105
+ echo -e "${YELLOW}⚠️ WARNING: Found keys outside 'swarm:' namespace:${NC}"
106
+ echo "$NON_SWARM_KEYS" | head -5
107
+ ((WARNINGS++))
108
+ else
109
+ echo -e "${GREEN}✅ All keys use 'swarm:' namespace${NC}"
110
+ fi
111
+
112
+ echo ""
113
+ echo -e "${BLUE}[4/4] Checking for missing TTLs...${NC}"
114
+
115
+ # Find SET commands without EX/EXPIRE
116
+ NO_TTL_COUNT=$(grep -rh "redis-cli.*SET" "$PROJECT_ROOT/.claude" 2>/dev/null | \
117
+ grep -v "EX\|EXPIRE" | \
118
+ wc -l || echo 0)
119
+
120
+ if [ "$NO_TTL_COUNT" -gt 0 ]; then
121
+ echo -e "${YELLOW}⚠️ WARNING: $NO_TTL_COUNT SET commands without TTL${NC}"
122
+ ((WARNINGS++))
123
+ else
124
+ echo -e "${GREEN}✅ All SET commands have TTL${NC}"
125
+ fi
126
+
127
+ echo ""
128
+ echo -e "${GREEN}=========================================="
129
+ echo "Validation Summary"
130
+ echo -e "==========================================${NC}"
131
+ echo "Violations: $VIOLATIONS"
132
+ echo "Warnings: $WARNINGS"
133
+
134
+ if [ $VIOLATIONS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
135
+ echo -e "${GREEN}✅ PASS: All Redis keys follow standards${NC}"
136
+ exit 0
137
+ elif [ $VIOLATIONS -eq 0 ]; then
138
+ echo -e "${YELLOW}⚠️ PASS WITH WARNINGS: $WARNINGS minor issues${NC}"
139
+ exit 0
140
+ else
141
+ echo -e "${RED}❌ FAIL: $VIOLATIONS critical violations${NC}"
142
+ exit 1
143
+ fi
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Bash Dependency Checker
5
+ # Validates script dependencies and checks for their existence
6
+
7
+ # Check if a file path is provided
8
+ if [[ $# -ne 1 ]]; then
9
+ echo "Usage: $0 <file_path>" >&2
10
+ exit 1
11
+ fi
12
+
13
+ FILE_PATH="$1"
14
+
15
+ # Skip non-bash files
16
+ if [[ ! "$FILE_PATH" =~ \.(sh|bash)$ ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Skip empty files
21
+ if [[ ! -s "$FILE_PATH" ]]; then
22
+ exit 0
23
+ fi
24
+
25
+ # Function to resolve relative paths
26
+ resolve_path() {
27
+ local base_dir script_path="$1"
28
+
29
+ # If path is absolute, return as-is
30
+ if [[ "$script_path" =~ ^/ ]]; then
31
+ echo "$script_path"
32
+ return 0
33
+ fi
34
+
35
+ # Get base directory of the current script
36
+ base_dir="$(dirname "$(readlink -f "$FILE_PATH")")"
37
+
38
+ # Resolve relative paths
39
+ readlink -f "$base_dir/$script_path"
40
+ }
41
+
42
+ # Function to extract script dependencies
43
+ extract_dependencies() {
44
+ local missing_deps=0
45
+
46
+ # Extract sourced scripts using source, ., or direct paths
47
+ while IFS= read -r line; do
48
+ local script_path=""
49
+
50
+ # Skip comments
51
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
52
+
53
+ # Match source, ., or sourced script patterns
54
+ if [[ "$line" =~ ^[[:space:]]*(source|\.)[[:space:]]+([^\;]+) ]]; then
55
+ script_path="${BASH_REMATCH[2]}"
56
+ elif [[ "$line" =~ ^[[:space:]]*bash[[:space:]]+([^\;]+) ]]; then
57
+ script_path="${BASH_REMATCH[1]}"
58
+ else
59
+ continue
60
+ fi
61
+
62
+ # Remove surrounding quotes and whitespace
63
+ script_path=$(echo "$script_path" | xargs)
64
+
65
+ # Skip variables and arithmetic expansions (after quote removal)
66
+ if [[ "$script_path" =~ ^\$ ]]; then
67
+ continue
68
+ fi
69
+
70
+ # Resolve path
71
+ local resolved_path
72
+ resolved_path=$(resolve_path "$script_path")
73
+
74
+ # Check if resolved script exists
75
+ if [[ ! -f "$resolved_path" ]]; then
76
+ echo "Missing dependency: $resolved_path" >&2
77
+ missing_deps=$((missing_deps + 1))
78
+ fi
79
+ done < "$FILE_PATH"
80
+
81
+ return $missing_deps
82
+ }
83
+
84
+ # Run dependency checks
85
+ if ! extract_dependencies; then
86
+ exit 1
87
+ fi
88
+
89
+ exit 0
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Bash Pipe Safety Validator
5
+ # Checks for potential pipe safety issues in bash scripts
6
+
7
+ # Risky commands list
8
+ RISKY_COMMANDS=(
9
+ "redis-cli"
10
+ "curl"
11
+ "wget"
12
+ "npm"
13
+ "docker"
14
+ "git"
15
+ "mysql"
16
+ "psql"
17
+ "python"
18
+ "node"
19
+ )
20
+
21
+ # Check if a file path is provided
22
+ if [[ $# -ne 1 ]]; then
23
+ echo "Usage: $0 <file_path>" >&2
24
+ exit 1
25
+ fi
26
+
27
+ FILE_PATH="$1"
28
+
29
+ # Skip non-bash files
30
+ if [[ ! "$FILE_PATH" =~ \.(sh|bash)$ ]]; then
31
+ exit 0
32
+ fi
33
+
34
+ # Skip empty files
35
+ if [[ ! -s "$FILE_PATH" ]]; then
36
+ exit 0
37
+ fi
38
+
39
+ # Check for pipefail (either 'set -o pipefail' or 'set -euo pipefail' or similar)
40
+ if ! grep -qE "set -(o pipefail|[a-z]*o[a-z]*)" "$FILE_PATH" || ! grep -q "pipefail" "$FILE_PATH"; then
41
+ echo "Warning: Missing 'set -o pipefail' in script" >&2
42
+ exit 2
43
+ fi
44
+
45
+ # Function to check for risky pipe usage
46
+ check_pipe_safety() {
47
+ local line issues=0
48
+
49
+ while IFS= read -r line; do
50
+ # Check for pipe usage without stderr redirection
51
+ if [[ "$line" =~ \| ]]; then
52
+ for cmd in "${RISKY_COMMANDS[@]}"; do
53
+ if [[ "$line" =~ $cmd ]] && [[ ! "$line" =~ (2>/dev/null|2>&1) ]]; then
54
+ echo "Potential pipe safety issue in line: $line" >&2
55
+ ((issues++))
56
+ fi
57
+ done
58
+ fi
59
+ done < "$FILE_PATH"
60
+
61
+ return $issues
62
+ }
63
+
64
+ # Run safety checks
65
+ if ! check_pipe_safety; then
66
+ exit 2
67
+ fi
68
+
69
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Enforce Line Endings Validator
5
+ # Converts CRLF to LF for text files
6
+
7
+ # Check if a file path is provided
8
+ if [[ $# -ne 1 ]]; then
9
+ echo "Usage: $0 <file_path>" >&2
10
+ exit 1
11
+ fi
12
+
13
+ FILE_PATH="$1"
14
+
15
+ # Skip empty files
16
+ if [[ ! -s "$FILE_PATH" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Check if file is binary
21
+ if file --mime-type "$FILE_PATH" | grep -qE '(binary|application/)'; then
22
+ exit 0
23
+ fi
24
+
25
+ # Skip files that are already LF (check for carriage return)
26
+ if ! grep -q $'\r' "$FILE_PATH"; then
27
+ exit 0
28
+ fi
29
+
30
+ # Convert CRLF to LF
31
+ sed -i 's/\r$//' "$FILE_PATH"
32
+
33
+ # Optional: Log the conversion
34
+ echo "Converted $FILE_PATH to LF line endings" >&2
35
+
36
+ exit 0
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # Manual check for unhandled promises (fallback if ESLint not available)
3
+
4
+ set -euo pipefail
5
+
6
+ FILE="${1:-}"
7
+ if [[ ! "$FILE" =~ \.(js|ts|jsx|tsx|mjs|cjs)$ ]]; then
8
+ exit 0
9
+ fi
10
+
11
+ # Read file into array for context-aware checking
12
+ mapfile -t FILE_LINES < "$FILE"
13
+
14
+ # Check for async function calls without await/catch/then/return
15
+ UNHANDLED=""
16
+
17
+ for ((i=0; i<${#FILE_LINES[@]}; i++)); do
18
+ line_num=$((i + 1))
19
+ content="${FILE_LINES[$i]}"
20
+
21
+ # Skip empty lines and comments
22
+ if [[ -z "$content" ]] || [[ "$content" =~ ^[[:space:]]*// ]] || [[ "$content" =~ ^[[:space:]]*\* ]]; then
23
+ continue
24
+ fi
25
+
26
+ # Skip if line doesn't contain function calls
27
+ if ! echo "$content" | grep -qE '\w+\(\)'; then
28
+ continue
29
+ fi
30
+
31
+ # Skip function definitions (async function foo(), function foo(), const foo = async)
32
+ if echo "$content" | grep -qE '(async[[:space:]]+function|function[[:space:]]+|const[[:space:]]+\w+[[:space:]]*=[[:space:]]*async|let[[:space:]]+\w+[[:space:]]*=[[:space:]]*async|var[[:space:]]+\w+[[:space:]]*=[[:space:]]*async)'; then
33
+ continue
34
+ fi
35
+
36
+ # Skip if line contains await, catch, then, or return
37
+ if echo "$content" | grep -qE '(await|\.catch|\.then|return)'; then
38
+ continue
39
+ fi
40
+
41
+ # Check next line for .catch() or .then() chaining (within 2 lines)
42
+ has_chaining=false
43
+ for ((j=1; j<=2 && (i+j)<${#FILE_LINES[@]}; j++)); do
44
+ next_line="${FILE_LINES[$((i+j))]}"
45
+ if echo "$next_line" | grep -qE '^[[:space:]]*\.(catch|then)'; then
46
+ has_chaining=true
47
+ break
48
+ fi
49
+ done
50
+
51
+ if [[ "$has_chaining" == "true" ]]; then
52
+ continue
53
+ fi
54
+
55
+ # Check if inside try-catch block (look back up to 10 lines)
56
+ in_try_catch=false
57
+ try_depth=0
58
+ for ((j=1; j<=10 && (i-j)>=0; j++)); do
59
+ prev_line="${FILE_LINES[$((i-j))]}"
60
+
61
+ # Count braces to track depth
62
+ if echo "$prev_line" | grep -qE '\{'; then
63
+ ((try_depth++)) || true
64
+ fi
65
+ if echo "$prev_line" | grep -qE '\}'; then
66
+ ((try_depth--)) || true
67
+ fi
68
+
69
+ # Found a try block at same depth
70
+ if [[ $try_depth -gt 0 ]] && echo "$prev_line" | grep -qE '^[[:space:]]*try[[:space:]]*\{'; then
71
+ in_try_catch=true
72
+ break
73
+ fi
74
+ done
75
+
76
+ if [[ "$in_try_catch" == "true" ]]; then
77
+ continue
78
+ fi
79
+
80
+ # Extract function name and check if it's async
81
+ FUNC_NAME=$(echo "$content" | grep -oE '\w+\(\)' | head -1 | sed 's/()//')
82
+
83
+ if [[ -z "$FUNC_NAME" ]]; then
84
+ continue
85
+ fi
86
+
87
+ # Check if function is async (look for async function definition)
88
+ if grep -qE "async[[:space:]]+(function[[:space:]]+${FUNC_NAME}|const[[:space:]]+${FUNC_NAME}|let[[:space:]]+${FUNC_NAME}|var[[:space:]]+${FUNC_NAME})" "$FILE"; then
89
+ if [[ -n "$UNHANDLED" ]]; then
90
+ UNHANDLED="${UNHANDLED}"$'\n'"$line_num:$content"
91
+ else
92
+ UNHANDLED="$line_num:$content"
93
+ fi
94
+ fi
95
+ done
96
+
97
+ if [ -n "$UNHANDLED" ]; then
98
+ echo "⚠️ Promise Safety Warning: Potential unhandled async calls" >&2
99
+ echo "" >&2
100
+ echo "$UNHANDLED" | while IFS=: read -r line_num content; do
101
+ echo " Line $line_num: ${content}" >&2
102
+ done
103
+ echo "" >&2
104
+ echo " Recommendation: Add 'await' or '.catch()' to handle promise" >&2
105
+ echo " Better: Run ESLint with @typescript-eslint/no-floating-promises" >&2
106
+ echo "" >&2
107
+ exit 2
108
+ fi
109
+
110
+ exit 0
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ import ast
3
+ import sys
4
+
5
+ class AsyncSafetyVisitor(ast.NodeVisitor):
6
+ def __init__(self, debug=False):
7
+ self.async_functions = {}
8
+ self.unsafe_calls = []
9
+ self.node_parents = {}
10
+ self.debug = debug
11
+
12
+ def visit(self, node):
13
+ # Recursive parent tracking
14
+ for child in ast.iter_child_nodes(node):
15
+ self.node_parents[child] = node
16
+ super().visit(node)
17
+
18
+ def get_parent(self, node, node_type=None):
19
+ """Recursively find parent of specified type"""
20
+ current = self.node_parents.get(node)
21
+ while current:
22
+ if node_type is None or isinstance(current, node_type):
23
+ return current
24
+ current = self.node_parents.get(current)
25
+ return None
26
+
27
+ def is_awaited(self, node):
28
+ """Check if node is directly inside an Await context"""
29
+ # Parent chain traversal to find await
30
+ parent = self.get_parent(node)
31
+ while parent:
32
+ if isinstance(parent, ast.Await):
33
+ return True
34
+ parent = self.get_parent(parent)
35
+ return False
36
+
37
+ def is_safe_call(self, node):
38
+ """Detect safe async calls"""
39
+ safe_async_funcs = {'create_task', 'gather'}
40
+ safe_modules = {'asyncio'}
41
+
42
+ return (
43
+ isinstance(node.func, ast.Attribute) and
44
+ node.func.attr in safe_async_funcs and
45
+ (node.func.value.id if isinstance(node.func.value, ast.Name) else None) in safe_modules
46
+ )
47
+
48
+ def visit_AsyncFunctionDef(self, node):
49
+ """Track async function contexts"""
50
+ self.async_functions[node] = {
51
+ 'name': node.name,
52
+ 'context': node
53
+ }
54
+ self.generic_visit(node)
55
+
56
+ def visit_Call(self, node):
57
+ """Detect unsafe async calls"""
58
+ # Skip if not in async function
59
+ async_context = next(
60
+ (func for func, details in self.async_functions.items()
61
+ if node in ast.walk(details['context'])),
62
+ None
63
+ )
64
+ if not async_context:
65
+ return
66
+
67
+ # Skip if it's a known safe call
68
+ if self.is_safe_call(node):
69
+ return
70
+
71
+ # Detect function name
72
+ func_name = (
73
+ node.func.attr if isinstance(node.func, ast.Attribute)
74
+ else node.func.id if isinstance(node.func, ast.Name)
75
+ else 'Unknown'
76
+ )
77
+
78
+ # Detect if call is awaited
79
+ if not self.is_awaited(node):
80
+ if self.debug:
81
+ print(f"Debugging - Unsafe Call: {ast.dump(node)}")
82
+ print(f"Function: {func_name}, Awaited: False")
83
+
84
+ self.unsafe_calls.append(
85
+ f"Line {node.lineno}: Async function '{async_context.name}' calls '{func_name}' without await"
86
+ )
87
+
88
+ self.generic_visit(node)
89
+
90
+ def validate_async_safety(file_path, debug=False):
91
+ try:
92
+ with open(file_path, 'r') as f:
93
+ content = f.read()
94
+ tree = ast.parse(content, filename=file_path)
95
+ except SyntaxError as e:
96
+ print(f"Syntax error in {file_path}: {e}")
97
+ return 1
98
+ except FileNotFoundError:
99
+ print(f"File not found: {file_path}")
100
+ return 1
101
+
102
+ visitor = AsyncSafetyVisitor(debug=debug)
103
+ visitor.visit(tree)
104
+
105
+ if visitor.unsafe_calls:
106
+ print("Async safety warnings:")
107
+ for call in visitor.unsafe_calls:
108
+ print(call)
109
+ return 2
110
+
111
+ return 0
112
+
113
+ def main():
114
+ if len(sys.argv) < 2:
115
+ print("Usage: python3 python-async-safety.py <python_file>")
116
+ return 1
117
+
118
+ file_path = sys.argv[1]
119
+ debug = "--debug" in sys.argv
120
+ result = validate_async_safety(file_path, debug=debug)
121
+ sys.exit(result)
122
+
123
+ if __name__ == '__main__':
124
+ main()
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ import ast
3
+ import sys
4
+ import importlib.util
5
+ import importlib.machinery
6
+
7
+ class ImportChecker(ast.NodeVisitor):
8
+ def __init__(self, debug=False):
9
+ self.imported_modules = set()
10
+ self.used_modules = {}
11
+ self.debug = debug
12
+
13
+ def visit_Name(self, node):
14
+ if isinstance(node.ctx, ast.Load):
15
+ # Specific modules to check
16
+ specific_modules = {'json', 'requests', 'numpy', 'np'}
17
+
18
+ if node.id in specific_modules and node.id not in self.imported_modules:
19
+ self.used_modules[node.id] = node.lineno
20
+ self.generic_visit(node)
21
+
22
+ def visit_Import(self, node):
23
+ for alias in node.names:
24
+ module_name = alias.name.split('.')[0]
25
+ self.imported_modules.add(module_name)
26
+ if alias.asname:
27
+ self.imported_modules.add(alias.asname)
28
+ if self.debug:
29
+ print(f"Imported module: {module_name}")
30
+ self.generic_visit(node)
31
+
32
+ def visit_ImportFrom(self, node):
33
+ if node.module:
34
+ base_module = node.module.split('.')[0]
35
+ self.imported_modules.add(base_module)
36
+
37
+ # Capture aliases for modules like 'numpy as np'
38
+ for alias in node.names:
39
+ if alias.asname:
40
+ self.imported_modules.add(alias.asname)
41
+
42
+ if self.debug:
43
+ print(f"Imported from module: {base_module}")
44
+ self.generic_visit(node)
45
+
46
+ def is_module_resolvable(module_name, debug=False):
47
+ """Specific import detection for known modules"""
48
+
49
+ # Always check specific known modules
50
+ specific_modules = {
51
+ 'json', 'requests', 'numpy', 'np',
52
+ 'pandas', 'scipy', 'sklearn', 'matplotlib'
53
+ }
54
+
55
+ if module_name in specific_modules:
56
+ return False
57
+
58
+ # Standard library
59
+ standard_modules = {
60
+ 'sys', 'os', 're', 'typing', 'collections', 'dataclasses',
61
+ 'datetime', 'math', 'asyncio', 'argparse', 'random',
62
+ 'functools', 'itertools', 'enum', 'pathlib', 'subprocess'
63
+ }
64
+
65
+ if module_name in standard_modules:
66
+ return True
67
+
68
+ # Fallback resolution strategies
69
+ try:
70
+ return importlib.util.find_spec(module_name) is not None
71
+ except (ImportError, AttributeError):
72
+ return False
73
+
74
+ def validate_imports(file_path, debug=False):
75
+ try:
76
+ with open(file_path, 'r') as f:
77
+ tree = ast.parse(f.read(), filename=file_path)
78
+ except SyntaxError as e:
79
+ print(f"Syntax error in {file_path}: {e}")
80
+ return 1
81
+ except FileNotFoundError:
82
+ print(f"File not found: {file_path}")
83
+ return 1
84
+
85
+ visitor = ImportChecker(debug=debug)
86
+ visitor.visit(tree)
87
+
88
+ # Check for unresolved modules
89
+ unresolved_modules = [
90
+ (module, lineno) for module, lineno in visitor.used_modules.items()
91
+ if not is_module_resolvable(module, debug=debug)
92
+ ]
93
+
94
+ # Validation
95
+ if unresolved_modules:
96
+ print("Import resolution warnings:")
97
+ for module, lineno in unresolved_modules:
98
+ print(f"Line {lineno}: Unable to resolve import: {module}")
99
+ return 2
100
+
101
+ return 0
102
+
103
+ def main():
104
+ if len(sys.argv) < 2:
105
+ print("Usage: python3 python-import-checker.py <python_file>")
106
+ return 1
107
+
108
+ file_path = sys.argv[1]
109
+ debug = "--debug" in sys.argv
110
+ result = validate_imports(file_path, debug=debug)
111
+ sys.exit(result)
112
+
113
+ if __name__ == '__main__':
114
+ main()