@mokoconsulting/mokogit-mcp 1.13.3 → 1.13.4

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.
@@ -7,8 +7,8 @@
7
7
  # INGROUP: MokoCLI.Release
8
8
  # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
9
  # PATH: /.mokogit/workflows/auto-release.yml
10
- # VERSION: 07.23.00
11
- # BRIEF: NEW (07.23.00): "Vendor PHP dependencies (build-time)" added to BOTH publish legs (promote-rc + stable) — this workflow had NO build-time composer vendoring at all, so any component declaring runtime PHP deps via a sub-package composer.json (vendor-dir admin/vendor, e.g. mPDF in MokonyxCRM/MokonyxCRMDCM) shipped its rc/stable zip WITHOUT admin/vendor while the component manifest lists <folder>vendor</folder> — JInstaller then aborts the ENTIRE package install on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to copy administrator files"). The step mirrors pre-release.yml 07.15.00: no-op without a sub-package composer.json; provisions ext-gd when missing (the release runners' php-cli lacks it, which failed mpdf dependency RESOLUTION outright); verifies the CONFIGURED vendor-dir (composer config vendor-dir) materialized autoload.php; FAILS the build loud when vendoring fails for a package whose manifest ships <folder>vendor</folder> (a green build with an install-bricking artifact is strictly worse than a red build); fail-soft warning for packages whose manifests do not reference vendor/. Runs before `moko publish` (release_publish.php → release_package.php zips the plain working tree; vendor/ was never excluded — the tree simply never had it in CI). PREVIOUS (07.22.00): #498 — the #469 SRC_DIR gate no longer self-traps, and the fast-merge guard is MINOR-identity aware (fixes stable version never advancing; MokoCLI stuck re-publishing v10.20.00). (1) The gate read SRC_DIR with grep|sed, capturing the LITERAL unexpanded `$(shell …)` from the fleet-standard `SRC_DIR := $(shell [ -d src ] && echo src || echo source)` — an unmatchable directory that forced should_bump=false on EVERY push even when source changed. It now resolves SRC_DIR via `make --eval` expansion, falls back to the grep|sed read only if make is unavailable, nulls out a still-unexpanded `$(...)`/backtick value, and treats a declared-but-NONEXISTENT SRC_DIR (repos with code in cli/lib/bin, not src/source) as the non-source denylist fallback instead of an unmatchable INCLUSION prefix. (2) The "Guarantee monotonic release version (fast-merge bump guard)" compared the FULL version, so dev's per-merge PATCH bumps (10.20.00→10.20.04) fooled it into "normal path, no heal" and it re-published the already-released vMAJOR.MINOR.00. It now compares the MINOR identity (MAJOR.MINOR), so a fast dev→stable merge with no promote-rc cuts a new MINOR. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.21.00): #469 — gate the version BUMP on whether the push touched the Makefile SRC_DIR. The former non-code paths-ignore denylist on the push trigger is REMOVED: a docs/workflow/sample push no longer skips the pipeline, it now RUNS in no-bump "update the existing stable release in place" mode (re-stamps the UNCHANGED current version via the existing `moko publish --bump none --no-commit`, refreshes notes/assets, no version advance, no new vNN tag). A new early step "Resolve SRC_DIR bump gate (#469)" (id: srcgate) decides should_bump: (1) ELECTRON (file-detected) or opt-out vars.RELEASE_SRCDIR_GATE=off -> BYPASS, always bump as today (electron has NO source dir, so a naive SRC_DIR test would never bump — a release-breaking trap); (2) else SRC_DIR is REQUIRED — a non-electron repo with no resolvable SRC_DIR FAILS LOUD (::error:: + exit 1), never silently never-bump; (3) else diff the push range (github.event.before..GITHUB_SHA, with a HEAD^1 / merge-base fallback) intersected with ${SRC_DIR}/ — source hit -> should_bump=true, none -> should_bump=false. workflow_dispatch always bumps. The self-heal monotonic bump + the new vNN semver tag are now gated on should_bump!='false' so no-bump mode keeps the version PUT; the publish + "Update release notes and reconcile changelog" (channel-pointer resolution) run on BOTH paths to refresh the current release. Loop-safety is unchanged: the job if: still re-checks [skip ci]/[skip sync], so the workflow's own commits never re-trigger. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.20.00): the dev/promote bump + release steps now name the per-run token env var GITHUB_TOKEN (was the misleadingly-named MOKOGIT_TOKEN; the VALUE was always ${{ github.token }}). All token use in this workflow is SAME-REPO (release API, the release_bump_dev dev base bump, pushes to its own repo) — the org MOKOGIT_TOKEN secret is not referenced here. Naming-only; no behavior change. Mirrors the pre-release.yml fix (MokoGIT#1721). PREVIOUS: NEW (07.19.00): #1621 Phase 6 / .mokogit-private#76 — resolve the CURRENT stable release by its CHANNEL POINTER, not by a fixed tag_name=stable. Under version-primary releases (a release is born with the immutable tag_name=vNN and `stable`/`rc` attached as movable is_channel tags), GET /releases/tags/stable no longer resolves the current stable release, which would break two steps that read "the last released version". Both now call GET /repos/{owner}/{repo}/releases/channel/stable (#1702 — resolves whichever release the `stable` channel points at via ReleaseStreamMap) with a GRACEFUL FALLBACK to the legacy GET /releases/tags/stable for forges predating #1702 (the channel route 404s pre-rollout and the rolling `stable` tag still exists). (1) The monotonic-version guard ("Guarantee monotonic release version") resolves ROLLING_LAST by parsing the same `VERSION: NN.NN.NN` out of the channel-resolved release NAME. (2) "Update release notes and reconcile changelog" resolves the stable RELEASE_ID + VERSION the same channel-first way. The #386 integrity gate is UNCHANGED (it already keys off the immutable vNN version tag). SEQUENCING unchanged: do NOT enable in prod until #1702 server support is on stable/prod; works pre-#1702 via the fallback. PREVIOUS (07.18.00): #1621 Phase 6 — version/* BRANCHES retired in favor of dual-tagged version RELEASES. REMOVED three now-redundant steps: "Step 12: Create version branch from main", "Step 13: Prune superseded-major version branches", and "Advance rolling channel tag via git". A release's permanent version identity is now the git tag v<version>, attached by the publish as a MOVABLE channel tag on the dual-tagged release (MokoCLI release_publish -> release_create channel_tags[]/update_stream; the forge attaches/moves the tag at the release commit via AttachChannelTag server-side) — so a new cut is born dual-tagged (rolling `stable`/`release-candidate` primary tag + permanent v<version> channel tag) and the channel tag MOVES onto the new release instead of a new version/* branch being created. The rolling `stable` tag is still pinned by release_create forceTagRef() (#808), so the "Advance rolling channel tag" force-push was redundant. KEPT UNCHANGED: "Create semver tag" (v<version>), the #386 integrity gate (still resolves tags/stable to the built commit), and the monotonic guard (still reads the stable release name VERSION: NN.NN.NN). Pruning EXISTING version/* branches is a SEPARATE one-time manual reconcile — not done here. SEQUENCING: do NOT enable in prod until the #1621 SERVER channel_tags support is on stable/prod. PREVIOUS (07.17.00): Setup MokoCLI tools no longer installs a STALE mokocli during the registry publish-lag window (#1671). After the fresh registry install, the step now compares the installed /tmp/mokocli PROJECT_VERSION against the /opt/mokocli failsafe and PREFERS whichever is NEWER (sort -V), and the opportunistic /opt refresh is gated to only move FORWARD (never downgrade /opt when it is the newer, just-deployed copy). Backward-compatible: runners with no /opt/mokocli use the registry copy exactly as before. PREVIOUS (07.16.00): release-integrity + dev-channel fixes. (1) A new pre-publish "no stale (pre-merge) tree" guard (#428/MokoCLI#901) hard-FAILS the release if the triggering GITHUB_SHA is an ANCESTOR of the current branch tip (a concurrent merge landed mid-run) -- otherwise a green, version-bumped release silently ships old code missing that merge (reproduced: built ef8c1a5d while tip was 2740342). Fail-open on any unresolved/replica-lagged tip so a good release is never falsely blocked. Complements the mokocli engine assertion (packaged-tree == GITHUB_SHA), which cannot see that GITHUB_SHA itself trails the tip. (2) The "Post-release: Reset dev version" safety-net (#309/MokoCLI#900) no longer swallows failure: a failed dev-advance now emits ::error:: + a job-summary block (dev-channel would trail stable), and a SUCCESSFUL advance dispatches pre-release.yml (development) to actually publish the leading dev build -- the advance commit is [skip ci]+paths-ignored and never auto-fires. Stays green (continue-on-error) since stable already published. NEW (07.15.00): the rolling stable channel tag is now advanced via a git force-push BEFORE the #386 integrity gate. MokoGIT's tags API 405s on the release-attached `stable` tag, so release_publish.php could not move it ("Could not pin tag stable ... HTTP 405") and every Joomla/electron stable cut left the tag lagging the freshly-built commit -- red-failing a correct release on the integrity gate. A new step git-force-pushes refs/tags/stable to the built HEAD (force-push allowlist -- the same escape hatch the rc BRANCH pin already uses), so the gate passes and no manual tag repoint is needed. PREVIOUS (07.14.00): integrity gate #386 no longer false-fails. It compared the published tag against GITHUB_SHA (the triggering commit), but the publish step commits the CHANGELOG entry ("build NN.NN.NN [skip ci]") and mokocli pins the channel tag + semver tag to that POST-PUBLISH HEAD -- so every cut whose CHANGELOG wasn't pre-seeded on dev (e.g. a version set straight on the release branch) diverged by one commit and red-failed a correct release. The gate now compares against the actual built/published HEAD (git rev-parse HEAD, falling back to GITHUB_SHA), which equals what the tag is pinned to in both the commit and no-commit cases while still catching a tag pinned to a commit we did NOT build; the semver tag is pinned to the same built HEAD for consistency. PREVIOUS (07.13.00): RC is ON-DEMAND ONLY. The `pull_request` trigger (RC-preview on a PR to main/stable) is REMOVED and the promote-rc job now runs solely on workflow_dispatch action=promote-rc. Only dev + stable release automatically (dev via pre-release.yml push->dev; stable via the push->main release job here); alpha/beta/rc never auto-cut. PREVIOUS (07.12.00): electron repos create NO release on ANY channel (RC enabled=false + stable publish/tag/notes/integrity guarded on is_electron; opt back in with vars.ELECTRON_CI_BUILD=true) and build NO installer in CI — always built locally + uploaded manually; the dev/promote version BUMP still runs. Template-Generic #390. NEW (07.11.00): the electron installer build/attach steps (electron-builder via Wine/Xvfb) are gated to MANUAL workflow_dispatch only (`&& github.event_name == 'workflow_dispatch'`). An automatic push no longer auto-builds electron installers (heavy ~90m, runner-fragile — OOM/Wine-zombie; migrating to mokocli ElectronPackager, MokoCLI #756/#758). The version bump, CHANGELOG, tag and npm publish STILL run on push — only the electron installer is skipped and pushed MANUALLY via dispatch. Non-electron platforms (joomla/go/npm) unaffected. Template-Generic #390. Universal build & release - BUMP-ON-DEV: base version + CHANGELOG promoted on dev pre-promotion (promote-rc), main publishes --bump none --no-commit so it never diverges from dev. MokoCLI build agent (moko publish). NEW (07.06.00): npm stable publishing ABSORBED here from Template-NPM's retired npm-publish.yml as npm-gated dual-registry steps (MokoGIT registry via per-run token + npmjs.org best-effort), replacing the former npm-publish.yml dispatch. No-Makefile repos SKIP the release job gracefully (green) instead of failing. Conflict-marker scan reads git-tracked files only (git ls-files | xargs grep) so vendored/generated trees can't false-match. NEW (07.07.00): npm stamp normalizes the zero-padded Makefile PROJECT_VERSION (NN.NN.NN) to strict semver via awk before `npm version`, and an unstampable version now HARD-FAILS instead of silently keeping the old package.json version (#382). NEW (07.00.00): `moko assets:optimize` artifact-time step added before publish (graceful-skip on non-asset repos); Setup MokoCLI tools makes the fresh `moko` executable + first on PATH to fix exit-126 (mokocli #650).
10
+ # VERSION: 07.24.00
11
+ # BRIEF: NEW (07.24.00): npmjs.org publish now FAILS LOUD (::error:: + exit $rc) on an AUTHORIZATION error (404 "not in this registry" / 401 / 403 / ENEEDAUTH / EOTP) instead of swallowing EVERY failure as a non-failing ::warning:: — a missing-scope / unauthorized / revoked / expired NPM_TOKEN previously hid behind a green release fleet-wide (the "secondary registry, not failing the run" catch masked a 404 PUT, which is exactly how npm reports an unauthorized scoped write; CI had in fact NEVER successfully published any MCP package to npmjs while every run reported success). Transient/5xx/network failures still stay a non-failing ::warning:: (npmjs is the SECONDARY mirror; the MokoGIT registry is primary and already hard-fails), and an unset NPM_TOKEN still skips cleanly — so repos that do not publish to npmjs are 100% unaffected. PREVIOUS (07.23.00): "Vendor PHP dependencies (build-time)" added to BOTH publish legs (promote-rc + stable) — this workflow had NO build-time composer vendoring at all, so any component declaring runtime PHP deps via a sub-package composer.json (vendor-dir admin/vendor, e.g. mPDF in MokonyxCRM/MokonyxCRMDCM) shipped its rc/stable zip WITHOUT admin/vendor while the component manifest lists <folder>vendor</folder> — JInstaller then aborts the ENTIRE package install on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to copy administrator files"). The step mirrors pre-release.yml 07.15.00: no-op without a sub-package composer.json; provisions ext-gd when missing (the release runners' php-cli lacks it, which failed mpdf dependency RESOLUTION outright); verifies the CONFIGURED vendor-dir (composer config vendor-dir) materialized autoload.php; FAILS the build loud when vendoring fails for a package whose manifest ships <folder>vendor</folder> (a green build with an install-bricking artifact is strictly worse than a red build); fail-soft warning for packages whose manifests do not reference vendor/. Runs before `moko publish` (release_publish.php → release_package.php zips the plain working tree; vendor/ was never excluded — the tree simply never had it in CI). PREVIOUS (07.22.00): #498 — the #469 SRC_DIR gate no longer self-traps, and the fast-merge guard is MINOR-identity aware (fixes stable version never advancing; MokoCLI stuck re-publishing v10.20.00). (1) The gate read SRC_DIR with grep|sed, capturing the LITERAL unexpanded `$(shell …)` from the fleet-standard `SRC_DIR := $(shell [ -d src ] && echo src || echo source)` — an unmatchable directory that forced should_bump=false on EVERY push even when source changed. It now resolves SRC_DIR via `make --eval` expansion, falls back to the grep|sed read only if make is unavailable, nulls out a still-unexpanded `$(...)`/backtick value, and treats a declared-but-NONEXISTENT SRC_DIR (repos with code in cli/lib/bin, not src/source) as the non-source denylist fallback instead of an unmatchable INCLUSION prefix. (2) The "Guarantee monotonic release version (fast-merge bump guard)" compared the FULL version, so dev's per-merge PATCH bumps (10.20.00→10.20.04) fooled it into "normal path, no heal" and it re-published the already-released vMAJOR.MINOR.00. It now compares the MINOR identity (MAJOR.MINOR), so a fast dev→stable merge with no promote-rc cuts a new MINOR. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.21.00): #469 — gate the version BUMP on whether the push touched the Makefile SRC_DIR. The former non-code paths-ignore denylist on the push trigger is REMOVED: a docs/workflow/sample push no longer skips the pipeline, it now RUNS in no-bump "update the existing stable release in place" mode (re-stamps the UNCHANGED current version via the existing `moko publish --bump none --no-commit`, refreshes notes/assets, no version advance, no new vNN tag). A new early step "Resolve SRC_DIR bump gate (#469)" (id: srcgate) decides should_bump: (1) ELECTRON (file-detected) or opt-out vars.RELEASE_SRCDIR_GATE=off -> BYPASS, always bump as today (electron has NO source dir, so a naive SRC_DIR test would never bump — a release-breaking trap); (2) else SRC_DIR is REQUIRED — a non-electron repo with no resolvable SRC_DIR FAILS LOUD (::error:: + exit 1), never silently never-bump; (3) else diff the push range (github.event.before..GITHUB_SHA, with a HEAD^1 / merge-base fallback) intersected with ${SRC_DIR}/ — source hit -> should_bump=true, none -> should_bump=false. workflow_dispatch always bumps. The self-heal monotonic bump + the new vNN semver tag are now gated on should_bump!='false' so no-bump mode keeps the version PUT; the publish + "Update release notes and reconcile changelog" (channel-pointer resolution) run on BOTH paths to refresh the current release. Loop-safety is unchanged: the job if: still re-checks [skip ci]/[skip sync], so the workflow's own commits never re-trigger. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.20.00): the dev/promote bump + release steps now name the per-run token env var GITHUB_TOKEN (was the misleadingly-named MOKOGIT_TOKEN; the VALUE was always ${{ github.token }}). All token use in this workflow is SAME-REPO (release API, the release_bump_dev dev base bump, pushes to its own repo) — the org MOKOGIT_TOKEN secret is not referenced here. Naming-only; no behavior change. Mirrors the pre-release.yml fix (MokoGIT#1721). PREVIOUS: NEW (07.19.00): #1621 Phase 6 / .mokogit-private#76 — resolve the CURRENT stable release by its CHANNEL POINTER, not by a fixed tag_name=stable. Under version-primary releases (a release is born with the immutable tag_name=vNN and `stable`/`rc` attached as movable is_channel tags), GET /releases/tags/stable no longer resolves the current stable release, which would break two steps that read "the last released version". Both now call GET /repos/{owner}/{repo}/releases/channel/stable (#1702 — resolves whichever release the `stable` channel points at via ReleaseStreamMap) with a GRACEFUL FALLBACK to the legacy GET /releases/tags/stable for forges predating #1702 (the channel route 404s pre-rollout and the rolling `stable` tag still exists). (1) The monotonic-version guard ("Guarantee monotonic release version") resolves ROLLING_LAST by parsing the same `VERSION: NN.NN.NN` out of the channel-resolved release NAME. (2) "Update release notes and reconcile changelog" resolves the stable RELEASE_ID + VERSION the same channel-first way. The #386 integrity gate is UNCHANGED (it already keys off the immutable vNN version tag). SEQUENCING unchanged: do NOT enable in prod until #1702 server support is on stable/prod; works pre-#1702 via the fallback. PREVIOUS (07.18.00): #1621 Phase 6 — version/* BRANCHES retired in favor of dual-tagged version RELEASES. REMOVED three now-redundant steps: "Step 12: Create version branch from main", "Step 13: Prune superseded-major version branches", and "Advance rolling channel tag via git". A release's permanent version identity is now the git tag v<version>, attached by the publish as a MOVABLE channel tag on the dual-tagged release (MokoCLI release_publish -> release_create channel_tags[]/update_stream; the forge attaches/moves the tag at the release commit via AttachChannelTag server-side) — so a new cut is born dual-tagged (rolling `stable`/`release-candidate` primary tag + permanent v<version> channel tag) and the channel tag MOVES onto the new release instead of a new version/* branch being created. The rolling `stable` tag is still pinned by release_create forceTagRef() (#808), so the "Advance rolling channel tag" force-push was redundant. KEPT UNCHANGED: "Create semver tag" (v<version>), the #386 integrity gate (still resolves tags/stable to the built commit), and the monotonic guard (still reads the stable release name VERSION: NN.NN.NN). Pruning EXISTING version/* branches is a SEPARATE one-time manual reconcile — not done here. SEQUENCING: do NOT enable in prod until the #1621 SERVER channel_tags support is on stable/prod. PREVIOUS (07.17.00): Setup MokoCLI tools no longer installs a STALE mokocli during the registry publish-lag window (#1671). After the fresh registry install, the step now compares the installed /tmp/mokocli PROJECT_VERSION against the /opt/mokocli failsafe and PREFERS whichever is NEWER (sort -V), and the opportunistic /opt refresh is gated to only move FORWARD (never downgrade /opt when it is the newer, just-deployed copy). Backward-compatible: runners with no /opt/mokocli use the registry copy exactly as before. PREVIOUS (07.16.00): release-integrity + dev-channel fixes. (1) A new pre-publish "no stale (pre-merge) tree" guard (#428/MokoCLI#901) hard-FAILS the release if the triggering GITHUB_SHA is an ANCESTOR of the current branch tip (a concurrent merge landed mid-run) -- otherwise a green, version-bumped release silently ships old code missing that merge (reproduced: built ef8c1a5d while tip was 2740342). Fail-open on any unresolved/replica-lagged tip so a good release is never falsely blocked. Complements the mokocli engine assertion (packaged-tree == GITHUB_SHA), which cannot see that GITHUB_SHA itself trails the tip. (2) The "Post-release: Reset dev version" safety-net (#309/MokoCLI#900) no longer swallows failure: a failed dev-advance now emits ::error:: + a job-summary block (dev-channel would trail stable), and a SUCCESSFUL advance dispatches pre-release.yml (development) to actually publish the leading dev build -- the advance commit is [skip ci]+paths-ignored and never auto-fires. Stays green (continue-on-error) since stable already published. NEW (07.15.00): the rolling stable channel tag is now advanced via a git force-push BEFORE the #386 integrity gate. MokoGIT's tags API 405s on the release-attached `stable` tag, so release_publish.php could not move it ("Could not pin tag stable ... HTTP 405") and every Joomla/electron stable cut left the tag lagging the freshly-built commit -- red-failing a correct release on the integrity gate. A new step git-force-pushes refs/tags/stable to the built HEAD (force-push allowlist -- the same escape hatch the rc BRANCH pin already uses), so the gate passes and no manual tag repoint is needed. PREVIOUS (07.14.00): integrity gate #386 no longer false-fails. It compared the published tag against GITHUB_SHA (the triggering commit), but the publish step commits the CHANGELOG entry ("build NN.NN.NN [skip ci]") and mokocli pins the channel tag + semver tag to that POST-PUBLISH HEAD -- so every cut whose CHANGELOG wasn't pre-seeded on dev (e.g. a version set straight on the release branch) diverged by one commit and red-failed a correct release. The gate now compares against the actual built/published HEAD (git rev-parse HEAD, falling back to GITHUB_SHA), which equals what the tag is pinned to in both the commit and no-commit cases while still catching a tag pinned to a commit we did NOT build; the semver tag is pinned to the same built HEAD for consistency. PREVIOUS (07.13.00): RC is ON-DEMAND ONLY. The `pull_request` trigger (RC-preview on a PR to main/stable) is REMOVED and the promote-rc job now runs solely on workflow_dispatch action=promote-rc. Only dev + stable release automatically (dev via pre-release.yml push->dev; stable via the push->main release job here); alpha/beta/rc never auto-cut. PREVIOUS (07.12.00): electron repos create NO release on ANY channel (RC enabled=false + stable publish/tag/notes/integrity guarded on is_electron; opt back in with vars.ELECTRON_CI_BUILD=true) and build NO installer in CI — always built locally + uploaded manually; the dev/promote version BUMP still runs. Template-Generic #390. NEW (07.11.00): the electron installer build/attach steps (electron-builder via Wine/Xvfb) are gated to MANUAL workflow_dispatch only (`&& github.event_name == 'workflow_dispatch'`). An automatic push no longer auto-builds electron installers (heavy ~90m, runner-fragile — OOM/Wine-zombie; migrating to mokocli ElectronPackager, MokoCLI #756/#758). The version bump, CHANGELOG, tag and npm publish STILL run on push — only the electron installer is skipped and pushed MANUALLY via dispatch. Non-electron platforms (joomla/go/npm) unaffected. Template-Generic #390. Universal build & release - BUMP-ON-DEV: base version + CHANGELOG promoted on dev pre-promotion (promote-rc), main publishes --bump none --no-commit so it never diverges from dev. MokoCLI build agent (moko publish). NEW (07.06.00): npm stable publishing ABSORBED here from Template-NPM's retired npm-publish.yml as npm-gated dual-registry steps (MokoGIT registry via per-run token + npmjs.org best-effort), replacing the former npm-publish.yml dispatch. No-Makefile repos SKIP the release job gracefully (green) instead of failing. Conflict-marker scan reads git-tracked files only (git ls-files | xargs grep) so vendored/generated trees can't false-match. NEW (07.07.00): npm stamp normalizes the zero-padded Makefile PROJECT_VERSION (NN.NN.NN) to strict semver via awk before `npm version`, and an unstampable version now HARD-FAILS instead of silently keeping the old package.json version (#382). NEW (07.00.00): `moko assets:optimize` artifact-time step added before publish (graceful-skip on non-asset repos); Setup MokoCLI tools makes the fresh `moko` executable + first on PATH to fix exit-126 (mokocli #650).
12
12
  #
