@mokoconsulting/mokoabs-mcp 0.0.0-stage → 4.0.2

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 (51) hide show
  1. package/.editorconfig +41 -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 +72 -0
  17. package/.mokogit/workflows/apidocs.yml +132 -0
  18. package/.mokogit/workflows/auto-assign.yml +76 -0
  19. package/.mokogit/workflows/auto-dev-issue.yml +207 -0
  20. package/.mokogit/workflows/auto-release.yml +1787 -0
  21. package/.mokogit/workflows/branch-cleanup.yml +63 -0
  22. package/.mokogit/workflows/cascade.yml +645 -0
  23. package/.mokogit/workflows/ci-issue-reporter.yml +100 -0
  24. package/.mokogit/workflows/cleanup.yml +112 -0
  25. package/.mokogit/workflows/makefile-lint.yml +240 -0
  26. package/.mokogit/workflows/npm-build-test.yml +507 -0
  27. package/.mokogit/workflows/npm-sdk-check.yml +110 -0
  28. package/.mokogit/workflows/npm-tool-inventory.yml +81 -0
  29. package/.mokogit/workflows/pr-branch-check.yml +92 -0
  30. package/.mokogit/workflows/pre-release.yml +1134 -0
  31. package/.mokogit/workflows/rc-revert.yml +75 -0
  32. package/AGENTS.md +48 -0
  33. package/CHANGELOG.md +37 -0
  34. package/CODE_OF_CONDUCT.md +70 -0
  35. package/CONTRIBUTING.md +164 -0
  36. package/LICENSE +696 -0
  37. package/Makefile +94 -0
  38. package/README.md +109 -2
  39. package/SECURITY.md +245 -0
  40. package/build/README.md +7 -0
  41. package/dist/README.md +7 -0
  42. package/dist/index.js +153 -0
  43. package/package.json +34 -5
  44. package/sample/config.example.json +15 -0
  45. package/source/client.ts +132 -0
  46. package/source/config.ts +139 -0
  47. package/source/credentialStore.ts +216 -0
  48. package/source/index.ts +235 -0
  49. package/source/tokenReader.ts +120 -0
  50. package/source/types.ts +63 -0
  51. package/tsconfig.json +19 -0
