@hybridlabor-api/aos 4.8.0 → 4.10.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.
Files changed (186) hide show
  1. package/.agents/{agents.md → AGENTS.md} +3 -1
  2. package/.agents/nodes.json +3 -1
  3. package/.agents/vendor-manifest.json +23 -1
  4. package/.claude/agents/godmode-media-eventtech.md +1 -1
  5. package/.claude/hooks/conventional-commits.mjs +125 -0
  6. package/.claude/hooks/env-file-protection.mjs +105 -0
  7. package/.claude/hooks/go-gate.mjs +101 -81
  8. package/.claude/hooks/memb-inject.mjs +29 -1
  9. package/.claude/settings.json +13 -0
  10. package/.claude/workflows/startcycle-dispatch.mjs +23 -1
  11. package/.opencode/agents/godmode-media-eventtech.md +1 -1
  12. package/.opencode/plugins/bdb-aos.js +31 -4
  13. package/CLAUDE.md +0 -571
  14. package/README.de.md +1 -1
  15. package/README.md +1 -1
  16. package/README.pt.md +1 -1
  17. package/THIRD_PARTY_NOTICES.md +126 -0
  18. package/bin/aos-doctor.mjs +1 -1
  19. package/docs/skills_table.md +1 -1
  20. package/installer.js +187 -55
  21. package/package.json +7 -3
  22. package/packages/aos-cli/README.md +80 -0
  23. package/packages/aos-cli/bin/aos-cli.mjs +134 -0
  24. package/packages/aos-cli/core-skills.json +12 -0
  25. package/packages/aos-cli/extensions/aos.ts +321 -0
  26. package/packages/aos-cli/package-lock.json +1923 -0
  27. package/packages/aos-cli/package.json +29 -0
  28. package/packages/aos-cli/scripts/check-theme.mjs +63 -0
  29. package/packages/aos-cli/themes/aos.json +97 -0
  30. package/scripts/build-plugin-manifest.mjs +131 -0
  31. package/scripts/validate-skills.mjs +81 -6
  32. package/skills/basic/ao-orchestrator/SKILL.md +116 -0
  33. package/skills/basic/bdb-eventagency-skill/SKILL.md +252 -0
  34. package/skills/basic/bdb-shipping-skill/SKILL.md +161 -0
  35. package/skills/basic/godmode-eventtech/SKILL.md +4 -1
  36. package/skills/global_config/agenttrail/SKILL.md +6 -1
  37. package/skills/global_config/aos-project-init/SKILL.md +2 -0
  38. package/skills/global_config/aos-project-init/assets/AGENTS.template.md +1 -1
  39. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +1 -1
  40. package/skills/global_config/aos-setup/SKILL.md +1 -1
  41. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  42. package/skills/global_config/ask-tim/SKILL.md +7 -7
  43. package/skills/global_config/bash-script-generator/SKILL.md +201 -0
  44. package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
  45. package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
  46. package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
  47. package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
  48. package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
  49. package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
  50. package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
  51. package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
  52. package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
  53. package/skills/global_config/bash-script-validator/SKILL.md +249 -0
  54. package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
  55. package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
  56. package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
  57. package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
  58. package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
  59. package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
  60. package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
  61. package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
  62. package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
  63. package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
  64. package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
  65. package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
  66. package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
  67. package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
  68. package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
  69. package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
  70. package/skills/global_config/ci-pipeline/SKILL.md +135 -0
  71. package/skills/global_config/deja-memory/SKILL.md +3 -1
  72. package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
  73. package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
  74. package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
  75. package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
  76. package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
  77. package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
  78. package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
  79. package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
  80. package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
  81. package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
  82. package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
  83. package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
  84. package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
  85. package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
  86. package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
  87. package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
  88. package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
  89. package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
  90. package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
  91. package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
  92. package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
  93. package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
  94. package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
  95. package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
  96. package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
  97. package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
  98. package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
  99. package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
  100. package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
  101. package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
  102. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
  103. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
  104. package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
  105. package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
  106. package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
  107. package/skills/global_config/github-actions-generator/SKILL.md +353 -0
  108. package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
  109. package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
  110. package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
  111. package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
  112. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
  113. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
  114. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
  115. package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
  116. package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
  117. package/skills/global_config/github-actions-generator/examples/README.md +147 -0
  118. package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
  119. package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
  120. package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
  121. package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
  122. package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
  123. package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
  124. package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
  125. package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
  126. package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
  127. package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
  128. package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
  129. package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
  130. package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
  131. package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
  132. package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
  133. package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
  134. package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
  135. package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
  136. package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
  137. package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
  138. package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
  139. package/skills/global_config/github-actions-templates/SKILL.md +7 -0
  140. package/skills/global_config/github-actions-validator/SKILL.md +576 -0
  141. package/skills/global_config/github-actions-validator/examples/README.md +88 -0
  142. package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
  143. package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
  144. package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
  145. package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
  146. package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
  147. package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
  148. package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
  149. package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
  150. package/skills/global_config/github-actions-validator/references/runners.md +317 -0
  151. package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
  152. package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
  153. package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
  154. package/skills/global_config/makefile-generator/SKILL.md +614 -0
  155. package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
  156. package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
  157. package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
  158. package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
  159. package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
  160. package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
  161. package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
  162. package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
  163. package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
  164. package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
  165. package/skills/global_config/makefile-validator/SKILL.md +244 -0
  166. package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
  167. package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
  168. package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
  169. package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
  170. package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
  171. package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
  172. package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
  173. package/skills/global_config/{MCP_Manage → mcp-manage}/SKILL.md +3 -3
  174. package/skills/global_config/plan-canvas/SKILL.md +9 -2
  175. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +10 -2
  176. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +1 -1
  177. package/skills/global_config/read-the-damn-docs/SKILL.md +175 -0
  178. package/skills/global_config/requesting-code-review/SKILL.md +98 -0
  179. package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
  180. package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
  181. package/skills/global_config/verification-before-completion/SKILL.md +123 -0
  182. package/skills/global_config/writing-plans/SKILL.md +126 -46
  183. package/skills/global_config/writing-plans-legacy/SKILL.md +152 -0
  184. package/.claude/CLAUDE.md +0 -12
  185. package/mcps/RhinoMCP/docs/content/docs/getting-started/gemini.md +0 -61
  186. /package/{GEMINI.md → RULES.md} +0 -0