13
13
  # +=======================================================================+
14
14
  # | UNIVERSAL BUILD & RELEASE PIPELINE |
@@ -1676,7 +1676,8 @@ jobs:
1676
1676
  rm -f .npmrc; echo "$out"
1677
1677
  if [ "$rc" -eq 0 ]; then echo "Published to npmjs.org."
1678
1678
  elif printf '%s' "$out" | grep -qiE 'already exists|cannot publish over|EPUBLISHCONFLICT|409 Conflict'; then echo "::notice::npmjs.org already has this version - no-op."
1679
- else echo "::warning::npmjs.org publish failed (npm exit ${rc}) - secondary registry only, not failing the run."; fi
1679
+ elif printf '%s' "$out" | grep -qiE 'not in this registry|ENEEDAUTH|EOTP|need auth|E401|E403|401 Unauthorized|403 Forbidden|404 Not Found|Forbidden'; then echo "::error::npmjs.org publish FAILED — authorization error (npm exit ${rc}). NPM_TOKEN lacks publish/create rights on the @mokoconsulting scope (new-scoped-name first publish, an unauthorized token, or a revoked/expired token). Failing loud so a broken publish token cannot hide behind a green release — a 404 PUT is how npm masks an unauthorized scoped write."; exit "$rc"
1680
+ else echo "::warning::npmjs.org publish failed (npm exit ${rc}) — transient/5xx/network issue; npmjs is the secondary mirror (MokoGIT registry is primary), not failing the run."; fi
1680
1681
 
