@mokoconsulting/mcp-mokosuite 1.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 (73) hide show
  1. package/.editorconfig +19 -0
  2. package/.gitattributes +94 -0
  3. package/.gitmessage +9 -0
  4. package/.mokogit/ISSUE_TEMPLATE/adr.md +110 -0
  5. package/.mokogit/ISSUE_TEMPLATE/bug_report.md +48 -0
  6. package/.mokogit/ISSUE_TEMPLATE/config.yml +18 -0
  7. package/.mokogit/ISSUE_TEMPLATE/documentation.md +52 -0
  8. package/.mokogit/ISSUE_TEMPLATE/feature_request.md +51 -0
  9. package/.mokogit/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
  10. package/.mokogit/ISSUE_TEMPLATE/mcp_connection_issue.md +69 -0
  11. package/.mokogit/ISSUE_TEMPLATE/mcp_tool_request.md +50 -0
  12. package/.mokogit/ISSUE_TEMPLATE/question.md +82 -0
  13. package/.mokogit/ISSUE_TEMPLATE/rfc.md +126 -0
  14. package/.mokogit/ISSUE_TEMPLATE/security.md +51 -0
  15. package/.mokogit/ISSUE_TEMPLATE/version.md +24 -0
  16. package/.mokogit/actions/resolve-source-dir/action.yml +108 -0
  17. package/.mokogit/workflows/auto-assign.yml +76 -0
  18. package/.mokogit/workflows/auto-bump.yml +78 -0
  19. package/.mokogit/workflows/auto-dev-issue.yml +207 -0
  20. package/.mokogit/workflows/auto-release.yml +596 -0
  21. package/.mokogit/workflows/branch-cleanup.yml +60 -0
  22. package/.mokogit/workflows/cascade-dev.yml +198 -0
  23. package/.mokogit/workflows/changelog-validation.yml +101 -0
  24. package/.mokogit/workflows/ci-generic.yml +203 -0
  25. package/.mokogit/workflows/ci-issue-reporter.yml +75 -0
  26. package/.mokogit/workflows/cleanup.yml +87 -0
  27. package/.mokogit/workflows/gitleaks.yml +94 -0
  28. package/.mokogit/workflows/issue-branch.yml +81 -0
  29. package/.mokogit/workflows/notify.yml +73 -0
  30. package/.mokogit/workflows/npm-build-test.yml +83 -0
  31. package/.mokogit/workflows/npm-publish.yml +126 -0
  32. package/.mokogit/workflows/npm-sdk-check.yml +110 -0
  33. package/.mokogit/workflows/npm-tool-inventory.yml +80 -0
  34. package/.mokogit/workflows/pr-branch-check.yml +90 -0
  35. package/.mokogit/workflows/pr-check.yml +565 -0
  36. package/.mokogit/workflows/pre-release.yml +413 -0
  37. package/.mokogit/workflows/push-notify.yml +43 -0
  38. package/.mokogit/workflows/rc-revert.yml +72 -0
  39. package/.mokogit/workflows/repo-health.yml +700 -0
  40. package/.mokogit/workflows/repository-cleanup.yml +525 -0
  41. package/.mokogit/workflows/standards-compliance.yml +2507 -0
  42. package/.mokogit/workflows/sync-version-on-merge.yml +130 -0
  43. package/.mokogit/workflows/version-set.yml +131 -0
  44. package/CHANGELOG.md +18 -0
  45. package/CLAUDE.md +52 -0
  46. package/CODE_OF_CONDUCT.md +70 -0
  47. package/CONTRIBUTING.md +161 -0
  48. package/LICENSE +696 -0
  49. package/Makefile +70 -0
  50. package/README.md +83 -0
  51. package/SECURITY.md +34 -0
  52. package/config.example.json +21 -0
  53. package/dist/client.d.ts +22 -0
  54. package/dist/client.js +124 -0
  55. package/dist/config.d.ts +4 -0
  56. package/dist/config.js +53 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +181 -0
  59. package/dist/signing.d.ts +14 -0
  60. package/dist/signing.js +62 -0
  61. package/dist/types.d.ts +44 -0
  62. package/dist/types.js +16 -0
  63. package/docs/API.md +63 -0
  64. package/docs/ARCHITECTURE.md +73 -0
  65. package/docs/INSTALLATION.md +102 -0
  66. package/docs/index.md +12 -0
  67. package/package.json +35 -0
  68. package/src/client.ts +142 -0
  69. package/src/config.ts +63 -0
  70. package/src/index.ts +336 -0
  71. package/src/signing.ts +67 -0
  72. package/src/types.ts +63 -0
  73. package/tsconfig.json +19 -0
