diff --git a/.gitea/.mokostandards b/.gitea/.mokostandards
new file mode 100644
index 0000000..ce1ce33
--- /dev/null
+++ b/.gitea/.mokostandards
@@ -0,0 +1,48 @@
+
+
+
+
+ MokoStandards-API
+ MokoConsulting
+ MokoStandards Enterprise API — PHP implementation (Composer package: mokoconsulting-tech/enterprise)
+ GNU General Public License v3
+
+
+ standards-repository
+ 04.07.00
+ https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
+ 2026-05-02T23:05:55+00:00
+
+
+ HCL
+ php:>=8.1
+ composer
+
+
+
+
+
+
+
+
diff --git a/.gitea/workflows/bulk-repo-sync.yml b/.gitea/workflows/bulk-repo-sync.yml
new file mode 100644
index 0000000..420d820
--- /dev/null
+++ b/.gitea/workflows/bulk-repo-sync.yml
@@ -0,0 +1,136 @@
+# Copyright (C) 2026 Moko Consulting
+# SPDX-License-Identifier: GPL-3.0-or-later
+# FILE INFORMATION
+# DEFGROUP: Gitea.Workflow
+# INGROUP: MokoStandards-API.Automation
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
+# PATH: /.gitea/workflows/bulk-repo-sync.yml
+# VERSION: 04.06.12
+# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos
+
+name: Bulk Repository Sync
+
+on:
+ schedule:
+ - cron: '0 0 1 * *'
+ workflow_dispatch:
+ inputs:
+ dry_run:
+ description: 'Preview mode (no changes)'
+ required: false
+ type: boolean
+ default: true
+ repos:
+ description: 'Comma-separated repo names (empty = all)'
+ required: false
+ type: string
+ default: ''
+ exclude:
+ description: 'Comma-separated repos to skip'
+ required: false
+ type: string
+ default: ''
+ force:
+ description: 'Force overwrite protected files'
+ required: false
+ type: boolean
+ default: false
+
+permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+
+jobs:
+ bulk-sync:
+ name: Sync Standards to Repositories
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.1'
+ extensions: json, mbstring, curl
+ tools: composer
+ coverage: none
+
+ - name: Install Dependencies
+ run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
+
+ - name: Build CLI Arguments
+ id: args
+ run: |
+ ARGS="--org MokoConsulting"
+ if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then
+ ARGS="$ARGS --dry-run"
+ fi
+ if [ -n "${{ inputs.repos }}" ]; then
+ ARGS="$ARGS --repos ${{ inputs.repos }}"
+ fi
+ if [ -n "${{ inputs.exclude }}" ]; then
+ ARGS="$ARGS --exclude ${{ inputs.exclude }}"
+ fi
+ if [ "${{ inputs.force }}" = "true" ]; then
+ ARGS="$ARGS --force"
+ fi
+ ARGS="$ARGS --yes"
+ echo "args=$ARGS" >> $GITHUB_OUTPUT
+
+ - name: Run Bulk Sync
+ run: |
+ echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}"
+ php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log
+ env:
+ GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
+ GIT_PLATFORM: gitea
+ GITEA_URL: https://git.mokoconsulting.tech
+ GITEA_ORG: MokoConsulting
+
+ - name: Commit Updated Definitions
+ if: success() && inputs.dry_run != 'true'
+ run: |
+ if [ -n "$(git status --porcelain definitions/sync/)" ]; then
+ git config user.name "gitea-actions[bot]"
+ git config user.email "gitea-actions[bot]@git.mokoconsulting.tech"
+ git add definitions/sync/*.def.tf
+ git commit -m "chore: update synced repository definitions" || true
+ git push || true
+ fi
+
+ - name: Enforce Release Channel Tags
+ if: success()
+ continue-on-error: true
+ run: |
+ echo "Enforcing standard tags on all repos..."
+ if [ "${{ inputs.dry_run }}" = "true" ]; then
+ bash automation/enforce_tags.sh --dry-run || echo "Tag enforcement skipped (non-fatal)"
+ else
+ bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)"
+ fi
+ env:
+ GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GITEA_URL: https://git.mokoconsulting.tech
+ GITEA_ORG: MokoConsulting
+
+ - name: Upload Sync Log
+ if: always() && github.server_url == 'https://github.com'
+ uses: actions/upload-artifact@v4
+ with:
+ name: bulk-sync-log-${{ github.run_number }}
+ path: /tmp/bulk_sync.log
+ retention-days: 30
+
+ - name: Log Summary (Gitea)
+ if: always() && github.server_url != 'https://github.com'
+ run: |
+ if [ -f /tmp/bulk_sync.log ]; then
+ echo "## Sync Log (last 20 lines)" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ tail -20 /tmp/bulk_sync.log >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/.gitignore b/.gitignore
index f0c54d5..5d085b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1062,3 +1062,5 @@ terraform.rc
# but can be ignored if you want flexibility across different platforms
# !.terraform.lock.hcl
logs/validation/*.md
+profile.ps1
+.mcp.json
diff --git a/automation/bulk_joomla_template.php b/automation/bulk_joomla_template.php
index 832cb1b..60e2a7c 100644
--- a/automation/bulk_joomla_template.php
+++ b/automation/bulk_joomla_template.php
@@ -15,11 +15,11 @@
* BRIEF: Bulk scaffold and sync Joomla template repositories
*
* USAGE
- * php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme
- * php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator
- * php api/automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme
- * php api/automation/bulk_joomla_template.php --sync --all
- * php api/automation/bulk_joomla_template.php --list
+ * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme
+ * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator
+ * php automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme
+ * php automation/bulk_joomla_template.php --sync --all
+ * php automation/bulk_joomla_template.php --list
*/
declare(strict_types=1);
@@ -717,13 +717,13 @@ class BulkJoomlaTemplate extends CLIApp
// ── Sync updates.xml between platforms ───────────────────────────────
/**
- * Sync updates.xml (or update.xml) between Gitea and GitHub for Joomla repos.
+ * Sync updates.xml (or updates.xml) between Gitea and GitHub for Joomla repos.
*
* Reads the file from both platforms, compares by latest tag,
* and pushes the newer one to the stale platform.
*
* Designed to be called from a CI workflow via:
- * php api/automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia
+ * php automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia
*/
private function syncUpdatesBetweenPlatforms(string $org): int
{
@@ -788,7 +788,7 @@ class BulkJoomlaTemplate extends CLIApp
$name = $repo['name'];
$this->log("\n[{$name}]", 'INFO');
- // Try both update.xml and updates.xml filenames
+ // Try both updates.xml and updates.xml filenames
$updateFile = $this->resolveUpdateFile($gitea, $github, $org, $name);
if ($updateFile === null) {
$this->log(" ⊘ No update(s).xml found on either platform", 'INFO');
@@ -849,7 +849,7 @@ class BulkJoomlaTemplate extends CLIApp
/**
* Find the updates file on both platforms, return the one with the higher version.
*
- * Checks both `updates.xml` and `update.xml` filenames.
+ * Checks both `updates.xml` and `updates.xml` filenames.
* Returns the content from the platform with the newer .
* Gitea wins ties (primary platform).
*
@@ -861,7 +861,7 @@ class BulkJoomlaTemplate extends CLIApp
string $org,
string $name
): ?array {
- $candidates = ['updates.xml', 'update.xml'];
+ $candidates = ['updates.xml', 'updates.xml'];
$found = []; // platform => [name, content, version]
foreach (['gitea' => $gitea, 'github' => $github] as $platform => $adapter) {
diff --git a/automation/bulk_sync.php b/automation/bulk_sync.php
index 0503c9c..ff525fc 100755
--- a/automation/bulk_sync.php
+++ b/automation/bulk_sync.php
@@ -379,6 +379,7 @@ class BulkSync extends CLIApp
// (e.g. 54 new labels on a fresh repo), reset it so file sync proceeds.
if (!$this->dryRun) {
$this->ensureRepoLabels($org, $repoName);
+ $this->ensureReleaseTags($org, $repoName);
$this->api->resetCircuitBreaker();
}
@@ -419,7 +420,7 @@ class BulkSync extends CLIApp
$this->log("The bulk repository sync is failing silently because the core", 'ERROR');
$this->log("synchronization logic has not been implemented yet.", 'ERROR');
$this->log("", 'ERROR');
- $this->log("Location: api/lib/Enterprise/RepositorySynchronizer.php", 'ERROR');
+ $this->log("Location: lib/Enterprise/RepositorySynchronizer.php", 'ERROR');
$this->log("Method: processRepository()", 'ERROR');
$this->log("", 'ERROR');
$this->log("Required Implementation:", 'ERROR');
@@ -508,7 +509,7 @@ class BulkSync extends CLIApp
]);
$script = basename(__FILE__);
$this->log("💾 Checkpoint saved. To resume once the issue is resolved, run:", 'INFO');
- $this->log(" php api/automation/{$script} --resume [same flags as before]", 'INFO');
+ $this->log(" php automation/{$script} --resume [same flags as before]", 'INFO');
} catch (\Exception $e) {
$this->log("⚠️ Failed to save interrupt checkpoint: " . $e->getMessage(), 'WARN');
}
@@ -958,8 +959,51 @@ class BulkSync extends CLIApp
}
/**
- * Create a tracking issue in the target repository after a successful sync.
+ * Ensure standard release tags exist on the repository.
*
+ * Creates 'development', 'beta', and 'release-candidate' tags pointing
+ * to the default branch HEAD if they don't already exist. These tags
+ * are used by the release workflow to track stability channels.
+ */
+ private function ensureReleaseTags(string $org, string $repo): void
+ {
+ $requiredTags = ['development', 'beta', 'release-candidate'];
+
+ try {
+ $existingTags = $this->api->get("/repos/{$org}/{$repo}/tags", ['limit' => 50]);
+ } catch (\Exception $e) {
+ return; // Non-critical
+ }
+
+ $existingNames = array_column($existingTags, 'name');
+
+ // Get default branch to point new tags at
+ try {
+ $repoInfo = $this->api->get("/repos/{$org}/{$repo}");
+ $defaultBranch = $repoInfo['default_branch'] ?? 'main';
+ } catch (\Exception $e) {
+ $defaultBranch = 'main';
+ }
+
+ foreach ($requiredTags as $tagName) {
+ if (in_array($tagName, $existingNames, true)) {
+ continue;
+ }
+
+ try {
+ $this->api->post("/repos/{$org}/{$repo}/tags", [
+ 'tag_name' => $tagName,
+ 'target' => $defaultBranch,
+ 'message' => "Release channel: {$tagName}",
+ ]);
+ $this->log(" 🏷️ Created tag '{$tagName}' on {$repo}", 'INFO');
+ } catch (\Exception $e) {
+ // Non-critical — tag may already exist as a release tag
+ }
+ }
+ }
+
+ /**
* Merge main into all open PR branches (except the sync branch itself).
*
* This ensures feature/development branches stay up to date with the
@@ -1020,6 +1064,39 @@ class BulkSync extends CLIApp
* MokoStandards version that was applied — giving each repo a clear audit
* trail of what was changed and why.
*/
+ /**
+ * Resolve label names to their integer IDs for the Gitea API.
+ * Creates missing labels automatically.
+ *
+ * @param string $org Organization name
+ * @param string $repo Repository name
+ * @param string[] $labelNames Label names to resolve
+ * @return int[] Array of label IDs
+ */
+ private function resolveLabelIds(string $org, string $repo, array $labelNames): array
+ {
+ try {
+ $existing = $this->api->get("/repos/{$org}/{$repo}/labels", ['limit' => 50]);
+ } catch (\Exception $e) {
+ return [];
+ }
+
+ $nameToId = [];
+ foreach ($existing as $label) {
+ $nameToId[$label['name']] = (int) $label['id'];
+ }
+
+ $ids = [];
+ foreach ($labelNames as $name) {
+ if (isset($nameToId[$name])) {
+ $ids[] = $nameToId[$name];
+ }
+ // Skip labels that don't exist (ensureRepoLabels creates them separately)
+ }
+
+ return $ids;
+ }
+
private function createTargetRepoIssue(string $org, string $repo, int $prNumber): ?int
{
$now = gmdate('Y-m-d H:i:s') . ' UTC';
@@ -1058,7 +1135,8 @@ class BulkSync extends CLIApp
// Dedent heredoc
$body = preg_replace('/^ /m', '', $body);
- $labels = ['standards-update', 'mokostandards', 'type: chore', 'automation'];
+ $labelNames = ['standards-update', 'mokostandards', 'type: chore', 'automation'];
+ $labels = $this->resolveLabelIds($org, $repo, $labelNames);
try {
// Check for an existing tracking issue (any state so we can reopen closed ones)
@@ -1072,7 +1150,7 @@ class BulkSync extends CLIApp
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
- $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
+ $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
@@ -1087,7 +1165,7 @@ class BulkSync extends CLIApp
'title' => $title,
'body' => $body,
'labels' => $labels,
- 'assignees' => ['jmiller-moko'],
+ 'assignees' => ['jmiller'],
]);
$num = $issue['number'] ?? '?';
$this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO');
@@ -1211,11 +1289,12 @@ class BulkSync extends CLIApp
'direction'=> 'desc',
]);
- $labels = ['sync-report', 'mokostandards', 'type: chore', 'automation'];
+ $labelNames = ['sync-report', 'mokostandards', 'type: chore', 'automation'];
+ $labels = $this->resolveLabelIds($org, 'MokoStandards', $labelNames);
if (!empty($existing) && isset($existing[0]['number'])) {
$issueNumber = $existing[0]['number'];
- $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
+ $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
@@ -1229,7 +1308,7 @@ class BulkSync extends CLIApp
'title' => $title,
'body' => $body,
'labels' => $labels,
- 'assignees' => ['jmiller-moko'],
+ 'assignees' => ['jmiller'],
]);
$issueNumber = $issue['number'] ?? '?';
$this->log("📋 Sync report issue created: {$org}/MokoStandards#{$issueNumber}", 'INFO');
@@ -1276,7 +1355,7 @@ class BulkSync extends CLIApp
1. Check the local audit log or re-run with `--repos=` to see the specific error.
2. Fix the underlying issue (API token, rate limit, branch protection, etc.).
- 3. Re-run: `php api/automation/bulk_sync.php --org={$org} --repos= --force --yes`
+ 3. Re-run: `php automation/bulk_sync.php --org={$org} --repos= --force --yes`
4. Close this issue once all repos are synced successfully.
---
@@ -1296,7 +1375,7 @@ class BulkSync extends CLIApp
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
- $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
+ $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
@@ -1306,8 +1385,8 @@ class BulkSync extends CLIApp
$issue = $this->api->post("/repos/{$org}/MokoStandards/issues", [
'title' => $title,
'body' => $body,
- 'labels' => ['sync-failure'],
- 'assignees' => ['jmiller-moko'],
+ 'labels' => $this->resolveLabelIds($org, 'MokoStandards', ['sync-failure']),
+ 'assignees' => ['jmiller'],
]);
$num = $issue['number'] ?? '?';
$this->log("🚨 Failure issue created: {$org}/MokoStandards#{$num}", 'WARN');
diff --git a/automation/enforce_tags.sh b/automation/enforce_tags.sh
new file mode 100755
index 0000000..9d4f329
--- /dev/null
+++ b/automation/enforce_tags.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+# =============================================================================
+# enforce_tags.sh — Ensure all repos have the 5 standard release channel tags
+#
+# Standard tags: development, alpha, beta, release-candidate, stable
+# Also removes non-standard tags (keeps vXX production tags)
+#
+# Usage:
+# GA_TOKEN=xxx ./enforce_tags.sh [--dry-run] [--repos repo1,repo2]
+#
+# Called by: bulk-repo-sync.yml, infrastructure-tests/mirror-check.yml
+# =============================================================================
+set -euo pipefail
+
+GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
+ORG="${GITEA_ORG:-MokoConsulting}"
+TOKEN="${GA_TOKEN:?GA_TOKEN required}"
+DRY_RUN=false
+FILTER_REPOS=""
+
+STANDARD_TAGS=("development" "alpha" "beta" "release-candidate" "stable")
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --dry-run) DRY_RUN=true; shift ;;
+ --repos) FILTER_REPOS="$2"; shift 2 ;;
+ *) shift ;;
+ esac
+done
+
+api() {
+ local method="$1" path="$2" data="${3:-}"
+ local args=(-sf -H "Authorization: token $TOKEN" -H "Content-Type: application/json" -X "$method")
+ [[ -n "$data" ]] && args+=(-d "$data")
+ curl "${args[@]}" "$GITEA_URL/api/v1$path" 2>/dev/null
+}
+
+# Get repos
+REPOS=""
+for page in 1 2 3; do
+ BATCH=$(api GET "/orgs/$ORG/repos?limit=50&page=$page" | python3 -c "
+import sys,json
+for r in json.load(sys.stdin):
+ if not r.get(empty) and not r.get(archived):
+ print(r[name])
+" 2>/dev/null)
+ [[ -z "$BATCH" ]] && break
+ REPOS="$REPOS $BATCH"
+done
+
+# Filter if specified
+if [[ -n "$FILTER_REPOS" ]]; then
+ FILTERED=""
+ IFS=, read -ra FILTER_ARR <<< "$FILTER_REPOS"
+ for repo in $REPOS; do
+ for f in "${FILTER_ARR[@]}"; do
+ [[ "$repo" == "$f" ]] && FILTERED="$FILTERED $repo"
+ done
+ done
+ REPOS="$FILTERED"
+fi
+
+TOTAL=$(echo $REPOS | wc -w)
+ADDED=0
+DELETED=0
+ERRORS=0
+
+echo "Enforcing tags on $TOTAL repos (dry_run=$DRY_RUN)"
+
+for repo in $REPOS; do
+ TAGS=$(api GET "/repos/$ORG/$repo/tags?limit=50" | python3 -c "import sys,json; print( .join(t[name] for t in json.load(sys.stdin)))" 2>/dev/null)
+ MAIN_SHA=$(api GET "/repos/$ORG/$repo/branches/main" | python3 -c "import sys,json; print(json.load(sys.stdin)[commit][id])" 2>/dev/null)
+ [[ -z "$MAIN_SHA" ]] && continue
+
+ # Add missing standard tags
+ for st in "${STANDARD_TAGS[@]}"; do
+ if ! echo " $TAGS " | grep -q " $st "; then
+ if [[ "$DRY_RUN" == "true" ]]; then
+ echo " [DRY] ADD $repo: $st"
+ else
+ STATUS=$(api POST "/repos/$ORG/$repo/tags" "{\"tag_name\":\"$st\",\"target\":\"$MAIN_SHA\"}" | python3 -c "import sys,json; print(ok)" 2>/dev/null || echo "err")
+ [[ "$STATUS" == "ok" ]] && ADDED=$((ADDED + 1)) || ERRORS=$((ERRORS + 1))
+ fi
+ fi
+ done
+
+ # Remove non-standard tags
+ for t in $TAGS; do
+ IS_STD=false
+ for st in "${STANDARD_TAGS[@]}"; do [[ "$t" == "$st" ]] && IS_STD=true; done
+ # Keep vXX production tags
+ if [[ "$t" =~ ^v[0-9]{1,3}$ ]]; then IS_STD=true; fi
+
+ if [[ "$IS_STD" == "false" ]]; then
+ if [[ "$DRY_RUN" == "true" ]]; then
+ echo " [DRY] DEL $repo: $t"
+ else
+ # Delete release first if exists
+ api DELETE "/repos/$ORG/$repo/releases/tags/$t" > /dev/null 2>&1 || true
+ api DELETE "/repos/$ORG/$repo/tags/$t" > /dev/null 2>&1
+ DELETED=$((DELETED + 1))
+ echo " DEL $repo: $t"
+ fi
+ fi
+ done
+done
+
+echo "Done: $ADDED added, $DELETED deleted, $ERRORS errors (dry_run=$DRY_RUN)"
diff --git a/automation/enrich_mokostandards_xml.php b/automation/enrich_mokostandards_xml.php
new file mode 100644
index 0000000..69c830e
--- /dev/null
+++ b/automation/enrich_mokostandards_xml.php
@@ -0,0 +1,308 @@
+#!/usr/bin/env php
+
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Enrich XML .mokostandards manifests with repo-specific build, deploy, and script details.
+ *
+ * Runs AFTER push_mokostandards_xml.php. Clones each repo, inspects its contents,
+ * and updates the manifest with discovered build/deploy/scripts config.
+ *
+ * Usage:
+ * php automation/enrich_mokostandards_xml.php [--dry-run] [--repo NAME] [--skip NAME,NAME]
+ *
+ * Note: This script uses proc_open for shell commands. All arguments are escaped
+ * via escapeshellarg(). No user-supplied input reaches the shell unescaped.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use MokoEnterprise\MokoStandardsParser;
+
+$giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/');
+$giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting';
+$token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: '';
+
+$dryRun = in_array('--dry-run', $argv, true);
+$repoFilter = null;
+$skipRepos = [];
+foreach ($argv as $i => $arg) {
+ if ($arg === '--repo' && isset($argv[$i + 1])) $repoFilter = $argv[$i + 1];
+ if ($arg === '--skip' && isset($argv[$i + 1])) $skipRepos = array_map('trim', explode(',', $argv[$i + 1]));
+}
+
+$parser = new MokoStandardsParser();
+$tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid();
+
+function safeExec(string $command, string $cwd = '.'): array {
+ $proc = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $cwd);
+ if (!is_resource($proc)) return [1, "proc_open failed"];
+ $stdout = stream_get_contents($pipes[1]);
+ $stderr = stream_get_contents($pipes[2]);
+ fclose($pipes[1]); fclose($pipes[2]);
+ return [proc_close($proc), trim($stdout . "\n" . $stderr)];
+}
+
+function rmTree(string $dir): void {
+ if (!is_dir($dir)) return;
+ $it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
+ $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($files as $file) {
+ if ($file->isDir()) @rmdir($file->getPathname());
+ else { @chmod($file->getPathname(), 0777); @unlink($file->getPathname()); }
+ }
+ @rmdir($dir);
+}
+
+function gitCmd(string $workDir, string ...$args): array {
+ $cmd = 'git';
+ foreach ($args as $a) $cmd .= ' ' . escapeshellarg($a);
+ return safeExec($cmd, $workDir);
+}
+
+function fetchRepos(string $url, string $org, string $token): array {
+ $repos = []; $page = 1;
+ do {
+ $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50");
+ curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30]);
+ $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
+ if ($code !== 200) break;
+ $batch = json_decode($body, true); if (empty($batch)) break;
+ $repos = array_merge($repos, $batch); $page++;
+ } while (count($batch) >= 50);
+ return $repos;
+}
+
+function inspectRepo(string $workDir, string $platform): array {
+ $enrichment = [];
+ $build = [];
+
+ // Detect entry point
+ if (is_dir("{$workDir}/src")) {
+ foreach (glob("{$workDir}/src/*.xml") ?: [] as $xf) {
+ $c = file_get_contents($xf);
+ if (str_contains($c, ' $pd, 'version' => $composer['require'][$pd], 'type' => 'platform'];
+ }
+ if (isset($composer['require']['mokoconsulting-tech/enterprise']))
+ $deps[] = ['name' => 'mokoconsulting-tech/enterprise', 'version' => $composer['require']['mokoconsulting-tech/enterprise'], 'type' => 'composer'];
+ if (!empty($deps)) $build['dependencies'] = $deps;
+ }
+
+ // Artifact from Makefile
+ if (file_exists("{$workDir}/Makefile")) {
+ $mk = file_get_contents("{$workDir}/Makefile");
+ if (preg_match('/\bdist\/(\S+\.zip)\b/', $mk, $m)) $build['artifact'] = ['format' => 'zip', 'path' => 'dist/', 'filename' => $m[1]];
+ }
+
+ if (!empty($build)) $enrichment['build'] = $build;
+
+ // Deploy targets from workflows
+ $targets = [];
+ $wfDir = is_dir("{$workDir}/.gitea/workflows") ? "{$workDir}/.gitea/workflows" : "{$workDir}/.github/workflows";
+ if (is_dir($wfDir)) {
+ foreach (['deploy-dev', 'deploy-demo', 'deploy-rs'] as $dn) {
+ $wf = "{$wfDir}/{$dn}.yml";
+ if (!file_exists($wf)) continue;
+ $wc = file_get_contents($wf);
+ $t = ['name' => str_replace('deploy-', '', $dn)];
+ if (str_contains($wc, 'sftp') || str_contains($wc, 'SFTP')) $t['method'] = 'sftp';
+ elseif (str_contains($wc, 'rsync')) $t['method'] = 'rsync';
+ if (str_contains($wc, 'src/')) $t['src_dir'] = 'src/';
+ if (preg_match('/branches:\s*\n\s*-\s*["\']?([^"\'}\s]+)/', $wc, $m)) $t['branch'] = $m[1];
+ $targets[] = $t;
+ }
+ }
+ if (!empty($targets)) $enrichment['deploy'] = $targets;
+
+ // Scripts from Makefile + composer
+ $scripts = [];
+ if (file_exists("{$workDir}/Makefile")) {
+ $mk = file_get_contents("{$workDir}/Makefile");
+ $known = ['build'=>'build','test'=>'test','lint'=>'lint','clean'=>'build','package'=>'build','validate'=>'validate','release'=>'release'];
+ if (preg_match_all('/^([a-zA-Z_-]+)\s*:/m', $mk, $matches)) {
+ foreach ($matches[1] as $tgt) {
+ $tl = strtolower($tgt);
+ if (isset($known[$tl])) $scripts[] = ['name'=>$tl, 'phase'=>$known[$tl], 'command'=>"make {$tgt}", 'desc'=>ucfirst($tl).' via make', 'runner'=>'make'];
+ }
+ }
+ }
+ if (file_exists("{$workDir}/composer.json")) {
+ $composer = json_decode(file_get_contents("{$workDir}/composer.json"), true) ?: [];
+ $km = ['test'=>'test','lint'=>'lint','cs'=>'lint','phpcs'=>'lint','phpstan'=>'lint','validate'=>'validate'];
+ foreach ($composer['scripts'] ?? [] as $sn => $cmd) {
+ $sl = strtolower($sn);
+ foreach ($km as $match => $phase) {
+ if (str_contains($sl, $match)) {
+ $exists = false;
+ foreach ($scripts as $s) { if ($s['name'] === $sl) { $exists = true; break; } }
+ if (!$exists) $scripts[] = ['name'=>$sn, 'phase'=>$phase, 'command'=>"composer run {$sn}", 'desc'=>is_string($cmd)?$cmd:"Run {$sn}", 'runner'=>'composer'];
+ break;
+ }
+ }
+ }
+ }
+ if (!empty($scripts)) $enrichment['scripts'] = $scripts;
+
+ return $enrichment;
+}
+
+function enrichManifestXml(string $xml, array $enrichment): string {
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ if (!$dom->loadXML($xml)) return $xml;
+
+ $ns = MokoStandardsParser::NAMESPACE_URI;
+ $root = $dom->documentElement;
+
+ foreach (['build', 'deploy', 'scripts'] as $tag) {
+ $toRemove = [];
+ $existing = $root->getElementsByTagNameNS($ns, $tag);
+ for ($i = 0; $i < $existing->length; $i++) $toRemove[] = $existing->item($i);
+ foreach ($toRemove as $node) $root->removeChild($node);
+ }
+
+ if (!empty($enrichment['build'])) {
+ $build = $dom->createElementNS($ns, 'build');
+ $b = $enrichment['build'];
+ foreach (['language', 'runtime'] as $f) { if (isset($b[$f])) $build->appendChild($dom->createElementNS($ns, $f, htmlspecialchars($b[$f], ENT_XML1))); }
+ if (isset($b['package_type'])) $build->appendChild($dom->createElementNS($ns, 'package-type', htmlspecialchars($b['package_type'], ENT_XML1)));
+ if (isset($b['entry_point'])) $build->appendChild($dom->createElementNS($ns, 'entry-point', htmlspecialchars($b['entry_point'], ENT_XML1)));
+ if (isset($b['artifact'])) {
+ $art = $dom->createElementNS($ns, 'artifact');
+ foreach (['format','path','filename'] as $af) { if (isset($b['artifact'][$af])) $art->appendChild($dom->createElementNS($ns, $af, htmlspecialchars($b['artifact'][$af], ENT_XML1))); }
+ $build->appendChild($art);
+ }
+ if (isset($b['dependencies'])) {
+ $deps = $dom->createElementNS($ns, 'dependencies');
+ foreach ($b['dependencies'] as $d) {
+ $req = $dom->createElementNS($ns, 'requires', '');
+ $req->setAttribute('name', $d['name']);
+ if (isset($d['version'])) $req->setAttribute('version', $d['version']);
+ if (isset($d['type'])) $req->setAttribute('type', $d['type']);
+ $deps->appendChild($req);
+ }
+ $build->appendChild($deps);
+ }
+ $root->appendChild($build);
+ }
+
+ if (!empty($enrichment['deploy'])) {
+ $deploy = $dom->createElementNS($ns, 'deploy');
+ foreach ($enrichment['deploy'] as $t) {
+ $target = $dom->createElementNS($ns, 'target');
+ $target->setAttribute('name', $t['name']);
+ $target->appendChild($dom->createElementNS($ns, 'host', '${{ secrets.' . strtoupper($t['name']) . '_HOST }}'));
+ $target->appendChild($dom->createElementNS($ns, 'path', '${{ secrets.' . strtoupper($t['name']) . '_PATH }}'));
+ if (isset($t['method'])) $target->appendChild($dom->createElementNS($ns, 'method', $t['method']));
+ if (isset($t['branch'])) $target->appendChild($dom->createElementNS($ns, 'branch', htmlspecialchars($t['branch'], ENT_XML1)));
+ if (isset($t['src_dir'])) $target->appendChild($dom->createElementNS($ns, 'src-dir', htmlspecialchars($t['src_dir'], ENT_XML1)));
+ $deploy->appendChild($target);
+ }
+ $root->appendChild($deploy);
+ }
+
+ if (!empty($enrichment['scripts'])) {
+ $scriptsEl = $dom->createElementNS($ns, 'scripts');
+ foreach ($enrichment['scripts'] as $s) {
+ $script = $dom->createElementNS($ns, 'script');
+ $script->setAttribute('name', $s['name']);
+ if (isset($s['phase'])) $script->setAttribute('phase', $s['phase']);
+ $script->appendChild($dom->createElementNS($ns, 'command', htmlspecialchars($s['command'], ENT_XML1)));
+ if (isset($s['desc'])) $script->appendChild($dom->createElementNS($ns, 'description', htmlspecialchars($s['desc'], ENT_XML1)));
+ if (isset($s['runner'])) $script->appendChild($dom->createElementNS($ns, 'runner', htmlspecialchars($s['runner'], ENT_XML1)));
+ $scriptsEl->appendChild($script);
+ }
+ $root->appendChild($scriptsEl);
+ }
+
+ return $dom->saveXML();
+}
+
+// ── Main ─────────────────────────────────────────────────────────────────
+echo "=== MokoStandards XML Manifest Enrichment ===\n";
+echo "Mode: " . ($dryRun ? "DRY RUN" : "LIVE") . "\n";
+if (!empty($skipRepos)) echo "Skipping: " . implode(', ', $skipRepos) . "\n";
+echo "\n";
+
+if (empty($token)) { fprintf(STDERR, "ERROR: GA_TOKEN required\n"); exit(1); }
+
+$repos = fetchRepos($giteaUrl, $giteaOrg, $token);
+echo "Found " . count($repos) . " repositories\n\n";
+
+$stats = ['enriched' => 0, 'skipped' => 0, 'failed' => 0];
+
+foreach ($repos as $repo) {
+ $name = $repo['name'];
+ if ($repoFilter && $name !== $repoFilter) continue;
+ if (in_array($name, $skipRepos, true)) { echo " {$name} ... SKIP (excluded)\n"; $stats['skipped']++; continue; }
+ if ($repo['archived'] ?? false) { $stats['skipped']++; continue; }
+
+ $defaultBranch = $repo['default_branch'] ?? 'main';
+ $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git";
+ $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl);
+
+ echo " {$name} ... ";
+
+ $workDir = "{$tmpBase}/{$name}";
+ @mkdir($workDir, 0755, true);
+ [$ret] = safeExec('git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) . ' ' . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir));
+ if ($ret !== 0) { echo "FAIL (clone)\n"; $stats['failed']++; continue; }
+
+ $manifestPath = "{$workDir}/.gitea/.mokostandards";
+ if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), 'extractPlatform($existingXml) ?? 'default-repository';
+ $enrichment = inspectRepo($workDir, $platform);
+
+ if (!isset($enrichment['build'])) $enrichment['build'] = [];
+ $enrichment['build']['language'] = $enrichment['build']['language'] ?? $repo['language'] ?? MokoStandardsParser::platformLanguage($platform);
+ $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? MokoStandardsParser::platformPackageType($platform);
+
+ $enrichedXml = enrichManifestXml($existingXml, $enrichment);
+ $dc = count($enrichment['deploy'] ?? []);
+ $sc = count($enrichment['scripts'] ?? []);
+ $details = "deploy={$dc} scripts={$sc}";
+
+ if ($dryRun) { echo "WOULD ENRICH [{$details}]\n"; $stats['enriched']++; rmTree($workDir); continue; }
+
+ file_put_contents($manifestPath, $enrichedXml);
+ gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]');
+ gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech');
+ gitCmd($workDir, 'add', '.gitea/.mokostandards');
+
+ [$cr, $co] = gitCmd($workDir, 'commit', '-m', "chore: enrich .mokostandards with build/deploy/scripts\n\nAuto-detected: {$details}");
+ if ($cr !== 0) { echo "SKIP (no diff)\n"; $stats['skipped']++; rmTree($workDir); continue; }
+
+ [$pr] = gitCmd($workDir, 'push', 'origin', $defaultBranch);
+ if ($pr !== 0) { echo "FAIL (push)\n"; $stats['failed']++; }
+ else { echo "ENRICHED [{$details}]\n"; $stats['enriched']++; }
+
+ rmTree($workDir);
+}
+
+@rmdir($tmpBase);
+echo "\n=== Summary ===\nEnriched: {$stats['enriched']}\nSkipped: {$stats['skipped']}\nFailed: {$stats['failed']}\n";
diff --git a/automation/migrate_to_gitea.php b/automation/migrate_to_gitea.php
index 6c9f24d..f0ab6b0 100644
--- a/automation/migrate_to_gitea.php
+++ b/automation/migrate_to_gitea.php
@@ -15,10 +15,10 @@
* BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance
*
* USAGE
- * php api/automation/migrate_to_gitea.php --dry-run
- * php api/automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods
- * php api/automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived
- * php api/automation/migrate_to_gitea.php --resume
+ * php automation/migrate_to_gitea.php --dry-run
+ * php automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods
+ * php automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived
+ * php automation/migrate_to_gitea.php --resume
*/
declare(strict_types=1);
diff --git a/automation/push_files.php b/automation/push_files.php
index abba031..2c671b9 100644
--- a/automation/push_files.php
+++ b/automation/push_files.php
@@ -358,7 +358,7 @@ class PushFiles extends CLIApp
$prBody = $this->buildPRBody($entries);
$pr = $this->adapter->createPullRequest(
$org, $repo, $prTitle, $branch, $defaultBranch, $prBody,
- ['assignees' => ['jmiller-moko']]
+ ['assignees' => ['jmiller']]
);
$prNumber = $pr['number'] ?? null;
$this->log(" 📋 PR #{$prNumber} created", 'INFO');
@@ -512,7 +512,7 @@ class PushFiles extends CLIApp
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
- $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
+ $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
@@ -526,7 +526,7 @@ class PushFiles extends CLIApp
'title' => $title,
'body' => $body,
'labels' => $labels,
- 'assignees' => ['jmiller-moko'],
+ 'assignees' => ['jmiller'],
]);
$num = $issue['number'] ?? null;
$this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO');
@@ -590,7 +590,7 @@ class PushFiles extends CLIApp
1. Check the output above for the specific error per repo.
2. Fix the underlying issue (API token, branch permissions, file path, etc.).
- 3. Re-run: `php api/automation/push_files.php --org={$org} --repos= --files= --yes`
+ 3. Re-run: `php automation/push_files.php --org={$org} --repos= --files= --yes`
4. Close this issue once resolved.
---
@@ -610,7 +610,7 @@ class PushFiles extends CLIApp
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
- $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
+ $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
@@ -621,7 +621,7 @@ class PushFiles extends CLIApp
'title' => $title,
'body' => $body,
'labels' => ['push-failure'],
- 'assignees' => ['jmiller-moko'],
+ 'assignees' => ['jmiller'],
]);
$num = $issue['number'] ?? '?';
$this->log("🚨 Failure issue created: {$org}/MokoStandards#{$num}", 'WARN');
diff --git a/automation/push_mokostandards_xml.php b/automation/push_mokostandards_xml.php
new file mode 100644
index 0000000..23587e9
--- /dev/null
+++ b/automation/push_mokostandards_xml.php
@@ -0,0 +1,308 @@
+#!/usr/bin/env php
+
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Push XML .mokostandards manifest to all governed repositories.
+ *
+ * Uses git SSH to bypass the Gitea reverse-proxy WAF that blocks
+ * API requests to paths containing ".gitea".
+ *
+ * Usage:
+ * php automation/push_mokostandards_xml.php [--dry-run] [--repo NAME] [--force]
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use MokoEnterprise\MokoStandardsParser;
+
+// ── Configuration ────────────────────────────────────────────────────────
+$giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/');
+$giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting';
+$token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: '';
+$sshBase = 'ssh://gitea@git.mokoconsulting.tech:2222';
+
+// ── CLI args ─────────────────────────────────────────────────────────────
+$dryRun = in_array('--dry-run', $argv, true);
+$force = in_array('--force', $argv, true);
+$repoFilter = null;
+$skipRepos = [];
+foreach ($argv as $i => $arg) {
+ if ($arg === '--repo' && isset($argv[$i + 1])) {
+ $repoFilter = $argv[$i + 1];
+ }
+ if ($arg === '--skip' && isset($argv[$i + 1])) {
+ $skipRepos = array_map('trim', explode(',', $argv[$i + 1]));
+ }
+}
+
+$parser = new MokoStandardsParser();
+$tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid();
+
+// ── Platform detection heuristics (mirrors RepositorySynchronizer) ───────
+$CRM_PLATFORM_REPOS = ['MokoDolibarr', 'MokoDoliMods'];
+
+function detectPlatform(array $repo): string {
+ global $CRM_PLATFORM_REPOS;
+ $name = $repo['name'] ?? '';
+ $nameLower = strtolower($name);
+ $description = strtolower($repo['description'] ?? '');
+ $topics = $repo['topics'] ?? [];
+
+ if (in_array($name, $CRM_PLATFORM_REPOS, true)) return 'crm-platform';
+ if (in_array('dolibarr-platform', $topics)) return 'crm-platform';
+ if (in_array('joomla-template', $topics)) return 'joomla-template';
+ if (in_array('joomla', $topics) || in_array('joomla-extension', $topics)) return 'waas-component';
+ if (in_array('dolibarr', $topics) || in_array('dolibarr-module', $topics)) return 'crm-module';
+
+ if (str_contains($nameLower, 'template') && (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'tpl'))) return 'joomla-template';
+ if (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'waas')) return 'waas-component';
+ if (str_contains($nameLower, 'doli') || str_contains($nameLower, 'crm')) return 'crm-module';
+
+ if (str_contains($description, 'joomla template')) return 'joomla-template';
+ if (str_contains($description, 'joomla') || str_contains($description, 'component')) return 'waas-component';
+ if (str_contains($description, 'dolibarr') || str_contains($description, 'module')) return 'crm-module';
+
+ if (str_contains($nameLower, 'standard')) return 'standards-repository';
+ return 'default-repository';
+}
+
+/**
+ * Safe shell execution — uses proc_open with explicit arguments to avoid injection.
+ * @return array{int, string}
+ */
+function safeExec(string $command, string $cwd = '.'): array {
+ $proc = proc_open(
+ $command,
+ [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes,
+ $cwd
+ );
+ if (!is_resource($proc)) {
+ return [1, "proc_open failed for: {$command}"];
+ }
+ $stdout = stream_get_contents($pipes[1]);
+ $stderr = stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ $code = proc_close($proc);
+ return [$code, trim($stdout . "\n" . $stderr)];
+}
+
+/** Recursively remove a directory (cross-platform). */
+function rmTree(string $dir): void {
+ if (!is_dir($dir)) return;
+ $it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
+ $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($files as $file) {
+ if ($file->isDir()) {
+ @rmdir($file->getPathname());
+ } else {
+ // Clear read-only flag (git objects on Windows)
+ @chmod($file->getPathname(), 0777);
+ @unlink($file->getPathname());
+ }
+ }
+ @rmdir($dir);
+}
+
+/**
+ * Run a git command safely in a given working directory.
+ * @return array{int, string}
+ */
+function gitCmd(string $workDir, string ...$args): array {
+ $cmd = 'git';
+ foreach ($args as $a) {
+ $cmd .= ' ' . escapeshellarg($a);
+ }
+ return safeExec($cmd, $workDir);
+}
+
+// ── Fetch all repos via API ──────────────────────────────────────────────
+function fetchRepos(string $url, string $org, string $token): array {
+ $repos = [];
+ $page = 1;
+ do {
+ $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50");
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
+ CURLOPT_TIMEOUT => 30,
+ ]);
+ $body = curl_exec($ch);
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($code !== 200) {
+ fprintf(STDERR, "API error (HTTP %d) fetching repos page %d\n", $code, $page);
+ break;
+ }
+
+ $batch = json_decode($body, true);
+ if (empty($batch)) break;
+ $repos = array_merge($repos, $batch);
+ $page++;
+ } while (count($batch) >= 50);
+
+ return $repos;
+}
+
+// ── Main ─────────────────────────────────────────────────────────────────
+echo "=== MokoStandards XML Manifest Push ===\n";
+echo "Org: {$giteaOrg}\n";
+echo "Mode: " . ($dryRun ? "DRY RUN" : "LIVE") . "\n";
+if ($repoFilter) echo "Filter: {$repoFilter}\n";
+echo "\n";
+
+if (empty($token)) {
+ fprintf(STDERR, "ERROR: GA_TOKEN or GH_TOKEN environment variable required\n");
+ exit(1);
+}
+
+$repos = fetchRepos($giteaUrl, $giteaOrg, $token);
+echo "Found " . count($repos) . " repositories\n\n";
+
+$stats = ['created' => 0, 'updated' => 0, 'skipped' => 0, 'failed' => 0];
+
+foreach ($repos as $repo) {
+ $name = $repo['name'];
+ if ($repoFilter && $name !== $repoFilter) continue;
+ if (in_array($name, $skipRepos, true)) {
+ echo " SKIP {$name} (excluded)\n";
+ $stats['skipped']++;
+ continue;
+ }
+ if ($repo['archived'] ?? false) {
+ echo " SKIP {$name} (archived)\n";
+ $stats['skipped']++;
+ continue;
+ }
+
+ $platform = detectPlatform($repo);
+ $defaultBranch = $repo['default_branch'] ?? 'main';
+ // Prefer HTTPS with token (SSH port 2222 may be blocked); fall back to SSH
+ $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git";
+ // Embed token in HTTPS URL for push auth
+ $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl);
+
+ echo " {$name} [{$platform}] ... ";
+
+ // Generate XML manifest
+ $xmlContent = $parser->generate([
+ 'name' => $name,
+ 'org' => $giteaOrg,
+ 'platform' => $platform,
+ 'standards_version' => '04.07.00',
+ 'description' => $repo['description'] ?? '',
+ 'license' => 'GPL-3.0-or-later',
+ 'topics' => $repo['topics'] ?? [],
+ 'language' => $repo['language'] ?? MokoStandardsParser::platformLanguage($platform),
+ 'package_type' => MokoStandardsParser::platformPackageType($platform),
+ 'last_synced' => date('c'),
+ ]);
+
+ if ($dryRun) {
+ echo "WOULD WRITE ({$platform})\n";
+ $stats['created']++;
+ continue;
+ }
+
+ // Clone shallow via HTTPS (token-authed)
+ $workDir = "{$tmpBase}/{$name}";
+ @mkdir($workDir, 0755, true);
+
+ [$ret, $out] = safeExec(
+ 'git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) . ' '
+ . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir)
+ );
+ if ($ret !== 0) {
+ echo "FAIL (clone)\n";
+ fprintf(STDERR, " %s\n", $out);
+ $stats['failed']++;
+ continue;
+ }
+
+ // Check if already XML and up-to-date
+ $manifestPath = "{$workDir}/.gitea/.mokostandards";
+ $existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), 'extractPlatform(file_get_contents($manifestPath));
+ if ($existingPlatform === $platform) {
+ echo "SKIP (already XML)\n";
+ $stats['skipped']++;
+ rmTree($workDir);
+ continue;
+ }
+ }
+
+ // Write manifest
+ @mkdir("{$workDir}/.gitea", 0755, true);
+ file_put_contents($manifestPath, $xmlContent);
+
+ // Delete legacy files if present
+ $legacyDeleted = [];
+ foreach (['.mokostandards', '.github/.mokostandards'] as $legacy) {
+ $legacyPath = "{$workDir}/{$legacy}";
+ if (file_exists($legacyPath)) {
+ unlink($legacyPath);
+ $legacyDeleted[] = $legacy;
+ }
+ }
+
+ // Commit
+ $isNew = !$existingIsXml;
+ $commitMsg = $isNew
+ ? 'chore: add XML .mokostandards manifest'
+ : 'chore: update .mokostandards to XML format';
+ if (!empty($legacyDeleted)) {
+ $commitMsg .= "\n\nRemoved legacy: " . implode(', ', $legacyDeleted);
+ }
+
+ gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]');
+ gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech');
+ gitCmd($workDir, 'add', '.gitea/.mokostandards');
+ foreach ($legacyDeleted as $lf) {
+ gitCmd($workDir, 'add', $lf);
+ }
+
+ [$commitRet, $commitOut] = gitCmd($workDir, 'commit', '-m', $commitMsg);
+ if ($commitRet !== 0 && str_contains($commitOut, 'nothing to commit')) {
+ echo "SKIP (no changes)\n";
+ $stats['skipped']++;
+ rmTree($workDir);
+ continue;
+ }
+ if ($commitRet !== 0) {
+ echo "FAIL (commit)\n";
+ fprintf(STDERR, " %s\n", $commitOut);
+ $stats['failed']++;
+ rmTree($workDir);
+ continue;
+ }
+
+ [$pushRet, $pushOut] = gitCmd($workDir, 'push', 'origin', $defaultBranch);
+ if ($pushRet !== 0) {
+ echo "FAIL (push)\n";
+ fprintf(STDERR, " %s\n", $pushOut);
+ $stats['failed']++;
+ } else {
+ $action = $isNew ? 'CREATED' : 'UPDATED';
+ echo "{$action}\n";
+ $stats[$isNew ? 'created' : 'updated']++;
+ }
+
+ // Cleanup
+ rmTree($workDir);
+}
+
+// Cleanup tmp base
+@rmdir($tmpBase);
+
+echo "\n=== Summary ===\n";
+echo "Created: {$stats['created']}\n";
+echo "Updated: {$stats['updated']}\n";
+echo "Skipped: {$stats['skipped']}\n";
+echo "Failed: {$stats['failed']}\n";
diff --git a/cli/archive_repo.php b/cli/archive_repo.php
index 65ed6b8..698955a 100644
--- a/cli/archive_repo.php
+++ b/cli/archive_repo.php
@@ -15,9 +15,9 @@
* BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def
*
* USAGE
- * php api/cli/archive_repo.php --repo MokoOldModule
- * php api/cli/archive_repo.php --repo MokoOldModule --dry-run
- * php api/cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open
+ * php cli/archive_repo.php --repo MokoOldModule
+ * php cli/archive_repo.php --repo MokoOldModule --dry-run
+ * php cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open
*/
declare(strict_types=1);
@@ -143,7 +143,7 @@ if (!$dryRun) {
"## Repository Archived\n\n**Repository:** `{$org}/{$repoName}`\n**Archived:** {$now}\n**Platform:** {$platformName}\n**Sync definition removed:** yes\n\n---\n*Auto-created by `archive_repo.php`*\n",
[
'labels' => ['type: chore', 'automation', 'archived'],
- 'assignees' => ['jmiller-moko'],
+ 'assignees' => ['jmiller'],
]
);
if (isset($issue['number'])) { echo " Archival record: MokoStandards#{$issue['number']}\n"; }
diff --git a/cli/create_project.php b/cli/create_project.php
index 85d6e5e..ae4bb9d 100644
--- a/cli/create_project.php
+++ b/cli/create_project.php
@@ -15,10 +15,10 @@
* BRIEF: Create baseline GitHub Projects for repositories with standard fields and views
*
* USAGE
- * php api/cli/create_project.php --repo MokoCRM # Auto-detect type, create project
- * php api/cli/create_project.php --repo MokoCRM --type dolibarr # Force type
- * php api/cli/create_project.php --org mokoconsulting-tech --all # All repos without projects
- * php api/cli/create_project.php --repo MokoCRM --dry-run # Preview without changes
+ * php cli/create_project.php --repo MokoCRM # Auto-detect type, create project
+ * php cli/create_project.php --repo MokoCRM --type dolibarr # Force type
+ * php cli/create_project.php --org mokoconsulting-tech --all # All repos without projects
+ * php cli/create_project.php --repo MokoCRM --dry-run # Preview without changes
*/
declare(strict_types=1);
@@ -385,7 +385,7 @@ function createProject(
updateProjectV2(input: {
projectId: $projectId,
shortDescription: $shortDescription,
- readme: "Managed by MokoStandards. Run `php api/cli/create_project.php` to regenerate."
+ readme: "Managed by MokoStandards. Run `php cli/create_project.php` to regenerate."
}) {
projectV2 { id }
}
diff --git a/cli/create_repo.php b/cli/create_repo.php
index 5e59bda..2812d80 100644
--- a/cli/create_repo.php
+++ b/cli/create_repo.php
@@ -15,9 +15,9 @@
* BRIEF: Scaffold a new governed repository with full MokoStandards baseline
*
* USAGE
- * php api/cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module"
- * php api/cli/create_repo.php --name MokoNewModule --type joomla --private
- * php api/cli/create_repo.php --name MokoNewModule --type generic --dry-run
+ * php cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module"
+ * php cli/create_repo.php --name MokoNewModule --type joomla --private
+ * php cli/create_repo.php --name MokoNewModule --type generic --dry-run
*/
declare(strict_types=1);
@@ -229,7 +229,7 @@ if (!$dryRun) {
if (file_exists($syncScript)) {
passthru("php " . escapeshellarg($syncScript) . " --repos " . escapeshellarg($name) . " --force --yes");
} else {
- echo " Run manually: php api/automation/bulk_sync.php --repos {$name} --force --yes\n";
+ echo " Run manually: php automation/bulk_sync.php --repos {$name} --force --yes\n";
}
} else {
echo " (dry-run) would run initial sync\n";
@@ -242,7 +242,7 @@ if (!$dryRun) {
if (file_exists($projectScript)) {
passthru("php " . escapeshellarg($projectScript) . " --repo " . escapeshellarg($name) . " --type " . escapeshellarg($type));
} else {
- echo " Run manually: php api/cli/create_project.php --repo {$name} --type {$type}\n";
+ echo " Run manually: php cli/create_project.php --repo {$name} --type {$type}\n";
}
} else {
echo " (dry-run) would create Project\n";
diff --git a/cli/joomla_release.php b/cli/joomla_release.php
index 3ceeb0a..487e29b 100644
--- a/cli/joomla_release.php
+++ b/cli/joomla_release.php
@@ -15,10 +15,10 @@
* BRIEF: Joomla release pipeline — build ZIP+tar.gz, upload to GitHub Release, update updates.xml
*
* USAGE
- * php api/cli/joomla_release.php --repo MokoCassiopeia --stability stable
- * php api/cli/joomla_release.php --repo MokoCassiopeia --stability development
- * php api/cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run
- * php api/cli/joomla_release.php --path /local/repo --stability stable
+ * php cli/joomla_release.php --repo MokoCassiopeia --stability stable
+ * php cli/joomla_release.php --repo MokoCassiopeia --stability development
+ * php cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run
+ * php cli/joomla_release.php --path /local/repo --stability stable
*/
declare(strict_types=1);
diff --git a/cli/release.php b/cli/release.php
index 05b29e1..0a0e7c2 100644
--- a/cli/release.php
+++ b/cli/release.php
@@ -13,10 +13,10 @@
* BRIEF: Automate the MokoStandards version branch release flow
*
* USAGE
- * php api/cli/release.php # Release current version
- * php api/cli/release.php --bump minor # Bump minor, then release
- * php api/cli/release.php --bump major # Bump major, then release
- * php api/cli/release.php --dry-run # Preview without changes
+ * php cli/release.php # Release current version
+ * php cli/release.php --bump minor # Bump minor, then release
+ * php cli/release.php --bump major # Bump major, then release
+ * php cli/release.php --dry-run # Preview without changes
*/
declare(strict_types=1);
@@ -30,7 +30,7 @@ foreach ($argv as $i => $arg) {
}
$repoRoot = dirname(__DIR__, 2);
-$syncFile = "{$repoRoot}/api/lib/Enterprise/RepositorySynchronizer.php";
+$syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php";
// Check both workflow directories for the bulk-repo-sync workflow
$bulkSyncFile = file_exists("{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml")
? "{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml"
diff --git a/cli/sync_rulesets.php b/cli/sync_rulesets.php
index 47fdef6..6d1ab67 100644
--- a/cli/sync_rulesets.php
+++ b/cli/sync_rulesets.php
@@ -15,10 +15,10 @@
* BRIEF: Apply branch protection rules to all repos via platform adapter
*
* USAGE
- * php api/cli/sync_rulesets.php # Apply to all repos
- * php api/cli/sync_rulesets.php --repo MokoCRM # Single repo
- * php api/cli/sync_rulesets.php --dry-run # Preview only
- * php api/cli/sync_rulesets.php --delete # Remove then re-apply
+ * php cli/sync_rulesets.php # Apply to all repos
+ * php cli/sync_rulesets.php --repo MokoCRM # Single repo
+ * php cli/sync_rulesets.php --dry-run # Preview only
+ * php cli/sync_rulesets.php --delete # Remove then re-apply
*
* NOTE: On GitHub, this creates rulesets via the rulesets API.
* On Gitea, this creates branch_protections via the branch protection API.
diff --git a/composer.json b/composer.json
index 5fce8b0..72eef53 100644
--- a/composer.json
+++ b/composer.json
@@ -13,26 +13,26 @@
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
- "php": ">=8.1",
- "ext-json": "*",
"ext-curl": "*",
+ "ext-json": "*",
+ "ext-zip": "*",
"guzzlehttp/guzzle": "^7.8",
"monolog/monolog": "^3.5",
+ "php": ">=8.1",
+ "phpseclib/phpseclib": "^3.0",
+ "psr/cache": "^3.0",
+ "psr/http-client": "^1.0",
+ "psr/log": "^3.0",
+ "symfony/cache": "^6.4",
"symfony/console": "^6.4",
- "symfony/yaml": "^6.4",
"symfony/filesystem": "^6.4",
- "symfony/process": "^6.4",
"symfony/finder": "^6.4",
"symfony/http-foundation": "^6.4",
+ "symfony/process": "^6.4",
"symfony/routing": "^6.4",
- "symfony/cache": "^6.4",
+ "symfony/yaml": "^6.4",
"twig/twig": "^3.8",
- "vlucas/phpdotenv": "^5.6",
- "psr/log": "^3.0",
- "psr/http-client": "^1.0",
- "psr/cache": "^3.0",
- "phpseclib/phpseclib": "^3.0",
- "ext-zip": "*"
+ "vlucas/phpdotenv": "^5.6"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
@@ -73,8 +73,8 @@
],
"scripts": {
"test": "phpunit",
- "phpcs": "phpcs --standard=phpcs.xml api/",
- "phpstan": "phpstan analyse -c phpstan.neon api/",
+ "phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/",
+ "phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/",
"psalm": "psalm --config=psalm.xml",
"check": [
"@phpcs",
diff --git a/definitions/default/client-site.tf b/definitions/default/client-site.tf
new file mode 100644
index 0000000..5f29420
--- /dev/null
+++ b/definitions/default/client-site.tf
@@ -0,0 +1,223 @@
+/**
+ * Client Joomla Site Structure Definition
+ * Standard repository structure for client Joomla site projects
+ *
+ * Copyright (C) 2026 Moko Consulting
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * Version: 01.00.00
+ * Schema Version: 1.0
+ */
+
+locals {
+ repository_structure = {
+ metadata = {
+ name = "Client Joomla Site"
+ description = "Standard repository structure for client Joomla site projects (overrides, media, configuration)"
+ repository_type = "client-site"
+ platform = "mokowaas"
+ last_updated = "2026-05-04T00:00:00Z"
+ maintainer = "Moko Consulting"
+ version = "01.00.00"
+ schema_version = "1.0"
+ template_repo = "MokoConsulting/MokoStandards-Template-Client"
+ }
+
+ root_files = [
+ {
+ name = "README.md"
+ description = "Client project documentation"
+ required = true
+ always_overwrite = false
+ },
+ {
+ name = "LICENSE"
+ description = "License file (GPL-3.0-or-later)"
+ required = true
+ },
+ {
+ name = "CHANGELOG.md"
+ description = "Version history and changes"
+ required = true
+ },
+ {
+ name = "SECURITY.md"
+ description = "Security policy and vulnerability reporting"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "CODE_OF_CONDUCT.md"
+ description = "Community code of conduct"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "CONTRIBUTING.md"
+ description = "Contribution guidelines"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "Makefile"
+ description = "Build automation"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "composer.json"
+ description = "PHP dependency management"
+ required = true
+ always_overwrite = false
+ },
+ {
+ name = "phpstan.neon"
+ description = "PHPStan static analysis config"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "codeception.yml"
+ description = "Codeception test framework config"
+ required = false
+ always_overwrite = false
+ },
+ {
+ name = ".editorconfig"
+ description = "Editor configuration for consistent coding style"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = ".gitignore"
+ description = "Git ignore patterns for client site projects"
+ required = true
+ always_overwrite = false
+ },
+ ]
+
+ // NOTE: Client sites do NOT have updates.xml — they are not installable extensions
+
+ subdirectories = [
+ {
+ name = "src"
+ description = "Site source files — template overrides, custom code, and configuration"
+ required = true
+ files = []
+ },
+ {
+ name = "src/images"
+ description = "Site images — branding, headers, event photos, profiles"
+ required = true
+ files = []
+ },
+ {
+ name = "src/media"
+ description = "Media assets — uploaded files, documents"
+ required = true
+ files = []
+ },
+ {
+ name = "docs"
+ description = "Client-specific documentation — brand reference, deployment, migration plans"
+ required = true
+ files = []
+ },
+ {
+ name = "scripts"
+ description = "Deployment and maintenance scripts"
+ required = false
+ files = []
+ },
+ {
+ name = "tests"
+ description = "Acceptance and unit tests"
+ required = false
+ files = []
+ },
+ {
+ name = ".gitea/workflows"
+ description = "Gitea Actions CI/CD workflows (10 workflows — no update-server)"
+ required = true
+ files = [
+ {
+ name = "auto-release.yml"
+ description = "Stable release on PR merge to main"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "pre-release.yml"
+ description = "Manual pre-release for dev/alpha/beta/rc channels"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "ci-joomla.yml"
+ description = "PHP lint, PHPStan, coding standards"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "pr-check.yml"
+ description = "PR gate — validates code quality before merge"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "deploy-manual.yml"
+ description = "Manual SFTP deploy to selected environment"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "repo-health.yml"
+ description = "Repository health checks"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "security-audit.yml"
+ description = "Dependency vulnerability scanning"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "notify.yml"
+ description = "ntfy push notifications on release success or failure"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "cleanup.yml"
+ description = "Weekly merged branch + old run cleanup"
+ required = true
+ always_overwrite = true
+ },
+ {
+ name = "sync-media.yml"
+ description = "Bidirectional SFTP sync for images/, files/, media/ between dev and production"
+ required = true
+ always_overwrite = true
+ },
+ ]
+ },
+ ]
+
+ // Per-repo variables required for sync-media.yml
+ required_variables = [
+ { name = "DEV_SYNC_HOST", description = "Dev server hostname" },
+ { name = "DEV_SYNC_PORT", description = "Dev SSH port (default 22)" },
+ { name = "DEV_SYNC_USERNAME", description = "Dev server username" },
+ { name = "DEV_SYNC_PATH", description = "Base path on dev server" },
+ { name = "PROD_SYNC_HOST", description = "Production server hostname" },
+ { name = "PROD_SYNC_PORT", description = "Production SSH port (default 22)" },
+ { name = "PROD_SYNC_USERNAME", description = "Production server username" },
+ { name = "PROD_SYNC_PATH", description = "Base path on production server" },
+ ]
+
+ required_secrets = [
+ { name = "DEV_SYNC_KEY", description = "SSH private key for dev server" },
+ { name = "PROD_SYNC_KEY", description = "SSH private key for production server" },
+ ]
+ }
+}
diff --git a/definitions/default/crm-module.tf b/definitions/default/crm-module.tf
index 637908a..e34ddd9 100644
--- a/definitions/default/crm-module.tf
+++ b/definitions/default/crm-module.tf
@@ -167,13 +167,14 @@ EOT
audience = "developer"
},
{
- name = ".mokostandards"
- extension = "yml"
- description = "MokoStandards governance attachment — links this repo back to the standards source"
+ name = ".gitea/.mokostandards"
+ extension = "xml"
+ description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
- always_overwrite = true
+ always_overwrite = false
audience = "developer"
- template = "templates/configs/mokostandards.yml.template"
+ template = "managed-by-sync"
+ source_type = "programmatic"
},
{
name = "GOVERNANCE.md"
diff --git a/definitions/default/crm-platform.tf b/definitions/default/crm-platform.tf
index b68190c..3e8a962 100644
--- a/definitions/default/crm-platform.tf
+++ b/definitions/default/crm-platform.tf
@@ -84,12 +84,13 @@ locals {
template = "templates/configs/ftp_ignore"
},
{
- name = ".mokostandards"
- extension = ""
- description = "MokoStandards platform identifier"
+ name = ".gitea/.mokostandards"
+ extension = "xml"
+ description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
- always_overwrite = true
- template = "templates/configs/mokostandards.yml.template"
+ always_overwrite = false
+ template = "managed-by-sync"
+ source_type = "programmatic"
}
]
diff --git a/definitions/default/default-repository.json b/definitions/default/default-repository.json
index d5ae941..9407d58 100644
--- a/definitions/default/default-repository.json
+++ b/definitions/default/default-repository.json
@@ -142,15 +142,24 @@
{
"name": ".github",
"path": ".github",
- "description": "GitHub-specific configuration",
+ "description": "Gitea/GitHub Actions configuration (Gitea reads .github/workflows natively)",
"requirementStatus": "required",
- "purpose": "Contains GitHub Actions workflows and configuration",
+ "purpose": "Contains CI/CD workflows and repository configuration. Gitea is the primary platform; GitHub is backup only.",
"subdirectories": [
{
"name": "workflows",
"path": ".github/workflows",
- "description": "GitHub Actions workflows",
- "requirementStatus": "suggested"
+ "description": "CI/CD workflows (Gitea-primary, GitHub-compatible)",
+ "requirementStatus": "required",
+ "requiredFiles": [
+ "auto-assign.yml",
+ "auto-dev-issue.yml",
+ "auto-release.yml",
+ "branch-freeze.yml",
+ "changelog-validation.yml",
+ "repository-cleanup.yml",
+ "sync-version-on-merge.yml"
+ ]
}
]
},
diff --git a/definitions/default/generic-repository.tf b/definitions/default/generic-repository.tf
index 38ee157..0d36b5f 100644
--- a/definitions/default/generic-repository.tf
+++ b/definitions/default/generic-repository.tf
@@ -119,13 +119,14 @@ locals {
template = "templates/configs/composer.generic.json"
},
{
- name = ".mokostandards"
- extension = "yml"
- description = "MokoStandards governance attachment — links this repo back to the standards source"
+ name = ".gitea/.mokostandards"
+ extension = "xml"
+ description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
- always_overwrite = true
+ always_overwrite = false
audience = "developer"
- template = "templates/configs/mokostandards.yml.template"
+ template = "managed-by-sync"
+ source_type = "programmatic"
},
{
name = "GOVERNANCE.md"
diff --git a/definitions/default/github-private-repository.tf b/definitions/default/github-private-repository.tf
index 4757c32..9f1a624 100644
--- a/definitions/default/github-private-repository.tf
+++ b/definitions/default/github-private-repository.tf
@@ -116,12 +116,13 @@ locals {
audience = "developer"
},
{
- name = ".mokostandards.yml"
- extension = "yml"
- description = "MokoStandards governance marker — identifies this repo as platform=github-private"
+ name = ".gitea/.mokostandards"
+ extension = "xml"
+ description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
- always_overwrite = true
- template = "templates/configs/mokostandards.yml.template"
+ always_overwrite = false
+ template = "managed-by-sync"
+ source_type = "programmatic"
}
]
diff --git a/definitions/default/joomla-template.tf b/definitions/default/joomla-template.tf
index a35083e..ca08e68 100644
--- a/definitions/default/joomla-template.tf
+++ b/definitions/default/joomla-template.tf
@@ -132,7 +132,7 @@ locals {
https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/raw/branch/main/updates.xml
- https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml
+ https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/main/updates.xml
@@ -179,7 +179,7 @@ locals {
https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip
- https://github.com/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip
+ https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip
@@ -417,6 +417,83 @@ locals {
required = false
files = []
},
+ {
+ name = ".gitea/workflows"
+ description = "Gitea Actions CI/CD workflows"
+ required = true
+ files = [
+ {
+ name = "auto-release.yml"
+ description = "Automated release — builds zip, creates Gitea release, updates SHA in updates.xml. Triggered by push to main (stable) or pre-release tags (development, alpha, beta, rc)"
+ required = true
+ always_overwrite = true
+ template = "workflows/auto-release.yml"
+ },
+ {
+ name = "ci-joomla.yml"
+ description = "Continuous integration — PHP linting, PHPStan static analysis, coding standards checks"
+ required = true
+ always_overwrite = true
+ template = "workflows/ci-joomla.yml"
+ },
+ {
+ name = "pre-release.yml"
+ description = "Manual pre-release — builds dev/alpha/beta/rc packages with patch version bump"
+ required = true
+ always_overwrite = true
+ template = "workflows/pre-release.yml"
+ },
+ {
+ name = "deploy-manual.yml"
+ description = "Manual deployment — allows selecting target environment and branch for on-demand deploys"
+ required = true
+ always_overwrite = true
+ template = "workflows/deploy-manual.yml"
+ },
+ {
+ name = "repo-health.yml"
+ description = "Repository health checks — validates required files, structure compliance, and standards alignment"
+ required = true
+ always_overwrite = true
+ template = "workflows/repo-health.yml"
+ },
+ {
+ name = "update-server.yml"
+ description = "Update server maintenance — validates updates.xml format and ensures download URLs are reachable"
+ required = true
+ always_overwrite = true
+ template = "workflows/update-server.yml"
+ },
+ {
+ name = "pr-check.yml"
+ description = "PR gate — validates PHP syntax, manifest XML, and package build before merge to main"
+ required = true
+ always_overwrite = true
+ template = "workflows/pr-check.yml"
+ },
+ {
+ name = "security-audit.yml"
+ description = "Dependency vulnerability scanning — weekly schedule and on PR when lock files change"
+ required = true
+ always_overwrite = true
+ template = "workflows/security-audit.yml"
+ },
+ {
+ name = "notify.yml"
+ description = "Push notifications via ntfy on release success or workflow failure"
+ required = true
+ always_overwrite = true
+ template = "workflows/notify.yml"
+ },
+ {
+ name = "cleanup.yml"
+ description = "Scheduled cleanup — delete merged branches and old workflow runs weekly"
+ required = true
+ always_overwrite = true
+ template = "workflows/cleanup.yml"
+ },
+ ]
+ },
]
}
}
diff --git a/definitions/default/standards-repository.tf b/definitions/default/standards-repository.tf
index 0706998..cd4732a 100644
--- a/definitions/default/standards-repository.tf
+++ b/definitions/default/standards-repository.tf
@@ -199,12 +199,14 @@ locals {
audience = "developer"
},
{
- name = ".mokostandards"
- extension = ""
- description = "MokoStandards sync tracking file — records last sync date, version, and compliance status"
+ name = ".gitea/.mokostandards"
+ extension = "xml"
+ description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
requirement_status = "required"
- always_overwrite = true
+ always_overwrite = false
audience = "developer"
+ template = "managed-by-sync"
+ source_type = "programmatic"
}
]
diff --git a/definitions/default/waas-component.tf b/definitions/default/waas-component.tf
index 55d6cd1..7756ec2 100644
--- a/definitions/default/waas-component.tf
+++ b/definitions/default/waas-component.tf
@@ -83,13 +83,13 @@ locals {
audience = "contributor"
},
{
- name = "update.xml"
+ name = "updates.xml"
extension = "xml"
- description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
+ description = "Joomla extension update server manifest — lists releases for Joomla auto-update; managed by release workflow, never overwritten by sync"
required = true
always_overwrite = false
+ protected = true
audience = "developer"
- template = "templates/joomla/update.xml.template"
stub_content = <<-MOKO_END
01.02.04
-
@@ -582,7 +583,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
+ ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -611,22 +612,22 @@ locals {
---
- ## update.xml — Required in Repo Root
+ ## updates.xml — Required in Repo Root
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
+ `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
+ - Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below.
+ - The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x.
@@ -635,8 +636,8 @@ locals {
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
+ - `` tag must be kept in sync with `README.md` version and `updates.xml`.
+ - Must include `` block pointing to this repo's `updates.xml`.
- Must include `` and `` sections.
- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
@@ -644,16 +645,16 @@ locals {
## GitHub Actions — Token Usage
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
+ Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases).
```yaml
# ✅ Correct
- uses: actions/checkout@v4
with:
- token: ${{ secrets.GH_TOKEN }}
+ token: ${{ secrets.GA_TOKEN }}
env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
+ GA_TOKEN: ${{ secrets.GA_TOKEN }}
```
```yaml
@@ -666,16 +667,16 @@ locals {
## MokoStandards Reference
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies:
+ This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies:
| Document | Purpose |
|----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
+ | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
+ | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
+ | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
+ | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
+ | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
+ | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
---
@@ -714,8 +715,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
+ | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
+ | New release | Prepend `` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -728,8 +729,8 @@ locals {
- Never skip the FILE INFORMATION block on a new file
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
+ - Use `secrets.GA_TOKEN` for Gitea operations. Use `secrets.GH_TOKEN` only for GitHub mirror (stable/RC). Never use `secrets.GITHUB_TOKEN` directly
+ - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END
},
{
@@ -762,7 +763,7 @@ locals {
> | Placeholder | Where to find the value |
> |---|---|
> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
+ > | `{{REPO_URL}}` | Full Gitea URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/` |
> | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description |
> | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
@@ -780,7 +781,7 @@ locals {
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
Repository URL: {{REPO_URL}}
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
+ This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
---
@@ -789,7 +790,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
+ ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -839,32 +840,32 @@ locals {
|------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
+ | `updates.xml` | `` in the most recent `` block |
The `make release` command / release workflow syncs all three automatically.
---
- # update.xml — Required in Repo Root
+ # updates.xml — Required in Repo Root
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
+ `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
+ - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`.
- `` must be a publicly accessible GitHub Releases asset URL.
- `` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x.
- Example `update.xml` entry for a new release:
+ Example `updates.xml` entry for a new release:
```xml
@@ -948,16 +949,16 @@ locals {
# GitHub Actions — Token Usage
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
+ Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases).
```yaml
# ✅ Correct
- uses: actions/checkout@v4
with:
- token: ${{ secrets.GH_TOKEN }}
+ token: ${{ secrets.GA_TOKEN }}
env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
+ GA_TOKEN: ${{ secrets.GA_TOKEN }}
```
```yaml
@@ -973,8 +974,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
+ | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
+ | New release | Prepend `` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -985,7 +986,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
+ - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -999,7 +1000,7 @@ locals {
Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
+ - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated
- [ ] Tests pass
@@ -1010,117 +1011,109 @@ locals {
| Document | Purpose |
|----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
+ | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
+ | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
+ | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
+ | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
+ | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
+ | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
MOKO_END
}
]
subdirectories = [
{
name = "workflows"
- path = ".github/workflows"
- description = "GitHub Actions workflows"
+ path = ".gitea/workflows"
+ description = "Gitea Actions CI/CD workflows"
requirement_status = "required"
files = [
- {
- name = "ci-joomla.yml"
- extension = "yml"
- description = "Joomla-specific CI workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/joomla/ci-joomla.yml.template"
- },
- {
- name = "codeql-analysis.yml"
- extension = "yml"
- description = "CodeQL security analysis workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/generic/codeql-analysis.yml.template"
- },
- {
- name = "standards-compliance.yml"
- extension = "yml"
- description = "MokoStandards compliance validation"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/workflows/standards-compliance.yml"
- },
- {
- name = "enterprise-firewall-setup.yml"
- extension = "yml"
- description = "Enterprise firewall configuration for trusted domain access"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
- },
- {
- name = "deploy-dev.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the development server"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-dev.yml.template"
- },
- {
- name = "deploy-demo.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the demo server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-demo.yml.template"
- },
- {
- name = "deploy-rs.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the release staging server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-rs.yml.template"
- },
- {
- name = "sync-version-on-merge.yml"
- extension = "yml"
- description = "Auto-bump patch version on merge and propagate to all file headers"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/sync-version-on-merge.yml.template"
- },
{
name = "auto-release.yml"
extension = "yml"
- description = "Auto-create GitHub Release on push to main with version from README.md"
+ description = "Automated release — builds zip, creates Gitea release, updates SHA in updates.xml. Triggered by push to main (stable) or pre-release tags"
requirement_status = "required"
always_overwrite = true
- template = "templates/workflows/shared/auto-release.yml.template"
+ template = "workflows/auto-release.yml"
},
{
- name = "repository-cleanup.yml"
+ name = "ci-dolibarr.yml"
extension = "yml"
- description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
+ description = "Continuous integration — PHP linting, PHPStan static analysis, Dolibarr module validation"
requirement_status = "required"
always_overwrite = true
- template = "templates/workflows/shared/repository-cleanup.yml.template"
+ template = "workflows/ci-dolibarr.yml"
},
{
- name = "auto-dev-issue.yml"
+ name = "publish-to-mokodolimods.yml"
extension = "yml"
- description = "Auto-create tracking issue when a dev/** branch is pushed"
+ description = "On release, copies src/ into htdocs/custom/ in mokodolimods repo and opens a PR"
requirement_status = "required"
always_overwrite = true
- template = "templates/workflows/shared/auto-dev-issue.yml.template"
+ template = "workflows/publish-to-mokodolimods.yml"
},
{
- name = "repo_health.yml"
+ name = "pre-release.yml"
extension = "yml"
- description = "Joomla-specific repository health check workflow"
+ description = "Manual pre-release — builds dev/alpha/beta/rc packages with patch version bump"
requirement_status = "required"
always_overwrite = true
- template = "templates/workflows/joomla/repo_health.yml.template"
+ template = "workflows/pre-release.yml"
+ },
+ {
+ name = "deploy-manual.yml"
+ extension = "yml"
+ description = "Manual deployment — allows selecting target environment and branch for on-demand deploys"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/deploy-manual.yml"
+ },
+ {
+ name = "repo-health.yml"
+ extension = "yml"
+ description = "Repository health checks — validates required files, structure compliance, and standards alignment"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/repo-health.yml"
+ },
+ {
+ name = "update-server.yml"
+ extension = "yml"
+ description = "Update server maintenance — validates updates.xml format and ensures download URLs are reachable"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/update-server.yml"
+ },
+ {
+ name = "pr-check.yml"
+ extension = "yml"
+ description = "PR gate — validates PHP syntax, manifest XML, and package build before merge to main"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/pr-check.yml"
+ },
+ {
+ name = "security-audit.yml"
+ extension = "yml"
+ description = "Dependency vulnerability scanning — weekly schedule and on PR when lock files change"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/security-audit.yml"
+ },
+ {
+ name = "notify.yml"
+ extension = "yml"
+ description = "Push notifications via ntfy on release success or workflow failure"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/notify.yml"
+ },
+ {
+ name = "cleanup.yml"
+ extension = "yml"
+ description = "Scheduled cleanup — delete merged branches and old workflow runs weekly"
+ requirement_status = "required"
+ always_overwrite = true
+ template = "workflows/cleanup.yml"
}
]
},
diff --git a/definitions/sync/MokoCassiopeia.def.tf b/definitions/sync/MokoCassiopeia.def.tf
index 8dc3298..59ff676 100644
--- a/definitions/sync/MokoCassiopeia.def.tf
+++ b/definitions/sync/MokoCassiopeia.def.tf
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
+ { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor"
},
{
- name = "update.xml"
+ name = "updates.xml"
extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true
always_overwrite = false
audience = "developer"
- template = "templates/joomla/update.xml.template"
+ template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END
01.02.04
-
@@ -661,7 +661,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
+ ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -690,22 +690,22 @@ locals {
---
- ## update.xml — Required in Repo Root
+ ## updates.xml — Required in Repo Root
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
+ `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
+ - Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below.
+ - The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
+ - `` tag must be kept in sync with `README.md` version and `updates.xml`.
+ - Must include `` block pointing to this repo's `updates.xml`.
- Must include `` and `` sections.
- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
+ | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
+ | New release | Prepend `` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
+ - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END
},
{
@@ -868,7 +868,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
+ ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
+ | `updates.xml` | `` in the most recent `` block |
The `make release` command / release workflow syncs all three automatically.
---
- # update.xml — Required in Repo Root
+ # updates.xml — Required in Repo Root
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
+ `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
+ - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`.
- `` must be a publicly accessible GitHub Releases asset URL.
- `` — backslash is literal (Joomla regex syntax).
- Example `update.xml` entry for a new release:
+ Example `updates.xml` entry for a new release:
```xml
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
+ | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
+ | New release | Prepend `` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
+ - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
+ - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated
- [ ] Tests pass
diff --git a/definitions/sync/MokoJoomHero.def.tf b/definitions/sync/MokoJoomHero.def.tf
index e61875b..ca5e45a 100644
--- a/definitions/sync/MokoJoomHero.def.tf
+++ b/definitions/sync/MokoJoomHero.def.tf
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
+ { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" },
@@ -164,13 +164,13 @@ locals {
audience = "contributor"
},
{
- name = "update.xml"
+ name = "updates.xml"
extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true
always_overwrite = false
audience = "developer"
- template = "templates/joomla/update.xml.template"
+ template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END
01.02.04
-
@@ -660,7 +660,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
+ ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -689,22 +689,22 @@ locals {
---
- ## update.xml — Required in Repo Root
+ ## updates.xml — Required in Repo Root
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
+ `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
+ - Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below.
+ - The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -713,8 +713,8 @@ locals {
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
+ - `` tag must be kept in sync with `README.md` version and `updates.xml`.
+ - Must include `` block pointing to this repo's `updates.xml`.
- Must include `` and `` sections.
- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
@@ -792,8 +792,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
+ | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
+ | New release | Prepend `` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -807,7 +807,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
+ - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END
},
{
@@ -867,7 +867,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
+ ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -917,32 +917,32 @@ locals {
|------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
+ | `updates.xml` | `` in the most recent `` block |
The `make release` command / release workflow syncs all three automatically.
---
- # update.xml — Required in Repo Root
+ # updates.xml — Required in Repo Root
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
+ `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
+ - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`.
- `` must be a publicly accessible GitHub Releases asset URL.
- `` — backslash is literal (Joomla regex syntax).
- Example `update.xml` entry for a new release:
+ Example `updates.xml` entry for a new release:
```xml
@@ -1051,8 +1051,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
+ | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
+ | New release | Prepend `` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1063,7 +1063,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
+ - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1077,7 +1077,7 @@ locals {
Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
+ - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated
- [ ] Tests pass
diff --git a/definitions/sync/MokoJoomTOS.def.tf b/definitions/sync/MokoJoomTOS.def.tf
index df9fdab..23a2ea1 100644
--- a/definitions/sync/MokoJoomTOS.def.tf
+++ b/definitions/sync/MokoJoomTOS.def.tf
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
+ { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" },
@@ -164,13 +164,13 @@ locals {
audience = "contributor"
},
{
- name = "update.xml"
+ name = "updates.xml"
extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true
always_overwrite = false
audience = "developer"
- template = "templates/joomla/update.xml.template"
+ template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END
01.02.04
-
@@ -660,7 +660,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
+ ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -689,22 +689,22 @@ locals {
---
- ## update.xml — Required in Repo Root
+ ## updates.xml — Required in Repo Root
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
+ `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
+ - Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below.
+ - The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -713,8 +713,8 @@ locals {
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
+ - `` tag must be kept in sync with `README.md` version and `updates.xml`.
+ - Must include `` block pointing to this repo's `updates.xml`.
- Must include `` and `` sections.
- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
@@ -792,8 +792,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
+ | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
+ | New release | Prepend `` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -807,7 +807,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
+ - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END
},
{
@@ -867,7 +867,7 @@ locals {
```
{{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
+ ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -917,32 +917,32 @@ locals {
|------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
+ | `updates.xml` | `` in the most recent `` block |
The `make release` command / release workflow syncs all three automatically.
---
- # update.xml — Required in Repo Root
+ # updates.xml — Required in Repo Root
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
+ `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml
- {{REPO_URL}}/raw/main/update.xml
+ {{REPO_URL}}/raw/main/updates.xml
```
**Rules:**
- Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
+ - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`.
- `` must be a publicly accessible GitHub Releases asset URL.
- `` — backslash is literal (Joomla regex syntax).
- Example `update.xml` entry for a new release:
+ Example `updates.xml` entry for a new release:
```xml
@@ -1051,8 +1051,8 @@ locals {
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
+ | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
+ | New release | Prepend `` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1063,7 +1063,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
+ - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1077,7 +1077,7 @@ locals {
Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
+ - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated
- [ ] Tests pass
diff --git a/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf
deleted file mode 100644
index d17abf8..0000000
--- a/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf
+++ /dev/null
@@ -1,1335 +0,0 @@
-/**
- * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Component
- *
- * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:30:04+00:00
- * Platform : waas-component
- * Description: A repo template for a Joomla Component coding project according to MokoStandards
- *
- * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
- * To change what gets synced, edit api/definitions/default/waas-component.tf
- * and re-run the bulk-repo-sync workflow.
- */
-
-locals {
- sync_record = {
- metadata = {
- repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Component"
- default_branch = "main"
- detected_platform = "waas-component"
- description = "A repo template for a Joomla Component coding project according to MokoStandards"
- sync_timestamp = "2026-04-02T15:30:04+00:00"
- source_repo = "mokoconsulting-tech/MokoStandards"
- base_definition = "api/definitions/default/waas-component.tf"
- }
-
- sync_stats = {
- total_files = 41
- created_files = 3
- updated_files = 35
- skipped_files = 3
- }
-
- synced_files = [
- { path = "LICENSE" action = "updated" },
- { path = "SECURITY.md" action = "updated" },
- { path = "CODE_OF_CONDUCT.md" action = "updated" },
- { path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
- { path = "phpstan.neon" action = "updated" },
- { path = "Makefile" action = "updated" },
- { path = ".gitignore" action = "updated" },
- { path = "composer.json" action = "updated" },
- { path = ".mokostandards" action = "created" },
- { path = "docs/update-server.md" action = "created" },
- { path = ".github/copilot.yml" action = "updated" },
- { path = ".github/copilot-instructions.md" action = "updated" },
- { path = ".github/CLAUDE.md" action = "updated" },
- { path = ".github/workflows/codeql-analysis.yml" action = "updated" },
- { path = ".github/workflows/standards-compliance.yml" action = "updated" },
- { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
- { path = ".github/workflows/deploy-dev.yml" action = "updated" },
- { path = ".github/workflows/deploy-demo.yml" action = "updated" },
- { path = ".github/workflows/deploy-rs.yml" action = "updated" },
- { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
- { path = ".github/workflows/auto-release.yml" action = "updated" },
- { path = ".github/workflows/repository-cleanup.yml" action = "updated" },
- { path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
- { path = ".github/workflows/repo_health.yml" action = "created" },
- { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" },
- { path = ".github/CODEOWNERS" action = "updated" },
- { path = ".github/.mokostandards" action = "migrated from root" },
- ]
-
- skipped_files = [
- { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" },
- { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" },
- { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
- ]
- }
-}
-
-# ---- Base platform definition (reference copy) ----
-/**
- * MokoWaaS Component Structure Definition
- * Standard repository structure for MokoWaaS (Joomla) components
- *
- * Copyright (C) 2026 Moko Consulting
- * SPDX-License-Identifier: GPL-3.0-or-later
- * Version: 04.05.00
- * Schema Version: 1.0
- */
-
-locals {
- repository_structure = {
- metadata = {
- name = "MokoWaaS Component"
- description = "Standard repository structure for MokoWaaS (Joomla) components"
- repository_type = "waas-component"
- platform = "mokowaas"
- last_updated = "2026-01-15T00:00:00Z"
- maintainer = "Moko Consulting"
- version = "04.05.00"
- schema_version = "1.0"
- }
-
- root_files = [
- {
- name = "README.md"
- extension = "md"
- description = "Developer-focused documentation for contributors and maintainers"
- required = true
- always_overwrite = false
- protected = true
- audience = "developer"
- },
- {
- name = "LICENSE"
- extension = ""
- description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components"
- required = true
- audience = "general"
- template = "templates/licenses/GPL-3.0"
- license_type = "GPL-3.0-or-later"
- },
- {
- name = "CHANGELOG.md"
- extension = "md"
- description = "Version history and changes"
- required = true
- audience = "general"
- },
- {
- name = "SECURITY.md"
- extension = "md"
- description = "Security policy and vulnerability reporting"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-SECURITY.md"
- audience = "general"
- },
- {
- name = "CODE_OF_CONDUCT.md"
- extension = "md"
- description = "Community code of conduct"
- required = true
- always_overwrite = true
- template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
- always_overwrite = true
- audience = "contributor"
- },
- {
- name = "ROADMAP.md"
- extension = "md"
- description = "Project roadmap with version goals and milestones"
- required = false
- audience = "general"
- },
- {
- name = "CONTRIBUTING.md"
- extension = "md"
- description = "Contribution guidelines"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-CONTRIBUTING.md"
- audience = "contributor"
- },
- {
- name = "update.xml"
- extension = "xml"
- description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/joomla/update.xml.template"
- stub_content = <<-MOKO_END
-
-
-
- {{EXTENSION_NAME}}
- {{REPO_NAME}} — Moko Consulting Joomla extension
- {{EXTENSION_ELEMENT}}
- {{EXTENSION_TYPE}}
- {{VERSION}}
- {{REPO_URL}}/releases/tag/{{VERSION}}
-
- {{DOWNLOAD_URL}}
-
-
- 7.4
- Moko Consulting
- {{MAINTAINER_URL}}
-
-
- MOKO_END
- },
- {
- name = "phpstan.neon"
- extension = "neon"
- description = "PHPStan static analysis config with Joomla framework class stubs"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/phpstan.joomla.neon"
- },
- {
- name = "Makefile"
- description = "Build automation using MokoStandards templates"
- required = true
- always_overwrite = true
- audience = "developer"
- source_path = "templates/makefiles"
- source_filename = "Makefile.joomla.template"
- source_type = "template"
- destination_path = "."
- destination_filename = "Makefile"
- create_path = false
- template = "templates/makefiles/Makefile.joomla.template"
- },
- {
- name = ".gitignore"
- extension = "gitignore"
- description = "Git ignore patterns for Joomla development - preserved during sync operations"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/.gitignore.joomla"
- validation_rules = [
- {
- type = "content-pattern"
- description = "Must contain sftp-config pattern to ignore SFTP sync configuration files"
- pattern = "sftp-config"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.css pattern to ignore custom user CSS overrides"
- pattern = "user\\.css"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.js pattern to ignore custom user JavaScript overrides"
- pattern = "user\\.js"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts"
- pattern = "modulebuilder\\.txt"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides"
- pattern = "colors_custom\\.css"
- severity = "error"
- }
- ]
- },
- {
- name = ".gitattributes"
- extension = "gitattributes"
- description = "Git attributes configuration"
- required = true
- audience = "developer"
- },
- {
- name = ".editorconfig"
- extension = "editorconfig"
- description = "Editor configuration for consistent coding style - preserved during sync"
- required = true
- always_overwrite = false
- audience = "developer"
- },
- {
- name = "composer.json"
- extension = "json"
- description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/composer.joomla.json"
- },
- {
- name = ".mokostandards"
- extension = "yml"
- description = "MokoStandards governance attachment — links this repo back to the standards source"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/mokostandards.yml.template"
- },
- {
- name = "GOVERNANCE.md"
- extension = "md"
- description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards"
- required = true
- always_overwrite = false
- protected = true
- audience = "all"
- template = "templates/docs/required/GOVERNANCE.md"
- }
- ]
-
- directories = [
- {
- name = "site"
- path = "site"
- description = "Component frontend (site) code"
- required = true
- purpose = "Contains frontend component code deployed to site"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main site controller"
- required = true
- audience = "developer"
- },
- {
- name = "manifest.xml"
- extension = "xml"
- description = "Component manifest for site"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "site/controllers"
- description = "Site controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "site/models"
- description = "Site models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "site/views"
- description = "Site views"
- required = true
- }
- ]
- },
- {
- name = "admin"
- path = "admin"
- description = "Component backend (admin) code"
- required = true
- purpose = "Contains backend component code for administrator"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main admin controller"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "admin/controllers"
- description = "Admin controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "admin/models"
- description = "Admin models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "admin/views"
- description = "Admin views"
- required = true
- },
- {
- name = "sql"
- path = "admin/sql"
- description = "Database schema files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "media"
- path = "media"
- description = "Media files (CSS, JS, images)"
- requirement_status = "suggested"
- purpose = "Contains static assets"
- subdirectories = [
- {
- name = "css"
- path = "media/css"
- description = "Stylesheets"
- requirement_status = "suggested"
- },
- {
- name = "js"
- path = "media/js"
- description = "JavaScript files"
- requirement_status = "suggested"
- },
- {
- name = "images"
- path = "media/images"
- description = "Image files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "language"
- path = "language"
- description = "Language translation files"
- required = true
- purpose = "Contains language INI files"
- },
- {
- name = "docs"
- path = "docs"
- description = "Developer and technical documentation"
- required = true
- purpose = "Contains technical documentation, API docs, architecture diagrams"
- files = [
- {
- name = "index.md"
- extension = "md"
- description = "Documentation index"
- required = true
- },
- {
- name = "update-server.md"
- extension = "md"
- description = "Joomla update server (update.xml) documentation"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-update-server-joomla.md"
- }
- ]
- },
- {
- name = "scripts"
- path = "scripts"
- description = "Repo-specific scripts — not managed by MokoStandards sync"
- required = false
- purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
- files = [
- {
- name = "MokoStandards.override.xml"
- extension = "xml"
- description = "MokoStandards sync override configuration - preserved during sync"
- requirement_status = "optional"
- always_overwrite = false
- audience = "developer"
- }
- ]
- },
- {
- name = "tests"
- path = "tests"
- description = "Test files"
- required = true
- purpose = "Contains unit tests, integration tests, and test fixtures"
- subdirectories = [
- {
- name = "unit"
- path = "tests/unit"
- description = "Unit tests"
- required = true
- },
- {
- name = "integration"
- path = "tests/integration"
- description = "Integration tests"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = ".github"
- path = ".github"
- description = "GitHub-specific configuration"
- requirement_status = "suggested"
- purpose = "Contains GitHub Actions workflows and configuration"
- files = [
- {
- name = "copilot.yml"
- extension = "yml"
- description = "GitHub Copilot allowed domains configuration"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/copilot.yml"
- },
- {
- name = "copilot-instructions.md"
- extension = "md"
- description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition"
- requirement_status = "required"
- always_overwrite = false
- destination_path = ".github"
- destination_filename = "copilot-instructions.md"
- template = "templates/github/copilot-instructions.joomla.md.template"
- stub_content = <<-MOKO_END
- > [!IMPORTANT]
- > **🔧 AI Self-Update Required on First Use**
- >
- > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
- > replaces them automatically at sync time; if you can still see them, sync has not yet run for
- > this repository — or this is a brand-new repo.
- >
- > **On your very first interaction in this repository, before answering any other question:**
- >
- > 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
- > 2. If they do, locate the values as described in the table below and replace every occurrence
- > in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
- > 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
- > through the closing `> ---` line) in both files.
- > 4. Tell the developer: *"I've self-updated the AI context files with this repository's
- > values — all set."*
- >
- > | Placeholder | Where to find the value |
- > |---|---|
- > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
- > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
- > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
- > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
- >
- > ---
-
- # {{REPO_NAME}} — GitHub Copilot Custom Instructions
-
- ## What This Repo Is
-
- This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
-
- Repository URL: {{REPO_URL}}
- Extension name: **{{EXTENSION_NAME}}**
- Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
- Platform: **Joomla 4.x / MokoWaaS**
-
- ---
-
- ## Primary Language
-
- **PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
-
- ---
-
- ## File Header — Always Required on New Files
-
- Every new file needs a copyright header as its first content.
-
- **PHP:**
- ```php
-
- *
- * This file is part of a Moko Consulting project.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- * FILE INFORMATION
- * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}}
- * INGROUP: {{REPO_NAME}}
- * REPO: {{REPO_URL}}
- * PATH: /path/to/file.php
- * VERSION: XX.YY.ZZ
- * BRIEF: One-line description of purpose
- */
-
- defined('_JEXEC') or die;
- ```
-
- **Markdown:**
- ```markdown
-
- ```
-
- **YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
-
- ---
-
- ## Version Management
-
- **`README.md` is the single source of truth for the repository version.**
-
- - **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
- - The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
- - Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- - Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
-
- ### Joomla Version Alignment
-
- The version in `README.md` **must always match** the `` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically.
-
- ```xml
-
- 01.02.04
-
-
-
-
- {{EXTENSION_NAME}}
- 01.02.04
-
-
- {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
-
-
-
-
-
-
- ```
-
- ---
-
- ## Joomla Extension Structure
-
- ```
- {{REPO_NAME}}/
- ├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
- ├── site/ # Frontend (site) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ └── views/
- ├── admin/ # Backend (admin) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ ├── views/
- │ └── sql/
- ├── language/ # Language INI files
- ├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
- ├── docs/ # Technical documentation
- ├── tests/ # Test suite
- ├── .github/
- │ ├── workflows/
- │ ├── copilot-instructions.md # This file
- │ └── CLAUDE.md
- ├── README.md # Version source of truth
- ├── CHANGELOG.md
- ├── CONTRIBUTING.md
- ├── LICENSE # GPL-3.0-or-later
- └── Makefile # Build automation
- ```
-
- ---
-
- ## update.xml — Required in Repo Root
-
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
-
- The `manifest.xml` must reference it via:
- ```xml
-
-
- {{REPO_URL}}/raw/main/update.xml
-
-
- ```
-
- **Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- - The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- - `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
-
- ---
-
- ## manifest.xml Rules
-
- - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
- - Must include `` and `` sections.
- - Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
-
- ---
-
- ## GitHub Actions — Token Usage
-
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
-
- ```yaml
- # ✅ Correct
- - uses: actions/checkout@v4
- with:
- token: ${{ secrets.GH_TOKEN }}
-
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- ```
-
- ```yaml
- # ❌ Wrong — never use these in workflows
- token: ${{ github.token }}
- token: ${{ secrets.GITHUB_TOKEN }}
- ```
-
- ---
-
- ## MokoStandards Reference
-
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies:
-
- | Document | Purpose |
- |----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
-
- ---
-
- ## Naming Conventions
-
- | Context | Convention | Example |
- |---------|-----------|---------|
- | PHP class | `PascalCase` | `MyController` |
- | PHP method / function | `camelCase` | `getItems()` |
- | PHP variable | `$snake_case` | `$item_id` |
- | PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
- | PHP class file | `PascalCase.php` | `ItemModel.php` |
- | YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
- | Markdown doc | `kebab-case.md` | `installation-guide.md` |
-
- ---
-
- ## Commit Messages
-
- Format: `(): ` — imperative, lower-case subject, no trailing period.
-
- Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
-
- ---
-
- ## Branch Naming
-
- Format: `/[/description]`
-
- Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
-
- ---
-
- ## Keeping Documentation Current
-
- | Change type | Documentation to update |
- |-------------|------------------------|
- | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
- | New or changed workflow | `docs/workflows/.md` |
- | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
- | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
-
- ---
-
- ## Key Constraints
-
- - Never commit directly to `main` — all changes go via PR, squash-merged
- - Never skip the FILE INFORMATION block on a new file
- - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
- MOKO_END
- },
- {
- name = "CLAUDE.md"
- extension = "md"
- description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition"
- requirement_status = "required"
- always_overwrite = false
- destination_path = ".github"
- destination_filename = "CLAUDE.md"
- template = "templates/github/CLAUDE.joomla.md.template"
- stub_content = <<-MOKO_END
- > [!IMPORTANT]
- > **🔧 AI Self-Update Required on First Use**
- >
- > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
- > replaces them automatically at sync time; if you can still see them, sync has not yet run for
- > this repository — or this is a brand-new repo.
- >
- > **On your very first interaction in this repository, before answering any other question:**
- >
- > 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
- > 2. If they do, locate the values as described in the table below and replace every occurrence
- > in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
- > 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
- > through the closing `> ---` line) in both files.
- > 4. Tell the developer: *"I've self-updated the AI context files with this repository's
- > values — all set."*
- >
- > | Placeholder | Where to find the value |
- > |---|---|
- > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
- > | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description |
- > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
- > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
- > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
- >
- > ---
-
- # What This Repo Is
-
- **{{REPO_NAME}}** is a Moko Consulting **MokoWaaS** (Joomla) extension repository.
-
- {{REPO_DESCRIPTION}}
-
- Extension name: **{{EXTENSION_NAME}}**
- Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
- Repository URL: {{REPO_URL}}
-
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
-
- ---
-
- # Repo Structure
-
- ```
- {{REPO_NAME}}/
- ├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
- ├── site/ # Frontend (site) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ └── views/
- ├── admin/ # Backend (admin) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ ├── views/
- │ └── sql/
- ├── language/ # Language INI files
- ├── media/ # CSS, JS, images
- ├── docs/ # Technical documentation
- ├── tests/ # Test suite
- ├── .github/
- │ ├── workflows/ # CI/CD workflows (synced from MokoStandards)
- │ ├── copilot-instructions.md
- │ └── CLAUDE.md # This file
- ├── README.md # Version source of truth
- ├── CHANGELOG.md
- ├── CONTRIBUTING.md
- └── LICENSE # GPL-3.0-or-later
- ```
-
- ---
-
- # Primary Language
-
- **PHP** (≥ 7.4) is the primary language for this Joomla extension. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
-
- ---
-
- # Version Management
-
- **`README.md` is the single source of truth for the repository version.**
-
- - **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it to all `FILE INFORMATION` headers automatically on merge.
- - Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- - Never hardcode a version number in body text — use the badge or FILE INFORMATION header only.
-
- ### Joomla Version Alignment
-
- Three files must **always have the same version**:
-
- | File | Where the version lives |
- |------|------------------------|
- | `README.md` | `FILE INFORMATION` block + badge |
- | `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
-
- The `make release` command / release workflow syncs all three automatically.
-
- ---
-
- # update.xml — Required in Repo Root
-
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
-
- ```xml
-
-
-
- {{REPO_URL}}/raw/main/update.xml
-
-
- ```
-
- **Rules:**
- - Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
- - `` must be a publicly accessible GitHub Releases asset URL.
- - `` — backslash is literal (Joomla regex syntax).
-
- Example `update.xml` entry for a new release:
- ```xml
-
-
- {{EXTENSION_NAME}}
- {{REPO_NAME}}
- {{EXTENSION_ELEMENT}}
- {{EXTENSION_TYPE}}
- 01.02.04
- {{REPO_URL}}/releases/tag/01.02.04
-
-
- {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
-
-
-
- 7.4
- Moko Consulting
- https://mokoconsulting.tech
-
-
- ```
-
- ---
-
- # File Header Requirements
-
- Every new file **must** have a copyright header as its first content. JSON files, binary files, generated files, and third-party files are exempt.
-
- **PHP:**
- ```php
-
- *
- * This file is part of a Moko Consulting project.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- * FILE INFORMATION
- * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}}
- * INGROUP: {{REPO_NAME}}
- * REPO: {{REPO_URL}}
- * PATH: /site/controllers/item.php
- * VERSION: XX.YY.ZZ
- * BRIEF: One-line description of file purpose
- */
-
- defined('_JEXEC') or die;
- ```
-
- **Markdown / YAML / Shell / XML:** Use the appropriate comment syntax with the same fields.
-
- ---
-
- # Coding Standards
-
- ## Naming Conventions
-
- | Context | Convention | Example |
- |---------|-----------|---------|
- | PHP class | `PascalCase` | `ItemModel` |
- | PHP method / function | `camelCase` | `getItems()` |
- | PHP variable | `$snake_case` | `$item_id` |
- | PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
- | PHP class file | `PascalCase.php` | `ItemModel.php` |
- | YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
- | Markdown doc | `kebab-case.md` | `installation-guide.md` |
-
- ## Commit Messages
-
- Format: `(): ` — imperative, lower-case subject, no trailing period.
-
- Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
-
- ## Branch Naming
-
- Format: `/[/description]`
-
- Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
-
- ---
-
- # GitHub Actions — Token Usage
-
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
-
- ```yaml
- # ✅ Correct
- - uses: actions/checkout@v4
- with:
- token: ${{ secrets.GH_TOKEN }}
-
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- ```
-
- ```yaml
- # ❌ Wrong — never use these
- token: ${{ github.token }}
- token: ${{ secrets.GITHUB_TOKEN }}
- ```
-
- ---
-
- # Keeping Documentation Current
-
- | Change type | Documentation to update |
- |-------------|------------------------|
- | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
- | New or changed workflow | `docs/workflows/.md` |
- | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
- | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
-
- ---
-
- # What NOT to Do
-
- - **Never commit directly to `main`** — all changes go through a PR.
- - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
- - **Never skip the FILE INFORMATION block** on a new source file.
- - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
- - **Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows** — always use `secrets.GH_TOKEN`.
- - **Never remove `defined('_JEXEC') or die;`** from web-accessible PHP files.
-
- ---
-
- # PR Checklist
-
- Before opening a PR, verify:
-
- - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
- - [ ] FILE INFORMATION headers updated in modified files
- - [ ] CHANGELOG.md updated
- - [ ] Tests pass
-
- ---
-
- # Key Policy Documents (MokoStandards)
-
- | Document | Purpose |
- |----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
- MOKO_END
- }
- ]
- subdirectories = [
- {
- name = "workflows"
- path = ".github/workflows"
- description = "GitHub Actions workflows"
- requirement_status = "required"
- files = [
- {
- name = "ci-joomla.yml"
- extension = "yml"
- description = "Joomla-specific CI workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/joomla/ci-joomla.yml.template"
- },
- {
- name = "codeql-analysis.yml"
- extension = "yml"
- description = "CodeQL security analysis workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/generic/codeql-analysis.yml.template"
- },
- {
- name = "standards-compliance.yml"
- extension = "yml"
- description = "MokoStandards compliance validation"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/workflows/standards-compliance.yml"
- },
- {
- name = "enterprise-firewall-setup.yml"
- extension = "yml"
- description = "Enterprise firewall configuration for trusted domain access"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
- },
- {
- name = "deploy-dev.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the development server"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-dev.yml.template"
- },
- {
- name = "deploy-demo.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the demo server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-demo.yml.template"
- },
- {
- name = "deploy-rs.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the release staging server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-rs.yml.template"
- },
- {
- name = "sync-version-on-merge.yml"
- extension = "yml"
- description = "Auto-bump patch version on merge and propagate to all file headers"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/sync-version-on-merge.yml.template"
- },
- {
- name = "auto-release.yml"
- extension = "yml"
- description = "Auto-create GitHub Release on push to main with version from README.md"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/auto-release.yml.template"
- },
- {
- name = "repository-cleanup.yml"
- extension = "yml"
- description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/repository-cleanup.yml.template"
- },
- {
- name = "auto-dev-issue.yml"
- extension = "yml"
- description = "Auto-create tracking issue when a dev/** branch is pushed"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/auto-dev-issue.yml.template"
- },
- {
- name = "repo_health.yml"
- extension = "yml"
- description = "Joomla-specific repository health check workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/joomla/repo_health.yml.template"
- }
- ]
- },
- {
- name = "ISSUE_TEMPLATE"
- path = ".github/ISSUE_TEMPLATE"
- description = "GitHub issue templates synced from MokoStandards"
- requirement_status = "required"
- files = [
- {
- name = "config.yml"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/config.yml"
- },
- {
- name = "adr.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/adr.md"
- },
- {
- name = "bug_report.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
- },
- {
- name = "documentation.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/documentation.md"
- },
- {
- name = "enterprise_support.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
- },
- {
- name = "feature_request.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
- },
- {
- name = "firewall-request.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
- },
- {
- name = "question.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/question.md"
- },
- {
- name = "request-license.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/request-license.md"
- },
- {
- name = "rfc.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/rfc.md"
- },
- {
- name = "security.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/security.md"
- },
- {
- name = "joomla_issue.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md"
- }
- ]
- }
- ]
- }
- ]
-
- repository_requirements = {
- secrets = [
- {
- name = "GH_TOKEN"
- description = "Org-level GitHub PAT for automation"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_KEY"
- description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
- required = false
- scope = "org"
- },
- {
- name = "DEV_FTP_PASSWORD"
- description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
- required = false
- scope = "org"
- note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
- }
- ]
-
- variables = [
- {
- name = "DEV_FTP_HOST"
- description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_PATH"
- description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_USERNAME"
- description = "SFTP username for dev server authentication"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_PORT"
- description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
- required = false
- scope = "org"
- },
- {
- name = "DEV_FTP_SUFFIX"
- description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)"
- required = false
- scope = "repo"
- }
- ]
- }
- }
-}
diff --git a/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf
deleted file mode 100644
index 9ea587f..0000000
--- a/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf
+++ /dev/null
@@ -1,1335 +0,0 @@
-/**
- * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Library
- *
- * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:31:13+00:00
- * Platform : waas-component
- * Description: A repo template for a Joomla Library coding project according to MokoStandards
- *
- * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
- * To change what gets synced, edit api/definitions/default/waas-component.tf
- * and re-run the bulk-repo-sync workflow.
- */
-
-locals {
- sync_record = {
- metadata = {
- repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Library"
- default_branch = "main"
- detected_platform = "waas-component"
- description = "A repo template for a Joomla Library coding project according to MokoStandards"
- sync_timestamp = "2026-04-02T15:31:13+00:00"
- source_repo = "mokoconsulting-tech/MokoStandards"
- base_definition = "api/definitions/default/waas-component.tf"
- }
-
- sync_stats = {
- total_files = 41
- created_files = 3
- updated_files = 35
- skipped_files = 3
- }
-
- synced_files = [
- { path = "LICENSE" action = "updated" },
- { path = "SECURITY.md" action = "updated" },
- { path = "CODE_OF_CONDUCT.md" action = "updated" },
- { path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
- { path = "phpstan.neon" action = "updated" },
- { path = "Makefile" action = "updated" },
- { path = ".gitignore" action = "updated" },
- { path = "composer.json" action = "updated" },
- { path = ".mokostandards" action = "created" },
- { path = "docs/update-server.md" action = "created" },
- { path = ".github/copilot.yml" action = "updated" },
- { path = ".github/copilot-instructions.md" action = "updated" },
- { path = ".github/CLAUDE.md" action = "updated" },
- { path = ".github/workflows/codeql-analysis.yml" action = "updated" },
- { path = ".github/workflows/standards-compliance.yml" action = "updated" },
- { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
- { path = ".github/workflows/deploy-dev.yml" action = "updated" },
- { path = ".github/workflows/deploy-demo.yml" action = "updated" },
- { path = ".github/workflows/deploy-rs.yml" action = "updated" },
- { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
- { path = ".github/workflows/auto-release.yml" action = "updated" },
- { path = ".github/workflows/repository-cleanup.yml" action = "updated" },
- { path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
- { path = ".github/workflows/repo_health.yml" action = "created" },
- { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" },
- { path = ".github/CODEOWNERS" action = "updated" },
- { path = ".github/.mokostandards" action = "migrated from root" },
- ]
-
- skipped_files = [
- { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" },
- { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" },
- { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
- ]
- }
-}
-
-# ---- Base platform definition (reference copy) ----
-/**
- * MokoWaaS Component Structure Definition
- * Standard repository structure for MokoWaaS (Joomla) components
- *
- * Copyright (C) 2026 Moko Consulting
- * SPDX-License-Identifier: GPL-3.0-or-later
- * Version: 04.05.00
- * Schema Version: 1.0
- */
-
-locals {
- repository_structure = {
- metadata = {
- name = "MokoWaaS Component"
- description = "Standard repository structure for MokoWaaS (Joomla) components"
- repository_type = "waas-component"
- platform = "mokowaas"
- last_updated = "2026-01-15T00:00:00Z"
- maintainer = "Moko Consulting"
- version = "04.05.00"
- schema_version = "1.0"
- }
-
- root_files = [
- {
- name = "README.md"
- extension = "md"
- description = "Developer-focused documentation for contributors and maintainers"
- required = true
- always_overwrite = false
- protected = true
- audience = "developer"
- },
- {
- name = "LICENSE"
- extension = ""
- description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components"
- required = true
- audience = "general"
- template = "templates/licenses/GPL-3.0"
- license_type = "GPL-3.0-or-later"
- },
- {
- name = "CHANGELOG.md"
- extension = "md"
- description = "Version history and changes"
- required = true
- audience = "general"
- },
- {
- name = "SECURITY.md"
- extension = "md"
- description = "Security policy and vulnerability reporting"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-SECURITY.md"
- audience = "general"
- },
- {
- name = "CODE_OF_CONDUCT.md"
- extension = "md"
- description = "Community code of conduct"
- required = true
- always_overwrite = true
- template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
- always_overwrite = true
- audience = "contributor"
- },
- {
- name = "ROADMAP.md"
- extension = "md"
- description = "Project roadmap with version goals and milestones"
- required = false
- audience = "general"
- },
- {
- name = "CONTRIBUTING.md"
- extension = "md"
- description = "Contribution guidelines"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-CONTRIBUTING.md"
- audience = "contributor"
- },
- {
- name = "update.xml"
- extension = "xml"
- description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/joomla/update.xml.template"
- stub_content = <<-MOKO_END
-
-
-
- {{EXTENSION_NAME}}
- {{REPO_NAME}} — Moko Consulting Joomla extension
- {{EXTENSION_ELEMENT}}
- {{EXTENSION_TYPE}}
- {{VERSION}}
- {{REPO_URL}}/releases/tag/{{VERSION}}
-
- {{DOWNLOAD_URL}}
-
-
- 7.4
- Moko Consulting
- {{MAINTAINER_URL}}
-
-
- MOKO_END
- },
- {
- name = "phpstan.neon"
- extension = "neon"
- description = "PHPStan static analysis config with Joomla framework class stubs"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/phpstan.joomla.neon"
- },
- {
- name = "Makefile"
- description = "Build automation using MokoStandards templates"
- required = true
- always_overwrite = true
- audience = "developer"
- source_path = "templates/makefiles"
- source_filename = "Makefile.joomla.template"
- source_type = "template"
- destination_path = "."
- destination_filename = "Makefile"
- create_path = false
- template = "templates/makefiles/Makefile.joomla.template"
- },
- {
- name = ".gitignore"
- extension = "gitignore"
- description = "Git ignore patterns for Joomla development - preserved during sync operations"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/.gitignore.joomla"
- validation_rules = [
- {
- type = "content-pattern"
- description = "Must contain sftp-config pattern to ignore SFTP sync configuration files"
- pattern = "sftp-config"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.css pattern to ignore custom user CSS overrides"
- pattern = "user\\.css"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.js pattern to ignore custom user JavaScript overrides"
- pattern = "user\\.js"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts"
- pattern = "modulebuilder\\.txt"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides"
- pattern = "colors_custom\\.css"
- severity = "error"
- }
- ]
- },
- {
- name = ".gitattributes"
- extension = "gitattributes"
- description = "Git attributes configuration"
- required = true
- audience = "developer"
- },
- {
- name = ".editorconfig"
- extension = "editorconfig"
- description = "Editor configuration for consistent coding style - preserved during sync"
- required = true
- always_overwrite = false
- audience = "developer"
- },
- {
- name = "composer.json"
- extension = "json"
- description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/composer.joomla.json"
- },
- {
- name = ".mokostandards"
- extension = "yml"
- description = "MokoStandards governance attachment — links this repo back to the standards source"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/mokostandards.yml.template"
- },
- {
- name = "GOVERNANCE.md"
- extension = "md"
- description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards"
- required = true
- always_overwrite = false
- protected = true
- audience = "all"
- template = "templates/docs/required/GOVERNANCE.md"
- }
- ]
-
- directories = [
- {
- name = "site"
- path = "site"
- description = "Component frontend (site) code"
- required = true
- purpose = "Contains frontend component code deployed to site"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main site controller"
- required = true
- audience = "developer"
- },
- {
- name = "manifest.xml"
- extension = "xml"
- description = "Component manifest for site"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "site/controllers"
- description = "Site controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "site/models"
- description = "Site models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "site/views"
- description = "Site views"
- required = true
- }
- ]
- },
- {
- name = "admin"
- path = "admin"
- description = "Component backend (admin) code"
- required = true
- purpose = "Contains backend component code for administrator"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main admin controller"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "admin/controllers"
- description = "Admin controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "admin/models"
- description = "Admin models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "admin/views"
- description = "Admin views"
- required = true
- },
- {
- name = "sql"
- path = "admin/sql"
- description = "Database schema files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "media"
- path = "media"
- description = "Media files (CSS, JS, images)"
- requirement_status = "suggested"
- purpose = "Contains static assets"
- subdirectories = [
- {
- name = "css"
- path = "media/css"
- description = "Stylesheets"
- requirement_status = "suggested"
- },
- {
- name = "js"
- path = "media/js"
- description = "JavaScript files"
- requirement_status = "suggested"
- },
- {
- name = "images"
- path = "media/images"
- description = "Image files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "language"
- path = "language"
- description = "Language translation files"
- required = true
- purpose = "Contains language INI files"
- },
- {
- name = "docs"
- path = "docs"
- description = "Developer and technical documentation"
- required = true
- purpose = "Contains technical documentation, API docs, architecture diagrams"
- files = [
- {
- name = "index.md"
- extension = "md"
- description = "Documentation index"
- required = true
- },
- {
- name = "update-server.md"
- extension = "md"
- description = "Joomla update server (update.xml) documentation"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-update-server-joomla.md"
- }
- ]
- },
- {
- name = "scripts"
- path = "scripts"
- description = "Repo-specific scripts — not managed by MokoStandards sync"
- required = false
- purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
- files = [
- {
- name = "MokoStandards.override.xml"
- extension = "xml"
- description = "MokoStandards sync override configuration - preserved during sync"
- requirement_status = "optional"
- always_overwrite = false
- audience = "developer"
- }
- ]
- },
- {
- name = "tests"
- path = "tests"
- description = "Test files"
- required = true
- purpose = "Contains unit tests, integration tests, and test fixtures"
- subdirectories = [
- {
- name = "unit"
- path = "tests/unit"
- description = "Unit tests"
- required = true
- },
- {
- name = "integration"
- path = "tests/integration"
- description = "Integration tests"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = ".github"
- path = ".github"
- description = "GitHub-specific configuration"
- requirement_status = "suggested"
- purpose = "Contains GitHub Actions workflows and configuration"
- files = [
- {
- name = "copilot.yml"
- extension = "yml"
- description = "GitHub Copilot allowed domains configuration"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/copilot.yml"
- },
- {
- name = "copilot-instructions.md"
- extension = "md"
- description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition"
- requirement_status = "required"
- always_overwrite = false
- destination_path = ".github"
- destination_filename = "copilot-instructions.md"
- template = "templates/github/copilot-instructions.joomla.md.template"
- stub_content = <<-MOKO_END
- > [!IMPORTANT]
- > **🔧 AI Self-Update Required on First Use**
- >
- > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
- > replaces them automatically at sync time; if you can still see them, sync has not yet run for
- > this repository — or this is a brand-new repo.
- >
- > **On your very first interaction in this repository, before answering any other question:**
- >
- > 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
- > 2. If they do, locate the values as described in the table below and replace every occurrence
- > in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
- > 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
- > through the closing `> ---` line) in both files.
- > 4. Tell the developer: *"I've self-updated the AI context files with this repository's
- > values — all set."*
- >
- > | Placeholder | Where to find the value |
- > |---|---|
- > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
- > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
- > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
- > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
- >
- > ---
-
- # {{REPO_NAME}} — GitHub Copilot Custom Instructions
-
- ## What This Repo Is
-
- This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
-
- Repository URL: {{REPO_URL}}
- Extension name: **{{EXTENSION_NAME}}**
- Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
- Platform: **Joomla 4.x / MokoWaaS**
-
- ---
-
- ## Primary Language
-
- **PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
-
- ---
-
- ## File Header — Always Required on New Files
-
- Every new file needs a copyright header as its first content.
-
- **PHP:**
- ```php
-
- *
- * This file is part of a Moko Consulting project.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- * FILE INFORMATION
- * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}}
- * INGROUP: {{REPO_NAME}}
- * REPO: {{REPO_URL}}
- * PATH: /path/to/file.php
- * VERSION: XX.YY.ZZ
- * BRIEF: One-line description of purpose
- */
-
- defined('_JEXEC') or die;
- ```
-
- **Markdown:**
- ```markdown
-
- ```
-
- **YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
-
- ---
-
- ## Version Management
-
- **`README.md` is the single source of truth for the repository version.**
-
- - **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
- - The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
- - Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- - Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
-
- ### Joomla Version Alignment
-
- The version in `README.md` **must always match** the `` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically.
-
- ```xml
-
- 01.02.04
-
-
-
-
- {{EXTENSION_NAME}}
- 01.02.04
-
-
- {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
-
-
-
-
-
-
- ```
-
- ---
-
- ## Joomla Extension Structure
-
- ```
- {{REPO_NAME}}/
- ├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
- ├── site/ # Frontend (site) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ └── views/
- ├── admin/ # Backend (admin) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ ├── views/
- │ └── sql/
- ├── language/ # Language INI files
- ├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
- ├── docs/ # Technical documentation
- ├── tests/ # Test suite
- ├── .github/
- │ ├── workflows/
- │ ├── copilot-instructions.md # This file
- │ └── CLAUDE.md
- ├── README.md # Version source of truth
- ├── CHANGELOG.md
- ├── CONTRIBUTING.md
- ├── LICENSE # GPL-3.0-or-later
- └── Makefile # Build automation
- ```
-
- ---
-
- ## update.xml — Required in Repo Root
-
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
-
- The `manifest.xml` must reference it via:
- ```xml
-
-
- {{REPO_URL}}/raw/main/update.xml
-
-
- ```
-
- **Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- - The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- - `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
-
- ---
-
- ## manifest.xml Rules
-
- - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
- - Must include `` and `` sections.
- - Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
-
- ---
-
- ## GitHub Actions — Token Usage
-
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
-
- ```yaml
- # ✅ Correct
- - uses: actions/checkout@v4
- with:
- token: ${{ secrets.GH_TOKEN }}
-
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- ```
-
- ```yaml
- # ❌ Wrong — never use these in workflows
- token: ${{ github.token }}
- token: ${{ secrets.GITHUB_TOKEN }}
- ```
-
- ---
-
- ## MokoStandards Reference
-
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies:
-
- | Document | Purpose |
- |----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
-
- ---
-
- ## Naming Conventions
-
- | Context | Convention | Example |
- |---------|-----------|---------|
- | PHP class | `PascalCase` | `MyController` |
- | PHP method / function | `camelCase` | `getItems()` |
- | PHP variable | `$snake_case` | `$item_id` |
- | PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
- | PHP class file | `PascalCase.php` | `ItemModel.php` |
- | YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
- | Markdown doc | `kebab-case.md` | `installation-guide.md` |
-
- ---
-
- ## Commit Messages
-
- Format: `(): ` — imperative, lower-case subject, no trailing period.
-
- Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
-
- ---
-
- ## Branch Naming
-
- Format: `/[/description]`
-
- Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
-
- ---
-
- ## Keeping Documentation Current
-
- | Change type | Documentation to update |
- |-------------|------------------------|
- | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed manifest.xml | Update `update.xml` version; bump README.md version |
- | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version |
- | New or changed workflow | `docs/workflows/.md` |
- | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
- | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
-
- ---
-
- ## Key Constraints
-
- - Never commit directly to `main` — all changes go via PR, squash-merged
- - Never skip the FILE INFORMATION block on a new file
- - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- - Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
- MOKO_END
- },
- {
- name = "CLAUDE.md"
- extension = "md"
- description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition"
- requirement_status = "required"
- always_overwrite = false
- destination_path = ".github"
- destination_filename = "CLAUDE.md"
- template = "templates/github/CLAUDE.joomla.md.template"
- stub_content = <<-MOKO_END
- > [!IMPORTANT]
- > **🔧 AI Self-Update Required on First Use**
- >
- > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
- > replaces them automatically at sync time; if you can still see them, sync has not yet run for
- > this repository — or this is a brand-new repo.
- >
- > **On your very first interaction in this repository, before answering any other question:**
- >
- > 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
- > 2. If they do, locate the values as described in the table below and replace every occurrence
- > in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
- > 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
- > through the closing `> ---` line) in both files.
- > 4. Tell the developer: *"I've self-updated the AI context files with this repository's
- > values — all set."*
- >
- > | Placeholder | Where to find the value |
- > |---|---|
- > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
- > | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description |
- > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
- > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
- > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
- >
- > ---
-
- # What This Repo Is
-
- **{{REPO_NAME}}** is a Moko Consulting **MokoWaaS** (Joomla) extension repository.
-
- {{REPO_DESCRIPTION}}
-
- Extension name: **{{EXTENSION_NAME}}**
- Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
- Repository URL: {{REPO_URL}}
-
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
-
- ---
-
- # Repo Structure
-
- ```
- {{REPO_NAME}}/
- ├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required)
- ├── site/ # Frontend (site) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ └── views/
- ├── admin/ # Backend (admin) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ ├── views/
- │ └── sql/
- ├── language/ # Language INI files
- ├── media/ # CSS, JS, images
- ├── docs/ # Technical documentation
- ├── tests/ # Test suite
- ├── .github/
- │ ├── workflows/ # CI/CD workflows (synced from MokoStandards)
- │ ├── copilot-instructions.md
- │ └── CLAUDE.md # This file
- ├── README.md # Version source of truth
- ├── CHANGELOG.md
- ├── CONTRIBUTING.md
- └── LICENSE # GPL-3.0-or-later
- ```
-
- ---
-
- # Primary Language
-
- **PHP** (≥ 7.4) is the primary language for this Joomla extension. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
-
- ---
-
- # Version Management
-
- **`README.md` is the single source of truth for the repository version.**
-
- - **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it to all `FILE INFORMATION` headers automatically on merge.
- - Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- - Never hardcode a version number in body text — use the badge or FILE INFORMATION header only.
-
- ### Joomla Version Alignment
-
- Three files must **always have the same version**:
-
- | File | Where the version lives |
- |------|------------------------|
- | `README.md` | `FILE INFORMATION` block + badge |
- | `manifest.xml` | `` tag |
- | `update.xml` | `` in the most recent `` block |
-
- The `make release` command / release workflow syncs all three automatically.
-
- ---
-
- # update.xml — Required in Repo Root
-
- `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
-
- ```xml
-
-
-
- {{REPO_URL}}/raw/main/update.xml
-
-
- ```
-
- **Rules:**
- - Every release prepends a new `` block at the top — older entries are preserved.
- - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`.
- - `` must be a publicly accessible GitHub Releases asset URL.
- - `` — backslash is literal (Joomla regex syntax).
-
- Example `update.xml` entry for a new release:
- ```xml
-
-
- {{EXTENSION_NAME}}
- {{REPO_NAME}}
- {{EXTENSION_ELEMENT}}
- {{EXTENSION_TYPE}}
- 01.02.04
- {{REPO_URL}}/releases/tag/01.02.04
-
-
- {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
-
-
-
- 7.4
- Moko Consulting
- https://mokoconsulting.tech
-
-
- ```
-
- ---
-
- # File Header Requirements
-
- Every new file **must** have a copyright header as its first content. JSON files, binary files, generated files, and third-party files are exempt.
-
- **PHP:**
- ```php
-
- *
- * This file is part of a Moko Consulting project.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- * FILE INFORMATION
- * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}}
- * INGROUP: {{REPO_NAME}}
- * REPO: {{REPO_URL}}
- * PATH: /site/controllers/item.php
- * VERSION: XX.YY.ZZ
- * BRIEF: One-line description of file purpose
- */
-
- defined('_JEXEC') or die;
- ```
-
- **Markdown / YAML / Shell / XML:** Use the appropriate comment syntax with the same fields.
-
- ---
-
- # Coding Standards
-
- ## Naming Conventions
-
- | Context | Convention | Example |
- |---------|-----------|---------|
- | PHP class | `PascalCase` | `ItemModel` |
- | PHP method / function | `camelCase` | `getItems()` |
- | PHP variable | `$snake_case` | `$item_id` |
- | PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
- | PHP class file | `PascalCase.php` | `ItemModel.php` |
- | YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
- | Markdown doc | `kebab-case.md` | `installation-guide.md` |
-
- ## Commit Messages
-
- Format: `(): ` — imperative, lower-case subject, no trailing period.
-
- Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
-
- ## Branch Naming
-
- Format: `/[/description]`
-
- Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
-
- ---
-
- # GitHub Actions — Token Usage
-
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
-
- ```yaml
- # ✅ Correct
- - uses: actions/checkout@v4
- with:
- token: ${{ secrets.GH_TOKEN }}
-
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- ```
-
- ```yaml
- # ❌ Wrong — never use these
- token: ${{ github.token }}
- token: ${{ secrets.GITHUB_TOKEN }}
- ```
-
- ---
-
- # Keeping Documentation Current
-
- | Change type | Documentation to update |
- |-------------|------------------------|
- | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
- | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
- | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
- | New or changed workflow | `docs/workflows/.md` |
- | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
- | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
-
- ---
-
- # What NOT to Do
-
- - **Never commit directly to `main`** — all changes go through a PR.
- - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- - **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
- - **Never skip the FILE INFORMATION block** on a new source file.
- - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
- - **Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows** — always use `secrets.GH_TOKEN`.
- - **Never remove `defined('_JEXEC') or die;`** from web-accessible PHP files.
-
- ---
-
- # PR Checklist
-
- Before opening a PR, verify:
-
- - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- - [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
- - [ ] FILE INFORMATION headers updated in modified files
- - [ ] CHANGELOG.md updated
- - [ ] Tests pass
-
- ---
-
- # Key Policy Documents (MokoStandards)
-
- | Document | Purpose |
- |----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
- MOKO_END
- }
- ]
- subdirectories = [
- {
- name = "workflows"
- path = ".github/workflows"
- description = "GitHub Actions workflows"
- requirement_status = "required"
- files = [
- {
- name = "ci-joomla.yml"
- extension = "yml"
- description = "Joomla-specific CI workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/joomla/ci-joomla.yml.template"
- },
- {
- name = "codeql-analysis.yml"
- extension = "yml"
- description = "CodeQL security analysis workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/generic/codeql-analysis.yml.template"
- },
- {
- name = "standards-compliance.yml"
- extension = "yml"
- description = "MokoStandards compliance validation"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/workflows/standards-compliance.yml"
- },
- {
- name = "enterprise-firewall-setup.yml"
- extension = "yml"
- description = "Enterprise firewall configuration for trusted domain access"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
- },
- {
- name = "deploy-dev.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the development server"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-dev.yml.template"
- },
- {
- name = "deploy-demo.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the demo server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-demo.yml.template"
- },
- {
- name = "deploy-rs.yml"
- extension = "yml"
- description = "SFTP deployment of src/ to the release staging server on merge to main"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/deploy-rs.yml.template"
- },
- {
- name = "sync-version-on-merge.yml"
- extension = "yml"
- description = "Auto-bump patch version on merge and propagate to all file headers"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/sync-version-on-merge.yml.template"
- },
- {
- name = "auto-release.yml"
- extension = "yml"
- description = "Auto-create GitHub Release on push to main with version from README.md"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/auto-release.yml.template"
- },
- {
- name = "repository-cleanup.yml"
- extension = "yml"
- description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/repository-cleanup.yml.template"
- },
- {
- name = "auto-dev-issue.yml"
- extension = "yml"
- description = "Auto-create tracking issue when a dev/** branch is pushed"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/shared/auto-dev-issue.yml.template"
- },
- {
- name = "repo_health.yml"
- extension = "yml"
- description = "Joomla-specific repository health check workflow"
- requirement_status = "required"
- always_overwrite = true
- template = "templates/workflows/joomla/repo_health.yml.template"
- }
- ]
- },
- {
- name = "ISSUE_TEMPLATE"
- path = ".github/ISSUE_TEMPLATE"
- description = "GitHub issue templates synced from MokoStandards"
- requirement_status = "required"
- files = [
- {
- name = "config.yml"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/config.yml"
- },
- {
- name = "adr.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/adr.md"
- },
- {
- name = "bug_report.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
- },
- {
- name = "documentation.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/documentation.md"
- },
- {
- name = "enterprise_support.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
- },
- {
- name = "feature_request.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
- },
- {
- name = "firewall-request.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
- },
- {
- name = "question.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/question.md"
- },
- {
- name = "request-license.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/request-license.md"
- },
- {
- name = "rfc.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/rfc.md"
- },
- {
- name = "security.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/security.md"
- },
- {
- name = "joomla_issue.md"
- always_overwrite = true
- template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md"
- }
- ]
- }
- ]
- }
- ]
-
- repository_requirements = {
- secrets = [
- {
- name = "GH_TOKEN"
- description = "Org-level GitHub PAT for automation"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_KEY"
- description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
- required = false
- scope = "org"
- },
- {
- name = "DEV_FTP_PASSWORD"
- description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
- required = false
- scope = "org"
- note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
- }
- ]
-
- variables = [
- {
- name = "DEV_FTP_HOST"
- description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_PATH"
- description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_USERNAME"
- description = "SFTP username for dev server authentication"
- required = true
- scope = "org"
- },
- {
- name = "DEV_FTP_PORT"
- description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
- required = false
- scope = "org"
- },
- {
- name = "DEV_FTP_SUFFIX"
- description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)"
- required = false
- scope = "repo"
- }
- ]
- }
- }
-}
diff --git a/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf
deleted file mode 100644
index 317bc66..0000000
--- a/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf
+++ /dev/null
@@ -1,1335 +0,0 @@
-/**
- * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Module
- *
- * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:29:31+00:00
- * Platform : waas-component
- * Description: A repo template for a Joomla Module coding project according to MokoStandards
- *
- * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
- * To change what gets synced, edit api/definitions/default/waas-component.tf
- * and re-run the bulk-repo-sync workflow.
- */
-
-locals {
- sync_record = {
- metadata = {
- repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Module"
- default_branch = "main"
- detected_platform = "waas-component"
- description = "A repo template for a Joomla Module coding project according to MokoStandards"
- sync_timestamp = "2026-04-02T15:29:31+00:00"
- source_repo = "mokoconsulting-tech/MokoStandards"
- base_definition = "api/definitions/default/waas-component.tf"
- }
-
- sync_stats = {
- total_files = 41
- created_files = 4
- updated_files = 34
- skipped_files = 3
- }
-
- synced_files = [
- { path = "LICENSE" action = "updated" },
- { path = "SECURITY.md" action = "created" },
- { path = "CODE_OF_CONDUCT.md" action = "updated" },
- { path = "CONTRIBUTING.md" action = "updated" },
- { path = "update.xml" action = "updated" },
- { path = "phpstan.neon" action = "updated" },
- { path = "Makefile" action = "updated" },
- { path = ".gitignore" action = "updated" },
- { path = "composer.json" action = "updated" },
- { path = ".mokostandards" action = "created" },
- { path = "docs/update-server.md" action = "created" },
- { path = ".github/copilot.yml" action = "updated" },
- { path = ".github/copilot-instructions.md" action = "updated" },
- { path = ".github/CLAUDE.md" action = "updated" },
- { path = ".github/workflows/codeql-analysis.yml" action = "updated" },
- { path = ".github/workflows/standards-compliance.yml" action = "updated" },
- { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
- { path = ".github/workflows/deploy-dev.yml" action = "updated" },
- { path = ".github/workflows/deploy-demo.yml" action = "updated" },
- { path = ".github/workflows/deploy-rs.yml" action = "updated" },
- { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
- { path = ".github/workflows/auto-release.yml" action = "updated" },
- { path = ".github/workflows/repository-cleanup.yml" action = "updated" },
- { path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
- { path = ".github/workflows/repo_health.yml" action = "created" },
- { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
- { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" },
- { path = ".github/CODEOWNERS" action = "updated" },
- { path = ".github/.mokostandards" action = "migrated from root" },
- ]
-
- skipped_files = [
- { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" },
- { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" },
- { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
- ]
- }
-}
-
-# ---- Base platform definition (reference copy) ----
-/**
- * MokoWaaS Component Structure Definition
- * Standard repository structure for MokoWaaS (Joomla) components
- *
- * Copyright (C) 2026 Moko Consulting
- * SPDX-License-Identifier: GPL-3.0-or-later
- * Version: 04.05.00
- * Schema Version: 1.0
- */
-
-locals {
- repository_structure = {
- metadata = {
- name = "MokoWaaS Component"
- description = "Standard repository structure for MokoWaaS (Joomla) components"
- repository_type = "waas-component"
- platform = "mokowaas"
- last_updated = "2026-01-15T00:00:00Z"
- maintainer = "Moko Consulting"
- version = "04.05.00"
- schema_version = "1.0"
- }
-
- root_files = [
- {
- name = "README.md"
- extension = "md"
- description = "Developer-focused documentation for contributors and maintainers"
- required = true
- always_overwrite = false
- protected = true
- audience = "developer"
- },
- {
- name = "LICENSE"
- extension = ""
- description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components"
- required = true
- audience = "general"
- template = "templates/licenses/GPL-3.0"
- license_type = "GPL-3.0-or-later"
- },
- {
- name = "CHANGELOG.md"
- extension = "md"
- description = "Version history and changes"
- required = true
- audience = "general"
- },
- {
- name = "SECURITY.md"
- extension = "md"
- description = "Security policy and vulnerability reporting"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-SECURITY.md"
- audience = "general"
- },
- {
- name = "CODE_OF_CONDUCT.md"
- extension = "md"
- description = "Community code of conduct"
- required = true
- always_overwrite = true
- template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
- always_overwrite = true
- audience = "contributor"
- },
- {
- name = "ROADMAP.md"
- extension = "md"
- description = "Project roadmap with version goals and milestones"
- required = false
- audience = "general"
- },
- {
- name = "CONTRIBUTING.md"
- extension = "md"
- description = "Contribution guidelines"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-CONTRIBUTING.md"
- audience = "contributor"
- },
- {
- name = "update.xml"
- extension = "xml"
- description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/joomla/update.xml.template"
- stub_content = <<-MOKO_END
-
-
-
- {{EXTENSION_NAME}}
- {{REPO_NAME}} — Moko Consulting Joomla extension
- {{EXTENSION_ELEMENT}}
- {{EXTENSION_TYPE}}
- {{VERSION}}
- {{REPO_URL}}/releases/tag/{{VERSION}}
-
- {{DOWNLOAD_URL}}
-
-
- 7.4
- Moko Consulting
- {{MAINTAINER_URL}}
-
-
- MOKO_END
- },
- {
- name = "phpstan.neon"
- extension = "neon"
- description = "PHPStan static analysis config with Joomla framework class stubs"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/phpstan.joomla.neon"
- },
- {
- name = "Makefile"
- description = "Build automation using MokoStandards templates"
- required = true
- always_overwrite = true
- audience = "developer"
- source_path = "templates/makefiles"
- source_filename = "Makefile.joomla.template"
- source_type = "template"
- destination_path = "."
- destination_filename = "Makefile"
- create_path = false
- template = "templates/makefiles/Makefile.joomla.template"
- },
- {
- name = ".gitignore"
- extension = "gitignore"
- description = "Git ignore patterns for Joomla development - preserved during sync operations"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/.gitignore.joomla"
- validation_rules = [
- {
- type = "content-pattern"
- description = "Must contain sftp-config pattern to ignore SFTP sync configuration files"
- pattern = "sftp-config"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.css pattern to ignore custom user CSS overrides"
- pattern = "user\\.css"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain user.js pattern to ignore custom user JavaScript overrides"
- pattern = "user\\.js"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts"
- pattern = "modulebuilder\\.txt"
- severity = "error"
- },
- {
- type = "content-pattern"
- description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides"
- pattern = "colors_custom\\.css"
- severity = "error"
- }
- ]
- },
- {
- name = ".gitattributes"
- extension = "gitattributes"
- description = "Git attributes configuration"
- required = true
- audience = "developer"
- },
- {
- name = ".editorconfig"
- extension = "editorconfig"
- description = "Editor configuration for consistent coding style - preserved during sync"
- required = true
- always_overwrite = false
- audience = "developer"
- },
- {
- name = "composer.json"
- extension = "json"
- description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
- required = true
- always_overwrite = false
- audience = "developer"
- template = "templates/configs/composer.joomla.json"
- },
- {
- name = ".mokostandards"
- extension = "yml"
- description = "MokoStandards governance attachment — links this repo back to the standards source"
- required = true
- always_overwrite = true
- audience = "developer"
- template = "templates/configs/mokostandards.yml.template"
- },
- {
- name = "GOVERNANCE.md"
- extension = "md"
- description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards"
- required = true
- always_overwrite = false
- protected = true
- audience = "all"
- template = "templates/docs/required/GOVERNANCE.md"
- }
- ]
-
- directories = [
- {
- name = "site"
- path = "site"
- description = "Component frontend (site) code"
- required = true
- purpose = "Contains frontend component code deployed to site"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main site controller"
- required = true
- audience = "developer"
- },
- {
- name = "manifest.xml"
- extension = "xml"
- description = "Component manifest for site"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "site/controllers"
- description = "Site controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "site/models"
- description = "Site models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "site/views"
- description = "Site views"
- required = true
- }
- ]
- },
- {
- name = "admin"
- path = "admin"
- description = "Component backend (admin) code"
- required = true
- purpose = "Contains backend component code for administrator"
- files = [
- {
- name = "controller.php"
- extension = "php"
- description = "Main admin controller"
- required = true
- audience = "developer"
- }
- ]
- subdirectories = [
- {
- name = "controllers"
- path = "admin/controllers"
- description = "Admin controllers"
- requirement_status = "suggested"
- },
- {
- name = "models"
- path = "admin/models"
- description = "Admin models"
- requirement_status = "suggested"
- },
- {
- name = "views"
- path = "admin/views"
- description = "Admin views"
- required = true
- },
- {
- name = "sql"
- path = "admin/sql"
- description = "Database schema files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "media"
- path = "media"
- description = "Media files (CSS, JS, images)"
- requirement_status = "suggested"
- purpose = "Contains static assets"
- subdirectories = [
- {
- name = "css"
- path = "media/css"
- description = "Stylesheets"
- requirement_status = "suggested"
- },
- {
- name = "js"
- path = "media/js"
- description = "JavaScript files"
- requirement_status = "suggested"
- },
- {
- name = "images"
- path = "media/images"
- description = "Image files"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = "language"
- path = "language"
- description = "Language translation files"
- required = true
- purpose = "Contains language INI files"
- },
- {
- name = "docs"
- path = "docs"
- description = "Developer and technical documentation"
- required = true
- purpose = "Contains technical documentation, API docs, architecture diagrams"
- files = [
- {
- name = "index.md"
- extension = "md"
- description = "Documentation index"
- required = true
- },
- {
- name = "update-server.md"
- extension = "md"
- description = "Joomla update server (update.xml) documentation"
- required = true
- always_overwrite = true
- template = "templates/docs/required/template-update-server-joomla.md"
- }
- ]
- },
- {
- name = "scripts"
- path = "scripts"
- description = "Repo-specific scripts — not managed by MokoStandards sync"
- required = false
- purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
- files = [
- {
- name = "MokoStandards.override.xml"
- extension = "xml"
- description = "MokoStandards sync override configuration - preserved during sync"
- requirement_status = "optional"
- always_overwrite = false
- audience = "developer"
- }
- ]
- },
- {
- name = "tests"
- path = "tests"
- description = "Test files"
- required = true
- purpose = "Contains unit tests, integration tests, and test fixtures"
- subdirectories = [
- {
- name = "unit"
- path = "tests/unit"
- description = "Unit tests"
- required = true
- },
- {
- name = "integration"
- path = "tests/integration"
- description = "Integration tests"
- requirement_status = "suggested"
- }
- ]
- },
- {
- name = ".github"
- path = ".github"
- description = "GitHub-specific configuration"
- requirement_status = "suggested"
- purpose = "Contains GitHub Actions workflows and configuration"
- files = [
- {
- name = "copilot.yml"
- extension = "yml"
- description = "GitHub Copilot allowed domains configuration"
- requirement_status = "required"
- always_overwrite = true
- template = ".github/copilot.yml"
- },
- {
- name = "copilot-instructions.md"
- extension = "md"
- description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition"
- requirement_status = "required"
- always_overwrite = false
- destination_path = ".github"
- destination_filename = "copilot-instructions.md"
- template = "templates/github/copilot-instructions.joomla.md.template"
- stub_content = <<-MOKO_END
- > [!IMPORTANT]
- > **🔧 AI Self-Update Required on First Use**
- >
- > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
- > replaces them automatically at sync time; if you can still see them, sync has not yet run for
- > this repository — or this is a brand-new repo.
- >
- > **On your very first interaction in this repository, before answering any other question:**
- >
- > 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
- > 2. If they do, locate the values as described in the table below and replace every occurrence
- > in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
- > 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
- > through the closing `> ---` line) in both files.
- > 4. Tell the developer: *"I've self-updated the AI context files with this repository's
- > values — all set."*
- >
- > | Placeholder | Where to find the value |
- > |---|---|
- > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
- > | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` |
- > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root |
- > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
- > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
- >
- > ---
-
- # {{REPO_NAME}} — GitHub Copilot Custom Instructions
-
- ## What This Repo Is
-
- This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
-
- Repository URL: {{REPO_URL}}
- Extension name: **{{EXTENSION_NAME}}**
- Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
- Platform: **Joomla 4.x / MokoWaaS**
-
- ---
-
- ## Primary Language
-
- **PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
-
- ---
-
- ## File Header — Always Required on New Files
-
- Every new file needs a copyright header as its first content.
-
- **PHP:**
- ```php
-
- *
- * This file is part of a Moko Consulting project.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- * FILE INFORMATION
- * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}}
- * INGROUP: {{REPO_NAME}}
- * REPO: {{REPO_URL}}
- * PATH: /path/to/file.php
- * VERSION: XX.YY.ZZ
- * BRIEF: One-line description of purpose
- */
-
- defined('_JEXEC') or die;
- ```
-
- **Markdown:**
- ```markdown
-
- ```
-
- **YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
-
- ---
-
- ## Version Management
-
- **`README.md` is the single source of truth for the repository version.**
-
- - **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
- - The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
- - Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- - Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
-
- ### Joomla Version Alignment
-
- The version in `README.md` **must always match** the `` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically.
-
- ```xml
-
- 01.02.04
-
-
-
-
- {{EXTENSION_NAME}}
- 01.02.04
-
-
- {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
-
-
-
-
-
-
- ```
-
- ---
-
- ## Joomla Extension Structure
-
- ```
- {{REPO_NAME}}/
- ├── manifest.xml # Joomla installer manifest (root — required)
- ├── update.xml # Update server manifest (root — required, see below)
- ├── site/ # Frontend (site) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ └── views/
- ├── admin/ # Backend (admin) code
- │ ├── controller.php
- │ ├── controllers/
- │ ├── models/
- │ ├── views/
- │ └── sql/
- ├── language/ # Language INI files
- ├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
- ├── docs/ # Technical documentation
- ├── tests/ # Test suite
- ├── .github/
- │ ├── workflows/
- │ ├── copilot-instructions.md # This file
- │ └── CLAUDE.md
- ├── README.md # Version source of truth
- ├── CHANGELOG.md
- ├── CONTRIBUTING.md
- ├── LICENSE # GPL-3.0-or-later
- └── Makefile # Build automation
- ```
-
- ---
-
- ## update.xml — Required in Repo Root
-
- `update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
-
- The `manifest.xml` must reference it via:
- ```xml
-
-
- {{REPO_URL}}/raw/main/update.xml
-
-
- ```
-
- **Rules:**
- - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below.
- - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`.
- - The `` must be a publicly accessible direct download link (GitHub Releases asset URL).
- - `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
-
- ---
-
- ## manifest.xml Rules
-
- - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- - `` tag must be kept in sync with `README.md` version and `update.xml`.
- - Must include `` block pointing to this repo's `update.xml`.
- - Must include `` and `` sections.
- - Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions.
-
- ---
-
- ## GitHub Actions — Token Usage
-
- Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
-
- ```yaml
- # ✅ Correct
- - uses: actions/checkout@v4
- with:
- token: ${{ secrets.GH_TOKEN }}
-
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- ```
-
- ```yaml
- # ❌ Wrong — never use these in workflows
- token: ${{ github.token }}
- token: ${{ secrets.GITHUB_TOKEN }}
- ```
-
- ---
-
- ## MokoStandards Reference
-
- This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies:
-
- | Document | Purpose |
- |----------|---------|
- | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
- | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
- | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
- | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
- | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
- | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
-
- ---
-
- ## Naming Conventions
-
- | Context | Convention | Example |
- |---------|-----------|---------|
- | PHP class | `PascalCase` | `MyController` |
- | PHP method / function | `camelCase` | `getItems()` |
- | PHP variable | `$snake_case` | `$item_id` |
- | PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
- | PHP class file | `PascalCase.php` | `ItemModel.php` |
- | YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
- | Markdown doc | `kebab-case.md` | `installation-guide.md` |
-
- ---
-
- ## Commit Messages
-
- Format: `(