Initial: MokoStandards Enterprise API extracted from MokoStandards

Standalone Composer package (mokoconsulting-tech/enterprise).
Source: api/, bin/, lib/ directories from MokoStandards main repo.
Autoload paths updated for standalone layout.
This commit is contained in:
Claude Code
2026-04-13 06:12:04 +00:00
parent e60fa539b2
commit 5e63faf229
215 changed files with 104197 additions and 2 deletions
+80
View File
@@ -0,0 +1,80 @@
# EditorConfig helps maintain consistent coding styles across different editors and IDEs
# https://editorconfig.org/
root = true
# Default settings — Tabs preferred, width = 2 spaces
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
tab_width = 2
# PowerShell scripts — tabs, 2-space visual width
[*.ps1]
indent_style = tab
tab_width = 2
end_of_line = crlf
# Markdown files — keep trailing whitespace for line breaks, use tabs for indentation
# No max_line_length specified - Markdown lines can be any length
[*.md]
trim_trailing_whitespace = false
indent_style = tab
tab_width = 2
max_line_length = off
# YAML files — spaces only (YAML spec forbids tabs)
[*.{yml,yaml}]
indent_style = space
indent_size = 2
# Python files — spaces only (PEP 8 standard)
[*.py]
indent_style = space
indent_size = 4
# Haskell files — spaces only (layout rules)
[*.{hs,lhs}]
indent_style = space
indent_size = 2
# F# files — spaces only (indentation-sensitive syntax)
[*.{fs,fsx,fsi}]
indent_style = space
indent_size = 4
# CoffeeScript files — spaces only (whitespace-significant)
[*.coffee]
indent_style = space
indent_size = 2
# Nim files — spaces only (style guide)
[*.{nim,nims,nimble}]
indent_style = space
indent_size = 2
# JSON files — spaces only (parser compatibility)
[*.json]
indent_style = space
indent_size = 2
# reStructuredText files — spaces only (indentation requirement)
[*.rst]
indent_style = space
indent_size = 3
# Makefiles — always tabs, default width
[Makefile]
indent_style = tab
tab_width = 2
# Windows batch scripts — keep CRLF endings
[*.{bat,cmd}]
end_of_line = crlf
# Shell scripts — ensure LF endings
[*.sh]
end_of_line = lf
+1064
View File
File diff suppressed because it is too large Load Diff
+575
View File
@@ -0,0 +1,575 @@
{
"metadata": {
"generated_at": "2026-03-10T19:51:42.238134Z",
"repository": "mokoconsulting-tech/MokoStandards",
"version": "1.0.0"
},
"scripts": [
{
"path": "scripts/analysis/analyze_dependencies.py",
"sha256": "7d282416b124d8cf908118e309667ace5e65d63202800df69f4f317208f42a52",
"category": "analysis",
"priority": "medium",
"size_bytes": 10186
},
{
"path": "scripts/analysis/analyze_pr_conflicts.py",
"sha256": "84a26f39470acd502e4288d24dfa17215ffbb90dfeb12e58a1cbb45fcdc72c48",
"category": "analysis",
"priority": "medium",
"size_bytes": 8678
},
{
"path": "scripts/analysis/code_metrics.py",
"sha256": "6f5008294b89ca2c393dd3dab7c6eb23808a3e078aede3ed48f8a0e4ffb55975",
"category": "analysis",
"priority": "medium",
"size_bytes": 8176
},
{
"path": "scripts/analysis/generate_canonical_config.py",
"sha256": "d7867a61200d904f08c7efa7e388734d7aa0c6d84aefb263facbc823ab14ab1c",
"category": "analysis",
"priority": "medium",
"size_bytes": 29163
},
{
"path": "scripts/automation/Invoke-BulkUpdateGUI.ps1",
"sha256": "95b506917ed12032c05bc76ae461a71882a1c2e52b64a0f5d48957ca9f33b841",
"category": "automation",
"priority": "high",
"size_bytes": 10464
},
{
"path": "scripts/automation/Update-BulkRepositories.ps1",
"sha256": "b7a67bead5cea84cc8826f6c02d7167d50797f1f08d5bf8dd8742cdbc697f874",
"category": "automation",
"priority": "high",
"size_bytes": 29695
},
{
"path": "scripts/automation/auto_create_org_projects.py",
"sha256": "3055340618b7d1bd9d428a525c9273b0fec7e41aa42a82e5957ebf7aef2c20db",
"category": "automation",
"priority": "high",
"size_bytes": 23108
},
{
"path": "scripts/automation/bulk_deploy_labels.sh",
"sha256": "b39c435a99d8b40c2f85b27d68c1547cd9833436cef5b9ff4e3e2a19a6b154b9",
"category": "automation",
"priority": "high",
"size_bytes": 7181
},
{
"path": "scripts/automation/bulk_update_repos.php",
"sha256": "6e47cce8a5528db0081dc246367ef1b5da876f591d43b34664fbb608ef26cecd",
"category": "automation",
"priority": "high",
"size_bytes": 61417
},
{
"path": "scripts/automation/check_outdated_actions.py",
"sha256": "f2fefc08a679c477cf4b3e031a93cc45261fdff4fd0165196058400b35f21451",
"category": "automation",
"priority": "high",
"size_bytes": 8292
},
{
"path": "scripts/automation/create_repo_project.py",
"sha256": "718240fdb232a606142eca6a7fca774acae19400a5fce6f1fa42eb7567ae6d0a",
"category": "automation",
"priority": "high",
"size_bytes": 8295
},
{
"path": "scripts/automation/dev-workstation-provisioner.ps1",
"sha256": "567062f6f513ea49616fb098732f9aa934505983d789fd8b29f9c1c1d5356d08",
"category": "automation",
"priority": "high",
"size_bytes": 5747
},
{
"path": "scripts/automation/file-distributor.ps1",
"sha256": "afc991e0e1a48c032da31d863fd95e6ebcfffaac12ae8b5ce03455818602a8da",
"category": "automation",
"priority": "high",
"size_bytes": 43979
},
{
"path": "scripts/automation/file-distributor.py",
"sha256": "a844784832928b427c5e9cd2b30d2e7c5dfb08894f6b8fb46cc2244e5a180be1",
"category": "automation",
"priority": "high",
"size_bytes": 22203
},
{
"path": "scripts/automation/generate_wrappers.py",
"sha256": "0077ffb71062345da9e81757715699bfd4eb601cc750408957635e1c7747ef76",
"category": "automation",
"priority": "high",
"size_bytes": 6769
},
{
"path": "scripts/automation/setup_dev_environment.py",
"sha256": "f9ec448c809b83c1af1606044ea27786dd0a8abfdfb58f59b7a3d30bc570b54f",
"category": "automation",
"priority": "high",
"size_bytes": 9025
},
{
"path": "scripts/automation/sync_dolibarr_changelog.py",
"sha256": "98e935800ac8d4289b3fdd2d31fc12ea993f25cf78bc5ac84817233985e98a4d",
"category": "automation",
"priority": "high",
"size_bytes": 14336
},
{
"path": "scripts/automation/sync_file_to_project.py",
"sha256": "2c7b385500d0327fe41421e30d8a04b905b4141e58eb629908924242e4e911e2",
"category": "automation",
"priority": "high",
"size_bytes": 16352
},
{
"path": "scripts/automation/ubuntu-dev-workstation-provisioner.sh",
"sha256": "7be2264da6ae7ef94bf0536a29e768308ab4af91e6ed37ab2c4e329d9349cec5",
"category": "automation",
"priority": "high",
"size_bytes": 16825
},
{
"path": "scripts/build/resolve_makefile.py",
"sha256": "980dee07bbc9a0d53496aea33bd1711495f3f599ed742e3ec1565a5f132cf99a",
"category": "build",
"priority": "high",
"size_bytes": 5498
},
{
"path": "scripts/docs/check_doc_coverage.py",
"sha256": "a60e66f4b5878e6c70ebda8044b9573c0d457ae228da09b21b51fccdf1c0b3e4",
"category": "docs",
"priority": "medium",
"size_bytes": 8923
},
{
"path": "scripts/docs/generate_script_catalog.py",
"sha256": "c99c33f2fdc1c7d3c51d22bec0648555505f5caed7cec5fa4586622bde6e00f0",
"category": "docs",
"priority": "medium",
"size_bytes": 7974
},
{
"path": "scripts/docs/rebuild_indexes.py",
"sha256": "4e5331fa56105eca71c61f9d3aa74dd96de2b93f73d30aa8e502aec3b2bfa399",
"category": "docs",
"priority": "medium",
"size_bytes": 15022
},
{
"path": "scripts/docs/update_metadata.py",
"sha256": "9a868f856ee8beb5b28713c619476cab3ead6b460cb6013f736e6c1d668ced18",
"category": "docs",
"priority": "medium",
"size_bytes": 11038
},
{
"path": "scripts/fix/file_headers.py",
"sha256": "d7345296a0f51f9e2c5b8affd71955bb09f19a27ec4b65c975de52f7745fcfac",
"category": "fix",
"priority": "high",
"size_bytes": 10981
},
{
"path": "scripts/fix/tabs.py",
"sha256": "d5db5d2ca3ef74a7399e77ee6181eae0ad6d5ed52d2ee08b03a413ce75b38fd3",
"category": "fix",
"priority": "high",
"size_bytes": 10332
},
{
"path": "scripts/fix/trailing_spaces.py",
"sha256": "9812f026c6136e33c19216466bdc1f26a12c01b0564abc3413eed5909068f345",
"category": "fix",
"priority": "high",
"size_bytes": 8156
},
{
"path": "scripts/maintenance/add_dry_run_support.py",
"sha256": "d892d4bfc7dba2122c4ca2908f74fbc8f2f65f4131ad93a63af945f051d170ee",
"category": "maintenance",
"priority": "critical",
"size_bytes": 7731
},
{
"path": "scripts/maintenance/add_terraform_metadata.py",
"sha256": "e8a46c37c737ed92b05e8ce60fd9ecb2de4154ce0f75eab6d52ce5bc6863b222",
"category": "maintenance",
"priority": "critical",
"size_bytes": 6316
},
{
"path": "scripts/maintenance/clean_old_branches.py",
"sha256": "5d98e59d502162c451ed5b0a656b15155d7226889d5416a5fb82faa9b29b348f",
"category": "maintenance",
"priority": "critical",
"size_bytes": 9079
},
{
"path": "scripts/maintenance/flush_actions_cache.py",
"sha256": "d1acdbba58e5ec01965317e67010f16a36b52ca00f49ec6cf3a83359fbc15404",
"category": "maintenance",
"priority": "critical",
"size_bytes": 8640
},
{
"path": "scripts/maintenance/generate_script_registry.py",
"sha256": "1c385c746e9803098a5eb7b702ca16b2a281c5f362c1de726c8fedd782b52074",
"category": "maintenance",
"priority": "critical",
"size_bytes": 10109
},
{
"path": "scripts/maintenance/release_version.py",
"sha256": "e2eaca49f632752aa53bce5d8c040171bb7130e9b33da18465f71c58aaf681f2",
"category": "maintenance",
"priority": "critical",
"size_bytes": 15455
},
{
"path": "scripts/maintenance/setup-labels.sh",
"sha256": "3a1996a3d2052b184e20cf9a729ff232249ad9f0796fd08b11febf983b9bdf29",
"category": "maintenance",
"priority": "critical",
"size_bytes": 7057
},
{
"path": "scripts/maintenance/update_changelog.py",
"sha256": "133685b81fe4a096a7987aeade3854bc5dc42a8005dacba3f62bc94116b14948",
"category": "maintenance",
"priority": "critical",
"size_bytes": 10624
},
{
"path": "scripts/maintenance/update_copyright_year.py",
"sha256": "f09e7790f9bd7d6ed94903891909feffb223a012abb3520768666e076c52570b",
"category": "maintenance",
"priority": "critical",
"size_bytes": 7062
},
{
"path": "scripts/maintenance/update_gitignore_patterns.sh",
"sha256": "ce33dbd74998bf9708eb41fee4999a8d233b8f4211e53b415b04d1d4c3077900",
"category": "maintenance",
"priority": "critical",
"size_bytes": 7192
},
{
"path": "scripts/maintenance/update_sha_hashes.py",
"sha256": "e917b438405d86c1cbeff7a3f265e0181e266935fd9aaa2e66c21b86a5e7b266",
"category": "maintenance",
"priority": "critical",
"size_bytes": 5462
},
{
"path": "scripts/maintenance/validate_file_headers.py",
"sha256": "f9aafe4982704f21f74583d3362ede8eaa9cda3cbf7eaf7064ac22072a046dc7",
"category": "maintenance",
"priority": "critical",
"size_bytes": 9065
},
{
"path": "scripts/maintenance/validate_script_registry.py",
"sha256": "8653a2fd2493a2b4659236d13b502a2630305c1832190ea97afcc879fd9b3455",
"category": "maintenance",
"priority": "critical",
"size_bytes": 7964
},
{
"path": "scripts/maintenance/validate_terraform_drift.py",
"sha256": "0397fd9d89b5d0bece9702c276644431f95a2ecbe4db91eb6bfd214d49eebd67",
"category": "maintenance",
"priority": "critical",
"size_bytes": 11484
},
{
"path": "scripts/release/detect_platform.py",
"sha256": "a27ed3d086aa9984b9d0a593705499e76fdb8b784c3ba4663989d312d5ab8745",
"category": "release",
"priority": "high",
"size_bytes": 2784
},
{
"path": "scripts/release/dolibarr_release.py",
"sha256": "4f966b4ad10e14dd65f85ab39ab4784bf3588620de2002c4c8c4d83c1d76d036",
"category": "release",
"priority": "high",
"size_bytes": 11637
},
{
"path": "scripts/release/package_extension.py",
"sha256": "d03fe260f21378ee6b2a92ff93bda4b34970908cf2d784aae50bf51ea706f847",
"category": "release",
"priority": "high",
"size_bytes": 9215
},
{
"path": "scripts/release/unified_release.py",
"sha256": "fa67e1e15b1570fd2c6d7a78b13050cbcc7aeb010bd612f738560fa6b7b9d9f9",
"category": "release",
"priority": "high",
"size_bytes": 17687
},
{
"path": "scripts/release/update_dates.sh",
"sha256": "77a7cd946cecfcbd9a6fa2fd4f736b297c6953bd035aa652a8ea105f9bba0d00",
"category": "release",
"priority": "high",
"size_bytes": 3724
},
{
"path": "scripts/run/Invoke-DemoDataLoaderGUI.ps1",
"sha256": "6f5ade6e9e52771a6e43f37050a3c854510f8ed33295016083080e96dcb8a499",
"category": "run",
"priority": "medium",
"size_bytes": 12596
},
{
"path": "scripts/run/Load-DemoData.ps1",
"sha256": "faccb2413d3c1de5124f26ff2977769398a07c430386f4a3b38dba158fea11e7",
"category": "run",
"priority": "medium",
"size_bytes": 9835
},
{
"path": "scripts/run/git_helper.sh",
"sha256": "56c520472707a9406285e911876e90cf5c385624e135f730671ccb07400ddaeb",
"category": "run",
"priority": "medium",
"size_bytes": 7960
},
{
"path": "scripts/run/load_demo_data.py",
"sha256": "9e5ee55f8eec3b3015cafe502b1f2b5bc7f832870d2eda93bf8450cbfff5bbac",
"category": "run",
"priority": "medium",
"size_bytes": 11125
},
{
"path": "scripts/run/setup_github_project_v2.py",
"sha256": "a0f9cb4dad755baec4311fa6164053047f894337bd831bfc06fba92eff7d96bc",
"category": "run",
"priority": "medium",
"size_bytes": 29687
},
{
"path": "scripts/tests/test_bulk_update_repos.php",
"sha256": "0794f2d9bc2020c741b427f9a8fcbd767120d00c97a040ebbff13b2427cb6833",
"category": "tests",
"priority": "medium",
"size_bytes": 4506
},
{
"path": "scripts/tests/test_dry_run.py",
"sha256": "2e99f3ea896d98692bcdd4d8d6c6fa7e117fa96e2518cf3a1f173f11e721e76f",
"category": "tests",
"priority": "medium",
"size_bytes": 6327
},
{
"path": "scripts/validate/Invoke-PlatformDetection.ps1",
"sha256": "cd0f25245465849b21aec5a74366f756ef1de79185d822a0190441a5577100d3",
"category": "validate",
"priority": "critical",
"size_bytes": 19116
},
{
"path": "scripts/validate/Invoke-RepoHealthCheckGUI.ps1",
"sha256": "c952922e37e1e6f1448f7620b88b1ff896597be46a0205acf1de0b96299091d6",
"category": "validate",
"priority": "critical",
"size_bytes": 9455
},
{
"path": "scripts/validate/auto_detect_platform.php",
"sha256": "82c6b47117cadecfcdee8c63f9c9566a7c27db34c19456ce49297e666840aa3b",
"category": "validate",
"priority": "critical",
"size_bytes": 13466
},
{
"path": "scripts/validate/check_all_files.py",
"sha256": "e708aae24271eb2d0457acb922809d5a51e4a7fb5f1fd4cd45783dea42a63cc6",
"category": "validate",
"priority": "critical",
"size_bytes": 32318
},
{
"path": "scripts/validate/check_license_headers.py",
"sha256": "cc5f4ba9539da8fbe0a397893571bfc9a18e46da1e2fb275389cc53737fdee89",
"category": "validate",
"priority": "critical",
"size_bytes": 9091
},
{
"path": "scripts/validate/check_markdown_links.py",
"sha256": "821277627cbaa681cff5aa0f668a738a3337f6e7227bf8bfd951442418407b24",
"category": "validate",
"priority": "critical",
"size_bytes": 6950
},
{
"path": "scripts/validate/check_repo_health.py",
"sha256": "d7b8aa256c2eb6f400b653d6b7a9d9bc51a31292bee822827d6aedb2ca690b88",
"category": "validate",
"priority": "critical",
"size_bytes": 23616
},
{
"path": "scripts/validate/check_script_security.py",
"sha256": "9e98e9fd750a4c0e4095b8c1951cfba3e2ceed004d056a53b58f21fd176f881b",
"category": "validate",
"priority": "critical",
"size_bytes": 12192
},
{
"path": "scripts/validate/check_version_consistency.php",
"sha256": "c33fc6823b92b26dd932f67d2c20817a9815ed3c993c0617b6a322548f6e8cd3",
"category": "validate",
"priority": "high",
"size_bytes": 10522
},
{
"path": "scripts/validate/find_todos.py",
"sha256": "9e064323374bce029466234433ec6eedaa46afe4027b8cdd90048ba972eb7274",
"category": "validate",
"priority": "critical",
"size_bytes": 8070
},
{
"path": "scripts/validate/generate_stubs.py",
"sha256": "e1c1bf1dd10449cfbef4678e1e09d12b7cc7e5e38f9c6fa3819d9c63600c597e",
"category": "validate",
"priority": "critical",
"size_bytes": 14350
},
{
"path": "scripts/validate/manifest.py",
"sha256": "a65779bcb4d184ef8583713f1b1f18399864bd2a8bf17a692ba2241566fc2b1d",
"category": "validate",
"priority": "critical",
"size_bytes": 5083
},
{
"path": "scripts/validate/no_secrets.py",
"sha256": "c514a64477c535dfd7c16b7a722253e601c51b3ec723efcb3ce933a169b427af",
"category": "validate",
"priority": "critical",
"size_bytes": 6784
},
{
"path": "scripts/validate/paths.py",
"sha256": "ae5500bb1d595af4a15e8c5e15e4d6ea500939f166dc6b14c4bcfc4d597dfe77",
"category": "validate",
"priority": "critical",
"size_bytes": 4792
},
{
"path": "scripts/validate/php_syntax.py",
"sha256": "9a99ce519688694fd0840d2515cc43b563be97382236750a72bef1c5364d16b7",
"category": "validate",
"priority": "critical",
"size_bytes": 6116
},
{
"path": "scripts/validate/schema_aware_health_check.py",
"sha256": "2e53e0ad48cb1303af08ca87256759369a625d52019b3d641bc24703f0a8559c",
"category": "validate",
"priority": "critical",
"size_bytes": 22482
},
{
"path": "scripts/validate/security_scan.py",
"sha256": "25ff80b8382546460b1c08fa05d4bf482de3f1a5c4afef4d568c25cd2a900077",
"category": "validate",
"priority": "critical",
"size_bytes": 17377
},
{
"path": "scripts/validate/tabs.py",
"sha256": "16ee38094e517642d76d2bd2de71b816bd24716e170cc70c6f9710e2231b6a52",
"category": "validate",
"priority": "critical",
"size_bytes": 10328
},
{
"path": "scripts/validate/validate_codeql_config.py",
"sha256": "b79bf71adc8968805fac1a36a764ffde543099d110a93593767bdc9fa890673d",
"category": "validate",
"priority": "critical",
"size_bytes": 8210
},
{
"path": "scripts/validate/validate_repo_health.py",
"sha256": "7908092192e4d09e18b424bae978c1c5fc84340413b1be9538bf1b11763ab3a0",
"category": "validate",
"priority": "critical",
"size_bytes": 12597
},
{
"path": "scripts/validate/validate_structure.py",
"sha256": "1de72b1b605fa60c138426fbdbae8fc15ec0320e999b02b54f30c5b185aa0e2b",
"category": "validate",
"priority": "critical",
"size_bytes": 15000
},
{
"path": "scripts/validate/validate_structure_terraform.py",
"sha256": "73d44fc2567c091c65b8fd8b2bfcf0c6f1925f2772420d6b1e8f71409820a40b",
"category": "validate",
"priority": "critical",
"size_bytes": 17188
},
{
"path": "scripts/validate/validate_structure_v2.py",
"sha256": "b0cdf73293b16777e0714eb978d3cd636655c9659903eed45cf8125706eaaf20",
"category": "validate",
"priority": "critical",
"size_bytes": 15938
},
{
"path": "scripts/validate/workflows.py",
"sha256": "31a4470ef5bfa7212c1ed1e0c707f840dfb9c06b2e199bac1a5fbab1b78dda1f",
"category": "validate",
"priority": "critical",
"size_bytes": 6571
},
{
"path": "scripts/validate/xml_wellformed.py",
"sha256": "8559a70cda76b08eddbe616256897a59ace643e274879c7ebd1998c8294868f8",
"category": "validate",
"priority": "critical",
"size_bytes": 5848
}
],
"summary": {
"total_scripts": 77,
"by_priority": {
"medium": 15,
"high": 24,
"critical": 38
},
"by_category": {
"analysis": 4,
"automation": 15,
"build": 1,
"docs": 4,
"fix": 3,
"maintenance": 14,
"release": 5,
"run": 5,
"tests": 2,
"validate": 24
}
}
}
+82
View File
@@ -0,0 +1,82 @@
# Plugin System CLI Scripts
Command-line scripts for validating, health checking, and managing projects using the MokoStandards plugin system.
## Available Scripts
| Script | Purpose |
|--------|---------|
| `plugin_validate.php` | Validate project structure and configuration |
| `plugin_health_check.php` | Run comprehensive health checks |
| `plugin_metrics.php` | Collect project metrics |
| `plugin_readiness.php` | Check release readiness |
| `plugin_list.php` | List all available plugins |
## Quick Examples
```bash
# List all available plugins
php api/plugin_list.php
# Validate a project (auto-detect type)
php api/plugin_validate.php --project-path /path/to/project
# Run health check
php api/plugin_health_check.php --project-path /path/to/project
# Collect metrics
php api/plugin_metrics.php --project-path /path/to/project --format table
# Check release readiness
php api/plugin_readiness.php --project-path /path/to/project
```
## Supported Project Types
- **joomla** - Joomla CMS projects and extensions
- **wordpress** - WordPress themes and plugins
- **nodejs** - Node.js applications and packages
- **python** - Python applications and packages
- **terraform** - Infrastructure as Code
- **mobile** - Mobile applications (iOS/Android)
- **api** - REST API and GraphQL services
- **dolibarr** - Dolibarr ERP/CRM modules
- **documentation** - Documentation projects
- **generic** - Generic project types
## Exit Codes
- **0** - Success
- **1** - Validation/check failed
- **2** - Script error (invalid arguments, plugin not found)
## Documentation
For detailed documentation, see:
- [Plugin Validation Workflow Templates](../templates/workflows/README.md)
- [Plugin System Implementation](lib/Enterprise/README.md)
- Script help: `php api/plugin_*.php --help`
## Integration
These scripts integrate with:
- GitHub Actions workflows (see `templates/workflows/`)
- Plugin system (see `lib/Enterprise/`)
- CI/CD pipelines (GitLab CI, Jenkins, etc.)
## Usage in CI/CD
```yaml
# GitHub Actions example
- name: Validate project
run: |
php api/plugin_validate.php --project-path . --json > validation.json
if jq -e '.valid == false' validation.json > /dev/null; then
exit 1
fi
```
For complete usage examples and documentation, run any script with `--help`:
```bash
php api/plugin_validate.php --help
```
+40 -2
View File
@@ -1,3 +1,41 @@
# MokoStandards-API
# MokoStandards Enterprise API
MokoStandards Enterprise API — PHP implementation (Composer package: mokoconsulting-tech/enterprise)
PHP implementation of MokoStandards — enterprise standards and automation framework.
## Installation
```bash
composer require mokoconsulting-tech/enterprise
```
### Composer Registry
This package is served from Gitea package registry. Add this to your `composer.json`:
```json
{
"repositories": [
{
"type": "composer",
"url": "https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"
}
]
}
```
## CLI Tools
```bash
# Health check
vendor/bin/moko health -- --path .
# Sync standards
vendor/bin/moko sync
# Inventory
vendor/bin/moko inventory -- --path .
```
## License
GPL-3.0-or-later — See [LICENSE.md](LICENSE.md)
+20
View File
@@ -0,0 +1,20 @@
# Docs Index: /api/analysis
## Purpose
This index provides navigation to documentation within this folder.
## Documents
- [README](./README.md)
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
+1374
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Example configuration file for file-distributor.ps1 v02.00.00",
"SourceFile": "C:\\path\\to\\your\\source\\file.txt",
"RootDirectory": "C:\\path\\to\\root\\directory",
"Depth": 1,
"DryRun": true,
"Overwrite": false,
"ConfirmEach": false,
"IncludeHidden": true,
"LogDirectory": "C:\\path\\to\\logs"
}
+21
View File
@@ -0,0 +1,21 @@
# Docs Index: /api/automation
## Purpose
This index provides navigation to documentation within this folder.
## Documents
- [README-file-distributor](./README-file-distributor.md)
- [README](./README.md)
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
+296
View File
@@ -0,0 +1,296 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Automation
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/automation/migrate_to_gitea.php
* VERSION: 04.06.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
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\CheckpointManager;
use MokoEnterprise\CliFramework;
use MokoEnterprise\Config;
use MokoEnterprise\PlatformAdapterFactory;
use MokoEnterprise\GitHubAdapter;
use MokoEnterprise\GiteaAdapter;
/**
* Gitea Migration Script
*
* Migrates repositories from GitHub to a self-hosted Gitea instance.
* Uses Gitea's built-in migration endpoint for git history, tags, releases,
* issues, and labels. Post-migration applies branch protection, topics,
* and workflow conversion.
*/
class MigrateToGitea extends CliFramework
{
private ?GitHubAdapter $github = null;
private ?GiteaAdapter $gitea = null;
private ?CheckpointManager $checkpoints = null;
protected function configure(): void
{
$this->setDescription('Migrate repositories from GitHub to Gitea');
$this->addArgument('--dry-run', 'Show what would be migrated without making changes', false);
$this->addArgument('--repos', 'Specific repositories to migrate (space-separated)', '');
$this->addArgument('--exclude', 'Repositories to exclude (space-separated)', '');
$this->addArgument('--skip-archived', 'Skip archived repositories', false);
$this->addArgument('--resume', 'Resume from last checkpoint', false);
$this->addArgument('--github-token', 'GitHub token override', '');
$this->addArgument('--gitea-token', 'Gitea token override', '');
}
protected function run(): int
{
$dryRun = (bool) $this->getArgument('--dry-run');
$specificRepos = array_filter(explode(' ', (string) $this->getArgument('--repos')));
$excludeRepos = array_filter(explode(' ', (string) $this->getArgument('--exclude')));
$skipArchived = (bool) $this->getArgument('--skip-archived');
$resume = (bool) $this->getArgument('--resume');
$config = Config::load();
// Override tokens if provided
$ghToken = (string) $this->getArgument('--github-token');
$giteaToken = (string) $this->getArgument('--gitea-token');
if ($ghToken !== '') { $config->set('github.token', $ghToken); }
if ($giteaToken !== '') { $config->set('gitea.token', $giteaToken); }
// Create both adapters
try {
$adapters = PlatformAdapterFactory::createBoth($config);
$this->github = $adapters['github'];
$this->gitea = $adapters['gitea'];
} catch (\RuntimeException $e) {
$this->log('ERROR', $e->getMessage());
return 1;
}
$this->checkpoints = new CheckpointManager('.checkpoints/migration');
$org = $config->getString('github.organization', 'mokoconsulting-tech');
$giteaOrg = $config->getString('gitea.organization', 'mokoconsulting-tech');
echo "=== Gitea Migration Tool ===\n";
echo "Source: GitHub ({$org})\n";
echo "Destination: Gitea ({$giteaOrg}) at " . $config->getString('gitea.url') . "\n";
echo "Mode: " . ($dryRun ? 'DRY RUN' : 'LIVE') . "\n\n";
// ── Phase 1: Discovery ──────────────────────────────────────────
$this->section('Phase 1: Discovery');
$ghRepos = $this->github->listOrgRepos($org, $skipArchived);
echo "Found " . count($ghRepos) . " repositories on GitHub\n";
// Filter repos
if (!empty($specificRepos)) {
$ghRepos = array_filter($ghRepos, fn($r) => in_array($r['name'], $specificRepos, true));
}
if (!empty($excludeRepos)) {
$ghRepos = array_filter($ghRepos, fn($r) => !in_array($r['name'], $excludeRepos, true));
}
// Check which already exist on Gitea
$giteaRepos = [];
try {
$existing = $this->gitea->listOrgRepos($giteaOrg);
foreach ($existing as $r) {
$giteaRepos[$r['name']] = true;
}
} catch (\Exception $e) {
echo "Note: Could not list Gitea repos (org may not exist yet): {$e->getMessage()}\n";
}
$toMigrate = [];
$toSkip = [];
foreach ($ghRepos as $repo) {
$name = $repo['name'];
if (isset($giteaRepos[$name])) {
$toSkip[] = $name;
} else {
$toMigrate[] = $repo;
}
}
echo "\nMigration plan:\n";
echo " Migrate: " . count($toMigrate) . " repositories\n";
echo " Skip: " . count($toSkip) . " (already on Gitea)\n";
if (!empty($toSkip)) {
echo " Skipped: " . implode(', ', $toSkip) . "\n";
}
echo "\n";
if (empty($toMigrate)) {
echo "Nothing to migrate.\n";
return 0;
}
if ($dryRun) {
echo "Repositories to migrate:\n";
foreach ($toMigrate as $repo) {
$vis = $repo['private'] ? 'private' : 'public';
echo " - {$repo['name']} ({$vis})\n";
}
echo "\nDry run complete. Use without --dry-run to execute.\n";
return 0;
}
// ── Phase 2: Migrate ────────────────────────────────────────────
$this->section('Phase 2: Migration');
$ghToken = $config->getString('github.token');
$results = ['migrated' => [], 'failed' => [], 'skipped' => $toSkip];
// Resume support
$checkpoint = $resume ? $this->checkpoints->loadCheckpoint('gitea_migration') : null;
$startFrom = $checkpoint['last_completed'] ?? '';
$skipUntil = !empty($startFrom);
foreach ($toMigrate as $index => $repo) {
$name = $repo['name'];
if ($skipUntil) {
if ($name === $startFrom) {
$skipUntil = false;
}
echo " Skipping {$name} (already migrated)\n";
continue;
}
echo "\n [{$index}/{" . count($toMigrate) . "}] Migrating {$name}...\n";
try {
// Shallow migration — copy current branch state only, no past
// commit history. This gives every repo a clean start on Gitea.
$this->gitea->migrateRepository([
'clone_addr' => "https://github.com/{$org}/{$name}.git",
'repo_name' => $name,
'repo_owner' => $giteaOrg,
'service' => 'github',
'auth_token' => $ghToken,
'mirror' => false,
'private' => $repo['private'],
'issues' => false,
'labels' => true,
'milestones' => false,
'releases' => false,
'pull_requests' => false,
'wiki' => false,
]);
echo " Migrated successfully\n";
$results['migrated'][] = $name;
// Save checkpoint after each successful migration
$this->checkpoints->saveCheckpoint('gitea_migration', [
'last_completed' => $name,
'migrated' => $results['migrated'],
'failed' => $results['failed'],
]);
} catch (\Exception $e) {
echo " FAILED: " . $e->getMessage() . "\n";
$results['failed'][] = ['name' => $name, 'error' => $e->getMessage()];
$this->gitea->getApiClient()->resetCircuitBreaker();
}
}
// ── Phase 3: Post-migration ─────────────────────────────────────
$this->section('Phase 3: Post-migration');
foreach ($results['migrated'] as $name) {
echo " Post-processing {$name}...\n";
try {
// Apply topics from GitHub
$ghTopics = $this->github->getRepoTopics($org, $name);
if (!empty($ghTopics)) {
$this->gitea->setRepoTopics($giteaOrg, $name, $ghTopics);
echo " Topics applied\n";
}
// Apply branch protection
$this->gitea->setBranchProtection($giteaOrg, $name, 'main', [
'required_reviews' => 1,
'dismiss_stale' => true,
'block_on_rejected' => true,
]);
echo " Branch protection applied\n";
} catch (\Exception $e) {
echo " Warning: post-processing issue: " . $e->getMessage() . "\n";
$this->gitea->getApiClient()->resetCircuitBreaker();
}
}
// ── Phase 4: Verification ───────────────────────────────────────
$this->section('Phase 4: Verification');
$report = "## Migration Report\n\n";
$report .= "**Date:** " . gmdate('Y-m-d H:i:s') . " UTC\n";
$report .= "**Source:** GitHub ({$org})\n";
$report .= "**Destination:** Gitea ({$giteaOrg})\n\n";
$report .= "### Results\n\n";
$report .= "| Status | Count |\n|--------|-------|\n";
$report .= "| Migrated | " . count($results['migrated']) . " |\n";
$report .= "| Failed | " . count($results['failed']) . " |\n";
$report .= "| Skipped (existing) | " . count($results['skipped']) . " |\n\n";
if (!empty($results['migrated'])) {
$report .= "### Migrated Repositories\n\n";
foreach ($results['migrated'] as $name) {
$report .= "- {$name}\n";
}
$report .= "\n";
}
if (!empty($results['failed'])) {
$report .= "### Failed Repositories\n\n";
foreach ($results['failed'] as $fail) {
$report .= "- **{$fail['name']}**: {$fail['error']}\n";
}
$report .= "\n";
}
echo $report;
// Create summary issue on Gitea
try {
$this->gitea->createIssue($giteaOrg, 'MokoStandards',
'chore: GitHub → Gitea migration report — ' . count($results['migrated']) . ' repos migrated',
$report,
['labels' => ['automation', 'type: chore']]
);
echo "Migration report issue created on Gitea.\n";
} catch (\Exception $e) {
echo "Could not create report issue: " . $e->getMessage() . "\n";
}
echo "\nMigration complete: " . count($results['migrated']) . " migrated, "
. count($results['failed']) . " failed, "
. count($results['skipped']) . " skipped\n";
return count($results['failed']) > 0 ? 1 : 0;
}
}
$script = new MigrateToGitea('migrate_to_gitea', 'Migrate repositories from GitHub to Gitea');
exit($script->execute());
+695
View File
@@ -0,0 +1,695 @@
#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Automation
* INGROUP: MokoStandards.Scripts
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/automation/push_files.php
* VERSION: 04.06.00
* BRIEF: Push one or more specific files to one or more remote repositories
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
use MokoEnterprise\{
ApiClient,
AuditLogger,
CLIApp,
Config,
DefinitionParser,
MetricsCollector,
ProjectTypeDetector
};
/**
* Targeted File Push Tool
*
* Pushes one or more specific files from MokoStandards templates to one or
* more remote repositories — without running a full sync.
*
* Files are specified by their destination path as they appear in the target
* repository (e.g., ".github/ISSUE_TEMPLATE/config.yml"). The tool looks up
* the matching source template from the appropriate platform definition.
*
* Files may also be given as "source:destination" pairs to bypass definition
* lookup and push any arbitrary local file.
*
* Usage:
* php push_files.php --files=.github/ISSUE_TEMPLATE/config.yml --repos=MokoCRM
* php push_files.php --files=".github/workflows/ci.yml,.github/workflows/codeql-analysis.yml" --repos=MokoCRM,WaasComponent
* php push_files.php --files=templates/foo.txt:docs/foo.txt --repos=MyRepo --direct
*/
class PushFiles extends CLIApp
{
public const DEFAULT_ORG = 'mokoconsulting-tech';
public const VERSION = '04.06.00';
private ApiClient $api;
private AuditLogger $logger;
private DefinitionParser $defParser;
private ProjectTypeDetector $typeDetector;
/**
* Setup command-line arguments
*/
protected function setupArguments(): array
{
return [
'org:' => 'GitHub organization (default: ' . self::DEFAULT_ORG . ')',
'repos:' => 'Target repositories — comma or space-separated (required)',
'files:' => 'Files to push — destination paths or source:destination pairs, comma/space-separated (required)',
'message:' => 'Custom commit message (optional)',
'branch:' => 'Target branch for direct pushes (default: repo default branch). Ignored unless --direct is set',
'direct' => 'Push directly to target branch instead of creating a PR',
'yes' => 'Auto-confirm without prompting',
'no-issue' => 'Skip creating a tracking issue in each target repository',
];
}
/**
* Main execution
*/
protected function run(): int
{
$this->log('📦 MokoStandards File Push v' . self::VERSION, 'INFO');
if (!$this->initializeComponents()) {
return 1;
}
$org = $this->getOption('org', self::DEFAULT_ORG);
$reposArg = $this->getOption('repos', '');
$filesArg = $this->getOption('files', '');
$direct = $this->hasOption('direct');
$autoYes = $this->hasOption('yes');
// Validate required arguments
if (empty($reposArg)) {
$this->log('❌ --repos is required. Specify one or more repository names.', 'ERROR');
$this->log(' Example: --repos=MokoCRM,WaasComponent', 'ERROR');
return 1;
}
if (empty($filesArg)) {
$this->log('❌ --files is required. Specify destination paths or source:destination pairs.', 'ERROR');
$this->log(' Example: --files=.github/ISSUE_TEMPLATE/config.yml', 'ERROR');
return 1;
}
$repos = $this->parseList($reposArg);
$files = $this->parseList($filesArg);
$this->log("Organisation: {$org}", 'INFO');
$this->log('Repositories: ' . implode(', ', $repos), 'INFO');
$this->log('Files: ' . implode(', ', $files), 'INFO');
$this->log('Mode: ' . ($direct ? 'direct commit' : 'pull request'), 'INFO');
// Resolve file mappings for each repo
$this->log("\n🔍 Resolving file mappings...", 'INFO');
$repoFileMaps = $this->buildRepoFileMaps($org, $repos, $files);
if (empty($repoFileMaps)) {
$this->log('❌ No files could be resolved. Check file paths and platform definitions.', 'ERROR');
return 1;
}
// Confirm before proceeding
if (!$autoYes && !$this->confirm($repoFileMaps, $direct)) {
$this->log('❌ Cancelled.', 'INFO');
return 0;
}
// Execute pushes
$results = $this->executePushes($org, $repoFileMaps, $direct);
$this->displayResults($results);
if ($results['failed'] > 0 && !isset($this->options['no-issue']) && !$this->dryRun) {
$this->createFailureIssue($org, $results);
}
return $results['failed'] > 0 ? 1 : 0;
}
/**
* Initialize enterprise components
*/
private function initializeComponents(): bool
{
$config = Config::load();
$token = $config->getString('github.token', '');
if (empty($token)) {
$this->log('❌ GitHub token not configured', 'ERROR');
$this->log('Set GH_TOKEN or GITHUB_TOKEN, or run: gh auth login', 'ERROR');
return false;
}
try {
$this->api = new ApiClient('https://api.github.com', $token);
$this->logger = new AuditLogger('push_files');
$this->defParser = new DefinitionParser();
$this->typeDetector = new ProjectTypeDetector($this->logger);
$this->log('✓ Components initialized', 'INFO');
return true;
} catch (\Exception $e) {
$this->log('❌ Failed to initialize: ' . $e->getMessage(), 'ERROR');
return false;
}
}
/**
* Parse a comma- or space-separated list into a clean array
*/
private function parseList(string $input): array
{
return array_values(array_filter(
array_map('trim', preg_split('/[\s,]+/', $input)),
fn($v) => $v !== ''
));
}
/**
* Build per-repo file maps: repo → [ [source, destination], … ]
*
* Each entry in $files is either:
* - "destination/path" → looked up in the platform definition
* - "source/path:destination/path" → used as-is (raw mode)
*
* @param string[] $repos
* @param string[] $files
* @return array<string, list<array{source: string, destination: string}>>
*/
private function buildRepoFileMaps(string $org, array $repos, array $files): array
{
$repoRoot = dirname(__DIR__, 2);
$maps = [];
foreach ($repos as $repo) {
// Detect the repo's platform so we load the right definition
$platform = $this->detectRepoPlatform($org, $repo);
$this->log(" {$repo}: platform = {$platform}", 'INFO');
// Build a destination→source lookup from the definition
$defEntries = $this->defParser->parseForPlatform($platform, $repoRoot);
$destToSource = [];
foreach ($defEntries as $entry) {
$destToSource[$entry['destination']] = $entry['source'];
}
$resolved = [];
foreach ($files as $fileSpec) {
if (str_contains($fileSpec, ':')) {
// Raw source:destination pair
[$src, $dest] = explode(':', $fileSpec, 2);
$srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
if (!file_exists($srcAbs)) {
$this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN');
continue;
}
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
$this->log("{$dest} (raw: {$src})", 'INFO');
} else {
// Destination path — look up in definition
$dest = ltrim($fileSpec, '/');
if (isset($destToSource[$dest])) {
$src = $destToSource[$dest];
$srcAbs = str_starts_with($src, '/')
? $src
: rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
if (!file_exists($srcAbs)) {
$this->log(" ⚠️ Template not found for {$repo}: {$src}", 'WARN');
continue;
}
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
$this->log("{$dest}", 'INFO');
} else {
$this->log(" ⚠️ {$dest} not found in {$platform} definition for {$repo}", 'WARN');
}
}
}
if (!empty($resolved)) {
$maps[$repo] = $resolved;
}
}
return $maps;
}
/**
* Detect platform for a repo by checking its sync def file, falling back
* to the live GitHub API detection used by bulk_sync.
*/
private function detectRepoPlatform(string $org, string $repo): string
{
// Check local sync def first — fastest path
$defDir = dirname(__DIR__) . '/definitions/sync';
$defFile = "{$defDir}/{$repo}.def.tf";
if (file_exists($defFile)) {
$content = file_get_contents($defFile) ?: '';
if (preg_match('/detected_platform\s*=\s*"([^"]+)"/', $content, $m)) {
return $m[1];
}
}
// Fall back to live detection
try {
$repoData = $this->api->get("/repos/{$org}/{$repo}");
return $this->typeDetector->detect($repoData, $org, $repo);
} catch (\Exception $e) {
$this->log(" ⚠️ Could not detect platform for {$repo}, using 'default'", 'WARN');
return 'default';
}
}
/**
* Prompt for confirmation before pushing
*
* @param array<string, list<array{source: string, destination: string}>> $repoFileMaps
*/
private function confirm(array $repoFileMaps, bool $direct): bool
{
if ($this->quiet) {
return true;
}
$totalFiles = array_sum(array_map('count', $repoFileMaps));
$totalRepos = count($repoFileMaps);
$mode = $direct ? 'direct commit' : 'PR';
echo "\n";
foreach ($repoFileMaps as $repo => $entries) {
echo " {$repo}:\n";
foreach ($entries as $entry) {
echo "{$entry['destination']}\n";
}
}
echo "\n";
echo "⚠️ About to push {$totalFiles} file(s) to {$totalRepos} repo(s) via {$mode}.\n";
echo "Continue? [y/N]: ";
$handle = fopen('php://stdin', 'r');
$line = fgets($handle);
if ($handle) {
fclose($handle);
}
return is_string($line) && strtolower(trim($line)) === 'y';
}
/**
* Execute all file pushes
*
* @param array<string, list<array{source: string, destination: string}>> $repoFileMaps
* @return array{total: int, success: int, failed: int, repos: array<string, string>}
*/
private function executePushes(string $org, array $repoFileMaps, bool $direct): array
{
$results = [
'total' => count($repoFileMaps),
'success' => 0,
'failed' => 0,
'repos' => [],
];
$customMessage = $this->getOption('message', '');
$targetBranch = $this->getOption('branch', '');
foreach ($repoFileMaps as $repo => $entries) {
$this->log("\n[{$repo}] Pushing " . count($entries) . ' file(s)...', 'INFO');
try {
// Resolve the default branch
$repoData = $this->api->get("/repos/{$org}/{$repo}");
$defaultBranch = $repoData['default_branch'] ?? 'main';
$branch = $direct
? ($targetBranch ?: $defaultBranch)
: $this->createSyncBranch($org, $repo, $defaultBranch);
$pushed = 0;
foreach ($entries as $entry) {
if ($this->pushSingleFile($org, $repo, $entry['source'], $entry['destination'], $branch, $customMessage)) {
$pushed++;
$this->log("{$entry['destination']}", 'INFO');
} else {
$this->log("{$entry['destination']}", 'ERROR');
}
}
if ($pushed === 0) {
$results['failed']++;
$results['repos'][$repo] = 'failed';
continue;
}
$prNumber = null;
if (!$direct) {
$prTitle = "chore: push " . count($entries) . " file(s) from MokoStandards";
$prBody = $this->buildPRBody($entries);
$pr = $this->api->post("/repos/{$org}/{$repo}/pulls", [
'title' => $prTitle,
'head' => $branch,
'base' => $defaultBranch,
'body' => $prBody,
'assignees' => ['jmiller-moko'],
]);
$prNumber = $pr['number'] ?? null;
$this->log(" 📋 PR #{$prNumber} created", 'INFO');
$results['repos'][$repo] = "pr#{$prNumber}";
} else {
$results['repos'][$repo] = 'pushed';
}
if (!isset($this->options['no-issue']) && !$this->dryRun) {
$this->createTargetRepoIssue($org, $repo, $entries, $prNumber, $direct ? $branch : null);
}
$results['success']++;
} catch (\Exception $e) {
$this->log("{$repo}: " . $e->getMessage(), 'ERROR');
$results['failed']++;
$results['repos'][$repo] = 'failed';
}
}
return $results;
}
/**
* Create a uniquely-named sync branch off the default branch
*/
private function createSyncBranch(string $org, string $repo, string $base): string
{
$branchName = 'moko/push-files-' . date('Ymd-His');
// Get SHA of the base branch tip
$ref = $this->api->get("/repos/{$org}/{$repo}/git/refs/heads/{$base}");
$sha = $ref['object']['sha'] ?? null;
if (empty($sha)) {
throw new \RuntimeException("Cannot resolve SHA for branch {$base} in {$repo}");
}
$this->api->post("/repos/{$org}/{$repo}/git/refs", [
'ref' => "refs/heads/{$branchName}",
'sha' => $sha,
]);
$this->log(" 🌿 Branch created: {$branchName}", 'INFO');
return $branchName;
}
/**
* Push a single file to a repository branch via the Contents API
*
* @return bool True on success
*/
private function pushSingleFile(
string $org,
string $repo,
string $sourcePath,
string $destPath,
string $branch,
string $customMessage
): bool {
$content = file_get_contents($sourcePath);
if ($content === false) {
$this->log(" ⚠️ Cannot read source: {$sourcePath}", 'WARN');
return false;
}
$message = !empty($customMessage)
? $customMessage
: "chore: update {$destPath} from MokoStandards";
$payload = [
'message' => $message,
'content' => base64_encode($content),
'branch' => $branch,
];
// Fetch existing file SHA (needed for updates)
try {
$existing = $this->api->get("/repos/{$org}/{$repo}/contents/{$destPath}", ['ref' => $branch]);
$payload['sha'] = $existing['sha'];
} catch (\Exception $e) {
// File does not exist — create it (no sha needed)
}
try {
$this->api->put("/repos/{$org}/{$repo}/contents/{$destPath}", $payload);
return true;
} catch (\Exception $e) {
$this->log(" ✗ API error pushing {$destPath}: " . $e->getMessage(), 'ERROR');
return false;
}
}
/**
* Create a tracking issue in the target repository after a successful push.
*
* @param list<array{source: string, destination: string}> $entries
*/
private function createTargetRepoIssue(
string $org,
string $repo,
array $entries,
?int $prNumber,
?string $directBranch
): void {
$now = gmdate('Y-m-d H:i:s') . ' UTC';
$version = self::VERSION;
$source = "https://github.com/{$org}/MokoStandards";
$title = "chore: MokoStandards file push tracking";
$deliveryLine = $prNumber !== null
? "| **Pull request** | [#{$prNumber}](https://github.com/{$org}/{$repo}/pull/{$prNumber}) |"
: "| **Delivery** | Direct commit to `{$directBranch}` |";
$fileRows = implode("\n", array_map(
fn($e) => "- `{$e['destination']}`",
$entries
));
$body = <<<MD
## MokoStandards File Push
One or more files were pushed to this repository from MokoStandards.
| Field | Value |
|-------|-------|
| **Pushed** | {$now} |
| **Standards version** | `{$version}` |
{$deliveryLine}
| **Source** | [{$source}]({$source}) |
### Files pushed
{$fileRows}
---
*Generated automatically by [MokoStandards]({$source}) `push_files.php`*
MD;
$body = preg_replace('/^ /m', '', $body);
$labels = ['standards-update', 'mokostandards', 'type: chore', 'automation'];
try {
$existing = $this->api->get("/repos/{$org}/{$repo}/issues", [
'labels' => 'standards-update',
'state' => 'all',
'per_page' => 1,
'sort' => 'created',
'direction' => 'desc',
]);
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
$patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
$this->api->patch("/repos/{$org}/{$repo}/issues/{$num}", $patch);
try {
$this->api->post("/repos/{$org}/{$repo}/issues/{$num}/labels", ['labels' => $labels]);
} catch (\Exception $le) { /* non-fatal */ }
$this->log(" 📋 Tracking issue #{$num} updated in {$repo}", 'INFO');
} else {
$issue = $this->api->post("/repos/{$org}/{$repo}/issues", [
'title' => $title,
'body' => $body,
'labels' => $labels,
'assignees' => ['jmiller-moko'],
]);
$num = $issue['number'] ?? null;
$this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO');
}
// Cross-link: patch the sync PR body to reference the tracking issue
// so GitHub shows it in the PR's Development sidebar.
if ($prNumber !== null && is_int($num)) {
try {
$pr = $this->api->get("/repos/{$org}/{$repo}/pulls/{$prNumber}");
$currentBody = $pr['body'] ?? '';
$ref = "Linked to #{$num}";
if (!str_contains($currentBody, $ref)) {
$this->api->patch("/repos/{$org}/{$repo}/pulls/{$prNumber}", [
'body' => $ref . "\n\n" . $currentBody,
]);
}
} catch (\Exception $le) { /* non-fatal */ }
}
} catch (\Exception $e) {
$this->log(" ⚠️ Could not create/update tracking issue in {$repo}: " . $e->getMessage(), 'WARN');
}
}
/**
* Create or update a failure issue in MokoStandards when repos fail to receive files.
* Uses the 'push-failure' label. Reopens a closed issue rather than creating a duplicate.
*/
private function createFailureIssue(string $org, array $results): void
{
$now = gmdate('Y-m-d H:i:s') . ' UTC';
$failed = $results['failed'];
$version = self::VERSION;
$failedRepos = array_keys(array_filter(
$results['repos'] ?? [],
fn($s) => $s === 'failed'
));
$repoList = implode("\n", array_map(fn($r) => "- `{$r}`", $failedRepos));
$fileArgs = $this->getOption('files', '');
$title = "fix: push_files failed for {$failed} repo(s) — action required";
$body = <<<MD
## File Push Failure
`push_files.php` v{$version} encountered failures pushing files on {$now}.
### Failed repositories
{$repoList}
### Files that were being pushed
```
{$fileArgs}
```
### Next steps
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=<repo> --files=<files> --yes`
4. Close this issue once resolved.
---
*Auto-created by `push_files.php` — close once resolved.*
MD;
$body = preg_replace('/^ /m', '', $body);
try {
$existing = $this->api->get("/repos/{$org}/MokoStandards/issues", [
'labels' => 'push-failure',
'state' => 'all',
'per_page' => 1,
'sort' => 'created',
'direction' => 'desc',
]);
if (!empty($existing) && isset($existing[0]['number'])) {
$num = $existing[0]['number'];
$patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']];
if (($existing[0]['state'] ?? 'open') === 'closed') {
$patch['state'] = 'open';
}
$this->api->patch("/repos/{$org}/MokoStandards/issues/{$num}", $patch);
$this->log("🚨 Failure issue #{$num} updated: {$org}/MokoStandards#{$num}", 'WARN');
} else {
$issue = $this->api->post("/repos/{$org}/MokoStandards/issues", [
'title' => $title,
'body' => $body,
'labels' => ['push-failure'],
'assignees' => ['jmiller-moko'],
]);
$num = $issue['number'] ?? '?';
$this->log("🚨 Failure issue created: {$org}/MokoStandards#{$num}", 'WARN');
}
} catch (\Exception $e) {
$this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN');
}
}
/**
* Build a markdown PR body listing every pushed file
*
* @param list<array{source: string, destination: string}> $entries
*/
private function buildPRBody(array $entries): string
{
$now = gmdate('Y-m-d H:i:s') . ' UTC';
$lines = ["## MokoStandards File Push\n", "**Pushed:** {$now}\n", '### Files\n'];
foreach ($entries as $entry) {
$lines[] = "- `{$entry['destination']}`";
}
$lines[] = "\n---\n*Generated by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) `push_files.php`*";
return implode("\n", $lines);
}
/**
* Display final results
*
* @param array{total: int, success: int, failed: int, repos: array<string, string>} $results
*/
private function displayResults(array $results): void
{
$this->log("\n" . str_repeat('=', 60), 'INFO');
$this->log('📊 Push Complete', 'INFO');
$this->log(str_repeat('=', 60), 'INFO');
$this->log(sprintf('Total: %d repos', $results['total']), 'INFO');
$this->log(sprintf('Success: %d', $results['success']), 'INFO');
$this->log(sprintf('Failed: %d', $results['failed']), 'INFO');
if ($this->verbose) {
$this->log("\n📋 Details:", 'INFO');
foreach ($results['repos'] as $repo => $outcome) {
$icon = str_starts_with($outcome, 'pr#') || $outcome === 'pushed' ? '✓' : '✗';
$this->log(" {$icon} {$repo}: {$outcome}", 'INFO');
}
}
$this->log(str_repeat('=', 60), 'INFO');
}
}
// Execute if run directly
if (php_sapi_name() === 'cli' && isset($argv[0]) && realpath($argv[0]) === __FILE__) {
$app = new PushFiles(
'push-files',
'Push one or more specific files to one or more remote repositories',
PushFiles::VERSION
);
exit($app->execute());
}
+517
View File
@@ -0,0 +1,517 @@
#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Automation
* INGROUP: MokoStandards.Scripts
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/automation/repo_cleanup.php
* VERSION: 04.06.00
* BRIEF: Enterprise repository cleanup — branches, PRs, issues, workflows, labels, logs
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
use MokoEnterprise\{ApiClient, AuditLogger, CLIApp, MetricsCollector};
/**
* Enterprise Repository Cleanup
*
* Comprehensive maintenance tool for governed repositories:
* 1. Delete stale sync branches (keeps current versioned branch)
* 2. Close superseded PRs on deleted branches
* 3. Close/lock resolved tracking issues where linked PR is merged
* 4. Delete retired workflow files from repos
* 5. Clean cancelled/stale workflow runs
* 6. Delete workflow run logs older than N days
* 7. Verify and provision standard labels
* 8. Version drift detection
*/
class RepoCleanup extends CLIApp
{
private const VERSION = '04.06.00';
private const SYNC_PREFIX = 'chore/sync-mokostandards-';
private const CURRENT_BRANCH = 'chore/sync-mokostandards-v04.02.00';
/** Workflow files that have been retired and should be deleted from governed repos. */
private const RETIRED_WORKFLOWS = [
'build.yml', 'code-quality.yml', 'release-cycle.yml', 'release-pipeline.yml',
'branch-cleanup.yml', 'auto-update-changelog.yml', 'enterprise-issue-manager.yml',
'flush-actions-cache.yml', 'mokostandards-script-runner.yml', 'unified-ci.yml',
'unified-platform-testing.yml', 'reusable-build.yml', 'reusable-ci-validation.yml',
'reusable-deploy.yml', 'reusable-php-quality.yml', 'reusable-platform-testing.yml',
'reusable-project-detector.yml', 'reusable-release.yml', 'reusable-script-executor.yml',
'rebuild-docs-indexes.yml', 'setup-project-v2.yml', 'sync-docs-to-project.yml',
'release.yml', 'sync-changelogs.yml', 'version_branch.yml',
'publish-to-mokodolibarr.yml', 'ci.yml',
'deploy-rs.yml',
];
private ApiClient $api;
private AuditLogger $logger;
private MetricsCollector $metrics;
private bool $dryRun = false;
private float $startTime;
protected function configure(): void
{
$this->setName('repo-cleanup');
$this->setDescription('Enterprise repository cleanup — branches, PRs, issues, workflows, labels, logs');
$this->setVersion(self::VERSION);
$this->addOption('org', 'GitHub organization', 'mokoconsulting-tech');
$this->addOption('repos', 'Specific repositories (space-separated)', '');
$this->addOption('skip-archived', 'Skip archived repositories', false);
$this->addOption('close-issues', 'Close resolved tracking issues (merged PR = done)', false);
$this->addOption('lock-old-issues', 'Lock issues closed >30 days', false);
$this->addOption('clean-workflows', 'Delete cancelled/stale workflow runs', false);
$this->addOption('clean-logs', 'Delete workflow run logs older than --log-days', false);
$this->addOption('log-days', 'Days to keep logs (default: 30)', '30');
$this->addOption('delete-retired', 'Delete retired workflow files from repos', false);
$this->addOption('check-labels', 'Verify mokostandards label exists', false);
$this->addOption('check-drift', 'Check for version drift against README.md', false);
$this->addOption('all', 'Run all cleanup operations', false);
$this->addOption('yes', 'Auto-confirm prompts', false);
$this->addOption('dry-run', 'Preview changes without making them', false);
$this->addOption('verbose', 'Show detailed output', false);
$this->addOption('quiet', 'Suppress non-error output', false);
$this->addOption('json', 'Output results as JSON', false);
}
protected function execute(): int
{
$this->startTime = microtime(true);
$org = $this->getOption('org', 'mokoconsulting-tech');
$this->dryRun = (bool) $this->getOption('dry-run', false);
$runAll = (bool) $this->getOption('all', false);
$token = getenv('GH_TOKEN') ?: getenv('GITHUB_TOKEN') ?: '';
if (empty($token)) {
$this->error('GH_TOKEN or GITHUB_TOKEN environment variable required');
return 1;
}
$this->api = new ApiClient(
'https://api.github.com',
$token,
circuitBreakerThreshold: 50,
circuitBreakerTimeout: 10,
);
$this->logger = new AuditLogger('repo_cleanup');
$this->metrics = new MetricsCollector('repo_cleanup');
$this->log("🧹 MokoStandards Repository Cleanup v" . self::VERSION);
$this->log("Organization: {$org}");
$this->log("Current sync branch: " . self::CURRENT_BRANCH);
if ($this->dryRun) {
$this->log("⚠️ DRY RUN — no changes will be made");
}
$this->log('');
$repos = $this->fetchRepositories($org);
$this->log("Found " . count($repos) . " repositories");
$this->log('');
$results = [
'repos_processed' => 0,
'repos_cleaned' => 0,
'branches_deleted' => 0,
'prs_closed' => 0,
'issues_closed' => 0,
'issues_locked' => 0,
'workflows_deleted' => 0,
'runs_deleted' => 0,
'logs_deleted' => 0,
'labels_missing' => 0,
'version_drift' => 0,
'retired_files' => 0,
'errors' => 0,
];
foreach ($repos as $i => $repo) {
$name = $repo['name'];
$num = $i + 1;
$total = count($repos);
$this->log("[{$num}/{$total}] {$name}");
$results['repos_processed']++;
try {
$this->api->resetCircuitBreaker();
$cleaned = false;
// Always: delete old sync branches + close their PRs
$cleaned = $this->cleanBranches($org, $name, $results) || $cleaned;
// Optional: close resolved issues
if ($runAll || $this->getOption('close-issues', false)) {
$cleaned = $this->closeResolvedIssues($org, $name, $results) || $cleaned;
}
// Optional: lock old closed issues
if ($runAll || $this->getOption('lock-old-issues', false)) {
$cleaned = $this->lockOldIssues($org, $name, $results) || $cleaned;
}
// Optional: delete retired workflow files
if ($runAll || $this->getOption('delete-retired', false)) {
$cleaned = $this->deleteRetiredWorkflows($org, $name, $results) || $cleaned;
}
// Optional: clean workflow runs
if ($runAll || $this->getOption('clean-workflows', false)) {
$cleaned = $this->cleanWorkflowRuns($org, $name, $results) || $cleaned;
}
// Optional: clean old logs
if ($runAll || $this->getOption('clean-logs', false)) {
$cleaned = $this->cleanOldLogs($org, $name, $results) || $cleaned;
}
// Optional: check labels
if ($runAll || $this->getOption('check-labels', false)) {
$this->checkLabels($org, $name, $results);
}
// Optional: check version drift
if ($runAll || $this->getOption('check-drift', false)) {
$this->checkVersionDrift($org, $name, $results);
}
if ($cleaned) {
$results['repos_cleaned']++;
}
} catch (\Exception $e) {
$this->error("{$name}: " . $e->getMessage());
$results['errors']++;
}
}
$duration = round(microtime(true) - $this->startTime, 1);
$this->log('');
$this->log('============================================================');
$this->log("🧹 Cleanup Complete ({$duration}s)");
$this->log('============================================================');
$this->log("Repos processed: {$results['repos_processed']}");
$this->log("Repos with changes: {$results['repos_cleaned']}");
$this->log("Branches deleted: {$results['branches_deleted']}");
$this->log("PRs closed: {$results['prs_closed']}");
$this->log("Issues closed: {$results['issues_closed']}");
$this->log("Issues locked: {$results['issues_locked']}");
$this->log("Retired files: {$results['retired_files']}");
$this->log("Workflow runs: {$results['runs_deleted']}");
$this->log("Logs cleaned: {$results['logs_deleted']}");
$this->log("Labels missing: {$results['labels_missing']}");
$this->log("Version drift: {$results['version_drift']}");
$this->log("Errors: {$results['errors']}");
$this->log('============================================================');
if ($this->getOption('json', false)) {
$results['duration_seconds'] = $duration;
echo json_encode($results, JSON_PRETTY_PRINT) . "\n";
}
return $results['errors'] > 0 ? 1 : 0;
}
// ─── Repository fetching ─────────────────────────────────────────────
private function fetchRepositories(string $org): array
{
$specificRepos = trim((string) $this->getOption('repos', ''));
$skipArchived = (bool) $this->getOption('skip-archived', false);
if (!empty($specificRepos)) {
$names = preg_split('/[\s,]+/', $specificRepos);
return array_map(fn($n) => ['name' => trim($n), 'archived' => false], $names);
}
$page = 1;
$repos = [];
do {
$batch = $this->api->get("/orgs/{$org}/repos", [
'per_page' => 100,
'page' => $page,
'type' => 'all',
]);
foreach ($batch as $r) {
if ($skipArchived && !empty($r['archived'])) continue;
if (in_array($r['name'], ['MokoStandards', '.github-private'], true)) continue;
$repos[] = $r;
}
$page++;
} while (count($batch) === 100);
return $repos;
}
// ─── Cleanup operations ──────────────────────────────────────────────
private function cleanBranches(string $org, string $repo, array &$results): bool
{
$changed = false;
try {
$branches = $this->api->get("/repos/{$org}/{$repo}/branches", ['per_page' => 100]);
} catch (\Exception $e) {
return false;
}
foreach ($branches as $branch) {
$name = $branch['name'] ?? '';
if (!str_starts_with($name, self::SYNC_PREFIX) || $name === self::CURRENT_BRANCH) {
continue;
}
// Close open PRs on this branch
try {
$prs = $this->api->get("/repos/{$org}/{$repo}/pulls", [
'state' => 'open', 'head' => "{$org}:{$name}", 'per_page' => 10,
]);
foreach ($prs as $pr) {
if (($pr['number'] ?? 0) > 0 && !$this->dryRun) {
$this->api->patch("/repos/{$org}/{$repo}/pulls/{$pr['number']}", ['state' => 'closed']);
}
$this->log(" 🔒 Closed PR #{$pr['number']} ({$name})");
$results['prs_closed']++;
$changed = true;
}
} catch (\Exception $e) { /* non-fatal */ }
if (!$this->dryRun) {
try {
$this->api->delete("/repos/{$org}/{$repo}/git/refs/heads/{$name}");
} catch (\Exception $e) { continue; }
}
$this->log(" 🗑️ Deleted branch: {$name}");
$results['branches_deleted']++;
$changed = true;
}
return $changed;
}
private function closeResolvedIssues(string $org, string $repo, array &$results): bool
{
$changed = false;
foreach (['standards-update', 'standards-drift'] as $label) {
try {
$issues = $this->api->get("/repos/{$org}/{$repo}/issues", [
'labels' => $label, 'state' => 'open', 'per_page' => 10,
]);
} catch (\Exception $e) { continue; }
foreach ($issues as $issue) {
$num = $issue['number'] ?? 0;
$body = $issue['body'] ?? '';
if (preg_match('/\[#(\d+)\]/', $body, $m)) {
$prNum = (int) $m[1];
try {
$pr = $this->api->get("/repos/{$org}/{$repo}/pulls/{$prNum}");
if (!empty($pr['merged_at'])) {
if (!$this->dryRun) {
$this->api->patch("/repos/{$org}/{$repo}/issues/{$num}", [
'state' => 'closed', 'state_reason' => 'completed',
]);
}
$this->log(" ✅ Closed issue #{$num} (PR #{$prNum} merged)");
$results['issues_closed']++;
$changed = true;
}
} catch (\Exception $e) { /* non-fatal */ }
}
}
}
return $changed;
}
private function lockOldIssues(string $org, string $repo, array &$results): bool
{
$changed = false;
$cutoff = date('Y-m-d\TH:i:s\Z', strtotime('-30 days'));
try {
$issues = $this->api->get("/repos/{$org}/{$repo}/issues", [
'state' => 'closed', 'per_page' => 50, 'sort' => 'updated', 'direction' => 'asc',
]);
} catch (\Exception $e) { return false; }
foreach ($issues as $issue) {
$closedAt = $issue['closed_at'] ?? '';
$locked = $issue['locked'] ?? false;
$num = $issue['number'] ?? 0;
if ($locked || $closedAt > $cutoff || $num === 0) continue;
if (!$this->dryRun) {
try {
$this->api->put("/repos/{$org}/{$repo}/issues/{$num}/lock", [
'lock_reason' => 'resolved',
]);
} catch (\Exception $e) { continue; }
}
$results['issues_locked']++;
$changed = true;
}
if ($results['issues_locked'] > 0) {
$this->log(" 🔒 Locked {$results['issues_locked']} old closed issue(s)");
}
return $changed;
}
private function deleteRetiredWorkflows(string $org, string $repo, array &$results): bool
{
$changed = false;
$defaultBranch = 'main';
try {
$repoInfo = $this->api->get("/repos/{$org}/{$repo}");
$defaultBranch = $repoInfo['default_branch'] ?? 'main';
} catch (\Exception $e) { /* fallback to main */ }
// Check both workflow directories for retired workflows
$wfDirs = ['.github/workflows', '.gitea/workflows'];
foreach (self::RETIRED_WORKFLOWS as $wf) {
foreach ($wfDirs as $wfDir) {
$path = "{$wfDir}/{$wf}";
try {
$file = $this->api->get("/repos/{$org}/{$repo}/contents/{$path}");
$sha = $file['sha'] ?? '';
if (empty($sha)) continue;
if (!$this->dryRun) {
$this->api->delete("/repos/{$org}/{$repo}/contents/{$path}", [
'message' => "chore: delete retired workflow {$wf}",
'sha' => $sha,
'branch' => $defaultBranch,
]);
}
$this->log(" Deleted retired: {$wf} (from {$wfDir})");
$results['retired_files']++;
$changed = true;
} catch (\Exception $e) {
// File doesn't exist in this dir — skip
$this->api->resetCircuitBreaker();
}
}
}
return $changed;
}
private function cleanWorkflowRuns(string $org, string $repo, array &$results): bool
{
$changed = false;
foreach (['cancelled', 'stale'] as $status) {
try {
$runs = $this->api->get("/repos/{$org}/{$repo}/actions/runs", [
'status' => $status, 'per_page' => 100,
]);
foreach (($runs['workflow_runs'] ?? []) as $run) {
$id = $run['id'] ?? 0;
if ($id > 0 && !$this->dryRun) {
try {
$this->api->delete("/repos/{$org}/{$repo}/actions/runs/{$id}");
$results['runs_deleted']++;
$changed = true;
} catch (\Exception $e) { $this->api->resetCircuitBreaker(); }
}
}
} catch (\Exception $e) { /* non-fatal */ }
}
if ($results['runs_deleted'] > 0) {
$this->log(" 🔄 Cleaned {$results['runs_deleted']} workflow run(s)");
}
return $changed;
}
private function cleanOldLogs(string $org, string $repo, array &$results): bool
{
$changed = false;
$days = (int) $this->getOption('log-days', '30');
$cutoff = date('Y-m-d\TH:i:s\Z', strtotime("-{$days} days"));
try {
$runs = $this->api->get("/repos/{$org}/{$repo}/actions/runs", [
'created' => "<{$cutoff}", 'per_page' => 100,
]);
foreach (($runs['workflow_runs'] ?? []) as $run) {
$id = $run['id'] ?? 0;
if ($id > 0 && !$this->dryRun) {
try {
$this->api->delete("/repos/{$org}/{$repo}/actions/runs/{$id}/logs");
$results['logs_deleted']++;
$changed = true;
} catch (\Exception $e) { $this->api->resetCircuitBreaker(); }
}
}
} catch (\Exception $e) { /* non-fatal */ }
if ($results['logs_deleted'] > 0) {
$this->log(" 📋 Cleaned {$results['logs_deleted']} old log(s)");
}
return $changed;
}
private function checkLabels(string $org, string $repo, array &$results): void
{
try {
$this->api->get("/repos/{$org}/{$repo}/labels/mokostandards");
} catch (\Exception $e) {
$this->log(" ⚠️ Missing 'mokostandards' label");
$results['labels_missing']++;
$this->api->resetCircuitBreaker();
}
}
private function checkVersionDrift(string $org, string $repo, array &$results): void
{
try {
$file = $this->api->get("/repos/{$org}/{$repo}/contents/README.md");
$content = base64_decode($file['content'] ?? '');
if (preg_match('/^\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) {
$version = $m[1];
// Check .mokostandards for the tracked MokoStandards version
try {
$mokoFile = $this->api->get("/repos/{$org}/{$repo}/contents/.mokostandards");
$mokoContent = base64_decode($mokoFile['content'] ?? '');
if (preg_match('/standards_version:\s*(\d{2}\.\d{2}\.\d{2})/m', $mokoContent, $vm)) {
if ($vm[1] !== self::VERSION) {
$this->log(" ⚠️ Standards drift: {$vm[1]} (expected " . self::VERSION . ")");
$results['version_drift']++;
}
}
} catch (\Exception $e) {
$this->api->resetCircuitBreaker();
}
}
} catch (\Exception $e) {
$this->api->resetCircuitBreaker();
}
}
// ─── Helpers ─────────────────────────────────────────────────────────
private function log(string $message): void
{
if (!$this->getOption('quiet', false)) {
echo $message . "\n";
}
}
private function error(string $message): void
{
fwrite(STDERR, $message . "\n");
}
}
$app = new RepoCleanup();
exit($app->execute());
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.CLI
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /bin/moko
* VERSION: 04.00.15
* BRIEF: Unified CLI dispatcher — run any MokoStandards script without needing GitHub Actions
*
* USAGE
* php bin/moko <command> [options] (all platforms)
* ./bin/moko <command> [options] (Unix, after: chmod +x bin/moko)
*
* COMMANDS
* sync Bulk-sync MokoStandards to organisation repos
* health Full repository health check (runs most validators)
* inventory Refresh docs/reference/REPOSITORY_INVENTORY.md
*
* check:syntax PHP syntax check (php -l) on all tracked .php files
* check:version Verify VERSION fields and badges match composer.json
* check:changelog Validate CHANGELOG.md format
* check:structure Verify required root files and directories
* check:headers Check SPDX-License-Identifier presence in source files
* check:secrets Scan for leaked credentials / API keys
* check:tabs Detect tab characters in YAML files
* check:paths Detect backslash path separators in PHP source
* check:xml Validate XML files are well-formed
* check:enterprise Full enterprise-readiness check (headers, strict types, PSR-12)
* check:dolibarr Validate Dolibarr module directory structure
* check:joomla Validate Joomla XML manifest
* check:language Validate Joomla/Dolibarr .ini language files
* detect Auto-detect repository platform type
* drift Scan org repos for drift from MokoStandards templates
*
* COMMON OPTIONS (passed through to each script)
* --path <dir> Repository root to check (default: .)
* --dry-run Preview changes without applying them
* --verbose Show passing checks as well as failures
* --quiet Show only failures
* --json Machine-readable JSON output
* --help Show help for the selected command
*
* AUTHENTICATION
* Token resolution order (first non-empty wins):
* 1. GH_TOKEN environment variable
* 2. GITHUB_TOKEN environment variable
* 3. `gh auth token` (GitHub CLI — run `gh auth login` once)
* 4. .env file in repo root (GH_TOKEN=... line)
*
* EXAMPLES
* php bin/moko health
* php bin/moko sync -- --repos MokoDoliTraining --dry-run
* php bin/moko check:version --path .
* php bin/moko drift -- --org mokoconsulting-tech --json
*/
declare(strict_types=1);
// ── Bootstrap ────────────────────────────────────────────────────────────────
$repoRoot = dirname(__DIR__);
$autoloader = $repoRoot . '/vendor/autoload.php';
if (!is_file($autoloader)) {
fwrite(STDERR, "Error: vendor/autoload.php not found.\nRun: composer install\n");
exit(2);
}
require_once $autoloader;
// ── Command map ──────────────────────────────────────────────────────────────
/**
* Map of moko command names → relative path to the PHP script.
* All paths are relative to the repo root.
*/
const COMMAND_MAP = [
// Automation
'sync' => 'api/automation/bulk_sync.php',
// Maintenance
'inventory' => 'api/maintenance/update_repo_inventory.php',
// Validation — general
'health' => 'api/validate/check_repo_health.php',
'check:syntax' => 'api/validate/check_php_syntax.php',
'check:version' => 'api/validate/check_version_consistency.php',
'check:changelog' => 'api/validate/check_changelog.php',
'check:structure' => 'api/validate/check_structure.php',
'check:headers' => 'api/validate/check_license_headers.php',
'check:secrets' => 'api/validate/check_no_secrets.php',
'check:tabs' => 'api/validate/check_tabs.php',
'check:paths' => 'api/validate/check_paths.php',
'check:xml' => 'api/validate/check_xml_wellformed.php',
'check:enterprise' => 'api/validate/check_enterprise_readiness.php',
// Validation — platform-specific
'check:dolibarr' => 'api/validate/check_dolibarr_module.php',
'check:joomla' => 'api/validate/check_joomla_manifest.php',
'check:language' => 'api/validate/check_language_structure.php',
// Detection
'detect' => 'api/validate/auto_detect_platform.php',
// Org-wide
'drift' => 'api/validate/scan_drift.php',
// Release
'release' => 'api/cli/release.php',
// CLI utilities (used by workflows — centralized logic)
'version:read' => 'api/cli/version_read.php',
'version:bump' => 'api/cli/version_bump.php',
'version:propagate' => 'api/maintenance/update_version_from_readme.php',
'version:set-platform' => 'api/cli/version_set_platform.php',
'platform:detect' => 'api/cli/platform_detect.php',
'release:notes' => 'api/cli/release_notes.php',
'validate:module' => 'bin/validate-module',
];
// ── Argument parsing ─────────────────────────────────────────────────────────
$args = array_slice($argv, 1);
$command = array_shift($args) ?? '';
// Strip leading -- separator that Composer passes when using `composer run-script cmd -- extra-args`
if (isset($args[0]) && $args[0] === '--') {
array_shift($args);
}
// ── Help / list ───────────────────────────────────────────────────────────────
if ($command === '' || $command === '--help' || $command === '-h' || $command === 'help') {
printHelp();
exit(0);
}
if ($command === 'list' || $command === 'commands') {
printCommandList();
exit(0);
}
// ── Dispatch ──────────────────────────────────────────────────────────────────
if (!array_key_exists($command, COMMAND_MAP)) {
fwrite(STDERR, "Error: Unknown command '{$command}'\n\n");
printCommandList();
exit(2);
}
$scriptPath = $repoRoot . '/' . COMMAND_MAP[$command];
if (!is_file($scriptPath)) {
fwrite(STDERR, "Error: Script not found: " . COMMAND_MAP[$command] . "\n");
fwrite(STDERR, "Ensure the repository is complete and run: composer install\n");
exit(2);
}
// Rebuild $argv as if the target script were invoked directly, then include it.
// This is equivalent to: php <script> [args…] but keeps us in the same process.
$argv = array_merge([$scriptPath], $args);
$argc = count($argv);
// Suppress the "run directly" guard that some scripts use (they check realpath($argv[0]) === __FILE__).
// By setting $argv[0] to the script's own path the guard passes naturally.
require $scriptPath;
// ── Helpers ───────────────────────────────────────────────────────────────────
function printHelp(): void
{
echo <<<'HELP'
╔══════════════════════════════════════════════════════════╗
║ MokoStandards CLI (bin/moko) ║
╚══════════════════════════════════════════════════════════╝
Run any MokoStandards script locally without GitHub Actions.
USAGE
php bin/moko <command> [options] (all platforms)
./bin/moko <command> [options] (Unix, after: chmod +x bin/moko)
Run `php bin/moko list` to see all available commands.
Run `php bin/moko <command> --help` for command-specific help.
QUICK START
1. composer install
2. cp .env.example .env # add your GH_TOKEN
3. php bin/moko health # run full health check
AUTHENTICATION
GH_TOKEN env var → GITHUB_TOKEN env var → gh auth login
HELP;
}
function printCommandList(): void
{
echo "Available commands:\n\n";
$groups = [
'Automation' => ['sync'],
'Maintenance' => ['inventory'],
'Validation (general)' => ['health', 'check:syntax', 'check:version', 'check:changelog',
'check:structure', 'check:headers', 'check:secrets',
'check:tabs', 'check:paths', 'check:xml', 'check:enterprise'],
'Validation (platform)' => ['check:dolibarr', 'check:joomla', 'check:language', 'detect'],
'Organisation-wide' => ['drift'],
];
foreach ($groups as $group => $commands) {
echo " {$group}:\n";
foreach ($commands as $cmd) {
printf(" %-22s %s\n", $cmd, COMMAND_MAP[$cmd]);
}
echo "\n";
}
echo "Run: php bin/moko <command> --help for command-specific options.\n";
echo "All platforms: php bin/moko <command>\n";
}
+108
View File
@@ -0,0 +1,108 @@
{
"name": "mokoconsulting-tech/enterprise",
"description": "MokoStandards Enterprise API \u2014 PHP implementation",
"type": "library",
"version": "04.05.00",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "Moko Consulting",
"email": "hello@mokoconsulting.tech"
}
],
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.1",
"ext-json": "*",
"ext-curl": "*",
"guzzlehttp/guzzle": "^7.8",
"monolog/monolog": "^3.5",
"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/routing": "^6.4",
"symfony/cache": "^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"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^1.10 || ^2.0",
"squizlabs/php_codesniffer": "^3.8 || ^4.0",
"vimeo/psalm": "^5.20",
"phpmd/phpmd": "^2.14"
},
"autoload": {
"psr-4": {
"MokoStandards\\": "src/",
"MokoEnterprise\\": "lib/Enterprise/",
"MokoStandards\\Plugins\\Joomla\\": "lib/plugins/Joomla/"
},
"classmap": [
"lib/Enterprise/CliFramework.php"
],
"files": [
"src/functions.php"
]
},
"archive": {
"exclude": [
"/tests",
"/wrappers",
"/logs",
"/.github"
]
},
"autoload-dev": {
"psr-4": {
"MokoStandards\\Tests\\": "tests/"
}
},
"bin": [
"bin/moko",
"bin/validate-module"
],
"scripts": {
"test": "phpunit",
"phpcs": "phpcs --standard=phpcs.xml api/",
"phpstan": "phpstan analyse -c phpstan.neon api/",
"psalm": "psalm --config=psalm.xml",
"check": [
"@phpcs",
"@phpstan",
"@test"
],
"moko": "php bin/moko",
"health": "php bin/moko health -- --path .",
"sync": "php bin/moko sync",
"inventory": "php bin/moko inventory -- --path .",
"validate": "php bin/moko health -- --path .",
"check:syntax": "php bin/moko check:syntax -- --path .",
"check:version": "php bin/moko check:version -- --path .",
"check:changelog": "php bin/moko check:changelog -- --path .",
"check:structure": "php bin/moko check:structure -- --path .",
"check:headers": "php bin/moko check:headers -- --path .",
"check:secrets": "php bin/moko check:secrets -- --path .",
"check:enterprise": "php bin/moko check:enterprise -- --path .",
"drift": "php bin/moko drift"
},
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"preferred-install": "dist"
},
"repositories": [
{
"type": "composer",
"url": "https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"
}
]
}
File diff suppressed because it is too large Load Diff
+306
View File
@@ -0,0 +1,306 @@
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation — htdocs/ root, not a module"
repository_type = "crm-platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards platform identifier"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root — entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
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"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
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"
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 minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
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 on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
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 = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
+187
View File
@@ -0,0 +1,187 @@
{
"schemaVersion": "1.0",
"metadata": {
"name": "Default Repository Structure",
"description": "Default repository structure applicable to all repository types with minimal requirements",
"repositoryType": "library",
"platform": "multi-platform",
"lastUpdated": "2026-01-16T00:00:00Z",
"maintainer": "Moko Consulting"
},
"structure": {
"rootFiles": [
{
"name": "README.md",
"extension": "md",
"description": "Project overview and documentation",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-README.md"
},
{
"name": "LICENSE",
"extension": "",
"description": "License file (GPL-3.0-or-later)",
"requirementStatus": "required",
"audience": "general",
"template": "templates/licenses/GPL-3.0"
},
{
"name": "CHANGELOG.md",
"extension": "md",
"description": "Version history and changes",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-CHANGELOG.md"
},
{
"name": "CONTRIBUTING.md",
"extension": "md",
"description": "Contribution guidelines",
"requirementStatus": "required",
"audience": "contributor",
"template": "templates/docs/required/template-CONTRIBUTING.md"
},
{
"name": "SECURITY.md",
"extension": "md",
"description": "Security policy and vulnerability reporting",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-SECURITY.md"
},
{
"name": "CODE_OF_CONDUCT.md",
"extension": "md",
"description": "Community code of conduct",
"requirementStatus": "suggested",
"audience": "contributor",
"template": "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
"name": ".gitignore",
"extension": "gitignore",
"description": "Git ignore patterns",
"requirementStatus": "required",
"alwaysOverwrite": false,
"audience": "developer"
},
{
"name": ".gitattributes",
"extension": "gitattributes",
"description": "Git attributes configuration",
"requirementStatus": "required",
"audience": "developer"
},
{
"name": ".editorconfig",
"extension": "editorconfig",
"description": "Editor configuration for consistent coding style",
"requirementStatus": "required",
"alwaysOverwrite": false,
"audience": "developer"
},
{
"name": "Makefile",
"description": "Build automation",
"requirementStatus": "suggested",
"audience": "developer"
}
],
"directories": [
{
"name": "docs",
"path": "docs",
"description": "Documentation directory",
"requirementStatus": "required",
"purpose": "Contains comprehensive project documentation",
"files": [
{
"name": "index.md",
"extension": "md",
"description": "Documentation index",
"requirementStatus": "suggested"
}
]
},
{
"name": "scripts",
"path": "scripts",
"description": "Build and automation scripts",
"requirementStatus": "required",
"purpose": "Contains scripts for building, testing, and deploying"
},
{
"name": "src",
"path": "src",
"description": "Source code directory",
"requirementStatus": "required",
"purpose": "Contains application source code"
},
{
"name": "tests",
"path": "tests",
"description": "Test files",
"requirementStatus": "suggested",
"purpose": "Contains unit tests, integration tests, and test fixtures",
"subdirectories": [
{
"name": "unit",
"path": "tests/unit",
"description": "Unit tests",
"requirementStatus": "suggested"
},
{
"name": "integration",
"path": "tests/integration",
"description": "Integration tests",
"requirementStatus": "optional"
}
]
},
{
"name": ".github",
"path": ".github",
"description": "GitHub-specific configuration",
"requirementStatus": "required",
"purpose": "Contains GitHub Actions workflows and configuration",
"subdirectories": [
{
"name": "workflows",
"path": ".github/workflows",
"description": "GitHub Actions workflows",
"requirementStatus": "suggested"
}
]
},
{
"name": "node_modules",
"path": "node_modules",
"description": "Node.js dependencies (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "vendor",
"path": "vendor",
"description": "PHP dependencies (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "build",
"path": "build",
"description": "Build artifacts (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "dist",
"path": "dist",
"description": "Distribution files (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
}
]
}
}
+640
View File
@@ -0,0 +1,640 @@
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,331 @@
/**
* .github-private Repository Structure Definition
* Org-level private repository containing universal GitHub Actions workflows,
* helper scripts, and default issue templates for all governed repositories.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*
* NOTES
* ─────
* • GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults for any
* governed repo that does not supply its own templates.
* • Workflows in .github/workflows/ support both standalone execution and
* workflow_call so governed repos can invoke them as reusable workflows via
* `uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main`.
* • This repo is EXCLUDED from bulk-repo-sync — it manages its own content
* independently as GitHub's org-level defaults repo.
*/
locals {
github_private_repository_structure = {
metadata = {
name = ".github-private"
description = "Private GitHub org defaults — universal workflows, issue templates, and helper scripts"
repository_type = "github-private"
platform = "github-private"
last_updated = "2026-03-12T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
visibility = "private"
sync_priority = -1
exclude_from_sync = true
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Repository overview — purpose, contents, and how governed repos use this repo"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
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 private vulnerability reporting"
required = true
audience = "general"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
required = true
always_overwrite = true
audience = "contributor"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
audience = "contributor"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Governance policy and decision-making process"
required = true
always_overwrite = true
audience = "general"
template = "templates/docs/required/GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
required = true
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".mokostandards.yml"
extension = "yml"
description = "MokoStandards governance marker — identifies this repo as platform=github-private"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "ISSUE_TEMPLATE"
path = "ISSUE_TEMPLATE"
description = "Org-default issue templates — applied to all governed repos without their own templates"
requirement_status = "required"
purpose = "GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults"
files = [
{
name = "config.yml"
extension = "yml"
description = "Issue template chooser — disables blank issues and lists contact links"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/ISSUE_TEMPLATE/config.yml.template"
},
{
name = "bug_report.md"
extension = "md"
description = "Bug report issue template"
requirement_status = "required"
always_overwrite = false
template = "templates/github-private/ISSUE_TEMPLATE/bug_report.md.template"
},
{
name = "feature_request.md"
extension = "md"
description = "Feature request issue template"
requirement_status = "required"
always_overwrite = false
template = "templates/github-private/ISSUE_TEMPLATE/feature_request.md.template"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Helper scripts used by universal workflows and available as git hooks"
requirement_status = "required"
purpose = "Reusable Bash utilities for commit-message and PR-title validation"
files = [
{
name = "check-pr-title.sh"
extension = "sh"
description = "Validates PR title follows conventional-commit format"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/scripts/check-pr-title.sh.template"
},
{
name = "check-commit-msg.sh"
extension = "sh"
description = "Validates individual commit messages follow conventional-commit format; usable as a git commit-msg hook"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/scripts/check-commit-msg.sh.template"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration for .github-private itself"
requirement_status = "required"
purpose = "Contains CI workflows for this repo and reusable workflows callable org-wide"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "CI + universal reusable workflows; callable via uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main"
requirement_status = "required"
files = [
{
name = "stale.yml"
extension = "yml"
description = "Marks stale issues and pull requests; standalone (schedule) and reusable (workflow_call)"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/stale.yml.template"
},
{
name = "auto-assign.yml"
extension = "yml"
description = "Auto-assigns PR author and logs CODEOWNERS status; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/auto-assign.yml.template"
},
{
name = "pr-labeler.yml"
extension = "yml"
description = "Labels PRs from branch name and validates PR title format; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/pr-labeler.yml.template"
},
{
name = "welcome.yml"
extension = "yml"
description = "Posts welcome message on first-time contributor PRs and issues; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/welcome.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 = "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 = "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"
}
]
}
]
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT for automation — required for bulk sync and workflow execution"
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_PATH_SUFFIX"
description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /.github-private)"
required = false
scope = "repo"
}
]
repository_settings = {
visibility = "private"
has_issues = true
has_projects = false
has_wiki = false
has_discussions = false
allow_squash_merge = true
allow_merge_commit = false
allow_rebase_merge = true
delete_branch_on_merge = true
}
}
}
}
+705
View File
@@ -0,0 +1,705 @@
/**
* MokoStandards Repository Structure Definition
* Repository structure definition for the MokoStandards standards and templates repository
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "MokoStandards Repository"
description = "Repository structure definition for MokoStandards - organizational standards, templates, and automation"
repository_type = "standards"
platform = "standards"
last_updated = "2026-03-03T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Repository overview and documentation"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
required = true
audience = "general"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
required = true
always_overwrite = false
protected = 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"
always_overwrite = false
protected = true
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 = false
protected = true
audience = "contributor"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
always_overwrite = false
protected = true
audience = "contributor"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "CITATION.cff"
extension = "cff"
description = "Citation file format for academic references"
required = true
audience = "general"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
required = true
audience = "developer"
},
{
name = ".gitmessage"
extension = "gitmessage"
description = "Git commit message template"
required = true
audience = "developer"
},
{
name = ".git-blame-ignore-revs"
extension = "git-blame-ignore-revs"
description = "Git blame ignore revisions"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".mailmap"
extension = "mailmap"
description = "Git mailmap for contributor attribution"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".eslintrc.json"
extension = "json"
description = "ESLint configuration for JavaScript"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".prettierrc.json"
extension = "json"
description = "Prettier configuration for code formatting"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".markdownlint.json"
extension = "json"
description = "Markdown linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".yamllint"
extension = "yamllint"
description = "YAML linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".pylintrc"
extension = "pylintrc"
description = "Python linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".htmlhintrc"
extension = "htmlhintrc"
description = "HTML linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = "composer.json"
extension = "json"
description = "PHP dependency management"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards sync tracking file — records last sync date, version, and compliance status"
requirement_status = "required"
always_overwrite = true
audience = "developer"
}
]
directories = [
{
name = "api"
path = "api"
description = "API scripts and automation"
required = true
purpose = "Contains all operational scripts - validation, automation, build, release, etc."
subdirectories = [
{
name = "validate"
path = "api/validate"
description = "Validation scripts"
required = true
purpose = "Scripts for validating repository structure, health, and compliance"
},
{
name = "automation"
path = "api/automation"
description = "Automation scripts"
required = true
purpose = "Scripts for bulk operations and repository synchronization"
},
{
name = "build"
path = "api/build"
description = "Build scripts"
requirement_status = "suggested"
purpose = "Scripts for building and packaging"
},
{
name = "release"
path = "api/release"
description = "Release scripts"
requirement_status = "suggested"
purpose = "Scripts for release management"
},
{
name = "tests"
path = "api/tests"
description = "Test scripts"
requirement_status = "suggested"
purpose = "Test scripts and test data"
},
{
name = "maintenance"
path = "api/maintenance"
description = "Maintenance scripts"
requirement_status = "suggested"
purpose = "Scripts for repository maintenance tasks"
},
{
name = "definitions"
path = "api/definitions"
description = "Repository structure definitions"
required = true
purpose = "HCL/Terraform definition files for different repository types"
},
{
name = "lib"
path = "api/lib"
description = "Shared libraries"
requirement_status = "suggested"
purpose = "Shared code libraries and utilities"
}
]
},
{
name = "docs"
path = "docs"
description = "Documentation"
required = true
purpose = "Comprehensive documentation for standards, guides, policies, and references"
subdirectories = [
{
name = "guide"
path = "docs/guide"
description = "User guides"
requirement_status = "suggested"
},
{
name = "reference"
path = "docs/reference"
description = "Reference documentation"
requirement_status = "suggested"
},
{
name = "policy"
path = "docs/policy"
description = "Policies and standards"
requirement_status = "suggested"
},
{
name = "workflows"
path = "docs/workflows"
description = "Workflow documentation"
requirement_status = "suggested"
},
{
name = "security"
path = "docs/security"
description = "Security documentation"
requirement_status = "suggested"
},
{
name = "development"
path = "docs/development"
description = "Development documentation"
requirement_status = "suggested"
}
]
},
{
name = "templates"
path = "templates"
description = "Template files"
required = true
purpose = "Template files for workflows, configs, documentation, and projects"
subdirectories = [
{
name = "workflows"
path = "templates/workflows"
description = "GitHub Actions workflow templates"
required = true
},
{
name = "github"
path = "templates/github"
description = "GitHub configuration templates"
required = true
},
{
name = "docs"
path = "templates/docs"
description = "Documentation templates"
requirement_status = "suggested"
},
{
name = "configs"
path = "templates/configs"
description = "Configuration file templates"
requirement_status = "suggested"
},
{
name = "licenses"
path = "templates/licenses"
description = "License templates"
requirement_status = "suggested"
},
{
name = "projects"
path = "templates/projects"
description = "Project definition templates"
requirement_status = "suggested"
},
{
name = "terraform"
path = "templates/terraform"
description = "Terraform configuration templates"
requirement_status = "suggested"
},
{
name = "scripts"
path = "templates/scripts"
description = "Script templates"
requirement_status = "suggested"
}
]
},
{
name = "logs"
path = "logs"
description = "Log files"
requirement_status = "suggested"
purpose = "Storage for operation logs, audit trails, and metrics"
subdirectories = [
{
name = "audit"
path = "logs/audit"
description = "Audit logs"
requirement_status = "suggested"
},
{
name = "automation"
path = "logs/automation"
description = "Automation logs"
requirement_status = "suggested"
},
{
name = "validation"
path = "logs/validation"
description = "Validation logs"
requirement_status = "suggested"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
required = true
purpose = "GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
required = true
files = [
{
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 = "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 = "repository-cleanup.yml"
extension = "yml"
description = "One-time cleanup: reset labels, strip issue template headers, delete old branches — self-deletes after run"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards self-compliance validation"
requirement_status = "suggested"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall rules setup workflow"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.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 = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
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"
}
]
}
]
files = [
{
name = "config.tf"
extension = "tf"
description = "Repository override configuration for bulk sync"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
},
{
name = "copilot.yml"
extension = "yml"
description = "GitHub Copilot configuration — topic list and repo metadata"
requirement_status = "required"
always_overwrite = true
audience = "developer"
},
{
name = "copilot-instructions.md"
extension = "md"
description = "GitHub Copilot custom instructions for this repository"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
},
{
name = "CLAUDE.md"
extension = "md"
description = "Claude Code context and instructions for this repository"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
}
]
},
{
name = ".checkpoints"
path = ".checkpoints"
description = "Checkpoint files for long-running operations"
requirement_status = "optional"
purpose = "Stores checkpoint data for resumable operations"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT for automation — configure in org Actions secrets"
required = true
},
{
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 = "STANDARDS_VERSION"
description = "Current MokoStandards version"
required = false
},
{
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. /mokostandards)"
required = false
scope = "repo"
}
]
branch_protections = {
main = {
required_status_checks = {
strict = true
contexts = ["standards-compliance", "code-quality"]
}
enforce_admins = false
required_pull_request_reviews = {
dismiss_stale_reviews = true
require_code_owner_reviews = true
required_approving_review_count = 1
}
}
}
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = true
allow_squash_merge = true
allow_merge_commit = false
allow_rebase_merge = true
delete_branch_on_merge = true
}
labels = [
{ name = "bulk-sync-success", color = "0e8a16", description = "Bulk sync completed successfully" },
{ name = "bulk-sync-failure", color = "d73a4a", description = "Bulk sync failed" },
{ name = "standards-update", color = "fbca04", description = "Standards update" },
{ name = "template-update", color = "d4c5f9", description = "Template file update" },
{ name = "documentation", color = "0075ca", description = "Documentation changes" },
{ name = "automation", color = "5319e7", description = "Automation scripts" }
]
}
}
}
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
# Docs Index: /api/definitions
## Purpose
This index provides navigation to documentation within this folder.
## Documents
- [README](./README.md)
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
+683
View File
@@ -0,0 +1,683 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/.github-private
*
* Auto-generated by MokoStandards bulk sync on 2026-03-24T19:30:16+00:00
* Platform : default-repository
* Description: This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/.github-private"
default_branch = "main"
detected_platform = "default-repository"
description = "This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization."
sync_timestamp = "2026-03-24T19:30:16+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 39
created_files = 0
updated_files = 28
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ 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" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "ci.yml"
extension = "yml"
description = "Continuous integration workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "ci.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "ci.yml"
create_path = true
template = "templates/workflows/generic/ci.yml.template"
},
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "repo-health.yml"
extension = "yml"
description = "Repository health monitoring"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "repo_health.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "repo-health.yml"
create_path = true
template = "templates/workflows/generic/repo_health.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
}
]
},
{
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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
+734
View File
@@ -0,0 +1,734 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/.github
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:55+00:00
* Platform : default-repository
* Description:
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/.github"
default_branch = "main"
detected_platform = "default-repository"
description = ""
sync_timestamp = "2026-04-02T21:05:55+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 42
updated_files = 5
skipped_files = 6
}
synced_files = [
{ path = "LICENSE" action = "created" },
{ path = "CONTRIBUTING.md" action = "created" },
{ path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "created" },
{ path = "ROADMAP.md" action = "created" },
{ path = "Makefile" action = "created" },
{ path = "composer.json" action = "created" },
{ path = "docs/index.md" action = "created" },
{ path = "docs/INSTALLATION.md" action = "created" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "created" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "created" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/workflows/deploy-dev.yml" action = "created" },
{ path = ".github/workflows/deploy-demo.yml" action = "created" },
{ path = ".github/workflows/deploy-rs.yml" action = "created" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "created" },
{ path = ".github/workflows/auto-release.yml" action = "created" },
{ path = ".github/workflows/repository-cleanup.yml" action = "created" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "created" },
{ path = ".github/CODEOWNERS" action = "created" },
{ path = "composer.json" action = "enterprise dependency added" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
View File
+735
View File
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/Copy-PortablePath
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:20:12+00:00
* Platform : default-repository
* Description: Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/Copy-PortablePath"
default_branch = "main"
detected_platform = "default-repository"
description = "Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights."
sync_timestamp = "2026-04-02T15:20:12+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/Copy-PortablePath/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+401
View File
@@ -0,0 +1,401 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDoliMods
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:19:29+00:00
* Platform : crm-platform
* Description: The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/crm-platform.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoDoliMods"
default_branch = "main"
detected_platform = "crm-platform"
description = "The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team."
sync_timestamp = "2026-04-02T15:19:29+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/crm-platform.tf"
}
sync_stats = {
total_files = 53
created_files = 3
updated_files = 40
skipped_files = 10
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/deploy-dev.yml" action = "updated" },
{ path = ".github/deploy-demo.yml" action = "updated" },
{ path = ".github/deploy-rs.yml" action = "updated" },
{ path = ".github/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/auto-release.yml" action = "updated" },
{ path = ".github/repository-cleanup.yml" action = "updated" },
{ path = ".github/auto-dev-issue.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDoliMods/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation htdocs/ root, not a module"
repository_type = "crm-platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards platform identifier"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
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"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
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"
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 minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
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 on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
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 = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+401
View File
@@ -0,0 +1,401 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDolibarr
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:42:39+00:00
* Platform : crm-platform
* Description: Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/crm-platform.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoDolibarr"
default_branch = "main"
detected_platform = "crm-platform"
description = "Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers."
sync_timestamp = "2026-04-02T15:42:39+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/crm-platform.tf"
}
sync_stats = {
total_files = 53
created_files = 16
updated_files = 29
skipped_files = 8
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "CONTRIBUTING.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "created" },
{ path = "ROADMAP.md" action = "created" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "created" },
{ path = "docs/INSTALLATION.md" action = "created" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDolibarr/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation htdocs/ root, not a module"
repository_type = "crm-platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards platform identifier"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
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"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
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"
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 minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
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 on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
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 = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoISOUpdatePortable
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:33:45+00:00
* Platform : default-repository
* Description: A PortableApp that keeps ISOs of selected systems up to date.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoISOUpdatePortable"
default_branch = "main"
detected_platform = "default-repository"
description = "A PortableApp that keeps ISOs of selected systems up to date."
sync_timestamp = "2026-04-02T15:33:45+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoISOUpdatePortable/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoPerfectPublisher-Discord
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:10+00:00
* Platform : default-repository
* Description: A Perfect Publisher plugin to post to Discord
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoPerfectPublisher-Discord"
default_branch = "main"
detected_platform = "default-repository"
description = "A Perfect Publisher plugin to post to Discord"
sync_timestamp = "2026-04-02T21:05:10+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoPerfectPublisher-Discord/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Client
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:40:35+00:00
* Platform : default-repository
* Description: A template repo for clients of Moko Consulting
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoStandards-Template-Client"
default_branch = "main"
detected_platform = "default-repository"
description = "A template repo for clients of Moko Consulting"
sync_timestamp = "2026-04-02T15:40:35+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Client/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Generic
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:27:41+00:00
* Platform : default-repository
* Description: A repo template for a generic 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/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoStandards-Template-Generic"
default_branch = "main"
detected_platform = "default-repository"
description = "A repo template for a generic coding project according to MokoStandards"
sync_timestamp = "2026-04-02T15:27:41+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Generic/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+735
View File
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoTesting
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:23:26+00:00
* Platform : default-repository
* Description: Testign grond for Moko Consulting
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoTesting"
default_branch = "main"
detected_platform = "default-repository"
description = "Testign grond for Moko Consulting"
sync_timestamp = "2026-04-02T15:23:26+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoTesting/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+735
View File
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoWinSetup
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:39:49+00:00
* Platform : default-repository
* Description: A setup script for Windows
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoWinSetup"
default_branch = "main"
detected_platform = "default-repository"
description = "A setup script for Windows"
sync_timestamp = "2026-04-02T15:39:49+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoWinSetup/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:33:10+00:00
* Platform : default-repository
* Description: Virtuemart Smart serachPlugin
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH"
default_branch = "main"
detected_platform = "default-repository"
description = "Virtuemart Smart serachPlugin "
sync_timestamp = "2026-04-02T15:33:10+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -0,0 +1,776 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/client-clarksvillefurs
*
* Auto-generated by MokoStandards bulk sync on 2026-03-30T08:30:51+00:00
* Platform : default-repository
* Description: The Clarksville Furs implementation of MokoCassiopeia
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/client-clarksvillefurs"
default_branch = "main"
detected_platform = "default-repository"
description = "The Clarksville Furs implementation of MokoCassiopeia"
sync_timestamp = "2026-03-30T08:30:51+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 56
created_files = 16
updated_files = 22
skipped_files = 18
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/workflows/deploy-dev.yml" action = "created" },
{ path = ".github/workflows/deploy-demo.yml" action = "created" },
{ path = ".github/workflows/deploy-rs.yml" action = "created" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "created" },
{ path = ".github/workflows/auto-release.yml" action = "created" },
{ path = ".github/workflows/repository-cleanup.yml" action = "created" },
{ path = ".github/workflows/auto-dev-issue.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" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/ci.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/ci.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/ci.yml does not match 1f2449072e0d3304fb645b2ef45526fcf978783e\",\"documentation_url\":\"https (truncated...)
" },
{ path = ".github/workflows/test.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/test.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/test.yml does not match 5c7b0f5639c622d5c910a066fe2415b69a49918d\",\"documentation_url\":\"htt (truncated...)
" },
{ path = ".github/workflows/code-quality.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/code-quality.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/code-quality.yml does not match f510c81073d573669477a96a646d3a8383e77b6b\",\"documentation_u (truncated...)
" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 04c7ef91744b3e482f495e592f268a7448dafe74\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/deploy.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/deploy.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/deploy.yml does not match 4895e597d4ee56a976c8792ab80da574bcfa23c8\",\"documentation_url\":\"h (truncated...)
" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/standards-compliance.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/standards-compliance.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/standards-compliance.yml does not match bc0516a1dbc8b2acfcf3189ac28be497c1a8c93e\",\"documen (truncated...)
" },
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "ci.yml"
extension = "yml"
description = "Continuous integration workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "ci.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "ci.yml"
create_path = true
template = "templates/workflows/generic/ci.yml.template"
},
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "repo-health.yml"
extension = "yml"
description = "Repository health monitoring"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "repo_health.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "repo-health.yml"
create_path = true
template = "templates/workflows/generic/repo_health.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
+735
View File
@@ -0,0 +1,735 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/client-kiddieland
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:41:12+00:00
* Platform : default-repository
* Description: Client repo for Kiddieland Child Care Center
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/client-kiddieland"
default_branch = "main"
detected_platform = "default-repository"
description = "Client repo for Kiddieland Child Care Center"
sync_timestamp = "2026-04-02T15:41:12+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.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/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/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-kiddieland/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Version: 04.05.00
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.05.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
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.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
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"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = 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 = "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 = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
+579
View File
@@ -0,0 +1,579 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Deploy
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/deploy/deploy-joomla.php
* VERSION: 04.06.00
* BRIEF: Smart Joomla deploy — routes files to correct Joomla directories based on XML manifest
*
* Parses the extension's XML manifest to determine type (component, module,
* plugin, template, library, package) and deploys each section directly to
* its correct location on the Joomla server. No reinstall needed for code
* changes — only XML manifest changes require a Joomla reinstall.
*
* USAGE
* php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json
* php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json --dry-run
* php api/deploy/deploy-joomla.php --path . --env dev
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use phpseclib3\Net\SFTP;
use phpseclib3\Crypt\PublicKeyLoader;
/**
* Joomla-aware SFTP deployment.
*
* Reads the extension XML manifest to determine:
* - Extension type (component, module, plugin, template, library)
* - Element name (com_xxx, mod_xxx, plg_group_name, tpl_xxx)
* - Client (site or administrator)
* - Plugin group (system, content, etc.)
*
* Then maps src/ subdirectories to their correct Joomla server paths:
*
* Component:
* src/admin/ → administrator/components/{element}/
* src/site/ → components/{element}/
* src/media/ → media/{element}/
* src/api/ → api/components/{element}/
*
* Module:
* src/ → modules/{element}/ (site) or administrator/modules/{element}/ (admin)
* src/media/ → media/{element}/
*
* Plugin:
* src/ → plugins/{group}/{name}/
* src/media/ → media/{element}/
*
* Template:
* src/ → templates/{name}/ (site) or administrator/templates/{name}/ (admin)
* src/media/ → media/templates/site/{name}/ or media/templates/administrator/{name}/
*
* Library:
* src/ → libraries/{name}/
* src/media/ → media/{element}/
*/
class DeployJoomla
{
private string $repoPath;
private string $srcDir;
private array $config = [];
private bool $dryRun = false;
private bool $verbose = false;
private int $uploaded = 0;
private int $unchanged = 0;
private int $skipped = 0;
private int $deleted = 0;
private array $ignorePatterns = [];
public function run(): int
{
$this->parseArgs();
$manifest = $this->findManifest();
if ($manifest === null) {
$this->log("No Joomla XML manifest found in {$this->srcDir}", 'ERROR');
return 1;
}
$ext = $this->parseManifest($manifest);
if ($ext === null) {
$this->log("Failed to parse manifest: {$manifest}", 'ERROR');
return 1;
}
$this->log("Extension: {$ext['type']} / {$ext['element']} (client: {$ext['client']})");
$deployMap = $this->buildDeployMap($ext);
if (empty($deployMap)) {
$this->log("No deploy mappings for extension type: {$ext['type']}", 'ERROR');
return 1;
}
$this->log("Deploy mappings:");
foreach ($deployMap as $map) {
$this->log(" {$map['local']}{$map['remote']}");
}
// Load ignore patterns
$this->ignorePatterns = $this->loadFtpIgnore();
// Check if manifest changed (warn user about reinstall)
$this->checkManifestChange($ext, $manifest);
if ($this->dryRun) {
$this->log("[DRY RUN] Would deploy " . count($deployMap) . " mappings");
foreach ($deployMap as $map) {
if (is_dir($map['local'])) {
$count = iterator_count(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($map['local'], \FilesystemIterator::SKIP_DOTS)
)
);
$this->log(" {$map['local']} ({$count} files) → {$map['remote']}");
}
}
return 0;
}
// Connect
$sftp = $this->connect();
if ($sftp === null) {
return 1;
}
// Deploy each mapping
$errors = 0;
foreach ($deployMap as $map) {
if (!is_dir($map['local'])) {
$this->log(" SKIP: {$map['local']} (directory not found)", 'DEBUG');
continue;
}
// Ensure remote directory exists
$stat = @$sftp->stat($map['remote']);
if ($stat === false) {
$this->log(" MKDIR: {$map['remote']}");
$sftp->mkdir($map['remote'], -1, true);
}
$result = $this->uploadDirectory($sftp, $map['local'], $map['remote']);
if ($result !== 0) {
$errors++;
}
}
// Also deploy the manifest file itself to the admin component directory
if ($ext['type'] === 'component' && file_exists($manifest)) {
$adminRemote = $this->getRemotePath($ext, 'admin');
$manifestName = basename($manifest);
$remoteDest = "{$adminRemote}/{$manifestName}";
$this->uploadFile($sftp, $manifest, $remoteDest);
$this->log(" Manifest: {$manifestName}{$remoteDest}");
}
$this->log("Done. Uploaded: {$this->uploaded}, Unchanged: {$this->unchanged}, Skipped: {$this->skipped}");
return $errors > 0 ? 1 : 0;
}
/**
* Find the Joomla XML manifest file.
*/
private function findManifest(): ?string
{
$searchDirs = [$this->srcDir, $this->repoPath];
foreach ($searchDirs as $dir) {
$iterator = new \DirectoryIterator($dir);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'xml') {
$content = file_get_contents($file->getPathname());
if ($content !== false && str_contains($content, '<extension')) {
return $file->getPathname();
}
}
}
// Also check one level deep
foreach (new \DirectoryIterator($dir) as $subdir) {
if ($subdir->isDir() && !$subdir->isDot()) {
foreach (new \DirectoryIterator($subdir->getPathname()) as $file) {
if ($file->isFile() && $file->getExtension() === 'xml') {
$content = file_get_contents($file->getPathname());
if ($content !== false && str_contains($content, '<extension')) {
return $file->getPathname();
}
}
}
}
}
}
return null;
}
/**
* Parse extension metadata from the XML manifest.
*
* @return array{type: string, element: string, client: string, group: string, name: string}|null
*/
private function parseManifest(string $path): ?array
{
$xml = @simplexml_load_file($path);
if ($xml === false || $xml->getName() !== 'extension') {
return null;
}
$type = (string) ($xml['type'] ?? 'component');
$client = (string) ($xml['client'] ?? 'site');
$group = (string) ($xml['group'] ?? '');
$element = (string) ($xml->element ?? '');
$name = (string) ($xml->name ?? '');
// Derive element from type + name if not explicit
if (empty($element)) {
$cleanName = strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', $name));
$element = match ($type) {
'component' => "com_{$cleanName}",
'module' => "mod_{$cleanName}",
'plugin' => "plg_{$group}_{$cleanName}",
'template' => "tpl_{$cleanName}",
'library' => "lib_{$cleanName}",
default => $cleanName,
};
}
// For plugins, derive the short name (without plg_group_ prefix)
$shortName = $element;
if ($type === 'plugin' && preg_match('/^plg_\w+_(.+)$/', $element, $m)) {
$shortName = $m[1];
} elseif ($type === 'template' && preg_match('/^tpl_(.+)$/', $element, $m)) {
$shortName = $m[1];
}
return [
'type' => $type,
'element' => $element,
'client' => $client,
'group' => $group,
'name' => $name,
'shortName' => $shortName,
];
}
/**
* Build the local→remote deploy mapping based on extension type.
*
* @return array<int, array{local: string, remote: string}>
*/
private function buildDeployMap(array $ext): array
{
$remotePath = rtrim((string) $this->config['remote_path'], '/');
$src = $this->srcDir;
$map = [];
switch ($ext['type']) {
case 'component':
// Admin files
if (is_dir("{$src}/admin") || is_dir("{$src}/administrator")) {
$adminLocal = is_dir("{$src}/admin") ? "{$src}/admin" : "{$src}/administrator";
$map[] = ['local' => $adminLocal, 'remote' => "{$remotePath}/administrator/components/{$ext['element']}"];
}
// Site files
if (is_dir("{$src}/site")) {
$map[] = ['local' => "{$src}/site", 'remote' => "{$remotePath}/components/{$ext['element']}"];
}
// Media files
if (is_dir("{$src}/media")) {
$map[] = ['local' => "{$src}/media", 'remote' => "{$remotePath}/media/{$ext['element']}"];
}
// API files (Joomla 4+)
if (is_dir("{$src}/api")) {
$map[] = ['local' => "{$src}/api", 'remote' => "{$remotePath}/api/components/{$ext['element']}"];
}
// Language files (admin)
if (is_dir("{$src}/language/admin") || is_dir("{$src}/admin/language")) {
$langDir = is_dir("{$src}/language/admin") ? "{$src}/language/admin" : "{$src}/admin/language";
$map[] = ['local' => $langDir, 'remote' => "{$remotePath}/administrator/language"];
}
// Language files (site)
if (is_dir("{$src}/language/site") || is_dir("{$src}/site/language")) {
$langDir = is_dir("{$src}/language/site") ? "{$src}/language/site" : "{$src}/site/language";
$map[] = ['local' => $langDir, 'remote' => "{$remotePath}/language"];
}
break;
case 'module':
$base = $ext['client'] === 'administrator'
? "{$remotePath}/administrator/modules/{$ext['element']}"
: "{$remotePath}/modules/{$ext['element']}";
$map[] = ['local' => $src, 'remote' => $base];
if (is_dir("{$src}/media")) {
$map[] = ['local' => "{$src}/media", 'remote' => "{$remotePath}/media/{$ext['element']}"];
}
break;
case 'plugin':
$map[] = ['local' => $src, 'remote' => "{$remotePath}/plugins/{$ext['group']}/{$ext['shortName']}"];
if (is_dir("{$src}/media")) {
$map[] = ['local' => "{$src}/media", 'remote' => "{$remotePath}/media/{$ext['element']}"];
}
break;
case 'template':
$clientDir = $ext['client'] === 'administrator' ? 'administrator/' : '';
$map[] = ['local' => $src, 'remote' => "{$remotePath}/{$clientDir}templates/{$ext['shortName']}"];
if (is_dir("{$src}/media")) {
$mediaClient = $ext['client'] === 'administrator' ? 'administrator' : 'site';
$map[] = ['local' => "{$src}/media", 'remote' => "{$remotePath}/media/templates/{$mediaClient}/{$ext['shortName']}"];
}
break;
case 'library':
$map[] = ['local' => $src, 'remote' => "{$remotePath}/libraries/{$ext['shortName']}"];
if (is_dir("{$src}/media")) {
$map[] = ['local' => "{$src}/media", 'remote' => "{$remotePath}/media/{$ext['element']}"];
}
break;
case 'package':
// Packages deploy their sub-extensions individually
// For now, deploy to administrator/manifests/packages/
$map[] = ['local' => $src, 'remote' => "{$remotePath}/administrator/manifests/packages"];
break;
}
return $map;
}
/**
* Get the remote path for a specific section of the extension.
*/
private function getRemotePath(array $ext, string $section): string
{
$remotePath = rtrim((string) $this->config['remote_path'], '/');
return match ($section) {
'admin' => "{$remotePath}/administrator/components/{$ext['element']}",
'site' => "{$remotePath}/components/{$ext['element']}",
'media' => "{$remotePath}/media/{$ext['element']}",
default => $remotePath,
};
}
/**
* Check if the XML manifest has changed and warn about reinstall.
*/
private function checkManifestChange(array $ext, string $manifestPath): void
{
$manifestName = basename($manifestPath);
$this->log("");
$this->log("NOTE: If {$manifestName} has changed (new fields, permissions, menu items,");
$this->log(" database schema), you must reinstall the extension through Joomla.");
$this->log(" Code changes (PHP, JS, CSS, language) do NOT require reinstall.");
$this->log("");
}
/**
* Upload a directory recursively to the remote server.
*/
private function uploadDirectory(SFTP $sftp, string $localDir, string $remoteDir): int
{
$errors = 0;
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($localDir, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
$relativePath = substr($item->getPathname(), strlen($localDir) + 1);
$relativePath = str_replace('\\', '/', $relativePath);
$remotePath = "{$remoteDir}/{$relativePath}";
// Check ignore patterns
if ($this->shouldIgnore($relativePath)) {
$this->skipped++;
continue;
}
if ($item->isDir()) {
$stat = @$sftp->stat($remotePath);
if ($stat === false) {
$sftp->mkdir($remotePath, -1, true);
}
} else {
$result = $this->uploadFile($sftp, $item->getPathname(), $remotePath);
if (!$result) {
$errors++;
}
}
}
return $errors;
}
/**
* Upload a single file with smart diff (skip if unchanged).
*/
private function uploadFile(SFTP $sftp, string $localPath, string $remotePath): bool
{
$localSize = filesize($localPath);
$remoteStat = @$sftp->stat($remotePath);
// Smart diff: skip if same size and hash
if ($remoteStat !== false && ($remoteStat['size'] ?? -1) === $localSize) {
$remoteContent = @$sftp->get($remotePath);
if ($remoteContent !== false && md5($remoteContent) === md5_file($localPath)) {
$this->unchanged++;
return true;
}
}
// Ensure parent directory exists
$parentDir = dirname($remotePath);
$parentStat = @$sftp->stat($parentDir);
if ($parentStat === false) {
$sftp->mkdir($parentDir, -1, true);
}
$result = $sftp->put($remotePath, $localPath, SFTP::SOURCE_LOCAL_FILE);
if ($result) {
$this->uploaded++;
if ($this->verbose) {
$this->log(" UPLOAD: {$remotePath}");
}
} else {
$this->log(" FAIL: {$remotePath}", 'ERROR');
}
return $result;
}
/**
* Check if a relative path should be ignored.
*/
private function shouldIgnore(string $relativePath): bool
{
foreach ($this->ignorePatterns as $pattern) {
if (preg_match($pattern, $relativePath)) {
return true;
}
}
// Always skip dotfiles and common non-deploy files
$basename = basename($relativePath);
if (str_starts_with($basename, '.') && $basename !== '.htaccess') {
return true;
}
return false;
}
/**
* Load .ftpignore patterns.
*
* @return string[] Regex patterns
*/
private function loadFtpIgnore(): array
{
$patterns = [];
foreach ([$this->srcDir, $this->repoPath] as $dir) {
$file = "{$dir}/.ftpignore";
if (!file_exists($file)) { continue; }
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) { continue; }
// Convert glob to regex
$regex = str_replace(['.', '*', '?'], ['\\.', '.*', '.'], $line);
$patterns[] = "#^{$regex}(/|$)#i";
}
}
return $patterns;
}
/**
* Connect to the SFTP server.
*/
private function connect(): ?SFTP
{
$host = (string) $this->config['host'];
$port = (int) ($this->config['port'] ?? 22);
$user = (string) $this->config['user'];
$this->log("Connecting to {$user}@{$host}:{$port}...");
$sftp = new SFTP($host, $port, 30);
// Try key auth first
if (!empty($this->config['ssh_key_file'])) {
$keyPath = $this->config['ssh_key_file'];
if (!file_exists($keyPath)) {
$keyPath = "{$this->repoPath}/scripts/keys/{$keyPath}";
}
if (file_exists($keyPath)) {
$passphrase = $this->config['key_passphrase'] ?? '';
$key = PublicKeyLoader::load(file_get_contents($keyPath), $passphrase);
if ($sftp->login($user, $key)) {
$this->log("Connected via SSH key");
return $sftp;
}
$this->log("Key auth failed", 'WARN');
}
}
// Fallback to password
if (!empty($this->config['password'])) {
if ($sftp->login($user, $this->config['password'])) {
$this->log("Connected via password");
return $sftp;
}
}
$this->log("Authentication failed", 'ERROR');
return null;
}
/**
* Parse CLI arguments.
*/
private function parseArgs(): void
{
global $argv;
$this->repoPath = '.';
$this->srcDir = 'src';
$configPath = null;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) { $this->repoPath = $argv[$i + 1]; }
if ($arg === '--src-dir' && isset($argv[$i + 1])) { $this->srcDir = $argv[$i + 1]; }
if ($arg === '--config' && isset($argv[$i + 1])) { $configPath = $argv[$i + 1]; }
if ($arg === '--key-passphrase' && isset($argv[$i + 1])) { $this->config['key_passphrase'] = $argv[$i + 1]; }
if ($arg === '--dry-run') { $this->dryRun = true; }
if ($arg === '--verbose') { $this->verbose = true; }
}
$this->repoPath = realpath($this->repoPath) ?: $this->repoPath;
// Resolve src dir
if (!str_starts_with($this->srcDir, '/')) {
$this->srcDir = "{$this->repoPath}/{$this->srcDir}";
}
// Try htdocs/ as fallback
if (!is_dir($this->srcDir) && is_dir("{$this->repoPath}/htdocs")) {
$this->srcDir = "{$this->repoPath}/htdocs";
}
// Load config
if ($configPath && file_exists($configPath)) {
$json = file_get_contents($configPath);
$json = preg_replace('#^\s*//.*$#m', '', $json);
$json = preg_replace('#,\s*([\]}])#', '$1', $json);
$parsed = json_decode($json, true);
if (is_array($parsed)) {
$this->config = array_merge($this->config, $parsed);
}
}
}
private function log(string $msg, string $level = 'INFO'): void
{
$prefix = match ($level) {
'ERROR' => 'ERROR: ',
'WARN' => 'WARN: ',
'DEBUG' => $this->verbose ? '' : null,
default => '',
};
if ($prefix === null) { return; }
fwrite($level === 'ERROR' ? STDERR : STDOUT, "{$prefix}{$msg}\n");
}
}
$deploy = new DeployJoomla();
exit($deploy->run());
+794
View File
@@ -0,0 +1,794 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Deploy
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/deploy/deploy-sftp.php
* VERSION: 04.06.00
* BRIEF: Deploy a repository src/ directory to a remote web server via SFTP
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\CLIApp;
use phpseclib3\Net\SFTP;
use phpseclib3\Crypt\PublicKeyLoader;
/**
* SFTP deployment script.
*
* Reads connection details from a sftp-config.json file (Sublime Text SFTP
* format with // comments stripped) and recursively uploads the src/
* directory of a repository to the configured remote path.
*/
class DeploySftp extends CLIApp
{
/** @var array<string,mixed> Parsed sftp-config.json contents */
private array $config = [];
/** @var int Count of files uploaded in the current run */
private int $uploaded = 0;
/** @var int Count of files skipped due to ignore rules */
private int $skipped = 0;
/** @var int Count of files unchanged (smart deploy) */
private int $unchanged = 0;
/** @var int Count of remote files deleted (smart deploy) */
private int $deleted = 0;
public function __construct()
{
parent::__construct(
'deploy-sftp',
'Deploy a repository src/ directory to a remote web server via SFTP',
'04.00.15'
);
}
/**
* Print full help including usage examples.
*
* Overrides CLIApp::printHelp() to add an EXAMPLES section and
* document the scripts/keys/ key-resolution convention.
*/
protected function printHelp(): void
{
parent::printHelp();
echo <<<'DEPLOY_SFTP_HELP'
ARGUMENTS
--path <dir> Repository root (default: current directory).
--src-dir <dir> Sub-directory inside the repo to upload (default: src).
--env <dev|rs> Target environment. Selects the named config file:
dev → {path}/scripts/sftp-config/sftp-config.dev.json
rs → {path}/scripts/sftp-config/sftp-config.rs.json
--config <file> Explicit config path — overrides --env and auto-lookup.
--key-passphrase <pw> Passphrase for the SSH private key if it is encrypted.
DIRECTORY LAYOUT (gitignored — create locally from templates/scripts/deploy/)
{repo_root}/
scripts/
sftp-config/ ← gitignored; place sftp-config.{env}.json files here
keys/ ← gitignored; place .ppk / PEM key files here
KEY RESOLUTION
ssh_key_file in sftp-config.json may be an absolute path or a bare filename.
When it is not absolute the script looks for the key under:
{path}/scripts/keys/{filename}
before falling back to the raw value as a relative path from CWD.
Supported key formats: PuTTY .ppk | OpenSSH PEM (via phpseclib)
CONFIG FORMAT
sftp-config.json follows Sublime Text SFTP plugin conventions.
// line comments and trailing commas are stripped before parsing.
EXAMPLES
# Dry-run preview of dev deployment
php api/deploy/deploy-sftp.php --env dev --dry-run --verbose
# Deploy to dev server
php api/deploy/deploy-sftp.php --path /repos/mymodule --env dev
# Deploy to release/production server
php api/deploy/deploy-sftp.php --path /repos/mymodule --env rs
# Use a different source directory
php api/deploy/deploy-sftp.php --env dev --src-dir htdocs
# Explicit config with encrypted key
php api/deploy/deploy-sftp.php \
--path /repos/mymodule \
--env rs \
--key-passphrase "my passphrase"
# Quiet mode (errors only)
php api/deploy/deploy-sftp.php --env dev --quiet
EXIT CODES
0 All files uploaded successfully
1 Connection failed or one or more files could not be uploaded
2 Invalid arguments or config file error
DEPLOY_SFTP_HELP;
}
/**
* Register script-specific CLI arguments.
*
* @return array<string,string> Option spec => description
*/
protected function setupArguments(): array
{
return [
'path:' => 'Path to the repository to deploy (default: current directory)',
'src-dir:' => 'Source sub-directory to upload (default: src)',
'env:' => 'Target environment: dev (sftp-config.dev.json) or rs (sftp-config.rs.json)',
'config:' => 'Explicit config file path — overrides --env and default lookup',
'key-passphrase:' => 'Passphrase for the SSH private key file (if required)',
];
}
/**
* Main execution logic.
*
* @return int POSIX exit code
*/
protected function run(): int
{
$repoPath = $this->resolveRepoPath();
$srcDir = $this->resolveSrcDir($repoPath);
$configPath = $this->resolveConfigPath($repoPath);
$this->log("Repository : {$repoPath}");
$this->log("Source dir : {$srcDir}");
$this->log("Config file: {$configPath}");
if (!$this->loadConfig($configPath)) {
return 1;
}
if (!$this->validateConfig()) {
return 1;
}
$host = (string) $this->config['host'];
$port = (int) ($this->config['port'] ?? 22);
$user = (string) $this->config['user'];
$remotePath = rtrim((string) $this->config['remote_path'], '/');
// Load .ftpignore from src/ directory (primary) and repo root (fallback)
$ignores = array_merge(
$this->buildIgnorePatterns(),
$this->loadFtpIgnorePatterns($srcDir),
$this->loadFtpIgnorePatterns($repoPath)
);
$this->log("Connecting to {$user}@{$host}:{$port} ...");
if ($this->dryRun) {
$this->log("[DRY RUN] Would connect and upload {$srcDir}{$remotePath}");
return $this->walkAndDryRun($srcDir, $remotePath, $srcDir, $ignores);
}
$sftp = $this->connect($host, $port, $user, $repoPath);
if ($sftp === null) {
return 1;
}
$this->log("Connected. Uploading {$srcDir}{$remotePath}");
// Ensure the remote destination directory exists; create it (recursively) if not.
// phpseclib3 has a channel reuse bug — is_dir() opens the SFTP subsystem, then
// mkdir() tries to open it again causing "Please close the channel" error.
// Fix: use nlist() which reuses the channel, then mkdir() works on the same channel.
$dirCheck = @$sftp->nlist(dirname($remotePath));
$baseName = basename($remotePath);
$dirExists = is_array($dirCheck) && in_array($baseName, $dirCheck, true);
if (!$dirExists) {
$this->log("Remote directory not found — creating: {$remotePath}");
if (!$sftp->mkdir($remotePath, -1, true)) {
$this->log("Failed to create remote directory: {$remotePath}", 'ERROR');
return 1;
}
}
$exitCode = $this->uploadDirectory($sftp, $srcDir, $remotePath, $srcDir, $ignores);
$this->log("Done. Uploaded: {$this->uploaded}, Unchanged: {$this->unchanged}, Deleted: {$this->deleted}, Skipped: {$this->skipped}");
return $exitCode;
}
// ─── Private helpers ──────────────────────────────────────────────────────
/**
* Resolve the absolute path to the repository root.
*
* @return string Absolute repository path
* @throws \RuntimeException When the path does not exist
*/
private function resolveRepoPath(): string
{
$raw = $this->getOption('path', '.');
$path = realpath($raw);
if ($path === false || !is_dir($path)) {
$this->log("Repository path does not exist or is not a directory: {$raw}", 'ERROR');
exit(1);
}
return $path;
}
/**
* Resolve the source directory that will be uploaded.
*
* @param string $repoPath Absolute repository root
* @return string Absolute path to the source directory
*/
private function resolveSrcDir(string $repoPath): string
{
$sub = $this->getOption('src-dir', 'src');
$dir = $repoPath . DIRECTORY_SEPARATOR . $sub;
if (!is_dir($dir)) {
$this->log("Source directory does not exist: {$dir}", 'ERROR');
exit(1);
}
return $dir;
}
/** Map of --env values to their sftp-config filename. */
private const ENV_CONFIG_MAP = [
'dev' => 'sftp-config.dev.json',
'rs' => 'sftp-config.rs.json',
];
/**
* Resolve the absolute path to the sftp-config file.
*
* Resolution order (first match wins):
* 1. --config <file> — explicit override
* 2. --env dev|rs — maps to sftp-config.{env}.json in {path}/scripts/sftp-config/
* 3. sftp-config.json — generic fallback in {path}/scripts/sftp-config/
*
* @param string $repoPath Absolute repository root
* @return string Absolute path to the config file
*/
private function resolveConfigPath(string $repoPath): string
{
$configDir = $repoPath . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'sftp-config';
// 1. Explicit --config wins unconditionally
$explicit = $this->getOption('config', null);
if ($explicit !== null) {
$path = realpath($explicit);
if ($path === false) {
$this->log("Config file not found: {$explicit}", 'ERROR');
exit(1);
}
return $path;
}
// 2. --env selects the named config file
$env = $this->getOption('env', null);
if ($env !== null) {
$env = strtolower((string) $env);
if (!isset(self::ENV_CONFIG_MAP[$env])) {
$valid = implode(', ', array_keys(self::ENV_CONFIG_MAP));
$this->log("Unknown --env value '{$env}'. Valid values: {$valid}", 'ERROR');
exit(2);
}
$envConfig = $configDir . DIRECTORY_SEPARATOR . self::ENV_CONFIG_MAP[$env];
if (!file_exists($envConfig)) {
$this->log("Config file not found for --env {$env}: {$envConfig}", 'ERROR');
$this->log("Copy templates/scripts/deploy/sftp-config.{$env}.json.example → {$envConfig}", 'ERROR');
exit(1);
}
return $envConfig;
}
// 3. Generic fallback
$default = $configDir . DIRECTORY_SEPARATOR . 'sftp-config.json';
if (!file_exists($default)) {
$this->log("No config file found. Tried: {$default}", 'ERROR');
$this->log("Use --env dev, --env rs, or --config <path>.", 'ERROR');
exit(1);
}
return $default;
}
/**
* Load and parse sftp-config.json, stripping JS-style // comments.
*
* The Sublime Text SFTP plugin allows // comments and trailing commas,
* so we strip those before passing the text to json_decode.
*
* @param string $configPath Absolute path to the config file
* @return bool True on success
*/
private function loadConfig(string $configPath): bool
{
$raw = file_get_contents($configPath);
if ($raw === false) {
$this->log("Cannot read config file: {$configPath}", 'ERROR');
return false;
}
// Strip // line comments (not inside strings — good enough for this format)
$stripped = preg_replace('#(?<!:)//[^\r\n]*#', '', $raw);
// Strip trailing commas before ] or }
$stripped = preg_replace('/,(\s*[}\]])/', '$1', $stripped ?? '');
$decoded = json_decode($stripped ?? '', true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->log("Failed to parse config file: " . json_last_error_msg(), 'ERROR');
return false;
}
$this->config = $decoded;
return true;
}
/**
* Validate that required config keys are present.
*
* @return bool True when all required fields exist
*/
private function validateConfig(): bool
{
$required = ['host', 'user', 'remote_path'];
$missing = [];
foreach ($required as $key) {
if (empty($this->config[$key])) {
$missing[] = $key;
}
}
if (empty($this->config['ssh_key_file']) && empty($this->config['password'])) {
$missing[] = 'ssh_key_file or password';
}
if (!empty($missing)) {
$this->log("Missing required config fields: " . implode(', ', $missing), 'ERROR');
return false;
}
return true;
}
/**
* Build the list of ignore regex patterns from config.
*
* @return array<int,string> Array of PCRE patterns
*/
private function buildIgnorePatterns(): array
{
$raw = $this->config['ignore_regexes'] ?? [];
return array_values(array_filter(array_map('strval', $raw)));
}
/**
* Load ignore patterns from a .ftpignore file in the source directory.
*
* Follows gitignore syntax: blank lines and # comments are skipped;
* glob wildcards (* ? **) are converted to PCRE; a trailing slash matches
* directories; a leading slash anchors the pattern to the upload root.
*
* @param string $srcDir Absolute path to the source directory being uploaded
* @return array<int,string> PCRE patterns ready for shouldIgnore()
*/
private function loadFtpIgnorePatterns(string $srcDir): array
{
$ignoreFile = $srcDir . DIRECTORY_SEPARATOR . '.ftpignore';
if (!is_file($ignoreFile)) {
return [];
}
$this->log("Loading ignore rules from .ftpignore", 'DEBUG');
$patterns = [];
$lines = file($ignoreFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines === false) {
return [];
}
foreach ($lines as $line) {
// Strip inline comments and trim whitespace
$line = trim((string) preg_replace('/#.*$/', '', $line));
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
// Negation patterns (!) are not supported — log and skip
if (str_starts_with($line, '!')) {
$this->log(" .ftpignore: negation patterns not supported, skipping: {$line}", 'DEBUG');
continue;
}
// A trailing slash means "match directories only"; we match the prefix
$line = rtrim($line, '/');
// A leading slash anchors to the root; strip it and anchor in regex
$anchored = str_starts_with($line, '/');
$line = ltrim($line, '/');
// Convert glob to PCRE
$regex = preg_quote($line, '/');
$regex = str_replace('\*\*', '.*', $regex); // ** → any path segment
$regex = str_replace('\*', '[^/]*', $regex); // * → any name chars
$regex = str_replace('\?', '[^/]', $regex); // ? → single char
$regex = $anchored ? '^' . $regex : '(^|/)' . $regex;
$patterns[] = $regex . '(/|$)';
$this->log(" .ftpignore rule: {$line} → /{$regex}/i", 'DEBUG');
}
return $patterns;
}
/**
* Establish an authenticated SFTP connection.
*
* @param string $host Remote hostname
* @param int $port SSH port
* @param string $user SSH username
* @param string $repoPath Absolute repository root (for key resolution)
* @return SFTP|null Authenticated SFTP object, or null on failure
*/
private function connect(string $host, int $port, string $user, string $repoPath): ?SFTP
{
try {
$sftp = new SFTP($host, $port, timeout: 30);
} catch (\Throwable $e) {
$this->log("Cannot reach {$host}:{$port}" . $e->getMessage(), 'ERROR');
return null;
}
$rawKeyFile = $this->config['ssh_key_file'] ?? null;
if (!empty($rawKeyFile)) {
$keyFile = $this->resolveKeyPath((string) $rawKeyFile, $repoPath);
$this->log("Using SSH key: {$keyFile}", 'DEBUG');
return $this->authenticateWithKey($sftp, $user, $keyFile);
}
// Password fallback
$password = (string) ($this->config['password'] ?? '');
if (!$sftp->login($user, $password)) {
$this->log("SFTP password authentication failed for {$user}@{$host}", 'ERROR');
return null;
}
return $sftp;
}
/**
* Resolve the SSH key file path.
*
* If the configured path is not absolute, look for the file under
* {repo_path}/scripts/keys/ before falling back to the raw value.
*
* @param string $configured Raw value from sftp-config.json
* @param string $repoPath Absolute repository root
* @return string Resolved absolute path
*/
private function resolveKeyPath(string $configured, string $repoPath): string
{
// Already absolute — use as-is
if (str_starts_with($configured, '/') || preg_match('/^[A-Za-z]:[\/\\\\]/', $configured)) {
return $configured;
}
// Relative path — check scripts/keys/ first
$keysDir = $repoPath . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'keys';
$candidate = $keysDir . DIRECTORY_SEPARATOR . ltrim($configured, '/\\');
if (file_exists($candidate)) {
return $candidate;
}
// Fall back to relative from CWD
return $configured;
}
/**
* Authenticate the SFTP session using a private key file.
*
* Supports both PuTTY .ppk keys and OpenSSH PEM keys via phpseclib.
*
* @param SFTP $sftp Open SFTP connection
* @param string $user SSH username
* @param string $keyFile Path to the private key file
* @return SFTP|null Authenticated connection, or null on failure
*/
private function authenticateWithKey(SFTP $sftp, string $user, string $keyFile): ?SFTP
{
if (!file_exists($keyFile)) {
$this->log("SSH key file not found: {$keyFile}", 'ERROR');
return null;
}
$passphrase = $this->getOption('key-passphrase', null);
try {
$keyData = file_get_contents($keyFile);
if ($keyData === false) {
throw new \RuntimeException("Cannot read key file: {$keyFile}");
}
$key = $passphrase !== null
? PublicKeyLoader::load($keyData, $passphrase)
: PublicKeyLoader::load($keyData);
} catch (\Throwable $e) {
$this->log("Failed to load SSH key: " . $e->getMessage(), 'ERROR');
return null;
}
if (!$sftp->login($user, $key)) {
$this->log("SFTP key authentication failed for {$user}", 'ERROR');
return null;
}
return $sftp;
}
/**
* Check whether a relative file path should be ignored.
*
* @param string $relativePath Path relative to the upload root
* @param array<int,string> $patterns PCRE patterns from sftp-config.json
* @return bool True when the file should be skipped
*/
private function shouldIgnore(string $relativePath, array $patterns): bool
{
foreach ($patterns as $pattern) {
if (preg_match('#' . $pattern . '#i', $relativePath) === 1) {
return true;
}
}
return false;
}
/**
* Recursively upload a local directory to the remote server.
*
* @param SFTP $sftp Authenticated SFTP connection
* @param string $localDir Absolute local directory path
* @param string $remotePath Absolute remote directory path
* @param string $srcRoot Absolute local root (for relative-path calculation)
* @param array<int,string> $ignorePatterns PCRE patterns to skip
* @return int POSIX exit code (0 = success)
*/
/**
* Smart deploy: upload only changed/new files, delete removed files.
*
* Compares local files against remote by size. If sizes match, compares
* MD5 hashes. Only uploads when content differs. Removes remote files
* that no longer exist locally (respecting ignore patterns).
*/
private function uploadDirectory(
SFTP $sftp,
string $localDir,
string $remotePath,
string $srcRoot,
array $ignorePatterns
): int {
$entries = scandir($localDir);
if ($entries === false) {
$this->log("Cannot read directory: {$localDir}", 'ERROR');
return 1;
}
// Build set of local entries for deletion detection
$localEntryNames = [];
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
if (str_starts_with($entry, '.')) {
$this->log(" SKIP {$entry} (dotfile)", 'DEBUG');
$this->skipped++;
continue;
}
$localEntry = $localDir . DIRECTORY_SEPARATOR . $entry;
$remoteEntry = $remotePath . '/' . $entry;
$relative = ltrim(str_replace($srcRoot, '', $localEntry), DIRECTORY_SEPARATOR . '/');
if ($this->shouldIgnore($relative, $ignorePatterns)) {
$this->log(" SKIP {$relative}", 'DEBUG');
$this->skipped++;
continue;
}
$localEntryNames[$entry] = true;
if (is_dir($localEntry)) {
$stat = @$sftp->stat($remoteEntry);
if ($stat === false) {
$this->log(" MKDIR {$remoteEntry}", 'DEBUG');
$sftp->mkdir($remoteEntry, -1, true);
}
$result = $this->uploadDirectory($sftp, $localEntry, $remoteEntry, $srcRoot, $ignorePatterns);
if ($result !== 0) {
return $result;
}
} else {
// Smart diff: compare local vs remote before uploading
if ($this->isFileUnchanged($sftp, $localEntry, $remoteEntry)) {
$this->log(" SAME {$relative}", 'DEBUG');
$this->unchanged++;
continue;
}
$this->log(" PUT {$relative}{$remoteEntry}");
if (!$sftp->put($remoteEntry, $localEntry, SFTP::SOURCE_LOCAL_FILE)) {
$this->log("Failed to upload: {$relative}", 'ERROR');
return 1;
}
$this->uploaded++;
}
}
// Delete remote files that no longer exist locally
$remoteEntries = $sftp->nlist($remotePath);
if (is_array($remoteEntries)) {
foreach ($remoteEntries as $remoteEntry) {
if ($remoteEntry === '.' || $remoteEntry === '..') {
continue;
}
if (!isset($localEntryNames[$remoteEntry])) {
$remoteFull = $remotePath . '/' . $remoteEntry;
$relative = ltrim(str_replace($srcRoot, '', $localDir . DIRECTORY_SEPARATOR . $remoteEntry), DIRECTORY_SEPARATOR . '/');
// Don't delete files that match ignore patterns
if ($this->shouldIgnore($relative, $ignorePatterns)) {
continue;
}
$remoteStat = @$sftp->stat($remoteFull);
if ($remoteStat !== false && ($remoteStat['type'] ?? 0) === 2) {
$this->deleteRemoteDirectory($sftp, $remoteFull);
$this->log(" RMDIR {$remoteFull}");
} else {
$sftp->delete($remoteFull);
$this->log(" DEL {$remoteFull}");
}
$this->deleted++;
}
}
}
return 0;
}
/**
* Check if a local file is identical to its remote counterpart.
*
* Uses size comparison first (fast), then MD5 hash if sizes match.
*/
private function isFileUnchanged(SFTP $sftp, string $localPath, string $remotePath): bool
{
$remoteStat = $sftp->stat($remotePath);
if ($remoteStat === false) {
return false; // Remote file doesn't exist — needs upload
}
$localSize = filesize($localPath);
$remoteSize = $remoteStat['size'] ?? -1;
if ($localSize !== $remoteSize) {
return false; // Different sizes — needs upload
}
// Sizes match — compare MD5 hashes
$localMd5 = md5_file($localPath);
$remoteContent = $sftp->get($remotePath);
if ($remoteContent === false) {
return false;
}
return $localMd5 === md5($remoteContent);
}
/**
* Recursively delete a remote directory and all its contents.
*/
private function deleteRemoteDirectory(SFTP $sftp, string $path): void
{
$entries = $sftp->nlist($path);
if (!is_array($entries)) {
return;
}
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$full = "{$path}/{$entry}";
$entryStat = @$sftp->stat($full);
if ($entryStat !== false && ($entryStat['type'] ?? 0) === 2) {
$this->deleteRemoteDirectory($sftp, $full);
$sftp->rmdir($full);
} else {
$sftp->delete($full);
}
}
$sftp->rmdir($path);
}
/**
* Walk the source directory and log what would be uploaded, without connecting.
*
* @param string $localDir Absolute local directory path
* @param string $remotePath Remote destination path
* @param string $srcRoot Absolute local root for relative paths
* @param array<int,string> $ignorePatterns PCRE patterns to skip
* @return int Always 0 in dry-run mode
*/
private function walkAndDryRun(
string $localDir,
string $remotePath,
string $srcRoot,
array $ignorePatterns
): int {
$entries = scandir($localDir);
if ($entries === false) {
$this->log("Cannot read directory: {$localDir}", 'ERROR');
return 1;
}
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
if (str_starts_with($entry, '.')) {
$this->log("[DRY RUN] SKIP {$entry} (dotfile)");
$this->skipped++;
continue;
}
$localEntry = $localDir . DIRECTORY_SEPARATOR . $entry;
$remoteEntry = $remotePath . '/' . $entry;
$relative = ltrim(str_replace($srcRoot, '', $localEntry), DIRECTORY_SEPARATOR . '/');
if ($this->shouldIgnore($relative, $ignorePatterns)) {
$this->log("[DRY RUN] SKIP {$relative}");
$this->skipped++;
continue;
}
if (is_dir($localEntry)) {
$this->log("[DRY RUN] MKDIR {$remoteEntry}");
$this->walkAndDryRun($localEntry, $remoteEntry, $srcRoot, $ignorePatterns);
} else {
$this->log("[DRY RUN] PUT {$relative}{$remoteEntry}");
$this->uploaded++;
}
}
return 0;
}
}
$script = new DeploySftp();
exit($script->execute());
+362
View File
@@ -0,0 +1,362 @@
# MokoStandards Scripts Architecture
**Version**: 2.0
**Last Updated**: 2026-01-19
**Status**: Comprehensive rebuild in progress
## Overview
This document defines the top-down architecture for all MokoStandards scripts and workflows.
## Design Principles
1. **Separation of Concerns**: Each module has a single, well-defined responsibility
2. **Type Safety**: Full type hints on all public interfaces
3. **Error Handling**: Comprehensive error messages with actionable guidance
4. **Documentation**: Docstrings following Google style guide
5. **Testability**: All modules designed for unit testing with clear interfaces
6. **Logging**: Structured logging with consistent levels and formats
7. **Configuration**: Centralized configuration management
8. **Dependencies**: Minimal external dependencies, clear dependency tree
## Module Hierarchy
```
scripts/
├── lib/ # Core libraries (no external script dependencies)
│ ├── common.py # Foundation: constants, utilities, decorators
│ ├── validation_framework.py # Base classes for validators
│ ├── config_manager.py # Configuration handling
│ ├── github_client.py # GitHub API wrapper
│ ├── audit_logger.py # Structured logging
│ ├── extension_utils.py # Extension/package utilities
│ ├── joomla_manifest.py # Joomla-specific manifest handling
│ └── gui_utils.py # GUI utilities (optional)
├── validate/ # Validation scripts (depend on lib/)
│ ├── auto_detect_platform.php # Platform detection (core)
│ ├── validate_structure_v2.py # Structure validation
│ ├── validate_repo_health.py # Repository health
│ ├── schema_aware_health_check.py # Schema-based validation
│ ├── check_repo_health.py # Health checker
│ ├── validate_codeql_config.py # CodeQL validation
│ ├── manifest.py # Manifest validation
│ ├── workflows.py # Workflow validation
│ ├── php_syntax.py # PHP syntax checking
│ ├── xml_wellformed.py # XML validation
│ ├── no_secrets.py # Secret detection
│ ├── tabs.py # Tab/whitespace checking
│ ├── paths.py # Path validation
│ └── generate_stubs.py # Stub generation
├── automation/ # Automation scripts (depend on lib/, validate/)
│ ├── bulk_update_repos.php (v2) # Bulk repository sync
│ ├── auto_create_org_projects.py # Organization project automation
│ ├── sync_dolibarr_changelog.py # Dolibarr changelog sync
│ ├── sync_file_to_project.py # File sync to projects
│ ├── create_repo_project.py # Repository project creation
│ └── file-distributor.py # File distribution
├── release/ # Release management (depend on lib/, validate/)
│ ├── detect_platform.py # Platform detection for releases
│ ├── package_extension.py # Extension packaging
│ └── dolibarr_release.py # Dolibarr release management
├── maintenance/ # Maintenance scripts (depend on lib/)
│ ├── release_version.py # Version management
│ ├── update_changelog.py # Changelog updates
│ ├── validate_file_headers.py # Header validation
│ └── flush_actions_cache.py # GitHub Actions cache management
├── analysis/ # Analysis tools (depend on lib/)
│ ├── analyze_pr_conflicts.py # PR conflict analysis
│ └── generate_canonical_config.py # Canonical configuration
├── build/ # Build scripts (depend on lib/)
│ └── resolve_makefile.py # Makefile resolution
├── docs/ # Documentation generation (depend on lib/)
│ └── rebuild_indexes.py # Index rebuilding
├── run/ # Runtime scripts (depend on lib/, automation/)
│ └── setup_github_project_v2.py # GitHub Project v2 setup
└── tests/ # Test scripts
├── test_bulk_update_repos.php # Bulk update tests
└── test_dry_run.py # Dry run tests
```
## Core Library Modules (`lib/`)
### common.py
**Purpose**: Foundation module with core utilities
**Exports**:
- Constants (VERSION dynamically read from README.md, REPO_URL, EXIT codes)
- File header generation
- Logging utilities
- Error handling decorators
- Path utilities
- Version extraction from README.md title line
### validation_framework.py
**Purpose**: Base classes and interfaces for validation
**Exports**:
- `Validator` base class
- `ValidationResult` data class
- `ValidationRule` interface
- Common validation patterns
### config_manager.py
**Purpose**: Centralized configuration management
**Exports**:
- `ConfigManager` class
- Configuration schema validation
- Environment variable handling
- Default configuration
### github_client.py
**Purpose**: GitHub API wrapper with authentication
**Exports**:
- `GitHubClient` class
- API request handling with retries
- Rate limit management
- Common GitHub operations
### audit_logger.py
**Purpose**: Structured logging framework
**Exports**:
- `AuditLogger` class
- Log level management
- Structured log formatting
- Log file handling
## Validation Modules (`validate/`)
All validation modules follow the pattern:
1. Import from `lib/`
2. Define validation rules
3. Implement `Validator` interface
4. Return `ValidationResult` objects
5. Provide CLI interface
### auto_detect_platform.php
**Critical Module**: Platform detection for all automation
**Dependencies**: `lib/common`, `lib/validation_framework`
**Exports**: `detect_platform()`, `PlatformType` enum
## Automation Modules (`automation/`)
### bulk_update_repos.php (v2)
**Status**: ✅ Rebuilt (2026-01-19)
**Purpose**: Schema-driven bulk repository synchronization
**Dependencies**: `lib/`, `validate/auto_detect_platform`
### auto_create_org_projects.py
**Purpose**: Automated organization project creation
**Dependencies**: `lib/github_client`, `lib/config_manager`
## Workflows (`.github/workflows/`)
### Reusable Workflows
All reusable workflows follow the pattern:
1. Clear input parameters with descriptions
2. Minimal dependencies
3. Comprehensive error handling
4. Status reporting
### Workflow Categories
1. **CI/CD**: Build, test, deploy workflows
2. **Quality**: Code quality, linting, validation
3. **Automation**: Bulk updates, project management
4. **Security**: Secret scanning, CodeQL, confidentiality
5. **Maintenance**: Cache management, changelog updates
## Coding Standards
### Python
- **Style**: PEP 8 compliant
- **Type Hints**: Required for all public functions
- **Docstrings**: Google style, required for all public functions
- **Error Handling**: Use specific exception types
- **Logging**: Use `audit_logger` for all logging
- **Testing**: Unit tests for all public functions
### YAML (Workflows)
- **Naming**: `kebab-case` for file names
- **Indentation**: 2 spaces
- **Comments**: Document all non-obvious steps
- **Secrets**: Use repository/organization secrets, never hardcode
- **Permissions**: Minimal required permissions only
## File Header Standard
All files must include:
```python
#!/usr/bin/env python3
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# [License text...]
#
# FILE INFORMATION
# DEFGROUP: [Group]
# INGROUP: [Parent Group]
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: [Relative path from repo root]
# VERSION: [X.Y.Z] # Version is dynamically read from README.md title line
# BRIEF: [One-line description]
```
**Note**: The VERSION constant in `common.py` and `MOKO_VERSION` in `common.sh` are now dynamically extracted from the README.md title line, which follows the format:
```markdown
# README - MokoStandards (VERSION: XX.YY.ZZ)
```
This ensures a single source of truth for the repository version.
## Error Handling Strategy
### Exit Codes
- `0`: Success
- `1`: General error
- `2`: Invalid arguments
- `3`: File/resource not found
- `4`: Permission error
- `5`: Validation failed
- `6`: External dependency error
### Error Messages
Format: `[LEVEL] Component: Message (Context)`
- **ERROR**: Unrecoverable errors
- **WARNING**: Non-fatal issues
- **INFO**: Informational messages
- **DEBUG**: Detailed diagnostic information
## Testing Strategy
### Unit Tests
- Located in `scripts/tests/`
- Use `pytest` framework
- Mock external dependencies
- 80%+ code coverage target
### Integration Tests
- Test workflows end-to-end
- Use test repositories
- Validate against schemas
### Validation Tests
- All validators must have test cases
- Test both valid and invalid inputs
- Test edge cases
## Configuration Management
### Configuration Hierarchy
1. Command-line arguments (highest priority)
2. Environment variables
3. Configuration files (`MokoStandards.override.xml`)
4. Default values (lowest priority)
### Configuration Schema
Validated against `schemas/unified-repository-schema.json`
## Logging Strategy
### Log Levels
- **DEBUG**: Detailed diagnostic for development
- **INFO**: General informational messages
- **WARNING**: Warning messages, recoverable issues
- **ERROR**: Error messages, unrecoverable issues
- **CRITICAL**: Critical failures requiring immediate attention
### Log Format
```
[TIMESTAMP] [LEVEL] [COMPONENT] Message (context_key=value)
```
## Dependency Management
### Python Dependencies
Minimal external dependencies:
- **Required**: None (stdlib only for core modules)
- **Optional**:
- `requests` for GitHub API (fallback to `subprocess` + `gh` CLI)
- `PyYAML` for YAML parsing (fallback to `json`)
- `pytest` for testing (dev only)
### Dependency Installation
```bash
pip install -r requirements.txt # Production
pip install -r requirements-dev.txt # Development
```
## Migration Guide
### From v1 to v2
1. Update imports to use `lib/` modules
2. Replace ad-hoc validation with `validation_framework`
3. Use `ConfigManager` instead of manual config parsing
4. Replace print statements with `audit_logger`
5. Add type hints to all functions
6. Add docstrings to all public functions
## Performance Considerations
### Caching
- Platform detection results
- GitHub API responses (with expiry)
- Validation results for unchanged files
### Parallelization
- Repository operations can run in parallel
- Use `concurrent.futures` for I/O-bound tasks
- Respect GitHub API rate limits
## Security Considerations
1. **No Hardcoded Secrets**: Use GitHub secrets
2. **Input Validation**: Validate all user inputs
3. **Path Traversal Prevention**: Validate all file paths
4. **Command Injection Prevention**: Use `subprocess` safely
5. **Least Privilege**: Minimal permissions in workflows
## Maintenance
### Version Numbering
- Major: Breaking changes (expected in v2 rebuild)
- Minor: New features
- Patch: Bug fixes
### Changelog
All changes documented in `CHANGELOG.md` following Keep a Changelog format.
### Review Process
1. All changes require pull request
2. Automated validation must pass
3. Manual review required
4. Tests must pass
## Future Enhancements
1. **Plugin System**: Allow custom validators
2. **Web Dashboard**: UI for health monitoring
3. **Metrics Collection**: Track validation trends
4. **AI Integration**: Automated issue detection
5. **Multi-language Support**: Beyond Python
## References
- [PEP 8 Style Guide](https://pep8.org/)
- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html)
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Keep a Changelog](https://keepachangelog.com/)
- [Semantic Versioning](https://semver.org/)
---
**Maintained by**: Moko Consulting
**Questions**: hello@mokoconsulting.tech
**Repository**: https://github.com/mokoconsulting-tech/MokoStandards
+383
View File
@@ -0,0 +1,383 @@
# Auto-Create Organization Projects
## Overview
The `auto_create_org_projects.py` script automatically creates smart GitHub Projects for every repository in the mokoconsulting-tech organization. It intelligently detects project types (Joomla, Dolibarr, or Generic) and creates appropriate project structures with customized fields and views.
## Features
- **Automatic Project Type Detection**: Detects Joomla, Dolibarr, or generic projects
- **Smart Project Creation**: Creates projects with type-specific custom fields and views
- **Roadmap Generation**: Automatically generates roadmaps for repos that don't have one
- **Roadmap Push**: Pushes generated roadmaps directly to repository docs/ROADMAP.md
- **MokoStandards Integration**: Respects existing Project #7 for MokoStandards
- **Dry Run Mode**: Test without making actual changes
- **Verbose Logging**: Detailed output for debugging
## Requirements
### Authentication
One of the following authentication methods:
1. **GitHub Token** (Recommended for automation):
```bash
export GH_PAT="your_personal_access_token"
```
2. **GitHub CLI**:
```bash
gh auth login
```
### Token Permissions
The token needs the following scopes:
- `repo` - Full repository access
- `project` - Project read/write access
- `read:org` - Organization read access
### Python Dependencies
```bash
pip3 install requests
```
## Usage
### Basic Usage
```bash
# Dry run first to see what would happen
python3 scripts/auto_create_org_projects.py --dry-run
# Actually create projects and roadmaps
export GH_PAT="your_token"
python3 scripts/auto_create_org_projects.py
```
### With Verbose Logging
```bash
python3 scripts/auto_create_org_projects.py --verbose
```
### For a Different Organization
```bash
python3 scripts/auto_create_org_projects.py --org your-org-name
```
### Combined Options
```bash
python3 scripts/auto_create_org_projects.py --dry-run --verbose --org mokoconsulting-tech
```
## What It Does
### 1. Repository Discovery
- Fetches all repositories in the organization
- Filters out archived repositories
- Skips MokoStandards (Project #7 already exists)
### 2. Project Type Detection
Automatically detects project type based on repository contents:
**Joomla Projects:**
- Presence of `.xml` manifest files
- Joomla-specific directory structure (`administrator/`, `components/`, etc.)
- Manifest content contains "joomla" keywords
**Dolibarr Projects:**
- Module descriptor files (`mod*.class.php`)
- Dolibarr directory structure (`htdocs/`, `core/modules/`)
- Class files in standard Dolibarr structure
**Generic Projects:**
- Any repository not matching Joomla or Dolibarr patterns
- Default fallback type
### 3. Roadmap Management
For each repository:
1. **Check for Existing Roadmap**: Looks for `docs/ROADMAP.md`
2. **Generate if Missing**: Creates type-specific roadmap with:
- Version-based milestone structure
- Appropriate deliverables for project type
- Metadata and revision history
3. **Push to Repository**: Commits roadmap directly to default branch
#### Roadmap Structure
**Joomla Projects:**
- Joomla version compatibility tracking
- Extension-specific milestones
- Marketplace considerations
**Dolibarr Projects:**
- Dolibarr version compatibility
- Module number and descriptor tracking
- Database migration planning
**Generic Projects:**
- Standard version milestones
- Core functionality tracking
- General development phases
### 4. Project Creation
Creates GitHub Project v2 with:
**Common Fields (All Types):**
- Status (Backlog, Todo, In Progress, etc.)
- Priority (Critical, High, Medium, Low)
- Size/Effort (XS, S, M, L, XL, XXL)
- Sprint
- Target Version
- Blocked Reason
- Acceptance Criteria
**Joomla-Specific Fields:**
- Joomla Version
- Extension Type
- Marketplace Status
- Update Server URL
- PHP Minimum Version
- Installation Type
**Dolibarr-Specific Fields:**
- Dolibarr Version
- Module Number
- Database Changes
- Module Descriptor Path
- Module Version
- Requires Sudo
**Generic Fields:**
- Technology Stack
- Environment
- Release Channel
- API Version
- Deployment Status
### 5. Project Views
Creates standard views for each project:
1. **Master Backlog** (Table) - All items by priority
2. **Sprint Board** (Board) - Kanban view by status
3. **Release Roadmap** (Roadmap) - Timeline by version
4. **Blocked Items** (Table) - Items needing attention
Plus type-specific views:
- **Joomla**: Extension Compatibility Matrix, Marketplace Pipeline
- **Dolibarr**: Module Compatibility Matrix, Database Migration Tracker
- **Generic**: Deployment Pipeline, Technology Stack View
## Example Output
```
==============================================================================
Auto-Create Smart Projects for Organization Repositories
==============================================================================
🔍 Fetching repositories from mokoconsulting-tech...
✅ Found 15 total repositories (12 active)
==============================================================================
Processing: MokoDoliTools
==============================================================================
📦 Detected type: dolibarr
⚠️ No roadmap found, creating one...
📋 Creating/updating roadmap for MokoDoliTools...
✅ Roadmap created/updated for MokoDoliTools
📁 Creating project for MokoDoliTools (dolibarr)...
✅ Project creation queued for MokoDoliTools
==============================================================================
Processing: MokoJoomlaExtension
==============================================================================
📦 Detected type: joomla
✅ Roadmap already exists
📁 Creating project for MokoJoomlaExtension (joomla)...
✅ Project creation queued for MokoJoomlaExtension
==============================================================================
SUMMARY REPORT
==============================================================================
📊 Organization: mokoconsulting-tech
✅ Projects Created: 12
📋 Roadmaps Created: 8
⏭️ Repositories Skipped: 1
✅ Created Projects:
- MokoDoliTools
- MokoJoomlaExtension
...
📋 Created Roadmaps:
- MokoDoliTools
- MokoGenericProject
...
⏭️ Skipped Repositories:
- MokoStandards (existing)
==============================================================================
✅ Processing complete!
```
## Integration with Existing Infrastructure
### Leverages Existing Templates
The script uses the project configuration templates from:
- `templates/projects/joomla-project-config.json`
- `templates/projects/dolibarr-project-config.json`
- `templates/projects/generic-project-config.json`
### Respects MokoStandards Project #7
The script explicitly skips MokoStandards repository since it already has Project #7 configured and operational.
### Uses GitHub GraphQL API
Leverages the same GraphQL API patterns as `setup_github_project_v2.py` for consistency.
## Dry Run Mode
Always test with `--dry-run` first:
```bash
python3 scripts/auto_create_org_projects.py --dry-run --verbose
```
This will show you:
- Which repositories would be processed
- What project types would be detected
- Which roadmaps would be created
- What projects would be created
No actual changes are made in dry run mode.
## Error Handling
The script handles errors gracefully:
- **Authentication Failures**: Clear messages about token requirements
- **API Errors**: Captured and reported at the end
- **Missing Configs**: Falls back to generic template
- **Repository Access Issues**: Skips and continues
All errors are collected and displayed in the summary report.
## Troubleshooting
### "requests library required"
```bash
pip3 install requests
```
### "GitHub token required"
```bash
export GH_PAT="your_token"
# OR
gh auth login
```
### "Permission denied"
Ensure your token has:
- `repo` scope
- `project` scope
- `read:org` scope
### "Failed to detect project type"
The script will fall back to "generic" type. You can manually adjust the project after creation.
### "Roadmap creation failed"
Check that:
- Token has write access to repository
- Default branch name is correct
- `docs/` directory exists or can be created
## Best Practices
1. **Always Dry Run First**: Test with `--dry-run` before making changes
2. **Use Verbose Mode**: Easier to debug with `--verbose`
3. **Check Token Permissions**: Ensure token has all required scopes
4. **Review Generated Roadmaps**: Customize roadmaps after automatic generation
5. **Monitor API Rate Limits**: Script may hit rate limits with many repos
## Next Steps After Running
After the script completes:
1. **Review Projects**: Check created projects in GitHub UI
2. **Customize Roadmaps**: Edit generated roadmaps for specific needs
3. **Configure Automations**: Set up GitHub Actions for project automation
4. **Add Initial Items**: Populate projects with issues and tasks
5. **Share with Team**: Notify team members about new project structure
## Integration with CI/CD
You can run this script in GitHub Actions:
```yaml
name: Auto-Create Projects
on:
schedule:
- cron: '0 0 * * 0' # Weekly
workflow_dispatch:
jobs:
create-projects:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: pip install requests
- name: Create projects
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: python3 scripts/auto_create_org_projects.py --verbose
```
## See Also
- [setup_github_project_v2.py](./run/setup_github_project_v2.py) - Single project setup
- [sync_file_to_project.py](./automation/sync_file_to_project.py) - Sync documentation files to Project #7
- [templates/projects/README.md](../templates/projects/README.md) - Project templates documentation
- [bulk_update_repos.php](./bulk_update_repos.php) - Bulk repository updates
## Support
For issues or questions:
1. Check this documentation
2. Run with `--dry-run --verbose` for debugging
3. Review error messages in summary report
4. Open issue in MokoStandards repository
5. Contact development team
## Version History
| Version | Date | Changes |
| -------- | ---------- | ------------------------------------------------ |
| 01.00.00 | 2026-01-12 | Initial release with auto-detection and roadmap generation |
+98
View File
@@ -0,0 +1,98 @@
# Standard Dry-Run Pattern for MokoStandards Scripts
## 1. Add --dry-run argument to argparse
```python
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes'
)
```
## 2. Store in args and use throughout script
```python
args = parser.parse_args()
dry_run = args.dry_run
if dry_run:
print("[DRY-RUN] Mode enabled - no changes will be made")
```
## 3. Add dry-run checks before write operations
```python
if dry_run:
print(f"[DRY-RUN] Would create file: {filepath}")
else:
with open(filepath, 'w') as f:
f.write(content)
print(f"Created file: {filepath}")
```
## 4. Pattern for file operations
```python
def write_file(path, content, dry_run=False):
if dry_run:
print(f"[DRY-RUN] Would write to: {path}")
print(f"[DRY-RUN] Content length: {len(content)} bytes")
return
with open(path, 'w') as f:
f.write(content)
print(f"Wrote to: {path}")
```
## 5. Pattern for API calls
```python
def update_repository(repo, data, dry_run=False):
if dry_run:
print(f"[DRY-RUN] Would update repository: {repo}")
print(f"[DRY-RUN] Data: {data}")
return None
response = api.update(repo, data)
print(f"Updated repository: {repo}")
return response
```
## 6. Pattern for shell commands
```python
import subprocess
def run_command(cmd, dry_run=False):
if dry_run:
print(f"[DRY-RUN] Would execute: {cmd}")
return 0
result = subprocess.run(cmd, shell=True)
return result.returncode
```
## 7. Summary reporting
```python
def main():
# ... script logic ...
if dry_run:
print()
print("=" * 60)
print("[DRY-RUN] Summary:")
print(f" Files that would be modified: {modified_count}")
print(f" Files that would be created: {created_count}")
print(f" API calls that would be made: {api_call_count}")
print("=" * 60)
else:
print()
print("Summary:")
print(f" Files modified: {modified_count}")
print(f" Files created: {created_count}")
print(f" API calls made: {api_call_count}")
```
+138
View File
@@ -0,0 +1,138 @@
# Legal Document Generator - Web Interface
## Overview
The Legal Document Generator Web Interface provides a browser-based UI for creating Terms of Service and Privacy Policy documents. Users can configure all options through an intuitive form and preview the generated HTML directly in their browser.
## Features
- **Interactive Web Interface**: Modern, responsive web UI
- **Real-time Preview**: See generated documents instantly in the browser
- **Download Capability**: Download generated HTML files directly
- **Three Website Types**: Support for membership, plain, and ecommerce websites
- **Customizable Options**: Company name, website URL, contact email
- **Professional Styling**: Generated documents include responsive CSS
## Installation
### Prerequisites
- Python 3.7 or higher
- Flask web framework
### Install Dependencies
```bash
pip install flask
```
## Usage
### Starting the Web Server
Basic usage (runs on http://127.0.0.1:5000):
```bash
python scripts/legal_doc_generator_web.py
```
Custom port:
```bash
python scripts/legal_doc_generator_web.py --port 8080
```
Make accessible from network:
```bash
python scripts/legal_doc_generator_web.py --host 0.0.0.0 --port 8080
```
Debug mode:
```bash
python scripts/legal_doc_generator_web.py --debug
```
### Using the Interface
1. **Start the server** using one of the commands above
2. **Open your browser** and navigate to the displayed URL (e.g., http://127.0.0.1:5000)
3. **Fill in the form**:
- Select your website type (Plain, Membership, or E-commerce)
- Enter your company name
- Enter your website URL
- Enter your contact email
- Choose which document(s) to generate
4. **Click "Generate Preview"** to see the document in the preview pane
5. **Click "Download HTML"** to save the generated document
## Form Options
### Website Type
- **Plain Website**: For informational websites with basic analytics and contact forms
- **Membership Website**: Includes terms for user accounts, membership fees, and user-generated content
- **E-commerce Website**: Includes terms for online sales, payments, shipping, and returns
### Document Type
- **Terms of Service**: Generate only the Terms of Service document
- **Privacy Policy**: Generate only the Privacy Policy document
- **Both**: Generate a combined document with both policies
## Features
### Live Preview
The preview pane shows exactly how your document will look, with professional styling applied.
### Instant Download
Click the download button to save the generated HTML file with an appropriate filename based on your company name.
### Responsive Design
The web interface works on desktop, tablet, and mobile devices.
## Security
- All user input is sanitized using HTML escaping
- XSS vulnerabilities are prevented
- No data is stored on the server
- Documents are generated on-demand
## Troubleshooting
### Flask Not Found
If you see "Flask is required for the web interface":
```bash
pip install flask
```
### Port Already in Use
If port 5000 is already in use, specify a different port:
```bash
python scripts/legal_doc_generator_web.py --port 8080
```
### Cannot Access from Other Devices
To make the server accessible from other devices on your network:
```bash
python scripts/legal_doc_generator_web.py --host 0.0.0.0
```
## Command Line Alternative
If you prefer command-line usage, the original CLI versions are still available:
**Python:**
```bash
python scripts/legal_doc_generator.py --type membership --company-name "Acme Inc"
```
**PHP:**
```bash
php scripts/legal_doc_generator.php --type membership --company-name "Acme Inc"
```
## Disclaimer
⚠️ **Important:** These generated documents are templates and should be reviewed by a qualified attorney before use. Customize them to fit your specific business needs and comply with applicable laws in your jurisdiction.
## License
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
+392
View File
@@ -0,0 +1,392 @@
# New Automation Scripts - v2.0.1
This document describes the new automation scripts added to MokoStandards.
## Overview
The following scripts have been added to enhance automation capabilities across analysis, validation, maintenance, documentation, and utility categories.
## Analysis Scripts
### `analyze_dependencies.py`
**Location:** `scripts/analysis/analyze_dependencies.py`
Analyzes project dependencies across multiple package managers (Python, npm, composer).
**Features:**
- Scans for requirements.txt, package.json, composer.json
- Lists all dependencies by type (production, dev)
- Optional check for outdated packages
- JSON output support
**Usage:**
```bash
# Analyze current directory
python3 scripts/analysis/analyze_dependencies.py
# Analyze specific path
python3 scripts/analysis/analyze_dependencies.py /path/to/project
# JSON output
python3 scripts/analysis/analyze_dependencies.py --json
# Check for outdated packages
python3 scripts/analysis/analyze_dependencies.py --check-outdated
```
### `code_metrics.py`
**Location:** `scripts/analysis/code_metrics.py`
Analyzes code metrics including lines of code, file counts, and language distribution.
**Features:**
- Counts total lines, code lines, and comment lines
- Breaks down by programming language
- Identifies largest files
- Comment ratio analysis
- JSON output support
**Usage:**
```bash
# Analyze current directory
python3 scripts/analysis/code_metrics.py
# Analyze specific path
python3 scripts/analysis/code_metrics.py /path/to/project
# JSON output
python3 scripts/analysis/code_metrics.py --json
```
## Automation Scripts
### `setup_dev_environment.py`
**Location:** `scripts/automation/setup_dev_environment.py`
Quick setup script for new contributors to configure their development environment.
**Features:**
- Checks for required tools (git, python3)
- Verifies Python version (3.8+)
- Sets up git commit message template
- Installs Python dependencies
- Configures pre-commit hooks
- Checks environment variables
**Usage:**
```bash
# Full setup
python3 scripts/automation/setup_dev_environment.py
# Skip dependency installation
python3 scripts/automation/setup_dev_environment.py --skip-install
```
### `check_outdated_actions.py`
**Location:** `scripts/automation/check_outdated_actions.py`
Checks for outdated GitHub Actions in workflow files.
**Features:**
- Scans all workflow files (.yml, .yaml)
- Identifies actions with multiple versions
- Detects SHA-based versions
- Compares against known latest versions
- Provides actionable recommendations
**Usage:**
```bash
# Check default location (.github/workflows)
python3 scripts/automation/check_outdated_actions.py
# Check custom workflow directory
python3 scripts/automation/check_outdated_actions.py --workflow-dir path/to/workflows
```
## Validation Scripts
### `check_markdown_links.py`
**Location:** `scripts/validate/check_markdown_links.py`
Validates links in markdown files to ensure they are not broken.
**Features:**
- Extracts all links from markdown files
- Validates local file links
- Detects broken relative links
- Categorizes links (external, local, anchor)
- Reports broken links with file and line number
**Usage:**
```bash
# Check current directory
python3 scripts/validate/check_markdown_links.py
# Check specific path
python3 scripts/validate/check_markdown_links.py docs/
# Skip external link validation
python3 scripts/validate/check_markdown_links.py --skip-external
```
**Exit Code:** Returns 1 if broken links are found, 0 otherwise.
### `find_todos.py`
**Location:** `scripts/validate/find_todos.py`
Finds and reports TODO, FIXME, and other code comments across the codebase.
**Features:**
- Searches for customizable markers (TODO, FIXME, HACK, XXX, BUG, NOTE)
- Supports multiple programming languages
- Groups results by marker type or file
- Excludes common directories (node_modules, vendor, etc.)
**Usage:**
```bash
# Find all markers
python3 scripts/validate/find_todos.py
# Find specific markers
python3 scripts/validate/find_todos.py --markers TODO FIXME
# Group by file instead of marker
python3 scripts/validate/find_todos.py --group-by file
# Search specific directory
python3 scripts/validate/find_todos.py src/
```
### `check_license_headers.py`
**Location:** `scripts/validate/check_license_headers.py`
Checks and optionally fixes missing or incorrect license headers in source files.
**Features:**
- Validates GPL-3.0-or-later license headers
- Supports multiple file types (Python, JavaScript, PHP, Shell, etc.)
- Can automatically add missing headers
- Respects shebang lines in scripts
- Configurable copyright year
**Usage:**
```bash
# Check for missing headers
python3 scripts/validate/check_license_headers.py
# Add missing headers
python3 scripts/validate/check_license_headers.py --fix
# Use specific year
python3 scripts/validate/check_license_headers.py --fix --year 2026
```
**Exit Code:** Returns 1 if missing headers are found, 0 otherwise.
## Maintenance Scripts
### `update_copyright_year.py`
**Location:** `scripts/maintenance/update_copyright_year.py`
Updates copyright year in file headers across the codebase.
**Features:**
- Updates various copyright formats
- Processes multiple file types
- Dry-run mode by default
- Excludes common build/dependency directories
- Batch processing
**Usage:**
```bash
# Dry run (preview changes)
python3 scripts/maintenance/update_copyright_year.py
# Actually update files
python3 scripts/maintenance/update_copyright_year.py --apply
# Use specific year
python3 scripts/maintenance/update_copyright_year.py --year 2026 --apply
# Update specific directory
python3 scripts/maintenance/update_copyright_year.py src/ --apply
```
### `clean_old_branches.py`
**Location:** `scripts/maintenance/clean_old_branches.py`
Identifies and optionally deletes old Git branches.
**Features:**
- Finds branches older than specified days
- Checks if branches are merged
- Protects main/master/develop branches
- Shows last commit date and days since last commit
- Optional forced deletion
**Usage:**
```bash
# Analyze branches older than 90 days
python3 scripts/maintenance/clean_old_branches.py
# Use custom threshold
python3 scripts/maintenance/clean_old_branches.py --days 60
# Delete merged old branches
python3 scripts/maintenance/clean_old_branches.py --delete-merged
# Delete all old branches (caution!)
python3 scripts/maintenance/clean_old_branches.py --delete-all --force
# Use different base branch
python3 scripts/maintenance/clean_old_branches.py --base-branch develop
```
## Documentation Scripts
### `generate_script_catalog.py`
**Location:** `scripts/docs/generate_script_catalog.py`
Generates a comprehensive catalog of all scripts in the repository.
**Features:**
- Scans all script directories
- Extracts metadata from file headers
- Organizes by category
- Generates markdown documentation
- Includes usage examples
**Usage:**
```bash
# Generate catalog to stdout
python3 scripts/docs/generate_script_catalog.py
# Save to file
python3 scripts/docs/generate_script_catalog.py --output SCRIPT_CATALOG.md
# Use custom scripts directory
python3 scripts/docs/generate_script_catalog.py --scripts-dir custom/scripts
```
### `check_doc_coverage.py`
**Location:** `scripts/docs/check_doc_coverage.py`
Checks documentation coverage by identifying undocumented scripts and templates.
**Features:**
- Analyzes scripts and templates
- Calculates documentation coverage percentage
- Identifies missing README files
- Provides overall quality rating
- Lists undocumented items
**Usage:**
```bash
# Check current directory
python3 scripts/docs/check_doc_coverage.py
# Check specific path
python3 scripts/docs/check_doc_coverage.py /path/to/project
```
## Utility Scripts
### `git_helper.sh`
**Location:** `scripts/run/git_helper.sh`
Helper script for common git operations with enhanced output.
**Features:**
- Enhanced status with statistics
- Interactive cleanup of untracked files
- Branch listing with dates
- Stash management with descriptions
- Commit history visualization
- Search commit messages
- Undo last commit (safely)
- Merge conflict detection
**Usage:**
```bash
# Show enhanced status
bash scripts/run/git_helper.sh status
# Clean untracked files
bash scripts/run/git_helper.sh clean
# Sync with remote
bash scripts/run/git_helper.sh sync
# List branches with dates
bash scripts/run/git_helper.sh branch
# Stash with description
bash scripts/run/git_helper.sh stash "WIP: feature implementation"
# Apply stash
bash scripts/run/git_helper.sh unstash
# Show commit history
bash scripts/run/git_helper.sh history 20
# Search commits
bash scripts/run/git_helper.sh search "fix bug"
# Undo last commit (keeps changes)
bash scripts/run/git_helper.sh undo-commit
# Show diff statistics
bash scripts/run/git_helper.sh diff-stats
# Check for merge conflicts
bash scripts/run/git_helper.sh conflicts
```
## Script Statistics
**New Scripts Added:** 11
- Analysis: 2
- Automation: 2
- Validation: 3
- Maintenance: 2
- Documentation: 2
- Utility: 1
**Total Lines of Code:** ~9,800 lines
## Common Features
All new scripts include:
- Comprehensive docstrings and headers
- Command-line argument parsing
- Help messages (`--help`)
- Error handling and validation
- Progress indicators and colored output
- Exclusion of common directories (node_modules, vendor, etc.)
- Support for dry-run modes where applicable
## Integration
These scripts integrate seamlessly with the existing MokoStandards automation framework:
- Follow the same file header conventions
- Use consistent coding standards
- Compatible with existing workflows
- Can be called from CI/CD pipelines
- Support batch operations
## Future Enhancements
Potential future additions:
- Web-based dashboards for metrics
- GitHub Actions integration for automatic checks
- Email notifications for outdated dependencies
- Automated pull request creation for updates
- Integration with project management tools
## Support
For issues or questions about these scripts:
1. Check the script's `--help` output
2. Review this documentation
3. Open an issue in the repository
4. Contact hello@mokoconsulting.tech
+178
View File
@@ -0,0 +1,178 @@
# Quick Start: Auto-Create Organization Projects
This guide provides a quick start for automatically creating smart GitHub Projects for all repositories in the mokoconsulting-tech organization.
## Prerequisites
- GitHub Personal Access Token with permissions:
- `repo` (full repository access)
- `project` (read and write)
- `read:org` (organization read)
- Python 3.7+ with `requests` library installed
## Option 1: GitHub Actions (Recommended)
### Run via GitHub UI
1. Go to **Actions** tab in the MokoStandards repository
2. Select **"Auto-Create Organization Projects"** workflow
3. Click **"Run workflow"**
4. Configure options:
- **Dry run**: Check to preview without making changes (recommended first time)
- **Verbose**: Check to enable detailed logging
5. Click **"Run workflow"**
6. Review the workflow logs and summary
### Scheduled Runs
The workflow automatically runs quarterly (every 3 months) in dry-run mode to detect new repositories and changes.
## Option 2: Command Line
### Dry Run First (Recommended)
```bash
# Preview what would be created
python3 scripts/auto_create_org_projects.py --dry-run --verbose
```
### Create Projects and Roadmaps
```bash
# Set your GitHub token
export GH_PAT="your_github_token"
# Run the script
python3 scripts/auto_create_org_projects.py
# Or with verbose logging
python3 scripts/auto_create_org_projects.py --verbose
```
## What Gets Created
### For Each Repository
1. **Project Type Detection**
- Joomla: Detects `.xml` manifests and Joomla directory structure
- Dolibarr: Detects `mod*.class.php` descriptors and Dolibarr structure
- Generic: Default for everything else
2. **Roadmap Generation** (if missing)
- Creates `docs/ROADMAP.md` in the repository
- Type-specific content with version milestones
- Committed directly to default branch
3. **GitHub Project Creation**
- Project with repository-specific name
- Custom fields based on project type
- Standard views (Master Backlog, Sprint Board, Release Roadmap, Blocked Items)
- Type-specific views
### Custom Fields by Type
**All Projects:**
- Status, Priority, Size/Effort, Sprint, Target Version
- Blocked Reason, Acceptance Criteria
**Joomla Projects Add:**
- Joomla Version, Extension Type, Marketplace Status
- Update Server URL, Extension Version, PHP Minimum
- Installation Type, Has Frontend/Backend/API
**Dolibarr Projects Add:**
- Dolibarr Version, Module Number, Database Changes
- Module Descriptor, Module Version, PHP Minimum
- Requires Sudo, Module Family, Has Triggers/Hooks/Widgets
**Generic Projects Add:**
- Technology Stack, Environment, Release Channel
- API Version, Deployment Status, Infrastructure
- Database Type
## Quick Workflow
```bash
# 1. Test with dry run
python3 scripts/auto_create_org_projects.py --dry-run --verbose
# 2. Review the output
# - Check detected project types
# - Verify roadmaps to be created
# - Confirm projects to be created
# 3. Run for real
export GH_PAT="your_token"
python3 scripts/auto_create_org_projects.py --verbose
# 4. Review created projects
# Visit: https://github.com/orgs/mokoconsulting-tech/projects
```
## Single Repository Mode
To create a project for a specific repository:
```bash
python3 scripts/create_repo_project.py REPO_NAME --type joomla
python3 scripts/create_repo_project.py REPO_NAME --type dolibarr
python3 scripts/create_repo_project.py REPO_NAME --type generic
```
## Troubleshooting
### "GitHub token required"
Set the `GH_PAT` environment variable:
```bash
export GH_PAT="ghp_your_token_here"
```
### "Permission denied"
Verify your token has these scopes:
- `repo` - Full repository access
- `project` - Project read/write
- `read:org` - Organization read
### "Failed to detect project type"
The script defaults to "generic" type. Manually specify type when creating:
```bash
python3 scripts/create_repo_project.py REPO_NAME --type joomla
```
### "Rate limit exceeded"
GitHub API has rate limits. Wait a few minutes and try again, or use a token with higher limits.
## Best Practices
1. **Always dry-run first** to preview changes
2. **Use verbose mode** for better visibility
3. **Review generated roadmaps** and customize as needed
4. **Check created projects** and adjust fields/views
5. **Configure automations** after project creation
6. **Add initial issues** to populate the project
## Next Steps After Creation
1. **Review Projects**: Visit each project and verify configuration
2. **Customize Roadmaps**: Edit generated roadmaps for specific needs
3. **Add Issues**: Populate projects with existing issues
4. **Configure Automations**: Set up workflow automations
5. **Share with Team**: Notify team members about new project boards
## Documentation
- [Full Documentation](./AUTO_CREATE_ORG_PROJECTS.md)
- [Project Templates](../templates/projects/README.md)
- [GitHub Projects v2 Standard](../templates/projects/README.md)
## Support
For issues:
1. Check logs with `--verbose` flag
2. Review error messages
3. Consult [AUTO_CREATE_ORG_PROJECTS.md](./AUTO_CREATE_ORG_PROJECTS.md)
4. Open issue in MokoStandards repository
+32
View File
@@ -0,0 +1,32 @@
# MokoStandards Documentation Catalog
This is a comprehensive catalog of all documentation in the MokoStandards repository.
## Quick Links
- [README](./README.md) - Documentation governance framework
- [ROADMAP](./ROADMAP.md) - Documentation roadmap and future plans
## Directory Structure
Browse documentation by folder:
- [checklist/](./checklist/index.md) - Checklists and procedures
- [glossary/](./glossary/index.md) - Terminology definitions
- [guide/](./guide/index.md) - How-to guides and tutorials
- [policy/](./policy/index.md) - Policies and standards
- [products/](./products/index.md) - Product-specific documentation
- [reference/](./reference/index.md) - Technical references and directories
- [reports/](./reports/index.md) - Reports and status documents
## Metadata
- **Document Type:** catalog
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
- **Last Updated:** This catalog is regenerated whenever documentation is added or removed
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
+887
View File
@@ -0,0 +1,887 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legal Document Generator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 400px 1fr;
gap: 20px;
}
.form-panel, .preview-panel {
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
padding: 30px;
}
.form-panel {
max-height: 95vh;
overflow-y: auto;
}
h1 {
color: #667eea;
font-size: 28px;
margin-bottom: 10px;
}
.subtitle {
color: #666;
margin-bottom: 25px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: 600;
color: #333;
margin-bottom: 8px;
font-size: 14px;
}
input[type="text"],
input[type="email"],
input[type="url"],
input[type="date"],
select,
textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: #667eea;
}
input::placeholder,
textarea::placeholder {
color: #999;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.checkbox-group label {
margin: 0;
font-weight: normal;
cursor: pointer;
}
.radio-group {
display: flex;
gap: 20px;
margin-top: 10px;
}
.radio-option {
display: flex;
align-items: center;
gap: 8px;
}
.radio-option input[type="radio"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.radio-option label {
margin: 0;
font-weight: normal;
cursor: pointer;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 25px;
}
button {
flex: 1;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #28a745;
color: white;
}
.btn-secondary:hover {
background: #218838;
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(40, 167, 69, 0.4);
}
.warning-box {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
.warning-box strong {
color: #856404;
}
.warning-box p {
color: #856404;
font-size: 13px;
line-height: 1.6;
margin-top: 5px;
}
.preview-panel h2 {
color: #333;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 2px solid #e0e0e0;
}
.preview-content {
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
min-height: 500px;
max-height: 80vh;
overflow-y: auto;
}
.welcome-message {
text-align: center;
color: #999;
padding: 100px 20px;
}
.welcome-message h3 {
font-size: 24px;
margin-bottom: 15px;
}
.document-content {
background: white;
padding: 40px;
border-radius: 8px;
}
.document-content h1 {
color: #333;
border-bottom: 3px solid #667eea;
padding-bottom: 15px;
margin-bottom: 25px;
}
.document-content h2 {
color: #444;
font-size: 22px;
margin-top: 30px;
margin-bottom: 15px;
}
.document-content h3 {
color: #555;
font-size: 18px;
margin-top: 20px;
margin-bottom: 12px;
}
.document-content p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.document-content ul {
margin-left: 25px;
margin-bottom: 15px;
}
.document-content li {
color: #666;
line-height: 1.8;
margin-bottom: 8px;
}
.document-content strong {
color: #333;
}
.document-footer {
margin-top: 40px;
padding-top: 20px;
border-top: 2px solid #e0e0e0;
font-size: 13px;
color: #999;
font-style: italic;
}
@media (max-width: 1024px) {
.container {
grid-template-columns: 1fr;
}
.form-panel {
max-height: none;
}
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="form-panel">
<h1>Legal Document Generator</h1>
<p class="subtitle">Create professional Terms of Service and Privacy Policy documents</p>
<form id="documentForm">
<div class="form-group">
<label for="websiteType">Website Type *</label>
<select id="websiteType" required>
<option value="plain">Plain Website (Informational)</option>
<option value="membership">Membership Website</option>
<option value="ecommerce">E-commerce Website</option>
</select>
</div>
<div class="form-group">
<label for="companyName">Company/Business Name *</label>
<input type="text" id="companyName" value="Your Company" required>
</div>
<div class="form-group">
<label for="websiteName">Website Name</label>
<input type="text" id="websiteName" placeholder="Leave blank to use company name">
</div>
<div class="form-group">
<label for="websiteUrl">Website URL *</label>
<input type="url" id="websiteUrl" value="https://yourwebsite.com" required>
</div>
<div class="form-group">
<label for="contactEmail">General Contact Email *</label>
<input type="email" id="contactEmail" value="contact@yourcompany.com" required>
</div>
<div class="form-group">
<label for="businessContact">Business Contact Email</label>
<input type="email" id="businessContact" placeholder="Leave blank to use general contact">
</div>
<div class="form-group">
<label for="complianceEmail">Compliance/Legal Contact Email</label>
<input type="email" id="complianceEmail" placeholder="Leave blank to use general contact">
</div>
<div class="form-group">
<label for="physicalAddress">Physical Address (Optional)</label>
<textarea id="physicalAddress" rows="2" placeholder="123 Main St, City, State, ZIP"></textarea>
</div>
<div class="form-group">
<label for="country">Country/Jurisdiction (Optional)</label>
<input type="text" id="country" placeholder="e.g., United States, European Union">
</div>
<div class="form-group">
<label for="stateRegion">State/Regional Law (Optional)</label>
<input type="text" id="stateRegion" placeholder="e.g., California, New York, Texas">
</div>
<div class="form-group">
<label for="customJurisdiction">Custom Jurisdiction (Optional)</label>
<input type="text" id="customJurisdiction" placeholder="Leave blank to auto-generate from country/state. Example: 'the State of California' or 'England and Wales'">
</div>
<div class="form-group">
<label for="industry">Industry (Optional)</label>
<select id="industry">
<option value="">-- Select Industry --</option>
<option value="Technology">Technology</option>
<option value="Healthcare">Healthcare</option>
<option value="Finance">Finance</option>
<option value="E-commerce">E-commerce</option>
<option value="Education">Education</option>
<option value="Entertainment">Entertainment</option>
<option value="Real Estate">Real Estate</option>
<option value="Manufacturing">Manufacturing</option>
<option value="Retail">Retail</option>
<option value="Professional Services">Professional Services</option>
<option value="Other">Other</option>
</select>
</div>
<div class="form-group">
<label for="dpoEmail">Data Protection Officer Email (Optional)</label>
<input type="email" id="dpoEmail" placeholder="dpo@yourcompany.com">
</div>
<div class="form-group">
<label for="effectiveDate">Effective Date (Optional)</label>
<input type="date" id="effectiveDate">
</div>
<div class="form-group">
<label for="version">Document Version</label>
<input type="text" id="version" value="1.0" placeholder="1.0">
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="gdprCompliant">
<label for="gdprCompliant">GDPR Compliant (EU Users)</label>
</div>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="ccpaCompliant">
<label for="ccpaCompliant">CCPA Compliant (California Users)</label>
</div>
</div>
<div class="form-group">
<label>Document Type *</label>
<div class="radio-group">
<div class="radio-option">
<input type="radio" id="docTypeTos" name="docType" value="tos">
<label for="docTypeTos">Terms of Service</label>
</div>
<div class="radio-option">
<input type="radio" id="docTypePrivacy" name="docType" value="privacy">
<label for="docTypePrivacy">Privacy Policy</label>
</div>
<div class="radio-option">
<input type="radio" id="docTypeBoth" name="docType" value="both" checked>
<label for="docTypeBoth">Both</label>
</div>
</div>
</div>
<div class="button-group">
<button type="submit" class="btn-primary">Generate Preview</button>
</div>
<div class="button-group">
<button type="button" id="downloadBtn" class="btn-secondary hidden">Download HTML</button>
</div>
</form>
<div class="warning-box">
<strong>⚠️ Important Disclaimer:</strong>
<p><strong>NO WARRANTY:</strong> These generated documents are provided "AS IS" without any warranty of any kind, either expressed or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, or non-infringement.</p>
<p style="margin-top: 10px;"><strong>NOT LEGAL ADVICE:</strong> These templates do not constitute legal advice. They are general information only and should be reviewed and customized by a qualified attorney before use.</p>
<p style="margin-top: 10px;"><strong>YOUR RESPONSIBILITY:</strong> You are solely responsible for ensuring these documents comply with all applicable laws in your jurisdiction and meet your specific business needs.</p>
<p style="margin-top: 10px;"><strong>SEEK PROFESSIONAL ADVICE:</strong> Always consult with a licensed attorney in your jurisdiction before using any legal documents.</p>
</div>
</div>
<div class="preview-panel">
<h2>Document Preview</h2>
<div class="preview-content" id="previewContent">
<div class="welcome-message">
<h3>Welcome to Legal Document Generator</h3>
<p>Fill in the form on the left and click 'Generate Preview' to see your document here.</p>
</div>
</div>
</div>
</div>
<script>
// Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Format date
function formatDate(dateStr) {
if (!dateStr) return new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
}
// Generate Terms of Service
function generateTermsOfService(data) {
const displayName = data.websiteName || data.companyName;
// Use custom jurisdiction if provided, otherwise auto-generate from state/country
const jurisdiction = data.customJurisdiction ||
(data.stateRegion
? `${data.stateRegion}${data.country ? ', ' + data.country : ''}`
: (data.country || 'applicable jurisdiction'));
let html = `
<h1>Terms of Service</h1>
<p><strong>Version:</strong> ${escapeHtml(data.version)}</p>
<p><strong>Last Updated:</strong> ${formatDate()}</p>
${data.effectiveDate ? `<p><strong>Effective Date:</strong> ${formatDate(data.effectiveDate)}</p>` : ''}
<h2>1. Acceptance of Terms</h2>
<p>By accessing and using ${escapeHtml(data.companyName)}'s website (${escapeHtml(data.websiteUrl)}), you accept and agree to be bound by the terms and provision of this agreement.</p>
<h2>2. Use License</h2>
<p>Permission is granted to temporarily access the materials on ${escapeHtml(data.companyName)}'s website for personal, non-commercial transitory viewing only.</p>
`;
// Add website-type specific sections
if (data.websiteType === 'membership') {
html += `
<h2>3. User Accounts</h2>
<p>When you create an account with us, you must provide accurate and complete information. You are responsible for maintaining the security of your account.</p>
<h2>4. Membership and Payments</h2>
<p>Membership fees are billed in advance on a recurring basis. You authorize us to charge your payment method for the applicable fees.</p>
`;
}
if (data.websiteType === 'ecommerce') {
html += `
<h2>3. Product Orders</h2>
<p>When you place an order through our website, you are offering to purchase a product subject to these terms.</p>
<h2>4. Pricing and Payment</h2>
<p>All prices are in the currency specified and are subject to change without notice. Payment must be received by us before order acceptance.</p>
<h2>5. Shipping and Delivery</h2>
<p>We will ship products to the address you provide. Shipping times are estimates and not guaranteed.</p>
<h2>6. Returns and Refunds</h2>
<p>Please review our Return Policy for information about returns, refunds, and exchanges.</p>
`;
}
html += `
<h2>7. Disclaimer</h2>
<p>The materials on ${escapeHtml(data.companyName)}'s website are provided on an 'as is' basis. ${escapeHtml(data.companyName)} makes no warranties, expressed or implied.</p>
<h2>8. Limitations</h2>
<p>In no event shall ${escapeHtml(data.companyName)} or its suppliers be liable for any damages arising out of the use or inability to use the materials on ${escapeHtml(data.companyName)}'s website.</p>
<h2>9. Governing Law</h2>
<p>These terms and conditions are governed by and construed in accordance with the laws of ${escapeHtml(jurisdiction)} and you irrevocably submit to the exclusive jurisdiction of the courts in that location.</p>
`;
// Add regulatory compliance sections
if (data.gdprCompliant || data.ccpaCompliant || data.industry) {
html += `<h2>Regulatory Compliance</h2>`;
if (data.gdprCompliant) {
html += `
<h3>GDPR Compliance (European Users)</h3>
<p>For users in the European Union, we comply with the General Data Protection Regulation (GDPR). You have the right to:</p>
<ul>
<li>Access your personal data</li>
<li>Rectify inaccurate data</li>
<li>Request erasure of your data ("right to be forgotten")</li>
<li>Restrict processing of your data</li>
<li>Data portability</li>
<li>Object to automated decision-making</li>
</ul>
${data.dpoEmail ? `<p>To exercise these rights, please contact our Data Protection Officer at: ${escapeHtml(data.dpoEmail)}</p>` : ''}
`;
}
if (data.ccpaCompliant) {
html += `
<h3>CCPA Compliance (California Users)</h3>
<p>For California residents, we comply with the California Consumer Privacy Act (CCPA). You have the right to:</p>
<ul>
<li>Know what personal information is collected</li>
<li>Know whether your personal information is sold or disclosed</li>
<li>Say no to the sale of personal information</li>
<li>Access your personal information</li>
<li>Request deletion of your personal information</li>
<li>Not be discriminated against for exercising your CCPA rights</li>
</ul>
<p>To submit a request, contact us at: ${escapeHtml(data.complianceEmail || data.contactEmail)}</p>
`;
}
if (data.industry) {
html += `
<h3>Industry-Specific Compliance</h3>
<p>This service operates in the ${escapeHtml(data.industry)} industry and complies with applicable industry-specific regulations and standards.</p>
`;
}
}
// Add contact information
html += `
<h2>Contact Information</h2>
<p>For questions about these Terms of Service, please contact us at:</p>
<ul>
<li>General Contact: ${escapeHtml(data.contactEmail)}</li>
${data.businessContact ? `<li>Business Inquiries: ${escapeHtml(data.businessContact)}</li>` : ''}
${data.complianceEmail ? `<li>Legal/Compliance: ${escapeHtml(data.complianceEmail)}</li>` : ''}
${data.dpoEmail ? `<li>Data Protection Officer: ${escapeHtml(data.dpoEmail)}</li>` : ''}
<li>Website: ${escapeHtml(data.websiteUrl)}</li>
${data.physicalAddress ? `<li>Mailing Address: ${escapeHtml(data.physicalAddress)}</li>` : ''}
</ul>
<h2>Document Information</h2>
<ul>
<li>Version: ${escapeHtml(data.version)}</li>
<li>Last Updated: ${formatDate()}</li>
${data.effectiveDate ? `<li>Effective Date: ${formatDate(data.effectiveDate)}</li>` : ''}
</ul>
`;
return html;
}
// Generate Privacy Policy
function generatePrivacyPolicy(data) {
const displayName = data.websiteName || data.companyName;
let html = `
<h1>Privacy Policy</h1>
<p><strong>Version:</strong> ${escapeHtml(data.version)}</p>
<p><strong>Last Updated:</strong> ${formatDate()}</p>
${data.effectiveDate ? `<p><strong>Effective Date:</strong> ${formatDate(data.effectiveDate)}</p>` : ''}
<h2>1. Introduction</h2>
<p>${escapeHtml(data.companyName)} ("we", "our", or "us") respects your privacy and is committed to protecting your personal data. This privacy policy explains how we collect, use, and protect your information when you visit ${escapeHtml(data.websiteUrl)}.</p>
<h2>2. Information We Collect</h2>
<p>We may collect the following types of information:</p>
<h3>2.1 Information You Provide</h3>
<ul>
<li>Contact information (name, email address, phone number)</li>
<li>Communication preferences</li>
<li>Any other information you choose to provide</li>
</ul>
<h3>2.2 Automatically Collected Information</h3>
<ul>
<li>IP address</li>
<li>Browser type and version</li>
<li>Operating system</li>
<li>Pages visited and time spent on pages</li>
<li>Referring website addresses</li>
<li>Cookies and similar tracking technologies</li>
</ul>
`;
// Add website-type specific sections
if (data.websiteType === 'membership') {
html += `
<h2>3. Membership Information</h2>
<p>As a membership website, we collect additional information including:</p>
<ul>
<li>Account credentials</li>
<li>Profile information</li>
<li>Subscription and billing information</li>
<li>Usage data and activity logs</li>
</ul>
`;
}
if (data.websiteType === 'ecommerce') {
html += `
<h2>3. E-commerce Information</h2>
<p>For processing orders, we collect:</p>
<ul>
<li>Billing and shipping addresses</li>
<li>Payment information (processed securely by payment providers)</li>
<li>Order history and preferences</li>
<li>Product reviews and ratings</li>
</ul>
`;
}
html += `
<h2>4. How We Use Your Information</h2>
<p>We use collected information for the following purposes:</p>
<ul>
<li>To provide and maintain our services</li>
<li>To notify you about changes to our services</li>
<li>To provide customer support</li>
<li>To gather analysis or valuable information to improve our services</li>
<li>To monitor the usage of our services</li>
<li>To detect, prevent and address technical issues</li>
</ul>
<h2>5. Data Retention</h2>
<p>We will retain your personal data only for as long as necessary for the purposes set out in this privacy policy.</p>
<h2>6. Data Security</h2>
<p>The security of your data is important to us. We strive to use commercially acceptable means to protect your personal data, but no method of transmission over the Internet or electronic storage is 100% secure.</p>
<h2>7. Your Rights</h2>
<p>You have the right to:</p>
<ul>
<li>Access your personal data</li>
<li>Correct inaccurate data</li>
<li>Request deletion of your data</li>
<li>Object to processing of your data</li>
<li>Request restriction of processing</li>
<li>Data portability</li>
<li>Withdraw consent</li>
</ul>
`;
// Add regulatory compliance sections
if (data.gdprCompliant || data.ccpaCompliant) {
html += `<h2>Your Privacy Rights</h2>`;
if (data.gdprCompliant) {
html += `
<h3>GDPR Rights (European Users)</h3>
<p>Under the General Data Protection Regulation (GDPR), you have enhanced privacy rights:</p>
<p><strong>Right to Access:</strong> You can request a copy of your personal data.</p>
<p><strong>Right to Rectification:</strong> You can request correction of inaccurate data.</p>
<p><strong>Right to Erasure:</strong> You can request deletion of your personal data ("right to be forgotten").</p>
<p><strong>Right to Restrict Processing:</strong> You can request limitation of how we process your data.</p>
<p><strong>Right to Data Portability:</strong> You can receive your data in a structured, machine-readable format.</p>
<p><strong>Right to Object:</strong> You can object to processing of your personal data.</p>
<p><strong>Right to Withdraw Consent:</strong> You can withdraw consent for data processing at any time.</p>
<p><strong>Right to Lodge a Complaint:</strong> You can file a complaint with your local data protection authority.</p>
${data.dpoEmail ? `<p>To exercise your rights, contact our Data Protection Officer at: ${escapeHtml(data.dpoEmail)}</p>` : ''}
`;
}
if (data.ccpaCompliant) {
html += `
<h3>CCPA Rights (California Residents)</h3>
<p>Under the California Consumer Privacy Act (CCPA), you have specific privacy rights:</p>
<p><strong>Right to Know:</strong> You have the right to know what personal information we collect, use, disclose, and sell.</p>
<p><strong>Right to Delete:</strong> You have the right to request deletion of your personal information.</p>
<p><strong>Right to Opt-Out:</strong> You have the right to opt-out of the sale of your personal information.</p>
<p><strong>Right to Non-Discrimination:</strong> You have the right to not be discriminated against for exercising your CCPA rights.</p>
<p><strong>Authorized Agent:</strong> You may designate an authorized agent to make requests on your behalf.</p>
<p>To submit a CCPA request, contact us at: ${escapeHtml(data.complianceEmail || data.contactEmail)}</p>
<p>We will verify your identity before processing your request. We aim to respond within 45 days of receipt.</p>
`;
}
if (data.dpoEmail) {
html += `
<h3>Data Protection Officer</h3>
<p>We have appointed a Data Protection Officer (DPO) to oversee our data protection strategy and ensure compliance with data protection laws. You can contact our DPO at: ${escapeHtml(data.dpoEmail)}</p>
`;
}
}
html += `
<h2>8. Cookies</h2>
<p>We use cookies and similar tracking technologies to track activity on our website. You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent.</p>
<h2>9. Third-Party Services</h2>
<p>We may employ third-party companies and individuals to facilitate our service. These third parties have access to your personal data only to perform these tasks on our behalf.</p>
<h2>10. Changes to This Privacy Policy</h2>
<p>We may update our privacy policy from time to time. We will notify you of any changes by posting the new privacy policy on this page.</p>
<h2>Contact Information</h2>
<p>If you have any questions about this Privacy Policy, please contact us at:</p>
<ul>
<li>General Contact: ${escapeHtml(data.contactEmail)}</li>
${data.businessContact ? `<li>Business Inquiries: ${escapeHtml(data.businessContact)}</li>` : ''}
${data.complianceEmail ? `<li>Privacy/Compliance: ${escapeHtml(data.complianceEmail)}</li>` : ''}
${data.dpoEmail ? `<li>Data Protection Officer: ${escapeHtml(data.dpoEmail)}</li>` : ''}
<li>Website: ${escapeHtml(data.websiteUrl)}</li>
${data.physicalAddress ? `<li>Mailing Address: ${escapeHtml(data.physicalAddress)}</li>` : ''}
</ul>
<h2>Document Information</h2>
<ul>
<li>Version: ${escapeHtml(data.version)}</li>
<li>Last Updated: ${formatDate()}</li>
${data.effectiveDate ? `<li>Effective Date: ${formatDate(data.effectiveDate)}</li>` : ''}
</ul>
`;
return html;
}
// Generate complete document
function generateDocument(data) {
const docType = document.querySelector('input[name="docType"]:checked').value;
let content = '';
if (docType === 'tos') {
content = generateTermsOfService(data);
} else if (docType === 'privacy') {
content = generatePrivacyPolicy(data);
} else {
content = generateTermsOfService(data) + '<hr style="margin: 50px 0; border: none; border-top: 2px solid #e0e0e0;">' + generatePrivacyPolicy(data);
}
content += `<div class="document-footer">*This document was generated by MokoStandards Legal Document Generator*</div>`;
return content;
}
// Handle form submission
document.getElementById('documentForm').addEventListener('submit', function(e) {
e.preventDefault();
const data = {
websiteType: document.getElementById('websiteType').value,
companyName: document.getElementById('companyName').value,
websiteName: document.getElementById('websiteName').value,
websiteUrl: document.getElementById('websiteUrl').value,
contactEmail: document.getElementById('contactEmail').value,
businessContact: document.getElementById('businessContact').value,
complianceEmail: document.getElementById('complianceEmail').value,
physicalAddress: document.getElementById('physicalAddress').value,
country: document.getElementById('country').value,
stateRegion: document.getElementById('stateRegion').value,
customJurisdiction: document.getElementById('customJurisdiction').value,
industry: document.getElementById('industry').value,
dpoEmail: document.getElementById('dpoEmail').value,
effectiveDate: document.getElementById('effectiveDate').value,
version: document.getElementById('version').value,
gdprCompliant: document.getElementById('gdprCompliant').checked,
ccpaCompliant: document.getElementById('ccpaCompliant').checked
};
const content = generateDocument(data);
const previewContent = document.getElementById('previewContent');
previewContent.innerHTML = `<div class="document-content">${content}</div>`;
// Show download button
document.getElementById('downloadBtn').classList.remove('hidden');
});
// Handle download
document.getElementById('downloadBtn').addEventListener('click', function() {
const docType = document.querySelector('input[name="docType"]:checked').value;
const content = document.getElementById('previewContent').innerHTML;
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legal Documents - ${document.getElementById('companyName').value}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 40px 20px;
line-height: 1.6;
color: #333;
}
h1 {
color: #333;
border-bottom: 3px solid #667eea;
padding-bottom: 15px;
margin-bottom: 25px;
}
h2 {
color: #444;
font-size: 22px;
margin-top: 30px;
margin-bottom: 15px;
}
h3 {
color: #555;
font-size: 18px;
margin-top: 20px;
margin-bottom: 12px;
}
p {
margin-bottom: 15px;
}
ul {
margin-left: 25px;
margin-bottom: 15px;
}
li {
margin-bottom: 8px;
}
hr {
margin: 50px 0;
border: none;
border-top: 2px solid #e0e0e0;
}
.document-footer {
margin-top: 40px;
padding-top: 20px;
border-top: 2px solid #e0e0e0;
font-size: 13px;
color: #999;
font-style: italic;
}
</style>
</head>
<body>
${content}
</body>
</html>`;
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = docType === 'both' ? 'LEGAL_DOCUMENTS.html' : (docType === 'tos' ? 'TERMS_OF_SERVICE.html' : 'PRIVACY_POLICY.html');
a.click();
URL.revokeObjectURL(url);
});
</script>
</body>
</html>
View File
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Fix
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/fix/fix_line_endings.php
* VERSION: 04.06.00
* BRIEF: CLI script to fix line endings (CRLF → LF) in tracked files
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/CliBase.php';
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\FileFixUtility;
/**
* CLI wrapper that delegates line-ending fixes to FileFixUtility.
*/
class FixLineEndings extends CliBase
{
/**
* Print usage information.
*/
protected function showHelp(): void
{
echo "Usage: {$this->scriptName} [--path DIR] [--dry-run] [--help]\n\n";
echo "Fixes CRLF line endings to LF in all tracked source files.\n\n";
echo "OPTIONS:\n";
echo " --path DIR Repository root (default: current directory)\n";
echo " --dry-run Show what would be changed without modifying files\n";
echo " --help Show this help message\n";
}
/**
* Run the line-ending fix via FileFixUtility.
*
* @return int Exit code: 0 on success.
*/
protected function execute(): int
{
$path = (string) ($this->getOption('path') ?? '.');
$files = FileFixUtility::fixLineEndings($path, $this->dryRun);
foreach ($files as $f) {
$this->success("Fixed: {$f}");
}
$label = $this->dryRun ? 'Would fix' : 'Fixed';
$this->log("{$label} " . count($files) . ' file(s)');
return 0;
}
}
$script = new FixLineEndings($argv);
exit($script->run());
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Fix
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/fix/fix_permissions.php
* VERSION: 04.06.00
* BRIEF: CLI script to fix file permissions (dirs 755, files 644, scripts 755)
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/CliBase.php';
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\FileFixUtility;
/**
* CLI wrapper that delegates permission fixes to FileFixUtility.
*/
class FixPermissions extends CliBase
{
/**
* Print usage information.
*/
protected function showHelp(): void
{
echo "Usage: {$this->scriptName} [--path DIR] [--dry-run] [--help]\n\n";
echo "Fixes file permissions: 644 for files, 755 for dirs and *.php/*.sh scripts.\n\n";
echo "OPTIONS:\n";
echo " --path DIR Repository root (default: current directory)\n";
echo " --dry-run Show what would be changed without modifying files\n";
echo " --help Show this help message\n";
}
/**
* Run the permissions fix via FileFixUtility.
*
* @return int Exit code: 0 on success.
*/
protected function execute(): int
{
$path = (string) ($this->getOption('path') ?? '.');
if ($this->dryRun) {
$this->warning('[DRY-RUN] Would fix permissions (dirs 755, files 644, scripts 755)');
return 0;
}
FileFixUtility::fixPermissions($path, $this->dryRun);
$this->success('[OK] Permissions fixed');
return 0;
}
}
$script = new FixPermissions($argv);
exit($script->run());
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Fix
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/fix/fix_tabs.php
* VERSION: 04.06.00
* BRIEF: CLI script to convert tabs to spaces in tracked source files
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/CliBase.php';
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\FileFixUtility;
/**
* CLI wrapper that delegates tab-to-space conversion to FileFixUtility.
*/
class FixTabs extends CliBase
{
/**
* Print usage information.
*/
protected function showHelp(): void
{
echo "Usage: {$this->scriptName} [--path DIR] [--type TYPE] [--dry-run] [--help]\n\n";
echo "Convert tabs to spaces in tracked source files.\n\n";
echo "OPTIONS:\n";
echo " --path DIR Repository root (default: current directory)\n";
echo " --type TYPE File type: yaml, python, shell, all (default: all)\n";
echo " --dry-run Show changes without modifying files\n";
echo " --help Show this help message\n\n";
echo "NOTE: Makefile variants are always skipped.\n";
}
/**
* Run the tab-fix via FileFixUtility.
*
* @return int Exit code: 0 on success, 2 on invalid arguments.
*/
protected function execute(): int
{
$path = (string) ($this->getOption('path') ?? '.');
$fileType = (string) ($this->getOption('type') ?? 'all');
try {
$files = FileFixUtility::fixTabs($path, $fileType, $this->dryRun);
} catch (\InvalidArgumentException $e) {
$this->log($e->getMessage(), 'ERROR');
return 2;
}
foreach ($files as $f) {
$this->success("Fixed: {$f}");
}
$label = $this->dryRun ? 'Would fix' : 'Fixed';
$this->log("{$label} " . count($files) . ' file(s)');
return 0;
}
}
$script = new FixTabs($argv);
exit($script->run());
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Fix
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/fix/fix_trailing_spaces.php
* VERSION: 04.06.00
* BRIEF: CLI script to remove trailing whitespace from tracked source files
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/CliBase.php';
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\FileFixUtility;
/**
* CLI wrapper that delegates trailing-space removal to FileFixUtility.
*/
class FixTrailingSpaces extends CliBase
{
/**
* Print usage information.
*/
protected function showHelp(): void
{
echo "Usage: {$this->scriptName} [--path DIR] [--type TYPE] [--dry-run] [--help]\n\n";
echo "Remove trailing whitespace from tracked source files.\n\n";
echo "OPTIONS:\n";
echo " --path DIR Repository root (default: current directory)\n";
echo " --type TYPE File type: yaml, python, shell, markdown, all (default: all)\n";
echo " --dry-run Show changes without modifying files\n";
echo " --help Show this help message\n";
}
/**
* Run the trailing-space fix via FileFixUtility.
*
* @return int Exit code: 0 on success, 2 on invalid arguments.
*/
protected function execute(): int
{
$path = (string) ($this->getOption('path') ?? '.');
$fileType = (string) ($this->getOption('type') ?? 'all');
try {
$files = FileFixUtility::fixTrailingSpaces($path, $fileType, $this->dryRun);
} catch (\InvalidArgumentException $e) {
$this->log($e->getMessage(), 'ERROR');
return 2;
}
foreach ($files as $f) {
$this->success("Fixed: {$f}");
}
$label = $this->dryRun ? 'Would fix' : 'Fixed';
$this->log("{$label} " . count($files) . ' file(s)');
return 0;
}
}
$script = new FixTrailingSpaces($argv);
exit($script->run());
+20
View File
@@ -0,0 +1,20 @@
# Docs Index: /api/fix
## Purpose
This index provides navigation to documentation within this folder.
## Documents
- [README](./README.md)
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
+89
View File
@@ -0,0 +1,89 @@
# Scripts Index
Quick navigation for MokoStandards scripts organized by function.
## Core Categories
### [Automation](automation/)
Repository automation and bulk operations
- Python, PowerShell, and Shell scripts for bulk updates
- File distribution utilities
- Project creation automation
### [Validation](validate/)
Code quality, security, and standards compliance
- Repository health checks (Python and PowerShell GUI)
- Structure validation
- Security scanning
- Platform detection
### [Maintenance](maintenance/)
Repository upkeep and housekeeping
- Changelog management
- Release automation
- File header validation
- Label setup
### [Analysis](analysis/)
Analysis and reporting tools
- PR conflict analysis
- Dependency analysis
- Configuration generation
### [Build](build/)
Build and compilation scripts
- Makefile resolution
- Build automation
### [Release](release/)
Release management and packaging
- Platform detection
- Package creation
- Dolibarr release automation
### [Lib](lib/)
Shared library code
- Python utilities (common.py)
- Shell utilities (common.sh)
- PowerShell modules (Common.psm1, GuiUtils.psm1, ConfigManager.psm1)
- Extension utilities
- GitHub client
### [Docs](docs/)
Documentation generation and maintenance
- Index rebuilding
- Documentation coverage
- Script catalogs
- Guides and architecture documentation
### [Tests](tests/)
Test scripts
- Bulk update tests
- Dry-run tests
### [Run](run/)
Operational setup scripts
- GitHub Projects setup
### [Fix](fix/)
Fix and repair scripts
- Tab/space fixing
### [Wrappers](wrappers/)
Cross-platform wrappers
- Bash wrappers (53 scripts)
- PowerShell wrappers (53 scripts)
## Multi-Language Support
Scripts are organized by **function** rather than **language**. You'll find:
- **Python** scripts (.py) for core functionality
- **PowerShell** scripts (.ps1) and modules (.psm1) for Windows users
- **Shell** scripts (.sh) for Unix/Linux systems
All three languages may coexist in the same directory for the same functionality.
## See Also
- [README.md](README.md) - Comprehensive scripts documentation
- [docs/](docs/) - Additional guides and documentation
View File
+591
View File
@@ -0,0 +1,591 @@
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Lib
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/lib/CliBase.php
* VERSION: 04.06.00
* BRIEF: Standalone base CLI class for api/ scripts that do not use CliFramework
*/
declare(strict_types=1);
/**
* Base CLI Application Class
*
* Provides common functionality for command-line scripts that do not
* require the full CliFramework enterprise stack.
*/
abstract class CliBase
{
protected array $args = [];
protected array $options = [];
protected bool $verbose = false;
protected bool $dryRun = false;
protected string $scriptName;
/**
* @param array<int,string> $argv Command-line argument vector.
*/
public function __construct(array $argv)
{
$this->scriptName = basename($argv[0] ?? 'script');
$this->parseArguments(array_slice($argv, 1));
$this->verbose = $this->hasOption('verbose') || $this->hasOption('v');
$this->dryRun = $this->hasOption('dry-run');
}
/**
* Parse command-line arguments into options and positional args.
*
* @param array<int,string> $args Argument list (argv without argv[0]).
*/
private function parseArguments(array $args): void
{
foreach ($args as $arg) {
if (str_starts_with($arg, '--')) {
$parts = explode('=', substr($arg, 2), 2);
$this->options[$parts[0]] = $parts[1] ?? true;
} elseif (str_starts_with($arg, '-')) {
$this->options[substr($arg, 1)] = true;
} else {
$this->args[] = $arg;
}
}
}
/**
* Get positional argument by index.
*
* @param int $index Zero-based position.
* @param mixed $default Fallback when argument is absent.
* @return mixed
*/
protected function getArg(int $index, mixed $default = null): mixed
{
return $this->args[$index] ?? $default;
}
/**
* Get option value.
*
* @param string $name Option name (without leading dashes).
* @param mixed $default Fallback when option is absent.
* @return mixed
*/
protected function getOption(string $name, mixed $default = null): mixed
{
return $this->options[$name] ?? $default;
}
/**
* Check if option exists.
*
* @param string $name Option name (without leading dashes).
*/
protected function hasOption(string $name): bool
{
return isset($this->options[$name]);
}
// -------------------------------------------------------------------------
// Console graphics constants
// -------------------------------------------------------------------------
private const ICONS = [
'SUCCESS' => "\u{2713}", // ✓
'ERROR' => "\u{2717}", // ✗
'WARNING' => "\u{26A0}", // ⚠
'INFO' => "\u{2192}", // →
];
private const ANSI = [
'ERROR' => "\033[31m",
'SUCCESS' => "\033[32m",
'WARNING' => "\033[33m",
'INFO' => "\033[36m",
'BOLD' => "\033[1m",
'DIM' => "\033[2m",
'GRAY' => "\033[90m",
'CYAN' => "\033[36m",
'MAGENTA' => "\033[35m",
'RESET' => "\033[0m",
];
/** Cached terminal-colour detection result. */
private ?bool $cliColorEnabled = null;
// -------------------------------------------------------------------------
// Colour helper
// -------------------------------------------------------------------------
/**
* Return whether ANSI colour output should be used.
*/
protected function isColorEnabled(): bool
{
if ($this->cliColorEnabled !== null) {
return $this->cliColorEnabled;
}
if (isset($this->options['no-color']) || getenv('NO_COLOR') !== false) {
return $this->cliColorEnabled = false;
}
return $this->cliColorEnabled = stream_isatty(STDOUT);
}
/**
* Wrap text in an ANSI colour; returns plain text when colour is off.
*/
protected function colorize(string $code, string $text): string
{
if (!$this->isColorEnabled()) {
return $text;
}
return $code . $text . self::ANSI['RESET'];
}
/**
* Return the terminal width (defaults to 80).
*/
protected function termWidth(): int
{
$cols = (int) getenv('COLUMNS');
return ($cols > 40) ? $cols : 80;
}
// -------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------
/**
* Print a levelled message to stdout.
*
* @param string $message Text to display.
* @param string $level One of INFO, SUCCESS, WARNING, ERROR.
*/
protected function log(string $message, string $level = 'INFO'): void
{
$level = strtoupper($level);
$color = self::ANSI[$level] ?? '';
$icon = self::ICONS[$level] ?? self::ICONS['INFO'];
$reset = self::ANSI['RESET'];
if ($this->isColorEnabled()) {
$badge = "{$color}" . self::ANSI['BOLD'] . "[{$level}]{$reset}";
$icon = "{$color}{$icon}{$reset}";
} else {
$badge = "[{$level}]";
}
$line = "{$icon} {$badge} {$message}\n";
if ($level === 'ERROR') {
fwrite(STDERR, $line);
} else {
echo $line;
}
}
/**
* Print verbose message (only when --verbose or -v is set).
*
* @param string $message Text to display.
*/
protected function verbose(string $message): void
{
if ($this->verbose) {
$this->log($message, 'INFO');
}
}
/**
* Print error message and exit.
*
* @param string $message Error text.
* @param int $exitCode Process exit code.
* @return never
*/
protected function error(string $message, int $exitCode = 1): never
{
$this->log($message, 'ERROR');
exit($exitCode);
}
/**
* Print success message.
*
* @param string $message Text to display.
*/
protected function success(string $message): void
{
$this->log($message, 'SUCCESS');
}
/**
* Print warning message.
*
* @param string $message Text to display.
*/
protected function warning(string $message): void
{
$this->log($message, 'WARNING');
}
/**
* Ask user for confirmation (reads from stdin).
*
* @param string $question Prompt text.
* @return bool True when user enters 'y'.
*/
protected function confirm(string $question): bool
{
echo "{$question} [y/N]: ";
$handle = fopen('php://stdin', 'r');
$line = fgets($handle);
fclose($handle);
return strtolower(trim((string) $line)) === 'y';
}
/**
* Print usage/help information.
*/
abstract protected function showHelp(): void;
/**
* Main execution method.
*
* @return int Exit code (0 = success).
*/
abstract protected function execute(): int;
/**
* Run the application, dispatching --help and catching exceptions.
*
* @return int Exit code.
*/
public function run(): int
{
if ($this->hasOption('help') || $this->hasOption('h')) {
$this->showHelp();
return 0;
}
if ($this->dryRun) {
$this->warning('Dry-run mode enabled - no changes will be made');
}
try {
return $this->execute();
} catch (\Exception $e) {
$this->log('Error: ' . $e->getMessage(), 'ERROR');
return 1;
}
}
/**
* Execute a shell command and return its output.
*
* In dry-run mode the command is logged but not executed.
*
* @param string $command Shell command string.
* @param array|null &$output Lines of output (populated by reference).
* @param int|null &$exitCode Process exit code (populated by reference).
* @return string Last line of output.
*/
protected function exec(string $command, ?array &$output = null, ?int &$exitCode = null): string
{
$this->verbose("Executing: {$command}");
if ($this->dryRun) {
$this->log("[DRY-RUN] Would execute: {$command}");
return '';
}
$result = exec($command, $output, $exitCode);
if ($exitCode !== 0) {
$this->warning("Command failed with exit code {$exitCode}");
}
return (string) $result;
}
/**
* Run command and return success status.
*
* @param string $command Shell command string.
* @return bool True when exit code is 0.
*/
protected function runCommand(string $command): bool
{
$exitCode = 0;
$this->exec($command, $output, $exitCode);
return $exitCode === 0;
}
/**
* Read file contents.
*
* @param string $path File path to read.
* @return string File contents.
* @throws \RuntimeException When file does not exist.
*/
protected function readFile(string $path): string
{
if (!file_exists($path)) {
throw new \RuntimeException("File not found: {$path}");
}
return (string) file_get_contents($path);
}
/**
* Write file contents, creating parent directories as needed.
*
* In dry-run mode the write is logged but not performed.
*
* @param string $path Destination file path.
* @param string $content Content to write.
*/
protected function writeFile(string $path, string $content): void
{
if ($this->dryRun) {
$this->log("[DRY-RUN] Would write to: {$path}");
return;
}
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($path, $content);
$this->verbose("Written: {$path}");
}
/**
* Copy a file, creating the destination directory if needed.
*
* In dry-run mode the copy is logged but not performed.
*
* @param string $source Source file path.
* @param string $dest Destination file path.
*/
protected function copyFile(string $source, string $dest): void
{
if ($this->dryRun) {
$this->log("[DRY-RUN] Would copy: {$source} -> {$dest}");
return;
}
$dir = dirname($dest);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
copy($source, $dest);
$this->verbose("Copied: {$source} -> {$dest}");
}
/**
* Delete a file or directory.
*
* In dry-run mode the deletion is logged but not performed.
*
* @param string $path Path to delete.
*/
protected function delete(string $path): void
{
if ($this->dryRun) {
$this->log("[DRY-RUN] Would delete: {$path}");
return;
}
if (is_dir($path)) {
$this->deleteDirectory($path);
} elseif (file_exists($path)) {
unlink($path);
}
$this->verbose("Deleted: {$path}");
}
// -------------------------------------------------------------------------
// Console graphics — visual primitives
// -------------------------------------------------------------------------
/**
* Print a script header banner with name and description.
*
* @param string $name Script name.
* @param string $desc One-line description.
* @param string $ver Version string.
*/
protected function printBanner(string $name, string $desc = '', string $ver = '04.00.15'): void
{
$w = min($this->termWidth(), 70);
$inner = $w - 2;
$h = "\u{2500}";
$v = "\u{2502}";
$tl = "\u{250C}";
$tr = "\u{2510}";
$bl = "\u{2514}";
$br = "\u{2518}";
$title = " {$name} v{$ver}";
$titlePad = str_pad($title, $inner);
$descPad = ($desc !== '') ? str_pad(" {$desc}", $inner) : null;
echo "\n";
echo $this->colorize(self::ANSI['CYAN'], $tl . str_repeat($h, $inner) . $tr) . "\n";
echo $this->colorize(self::ANSI['CYAN'], $v)
. $this->colorize(self::ANSI['BOLD'], $titlePad)
. $this->colorize(self::ANSI['CYAN'], $v) . "\n";
if ($descPad !== null) {
echo $this->colorize(self::ANSI['CYAN'], $v)
. $this->colorize(self::ANSI['DIM'], $descPad)
. $this->colorize(self::ANSI['CYAN'], $v) . "\n";
}
echo $this->colorize(self::ANSI['CYAN'], $bl . str_repeat($h, $inner) . $br) . "\n\n";
}
/**
* Print a section header rule.
*
* Output example: ── Section Title ──────────────────────────
*/
protected function section(string $title): void
{
$h = "\u{2500}";
$w = $this->termWidth();
$text = " {$title} ";
$fill = max(0, $w - strlen($text) - 4);
echo "\n";
echo $this->colorize(self::ANSI['CYAN'],
str_repeat($h, 2) . $text . str_repeat($h, $fill)) . "\n\n";
}
/**
* Print a plain horizontal divider.
*/
protected function printDivider(): void
{
echo $this->colorize(self::ANSI['DIM'],
str_repeat("\u{2500}", $this->termWidth())) . "\n";
}
/**
* Print a single pass/fail status line.
*
* @param bool $passed Whether the check passed.
* @param string $label Check description.
* @param string $detail Optional detail in dim text.
*/
protected function statusLine(bool $passed, string $label, string $detail = ''): void
{
[$icon, $color] = $passed
? ["\u{2713}", self::ANSI['SUCCESS']]
: ["\u{2717}", self::ANSI['ERROR']];
$suffix = ($detail !== '')
? ' ' . $this->colorize(self::ANSI['DIM'], "{$detail}")
: '';
echo ' ' . $this->colorize($color . self::ANSI['BOLD'], $icon)
. ' ' . $label . $suffix . "\n";
}
/**
* Render an in-place progress bar.
*
* @param int $current Items done.
* @param int $total Total items.
* @param string $label Optional trailing label.
* @param bool $newline Finish bar with newline.
*/
protected function progress(int $current, int $total, string $label = '', bool $newline = false): void
{
$barWidth = min(30, $this->termWidth() - 22);
$filled = ($total > 0) ? (int) round($barWidth * $current / $total) : 0;
$pct = ($total > 0) ? (int) round(100 * $current / $total) : 0;
$bar = $this->colorize(self::ANSI['SUCCESS'], str_repeat("\u{2588}", $filled))
. $this->colorize(self::ANSI['DIM'], str_repeat("\u{2591}", $barWidth - $filled));
$line = sprintf(
' [%s] %s %s%s',
$bar,
$this->colorize(self::ANSI['BOLD'], sprintf('%3d%%', $pct)),
$this->colorize(self::ANSI['DIM'], "({$current}/{$total})"),
($label !== '') ? " {$label}" : ''
);
echo $newline ? "\r{$line}\n" : "\r{$line}";
}
/**
* Print a bordered summary box.
*
* @param array<string, string|int|float> $rows Label => value pairs.
* @param bool|null $passed Border colour: green/red/cyan.
*/
protected function printSummaryBox(array $rows, ?bool $passed = null): void
{
$color = match ($passed) {
true => self::ANSI['SUCCESS'],
false => self::ANSI['ERROR'],
default => self::ANSI['CYAN'],
};
$h = "\u{2500}";
$v = "\u{2502}";
$tl = "\u{250C}";
$tr = "\u{2510}";
$bl = "\u{2514}";
$br = "\u{2518}";
$maxKey = max(array_map('strlen', array_keys($rows)));
$inner = $maxKey + 20;
echo "\n";
echo $this->colorize($color, $tl . str_repeat($h, $inner) . $tr) . "\n";
foreach ($rows as $label => $value) {
$valStr = (string) $value;
$padding = $inner - strlen($label) - strlen($valStr) - 4;
$row = ' ' . $this->colorize(self::ANSI['BOLD'], $label)
. str_repeat(' ', max(1, $padding)) . $valStr . ' ';
echo $this->colorize($color, $v) . $row . $this->colorize($color, $v) . "\n";
}
echo $this->colorize($color, $bl . str_repeat($h, $inner) . $br) . "\n\n";
}
// -------------------------------------------------------------------------
// Recursively delete a directory (private — used by delete())
// -------------------------------------------------------------------------
/**
* Recursively delete a directory and all its contents.
*
* @param string $dir Directory path.
*/
private function deleteDirectory(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff((array) scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = "{$dir}/{$file}";
is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
}
rmdir($dir);
}
}
+298
View File
@@ -0,0 +1,298 @@
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Lib
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /api/lib/Common.php
* VERSION: 04.06.00
* BRIEF: Common utility functions for api/ scripts
* NOTE: Version format used throughout is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04).
* All version regex patterns enforce exactly two digits per component by design.
*/
declare(strict_types=1);
/**
* Common utility class for Moko Consulting scripts.
*
* Provides static helpers for logging, git introspection, and guards.
*/
class Common
{
/**
* Fallback version used when README.md cannot be parsed.
* NOTE: Kept in sync with _FALLBACK_VERSION in the original common.sh.
* Update this constant when the minimum supported baseline version changes.
*/
const FALLBACK_VERSION = '04.00.00';
const REPO_URL = 'https://github.com/mokoconsulting-tech/MokoStandards';
const COPYRIGHT = 'Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>';
const LICENSE = 'GPL-3.0-or-later';
// Exit codes
const EXIT_SUCCESS = 0;
const EXIT_ERROR = 1;
const EXIT_INVALID_ARGS = 2;
const EXIT_NOT_FOUND = 3;
const EXIT_PERMISSION = 4;
// ── Logging ───────────────────────────────────────────────────────────────
/**
* Print an informational message.
*
* @param string $message Text to display.
*/
public static function info(string $message): void
{
echo '️ ' . $message . "\n";
}
/**
* Print a success message.
*
* @param string $message Text to display.
*/
public static function success(string $message): void
{
echo '✅ ' . $message . "\n";
}
/**
* Print a warning message.
*
* @param string $message Text to display.
*/
public static function warn(string $message): void
{
echo '⚠️ ' . $message . "\n";
}
/**
* Print an error message to STDERR.
*
* @param string $message Error text.
*/
public static function error(string $message): void
{
fwrite(STDERR, '❌ ' . $message . "\n");
}
/**
* Print a fatal error to STDERR and exit.
*
* @param string $message Error text.
* @param int $exitCode One of the EXIT_* constants.
* @return never
*/
public static function fatal(string $message, int $exitCode = self::EXIT_ERROR): never
{
fwrite(STDERR, '❌ ' . $message . "\n");
exit($exitCode);
}
/**
* Print a debug message to STDERR when the DEBUG env var is set.
*
* @param string $message Debug text.
*/
public static function debug(string $message): void
{
if (!empty($_SERVER['DEBUG'] ?? getenv('DEBUG'))) {
fwrite(STDERR, '🔍 ' . $message . "\n");
}
}
/**
* Print a plain message to stdout.
*
* @param string $message Text to display.
*/
public static function plain(string $message): void
{
echo $message . "\n";
}
// ── Guards ────────────────────────────────────────────────────────────────
/**
* Abort if a command is not available on PATH.
*
* @param string $cmd Command name (e.g. 'git').
* @param string $description Human-readable description for the error message.
*/
public static function requireCommand(string $cmd, string $description = ''): void
{
$which = trim((string) shell_exec('command -v ' . escapeshellarg($cmd) . ' 2>/dev/null'));
if ($which === '') {
$msg = $description !== '' ? $description : "Command required: {$cmd}";
self::fatal($msg, self::EXIT_NOT_FOUND);
}
}
/**
* Abort if a file does not exist.
*
* @param string $path Absolute or relative file path.
* @param string $description Human-readable label used in the error message.
*/
public static function requireFile(string $path, string $description = 'File'): void
{
if (!is_file($path)) {
self::fatal("{$description} not found: {$path}", self::EXIT_NOT_FOUND);
}
}
/**
* Abort if a directory does not exist.
*
* @param string $path Absolute or relative directory path.
* @param string $description Human-readable label used in the error message.
*/
public static function requireDir(string $path, string $description = 'Directory'): void
{
if (!is_dir($path)) {
self::fatal("{$description} not found: {$path}", self::EXIT_NOT_FOUND);
}
}
// ── Repository utilities ──────────────────────────────────────────────────
/**
* Return the absolute path to the repository root by walking up from cwd.
*
* @throws \RuntimeException When no .git directory is found.
* @return string Absolute path (no trailing slash).
*/
public static function getRepoRoot(): string
{
$dir = (string) getcwd();
while ($dir !== '/') {
if (is_dir($dir . '/.git')) {
return $dir;
}
$dir = dirname($dir);
}
self::fatal('Not in a git repository', self::EXIT_ERROR);
}
/**
* Return the current git branch name (or "unknown").
*
* @return string Branch name.
*/
public static function getGitBranch(): string
{
$branch = trim((string) shell_exec('git rev-parse --abbrev-ref HEAD 2>/dev/null'));
return $branch !== '' ? $branch : 'unknown';
}
/**
* Return the current full git commit hash (or "unknown").
*
* @return string Full commit SHA.
*/
public static function getGitCommit(): string
{
$hash = trim((string) shell_exec('git rev-parse HEAD 2>/dev/null'));
return $hash !== '' ? $hash : 'unknown';
}
/**
* Return the short git commit hash (or "unknown").
*
* @return string Short commit SHA.
*/
public static function getGitCommitShort(): string
{
$hash = trim((string) shell_exec('git rev-parse --short HEAD 2>/dev/null'));
return $hash !== '' ? $hash : 'unknown';
}
/**
* Return true when the git working directory is clean.
*
* @return bool True if no uncommitted changes.
*/
public static function isGitClean(): bool
{
return trim((string) shell_exec('git status --porcelain 2>/dev/null')) === '';
}
/**
* Return true when the current directory is inside a git repository.
*
* @return bool True if inside a git repo.
*/
public static function isGitRepo(): bool
{
exec('git rev-parse --git-dir 2>/dev/null', $out, $code);
return $code === 0;
}
// ── Path utilities ────────────────────────────────────────────────────────
/**
* Return the path relative to the repository root, prefixed with '/'.
*
* @param string $absolutePath Absolute filesystem path.
* @return string Repo-relative path starting with '/'.
*/
public static function getRelativePath(string $absolutePath): string
{
$root = self::getRepoRoot();
$rel = str_starts_with($absolutePath, $root)
? substr($absolutePath, strlen($root))
: $absolutePath;
return '/' . ltrim($rel, '/');
}
/**
* Create a directory (and parents) if it does not already exist.
*
* @param string $path Directory path to ensure.
* @param string $description Human-readable label for log output.
*/
public static function ensureDir(string $path, string $description = 'Directory'): void
{
if (!is_dir($path)) {
mkdir($path, 0755, true);
self::info("Created {$description}: {$path}");
}
}
// ── Version helpers ───────────────────────────────────────────────────────
/**
* Read the VERSION from the FILE INFORMATION block in README.md.
*
* Searches upward from cwd for the repo root, then reads README.md.
* Falls back to FALLBACK_VERSION when the file is absent or unparseable.
*
* @return string Zero-padded semver string, e.g. "04.00.04".
*/
public static function getVersionFromReadme(): string
{
try {
$root = self::getRepoRoot();
$readme = $root . '/README.md';
if (!is_file($readme)) {
return self::FALLBACK_VERSION;
}
$content = file_get_contents($readme);
if (preg_match('/^\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', (string) $content, $m)) {
return $m[1];
}
} catch (\Throwable $e) {
// Fall through to fallback
}
return self::FALLBACK_VERSION;
}
}
+259
View File
@@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
namespace MokoEnterprise;
/**
* Abstract base class for project type plugins
*
* Provides common functionality for all project type plugins
*
* @package MokoStandards\Enterprise
* @version 1.0.0
*/
abstract class AbstractProjectPlugin implements ProjectPluginInterface
{
/** @var AuditLogger */
protected $logger;
/** @var MetricsCollector */
protected $metricsCollector;
/** @var array Plugin configuration */
protected $config;
/**
* Constructor
*
* @param AuditLogger|null $logger Optional audit logger
* @param MetricsCollector|null $metricsCollector Optional metrics collector
* @param array $config Plugin configuration
*/
public function __construct(
?AuditLogger $logger = null,
?MetricsCollector $metricsCollector = null,
array $config = []
) {
$this->logger = $logger ?? new AuditLogger('project_plugin');
$this->metricsCollector = $metricsCollector ?? new MetricsCollector();
$this->config = $config;
}
/**
* {@inheritdoc}
*/
abstract public function getProjectType(): string;
/**
* {@inheritdoc}
*/
abstract public function getPluginName(): string;
/**
* {@inheritdoc}
*/
public function getPluginVersion(): string
{
return '1.0.0';
}
/**
* {@inheritdoc}
*/
abstract public function validateProject(array $config, string $projectPath): array;
/**
* {@inheritdoc}
*/
abstract public function collectMetrics(string $projectPath, array $config): array;
/**
* {@inheritdoc}
*/
abstract public function healthCheck(string $projectPath, array $config): array;
/**
* {@inheritdoc}
*/
abstract public function getRequiredFiles(): array;
/**
* {@inheritdoc}
*/
abstract public function getRecommendedFiles(): array;
/**
* {@inheritdoc}
*/
abstract public function getConfigSchema(): array;
/**
* {@inheritdoc}
*/
abstract public function getBestPractices(): array;
/**
* {@inheritdoc}
*/
public function checkReadiness(string $projectPath, array $config): array
{
$validation = $this->validateProject($config, $projectPath);
$health = $this->healthCheck($projectPath, $config);
$blockers = array_merge(
$validation['errors'] ?? [],
array_filter($health['issues'] ?? [], function ($issue) {
return ($issue['severity'] ?? '') === 'critical';
})
);
$warnings = array_merge(
$validation['warnings'] ?? [],
array_filter($health['issues'] ?? [], function ($issue) {
return ($issue['severity'] ?? '') === 'warning';
})
);
return [
'ready' => empty($blockers),
'blockers' => $blockers,
'warnings' => $warnings,
'score' => $health['score'] ?? 0,
];
}
/**
* {@inheritdoc}
*/
public function getCommands(): array
{
// Default: no custom commands
return [];
}
/**
* {@inheritdoc}
*/
public function initializeProject(string $projectPath, array $options = []): array
{
// Default: no initialization
return [
'success' => true,
'message' => 'No initialization required for ' . $this->getProjectType(),
'files_created' => [],
];
}
/**
* Check if a file exists in the project
*
* @param string $projectPath Project directory path
* @param string $filePath Relative file path
* @return bool True if file exists
*/
protected function fileExists(string $projectPath, string $filePath): bool
{
return file_exists(rtrim($projectPath, '/') . '/' . ltrim($filePath, '/'));
}
/**
* Read a file from the project
*
* @param string $projectPath Project directory path
* @param string $filePath Relative file path
* @return string|null File contents or null if not found
*/
protected function readFile(string $projectPath, string $filePath): ?string
{
$fullPath = rtrim($projectPath, '/') . '/' . ltrim($filePath, '/');
return file_exists($fullPath) ? file_get_contents($fullPath) : null;
}
/**
* Check if files match a pattern in the project
*
* @param string $projectPath Project directory path
* @param string $pattern Glob pattern
* @return array Matching file paths
*/
protected function findFiles(string $projectPath, string $pattern): array
{
$fullPattern = rtrim($projectPath, '/') . '/' . ltrim($pattern, '/');
$matches = glob($fullPattern);
return is_array($matches) ? $matches : [];
}
/**
* Count files matching a pattern
*
* @param string $projectPath Project directory path
* @param string $pattern Glob pattern
* @return int Number of matching files
*/
protected function countFiles(string $projectPath, string $pattern): int
{
return count($this->findFiles($projectPath, $pattern));
}
/**
* Parse JSON file
*
* @param string $projectPath Project directory path
* @param string $filePath Relative file path
* @return array|null Parsed JSON data or null on error
*/
protected function parseJsonFile(string $projectPath, string $filePath): ?array
{
$content = $this->readFile($projectPath, $filePath);
if ($content === null) {
return null;
}
$data = json_decode($content, true);
return json_last_error() === JSON_ERROR_NONE ? $data : null;
}
/**
* Log plugin activity
*
* @param string $message Log message
* @param string $level Log level (info, warning, error)
* @param array $context Additional context
* @return void
*/
protected function log(string $message, string $level = 'info', array $context = []): void
{
$context['plugin'] = $this->getPluginName();
$context['project_type'] = $this->getProjectType();
switch ($level) {
case 'error':
$this->logger->logError($message, $context);
break;
case 'warning':
$this->logger->logWarning($message, $context);
break;
default:
$this->logger->logInfo($message, $context);
break;
}
}
/**
* Record metrics
*
* @param string $category Metric category
* @param string $name Metric name
* @param mixed $value Metric value
* @param array $tags Optional tags
* @return void
*/
protected function recordMetric(string $category, string $name, $value, array $tags = []): void
{
$tags['plugin'] = $this->getPluginName();
$tags['project_type'] = $this->getProjectType();
$this->metricsCollector->record($category, $name, $value, $tags);
}
}
+525
View File
@@ -0,0 +1,525 @@
<?php
declare(strict_types=1);
/**
* API Client Library - Rate-limited, resilient API interactions.
*
* This class provides enterprise-grade API client capabilities with:
* - Automatic rate limiting with backoff
* - Retry logic with exponential backoff
* - Request tracking and throttling
* - Response caching
* - Circuit breaker pattern
* - Health monitoring
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* @package MokoStandards\Enterprise
* @version 04.00.04
* @author MokoStandards Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use DateTime;
use DateTimeZone;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
/**
* Circuit breaker states.
*/
enum CircuitState: string
{
case CLOSED = 'closed'; // Normal operation
case OPEN = 'open'; // Failures exceeded threshold, blocking requests
case HALF_OPEN = 'half_open'; // Testing if service recovered
}
/**
* Exception raised when rate limit is exceeded.
*/
class RateLimitExceeded extends RuntimeException
{
}
/**
* Exception raised when circuit breaker is open.
*/
class CircuitBreakerOpen extends RuntimeException
{
}
/**
* Enterprise API client with rate limiting, retry logic, and circuit breaker.
*
* Features:
* - Rate limiting with configurable limits
* - Exponential backoff retry
* - Response caching with TTL
* - Circuit breaker pattern
* - Request tracking and metrics
*
* Example:
* ```php
* $client = new ApiClient(
* baseUrl: 'https://api.github.com',
* authToken: $token,
* maxRequestsPerHour: 5000
* );
* $response = $client->get('/repos/owner/repo');
* ```
*/
class ApiClient
{
private Client $httpClient;
private string $baseUrl;
private ?string $authToken;
private int $maxRequestsPerHour;
private int $maxRetries;
private float $retryBackoffFactor;
private int $cacheTtlSeconds;
private int $circuitBreakerThreshold;
private int $circuitBreakerTimeout;
private bool $enableCaching;
private string $userAgent;
private string $authScheme;
/** @var array<int> Request timestamps for rate limiting */
private array $requestTimestamps = [];
/** @var CacheItemPoolInterface Response cache */
private CacheItemPoolInterface $cache;
/** Circuit breaker state */
private CircuitState $circuitState = CircuitState::CLOSED;
/** Circuit breaker failure count */
private int $circuitFailureCount = 0;
/** Circuit breaker last failure time */
private ?DateTime $circuitLastFailure = null;
/** @var array<string, mixed> Request metrics */
private array $metrics = [
'total_requests' => 0,
'successful_requests' => 0,
'failed_requests' => 0,
'cache_hits' => 0,
'cache_misses' => 0,
'rate_limit_waits' => 0,
'circuit_breaker_trips' => 0,
];
public const VERSION = '04.06.00';
/**
* Initialize API client.
*
* @param string $baseUrl Base URL for API (e.g., 'https://api.github.com')
* @param string|null $authToken Authentication token (optional)
* @param int $maxRequestsPerHour Maximum requests per hour
* @param int $maxRetries Maximum retry attempts for failed requests
* @param float $retryBackoffFactor Exponential backoff factor
* @param int $cacheTtlSeconds Cache time-to-live in seconds
* @param int $circuitBreakerThreshold Failures before opening circuit
* @param int $circuitBreakerTimeout Seconds before attempting recovery
* @param bool $enableCaching Enable response caching
* @param string $userAgent User agent string
* @param LoggerInterface|null $logger Optional logger
* @param string $authScheme Authorization scheme ('Bearer' for GitHub, 'token' for Gitea)
*/
public function __construct(
string $baseUrl,
?string $authToken = null,
int $maxRequestsPerHour = 5000,
int $maxRetries = 3,
float $retryBackoffFactor = 2.0,
int $cacheTtlSeconds = 300,
int $circuitBreakerThreshold = 5,
int $circuitBreakerTimeout = 60,
bool $enableCaching = true,
string $userAgent = 'MokoStandards-APIClient/1.0',
?LoggerInterface $logger = null,
string $authScheme = 'Bearer'
) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->authToken = $authToken;
$this->maxRequestsPerHour = $maxRequestsPerHour;
$this->maxRetries = $maxRetries;
$this->retryBackoffFactor = $retryBackoffFactor;
$this->cacheTtlSeconds = $cacheTtlSeconds;
$this->circuitBreakerThreshold = $circuitBreakerThreshold;
$this->circuitBreakerTimeout = $circuitBreakerTimeout;
$this->enableCaching = $enableCaching;
$this->userAgent = $userAgent;
$this->authScheme = $authScheme;
// Initialize HTTP client
$this->httpClient = new Client([
'base_uri' => $this->baseUrl,
'timeout' => 30,
'headers' => [
'User-Agent' => $this->userAgent,
'Accept' => 'application/json',
],
]);
// Initialize cache
$cacheDir = sys_get_temp_dir() . '/mokostandards/api_cache';
$this->cache = new FilesystemAdapter('api_client', $this->cacheTtlSeconds, $cacheDir);
}
/**
* Perform GET request.
*
* @param string $endpoint API endpoint
* @param array<string, mixed> $params Query parameters
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
public function get(string $endpoint, array $params = []): array
{
return $this->request('GET', $endpoint, ['query' => $params]);
}
/**
* Perform POST request.
*
* @param string $endpoint API endpoint
* @param array<string, mixed> $data Request body data
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
public function post(string $endpoint, array $data = []): array
{
return $this->request('POST', $endpoint, ['json' => $data]);
}
/**
* Perform PUT request.
*
* @param string $endpoint API endpoint
* @param array<string, mixed> $data Request body data
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
public function put(string $endpoint, array $data = []): array
{
return $this->request('PUT', $endpoint, ['json' => $data]);
}
/**
* Perform PATCH request.
*
* @param string $endpoint API endpoint
* @param array<string, mixed> $data Request body
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
public function patch(string $endpoint, array $data = []): array
{
return $this->request('PATCH', $endpoint, ['json' => $data]);
}
/**
* Perform DELETE request.
*
* @param string $endpoint API endpoint
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
public function delete(string $endpoint): array
{
return $this->request('DELETE', $endpoint);
}
/**
* Perform HTTP request with rate limiting, caching, and resilience.
*
* @param string $method HTTP method
* @param string $endpoint API endpoint
* @param array<string, mixed> $options Request options
* @return array<string, mixed> Response data
* @throws RateLimitExceeded
* @throws CircuitBreakerOpen
*/
private function request(string $method, string $endpoint, array $options = []): array
{
$this->metrics['total_requests']++;
// Check circuit breaker
$this->checkCircuitBreaker();
// Generate cache key
$cacheKey = $this->getCacheKey($method, $endpoint, $options);
// Check cache for GET requests
if ($method === 'GET' && $this->enableCaching) {
$cachedItem = $this->cache->getItem($cacheKey);
if ($cachedItem->isHit()) {
$this->metrics['cache_hits']++;
return $cachedItem->get();
}
$this->metrics['cache_misses']++;
}
// Check rate limit
$this->checkRateLimit();
// Add authentication
if ($this->authToken) {
$options['headers']['Authorization'] = $this->authScheme . ' ' . $this->authToken;
}
// Perform request with retry logic
$response = $this->requestWithRetry($method, $endpoint, $options);
// Cache successful GET responses
if ($method === 'GET' && $this->enableCaching) {
$cachedItem = $this->cache->getItem($cacheKey);
$cachedItem->set($response);
$cachedItem->expiresAfter($this->cacheTtlSeconds);
$this->cache->save($cachedItem);
}
return $response;
}
/**
* Perform request with exponential backoff retry.
*
* @param string $method HTTP method
* @param string $endpoint API endpoint
* @param array<string, mixed> $options Request options
* @return array<string, mixed> Response data
* @throws RuntimeException
*/
private function requestWithRetry(string $method, string $endpoint, array $options): array
{
$attempt = 0;
$lastException = null;
while ($attempt < $this->maxRetries) {
try {
$response = $this->httpClient->request($method, $endpoint, $options);
$body = (string) $response->getBody();
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$this->metrics['successful_requests']++;
$this->recordSuccess();
return $data;
} catch (GuzzleException $e) {
$lastException = $e;
$attempt++;
// Do not retry 4xx client errors — they indicate a definitive
// "not found / forbidden / conflict" response, not a transient fault.
// Retrying wastes time and inflates the circuit-breaker failure count.
$statusCode = ($e instanceof RequestException && $e->hasResponse())
? $e->getResponse()->getStatusCode()
: 0;
if ($statusCode >= 400 && $statusCode < 500) {
$this->recordFailure();
break;
}
if ($attempt < $this->maxRetries) {
$waitTime = $this->retryBackoffFactor ** $attempt;
usleep((int) ($waitTime * 1000000));
}
$this->recordFailure();
}
}
$this->metrics['failed_requests']++;
throw new RuntimeException(
"Request failed after {$this->maxRetries} attempts: " . ($lastException?->getMessage() ?? 'Unknown error')
);
}
/**
* Check and enforce rate limit.
*
* @throws RateLimitExceeded
*/
private function checkRateLimit(): void
{
$now = time();
$oneHourAgo = $now - 3600;
// Remove old timestamps
$this->requestTimestamps = array_filter(
$this->requestTimestamps,
fn($ts) => $ts > $oneHourAgo
);
// Check if limit exceeded
if (count($this->requestTimestamps) >= $this->maxRequestsPerHour) {
$oldestTimestamp = min($this->requestTimestamps);
$waitTime = 3600 - ($now - $oldestTimestamp);
$this->metrics['rate_limit_waits']++;
throw new RateLimitExceeded(
"Rate limit of {$this->maxRequestsPerHour} requests/hour exceeded. Wait {$waitTime} seconds."
);
}
// Record this request
$this->requestTimestamps[] = $now;
}
/**
* Check circuit breaker state.
*
* @throws CircuitBreakerOpen
*/
private function checkCircuitBreaker(): void
{
if ($this->circuitState === CircuitState::CLOSED) {
return;
}
if ($this->circuitState === CircuitState::OPEN) {
$now = new DateTime('now', new DateTimeZone('UTC'));
$timeSinceFailure = $now->getTimestamp() - $this->circuitLastFailure?->getTimestamp();
if ($timeSinceFailure >= $this->circuitBreakerTimeout) {
// Try half-open state
$this->circuitState = CircuitState::HALF_OPEN;
$this->circuitFailureCount = 0;
} else {
throw new CircuitBreakerOpen(
"Circuit breaker is open. Service unavailable. Retry in " .
($this->circuitBreakerTimeout - $timeSinceFailure) . " seconds."
);
}
}
}
/**
* Record successful request for circuit breaker.
*/
private function recordSuccess(): void
{
if ($this->circuitState === CircuitState::HALF_OPEN) {
// Service recovered, close circuit
$this->circuitState = CircuitState::CLOSED;
}
// Reset failure count on any success so only truly consecutive failures
// trip the breaker. Without this, expected 404s (e.g. checking if a branch
// or file exists before creating it) accumulate failure_count even when
// subsequent calls succeed, causing premature circuit-open events.
$this->circuitFailureCount = 0;
}
/**
* Record failed request for circuit breaker.
*/
private function recordFailure(): void
{
$this->circuitFailureCount++;
$this->circuitLastFailure = new DateTime('now', new DateTimeZone('UTC'));
if ($this->circuitFailureCount >= $this->circuitBreakerThreshold) {
$this->circuitState = CircuitState::OPEN;
$this->metrics['circuit_breaker_trips']++;
}
}
/**
* Generate cache key for request.
*
* @param string $method HTTP method
* @param string $endpoint API endpoint
* @param array<string, mixed> $options Request options
* @return string Cache key
*/
private function getCacheKey(string $method, string $endpoint, array $options): string
{
$key = $method . '_' . $endpoint;
if (isset($options['query'])) {
$key .= '_' . http_build_query($options['query']);
}
return md5($key);
}
/**
* Get current metrics.
*
* @return array<string, mixed> Metrics data
*/
public function getMetrics(): array
{
return array_merge($this->metrics, [
'circuit_state' => $this->circuitState->value,
'circuit_failure_count' => $this->circuitFailureCount,
'rate_limit_remaining' => max(0, $this->maxRequestsPerHour - count($this->requestTimestamps)),
]);
}
/**
* Get current circuit breaker state.
*
* @return string Circuit state ('CLOSED', 'OPEN', or 'HALF_OPEN')
*/
public function getCircuitState(): string
{
return strtoupper($this->circuitState->value);
}
/**
* Simulate a failure for testing circuit breaker functionality.
* This method is intended for testing only and checks for test environment.
*
* @throws RuntimeException If not in test environment, or always to simulate failure
*/
public function simulateFailure(): void
{
// Only allow in test/development environments
$allowedEnvs = ['test', 'testing', 'development', 'dev', 'ci'];
$currentEnv = getenv('APP_ENV') ?: $_ENV['APP_ENV'] ?? getenv('ENVIRONMENT') ?: $_ENV['ENVIRONMENT'] ?? $_SERVER['APP_ENV'] ?? 'production';
if (!in_array(strtolower($currentEnv), $allowedEnvs, true)) {
throw new RuntimeException('simulateFailure() can only be called in test environments');
}
$this->recordFailure();
throw new RuntimeException('Simulated failure for circuit breaker testing');
}
/**
* Reset circuit breaker to closed state.
*/
public function resetCircuitBreaker(): void
{
$this->circuitState = CircuitState::CLOSED;
$this->circuitFailureCount = 0;
$this->circuitLastFailure = null;
}
/**
* Clear response cache.
*/
public function clearCache(): void
{
$this->cache->clear();
}
}
+459
View File
@@ -0,0 +1,459 @@
<?php
declare(strict_types=1);
/**
* Enterprise Audit Library - Structured audit logging for all operations.
*
* This class provides enterprise-grade audit logging capabilities with:
* - Structured JSON logging to audit database
* - Transaction ID tracking across operations
* - Security event logging (who, what, when, where)
* - Audit log rotation and archival
* - Compliance reporting capabilities
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* @package MokoStandards\Enterprise
* @version 04.00.04
* @author MokoStandards Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use DateTime;
use DateTimeZone;
use RuntimeException;
/**
* Enterprise audit logger with transaction tracking and structured logging.
*
* Features:
* - Transaction ID tracking
* - Security event logging
* - Structured JSON output
* - Automatic log rotation
* - Context manager support
*
* Example:
* ```php
* $logger = new AuditLogger('version_bump');
* $transaction = $logger->startTransaction('bump_version');
* $transaction->logEvent('version_change', ['old' => '1.0.0', 'new' => '1.1.0']);
* $transaction->logSecurityEvent('file_modified', ['file' => 'README.md']);
* $transaction->end();
* ```
*/
class AuditLogger
{
/** @var string Service name */
private string $service;
/** @var string User performing actions */
private string $user;
/** @var string Directory for audit logs */
private string $logDir;
/** @var bool Enable console output */
private bool $enableConsole;
/** @var bool Enable file logging */
private bool $enableFile;
/** @var int Maximum log file size in MB */
private int $maxLogSizeMb;
/** @var int Days to retain audit logs */
private int $retentionDays;
/** @var string Session ID */
private string $sessionId;
/** @var array Transaction stack */
private array $transactionStack = [];
/** @var string Version constant */
public const VERSION = '04.06.00';
/**
* Initialize audit logger.
*
* @param string $service Service name (e.g., 'version_bump', 'branch_cleanup')
* @param string|null $logDir Directory for audit logs (default: var/logs/audit/)
* @param string|null $user Username for audit trail (default: from environment)
* @param bool $enableConsole Output to console (default: true)
* @param bool $enableFile Write to file (default: true)
* @param int $maxLogSizeMb Maximum log file size before rotation
* @param int $retentionDays Days to retain audit logs
*/
public function __construct(
string $service,
?string $logDir = null,
?string $user = null,
bool $enableConsole = true,
bool $enableFile = true,
int $maxLogSizeMb = 10,
int $retentionDays = 90
) {
$this->service = $service;
$this->enableConsole = $enableConsole;
$this->enableFile = $enableFile;
$this->maxLogSizeMb = $maxLogSizeMb;
$this->retentionDays = $retentionDays;
// Determine user
$this->user = $user ?? $_SERVER['USER'] ?? $_SERVER['USERNAME'] ?? posix_getpwuid(posix_geteuid())['name'] ?? 'unknown';
// Set up log directory
if ($logDir === null) {
// Default to var/logs/audit/ in repository root
$repoRoot = dirname(__DIR__, 3);
$this->logDir = $repoRoot . '/var/logs/audit';
} else {
$this->logDir = $logDir;
}
// Create log directory if it doesn't exist
if ($this->enableFile && !is_dir($this->logDir)) {
if (!mkdir($this->logDir, 0755, true) && !is_dir($this->logDir)) {
throw new RuntimeException("Failed to create log directory: {$this->logDir}");
}
}
// Session ID for this logger instance
$this->sessionId = $this->generateSessionId();
// Log session start
$this->logSystemEvent('session_start', [
'service' => $this->service,
'user' => $this->user,
'session_id' => $this->sessionId,
]);
}
/**
* Generate unique session ID.
*
* @return string Session ID
*/
private function generateSessionId(): string
{
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->format('Ymd_His');
$uniqueId = substr(bin2hex(random_bytes(4)), 0, 8);
return "{$timestamp}_{$uniqueId}";
}
/**
* Generate unique transaction ID.
*
* @return string Transaction ID (UUID v4)
*/
private function generateTransactionId(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
/**
* Get current log file path with rotation support.
*
* @return string Log file path
*/
private function getLogFilePath(): string
{
$dateStr = (new DateTime('now', new DateTimeZone('UTC')))->format('Ymd');
return "{$this->logDir}/audit_{$this->service}_{$dateStr}.jsonl";
}
/**
* Check if log file should be rotated based on size.
*
* @param string $logFile Log file path
* @return bool True if should rotate
*/
private function shouldRotateLog(string $logFile): bool
{
if (!file_exists($logFile)) {
return false;
}
$sizeMb = filesize($logFile) / (1024 * 1024);
return $sizeMb >= $this->maxLogSizeMb;
}
/**
* Rotate log file if it exceeds size limit.
*
* @param string $logFile Log file path
*/
private function rotateLogIfNeeded(string $logFile): void
{
if ($this->shouldRotateLog($logFile)) {
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->format('His');
$rotatedFile = preg_replace('/\.jsonl$/', ".{$timestamp}.jsonl", $logFile);
rename($logFile, $rotatedFile);
}
}
/**
* Write log entry to file and/or console.
*
* @param array $entry Log entry data
*/
private function writeLogEntry(array $entry): void
{
// Add timestamp and session info
$entry['timestamp'] = (new DateTime('now', new DateTimeZone('UTC')))->format('c');
$entry['session_id'] = $this->sessionId;
$entry['service'] = $this->service;
$entry['user'] = $this->user;
// Console output
if ($this->enableConsole) {
$jsonOutput = json_encode($entry, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
echo "[AUDIT] {$jsonOutput}\n";
}
// File output
if ($this->enableFile) {
$logFile = $this->getLogFilePath();
$this->rotateLogIfNeeded($logFile);
$jsonLine = json_encode($entry, JSON_UNESCAPED_SLASHES) . "\n";
file_put_contents($logFile, $jsonLine, FILE_APPEND | LOCK_EX);
}
}
/**
* Log a system event.
*
* @param string $eventType Type of system event
* @param array $data Event data
*/
private function logSystemEvent(string $eventType, array $data = []): void
{
$entry = [
'event_type' => 'system',
'event_subtype' => $eventType,
'data' => $data,
];
$this->writeLogEntry($entry);
}
/**
* Start a new transaction.
*
* @param string $operation Operation name
* @param array $context Additional context data
* @return AuditTransaction Transaction object
*/
public function startTransaction(string $operation, array $context = []): AuditTransaction
{
$transactionId = $this->generateTransactionId();
$transaction = new AuditTransaction($this, $transactionId, $operation, $context);
$this->transactionStack[] = $transactionId;
return $transaction;
}
/**
* End a transaction.
*
* @param string $transactionId Transaction ID to end
*/
public function endTransaction(string $transactionId): void
{
$key = array_search($transactionId, $this->transactionStack, true);
if ($key !== false) {
unset($this->transactionStack[$key]);
}
}
/**
* Log an event within a transaction.
*
* @param string $transactionId Transaction ID
* @param string $eventType Event type
* @param array $data Event data
*/
public function logEvent(string $transactionId, string $eventType, array $data = []): void
{
$entry = [
'event_type' => 'audit',
'transaction_id' => $transactionId,
'event_subtype' => $eventType,
'data' => $data,
];
$this->writeLogEntry($entry);
}
/**
* Log a security event.
*
* @param string $transactionId Transaction ID
* @param string $eventType Security event type
* @param array $data Event data
*/
public function logSecurityEvent(string $transactionId, string $eventType, array $data = []): void
{
$entry = [
'event_type' => 'security',
'transaction_id' => $transactionId,
'event_subtype' => $eventType,
'severity' => $data['severity'] ?? 'medium',
'data' => $data,
];
$this->writeLogEntry($entry);
}
/**
* Log a message with specified level.
*
* @param string $level Log level (info, warning, error)
* @param string $message Message to log
* @param array $data Additional data
*/
private function logMessage(string $level, string $message, array $data = []): void
{
$entry = [
'event_type' => 'log',
'level' => $level,
'message' => $message,
'data' => $data,
];
$this->writeLogEntry($entry);
}
/**
* Log an informational message.
*
* @param string $message Message to log
* @param array $data Additional data
*/
public function logInfo(string $message, array $data = []): void
{
$this->logMessage('info', $message, $data);
}
/**
* Log a warning message.
*
* @param string $message Message to log
* @param array $data Additional data
*/
public function logWarning(string $message, array $data = []): void
{
$this->logMessage('warning', $message, $data);
}
/**
* Log an error message.
*
* @param string $message Message to log
* @param array $data Additional data
*/
public function logError(string $message, array $data = []): void
{
$this->logMessage('error', $message, $data);
}
}
/**
* Audit transaction context manager.
*/
class AuditTransaction
{
private AuditLogger $logger;
private string $transactionId;
private string $operation;
private array $context;
private float $startTime;
public function __construct(
AuditLogger $logger,
string $transactionId,
string $operation,
array $context = []
) {
$this->logger = $logger;
$this->transactionId = $transactionId;
$this->operation = $operation;
$this->context = $context;
$this->startTime = microtime(true);
// Log transaction start
$this->logger->logEvent($this->transactionId, 'transaction_start', [
'operation' => $this->operation,
'context' => $this->context,
]);
}
/**
* Get the transaction ID.
*
* @return string Transaction ID
*/
public function getTransactionId(): string
{
return $this->transactionId;
}
/**
* Log an event within this transaction.
*
* @param string $eventType Event type
* @param array $data Event data
*/
public function logEvent(string $eventType, array $data = []): void
{
$this->logger->logEvent($this->transactionId, $eventType, $data);
}
/**
* Log a security event within this transaction.
*
* @param string $eventType Security event type
* @param array $data Event data
*/
public function logSecurityEvent(string $eventType, array $data = []): void
{
$this->logger->logSecurityEvent($this->transactionId, $eventType, $data);
}
/**
* End the transaction.
*
* @param string|null $status Transaction status (success|failure)
* @param array $result Transaction result data
*/
public function end(?string $status = 'success', array $result = []): void
{
$duration = microtime(true) - $this->startTime;
$this->logger->logEvent($this->transactionId, 'transaction_end', [
'operation' => $this->operation,
'status' => $status,
'duration_seconds' => round($duration, 3),
'result' => $result,
]);
$this->logger->endTransaction($this->transactionId);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
/**
* Checkpoint Manager - Manages checkpoints for recovery operations
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* @package MokoStandards\Enterprise
* @version 04.00.04
* @author MokoStandards Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use DateTime;
use DateTimeZone;
use Throwable;
/**
* Manages checkpoints for recovery operations.
*
* Features:
* - Save/load checkpoint state
* - Automatic timestamp tracking
* - Checkpoint listing and cleanup
* - JSON-based state persistence
*
* Example:
* ```php
* $manager = new CheckpointManager('.checkpoints');
* $manager->saveCheckpoint('operation', ['step' => 1, 'data' => 'value']);
* $state = $manager->loadCheckpoint('operation');
* ```
*/
class CheckpointManager
{
private string $checkpointDir;
public const VERSION = '04.06.00';
/**
* Initialize checkpoint manager.
*
* @param string $checkpointDir Directory to store checkpoints
*/
public function __construct(string $checkpointDir = '.checkpoints')
{
$this->checkpointDir = $checkpointDir;
// Create checkpoint directory if it doesn't exist
if (!is_dir($this->checkpointDir)) {
if (!mkdir($this->checkpointDir, 0755, true) && !is_dir($this->checkpointDir)) {
throw new RecoveryError("Failed to create checkpoint directory: {$this->checkpointDir}");
}
}
}
/**
* Save a checkpoint.
*
* @param string $name Checkpoint name
* @param array<string, mixed> $state State to save
* @return string Path to checkpoint file
* @throws RecoveryError
*/
public function saveCheckpoint(string $name, array $state): string
{
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->format('Ymd_His');
$checkpointFile = "{$this->checkpointDir}/{$name}_{$timestamp}.json";
try {
$json = json_encode($state, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
file_put_contents($checkpointFile, $json, LOCK_EX);
error_log("Checkpoint saved: {$checkpointFile}");
return $checkpointFile;
} catch (Throwable $e) {
error_log("Failed to save checkpoint: {$e->getMessage()}");
throw new RecoveryError("Checkpoint save failed: {$e->getMessage()}");
}
}
/**
* Load the most recent checkpoint for a name.
*
* @param string $name Checkpoint name
* @return array<string, mixed>|null Checkpoint state or null if not found
*/
public function loadCheckpoint(string $name): ?array
{
$checkpoints = glob("{$this->checkpointDir}/{$name}_*.json");
if ($checkpoints === false || empty($checkpoints)) {
return null;
}
// Sort by filename (which includes timestamp) to get latest
sort($checkpoints);
$latest = end($checkpoints);
try {
$json = file_get_contents($latest);
if ($json === false) {
error_log("Failed to read checkpoint: {$latest}");
return null;
}
$state = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
error_log("Checkpoint loaded: {$latest}");
return $state;
} catch (Throwable $e) {
error_log("Failed to load checkpoint: {$e->getMessage()}");
return null;
}
}
/**
* List available checkpoints.
*
* @param string|null $name Filter by checkpoint name (optional)
* @return array<string> List of checkpoint file paths
*/
public function listCheckpoints(?string $name = null): array
{
$pattern = $name ? "{$this->checkpointDir}/{$name}_*.json" : "{$this->checkpointDir}/*.json";
$checkpoints = glob($pattern);
return $checkpoints !== false ? $checkpoints : [];
}
/**
* Clean up old checkpoints.
*
* @param string|null $name Filter by checkpoint name (optional)
* @param int $keepLatest Number of latest checkpoints to keep
*/
public function cleanupCheckpoints(?string $name = null, int $keepLatest = 5): void
{
$checkpoints = $this->listCheckpoints($name);
sort($checkpoints);
if (count($checkpoints) > $keepLatest) {
$toRemove = array_slice($checkpoints, 0, -$keepLatest);
foreach ($toRemove as $checkpoint) {
unlink($checkpoint);
error_log("Removed old checkpoint: {$checkpoint}");
}
}
}
}
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
<?php
declare(strict_types=1);
/**
* Configuration Manager - Centralized, environment-aware configuration.
*
* This class provides enterprise-grade configuration management with:
* - Environment variable loading (.env support via phpdotenv)
* - YAML and JSON configuration file parsing
* - Secure secret management
* - Configuration validation
* - Default values with overrides
* - Type-safe configuration access
* - Dot notation for nested values
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* @package MokoStandards\Enterprise
* @version 04.00.04
* @author MokoStandards Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use RuntimeException;
/**
* Exception raised when configuration validation fails.
*/
class ConfigValidationError extends RuntimeException
{
}
/**
* Enterprise configuration manager with environment support.
*
* Features:
* - Environment-based configuration
* - Dot notation for nested access (e.g., 'github.rate_limit')
* - Runtime overrides
* - Type-safe getters
* - Default value fallbacks
* - Environment detection (dev/staging/production)
*
* Example:
* ```php
* $config = Config::load();
* $org = $config->get('github.organization');
* $rateLimit = $config->getInt('github.rate_limit', 5000);
* $isProduction = $config->isProduction();
* ```
*/
class Config
{
/** @var array<string, mixed> Default configuration values */
private const DEFAULT_CONFIG = [
'version' => '04.00.04',
'environment' => 'development',
'platform' => 'github',
'github' => [
'organization' => 'mokoconsulting-tech',
'rate_limit' => 5000,
'max_retries' => 3,
'timeout' => 30,
],
'gitea' => [
'url' => 'https://git.mokoconsulting.tech',
'organization' => 'mokoconsulting-tech',
'rate_limit' => 5000,
'max_retries' => 3,
'timeout' => 30,
],
'logging' => [
'level' => 'INFO',
'format' => 'json',
'directory' => 'logs',
'retention_days' => 90,
],
'audit' => [
'enabled' => true,
'directory' => 'var/logs/audit',
'max_file_size_mb' => 20,
'retention_days' => 90,
],
'cache' => [
'enabled' => true,
'ttl_seconds' => 300,
],
'circuit_breaker' => [
'enabled' => true,
'threshold' => 5,
'timeout_seconds' => 60,
],
];
/** @var array<string, mixed> Configuration data */
private array $configData;
/** @var string Current environment */
private string $environment;
/** @var array<string, mixed> Runtime override data */
private array $overrideData = [];
public const VERSION = '04.06.00';
/**
* Constructor.
*
* @param array<string, mixed> $configData Configuration data
* @param string $environment Environment name
*/
public function __construct(array $configData, string $environment = 'development')
{
$this->configData = $configData;
$this->environment = $environment;
}
/**
* Load configuration from environment.
*
* @param string|null $env Environment override (null = auto-detect)
* @return self Configuration instance
*/
public static function load(?string $env = null): self
{
// Detect environment from env var or default to development
$env = $env ?? $_ENV['MOKO_ENV'] ?? getenv('MOKO_ENV') ?: 'development';
// Start with default config
$configData = self::DEFAULT_CONFIG;
$configData['environment'] = $env;
// Load from .env file if exists using vlucas/phpdotenv
$repoRoot = dirname(__DIR__, 2);
if (file_exists($repoRoot . '/.env')) {
// Note: In production, you'd use Dotenv::createImmutable() here
// For now, we'll manually parse simple .env files
self::loadEnvFile($repoRoot . '/.env');
}
// Override with environment variables
self::applyEnvironmentOverrides($configData);
return new self($configData, $env);
}
/**
* Load environment variables from .env file.
*
* @param string $envFile Path to .env file
*/
private static function loadEnvFile(string $envFile): void
{
if (!is_readable($envFile)) {
return;
}
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines === false) {
return;
}
foreach ($lines as $line) {
// Skip comments
if (str_starts_with(trim($line), '#')) {
continue;
}
// Parse KEY=VALUE format
if (strpos($line, '=') !== false) {
[$key, $value] = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// Remove quotes if present
if (preg_match('/^(["\'])(.*)\\1$/', $value, $matches)) {
$value = $matches[2];
}
// Set environment variable
putenv("$key=$value");
$_ENV[$key] = $value;
}
}
}
/**
* Apply environment variable overrides to config.
*
* @param array<string, mixed> &$configData Configuration data to modify
*/
private static function applyEnvironmentOverrides(array &$configData): void
{
// Platform selection: GIT_PLATFORM env var overrides default
if ($platform = getenv('GIT_PLATFORM')) {
$configData['platform'] = strtolower($platform);
}
// GitHub token resolution (in priority order):
// 1. GH_TOKEN env var (GitHub Actions org/repo secret)
// 2. GITHUB_TOKEN env var (GitHub Actions built-in)
// 3. `gh auth token` from the gh CLI (local developer machines)
$token = getenv('GH_TOKEN') ?: getenv('GITHUB_TOKEN') ?: self::resolveGhCliToken();
if (!empty($token)) {
$configData['github']['token'] = $token;
}
if ($org = getenv('GITHUB_ORG')) {
$configData['github']['organization'] = $org;
}
// Gitea token resolution: GITEA_TOKEN env var
$giteaToken = getenv('GITEA_TOKEN') ?: '';
if (!empty($giteaToken)) {
$configData['gitea']['token'] = $giteaToken;
}
if ($giteaUrl = getenv('GITEA_URL')) {
$configData['gitea']['url'] = rtrim($giteaUrl, '/');
}
if ($giteaOrg = getenv('GITEA_ORG')) {
$configData['gitea']['organization'] = $giteaOrg;
}
// Logging configuration
if ($logLevel = getenv('LOG_LEVEL')) {
$configData['logging']['level'] = $logLevel;
}
}
/**
* Attempt to retrieve a GitHub token from the gh CLI.
*
* Runs `gh auth token` non-interactively (stdin from null device) and
* validates the output matches a known GitHub token prefix before returning
* it. Returns an empty string when gh is not installed, not authenticated,
* or the output is not a recognisable token.
*/
private static function resolveGhCliToken(): string
{
$nullDevice = PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null';
$proc = proc_open(
['gh', 'auth', 'token'],
[0 => ['file', $nullDevice, 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
$pipes
);
if (!is_resource($proc)) {
return '';
}
$output = trim(stream_get_contents($pipes[1]));
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($proc);
// Accept only strings that look like a real GitHub token
return preg_match('/^(ghp_|github_pat_|gho_|ghu_|ghs_)\S+$/', $output) ? $output : '';
}
/**
* Get configuration value with dot notation.
*
* @param string $key Configuration key (e.g., 'github.rate_limit')
* @param mixed $default Default value if key not found
* @return mixed Configuration value
*/
public function get(string $key, mixed $default = null): mixed
{
// Check runtime overrides first
if (array_key_exists($key, $this->overrideData)) {
return $this->overrideData[$key];
}
// Navigate nested configuration using dot notation
$value = $this->configData;
foreach (explode('.', $key) as $part) {
if (is_array($value) && array_key_exists($part, $value)) {
$value = $value[$part];
} else {
return $default;
}
}
return $value;
}
/**
* Set configuration value (runtime override).
*
* @param string $key Configuration key
* @param mixed $value Value to set
*/
public function set(string $key, mixed $value): void
{
$this->overrideData[$key] = $value;
}
/**
* Get integer value.
*
* @param string $key Configuration key
* @param int $default Default value
* @return int Integer value
*/
public function getInt(string $key, int $default = 0): int
{
$value = $this->get($key, $default);
return is_numeric($value) ? (int) $value : $default;
}
/**
* Get string value.
*
* @param string $key Configuration key
* @param string $default Default value
* @return string String value
*/
public function getString(string $key, string $default = ''): string
{
$value = $this->get($key, $default);
return is_scalar($value) ? (string) $value : $default;
}
/**
* Get boolean value.
*
* @param string $key Configuration key
* @param bool $default Default value
* @return bool Boolean value
*/
public function getBool(string $key, bool $default = false): bool
{
$value = $this->get($key, $default);
// Handle string representations of booleans
if (is_string($value)) {
$value = strtolower($value);
if (in_array($value, ['true', '1', 'yes', 'on'], true)) {
return true;
}
if (in_array($value, ['false', '0', 'no', 'off'], true)) {
return false;
}
}
return (bool) $value;
}
/**
* Get entire configuration section.
*
* @param string $section Section name
* @return array<string, mixed> Section data
*/
public function getSection(string $section): array
{
$value = $this->get($section, []);
return is_array($value) ? $value : [];
}
/**
* Get current environment.
*
* @return string Environment name
*/
public function getEnvironment(): string
{
return $this->environment;
}
/**
* Check if production environment.
*
* @return bool True if production
*/
public function isProduction(): bool
{
return in_array($this->environment, ['production', 'prod'], true);
}
/**
* Check if development environment.
*
* @return bool True if development
*/
public function isDevelopment(): bool
{
return in_array($this->environment, ['development', 'dev'], true);
}
/**
* Check if staging environment.
*
* @return bool True if staging
*/
public function isStaging(): bool
{
return in_array($this->environment, ['staging', 'stage'], true);
}
/**
* Get all configuration data.
*
* @return array<string, mixed> All configuration
*/
public function all(): array
{
return array_merge($this->configData, $this->overrideData);
}
/**
* Validate required configuration keys exist.
*
* @param array<string> $requiredKeys Required configuration keys
* @throws ConfigValidationError If validation fails
*/
public function validate(array $requiredKeys): void
{
$missing = [];
foreach ($requiredKeys as $key) {
if ($this->get($key) === null) {
$missing[] = $key;
}
}
if (!empty($missing)) {
throw new ConfigValidationError(
'Missing required configuration keys: ' . implode(', ', $missing)
);
}
}
/**
* String representation.
*
* @return string
*/
public function __toString(): string
{
return "Config(environment='{$this->environment}')";
}
}

Some files were not shown because too many files have changed in this diff Show More