Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions .github/workflows/import-mod-archive.yml

This file was deleted.

94 changes: 0 additions & 94 deletions .github/workflows/publish-ready-build.yml

This file was deleted.

96 changes: 96 additions & 0 deletions .github/workflows/workshop-runtime-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Workshop runtime residue audit

on:
push:
branches: [workshop]
paths:
- '.github/workflows/workshop-runtime-audit.yml'

permissions:
contents: read

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Scan exact Steam upload set
id: scan
shell: python
run: |
from pathlib import Path
import os
import re
import xml.etree.ElementTree as ET

root = Path('metamorph_creative_menu')
workshop = ET.parse(root / 'workshop.xml').getroot()

def split_pipe(value):
return [x.strip().replace('\\', '/') for x in value.split('|') if x.strip()]

excluded_dirs = set(split_pipe(workshop.attrib.get('dont_upload_folders', '')))
excluded_files = set(split_pipe(workshop.attrib.get('dont_upload_files', '')))
text_suffixes = {'.lua', '.xml', '.txt', '.csv', '.json', '.frag', '.vert'}
rules = [
('numbered test chronology', re.compile(r'\bTEST\s+\d+\b', re.I)),
('test-only production API', re.compile(r'\b[A-Za-z0-9_]+_for_test\b')),
('conversation wording', re.compile(r"\b(?:the\s+)?user['’]s\s+build\b|\bat\s+the\s+user['’]s\s+request\b", re.I)),
('numbered runtime label', re.compile(r'\[MCM\s*(?:TEST\s*)?\d{2,}\]', re.I)),
('AI attribution residue', re.compile(r'\b(?:ChatGPT|OpenAI|Claude|Gemini|Copilot|LLM|AI[- ]generated|generated\s+by\s+AI)\b', re.I)),
('debug identifier', re.compile(r'\b(?:debug_[A-Za-z0-9_]+|[A-Za-z0-9_]+_debug|[A-Za-z0-9_]+\.debug(?:_[A-Za-z0-9_]+)?)\b')),
('diagnostic identifier', re.compile(r'\b(?:diagnostic|diagnostics|DIAGNOSTICS|DIAGNOSTIC)\b')),
('dev mode', re.compile(r'\b(?:dev_mode|DEV_MODE|METAMORPH_CREATIVE_MENU_DEV_MODE)\b')),
('profiling or metrics', re.compile(r'\b(?:profiling|profile_metrics|metrics_enabled|set_(?:profiling|metrics)_enabled)\b', re.I)),
('runtime print', re.compile(r'\bprint\s*\(')),
('unfinished marker', re.compile(r'\b(?:TODO|FIXME|HACK)\b', re.I)),
]

findings = []
scanned = 0
for path in sorted(root.rglob('*')):
if not path.is_file() or path.suffix.lower() not in text_suffixes:
continue
rel = path.relative_to(root).as_posix()
if rel in excluded_files or any(rel == d or rel.startswith(d + '/') for d in excluded_dirs):
continue
scanned += 1
try:
text = path.read_text(encoding='utf-8')
except UnicodeDecodeError:
continue
for lineno, line in enumerate(text.splitlines(), 1):
for label, pattern in rules:
if pattern.search(line):
findings.append(f'{rel}:{lineno}: {label}: {line.strip()[:240]}')

status = 'FAIL' if findings else 'PASS'
report = '\n'.join([f'WORKSHOP_RUNTIME_AUDIT={status} files={scanned} findings={len(findings)}', *findings]) + '\n'
Path('workshop-runtime-audit.txt').write_text(report, encoding='utf-8')
print(report, end='')
with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output:
output.write(f'findings={len(findings)}\n')

- name: Upload audit report
if: always()
uses: actions/upload-artifact@v7
with:
name: workshop-runtime-audit
path: workshop-runtime-audit.txt
if-no-files-found: error
retention-days: 1

- name: Upload source snapshot
if: always()
uses: actions/upload-artifact@v7
with:
name: workshop-source-snapshot
path: metamorph_creative_menu
if-no-files-found: error
retention-days: 1

- name: Fail on runtime residue
if: steps.scan.outputs.findings != '0'
shell: bash
run: exit 1
Loading
Loading