← Files Software & AI CopilotARCHIVED FILE
scripts/validate_package.py
3.46 KB · Nov 3, 2026 · 00:12 UTC
#!/usr/bin/env python3
"""Offline structural checker. Does not prove live skill behavior."""
from pathlib import Path
import json,re,sys
root=Path(__file__).resolve().parents[1]
errors=[];checks=0
def check(condition,message):
global checks
checks+=1
if not condition:errors.append(message)
m=json.loads((root/'plugin.json').read_text(encoding='utf8'))
compat=json.loads((root/'.codex-plugin/plugin.json').read_text(encoding='utf8'))
iface=m['extensions']['com.openai']['interface']
check(m['name']=='software-data-ai-copilot','identity changed')
check(m['version']=='0.2.0','wrong version')
check(iface['displayName']=='Software & AI Copilot','public name changed')
check(iface['developerName']=='Tekris Labs','publisher mismatch')
check(m['author']['name']=='Tekris Labs','author mismatch')
check(compat['name']==m['name'] and compat['version']==m['version'],'compatibility identity mismatch')
check(compat['interface']==iface,'compatibility interface mismatch')
check(len(iface['defaultPrompt'])==3,'starter prompt count not three')
check(len(iface['shortDescription'])<=30,'subtitle too long')
check(len(iface['displayName'])<=30,'display name too long')
for i,p in enumerate(iface['defaultPrompt']):check(0<len(p)<=128,f'prompt {i} length invalid')
for k,v in {'websiteURL':'https://tekrislabs.com/','supportURL':'https://tekrislabs.com/contact','privacyPolicyURL':'https://tekrislabs.com/privacy','termsOfServiceURL':'https://tekrislabs.com/terms'}.items():check(iface.get(k)==v,k+' missing or incorrect')
for k in ('logo','composerIcon'):
p=iface[k];check(p.startswith('./') and '..' not in p and (root/p[2:]).is_file(),k+' invalid path')
check(not (root/'mcp.json').exists() and not (root/'.mcp.json').exists(), 'unexpected mcp server config')
check((root/'skills/get-started/SKILL.md').exists(),'onboarding skill missing')
paths=sorted((root/'skills').glob('*/SKILL.md'))
check(len(paths)==18,f'expected 18 focused skills, found {len(paths)}')
for p in paths:
s=p.read_text(encoding='utf8')
check(s.startswith('---\n'),f'{p} frontmatter missing')
check(re.search(r'(?m)^name: '+re.escape(p.parent.name)+r'$',s) is not None,f'{p} name mismatch')
check(re.search(r'(?m)^description: .{30,}',s) is not None,f'{p} short description')
for match in re.findall(r'`(\.\./\.\./(?:references|templates)/[^`]+)`',s):
check((p.parent/match).resolve().is_file(),f'{p} broken reference {match}')
for item in ('archive/original-skill-v0.1.0.md','references/original/code_review_framework.md','references/original/data_ai_coding_patterns.md','references/original/implementation_safety_patterns.md','references/original/official_source_registry.md','references/original/repository_workflow.md','references/original/testing_strategy_reference.md'):
check((root/item).is_file(),'missing original '+item)
b=json.loads((root/'benchmarks/cases.json').read_text(encoding='utf8'))
check(len(b['cases'])>=24,'too few benchmark scenarios')
ids=[x['id'] for x in b['cases']];check(len(ids)==len(set(ids)),'duplicate benchmarks')
for case in b['cases']:
check((root/'skills'/case['skill']/'SKILL.md').is_file(),f'unknown benchmark skill {case["skill"]}')
check(case['type'] in ('positive','negative','routing'),'unknown benchmark type')
check(len(case['expected'])>20,'weak benchmark expected result')
for p in (root/'references').rglob('*.md'):check(len(p.read_text(encoding='utf8').strip())>100, f'empty reference {p}')
print(f'Package checks: {checks}; failures: {len(errors)}')
for e in errors:print('FAIL:',e)
sys.exit(1 if errors else 0)
SHA-256: d24fc21c401be3b9627ff7f43b6a4838ad534273bace0004216091538da74c4f