@@ -0,0 +1,198 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ #
5
+ # FILE INFORMATION
6
+ # DEFGROUP: MokoGIT.Workflow
7
+ # INGROUP: MokoCLI.Cascade
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
+ # PATH: /.mokogit/workflows/cascade-dev.yml
10
+ # VERSION: 02.01.00
11
+ # BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
12
+
13
+ name: "Cascade Main -> Dev"
14
+
15
+ on:
16
+ push:
17
+ branches:
18
+ - main
19
+ # A sync-only push to main (workflow/CI-config files) should not immediately
20
+ # cascade to dev and light up the release pipeline. The daily schedule below
21
+ # still carries the .mokogit drift down to dev.
22
+ paths-ignore:
23
+ - '.mokogit/**'
24
+ - '.gitea/**'
25
+ - '.github/**'
26
+ # Daily safety net: catches drift even when main only received [skip ci] pushes
27
+ # (which never fire the push trigger above), and .mokogit-only syncs ignored above.
28
+ # Off-round minute to avoid a fleet-wide spike.
29
+ schedule:
30
+ - cron: '23 7 * * *'
31
+ workflow_dispatch:
32
+
33
+ permissions:
34
+ contents: write
35
+ pull-requests: write
36
+
37
+ env:
38
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
39
+ # ntfy destination is configured via repo or org variables (org vars are inherited).
40
+ NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
41
+ NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'git-releases' }}
42
+
43
+ jobs:
44
+ cascade:
45
+ name: Cascade main -> dev
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - name: Checkout (full history for merge/resolve)
49
+ uses: actions/checkout@v4
50
+ with:
51
+ fetch-depth: 0
52
+ token: ${{ github.token }}
53
+
54
+ - name: Cascade main -> dev (auto-resolve version stamps, else notify)
55
+ env:
56
+ TOKEN: ${{ github.token }}
57
+ REPO: ${{ github.repository }}
58
+ run: |
59
+ set -uo pipefail
60
+ API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
61
+ AUTH="Authorization: token ${TOKEN}"
62
+ jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
63
+
64
+ # 0. dev must exist
65
+ if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
66
+ echo "No dev branch - nothing to cascade."; exit 0
67
+ fi
68
+
69
+ # 1. is main ahead of dev?
70
+ AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
71
+ | python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
72
+ if [ "${AHEAD:-0}" -eq 0 ]; then
73
+ echo "dev already up to date with main."; exit 0
74
+ fi
75
+ echo "main is ${AHEAD} commit(s) ahead of dev."
76
+
77
+ # 2. reuse an open main->dev PR, else create one
78
+ PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
79
+ | python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
80
+ if [ -z "$PR" ]; then
81
+ RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
82
+ -d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
83
+ PR=$(printf '%s' "$RESP" | jqget number)
84
+ if [ -z "$PR" ]; then
85
+ echo "::warning::Could not open cascade PR: $RESP"; exit 0
86
+ fi
87
+ echo "Opened cascade PR #${PR}"
88
+ else
89
+ echo "Reusing open cascade PR #${PR}"
90
+ fi
91
+
92
+ notify() {
93
+ curl -sS \
94
+ -H "Title: ${REPO}: dev cascade needs manual merge" \
95
+ -H "Tags: warning,twisted_rightwards_arrows" \
96
+ -H "Priority: high" \
97
+ -H "Click: ${MOKOGIT_URL}/${REPO}/pulls/${PR}" \
98
+ -d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
99
+ "${NTFY_URL}/${NTFY_TOPIC}" || true
100
+ }
101
+
102
+ # 3. wait for MokoGIT to compute mergeability (conflict detection)
103
+ MERGEABLE=""
104
+ for _ in 1 2 3 4 5 6; do
105
+ MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
106
+ case "$MERGEABLE" in True|False) break ;; esac
107
+ sleep 3
108
+ done
109
+ echo "mergeable=${MERGEABLE}"
110
+
111
+ # 4a. conflict-free -> merge via API (existing behaviour)
112
+ if [ "$MERGEABLE" = "True" ]; then
113
+ CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
114
+ -X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
115
+ if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
116
+ echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
117
+ exit 0
118
+ fi
119
+ echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
120
+ notify "could not be auto-merged (HTTP ${CODE})."
121
+ exit 0
122
+ fi
123
+
124
+ # 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
125
+ echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
126
+ git config user.name "MokoGIT Cascade"
127
+ git config user.email "actions@mokoconsulting.tech"
128
+ git fetch --quiet origin main dev
129
+ git checkout -B dev origin/dev
130
+
131
+ if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
132
+ # Became clean at git level (e.g. mergeability was still computing) -> commit + push.
133
+ git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
134
+ git push origin dev
135
+ echo "Cascade merged cleanly at git level and pushed to dev."
136
+ exit 0
137
+ fi
138
+
139
+ CONFLICTS=$(git diff --name-only --diff-filter=U)
140
+ echo "Conflicted files:"; echo "${CONFLICTS}"
141
+
142
+ # A conflict is "stamp-only" when every line inside every conflict block matches
143
+ # a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
144
+ is_stamp_only() {
145
+ awk '
146
+ /^<<<<<<< / { inc=1; next }
147
+ inc && /^=======$/ { next }
148
+ /^>>>>>>> / { inc=0; next }
149
+ inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
150
+ END { exit(bad ? 1 : 0) }
151
+ ' "$1"
152
+ }
153
+ # Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
154
+ # preserving all auto-merged content around them.
155
+ keep_ours() {
156
+ awk '
157
+ /^<<<<<<< / { inc=1; side="ours"; next }
158
+ inc && /^=======$/ { side="theirs"; next }
159
+ /^>>>>>>> / { inc=0; next }
160
+ { if (!inc) { print; next } if (side=="ours") print }
161
+ ' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
162
+ }
163
+
164
+ ALL_STAMP=1
165
+ for f in ${CONFLICTS}; do
166
+ if ! is_stamp_only "$f"; then
167
+ echo "::notice::$f has non-stamp conflicts -> manual resolution required."
168
+ ALL_STAMP=0; break
169
+ fi
170
+ done
171
+
172
+ if [ "$ALL_STAMP" != "1" ]; then
173
+ git merge --abort || true
174
+ notify "has non-version-stamp conflicts and cannot be auto-resolved."
175
+ exit 0
176
+ fi
177
+
178
+ echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
179
+ for f in ${CONFLICTS}; do
180
+ keep_ours "$f"
181
+ git add "$f"
182
+ done
183
+
184
+ # Best-effort: normalise stamps to dev's version if mokocli is available.
185
+ if [ -f /opt/mokocli/cli/version_check.php ]; then
186
+ php /opt/mokocli/cli/version_check.php --fix || true
187
+ git add -A
188
+ fi
189
+
190
+ git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
191
+ git push origin dev
192
+ echo "Cascade auto-resolved and pushed to dev."
193
+
194
+ # Close the now-redundant PR (its changes are in dev) with an explanatory comment.
195
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
196
+ -d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
197
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
198
+ -d '{"state":"closed"}' >/dev/null || true
@@ -0,0 +1,101 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # This file is part of a Moko Consulting project.
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ #
7
+ # FILE INFORMATION
8
+ # DEFGROUP: GitHub.Workflow.Template
9
+ # INGROUP: MokoStandards.CI
10
+ # REPO: https://github.com/mokoconsulting-tech/MokoStandards
11
+ # PATH: /templates/workflows/shared/changelog-validation.yml.template
12
+ # VERSION: 04.06.00
13
+ # BRIEF: Validates CHANGELOG.md format and version consistency
14
+ # NOTE: Deployed to .mokogit/workflows/changelog-validation.yml in governed repos.
15
+
16
+ name: "Universal: Changelog Validation"
17
+
18
+ on:
19
+ push:
20
+ branches:
21
+ - main
22
+ pull_request:
23
+ branches:
24
+ - main
25
+ workflow_dispatch:
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ env:
31
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
32
+
33
+ jobs:
34
+ validate-changelog:
35
+ name: Validate CHANGELOG.md
36
+ runs-on: ubuntu-latest
37
+
38
+ steps:
39
+ - name: Checkout repository
40
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
41
+
42
+ - name: Check CHANGELOG.md exists
43
+ run: |
44
+ echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
45
+ if [ ! -f "CHANGELOG.md" ]; then
46
+ echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
47
+ exit 1
48
+ fi
49
+ echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
50
+
51
+ - name: Check VERSION header matches README.md
52
+ run: |
53
+ # Extract version from README.md FILE INFORMATION block
54
+ README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
55
+ if [ -z "$README_VERSION" ]; then
56
+ echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
57
+ exit 1
58
+ fi
59
+
60
+ # Check that CHANGELOG.md has a matching version header
61
+ CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1)
62
+ if [ -z "$CHANGELOG_VERSION" ]; then
63
+ echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
64
+ exit 1
65
+ fi
66
+
67
+ if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
68
+ echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
69
+ exit 1
70
+ fi
71
+
72
+ echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
73
+
74
+ - name: Validate conventional changelog format
75
+ run: |
76
+ ERRORS=0
77
+
78
+ # Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
79
+ while IFS= read -r LINE; do
80
+ if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
81
+ echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY
82
+ echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY
83
+ ERRORS=$((ERRORS + 1))
84
+ fi
85
+ done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
86
+
87
+ ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0")
88
+ if [ "$ENTRY_COUNT" -eq 0 ]; then
89
+ echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
90
+ ERRORS=$((ERRORS + 1))
91
+ else
92
+ echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
93
+ fi
94
+
95
+ echo "" >> $GITHUB_STEP_SUMMARY
96
+ if [ "${ERRORS}" -gt 0 ]; then
97
+ echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
98
+ exit 1
99
+ else
100
+ echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
101
+ fi
@@ -0,0 +1,203 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ #
5
+ # FILE INFORMATION
6
+ # DEFGROUP: MokoGIT.Workflow
7
+ # INGROUP: MokoCLI.CI
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
+ # PATH: /.mokogit/workflows/ci-generic.yml
10
+ # VERSION: 01.00.00
11
+ # BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
12
+
13
+ name: "Generic: Project CI"
14
+
15
+ on:
16
+ pull_request:
17
+ branches:
18
+ - main
19
+ - dev
20
+ - dev/**
21
+ - rc/**
22
+ workflow_dispatch:
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ env:
28
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
29
+
30
+ jobs:
31
+ # ── Lint & Validate ───────────────────────────────────────────────────
32
+ lint:
33
+ name: Lint & Validate
34
+ runs-on: ubuntu-latest
35
+ # Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
36
+ if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
37
+
38
+ steps:
39
+ - name: Checkout
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Detect toolchain
43
+ id: detect
44
+ run: |
45
+ HAS_PHP=false
46
+ HAS_NODE=false
47
+ [ -f "composer.json" ] && HAS_PHP=true
48
+ [ -f "package.json" ] && HAS_NODE=true
49
+ echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
50
+ echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
51
+ echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
52
+
53
+ - name: Setup PHP
54
+ if: steps.detect.outputs.has_php == 'true'
55
+ run: |
56
+ if ! command -v php &> /dev/null; then
57
+ sudo apt-get update -qq
58
+ sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
59
+ fi
60
+ php -v
61
+
62
+ - name: Setup Node.js
63
+ if: steps.detect.outputs.has_node == 'true'
64
+ uses: actions/setup-node@v4
65
+ with:
66
+ node-version: '20'
67
+
68
+ - name: Install PHP dependencies
69
+ if: steps.detect.outputs.has_php == 'true'
70
+ run: |
71
+ if [ -f "composer.json" ]; then
72
+ composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
73
+ fi
74
+
75
+ - name: Install Node.js dependencies
76
+ if: steps.detect.outputs.has_node == 'true'
77
+ run: |
78
+ if [ -f "package.json" ]; then
79
+ npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
80
+ fi
81
+
82
+ - name: PHP syntax check
83
+ if: steps.detect.outputs.has_php == 'true'
84
+ run: |
85
+ ERRORS=0
86
+ while IFS= read -r -d '' file; do
87
+ if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
88
+ echo "::error file=${file}::PHP syntax error"
89
+ ERRORS=$((ERRORS + 1))
90
+ fi
91
+ done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
92
+
93
+ echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
94
+ if [ "$ERRORS" -eq 0 ]; then
95
+ echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
96
+ else
97
+ echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
98
+ exit 1
99
+ fi
100
+
101
+ - name: TypeScript/JavaScript lint
102
+ if: steps.detect.outputs.has_node == 'true'
103
+ run: |
104
+ if [ -f "node_modules/.bin/eslint" ]; then
105
+ npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
106
+ echo "## ESLint" >> $GITHUB_STEP_SUMMARY
107
+ echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
108
+ elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
109
+ echo "::warning::ESLint config found but eslint not installed"
110
+ else
111
+ echo "No ESLint configured — skipping"
112
+ fi
113
+
114
+ - name: TypeScript compile check
115
+ if: steps.detect.outputs.has_node == 'true'
116
+ run: |
117
+ if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
118
+ npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
119
+ echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
120
+ echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
121
+ fi
122
+
123
+ - name: PHPStan static analysis
124
+ if: steps.detect.outputs.has_php == 'true'
125
+ run: |
126
+ if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
127
+ vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
128
+ fi
129
+
130
+ # ── Tests ─────────────────────────────────────────────────────────────
131
+ test:
132
+ name: Tests
133
+ runs-on: ubuntu-latest
134
+ # Independent job (no `needs: lint`): the MokoGIT Actions scheduler does not
135
+ # offer the dependent 2nd job of a needs-chain to runners, so it stalls in
136
+ # "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos
137
+ # directly (same condition lint uses) instead of gating on lint's result.
138
+ if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
139
+
140
+ steps:
141
+ - name: Checkout
142
+ uses: actions/checkout@v4
143
+
144
+ - name: Detect toolchain
145
+ id: detect
146
+ run: |
147
+ HAS_PHP=false
148
+ HAS_NODE=false
149
+ [ -f "composer.json" ] && HAS_PHP=true
150
+ [ -f "package.json" ] && HAS_NODE=true
151
+ echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
152
+ echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
153
+
154
+ - name: Setup PHP
155
+ if: steps.detect.outputs.has_php == 'true'
156
+ run: |
157
+ if ! command -v php &> /dev/null; then
158
+ sudo apt-get update -qq
159
+ sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
160
+ fi
161
+
162
+ - name: Setup Node.js
163
+ if: steps.detect.outputs.has_node == 'true'
164
+ uses: actions/setup-node@v4
165
+ with:
166
+ node-version: '20'
167
+
168
+ - name: Install dependencies
169
+ run: |
170
+ [ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
171
+ [ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; } || true
172
+
173
+ - name: Run PHP tests
174
+ if: steps.detect.outputs.has_php == 'true'
175
+ run: |
176
+ if [ -f "vendor/bin/phpunit" ]; then
177
+ vendor/bin/phpunit --testdox 2>&1
178
+ echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
179
+ echo "Tests passed." >> $GITHUB_STEP_SUMMARY
180
+ elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
181
+ echo "::warning::PHPUnit config found but phpunit not installed"
182
+ else
183
+ echo "No PHPUnit configured — skipping"
184
+ fi
185
+
186
+ - name: Run Node.js tests
187
+ if: steps.detect.outputs.has_node == 'true'
188
+ run: |
189
+ if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
190
+ npm test 2>&1
191
+ echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
192
+ echo "Tests passed." >> $GITHUB_STEP_SUMMARY
193
+ else
194
+ echo "No test script in package.json — skipping"
195
+ fi
196
+
197
+ - name: Build check
198
+ run: |
199
+ if [ -f "Makefile" ]; then
200
+ make build 2>&1 || echo "::warning::Build failed or not configured"
201
+ elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
202
+ npm run build 2>&1 || echo "::warning::Build failed"
203
+ fi
@@ -0,0 +1,75 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ #
5
+ # FILE INFORMATION
6
+ # DEFGROUP: MokoGIT.Workflow
7
+ # INGROUP: MokoCLI.Universal
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
+ # PATH: /.mokogit/workflows/ci-issue-reporter.yml
10
+ # VERSION: 01.00.00
11
+ # BRIEF: Reusable workflow � creates/updates a MokoGIT issue when a CI gate fails.
12
+ # Clones MokoCLI and runs cli/ci_issue_reporter.sh.
13
+
14
+ name: "Universal: CI Issue Reporter"
15
+
16
+ on:
17
+ workflow_call:
18
+ inputs:
19
+ gate:
20
+ description: "CI gate name (e.g. PR Validation, Repository Health)"
21
+ required: true
22
+ type: string
23
+ details:
24
+ description: "Human-readable failure description"
25
+ required: true
26
+ type: string
27
+ severity:
28
+ description: "error or warning"
29
+ required: false
30
+ type: string
31
+ default: "error"
32
+ workflow:
33
+ description: "Workflow name for the issue title"
34
+ required: false
35
+ type: string
36
+ default: ""
37
+ secrets:
38
+ MOKOGIT_TOKEN:
39
+ required: true
40
+
41
+ env:
42
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
43
+
44
+ jobs:
45
+ report:
46
+ name: "Report: ${{ inputs.gate }}"
47
+ runs-on: ubuntu-latest
48
+
49
+ steps:
50
+ - name: Clone MokoCLI
51
+ env:
52
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
53
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
54
+ run: |
55
+ git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
56
+ cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
57
+
58
+ - name: Report CI failure
59
+ env:
60
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
61
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
62
+ # Route all workflow_call inputs through env vars — referencing ${{ inputs.* }}
63
+ # directly in a run: block interpolates at YAML-parse time and is a shell-injection
64
+ # vector. Shell variables are not re-parsed, so a crafted input can't break out.
65
+ INPUT_GATE: ${{ inputs.gate }}
66
+ INPUT_DETAILS: ${{ inputs.details }}
67
+ INPUT_SEVERITY: ${{ inputs.severity }}
68
+ INPUT_WORKFLOW: ${{ inputs.workflow }}
69
+ run: |
70
+ chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
71
+ /tmp/mokocli/cli/ci_issue_reporter.sh \
72
+ --gate "$INPUT_GATE" \
73
+ --details "$INPUT_DETAILS" \
74
+ --severity "$INPUT_SEVERITY" \
75
+ --workflow "$INPUT_WORKFLOW"
@@ -0,0 +1,87 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ #
5
+ # FILE INFORMATION
6
+ # DEFGROUP: MokoGIT.Workflow
7
+ # INGROUP: MokoCLI.Maintenance
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
9
+ # PATH: /.mokogit/workflows/cleanup.yml
10
+ # VERSION: 01.00.00
11
+ # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
12
+
13
+ name: "Universal: Repository Cleanup"
14
+
15
+ on:
16
+ schedule:
17
+ - cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: write
22
+
23
+ env:
24
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
25
+
26
+ jobs:
27
+ cleanup:
28
+ name: Clean Merged Branches
29
+ runs-on: ubuntu-latest
30
+
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@v4
34
+ with:
35
+ fetch-depth: 0
36
+ token: ${{ secrets.MOKOGIT_TOKEN }}
37
+
38
+ - name: Delete merged branches
39
+ env:
40
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
41
+ run: |
42
+ echo "=== Merged Branch Cleanup ==="
43
+ API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
44
+
45
+ # List branches via API
46
+ BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
47
+ "${API}/branches?limit=50" | jq -r '.[].name')
48
+
49
+ DELETED=0
50
+ for BRANCH in $BRANCHES; do
51
+ # Skip protected branches
52
+ case "$BRANCH" in
53
+ main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
54
+ esac
55
+
56
+ # Check if branch is merged into main
57
+ if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
58
+ echo " Deleting merged branch: ${BRANCH}"
59
+ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
60
+ "${API}/branches/${BRANCH}" 2>/dev/null || true
61
+ DELETED=$((DELETED + 1))
62
+ fi
63
+ done
64
+
65
+ echo "Deleted ${DELETED} merged branch(es)"
66
+
67
+ - name: Clean old workflow runs
68
+ env:
69
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
70
+ run: |
71
+ echo "=== Workflow Run Cleanup ==="
72
+ API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
73
+ CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
74
+
75
+ # Get old completed runs
76
+ RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
77
+ "${API}/actions/runs?status=completed&limit=50" | \
78
+ jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
79
+
80
+ DELETED=0
81
+ for RUN_ID in $RUNS; do
82
+ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
83
+ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
84
+ DELETED=$((DELETED + 1))
85
+ done
86
+
87
+ echo "Deleted ${DELETED} old workflow run(s)"