diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index cbab803..a420f95 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -13,7 +13,7 @@
generic
- 04.07.00
+ 04.09.00
https://git.mokoconsulting.tech/MokoConsulting/moko-platform
2026-05-10T19:51:08+00:00
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd4ce7b..7b9d626 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,30 @@ Version format: `XX.YY.ZZ` (zero-padded semver).
## [Unreleased]
+## [04.09.00] - 2026-05-12
+
+### Added
+- `` section support in `.manifest.xml` schema: `source-dir`, `remote-subdir`, `excludes`, `dev-host`, `demo-host`
+- `manifest_read.php` now parses all deploy fields for CI consumption
+
+### Changed
+- Deploy workflows can now read deploy paths from manifest instead of guessing from directory structure
+
+## [04.08.00] - 2026-05-12
+
+### Added
+- `cli/manifest_read.php` -- full `.manifest.xml` parser for CI consumption
+ - Supports `--field`, `--all`, `--json`, and `--github-output` modes
+ - Backward-compatible with `.moko-platform` (XML) and `.mokostandards` (YAML) formats
+ - Replaces inline `sed` detection blocks in workflows
+
+### Changed
+- Workflows (`auto-release`, `pre-release`, `pr-check`) now use `manifest_read.php` for platform detection
+- `entry-point` field from manifest replaces `find` tree scan for mod file discovery
+- Platform detection outputs all manifest fields to `GITHUB_OUTPUT` (name, org, language, package-type, etc.)
+
+
+
## [05.00.00] - 2026-05-11
### Added
diff --git a/cli/manifest_read.php b/cli/manifest_read.php
new file mode 100644
index 0000000..4e8a8ca
--- /dev/null
+++ b/cli/manifest_read.php
@@ -0,0 +1,170 @@
+#!/usr/bin/env php
+
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * FILE INFORMATION
+ * DEFGROUP: MokoStandards.CLI
+ * INGROUP: MokoStandards
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
+ * PATH: /cli/manifest_read.php
+ * VERSION: 04.09.00
+ * BRIEF: Parse .manifest.xml and output requested field(s) for CI consumption
+ *
+ * Usage:
+ * php manifest_read.php --path /repo --field platform
+ * php manifest_read.php --path /repo --field entry-point
+ * php manifest_read.php --path /repo --all
+ * php manifest_read.php --path /repo --github-output
+ *
+ * Fields: name, org, description, license, license-spdx, platform,
+ * standards-version, standards-source, language, package-type, entry-point,
+ * source-dir, remote-subdir, excludes, dev-host, demo-host
+ *
+ * --all Print all fields as KEY=VALUE lines
+ * --github-output Append all fields to $GITHUB_OUTPUT (for Gitea/GitHub Actions)
+ * --json Output all fields as JSON
+ * --field Print a single field value (no key, just value)
+ */
+
+declare(strict_types=1);
+
+// -- Argument parsing ---------------------------------------------------------
+$path = '.';
+$field = null;
+$mode = 'field'; // field | all | github-output | json
+
+foreach ($argv as $i => $arg) {
+ if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
+ if ($arg === '--field' && isset($argv[$i + 1])) $field = $argv[$i + 1];
+ if ($arg === '--all') $mode = 'all';
+ if ($arg === '--github-output') $mode = 'github-output';
+ if ($arg === '--json') $mode = 'json';
+}
+
+// -- Locate manifest ----------------------------------------------------------
+$root = realpath($path) ?: $path;
+$manifestFile = null;
+
+// Priority: .manifest.xml > .moko-platform (backward compat)
+$candidates = [
+ "{$root}/.mokogitea/.manifest.xml",
+ "{$root}/.mokogitea/.moko-platform",
+ "{$root}/.gitea/.mokostandards", // legacy v4
+];
+
+foreach ($candidates as $candidate) {
+ if (file_exists($candidate)) {
+ $manifestFile = $candidate;
+ break;
+ }
+}
+
+if ($manifestFile === null) {
+ fwrite(STDERR, "No manifest found in {$root}
+");
+ exit(1);
+}
+
+// -- Parse XML ----------------------------------------------------------------
+$xml = @simplexml_load_file($manifestFile);
+
+if ($xml === false) {
+ // Fallback: try YAML format (.mokostandards legacy)
+ $content = file_get_contents($manifestFile);
+ $fields = [];
+ if (preg_match('/^platform:\s*(.+)/m', $content, $m)) {
+ $fields['platform'] = trim($m[1], "
+
\"'");
+ }
+ if (preg_match('/^standards_version:\s*(.+)/m', $content, $m)) {
+ $fields['standards-version'] = trim($m[1], "
+
\"'");
+ }
+ if (preg_match('/^governed_repo:\s*(.+)/m', $content, $m)) {
+ $fields['name'] = trim($m[1], "
+
\"'");
+ }
+} else {
+ // Register namespace for XPath (optional, simple path works without)
+ $fields = [
+ 'name' => (string)($xml->identity->name ?? ''),
+ 'org' => (string)($xml->identity->org ?? ''),
+ 'description' => (string)($xml->identity->description ?? ''),
+ 'license' => (string)($xml->identity->license ?? ''),
+ 'license-spdx' => (string)($xml->identity->license['spdx'] ?? ''),
+ 'platform' => (string)($xml->governance->platform ?? ''),
+ 'standards-version' => (string)($xml->governance->{"standards-version"} ?? ''),
+ 'standards-source' => (string)($xml->governance->{"standards-source"} ?? ''),
+ 'language' => (string)($xml->build->language ?? ''),
+ 'package-type' => (string)($xml->build->{"package-type"} ?? ''),
+ 'entry-point' => (string)($xml->build->{"entry-point"} ?? ''),
+ 'source-dir' => (string)($xml->deploy->{"source-dir"} ?? ''),
+ 'remote-subdir' => (string)($xml->deploy->{"remote-subdir"} ?? ''),
+ 'excludes' => (string)($xml->deploy->excludes ?? ''),
+ 'dev-host' => (string)($xml->deploy->{"dev-host"} ?? ''),
+ 'demo-host' => (string)($xml->deploy->{"demo-host"} ?? ''),
+ 'manifest-file' => $manifestFile,
+ ];
+}
+
+// Strip empty values for cleaner output
+$fields = array_filter($fields, fn($v) => $v !== '');
+
+// -- Output -------------------------------------------------------------------
+switch ($mode) {
+ case 'field':
+ if ($field === null) {
+ fwrite(STDERR, "Usage: manifest_read.php --path --field
+");
+ fwrite(STDERR, " manifest_read.php --path --all
+");
+ fwrite(STDERR, " manifest_read.php --path --json
+");
+ fwrite(STDERR, " manifest_read.php --path --github-output
+");
+ exit(2);
+ }
+ echo ($fields[$field] ?? '') . "
+";
+ break;
+
+ case 'all':
+ foreach ($fields as $k => $v) {
+ echo "{$k}={$v}
+";
+ }
+ break;
+
+ case 'json':
+ echo json_encode($fields, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "
+";
+ break;
+
+ case 'github-output':
+ $outputFile = getenv('GITHUB_OUTPUT');
+ if ($outputFile === false || $outputFile === '') {
+ fwrite(STDERR, "GITHUB_OUTPUT not set — printing to stdout instead
+");
+ foreach ($fields as $k => $v) {
+ // Convert field-name to FIELD_NAME for env var style
+ $envKey = str_replace('-', '_', $k);
+ echo "{$envKey}={$v}
+";
+ }
+ } else {
+ $fh = fopen($outputFile, 'a');
+ foreach ($fields as $k => $v) {
+ $envKey = str_replace('-', '_', $k);
+ fwrite($fh, "{$envKey}={$v}
+");
+ }
+ fclose($fh);
+ fwrite(STDERR, "Wrote " . count($fields) . " fields to GITHUB_OUTPUT
+");
+ }
+ break;
+}
+
+exit(0);