@mokoconsulting/mcp-windows 3.0.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 (184) hide show
  1. package/.gitattributes +94 -0
  2. package/.gitmessage +9 -0
  3. package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
  4. package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
  5. package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
  6. package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
  7. package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
  8. package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
  9. package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
  10. package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
  11. package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
  12. package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
  13. package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
  14. package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
  15. package/.mokogitea/branch-protection.yml +251 -0
  16. package/.mokogitea/workflows/auto-assign.yml +76 -0
  17. package/.mokogitea/workflows/auto-bump.yml +66 -0
  18. package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
  19. package/.mokogitea/workflows/auto-release.yml +421 -0
  20. package/.mokogitea/workflows/branch-cleanup.yml +48 -0
  21. package/.mokogitea/workflows/cascade-dev.yml +10 -0
  22. package/.mokogitea/workflows/changelog-validation.yml +101 -0
  23. package/.mokogitea/workflows/ci-generic.yml +191 -0
  24. package/.mokogitea/workflows/cleanup.yml +87 -0
  25. package/.mokogitea/workflows/codeql-analysis.yml +115 -0
  26. package/.mokogitea/workflows/copilot-agent.yml +44 -0
  27. package/.mokogitea/workflows/deploy-manual.yml +126 -0
  28. package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
  29. package/.mokogitea/workflows/gitleaks.yml +92 -0
  30. package/.mokogitea/workflows/issue-branch.yml +73 -0
  31. package/.mokogitea/workflows/mcp-auto-release.yml +278 -0
  32. package/.mokogitea/workflows/mcp-build-test.yml +65 -0
  33. package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
  34. package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
  35. package/.mokogitea/workflows/notify.yml +70 -0
  36. package/.mokogitea/workflows/npm-publish.yml +113 -0
  37. package/.mokogitea/workflows/pr-check.yml +534 -0
  38. package/.mokogitea/workflows/pre-release.yml +252 -0
  39. package/.mokogitea/workflows/rc-revert.yml +66 -0
  40. package/.mokogitea/workflows/repo-health.yml +712 -0
  41. package/.mokogitea/workflows/repository-cleanup.yml +525 -0
  42. package/.mokogitea/workflows/security-audit.yml +82 -0
  43. package/.mokogitea/workflows/standards-compliance.yml +2614 -0
  44. package/.mokogitea/workflows/sync-version-on-merge.yml +133 -0
  45. package/.mokogitea/workflows/update-server.yml +312 -0
  46. package/.mokogitea/workflows/workflow-sync-trigger.yml +73 -0
  47. package/CHANGELOG.md +130 -0
  48. package/CLAUDE.md +49 -0
  49. package/CONTRIBUTING.md +161 -0
  50. package/ISSUES.md +601 -0
  51. package/Makefile +70 -0
  52. package/README.md +80 -0
  53. package/automation/ci-issue-reporter.sh +237 -0
  54. package/config.example.json +18 -0
  55. package/dist/index.d.ts +3 -0
  56. package/dist/index.js +111 -0
  57. package/dist/shell.d.ts +50 -0
  58. package/dist/shell.js +209 -0
  59. package/dist/tools/apps.d.ts +3 -0
  60. package/dist/tools/apps.js +63 -0
  61. package/dist/tools/audio.d.ts +3 -0
  62. package/dist/tools/audio.js +142 -0
  63. package/dist/tools/audio_apps.d.ts +3 -0
  64. package/dist/tools/audio_apps.js +86 -0
  65. package/dist/tools/automation.d.ts +3 -0
  66. package/dist/tools/automation.js +261 -0
  67. package/dist/tools/bluetooth.d.ts +3 -0
  68. package/dist/tools/bluetooth.js +96 -0
  69. package/dist/tools/clipboard.d.ts +3 -0
  70. package/dist/tools/clipboard.js +118 -0
  71. package/dist/tools/config.d.ts +3 -0
  72. package/dist/tools/config.js +85 -0
  73. package/dist/tools/dialog.d.ts +3 -0
  74. package/dist/tools/dialog.js +72 -0
  75. package/dist/tools/display.d.ts +3 -0
  76. package/dist/tools/display.js +256 -0
  77. package/dist/tools/drives.d.ts +3 -0
  78. package/dist/tools/drives.js +98 -0
  79. package/dist/tools/environment.d.ts +3 -0
  80. package/dist/tools/environment.js +129 -0
  81. package/dist/tools/execute.d.ts +3 -0
  82. package/dist/tools/execute.js +28 -0
  83. package/dist/tools/filesystem.d.ts +3 -0
  84. package/dist/tools/filesystem.js +230 -0
  85. package/dist/tools/firewall.d.ts +3 -0
  86. package/dist/tools/firewall.js +108 -0
  87. package/dist/tools/hosts.d.ts +3 -0
  88. package/dist/tools/hosts.js +119 -0
  89. package/dist/tools/maintenance.d.ts +3 -0
  90. package/dist/tools/maintenance.js +236 -0
  91. package/dist/tools/netstat.d.ts +3 -0
  92. package/dist/tools/netstat.js +56 -0
  93. package/dist/tools/network.d.ts +3 -0
  94. package/dist/tools/network.js +70 -0
  95. package/dist/tools/notification.d.ts +3 -0
  96. package/dist/tools/notification.js +41 -0
  97. package/dist/tools/power.d.ts +3 -0
  98. package/dist/tools/power.js +104 -0
  99. package/dist/tools/printer.d.ts +3 -0
  100. package/dist/tools/printer.js +97 -0
  101. package/dist/tools/process.d.ts +3 -0
  102. package/dist/tools/process.js +54 -0
  103. package/dist/tools/process_kill.d.ts +3 -0
  104. package/dist/tools/process_kill.js +48 -0
  105. package/dist/tools/recycle_bin.d.ts +3 -0
  106. package/dist/tools/recycle_bin.js +108 -0
  107. package/dist/tools/registry.d.ts +3 -0
  108. package/dist/tools/registry.js +136 -0
  109. package/dist/tools/scheduler.d.ts +3 -0
  110. package/dist/tools/scheduler.js +116 -0
  111. package/dist/tools/service.d.ts +3 -0
  112. package/dist/tools/service.js +79 -0
  113. package/dist/tools/startup.d.ts +3 -0
  114. package/dist/tools/startup.js +159 -0
  115. package/dist/tools/storage.d.ts +3 -0
  116. package/dist/tools/storage.js +129 -0
  117. package/dist/tools/system.d.ts +3 -0
  118. package/dist/tools/system.js +84 -0
  119. package/dist/tools/system_mgmt.d.ts +3 -0
  120. package/dist/tools/system_mgmt.js +174 -0
  121. package/dist/tools/terminal.d.ts +3 -0
  122. package/dist/tools/terminal.js +80 -0
  123. package/dist/tools/theme.d.ts +3 -0
  124. package/dist/tools/theme.js +165 -0
  125. package/dist/tools/usb.d.ts +3 -0
  126. package/dist/tools/usb.js +52 -0
  127. package/dist/tools/virtual_desktop.d.ts +3 -0
  128. package/dist/tools/virtual_desktop.js +112 -0
  129. package/dist/tools/wifi.d.ts +3 -0
  130. package/dist/tools/wifi.js +136 -0
  131. package/dist/tools/window.d.ts +3 -0
  132. package/dist/tools/window.js +189 -0
  133. package/dist/tools/winget.d.ts +3 -0
  134. package/dist/tools/winget.js +79 -0
  135. package/dist/tools/wsl.d.ts +3 -0
  136. package/dist/tools/wsl.js +99 -0
  137. package/docs/API.md +63 -0
  138. package/docs/ARCHITECTURE.md +73 -0
  139. package/docs/INSTALLATION.md +102 -0
  140. package/docs/index.md +12 -0
  141. package/package.json +35 -0
  142. package/scripts/setup.mjs +123 -0
  143. package/src/index.ts +125 -0
  144. package/src/shell.ts +253 -0
  145. package/src/tools/apps.ts +76 -0
  146. package/src/tools/audio.ts +161 -0
  147. package/src/tools/audio_apps.ts +98 -0
  148. package/src/tools/automation.ts +297 -0
  149. package/src/tools/bluetooth.ts +114 -0
  150. package/src/tools/clipboard.ts +138 -0
  151. package/src/tools/config.ts +105 -0
  152. package/src/tools/dialog.ts +87 -0
  153. package/src/tools/display.ts +285 -0
  154. package/src/tools/drives.ts +124 -0
  155. package/src/tools/environment.ts +146 -0
  156. package/src/tools/execute.ts +35 -0
  157. package/src/tools/filesystem.ts +273 -0
  158. package/src/tools/firewall.ts +125 -0
  159. package/src/tools/hosts.ts +135 -0
  160. package/src/tools/maintenance.ts +299 -0
  161. package/src/tools/netstat.ts +72 -0
  162. package/src/tools/network.ts +84 -0
  163. package/src/tools/notification.ts +50 -0
  164. package/src/tools/power.ts +123 -0
  165. package/src/tools/printer.ts +114 -0
  166. package/src/tools/process.ts +80 -0
  167. package/src/tools/process_kill.ts +57 -0
  168. package/src/tools/recycle_bin.ts +126 -0
  169. package/src/tools/registry.ts +165 -0
  170. package/src/tools/scheduler.ts +140 -0
  171. package/src/tools/service.ts +102 -0
  172. package/src/tools/startup.ts +180 -0
  173. package/src/tools/storage.ts +141 -0
  174. package/src/tools/system.ts +99 -0
  175. package/src/tools/system_mgmt.ts +190 -0
  176. package/src/tools/terminal.ts +117 -0
  177. package/src/tools/theme.ts +205 -0
  178. package/src/tools/usb.ts +65 -0
  179. package/src/tools/virtual_desktop.ts +122 -0
  180. package/src/tools/wifi.ts +157 -0
  181. package/src/tools/window.ts +211 -0
  182. package/src/tools/winget.ts +100 -0
  183. package/src/tools/wsl.ts +112 -0
  184. package/tsconfig.json +19 -0