@@ -0,0 +1,910 @@
1
+ #!/bin/bash
2
+ # GitHub Actions Validator - Workflow Validation Script
3
+ # Validates GitHub Actions workflows using actionlint and act
4
+ # Includes version checking and reference file hints
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ TOOLS_DIR="${SCRIPT_DIR}/.tools"
8
+ SKILL_DIR="$(dirname "${SCRIPT_DIR}")"
9
+ REFERENCES_DIR="${SKILL_DIR}/references"
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ CYAN='\033[0;36m'
17
+ NC='\033[0m' # No Color
18
+
19
+ log_info() {
20
+ echo -e "${GREEN}[INFO]${NC} $1"
21
+ }
22
+
23
+ log_warn() {
24
+ echo -e "${YELLOW}[WARN]${NC} $1"
25
+ }
26
+
27
+ log_error() {
28
+ echo -e "${RED}[ERROR]${NC} $1"
29
+ }
30
+
31
+ log_section() {
32
+ echo ""
33
+ echo -e "${BLUE}=== $1 ===${NC}"
34
+ echo ""
35
+ }
36
+
37
+ log_reference() {
38
+ echo -e "${CYAN}[REF]${NC} $1"
39
+ }
40
+
41
+ # Check if a tool is available either in scripts/.tools or on PATH
42
+ tool_exists() {
43
+ local tool_name=$1
44
+ [ -x "${TOOLS_DIR}/${tool_name}" ] || command -v "${tool_name}" &> /dev/null
45
+ }
46
+
47
+ # Check if Docker is running
48
+ check_docker() {
49
+ if ! docker info &> /dev/null 2>&1; then
50
+ return 1
51
+ fi
52
+ return 0
53
+ }
54
+
55
+ # Pre-check Docker status and inform user early
56
+ precheck_docker() {
57
+ if ! check_docker; then
58
+ log_warn "Docker is not running - act testing will be skipped"
59
+ log_info "To enable full validation, start Docker Desktop or Docker daemon"
60
+ log_info "Continuing with actionlint validation only..."
61
+ echo ""
62
+ return 1
63
+ fi
64
+ return 0
65
+ }
66
+
67
+ # Current recommended action versions (December 2025)
68
+ # Format: action_name:current_version:minimum_version
69
+ declare -A ACTION_VERSIONS=(
70
+ ["actions/checkout"]="v6:v4"
71
+ ["actions/setup-node"]="v6:v4"
72
+ ["actions/setup-python"]="v5:v4"
73
+ ["actions/setup-java"]="v4:v4"
74
+ ["actions/setup-go"]="v5:v4"
75
+ ["actions/cache"]="v4:v4"
76
+ ["actions/upload-artifact"]="v4:v4"
77
+ ["actions/download-artifact"]="v4:v4"
78
+ ["docker/setup-buildx-action"]="v3:v3"
79
+ ["docker/login-action"]="v3:v3"
80
+ ["docker/build-push-action"]="v6:v5"
81
+ ["docker/metadata-action"]="v5:v5"
82
+ ["aws-actions/configure-aws-credentials"]="v4:v4"
83
+ )
84
+
85
+ # Extract major version from version string (v4.1.1 -> 4, v4 -> 4)
86
+ get_major_version() {
87
+ local version=$1
88
+ # Remove 'v' prefix and get first number
89
+ echo "$version" | sed 's/^v//' | cut -d'.' -f1
90
+ }
91
+
92
+ # Check action versions in workflow files
93
+ check_action_versions() {
94
+ local workflow_path=$1
95
+ log_section "Action Version Check"
96
+
97
+ local files_to_check=()
98
+
99
+ if [ -f "$workflow_path" ]; then
100
+ files_to_check+=("$workflow_path")
101
+ elif [ -d "$workflow_path" ]; then
102
+ while IFS= read -r -d '' file; do
103
+ files_to_check+=("$file")
104
+ done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
105
+ fi
106
+
107
+ if [ ${#files_to_check[@]} -eq 0 ]; then
108
+ log_warn "No workflow files found to check"
109
+ return 0
110
+ fi
111
+
112
+ local has_issues=0
113
+ local outdated_count=0
114
+ local deprecated_count=0
115
+ local uptodate_count=0
116
+
117
+ for file in "${files_to_check[@]}"; do
118
+ log_info "Checking: $file"
119
+
120
+ # Extract all 'uses:' statements
121
+ while IFS= read -r line; do
122
+ # Skip empty lines and comments
123
+ [[ -z "$line" ]] && continue
124
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
125
+
126
+ # Extract action reference (e.g., actions/checkout@v4 or actions/checkout@sha)
127
+ if [[ "$line" =~ uses:[[:space:]]*([^@]+)@([^[:space:]#]+) ]]; then
128
+ local action="${BASH_REMATCH[1]}"
129
+ local version="${BASH_REMATCH[2]}"
130
+
131
+ # Clean up action name (remove quotes if present)
132
+ action=$(echo "$action" | tr -d '"' | tr -d "'" | xargs)
133
+
134
+ # Check if this action is in our version database
135
+ if [[ -v ACTION_VERSIONS["$action"] ]]; then
136
+ local version_info="${ACTION_VERSIONS[$action]}"
137
+ local current_version="${version_info%%:*}"
138
+ local minimum_version="${version_info##*:}"
139
+
140
+ local current_major=$(get_major_version "$current_version")
141
+ local minimum_major=$(get_major_version "$minimum_version")
142
+
143
+ # Handle SHA pinning - extract version from comment if present
144
+ local used_major=""
145
+ if [[ "$version" =~ ^[0-9a-f]{40}$ ]] || [[ "$version" =~ ^[0-9a-f]{7,}$ ]]; then
146
+ # SHA pinning - try to find version in the same line or comment
147
+ if [[ "$line" =~ v([0-9]+) ]]; then
148
+ used_major="${BASH_REMATCH[1]}"
149
+ else
150
+ # Can't determine version from SHA, skip
151
+ echo " ⚪ ${action}@${version:0:12}... - SHA pinned (version unknown)"
152
+ continue
153
+ fi
154
+ else
155
+ used_major=$(get_major_version "$version")
156
+ fi
157
+
158
+ if [ -z "$used_major" ] || ! [[ "$used_major" =~ ^[0-9]+$ ]]; then
159
+ echo " ⚪ ${action}@${version} - Unable to parse version"
160
+ continue
161
+ fi
162
+
163
+ if [ "$used_major" -lt "$minimum_major" ]; then
164
+ echo -e " ${RED}❌${NC} ${action}@${version} - ${RED}DEPRECATED${NC} (minimum: ${minimum_version}, using: v${used_major})"
165
+ ((deprecated_count++))
166
+ has_issues=1
167
+ elif [ "$used_major" -lt "$current_major" ]; then
168
+ echo -e " ${YELLOW}⚠️${NC} ${action}@${version} - ${YELLOW}OUTDATED${NC} (current: ${current_version}, using: v${used_major})"
169
+ ((outdated_count++))
170
+ else
171
+ echo -e " ${GREEN}✅${NC} ${action}@${version} - UP-TO-DATE (current: ${current_version})"
172
+ ((uptodate_count++))
173
+ fi
174
+ fi
175
+ fi
176
+ done < "$file"
177
+ done
178
+
179
+ echo ""
180
+ log_info "Version Check Summary:"
181
+ log_info " Up-to-date: $uptodate_count"
182
+ if [ $outdated_count -gt 0 ]; then
183
+ log_warn " Outdated: $outdated_count"
184
+ fi
185
+ if [ $deprecated_count -gt 0 ]; then
186
+ log_error " Deprecated: $deprecated_count"
187
+ fi
188
+
189
+ if [ $outdated_count -gt 0 ] || [ $deprecated_count -gt 0 ]; then
190
+ echo ""
191
+ log_info "Recommendations:"
192
+ if [ $deprecated_count -gt 0 ]; then
193
+ log_error " - Update deprecated actions to current versions (see references/action_versions.md)"
194
+ fi
195
+ if [ $outdated_count -gt 0 ]; then
196
+ log_warn " - Consider updating outdated actions for latest features"
197
+ fi
198
+ log_info " - Use SHA pinning for security: action@SHA # vX.Y.Z"
199
+ fi
200
+
201
+ return $has_issues
202
+ }
203
+
204
+ # Advisory security hardening checks.
205
+ # These checks emit warnings and do not change overall pass/fail status.
206
+ check_security_policies() {
207
+ local workflow_path=$1
208
+ log_section "Security Policy Checks (Advisory)"
209
+
210
+ local files_to_check=()
211
+ if [ -f "$workflow_path" ]; then
212
+ files_to_check+=("$workflow_path")
213
+ elif [ -d "$workflow_path" ]; then
214
+ while IFS= read -r -d '' file; do
215
+ files_to_check+=("$file")
216
+ done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
217
+ else
218
+ log_error "Path not found: $workflow_path"
219
+ return 1
220
+ fi
221
+
222
+ if [ ${#files_to_check[@]} -eq 0 ]; then
223
+ log_warn "No workflow files found for security checks"
224
+ return 0
225
+ fi
226
+
227
+ local warning_count=0
228
+
229
+ for file in "${files_to_check[@]}"; do
230
+ log_info "Checking policies in: $file"
231
+
232
+ # 1) Third-party actions should be pinned to full SHA.
233
+ while IFS= read -r match; do
234
+ local line_no="${match%%:*}"
235
+ local line="${match#*:}"
236
+ if [[ "$line" =~ uses:[[:space:]]*([^@[:space:]]+)@([^[:space:]#]+) ]]; then
237
+ local action="${BASH_REMATCH[1]}"
238
+ local version="${BASH_REMATCH[2]}"
239
+ action=$(echo "$action" | tr -d '"' | tr -d "'" | xargs)
240
+ version=$(echo "$version" | tr -d '"' | tr -d "'" | sed 's/[[:space:],]*$//')
241
+
242
+ # Skip local actions/reusable workflows and docker:// references.
243
+ if [[ "$action" == ./* ]] || [[ "$action" == ../* ]] || [[ "$action" == docker://* ]]; then
244
+ continue
245
+ fi
246
+
247
+ # Only enforce SHA pinning for third-party actions.
248
+ if [[ "$action" == */* ]]; then
249
+ local owner="${action%%/*}"
250
+ if [ "$owner" != "actions" ] && [ "$owner" != "github" ]; then
251
+ if ! [[ "$version" =~ ^[0-9a-fA-F]{40}$ ]]; then
252
+ log_warn "$file:$line_no third-party action is not SHA pinned: ${action}@${version}"
253
+ ((warning_count++))
254
+ fi
255
+ fi
256
+ fi
257
+ fi
258
+ done < <(grep -nE '^[[:space:]]*(-[[:space:]]*)?uses:[[:space:]]*[^[:space:]]+@[^[:space:]#]+' "$file" || true)
259
+
260
+ # 2) Explicit least-privilege permissions.
261
+ if ! grep -qE '^[[:space:]]*permissions:[[:space:]]*' "$file"; then
262
+ log_warn "$file missing explicit permissions block. Add workflow/job-level permissions (or permissions: {})."
263
+ ((warning_count++))
264
+ fi
265
+ if grep -qE '^[[:space:]]*permissions:[[:space:]]*write-all([[:space:]]*(#.*)?)?$' "$file"; then
266
+ log_warn "$file uses permissions: write-all. Prefer least-privilege scopes."
267
+ ((warning_count++))
268
+ fi
269
+
270
+ # 3) Heuristic detection for untrusted github context in run scripts.
271
+ while IFS= read -r risky_line; do
272
+ log_warn "$file:$risky_line potential script injection risk in run step. Move untrusted input to env and quote it."
273
+ ((warning_count++))
274
+ done < <(
275
+ awk '
276
+ function indent_of(s, t) { t=s; sub(/^[ ]*/, "", t); return length(s)-length(t) }
277
+ BEGIN { in_run_block=0; run_indent=-1 }
278
+ {
279
+ line=$0
280
+ indent=indent_of(line)
281
+ if (in_run_block && indent <= run_indent && line !~ /^[[:space:]]*$/) {
282
+ in_run_block=0
283
+ }
284
+ if (line ~ /^[[:space:]]*run:[[:space:]]*[|>][[:space:]]*$/) {
285
+ in_run_block=1
286
+ run_indent=indent
287
+ next
288
+ }
289
+ if (line ~ /^[[:space:]]*run:[[:space:]]*.*\$\{\{[[:space:]]*github\.(event|head_ref|ref_name|actor|triggering_actor|repository_owner|base_ref)/) {
290
+ print NR
291
+ next
292
+ }
293
+ if (in_run_block && line ~ /\$\{\{[[:space:]]*github\.(event|head_ref|ref_name|actor|triggering_actor|repository_owner|base_ref)/) {
294
+ print NR
295
+ }
296
+ }
297
+ ' "$file"
298
+ )
299
+
300
+ # 4) OIDC-integrated actions should explicitly request id-token: write.
301
+ if grep -qiE 'uses:[[:space:]]*(aws-actions/configure-aws-credentials|azure/login|google-github-actions/auth|hashicorp/vault-action|actions/attest-build-provenance)@' "$file"; then
302
+ if ! grep -qiE 'id-token:[[:space:]]*write' "$file"; then
303
+ log_warn "$file uses an OIDC-related action but does not declare id-token: write in permissions."
304
+ ((warning_count++))
305
+ fi
306
+ fi
307
+ done
308
+
309
+ echo ""
310
+ if [ $warning_count -eq 0 ]; then
311
+ log_info "✓ No security policy warnings found"
312
+ else
313
+ log_warn "Security policy warnings: $warning_count (advisory only; exit code unchanged)"
314
+ log_info "See references/common_errors.md (Security section) and references/modern_features.md"
315
+ fi
316
+
317
+ return 0
318
+ }
319
+
320
+ # Show reference file hints based on error type
321
+ show_reference_hints() {
322
+ local error_output=$1
323
+ echo ""
324
+ log_section "Reference Documentation"
325
+
326
+ local showed_hint=0
327
+
328
+ # Check for various error patterns and suggest references
329
+ if echo "$error_output" | grep -qi "syntax\|yaml\|unexpected"; then
330
+ log_reference "Syntax errors detected - see references/common_errors.md (Syntax Errors section)"
331
+ showed_hint=1
332
+ fi
333
+
334
+ if echo "$error_output" | grep -qi "expression\|\${{"; then
335
+ log_reference "Expression errors detected - see references/common_errors.md (Expression Errors section)"
336
+ showed_hint=1
337
+ fi
338
+
339
+ if echo "$error_output" | grep -qi "cron\|schedule"; then
340
+ log_reference "Schedule errors detected - see references/common_errors.md (Schedule Errors section)"
341
+ showed_hint=1
342
+ fi
343
+
344
+ if echo "$error_output" | grep -qi "runner\|runs-on\|ubuntu\|macos\|windows"; then
345
+ log_reference "Runner label issues - see references/runners.md"
346
+ showed_hint=1
347
+ fi
348
+
349
+ if echo "$error_output" | grep -qi "action\|uses:"; then
350
+ log_reference "Action issues detected - see references/common_errors.md (Action Errors section)"
351
+ showed_hint=1
352
+ fi
353
+
354
+ if echo "$error_output" | grep -qi "docker\|container"; then
355
+ log_reference "Docker/container issues - see references/act_usage.md (Troubleshooting section)"
356
+ showed_hint=1
357
+ fi
358
+
359
+ if echo "$error_output" | grep -qi "needs:\|dependency\|job"; then
360
+ log_reference "Job dependency issues - see references/common_errors.md (Job Configuration Errors section)"
361
+ showed_hint=1
362
+ fi
363
+
364
+ if echo "$error_output" | grep -qi "injection\|security\|secret\|untrusted"; then
365
+ log_reference "Security issues detected - see references/common_errors.md (Security section)"
366
+ showed_hint=1
367
+ fi
368
+
369
+ if echo "$error_output" | grep -qi "workflow_call\|reusable\|oidc\|id-token\|attestation\|environment:\|permissions:"; then
370
+ log_reference "Modern features - see references/modern_features.md"
371
+ showed_hint=1
372
+ fi
373
+
374
+ if echo "$error_output" | grep -qi "version\|deprecated\|outdated\|v[0-9]"; then
375
+ log_reference "Action versions - see references/action_versions.md"
376
+ showed_hint=1
377
+ fi
378
+
379
+ if [ $showed_hint -eq 0 ]; then
380
+ log_reference "No direct mapping found for this error output"
381
+ log_reference "Fallback: check references/common_errors.md, then search the exact error text in official docs"
382
+ log_reference "Include exact tool output, workflow file, and line number in your report"
383
+ fi
384
+ }
385
+
386
+ # Validate required tools for selected execution mode
387
+ check_tools() {
388
+ local run_actionlint=$1
389
+ local run_act=$2
390
+ local allow_fallback=$3
391
+ local missing_tools=0
392
+
393
+ if [ "$run_actionlint" = true ] && ! tool_exists "actionlint"; then
394
+ if [ "$allow_fallback" = true ] && [ "$run_act" = true ] && tool_exists "act"; then
395
+ log_warn "actionlint not found. Falling back to act-only validation."
396
+ run_actionlint=false
397
+ else
398
+ log_error "actionlint not found. Please run install_tools.sh first."
399
+ missing_tools=1
400
+ fi
401
+ fi
402
+
403
+ if [ "$run_act" = true ] && ! tool_exists "act"; then
404
+ if [ "$allow_fallback" = true ] && [ "$run_actionlint" = true ] && tool_exists "actionlint"; then
405
+ log_warn "act not found. Falling back to actionlint-only validation."
406
+ run_act=false
407
+ else
408
+ log_error "act not found. Please run install_tools.sh first."
409
+ missing_tools=1
410
+ fi
411
+ fi
412
+
413
+ if [ $missing_tools -eq 1 ]; then
414
+ log_info "Run: bash ${SCRIPT_DIR}/install_tools.sh"
415
+ exit 1
416
+ fi
417
+
418
+ CHECK_TOOLS_RUN_ACTIONLINT=$run_actionlint
419
+ CHECK_TOOLS_RUN_ACT=$run_act
420
+ }
421
+
422
+ # Get the appropriate tool path
423
+ get_tool_path() {
424
+ local tool_name=$1
425
+
426
+ if [ -f "${TOOLS_DIR}/${tool_name}" ]; then
427
+ echo "${TOOLS_DIR}/${tool_name}"
428
+ elif command -v "${tool_name}" &> /dev/null; then
429
+ command -v "${tool_name}"
430
+ else
431
+ log_error "${tool_name} not found"
432
+ exit 1
433
+ fi
434
+ }
435
+
436
+ # Validate workflow with actionlint.
437
+ # Captures output to global ACTIONLINT_OUTPUT (for reference hints) while printing it.
438
+ validate_with_actionlint() {
439
+ local workflow_path=$1
440
+ log_section "Running actionlint"
441
+
442
+ local actionlint_path
443
+ actionlint_path=$(get_tool_path "actionlint")
444
+
445
+ local output=""
446
+ local actionlint_exit=0
447
+
448
+ if [ -f "$workflow_path" ]; then
449
+ log_info "Validating: $workflow_path"
450
+ output=$("${actionlint_path}" "$workflow_path" 2>&1) || actionlint_exit=$?
451
+ [ -n "$output" ] && echo "$output"
452
+ ACTIONLINT_OUTPUT="$output"
453
+ if [ $actionlint_exit -eq 0 ]; then
454
+ log_info "✓ actionlint validation passed"
455
+ return 0
456
+ else
457
+ log_error "✗ actionlint found issues"
458
+ return 1
459
+ fi
460
+ elif [ -d "$workflow_path" ]; then
461
+ log_info "Validating all workflows in: $workflow_path"
462
+
463
+ # Find all .yml and .yaml files
464
+ local workflow_files=()
465
+ while IFS= read -r -d '' file; do
466
+ workflow_files+=("$file")
467
+ done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
468
+
469
+ if [ ${#workflow_files[@]} -eq 0 ]; then
470
+ log_warn "No workflow files found in: $workflow_path"
471
+ return 0
472
+ fi
473
+
474
+ output=$("${actionlint_path}" "${workflow_files[@]}" 2>&1) || actionlint_exit=$?
475
+ [ -n "$output" ] && echo "$output"
476
+ ACTIONLINT_OUTPUT="$output"
477
+ if [ $actionlint_exit -eq 0 ]; then
478
+ log_info "✓ actionlint validation passed for ${#workflow_files[@]} file(s)"
479
+ return 0
480
+ else
481
+ log_error "✗ actionlint found issues"
482
+ return 1
483
+ fi
484
+ else
485
+ log_error "Path not found: $workflow_path"
486
+ return 1
487
+ fi
488
+ }
489
+
490
+ # Test workflow with act
491
+ test_with_act() {
492
+ local workflow_path=$1
493
+ log_section "Running act (validation)"
494
+ ACT_SKIP_REASON=""
495
+
496
+ # Check if Docker is running
497
+ if ! check_docker; then
498
+ log_error "Docker is not running!"
499
+ log_warn "act requires Docker to validate and test workflows."
500
+ log_warn ""
501
+ log_warn "Solutions:"
502
+ log_warn " 1. Start Docker Desktop or Docker daemon"
503
+ log_warn " 2. Use --lint-only flag to skip act testing"
504
+ log_warn ""
505
+ return 1
506
+ fi
507
+
508
+ local act_path=$(get_tool_path "act")
509
+
510
+ # Convert workflow_path to absolute path
511
+ local abs_workflow_path="$workflow_path"
512
+ if [[ ! "$abs_workflow_path" = /* ]]; then
513
+ abs_workflow_path="$(cd "$(dirname "$workflow_path")" 2>/dev/null && pwd)/$(basename "$workflow_path")" || abs_workflow_path="$workflow_path"
514
+ fi
515
+
516
+ # Find the repository root (where .github/workflows exists)
517
+ local repo_root=""
518
+ local search_path="$abs_workflow_path"
519
+
520
+ # If workflow_path is a file, get its directory for searching
521
+ if [ -f "$search_path" ]; then
522
+ search_path="$(dirname "$search_path")"
523
+ fi
524
+
525
+ # Search upwards for .github/workflows directory
526
+ local current_dir="$search_path"
527
+ while [ "$current_dir" != "/" ]; do
528
+ if [ -d "$current_dir/.github/workflows" ]; then
529
+ repo_root="$current_dir"
530
+ break
531
+ fi
532
+ # Also check if we're inside .github/workflows
533
+ if [[ "$current_dir" == *"/.github/workflows"* ]] || [[ "$current_dir" == *"/.github/workflows" ]]; then
534
+ # Extract the part before .github
535
+ repo_root="${current_dir%%/.github/workflows*}"
536
+ if [ -d "$repo_root/.github/workflows" ]; then
537
+ break
538
+ fi
539
+ fi
540
+ current_dir="$(dirname "$current_dir")"
541
+ done
542
+
543
+ # Fallback to current directory
544
+ if [ -z "$repo_root" ] && [ -d "./.github/workflows" ]; then
545
+ repo_root="$(pwd)"
546
+ fi
547
+
548
+ if [ -z "$repo_root" ]; then
549
+ log_warn "No .github/workflows directory found in path hierarchy"
550
+ log_warn "Skipping act validation - workflows must be in .github/workflows/ directory"
551
+ log_info "Searched from: $workflow_path"
552
+ ACT_SKIP_REASON="no .github/workflows directory found in path hierarchy"
553
+ return 2
554
+ fi
555
+
556
+ log_info "Repository root: $repo_root"
557
+
558
+ # Determine the workflow file(s) to validate with act
559
+ # act requires workflows to be in .github/workflows/ directory
560
+ local workflow_flag=""
561
+ local target_description=""
562
+
563
+ if [ -f "$abs_workflow_path" ]; then
564
+ # Check if the file is inside .github/workflows
565
+ if [[ "$abs_workflow_path" == *"/.github/workflows/"* ]]; then
566
+ # File is in .github/workflows - use -W flag with relative path
567
+ workflow_flag="-W ${abs_workflow_path#$repo_root/}"
568
+ target_description="workflow: $(basename "$abs_workflow_path")"
569
+ else
570
+ # File is outside .github/workflows (e.g., examples/)
571
+ # act cannot directly validate files outside .github/workflows
572
+ log_warn "Target file is outside .github/workflows/: $abs_workflow_path"
573
+ log_warn "act can only validate workflows in .github/workflows/ directory"
574
+ log_info "Skipping act validation for this file"
575
+ log_info "Note: actionlint validation still applies to this file"
576
+ ACT_SKIP_REASON="target file is outside .github/workflows"
577
+ return 2
578
+ fi
579
+ elif [ -d "$abs_workflow_path" ]; then
580
+ # Directory specified
581
+ if [[ "$abs_workflow_path" == *"/.github/workflows"* ]] || [[ "$abs_workflow_path" == "$repo_root/.github/workflows" ]]; then
582
+ # It's the .github/workflows directory - validate all workflows
583
+ workflow_flag=""
584
+ target_description="all workflows in .github/workflows/"
585
+ else
586
+ # Directory outside .github/workflows
587
+ log_warn "Target directory is outside .github/workflows/: $abs_workflow_path"
588
+ log_warn "act can only validate workflows in .github/workflows/ directory"
589
+ log_info "Skipping act validation for this directory"
590
+ ACT_SKIP_REASON="target directory is outside .github/workflows"
591
+ return 2
592
+ fi
593
+ fi
594
+
595
+ # Save current directory
596
+ local original_dir="$(pwd)"
597
+
598
+ # Change to repository root for act
599
+ cd "$repo_root" || {
600
+ log_error "Failed to change to repository root: $repo_root"
601
+ return 1
602
+ }
603
+
604
+ log_info "Target: $target_description"
605
+ log_info "Step 1: Listing workflows..."
606
+ echo ""
607
+
608
+ # Define default runner images to avoid interactive prompts
609
+ # Using medium-sized images for good compatibility without huge downloads
610
+ local runner_images=(
611
+ "-P" "ubuntu-latest=catthehacker/ubuntu:act-latest"
612
+ "-P" "ubuntu-22.04=catthehacker/ubuntu:act-22.04"
613
+ "-P" "ubuntu-20.04=catthehacker/ubuntu:act-20.04"
614
+ )
615
+
616
+ # Use --list to show available workflows
617
+ # This validates that workflows can be parsed
618
+ local list_cmd="${act_path} --list ${workflow_flag} ${runner_images[*]}"
619
+ log_info "Running: act --list ${workflow_flag}"
620
+ if ! eval "${list_cmd}" 2>&1 | head -30; then
621
+ log_warn "Could not list workflows - this may indicate parsing issues"
622
+ echo ""
623
+ else
624
+ echo ""
625
+ log_info "✓ Workflow listing successful"
626
+ fi
627
+
628
+ echo ""
629
+ log_info "Step 2: Validating workflow syntax with dry-run..."
630
+ log_info "Note: This validates workflow structure without executing jobs"
631
+ log_info "Using medium-sized runner images (catthehacker/ubuntu:act-*)"
632
+ echo ""
633
+
634
+ # Run act in dry-run mode
635
+ # --dryrun: validates without executing
636
+ # --container-architecture: ensures consistent platform
637
+ # -W: specifies the workflow file to validate
638
+ # -P: specifies runner images to avoid interactive prompt
639
+ # 2>&1: capture both stdout and stderr
640
+ local act_output
641
+ local act_exit_code
642
+
643
+ local dryrun_cmd="${act_path} --dryrun ${workflow_flag} --container-architecture linux/amd64 ${runner_images[*]}"
644
+ log_info "Running: act --dryrun ${workflow_flag} --container-architecture linux/amd64"
645
+ act_output=$(eval "${dryrun_cmd}" 2>&1)
646
+ act_exit_code=$?
647
+
648
+ # Display output
649
+ echo "$act_output"
650
+ echo ""
651
+
652
+ # Interpret results
653
+ if [ $act_exit_code -eq 0 ]; then
654
+ log_info "✓ act validation passed"
655
+ cd "$original_dir"
656
+ return 0
657
+ else
658
+ # Check for specific error conditions
659
+ if echo "$act_output" | grep -qi "EOF"; then
660
+ log_error "✗ act encountered EOF error"
661
+ log_warn "This should not happen with -P flags set"
662
+ log_info "Try running: act --list manually to diagnose"
663
+ cd "$original_dir"
664
+ return 1
665
+ elif echo "$act_output" | grep -q "unable to get git repo"; then
666
+ log_warn "Not a git repository - some act features limited"
667
+ log_info "act validation completed with warnings"
668
+ cd "$original_dir"
669
+ return 0
670
+ elif echo "$act_output" | grep -qi "pull access denied\|image.*not found"; then
671
+ log_error "✗ Docker image pull failed"
672
+ log_warn "Cannot pull runner images. This may be due to:"
673
+ log_warn " - Docker registry connectivity issues"
674
+ log_warn " - Rate limiting"
675
+ log_warn "First-time run will download ~500MB of images"
676
+ cd "$original_dir"
677
+ return 1
678
+ elif echo "$act_output" | grep -qi "error\|failed"; then
679
+ log_error "✗ act validation failed (exit code: $act_exit_code)"
680
+ log_warn "This may indicate:"
681
+ log_warn " - Workflow syntax errors"
682
+ log_warn " - Invalid action references"
683
+ log_warn " - Docker image issues"
684
+ log_warn " - Configuration problems"
685
+ cd "$original_dir"
686
+ return 1
687
+ else
688
+ log_warn "act completed with warnings (exit code: $act_exit_code)"
689
+ cd "$original_dir"
690
+ return 0
691
+ fi
692
+ fi
693
+ }
694
+
695
+ # Display usage
696
+ # Optional first argument: exit code (default 0, pass 1 for error paths)
697
+ usage() {
698
+ local exit_code="${1:-0}"
699
+ echo "Usage: $0 [OPTIONS] <workflow-file-or-directory>"
700
+ echo ""
701
+ echo "Options:"
702
+ echo " --lint-only Run only actionlint validation"
703
+ echo " --test-only Run only act testing (requires Docker)"
704
+ echo " --check-versions Check action versions against recommended versions"
705
+ echo " --policy-checks Run advisory security policy checks (warnings only)"
706
+ echo " --help Display this help message"
707
+ echo ""
708
+ echo "Examples:"
709
+ echo " $0 .github/workflows/ci.yml"
710
+ echo " $0 .github/workflows/"
711
+ echo " $0 --lint-only .github/workflows/ci.yml"
712
+ echo " $0 --test-only .github/workflows/"
713
+ echo " $0 --check-versions .github/workflows/ci.yml"
714
+ echo " $0 --lint-only --policy-checks .github/workflows/ci.yml"
715
+ echo ""
716
+ echo "Requirements:"
717
+ echo " - actionlint: For static analysis (installed via install_tools.sh)"
718
+ echo " - act: For workflow testing (installed via install_tools.sh)"
719
+ echo " - Docker: Required for act to run (must be running)"
720
+ echo ""
721
+ exit "$exit_code"
722
+ }
723
+
724
+ # Main validation
725
+ main() {
726
+ local workflow_path=""
727
+ local lint_only=false
728
+ local test_only=false
729
+ local check_versions=false
730
+ local policy_checks=false
731
+ local version_only=false
732
+ local run_actionlint=true
733
+ local run_act=true
734
+ local allow_tool_fallback=false
735
+ local docker_available=true
736
+ local did_actionlint=false
737
+ local did_act=false
738
+ local act_skip_reason=""
739
+ local act_result=0
740
+ CHECK_TOOLS_RUN_ACTIONLINT=true
741
+ CHECK_TOOLS_RUN_ACT=true
742
+ ACT_SKIP_REASON=""
743
+
744
+ # Parse arguments
745
+ while [[ $# -gt 0 ]]; do
746
+ case $1 in
747
+ --lint-only)
748
+ lint_only=true
749
+ shift
750
+ ;;
751
+ --test-only)
752
+ test_only=true
753
+ shift
754
+ ;;
755
+ --check-versions)
756
+ check_versions=true
757
+ shift
758
+ ;;
759
+ --policy-checks)
760
+ policy_checks=true
761
+ shift
762
+ ;;
763
+ --help)
764
+ usage
765
+ ;;
766
+ *)
767
+ workflow_path=$1
768
+ shift
769
+ ;;
770
+ esac
771
+ done
772
+
773
+ if [ -z "$workflow_path" ]; then
774
+ log_error "No workflow file or directory specified"
775
+ echo ""
776
+ usage 1
777
+ fi
778
+
779
+ if [ "$lint_only" = true ] && [ "$test_only" = true ]; then
780
+ log_error "Cannot combine --lint-only and --test-only"
781
+ exit 1
782
+ fi
783
+
784
+ # Determine mode-specific execution
785
+ if [ "$test_only" = true ]; then
786
+ run_actionlint=false
787
+ run_act=true
788
+ elif [ "$lint_only" = true ]; then
789
+ run_actionlint=true
790
+ run_act=false
791
+ else
792
+ run_actionlint=true
793
+ run_act=true
794
+ allow_tool_fallback=true
795
+ fi
796
+
797
+ if [ "$check_versions" = true ] && [ "$lint_only" = false ] && [ "$test_only" = false ] && [ "$policy_checks" = false ]; then
798
+ version_only=true
799
+ run_actionlint=false
800
+ run_act=false
801
+ allow_tool_fallback=false
802
+ fi
803
+
804
+ log_section "GitHub Actions Validator"
805
+ log_info "Target: $workflow_path"
806
+
807
+ check_tools "$run_actionlint" "$run_act" "$allow_tool_fallback"
808
+ run_actionlint=$CHECK_TOOLS_RUN_ACTIONLINT
809
+ run_act=$CHECK_TOOLS_RUN_ACT
810
+
811
+ # Pre-check Docker status if act testing is enabled
812
+ if [ "$run_act" = true ]; then
813
+ if ! precheck_docker; then
814
+ if [ "$test_only" = true ]; then
815
+ log_error "Docker is required for --test-only mode"
816
+ exit 1
817
+ fi
818
+ if [ "$run_actionlint" = true ]; then
819
+ docker_available=false
820
+ run_act=false
821
+ log_warn "Proceeding without act because Docker is unavailable"
822
+ else
823
+ log_error "Docker is required for act validation in the selected mode"
824
+ exit 1
825
+ fi
826
+ fi
827
+ fi
828
+
829
+ local exit_code=0
830
+ ACTIONLINT_OUTPUT=""
831
+
832
+ # Run version check if requested
833
+ if [ "$check_versions" = true ]; then
834
+ if ! check_action_versions "$workflow_path"; then
835
+ exit_code=1
836
+ fi
837
+ # If only checking versions, exit here
838
+ if [ "$version_only" = true ]; then
839
+ log_section "Version Check Complete"
840
+ exit $exit_code
841
+ fi
842
+ fi
843
+
844
+ # Run actionlint (output is captured and printed inside validate_with_actionlint,
845
+ # and stored in ACTIONLINT_OUTPUT for reference hint routing)
846
+ if [ "$run_actionlint" = true ]; then
847
+ did_actionlint=true
848
+ if ! validate_with_actionlint "$workflow_path"; then
849
+ exit_code=1
850
+ fi
851
+ fi
852
+
853
+ # Run advisory security checks if requested.
854
+ if [ "$policy_checks" = true ]; then
855
+ if ! check_security_policies "$workflow_path"; then
856
+ exit_code=1
857
+ fi
858
+ fi
859
+
860
+ # Run act if enabled and Docker is available
861
+ if [ "$run_act" = true ] && [ "$docker_available" = true ]; then
862
+ test_with_act "$workflow_path"
863
+ act_result=$?
864
+ if [ $act_result -eq 0 ]; then
865
+ did_act=true
866
+ elif [ $act_result -eq 2 ]; then
867
+ act_skip_reason="$ACT_SKIP_REASON"
868
+ log_warn "act validation skipped: $act_skip_reason"
869
+ if [ "$did_actionlint" = false ]; then
870
+ log_error "No effective validator executed: act was skipped and actionlint did not run"
871
+ exit_code=1
872
+ fi
873
+ else
874
+ exit_code=1
875
+ fi
876
+ fi
877
+
878
+ if [ "$version_only" = false ] && [ "$did_actionlint" = false ] && [ "$did_act" = false ]; then
879
+ log_error "No validator executed; refusing to report success"
880
+ exit_code=1
881
+ fi
882
+
883
+ log_section "Validation Summary"
884
+ if [ $exit_code -eq 0 ]; then
885
+ if [ -n "$act_skip_reason" ]; then
886
+ log_info "✓ Validation passed (actionlint completed; act skipped: $act_skip_reason)"
887
+ else
888
+ log_info "✓ All validations passed"
889
+ fi
890
+ else
891
+ log_error "✗ Some validations failed"
892
+
893
+ # Show reference hints based on errors
894
+ if [ -n "$ACTIONLINT_OUTPUT" ]; then
895
+ show_reference_hints "$ACTIONLINT_OUTPUT"
896
+ fi
897
+
898
+ echo ""
899
+ log_info "Tips:"
900
+ log_info " - Review error messages above"
901
+ log_info " - Use --lint-only to skip Docker-dependent tests"
902
+ log_info " - Use --check-versions to check for outdated actions"
903
+ log_info " - Use --policy-checks for security hardening warnings"
904
+ log_info " - Check references/common_errors.md for solutions"
905
+ fi
906
+
907
+ exit $exit_code
908
+ }
909
+
910
+ main "$@"