"""Inventory source changes without including private data, generated releases or retired demos.""" import hashlib import json from pathlib import Path import re import subprocess ROOT = Path(__file__).resolve().parents[1] ALLOWED = ('scripts/', 'docs/', 'deploy/', 'integrations/', 'server/', 'skills/', 'test/') def main(): pending = subprocess.check_output(['git', 'ls-files', '--others', '--exclude-standard', '-z'], cwd=ROOT).decode('utf-8').split('\0') for args in (['git', 'diff', '--name-only', '-z'], ['git', 'diff', '--cached', '--name-only', '-z']): pending += subprocess.check_output(args, cwd=ROOT).decode('utf-8').split('\0') candidates = sorted({p for p in pending if p and (p.startswith(ALLOWED) or p in {'.gitignore', '.gitattributes', 'package.json'})}) findings = [] forbidden = [] for name in candidates: p = ROOT / name if not p.exists(): continue if p.is_symlink() or p.suffix.lower() in {'.zip', '.sqlite', '.db', '.pem', '.key', '.pyc', '.png'} or name.endswith('credential-binding.json'): forbidden.append(name) continue text = p.read_text(encoding='utf-8-sig') for m in re.finditer(r'''(?i)\b(master_?key|service_?token|access_?token|api_?key|client_?secret)\b\s*[:=]\s*['"]([^'"\n]{16,})['"]''', text): if not ('test/' in name or 'server/test/' in name or Path(name).name.startswith('test_')): findings.append({'path': name, 'line': text.count('\n', 0, m.start()) + 1, 'field': m[1], 'literalLength': len(m[2])}) if re.search(r'-----BEGIN (?:RSA |OPENSSH )?PRIVATE KEY-----\s+[A-Za-z0-9+/]{40}', text): forbidden.append(name) skills = {} for skill in (ROOT / 'skills').iterdir(): manifest = skill / 'FILES.sha256' if not manifest.is_file(): continue rows = manifest.read_text(encoding='utf-8').splitlines() for line in rows: expected, relative = line.split(' ', 1) actual = hashlib.sha256((skill / relative).read_bytes()).hexdigest() if expected != actual: raise ValueError('SKILL_MANIFEST_MISMATCH:' + skill.name + '/' + relative) skills[skill.name] = {'filesVerified': len(rows), 'manifestSha256': hashlib.sha256(manifest.read_bytes()).hexdigest()} out = {'ok': not forbidden and not findings, 'sourceCandidates': candidates, 'sourceCandidateCount': len(candidates), 'forbiddenCandidates': forbidden, 'literalCredentialCandidates': findings, 'skills': skills, 'excludedLocalOutputs': ['artifacts/', 'data/', 'work/', 'private database files', 'credentials', 'retired demo drafts'], 'productionWrites': 0} target = ROOT / 'work/complete-release-20261106' target.mkdir(parents=True, exist_ok=True) (target / 'workspace-audit.json').write_text(json.dumps(out, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') print(json.dumps({k:v for k,v in out.items() if k != 'sourceCandidates'}, ensure_ascii=False, indent=2)) if __name__ == '__main__': main()