@@ -0,0 +1,100 @@
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: false
40
+
41
+ env:
42
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
43
+
44
+ permissions:
45
+ contents: read
46
+ issues: write
47
+
48
+ jobs:
49
+ report:
50
+ name: "Report: ${{ inputs.gate }}"
51
+ runs-on: ubuntu-latest
52
+
53
+ steps:
54
+ - name: Setup MokoCLI tools
55
+ run: |
56
+ # Standard mokocli install: FRESH from the MokoGIT Composer registry every
57
+ # run (PRIMARY); /opt/mokocli is a FAILSAFE only (its sync unit is disabled,
58
+ # so it rots). Registry path /api/packages/** is anon-allowlisted (no token)
59
+ # and reliable — unlike the former anonymous git clone, which REQUIRE_SIGNIN
60
+ # can block. Installs to /tmp/mokocli (the path this workflow's steps use).
61
+ if ! command -v composer > /dev/null 2>&1; then
62
+ sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 || true
63
+ fi
64
+ rm -rf /tmp/mokocli
65
+ REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
66
+ if composer create-project --no-install --no-interaction --no-progress --quiet \
67
+ --repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
68
+ && [ -f /tmp/mokocli/cli/version_bump.php ]; then
69
+ echo "Installed mokocli fresh from the Composer registry (primary)"
70
+ echo "MOKO_CLI=/tmp/mokocli/cli" >> $GITHUB_ENV
71
+ if command -v sudo >/dev/null 2>&1 && [ -d /opt/mokocli ] \
72
+ && ! cmp -s /tmp/mokocli/cli/version_reset_dev.php /opt/mokocli/cli/version_reset_dev.php 2>/dev/null; then
73
+ sudo cp -a /tmp/mokocli/. /opt/mokocli/ 2>/dev/null && echo "Refreshed /opt/mokocli failsafe from the fresh registry copy" || true
74
+ fi
75
+ elif [ -f /opt/mokocli/cli/version_bump.php ]; then
76
+ echo "::warning::Registry install failed — using /opt/mokocli FAILSAFE (may be stale)"
77
+ echo "MOKO_CLI=/opt/mokocli/cli" >> $GITHUB_ENV
78
+ [ -d /tmp/mokocli ] || cp -a /opt/mokocli /tmp/mokocli 2>/dev/null || true
79
+ else
80
+ echo "::warning::mokocli unavailable — issue-reporter steps may be degraded"
81
+ fi
82
+
83
+ - name: Report CI failure
84
+ env:
85
+ MOKOGIT_TOKEN: ${{ github.token }}
86
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
87
+ # Route all workflow_call inputs through env vars — referencing ${{ inputs.* }}
88
+ # directly in a run: block interpolates at YAML-parse time and is a shell-injection
89
+ # vector. Shell variables are not re-parsed, so a crafted input can't break out.
90
+ INPUT_GATE: ${{ inputs.gate }}
91
+ INPUT_DETAILS: ${{ inputs.details }}
92
+ INPUT_SEVERITY: ${{ inputs.severity }}
93
+ INPUT_WORKFLOW: ${{ inputs.workflow }}
94
+ run: |
95
+ chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
96
+ /tmp/mokocli/cli/ci_issue_reporter.sh \
97
+ --gate "$INPUT_GATE" \
98
+ --details "$INPUT_DETAILS" \
99
+ --severity "$INPUT_SEVERITY" \
100
+ --workflow "$INPUT_WORKFLOW"
@@ -0,0 +1,112 @@
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/Template-Generic
9
+ # PATH: /.mokogit/workflows/cleanup.yml
10
+ # VERSION: 01.01.00
11
+ # CHANGE (01.01.00): fail-open branch/run cleanup (#430) — the run token may lack REST read on some repos, returning an error OBJECT; `jq '.[]'` on it exit-5'd the job under pipefail (fleet-wide red). Guard type=="array" / `(.workflow_runs // [])` and skip gracefully; cleanup is best-effort housekeeping and must never red the run.
12
+ # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
13
+
14
+ name: "Universal: Repository Cleanup"
15
+
16
+ on:
17
+ schedule:
18
+ - cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: write
23
+ actions: write
24
+
25
+ env:
26
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
27
+
28
+ jobs:
29
+ cleanup:
30
+ name: Clean Merged Branches
31
+ runs-on: ubuntu-latest
32
+
33
+ steps:
34
+ - name: Checkout
35
+ uses: actions/checkout@v4
36
+ with:
37
+ fetch-depth: 0
38
+ token: ${{ github.token }}
39
+
40
+ - name: Delete merged branches
41
+ env:
42
+ MOKOGIT_TOKEN: ${{ github.token }}
43
+ run: |
44
+ echo "=== Merged Branch Cleanup ==="
45
+ API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
46
+
47
+ # List branches via API. FAIL-OPEN (#430): the run token may lack REST read
48
+ # on some repos, so the API returns an error OBJECT (not an array); piping
49
+ # that into `jq '.[]'` errors (exit 5) and, under `set -o pipefail`, fails the
50
+ # whole job. Guard type=="array" and skip gracefully — branch cleanup is
51
+ # best-effort housekeeping and must never red the run.
52
+ BR_RESP=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
53
+ "${API}/branches?limit=50" 2>/dev/null || true)
54
+ BRANCHES=$(printf '%s' "$BR_RESP" | jq -r 'if type=="array" then .[].name else empty end' 2>/dev/null || true)
55
+ if [ -z "$BRANCHES" ]; then
56
+ echo "::warning::branch cleanup: could not list branches (token/API); skipping (not a failure)."
57
+ exit 0
58
+ fi
59
+
60
+ # Head refs of every OPEN pull request — never delete one of these. A
61
+ # branch can be an ancestor of main yet still back an open PR (e.g. the
62
+ # native sync engine's moko-sync/* review PRs); deleting it orphans the PR.
63
+ OPEN_PR_HEADS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
64
+ "${API}/pulls?state=open&limit=100" 2>/dev/null \
65
+ | jq -r 'if type=="array" then .[].head.ref else empty end' 2>/dev/null || true)
66
+
67
+ DELETED=0
68
+ for BRANCH in $BRANCHES; do
69
+ # Skip protected branches
70
+ case "$BRANCH" in
71
+ main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
72
+ esac
73
+
74
+ # Skip any branch that backs an open PR
75
+ if echo "${OPEN_PR_HEADS}" | grep -qxF "${BRANCH}"; then
76
+ echo " Skipping (open PR): ${BRANCH}"
77
+ continue
78
+ fi
79
+
80
+ # Check if branch is merged into main
81
+ if git merge-base --is-ancestor "origin/${BRANCH}" "origin/${{ github.event.repository.default_branch }}" 2>/dev/null; then
82
+ echo " Deleting merged branch: ${BRANCH}"
83
+ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
84
+ "${API}/branches/${BRANCH}" 2>/dev/null || true
85
+ DELETED=$((DELETED + 1))
86
+ fi
87
+ done
88
+
89
+ echo "Deleted ${DELETED} merged branch(es)"
90
+
91
+ - name: Clean old workflow runs
92
+ env:
93
+ MOKOGIT_TOKEN: ${{ github.token }}
94
+ run: |
95
+ echo "=== Workflow Run Cleanup ==="
96
+ API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
97
+ CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
98
+
99
+ # Get old completed runs. FAIL-OPEN (#430): `(.workflow_runs // [])` guards a
100
+ # non-array/error response so `jq` can't exit-5 the job under pipefail.
101
+ RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
102
+ "${API}/actions/runs?status=completed&limit=50" 2>/dev/null \
103
+ | jq -r "(.workflow_runs // []) | .[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null || true)
104
+
105
+ DELETED=0
106
+ for RUN_ID in $RUNS; do
107
+ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
108
+ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
109
+ DELETED=$((DELETED + 1))
110
+ done
111
+
112
+ echo "Deleted ${DELETED} old workflow run(s)"
@@ -0,0 +1,240 @@
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.Validation
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
+ # PATH: /.mokogit/workflows/makefile-lint.yml
10
+ # VERSION: 01.02.00
11
+ # BRIEF: Makefile sanity lint -- identity fields + release contract (SRC_DIR, PROJECT_VERSION format, BUILD_DIR, required targets, parse). Release-contract checks are WARN by default; set vars.MAKEFILE_LINT_ENFORCE=enforce to hard-fail (JOOMLA_I18N_ENFORCE-style, per-repo opt-in).
12
+ #
13
+ # +========================================================================+
14
+ # | MAKEFILE IDENTITY LINT |
15
+ # +========================================================================+
16
+ # | |
17
+ # | The release pipeline reads project identity from the Makefile |
18
+ # | (PROJECT_VERSION drives the version; PROJECT_AUTHOR is stamped into |
19
+ # | Joomla <author> at build -- see MokoCLI #803 / #289). A missing field |
20
+ # | degrades silently (the build just omits it), so this lints the |
21
+ # | Makefile on every PR and surfaces gaps early. |
22
+ # | |
23
+ # | Tiering (so a fleet-wide sync does NOT redden CI everywhere): |
24
+ # | - EXEMPT: a Makefile with NO PROJECT_* fields (foreign/native, e.g. |
25
+ # | MokoGIT=gitea, MokoNtfy=ntfy) is skipped, not failed. |
26
+ # | - HARD FAIL: no Makefile, or PROJECT_NAME / PROJECT_VERSION missing |
27
+ # | or empty (long-established, every repo already has them). |
28
+ # | - WARN only: PROJECT_AUTHOR missing (newly required -- grace period), |
29
+ # | or an identity value left at a scaffold placeholder. |
30
+ # | - PLATFORM is validated only IF present (it is platform-specific; |
31
+ # | generic repos derive platform from .mokogit/manifest.xml). |
32
+ # | |
33
+ # +========================================================================+
34
+
35
+ name: "Universal: Makefile Identity Lint"
36
+
37
+ on:
38
+ pull_request:
39
+ workflow_dispatch:
40
+
41
+ permissions:
42
+ contents: read
43
+
44
+ jobs:
45
+ makefile-identity:
46
+ name: Makefile Identity Lint
47
+ runs-on: ubuntu-latest
48
+
49
+ steps:
50
+ - name: Checkout
51
+ uses: actions/checkout@v4
52
+
53
+ - name: Lint Makefile identity block
54
+ env:
55
+ # Opt-in hard-fail for the release-contract checks (SRC_DIR, parse),
56
+ # mirroring the JOOMLA_I18N_ENFORCE rollout: unset => WARN only (a
57
+ # fleet-wide sync never reddens anyone), enforce|true|1 => hard fail.
58
+ MAKEFILE_LINT_ENFORCE: ${{ vars.MAKEFILE_LINT_ENFORCE }}
59
+ # Mirror the pre-release #469 SRC_DIR gate opt-out: electron-style repos
60
+ # set this "off" (always-bump) and are not required to declare SRC_DIR.
61
+ RELEASE_SRCDIR_GATE: ${{ vars.RELEASE_SRCDIR_GATE }}
62
+ run: |
63
+ # The runner shell is `bash -e -o pipefail`. `read_field` greps the
64
+ # Makefile, and a MISSING field makes grep exit 1 -> pipefail would abort
65
+ # the whole script (set -e) before the warn/heal logic runs. Disable
66
+ # pipefail here so a grep non-match is treated as "field absent", not a
67
+ # fatal error (the field-presence logic below handles absence explicitly).
68
+ set +o pipefail
69
+ echo "### Makefile Identity Lint" >> "$GITHUB_STEP_SUMMARY"
70
+
71
+ if [ ! -f Makefile ]; then
72
+ echo "::notice::No Makefile in repo root -- skipping identity lint (nothing to release from a Makefile)."
73
+ echo "No Makefile -- skipped." >> "$GITHUB_STEP_SUMMARY"
74
+ exit 0
75
+ fi
76
+
77
+ # Exempt foreign/native Makefiles that don't participate in the Moko
78
+ # identity convention at all (no PROJECT_* fields) -- e.g. MokoGIT ships
79
+ # the upstream gitea Makefile and MokoNtfy the ntfy Makefile. A
80
+ # Moko-standard Makefile always declares at least one PROJECT_* field,
81
+ # so a half-configured standard Makefile (has some PROJECT_* but is
82
+ # missing NAME/VERSION) is still caught below; only genuinely-foreign
83
+ # Makefiles are skipped, so a fleet-wide sync does not redden them.
84
+ if ! grep -qE '^[[:space:]]*PROJECT_[A-Z_]+[[:space:]]*:?=' Makefile; then
85
+ echo "::notice::Makefile has no PROJECT_* identity fields -- treating as a non-Moko/native Makefile and skipping identity lint."
86
+ echo "Non-Moko Makefile (no PROJECT_* fields) -- skipped." >> "$GITHUB_STEP_SUMMARY"
87
+ exit 0
88
+ fi
89
+
90
+ # Read a `FIELD := value` (or `FIELD = value`) identity line, trimmed.
91
+ read_field() {
92
+ { grep -m1 -E "^[[:space:]]*$1[[:space:]]*:?=" Makefile 2>/dev/null || true; } \
93
+ | sed -E 's/^[^=]*:?=[[:space:]]*//' \
94
+ | sed -E 's/[[:space:]]+#.*$//' \
95
+ | sed -E 's/[[:space:]]*$//'
96
+ }
97
+
98
+ is_placeholder() {
99
+ # scaffold leftovers: myproject, {{PROJECT_NAME}}, CHANGEME, etc.
100
+ case "$1" in
101
+ myproject|CHANGEME|TODO|"{{"*"}}"|"") return 0 ;;
102
+ *) return 1 ;;
103
+ esac
104
+ }
105
+
106
+ # Release-contract severity: hard-fail only when enforce is opted in
107
+ # (per-repo vars.MAKEFILE_LINT_ENFORCE), else warn -- so a fleet-wide
108
+ # sync never reddens a repo that has not adopted the contract yet.
109
+ ENFORCE=0
110
+ case "${MAKEFILE_LINT_ENFORCE:-}" in enforce|true|1) ENFORCE=1 ;; esac
111
+
112
+ gate() { # gate "<message>" "<summary-label>"
113
+ if [ "$ENFORCE" -eq 1 ]; then
114
+ echo "::error::$1"
115
+ echo "- [FAIL] $2" >> "$GITHUB_STEP_SUMMARY"
116
+ FAIL=1
117
+ else
118
+ echo "::warning::$1"
119
+ echo "- [WARN] $2 (MAKEFILE_LINT_ENFORCE off)" >> "$GITHUB_STEP_SUMMARY"
120
+ fi
121
+ }
122
+
123
+ FAIL=0
124
+
125
+ # -- Hard-required fields --
126
+ for f in PROJECT_NAME PROJECT_VERSION; do
127
+ v="$(read_field "$f")"
128
+ if [ -z "$v" ]; then
129
+ echo "::error::Makefile is missing required identity field ${f} (expected e.g. '${f} := ...')."
130
+ echo "- [FAIL] ${f}: missing" >> "$GITHUB_STEP_SUMMARY"
131
+ FAIL=1
132
+ elif is_placeholder "$v"; then
133
+ echo "::warning::Makefile ${f} is still a scaffold placeholder ('${v}') -- set it for this project."
134
+ echo "- [WARN] ${f}: placeholder (${v})" >> "$GITHUB_STEP_SUMMARY"
135
+ else
136
+ echo "- [OK] ${f}: ${v}" >> "$GITHUB_STEP_SUMMARY"
137
+ fi
138
+ done
139
+
140
+ # -- Newly-required (warn only for now: #289/#408 rollout grace period) --
141
+ author="$(read_field PROJECT_AUTHOR)"
142
+ if [ -z "$author" ]; then
143
+ echo "::warning::Makefile is missing PROJECT_AUTHOR -- release builds cannot stamp Joomla <author> (MokoCLI #803/#289). Add 'PROJECT_AUTHOR := <name>'."
144
+ echo "- [WARN] PROJECT_AUTHOR: missing (recommended)" >> "$GITHUB_STEP_SUMMARY"
145
+ elif is_placeholder "$author"; then
146
+ echo "::warning::Makefile PROJECT_AUTHOR is still a placeholder ('${author}')."
147
+ echo "- [WARN] PROJECT_AUTHOR: placeholder (${author})" >> "$GITHUB_STEP_SUMMARY"
148
+ else
149
+ echo "- [OK] PROJECT_AUTHOR: ${author}" >> "$GITHUB_STEP_SUMMARY"
150
+ fi
151
+
152
+ # -- PLATFORM: validate only if the repo declares one (platform-specific) --
153
+ if grep -qE '^[[:space:]]*PLATFORM[[:space:]]*:?=' Makefile; then
154
+ platform="$(read_field PLATFORM)"
155
+ if [ -z "$platform" ] || is_placeholder "$platform"; then
156
+ echo "::warning::Makefile declares PLATFORM but it is empty/placeholder ('${platform}')."
157
+ echo "- [WARN] PLATFORM: empty/placeholder" >> "$GITHUB_STEP_SUMMARY"
158
+ else
159
+ echo "- [OK] PLATFORM: ${platform}" >> "$GITHUB_STEP_SUMMARY"
160
+ fi
161
+ fi
162
+
163
+ # === Release-contract checks (beyond identity) -- #280 / #483 ===
164
+ # The pre-release pipeline reads SRC_DIR (the #469 bump gate) and
165
+ # PROJECT_VERSION, and runs `make build/package/version`. A stale,
166
+ # renamed, or missing var -- or a Makefile that doesn't parse --
167
+ # becomes a LATE, silent pre-release failure (no artifact). Surface it
168
+ # here at PR time. WARN unless vars.MAKEFILE_LINT_ENFORCE is set.
169
+ platform="$(read_field PLATFORM)"
170
+ platform_lc="$(printf '%s' "$platform" | tr '[:upper:]' '[:lower:]')"
171
+
172
+ # -- SRC_DIR: source root the #469 pre-release gate greps for.
173
+ # electron / RELEASE_SRCDIR_GATE=off opt out (always-bump). --
174
+ if [ "$platform_lc" = "electron" ] || [ "${RELEASE_SRCDIR_GATE:-}" = "off" ]; then
175
+ echo "- [OK] SRC_DIR: n/a (electron / RELEASE_SRCDIR_GATE=off)" >> "$GITHUB_STEP_SUMMARY"
176
+ elif grep -qE '^[[:space:]]*SRC_DIR[[:space:]]*:?=' Makefile; then
177
+ srcdir="$(read_field SRC_DIR)"
178
+ if [ -z "$srcdir" ] || is_placeholder "$srcdir"; then
179
+ gate "Makefile SRC_DIR is empty/placeholder ('${srcdir}') -- the pre-release #469 bump gate needs a real source root." "SRC_DIR: empty/placeholder"
180
+ else
181
+ echo "- [OK] SRC_DIR: ${srcdir}" >> "$GITHUB_STEP_SUMMARY"
182
+ if [ ! -d "$srcdir" ]; then
183
+ echo "::warning::SRC_DIR='${srcdir}' but no such directory exists in the repo."
184
+ echo "- [WARN] SRC_DIR: '${srcdir}' not on disk" >> "$GITHUB_STEP_SUMMARY"
185
+ fi
186
+ fi
187
+ elif grep -qE '^[[:space:]]*SRC[[:space:]]*:?=' Makefile; then
188
+ gate "Makefile uses legacy 'SRC :=' but not 'SRC_DIR :='. The pre-release #469 gate greps ^SRC_DIR= and will HARD-FAIL the publish (no artifact). Rename SRC -> SRC_DIR, or re-run 'mokocli workflow sync --makefiles'. See MokonyxSocial #280." "SRC_DIR: legacy 'SRC' var (#280)"
189
+ else
190
+ gate "Makefile is missing SRC_DIR (source root). The pre-release #469 bump gate requires it on non-electron repos; add 'SRC_DIR := <dir>' (or set vars.RELEASE_SRCDIR_GATE=off to opt out)." "SRC_DIR: missing"
191
+ fi
192
+
193
+ # -- PROJECT_VERSION format (warn; scheme varies by platform) --
194
+ ver="$(read_field PROJECT_VERSION)"
195
+ if printf '%s' "$ver" | grep -qE '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$'; then
196
+ echo "- [OK] PROJECT_VERSION format: NN.NN.NN" >> "$GITHUB_STEP_SUMMARY"
197
+ elif printf '%s' "$ver" | grep -qE '^v?[0-9]+\.[0-9]+\.[0-9]+([.+-].*)?$'; then
198
+ echo "- [OK] PROJECT_VERSION format: semver-ish (${ver})" >> "$GITHUB_STEP_SUMMARY"
199
+ else
200
+ echo "::warning::PROJECT_VERSION '${ver}' matches neither zero-padded NN.NN.NN (Joomla/PHP) nor semver x.y.z -- release tagging/version integrity may misbehave."
201
+ echo "- [WARN] PROJECT_VERSION format: unrecognized (${ver})" >> "$GITHUB_STEP_SUMMARY"
202
+ fi
203
+
204
+ # -- BUILD_DIR present (warn) --
205
+ if grep -qE '^[[:space:]]*BUILD_DIR[[:space:]]*:?=' Makefile; then
206
+ echo "- [OK] BUILD_DIR: present" >> "$GITHUB_STEP_SUMMARY"
207
+ else
208
+ echo "::warning::Makefile has no BUILD_DIR -- build/package targets usually stage into it."
209
+ echo "- [WARN] BUILD_DIR: missing" >> "$GITHUB_STEP_SUMMARY"
210
+ fi
211
+
212
+ # -- Required release targets the pipeline invokes (warn) --
213
+ missing_targets=""
214
+ for t in build package version; do
215
+ grep -qE "^${t}:" Makefile || missing_targets="${missing_targets} ${t}"
216
+ done
217
+ if [ -n "$missing_targets" ]; then
218
+ echo "::warning::Makefile is missing release target(s):${missing_targets} -- the pipeline runs 'make <target>'."
219
+ echo "- [WARN] targets: missing${missing_targets}" >> "$GITHUB_STEP_SUMMARY"
220
+ else
221
+ echo "- [OK] targets: build, package, version present" >> "$GITHUB_STEP_SUMMARY"
222
+ fi
223
+
224
+ # -- Makefile parses cleanly -- dry-run the always-present `version`
225
+ # target so a syntax/eval error is caught without running a build. --
226
+ if command -v make >/dev/null 2>&1 && grep -qE '^version:' Makefile; then
227
+ if make -n version >/dev/null 2>parse.err; then
228
+ echo "- [OK] make -n version: parses" >> "$GITHUB_STEP_SUMMARY"
229
+ else
230
+ gate "'make -n version' failed -- the Makefile has a parse/evaluation error: $(sed -n '1,3p' parse.err | tr '\n' ' ')" "make -n version: parse/eval error"
231
+ fi
232
+ rm -f parse.err
233
+ fi
234
+
235
+ if [ "$FAIL" -ne 0 ]; then
236
+ echo "" >> "$GITHUB_STEP_SUMMARY"
237
+ echo "**Fix the FAIL item(s) above (identity + release contract).**" >> "$GITHUB_STEP_SUMMARY"
238
+ exit 1
239
+ fi
240
+ echo "Makefile sanity OK ($([ "$ENFORCE" -eq 1 ] && echo 'enforce' || echo 'contract checks warn-only'))." >> "$GITHUB_STEP_SUMMARY"