1681
1682
  # GitHub mirroring (release assets + main branch) moved to an out-of-band
1682
1683
  # OAuth push-mirror (Gitea repo setting) -- former Steps 9 & 10 removed.
package/CHANGELOG.md CHANGED
@@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
21
  ### Changed
22
22
  - **`git_org_sync_profile_apply` now polls to completion by default (#264).** The generated tool fired the `POST .../sync-profiles/{id}/sync` once and returned. The server applies in **batches**, so on a large profile (observed: 79 targets / 209 pending changes) a single call only advanced part way (209→54) and the operator had to manually re-invoke apply and watch `git_org_sync_profile_status` until it hit 0 — and a big batch could exceed the client's 30s timeout, surfacing as a hard failure while the server kept working in the background. A hand-written override (registered ahead of the generated registration, so the existing collision guard skips the generated name) now **apply → status → repeat** until `in_sync`, a stall (pending stops decreasing across 2 rounds), or a 10-round safety cap. An apply-round **timeout is treated as "still running"** — swallowed, then status is polled to observe real progress and the loop continues. Returns a structured result: per-round `pending_trajectory` (e.g. `[209,54,0]`), `rounds`, final `{in_sync, pending_changes, target_count}`, the merged `applied_target_repo_ids`, and a `stop_reason`. New optional **`wait`** param (default `true` = polling); `wait:false` restores the old single-shot apply. Backward compatible — existing callers get the completion behavior automatically.
23
23
 
24
- ## [1.13.0] --- 2026-10-28
24
+ ## [1.13.0] --- 2026-10-29
25
25
 
26
26
  ### Added
27
27
  - **Channel-name tag/branch collision guardrail on ref-resolving read tools (#236).** MokoGIT's release model uses channel-named rolling tags (`stable`, `dev`, `rc`, …) that are force-advanced to each build, so on channel-named-branch repos a git **tag and branch can share a name**. Git's bare-ref precedence resolves the name to the **tag before the branch**, so a tool handed a bare channel name silently returned the tag's content. Twelve ref-taking read tools (`file_get`, `dir_get`, `tree_get`, `commits_list`, `compare`, `ref_statuses_list`, `status_combined_get`, `repo_raw_get`, `repo_media_get`, `contents_ext_get`, `contents_list`, `file_contents_get`) now probe, **in parallel with the real request**, whether the ref name exists as both `refs/heads/<name>` and `refs/tags/<name>` at **different** commits and, if so, prepend a non-fatal `⚠` warning naming both targets, which one git resolves to, and how to qualify explicitly. The guard is **fail-open** (any probe error returns no warning, never breaking the real call), skips already-qualified refs (`refs/…`, `heads/…`, `tags/…`) and SHA-looking inputs, and uses an **exact** `ref` match so Gitea's prefix matching can't false-positive. Verified end-to-end against the live `stable` collision on this repo.
@@ -35,6 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
35
35
 
36
36
  - **Rapid-merge guard on `git_pull_merge` (#288).** Merging several PRs into the same base back-to-back fired one `pre-release.yml` per merge, and those runs raced to bump the base version files via the Contents API — the loser got HTTP 422 "sha does not match" on its `bot/version-advance/<base>-*` temp branch (0 files stamped, red run, duplicate/under-bumped versions; observed laddering 4 MokonyxInsight PRs into `dev`). The root cause is merge concurrency, so the guard sits at the merge layer: a new `source/rapid-merge-guard.ts` module (1) **serializes** merges to the same `(repo, base)` through an in-process async lock/queue so two `pull_merge` calls can't overlap; (2) **settle-waits** — detects an in-flight `pre-release.yml` (or any run writing `bot/version-advance/<base>-*`) on the base and holds until it completes (bounded by `max_wait`) before releasing the next merge, or applies a fixed cooldown; and (3) **reports** the wait + reason in the merge result (e.g. `held 42s for in-flight pre-release run #NNN on dev before merging`). New optional `git_pull_merge` params: `rapid_merge_guard` (`settle` (default) | `cooldown:<sec>` | `off`), `max_wait` (seconds, default 180), and `force` (bypass). Falls back to the `MOKOGIT_RAPID_MERGE_GUARD` / `MOKOGIT_RAPID_MERGE_MAX_WAIT` env vars then the defaults. **Fail-open**: a PR/runs lookup error merges unguarded rather than blocking. Complements #287 (pre-merge CI red gate), which edits the same handler — this prevents merges from *creating* the race; the deeper server-side retry-on-422 fix is tracked under MokoGIT #1721.
37
37
 
38
+ - **`git_user_token_create` can now scope a token to specific repositories (#286).** The forge side (MokoGIT #1952, prod 02.85.00) extended `CreateAccessTokenOption` with `all_repos` and `repo_ids`, letting a personal access token be restricted to a chosen set of repositories instead of the implicit "all repos incl. future". The tool gains three optional params: **`all_repos`** (boolean — token covers all repos incl. future, forge default `true`), **`repo_ids`** (`number[]` — when `all_repos=false`, restrict to these repository IDs), and **`repos`** (`string[]` — convenience: repo full-names `owner/name` resolved to IDs client-side via `GET /repos/{owner}/{repo}` and merged into `repo_ids`). The forge field names sent on the body are `all_repos` and `repo_ids`. Backward compatible — omitting all three preserves the prior all-repos behavior. `git_user_tokens_list` is a raw passthrough and already surfaces the new response fields.
39
+ - **MCP server version is now read from `package.json` instead of a hardcoded literal.** The `McpServer({ version })` constructor was pinned at `1.5.0`, long stale versus the real package version. It now resolves `package.json`'s `version` at runtime (`createRequire(import.meta.url)`), so the advertised server version tracks the Makefile `PROJECT_VERSION` / `package.json` source of truth automatically.
40
+
38
41
  ## [1.12.3] --- 2026-09-11
39
42
 
40
43
  ### Deprecated
package/Makefile CHANGED
@@ -10,7 +10,7 @@
10
10
  # Identity (auto-filled by workflow sync — the Makefile is the version source of truth)
11
11
  # ==============================================================================
12
12
  PROJECT_NAME := mcp-mokogit
13
- PROJECT_VERSION := 1.13.3
13
+ PROJECT_VERSION := 1.13.4
14
14
  PLATFORM := npm
15
15
 
16
16
  # ==============================================================================
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import * as https from 'node:https';
17
17
  import * as http from 'node:http';
18
+ import { createRequire } from 'node:module';
18
19
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
20
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
21
  import { z } from 'zod';
@@ -198,9 +199,13 @@ const OwnerRepo = {
198
199
  owner: z.string().describe('Repository owner (user or org)'),
199
200
  repo: z.string().describe('Repository name'),
200
201
  };
202
+ // Resolve the real package version from package.json (single source of truth,
203
+ // kept in sync with the Makefile PROJECT_VERSION) rather than a hardcoded string.
204
+ const pkg = createRequire(import.meta.url)('../package.json');
205
+ const SERVER_VERSION = pkg.version ?? '0.0.0';
201
206
  const server = new McpServer({
202
207
  name: 'mokogit-mcp',
203
- version: '1.5.0',
208
+ version: SERVER_VERSION,
204
209
  });
205
210
  // ── Tool registration wrapper (Git → MokoGIT brand normalization) ────
206
211
  //
@@ -2270,12 +2275,37 @@ registerTool('git_user_token_create', 'Create an access token for a user', {
2270
2275
  username: z.string().describe('Owner of the token (username in the path)'),
2271
2276
  name: z.string().describe('Name of the token'),
2272
2277
  scopes: z.array(z.string()).optional().describe('Token scopes (e.g. ["read:user", "write:repository"])'),
2278
+ all_repos: z.boolean().optional().describe('Token covers all repos incl. future (default true)'),
2279
+ repo_ids: z.array(z.number()).optional().describe('When all_repos=false, restrict to these repository IDs'),
2280
+ repos: z.array(z.string()).optional().describe('Convenience: repo full-names (owner/name) resolved to IDs client-side'),
2273
2281
  ...ConnectionParam,
2274
- }, async ({ username, name, scopes, connection }) => {
2282
+ }, async ({ username, name, scopes, all_repos, repo_ids, repos, connection }) => {
2283
+ const client = clientFor(connection);
2275
2284
  const body = { name };
2276
2285
  if (scopes)
2277
2286
  body.scopes = scopes;
2278
- return formatResponse(await clientFor(connection).post(`/users/${enc(username)}/tokens`, body));
2287
+ if (all_repos !== undefined)
2288
+ body.all_repos = all_repos;
2289
+ // Resolve any repo full-names (owner/name) to numeric IDs and merge with
2290
+ // any explicit repo_ids. The forge expects the field name `repo_ids`.
2291
+ const resolvedIds = Array.isArray(repo_ids) ? [...repo_ids] : [];
2292
+ if (repos?.length) {
2293
+ for (const full of repos) {
2294
+ const slash = full.indexOf('/');
2295
+ if (slash < 0)
2296
+ throw new Error(`repos entries must be "owner/name": got "${full}"`);
2297
+ const ownerPart = full.slice(0, slash);
2298
+ const repoPart = full.slice(slash + 1);
2299
+ const res = await client.get(`/repos/${enc(ownerPart)}/${enc(repoPart)}`);
2300
+ const id = res.data?.id;
2301
+ if (typeof id !== 'number')
2302
+ throw new Error(`Could not resolve repository id for "${full}" (status ${res.status})`);
2303
+ resolvedIds.push(id);
2304
+ }
2305
+ }
2306
+ if (resolvedIds.length)
2307
+ body.repo_ids = resolvedIds;
2308
+ return formatResponse(await client.post(`/users/${enc(username)}/tokens`, body));
2279
2309
  });
2280
2310
  registerTool('git_user_token_delete', 'Delete an access token for a user', { username: z.string().describe('Owner of the token (username in the path)'), token: z.string().describe('Token id or name to delete'), ...ConnectionParam }, async ({ username, token, connection }) => formatResponse(await clientFor(connection).delete(`/users/${enc(username)}/tokens/${token}`)));
2281
2311
  registerTool('git_oauth2_apps_list', 'List OAuth2 applications for the authenticated user', { ...PaginationParams, ...ConnectionParam }, async ({ page, limit, connection }) => formatResponse(await clientFor(connection).get('/user/applications/oauth2', pageQuery({ page, limit }))));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mokoconsulting/mokogit-mcp",
3
- "version": "1.13.3",
3
+ "version": "1.13.4",
4
4
  "description": "MCP server for MokoGIT (Git) REST API v1 operations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/source/index.ts CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  import * as https from 'node:https';
18
18
  import * as http from 'node:http';
19
+ import { createRequire } from 'node:module';
19
20
  import { McpServer, type ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js';
20
21
  import type { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
21
22
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -227,9 +228,14 @@ const OwnerRepo = {
227
228
  repo: z.string().describe('Repository name'),
228
229
  };
229
230
 
231
+ // Resolve the real package version from package.json (single source of truth,
232
+ // kept in sync with the Makefile PROJECT_VERSION) rather than a hardcoded string.
233
+ const pkg = createRequire(import.meta.url)('../package.json') as { version?: string };
234
+ const SERVER_VERSION = pkg.version ?? '0.0.0';
235
+
230
236
  const server = new McpServer({
231
237
  name: 'mokogit-mcp',
232
- version: '1.5.0',
238
+ version: SERVER_VERSION,
233
239
  });
234
240
 
235
241
  // ── Tool registration wrapper (Git → MokoGIT brand normalization) ────
@@ -4053,12 +4059,33 @@ registerTool(
4053
4059
  username: z.string().describe('Owner of the token (username in the path)'),
4054
4060
  name: z.string().describe('Name of the token'),
4055
4061
  scopes: z.array(z.string()).optional().describe('Token scopes (e.g. ["read:user", "write:repository"])'),
4062
+ all_repos: z.boolean().optional().describe('Token covers all repos incl. future (default true)'),
4063
+ repo_ids: z.array(z.number()).optional().describe('When all_repos=false, restrict to these repository IDs'),
4064
+ repos: z.array(z.string()).optional().describe('Convenience: repo full-names (owner/name) resolved to IDs client-side'),
4056
4065
  ...ConnectionParam,
4057
4066
  },
4058
- async ({ username, name, scopes, connection }) => {
4067
+ async ({ username, name, scopes, all_repos, repo_ids, repos, connection }) => {
4068
+ const client = clientFor(connection);
4059
4069
  const body: Record<string, unknown> = { name };
4060
4070
  if (scopes) body.scopes = scopes;
4061
- return formatResponse(await clientFor(connection).post(`/users/${enc(username)}/tokens`, body));
4071
+ if (all_repos !== undefined) body.all_repos = all_repos;
4072
+ // Resolve any repo full-names (owner/name) to numeric IDs and merge with
4073
+ // any explicit repo_ids. The forge expects the field name `repo_ids`.
4074
+ const resolvedIds: number[] = Array.isArray(repo_ids) ? [...repo_ids] : [];
4075
+ if (repos?.length) {
4076
+ for (const full of repos) {
4077
+ const slash = full.indexOf('/');
4078
+ if (slash < 0) throw new Error(`repos entries must be "owner/name": got "${full}"`);
4079
+ const ownerPart = full.slice(0, slash);
4080
+ const repoPart = full.slice(slash + 1);
4081
+ const res = await client.get(`/repos/${enc(ownerPart)}/${enc(repoPart)}`);
4082
+ const id = (res.data as { id?: number } | null)?.id;
4083
+ if (typeof id !== 'number') throw new Error(`Could not resolve repository id for "${full}" (status ${res.status})`);
4084
+ resolvedIds.push(id);
4085
+ }
4086
+ }
4087
+ if (resolvedIds.length) body.repo_ids = resolvedIds;
4088
+ return formatResponse(await client.post(`/users/${enc(username)}/tokens`, body));
4062
4089
  },
4063
4090
  );
4064
4091