@@ -0,0 +1,712 @@
1
+ # ============================================================================
2
+ # Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
3
+ #
4
+ # This file is part of a Moko Consulting project.
5
+ #
6
+ # SPDX-License-Identifier: GPL-3.0-or-later
7
+ #
8
+ # FILE INFORMATION
9
+ # DEFGROUP: Gitea.Workflow
10
+ # INGROUP: mokocli.Validation
11
+ # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
12
+ # PATH: /templates/workflows/joomla/repo_health.yml.template
13
+ # VERSION: 09.23.00
14
+ # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
15
+ # ============================================================================
16
+
17
+ name: "Generic: Repo Health"
18
+
19
+ defaults:
20
+ run:
21
+ shell: bash
22
+
23
+ on:
24
+ workflow_dispatch:
25
+ inputs:
26
+ profile:
27
+ description: 'Validation profile: all, scripts, or repo'
28
+ required: true
29
+ default: all
30
+ type: choice
31
+ options:
32
+ - all
33
+ - scripts
34
+ - repo
35
+ pull_request:
36
+ branches:
37
+ - main
38
+
39
+ permissions:
40
+ contents: read
41
+
42
+ env:
43
+ # Scripts governance policy
44
+ SCRIPTS_REQUIRED_DIRS:
45
+ SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
46
+
47
+ # Repo health policy
48
+ REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
49
+ REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
50
+ REPO_DISALLOWED_DIRS:
51
+ REPO_DISALLOWED_FILES: TODO.md,todo.md
52
+
53
+ # Extended checks toggles
54
+ EXTENDED_CHECKS: "true"
55
+
56
+ # File / directory variables
57
+ DOCS_INDEX: docs/docs-index.md
58
+ SCRIPT_DIR: scripts
59
+ WORKFLOWS_DIR: .mokogitea/workflows
60
+ SHELLCHECK_PATTERN: '*.sh'
61
+ SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
62
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
63
+
64
+ jobs:
65
+ access_check:
66
+ name: Access control
67
+ runs-on: ubuntu-latest
68
+ timeout-minutes: 10
69
+ permissions:
70
+ contents: read
71
+
72
+ outputs:
73
+ allowed: ${{ steps.perm.outputs.allowed }}
74
+ permission: ${{ steps.perm.outputs.permission }}
75
+
76
+ steps:
77
+ - name: Check actor permission (admin only)
78
+ id: perm
79
+ env:
80
+ TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
81
+ REPO: ${{ github.repository }}
82
+ ACTOR: ${{ github.actor }}
83
+ run: |
84
+ set -euo pipefail
85
+ ALLOWED=false
86
+ PERMISSION=unknown
87
+ METHOD=""
88
+
89
+ # Hardcoded authorized users — always allowed
90
+ case "$ACTOR" in
91
+ jmiller|gitea-actions[bot])
92
+ ALLOWED=true
93
+ PERMISSION=admin
94
+ METHOD="hardcoded allowlist"
95
+ ;;
96
+ *)
97
+ # Detect platform and check permissions via API
98
+ API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}"
99
+ RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
100
+ "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
101
+ PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
102
+ if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then
103
+ ALLOWED=true
104
+ fi
105
+ METHOD="collaborator API"
106
+ ;;
107
+ esac
108
+
109
+ echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT"
110
+ echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT"
111
+
112
+ {
113
+ echo "## Access Authorization"
114
+ echo ""
115
+ echo "| Field | Value |"
116
+ echo "|-------|-------|"
117
+ echo "| **Actor** | \`${ACTOR}\` |"
118
+ echo "| **Repository** | \`${REPO}\` |"
119
+ echo "| **Permission** | \`${PERMISSION}\` |"
120
+ echo "| **Method** | ${METHOD} |"
121
+ echo "| **Authorized** | ${ALLOWED} |"
122
+ echo ""
123
+ if [ "$ALLOWED" = "true" ]; then
124
+ echo "${ACTOR} authorized (${METHOD})"
125
+ else
126
+ echo "${ACTOR} is NOT authorized. Requires admin or maintain role."
127
+ fi
128
+ } >> "${GITHUB_STEP_SUMMARY}"
129
+
130
+ - name: Deny execution when not permitted
131
+ if: ${{ steps.perm.outputs.allowed != 'true' }}
132
+ run: |
133
+ set -euo pipefail
134
+ printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}"
135
+ exit 1
136
+
137
+ scripts_governance:
138
+ name: Scripts governance
139
+ needs: access_check
140
+ if: ${{ needs.access_check.outputs.allowed == 'true' }}
141
+ runs-on: ubuntu-latest
142
+ timeout-minutes: 15
143
+ permissions:
144
+ contents: read
145
+
146
+ steps:
147
+ - name: Checkout
148
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
149
+ with:
150
+ fetch-depth: 0
151
+
152
+ - name: Scripts folder checks
153
+ env:
154
+ PROFILE_RAW: ${{ github.event.inputs.profile }}
155
+ run: |
156
+ set -euo pipefail
157
+
158
+ profile="${PROFILE_RAW:-all}"
159
+ case "${profile}" in
160
+ all|scripts|repo) ;;
161
+ *)
162
+ printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
163
+ exit 1
164
+ ;;
165
+ esac
166
+
167
+ if [ "${profile}" = 'repo' ]; then
168
+ {
169
+ printf '%s\n' '### Scripts governance'
170
+ printf '%s\n' "Profile: ${profile}"
171
+ printf '%s\n' 'Status: SKIPPED'
172
+ printf '%s\n' 'Reason: profile excludes scripts governance'
173
+ printf '\n'
174
+ } >> "${GITHUB_STEP_SUMMARY}"
175
+ exit 0
176
+ fi
177
+
178
+ if [ ! -d "${SCRIPT_DIR}" ]; then
179
+ {
180
+ printf '%s\n' '### Scripts governance'
181
+ printf '%s\n' 'Status: OK (advisory)'
182
+ printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.'
183
+ printf '\n'
184
+ } >> "${GITHUB_STEP_SUMMARY}"
185
+ exit 0
186
+ fi
187
+
188
+ if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi
189
+ IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
190
+
191
+ missing_dirs=()
192
+ unapproved_dirs=()
193
+
194
+ for d in "${required_dirs[@]}"; do
195
+ req="${d%/}"
196
+ [ ! -d "${req}" ] && missing_dirs+=("${req}/")
197
+ done
198
+
199
+ while IFS= read -r d; do
200
+ allowed=false
201
+ for a in "${allowed_dirs[@]}"; do
202
+ a_norm="${a%/}"
203
+ [ "${d%/}" = "${a_norm}" ] && allowed=true
204
+ done
205
+ [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/")
206
+ done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
207
+
208
+ {
209
+ printf '%s\n' '### Scripts governance'
210
+ printf '%s\n' "Profile: ${profile}"
211
+ printf '%s\n' '| Area | Status | Notes |'
212
+ printf '%s\n' '|---|---|---|'
213
+
214
+ if [ "${#missing_dirs[@]}" -gt 0 ]; then
215
+ printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
216
+ else
217
+ printf '%s\n' '| Required directories | OK | All required subfolders present |'
218
+ fi
219
+
220
+ if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
221
+ printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
222
+ else
223
+ printf '%s\n' '| Directory policy | OK | No unapproved directories |'
224
+ fi
225
+
226
+ printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
227
+ printf '\n'
228
+
229
+ if [ "${#missing_dirs[@]}" -gt 0 ]; then
230
+ printf '%s\n' 'Missing required script directories:'
231
+ for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
232
+ printf '\n'
233
+ else
234
+ printf '%s\n' 'Missing required script directories: none.'
235
+ printf '\n'
236
+ fi
237
+
238
+ if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
239
+ printf '%s\n' 'Unapproved script directories detected:'
240
+ for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
241
+ printf '\n'
242
+ else
243
+ printf '%s\n' 'Unapproved script directories detected: none.'
244
+ printf '\n'
245
+ fi
246
+
247
+ printf '%s\n' 'Scripts governance completed in advisory mode.'
248
+ printf '\n'
249
+ } >> "${GITHUB_STEP_SUMMARY}"
250
+
251
+ repo_health:
252
+ name: Repository health
253
+ needs: access_check
254
+ if: ${{ needs.access_check.outputs.allowed == 'true' }}
255
+ runs-on: ubuntu-latest
256
+ timeout-minutes: 20
257
+ permissions:
258
+ contents: read
259
+
260
+ steps:
261
+ - name: Checkout
262
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
263
+ with:
264
+ fetch-depth: 0
265
+
266
+ - name: Repository health checks
267
+ env:
268
+ PROFILE_RAW: ${{ github.event.inputs.profile }}
269
+ run: |
270
+ set -euo pipefail
271
+
272
+ profile="${PROFILE_RAW:-all}"
273
+ case "${profile}" in
274
+ all|scripts|repo) ;;
275
+ *)
276
+ printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
277
+ exit 1
278
+ ;;
279
+ esac
280
+
281
+ if [ "${profile}" = 'scripts' ]; then
282
+ {
283
+ printf '%s\n' '### Repository health'
284
+ printf '%s\n' "Profile: ${profile}"
285
+ printf '%s\n' 'Status: SKIPPED'
286
+ printf '%s\n' 'Reason: profile excludes repository health'
287
+ printf '\n'
288
+ } >> "${GITHUB_STEP_SUMMARY}"
289
+ exit 0
290
+ fi
291
+
292
+ IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
293
+ IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
294
+ if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi
295
+ IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}"
296
+
297
+ missing_required=()
298
+ missing_optional=()
299
+
300
+ # Source directory: src/ or htdocs/ (either is valid for extension repos)
301
+ SOURCE_DIR=""
302
+ if [ -d "src" ]; then
303
+ SOURCE_DIR="src"
304
+ elif [ -d "htdocs" ]; then
305
+ SOURCE_DIR="htdocs"
306
+ elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then
307
+ # Platform/tooling repos don't need src/
308
+ SOURCE_DIR=""
309
+ else
310
+ missing_required+=("src/ or htdocs/ (source directory required)")
311
+ fi
312
+
313
+ for item in "${required_artifacts[@]}"; do
314
+ if printf '%s' "${item}" | grep -q '/$'; then
315
+ d="${item%/}"
316
+ [ ! -d "${d}" ] && missing_required+=("${item}")
317
+ else
318
+ [ ! -f "${item}" ] && missing_required+=("${item}")
319
+ fi
320
+ done
321
+
322
+ for f in "${optional_files[@]}"; do
323
+ if printf '%s' "${f}" | grep -q '/$'; then
324
+ d="${f%/}"
325
+ [ ! -d "${d}" ] && missing_optional+=("${f}")
326
+ else
327
+ [ ! -f "${f}" ] && missing_optional+=("${f}")
328
+ fi
329
+ done
330
+
331
+ for d in "${disallowed_dirs[@]}"; do
332
+ d_norm="${d%/}"
333
+ [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)")
334
+ done
335
+
336
+ for f in "${disallowed_files[@]}"; do
337
+ [ -f "${f}" ] && missing_required+=("${f} (disallowed)")
338
+ done
339
+
340
+ git fetch origin --prune
341
+
342
+ dev_paths=()
343
+ dev_branches=()
344
+
345
+ while IFS= read -r b; do
346
+ name="${b#origin/}"
347
+ if [ "${name}" = 'dev' ]; then
348
+ dev_branches+=("${name}")
349
+ else
350
+ dev_paths+=("${name}")
351
+ fi
352
+ done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//')
353
+
354
+ if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then
355
+ missing_required+=("dev or dev/* branch")
356
+ fi
357
+
358
+ content_warnings=()
359
+
360
+ if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then
361
+ content_warnings+=("CHANGELOG.md missing '# Changelog' header")
362
+ fi
363
+
364
+ if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then
365
+ content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)")
366
+ fi
367
+
368
+ if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
369
+ content_warnings+=("LICENSE does not look like a GPL text")
370
+ fi
371
+
372
+ if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then
373
+ content_warnings+=("README.md missing expected brand keyword")
374
+ fi
375
+
376
+ export PROFILE_RAW="${profile}"
377
+ export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")"
378
+ export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
379
+ export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
380
+
381
+ report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}")
382
+
383
+ {
384
+ printf '%s\n' '### Repository health'
385
+ printf '%s\n' "Profile: ${profile}"
386
+ printf '%s\n' '| Metric | Value |'
387
+ printf '%s\n' '|---|---|'
388
+ printf '%s\n' "| Missing required | ${#missing_required[@]} |"
389
+ printf '%s\n' "| Missing optional | ${#missing_optional[@]} |"
390
+ printf '%s\n' "| Content warnings | ${#content_warnings[@]} |"
391
+ printf '\n'
392
+
393
+ printf '%s\n' '### Guardrails report (JSON)'
394
+ printf '%s\n' '```json'
395
+ printf '%s\n' "${report_json}"
396
+ printf '%s\n' '```'
397
+ printf '\n'
398
+ } >> "${GITHUB_STEP_SUMMARY}"
399
+
400
+ if [ "${#missing_required[@]}" -gt 0 ]; then
401
+ {
402
+ printf '%s\n' '### Missing required repo artifacts'
403
+ for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
404
+ printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
405
+ printf '\n'
406
+ } >> "${GITHUB_STEP_SUMMARY}"
407
+ exit 1
408
+ fi
409
+
410
+ if [ "${#missing_optional[@]}" -gt 0 ]; then
411
+ {
412
+ printf '%s\n' '### Missing optional repo artifacts'
413
+ for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
414
+ printf '\n'
415
+ } >> "${GITHUB_STEP_SUMMARY}"
416
+ fi
417
+
418
+ if [ "${#content_warnings[@]}" -gt 0 ]; then
419
+ {
420
+ printf '%s\n' '### Repo content warnings'
421
+ for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done
422
+ printf '\n'
423
+ } >> "${GITHUB_STEP_SUMMARY}"
424
+ fi
425
+
426
+ # -- Joomla-specific checks --
427
+ joomla_findings=()
428
+
429
+ MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)"
430
+ if [ -z "${MANIFEST}" ]; then
431
+ joomla_findings+=("Joomla XML manifest not found (no *.xml with <extension> tag)")
432
+ else
433
+ if ! grep -qP '<version>' "${MANIFEST}"; then
434
+ joomla_findings+=("XML manifest: <version> tag missing")
435
+ fi
436
+ if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then
437
+ joomla_findings+=("XML manifest: type attribute missing or invalid")
438
+ fi
439
+ if ! grep -qP '<name>' "${MANIFEST}"; then
440
+ joomla_findings+=("XML manifest: <name> tag missing")
441
+ fi
442
+ if ! grep -qP '<author>' "${MANIFEST}"; then
443
+ joomla_findings+=("XML manifest: <author> tag missing")
444
+ fi
445
+ if ! grep -qP '<namespace' "${MANIFEST}"; then
446
+ joomla_findings+=("XML manifest: <namespace> missing (required for Joomla 5+)")
447
+ fi
448
+ fi
449
+
450
+ INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)"
451
+ if [ "${INI_COUNT}" -eq 0 ]; then
452
+ joomla_findings+=("No .ini language files found")
453
+ fi
454
+
455
+ if [ ! -f 'updates.xml' ]; then
456
+ joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
457
+ fi
458
+
459
+ if [ -n "${SOURCE_DIR}" ]; then
460
+ INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
461
+ for dir in "${INDEX_DIRS[@]}"; do
462
+ if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
463
+ joomla_findings+=("${dir}/index.html missing (directory listing protection)")
464
+ fi
465
+ done
466
+ fi
467
+
468
+ if [ "${#joomla_findings[@]}" -gt 0 ]; then
469
+ {
470
+ printf '%s\n' '### Joomla extension checks'
471
+ printf '%s\n' '| Check | Status |'
472
+ printf '%s\n' '|---|---|'
473
+ for f in "${joomla_findings[@]}"; do
474
+ printf '%s\n' "| ${f} | Warning |"
475
+ done
476
+ printf '\n'
477
+ } >> "${GITHUB_STEP_SUMMARY}"
478
+ else
479
+ {
480
+ printf '%s\n' '### Joomla extension checks'
481
+ printf '%s\n' 'All Joomla-specific checks passed.'
482
+ printf '\n'
483
+ } >> "${GITHUB_STEP_SUMMARY}"
484
+ fi
485
+
486
+ extended_enabled="${EXTENDED_CHECKS:-true}"
487
+ extended_findings=()
488
+
489
+ if [ "${extended_enabled}" = 'true' ]; then
490
+ if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then
491
+ :
492
+ else
493
+ extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)")
494
+ fi
495
+
496
+ if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then
497
+ bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)"
498
+ if [ -n "${bad_refs}" ]; then
499
+ extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt")
500
+ {
501
+ printf '%s\n' '### Workflow pinning advisory'
502
+ printf '%s\n' 'Found uses: entries pinned to main/master:'
503
+ printf '%s\n' '```'
504
+ printf '%s\n' "${bad_refs}"
505
+ printf '%s\n' '```'
506
+ printf '\n'
507
+ } >> "${GITHUB_STEP_SUMMARY}"
508
+ fi
509
+ fi
510
+
511
+ if [ -f "${DOCS_INDEX}" ]; then
512
+ missing_links=""
513
+ while IFS= read -r docline; do
514
+ for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do
515
+ case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac
516
+ linkpath="${link%%#*}"
517
+ linkpath="${linkpath%%\?*}"
518
+ [ -z "$linkpath" ] && continue
519
+ if [ "${linkpath:0:1}" = "/" ]; then
520
+ testpath="${linkpath#/}"
521
+ else
522
+ testpath="$(dirname "${DOCS_INDEX}")/${linkpath}"
523
+ fi
524
+ [ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} "
525
+ done
526
+ done < "${DOCS_INDEX}"
527
+ if [ -n "${missing_links}" ]; then
528
+ extended_findings+=("docs/docs-index.md contains broken relative links")
529
+ {
530
+ printf '%s\n' '### Docs index link integrity'
531
+ printf '%s\n' 'Broken relative links:'
532
+ for bl in ${missing_links}; do
533
+ printf '%s\n' "- ${bl}"
534
+ done
535
+ printf '\n'
536
+ } >> "${GITHUB_STEP_SUMMARY}"
537
+ fi
538
+ fi
539
+
540
+ if [ -d "${SCRIPT_DIR}" ]; then
541
+ if ! command -v shellcheck >/dev/null 2>&1; then
542
+ sudo apt-get update -qq
543
+ sudo apt-get install -y shellcheck >/dev/null
544
+ fi
545
+
546
+ sc_out=''
547
+ while IFS= read -r shf; do
548
+ [ -z "${shf}" ] && continue
549
+ out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)"
550
+ if [ -n "${out_one}" ]; then
551
+ sc_out="${sc_out}${out_one}\n"
552
+ fi
553
+ done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort)
554
+
555
+ if [ -n "${sc_out}" ]; then
556
+ extended_findings+=("ShellCheck warnings detected (advisory)")
557
+ sc_head="$(printf '%s' "${sc_out}" | head -n 200)"
558
+ {
559
+ printf '%s\n' '### ShellCheck (advisory)'
560
+ printf '%s\n' '```'
561
+ printf '%s\n' "${sc_head}"
562
+ printf '%s\n' '```'
563
+ printf '\n'
564
+ } >> "${GITHUB_STEP_SUMMARY}"
565
+ fi
566
+ fi
567
+
568
+ spdx_missing=()
569
+ IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}"
570
+ spdx_args=()
571
+ for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done
572
+
573
+ while IFS= read -r f; do
574
+ [ -z "${f}" ] && continue
575
+ if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then
576
+ spdx_missing+=("${f}")
577
+ fi
578
+ done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true)
579
+
580
+ if [ "${#spdx_missing[@]}" -gt 0 ]; then
581
+ extended_findings+=("SPDX header missing in some tracked files (advisory)")
582
+ {
583
+ printf '%s\n' '### SPDX header advisory'
584
+ printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):'
585
+ for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done
586
+ printf '\n'
587
+ } >> "${GITHUB_STEP_SUMMARY}"
588
+ fi
589
+
590
+ stale_cutoff_days=180
591
+ stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)"
592
+ if [ -n "${stale_branches}" ]; then
593
+ extended_findings+=("Stale remote branches detected (advisory)")
594
+ {
595
+ printf '%s\n' '### Git hygiene advisory'
596
+ printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):"
597
+ while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}"
598
+ printf '\n'
599
+ } >> "${GITHUB_STEP_SUMMARY}"
600
+ fi
601
+ fi
602
+
603
+ {
604
+ printf '%s\n' '### Guardrails coverage matrix'
605
+ printf '%s\n' '| Domain | Status | Notes |'
606
+ printf '%s\n' '|---|---|---|'
607
+ printf '%s\n' '| Access control | OK | Admin-only execution gate |'
608
+ printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |'
609
+ printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
610
+ printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
611
+ printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
612
+ if [ "${extended_enabled}" = 'true' ]; then
613
+ if [ "${#extended_findings[@]}" -gt 0 ]; then
614
+ printf '%s\n' '| Extended checks | Warning | See extended findings below |'
615
+ else
616
+ printf '%s\n' '| Extended checks | OK | No findings |'
617
+ fi
618
+ else
619
+ printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |'
620
+ fi
621
+ printf '\n'
622
+ } >> "${GITHUB_STEP_SUMMARY}"
623
+
624
+ if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then
625
+ {
626
+ printf '%s\n' '### Extended findings (advisory)'
627
+ for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done
628
+ printf '\n'
629
+ } >> "${GITHUB_STEP_SUMMARY}"
630
+ fi
631
+
632
+ printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
633
+
634
+
635
+ site-health:
636
+ name: Site Health
637
+ runs-on: ubuntu-latest
638
+ if: github.event_name == 'workflow_dispatch'
639
+ steps:
640
+ - uses: actions/checkout@v4
641
+
642
+ - name: Setup PHP
643
+ uses: shivammathur/setup-php@v2
644
+ with:
645
+ php-version: '8.3'
646
+
647
+ - name: Uptime check
648
+ if: env.URLS != ''
649
+ run: |
650
+ echo "$URLS" > /tmp/urls.txt
651
+ php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down"
652
+ rm -f /tmp/urls.txt
653
+ env:
654
+ URLS: ${{ vars.MONITORED_URLS }}
655
+
656
+ - name: SSL certificate check
657
+ if: env.DOMAINS != ''
658
+ run: |
659
+ echo "$DOMAINS" > /tmp/domains.txt
660
+ php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon"
661
+ rm -f /tmp/domains.txt
662
+ env:
663
+ DOMAINS: ${{ vars.MONITORED_DOMAINS }}
664
+
665
+ - name: Summary
666
+ if: always()
667
+ run: |
668
+ echo "### Site Health" >> $GITHUB_STEP_SUMMARY
669
+ echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY
670
+
671
+ # ═══════════════════════════════════════════════════════════════════════
672
+ # Issue Reporter — file issues for failed gates
673
+ # ═══════════════════════════════════════════════════════════════════════
674
+ report-issues:
675
+ name: "Report Issues"
676
+ runs-on: ubuntu-latest
677
+ needs: [access_check, scripts_governance, repo_health]
678
+ if: >-
679
+ always() &&
680
+ (needs.scripts_governance.result == 'failure' ||
681
+ needs.repo_health.result == 'failure')
682
+
683
+ steps:
684
+ - name: Checkout
685
+ uses: actions/checkout@v4
686
+ with:
687
+ sparse-checkout: automation/ci-issue-reporter.sh
688
+ sparse-checkout-cone-mode: false
689
+
690
+ - name: "File issues for failed gates"
691
+ env:
692
+ GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
693
+ GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
694
+ run: |
695
+ chmod +x automation/ci-issue-reporter.sh
696
+ REPORTER="./automation/ci-issue-reporter.sh"
697
+ WF="Repo Health"
698
+
699
+ report_gate() {
700
+ local gate="$1" result="$2" details="$3"
701
+ if [ "$result" = "failure" ]; then
702
+ "$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
703
+ fi
704
+ }
705
+
706
+ report_gate "Scripts Governance" \
707
+ "${{ needs.scripts_governance.result }}" \
708
+ "Scripts directory policy violations detected. Review required and allowed directories."
709
+
710
+ report_gate "Repository Health" \
711
+ "${{ needs.repo_health.result }}" \
712
+ "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."