Compare commits

...

12 Commits

Author SHA1 Message Date
bb3fe5e108 Merge pull request 'ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)' (#6) from ci-rename-github-to-gitea into main
Some checks are pending
CI / validate (push) Waiting to run
2026-05-10 21:18:13 +00:00
1c04f827e2 ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)
All checks were successful
CI / validate (push) Successful in 1m1s
CI / validate (pull_request) Successful in 57s
GitHub org Molecule-AI was suspended 2026-05-06; SCM moved to Gitea
(git.moleculesai.app). The wholesale `git push --mirror` migration left
workflow files under .github/workflows/, which Gitea Actions does NOT
read - it reads .gitea/workflows/ exclusively.

This rename + the cross-repo `uses:` path rewrite are the minimum
edits to make CI fire on this repo again. The workflow content itself
is not modified (other than the path rewrites and lowercasing of the
old `Molecule-AI` org reference to the post-suspension `molecule-ai`).

Refs: feedback_post_suspension_migration_must_sweep_dormant_repos
2026-05-10 14:13:11 -07:00
752496682e chore(ci): remove recovery marker (rerun delivered, see internal#233)
Some checks failed
CI / validate (push) Failing after 1s
2026-05-10 19:51:53 +00:00
f8e8f7800f chore(ci): re-fire after incident recovery 2026-05-10 (see internal#233; revert me)
Some checks failed
CI / validate (push) Failing after 2s
2026-05-10 19:51:16 +00:00
7a147b7418 Merge pull request 'chore: plugin hygiene — .gitignore Python ignores + __pycache__ cleanup' (#5) from plugin/hygiene into main
Some checks failed
CI / validate (push) Failing after 1s
2026-05-10 16:23:13 +00:00
9cf72407c9 chore: append Python ignores to .gitignore
Some checks failed
CI / validate (push) Failing after 0s
CI / validate (pull_request) Failing after 2s
2026-05-10 16:20:00 +00:00
421a299e7d chore: remove committed __pycache__/deepagents.cpython-313.pyc
Some checks failed
CI / validate (push) Failing after 1s
2026-05-10 16:18:09 +00:00
8cc3b1b785 chore: remove committed __pycache__/claude_code.cpython-313.pyc
Some checks are pending
CI / validate (push) Waiting to run
2026-05-10 16:18:08 +00:00
4cc7184070 chore: append Python ignores to .gitignore
Some checks failed
CI / validate (push) Failing after 1s
2026-05-10 16:17:29 +00:00
1f99cf34bd Merge pull request 'test(ecc): add 23-test smoke suite + coverage rationale' (#4) from plugin/test-coverage into main
Some checks failed
CI / validate (push) Failing after 1s
2026-05-10 13:53:12 +00:00
f62faec77b test(ecc): add 23-test smoke suite + coverage rationale
Some checks failed
CI / validate (push) Failing after 2s
CI / validate (pull_request) Failing after 2s
- tests/test_ecc_smoke.py: 23 tests covering:
  - plugin.yaml schema (5 skills, 3 rules, runtimes)
  - All 3 rules/ files exist and are non-empty
  - All 5 skills/ directories + SKILL.md with valid frontmatter + body heading
  - Claude Code + deepagents adapters exist and are wired
  - known-issues.md structure (severity definitions)
  - validate-plugin.py exit 0 smoke test
- tests/README.md: explains why coverage is limited (skill+rules plugin,
  no executable hooks), and where to find integration-test guidance
2026-05-10 13:51:47 +00:00
a4985baf9a Merge pull request 'fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)' (#3) from fix/post-suspension-github-urls into main
All checks were successful
CI / validate (push) Successful in 1m24s
2026-05-07 20:03:33 +00:00
7 changed files with 264 additions and 5 deletions

5
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,5 @@
name: CI
on: [push, pull_request]
jobs:
validate:
uses: molecule-ai/molecule-ci/.gitea/workflows/validate-plugin.yml@main

View File

@ -1,5 +0,0 @@
name: CI
on: [push, pull_request]
jobs:
validate:
uses: molecule-ai/molecule-ci/.github/workflows/validate-plugin.yml@main

18
.gitignore vendored
View File

@ -19,3 +19,21 @@
# Workspace auth tokens # Workspace auth tokens
.auth-token .auth-token
.auth_token .auth_token
# Python bytecode (append only — do not remove entries above)
__pycache__/
*.pyc
*.py[cod]
*$py.class
.pytest_cache/
# Python bytecode (append only — do not remove entries above)
__pycache__/
*.pyc
*.py[cod]
*$py.class
.Python
*.egg-info/
*.egg
.pytest_cache/
build/
dist/
.eggs/

36
tests/README.md Normal file
View File

@ -0,0 +1,36 @@
# Test Coverage Rationale — molecule-ecc
## Why This Plugin Has Limited Unit-Test Coverage
`molecule-ecc` is a **skill+rules plugin** — it provides development guidelines and
development skills (api-design, coding-standards, deep-research, security-review, tdd-workflow)
via prose SKILL.md files and rules/*.md files.
There are no hooks, no Python business logic, and no testable adapters in this plugin.
The "logic" is prose documentation.
## What We Test (and Why)
| What | Why |
|------|-----|
| `plugin.yaml` schema | Verifies all 5 skills and 3 rules are registered |
| Rules files (3) | Each declared rule file exists and is non-empty |
| Skills (5) | Each skill directory + SKILL.md exists with valid YAML frontmatter and `#` heading |
| Adapters (2) | Claude Code + deepagents adapters are wired |
| `known-issues.md` | Severity definitions present |
| `validate-plugin.py` exit 0 | Smoke test — shared CI validator passes |
## What We Cannot Unit-Test Here
- **SKILL.md prose content** — the development guidelines are prose; their quality is
a documentation review concern, not a unit-test concern.
- **Agent behavior when using skills** — write integration tests in `workspace-template/`.
## Integration Tests
If you want to test that agents actually use the ecc skills correctly, write
integration tests that:
1. Install `molecule-ecc` on a test workspace
2. Ask the agent to use a specific skill (e.g., "use TDD workflow")
3. Verify the agent follows the documented process

205
tests/test_ecc_smoke.py Normal file
View File

@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""
Smoke tests for molecule-ecc (Everything Claude Code).
Rationale: This is a skill+rules plugin no hooks. The "logic" is prose in
SKILL.md files and rules/*.md files. Smoke tests verify all artifacts exist,
parse correctly, and document required sections. See tests/README.md.
Run: python tests/test_ecc_smoke.py
"""
import os
import sys
import unittest
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO_ROOT, '.molecule-ci', 'scripts'))
def load_manifest():
import yaml
with open(os.path.join(REPO_ROOT, 'plugin.yaml')) as f:
return yaml.safe_load(f)
class TestPluginManifest(unittest.TestCase):
"""Verify plugin.yaml is well-formed."""
@classmethod
def setUpClass(cls):
cls.manifest = load_manifest()
def test_plugin_yaml_loads(self):
self.assertIsInstance(self.manifest, dict)
def test_name(self):
self.assertEqual(self.manifest['name'], 'ecc')
def test_version_semver(self):
import re
v = self.manifest['version']
self.assertRegex(v, r'^\d+\.\d+\.\d+$', f"Version {v!r} not semver")
def test_description_present(self):
self.assertGreater(len(self.manifest.get('description', '')), 10)
def test_runtimes_include_claude_code(self):
self.assertIn('claude_code', self.manifest.get('runtimes', []))
def test_rules_declared(self):
rules = self.manifest.get('rules', [])
self.assertIsInstance(rules, list)
self.assertGreater(len(rules), 0)
def test_skills_declared(self):
skills = self.manifest.get('skills', [])
self.assertIsInstance(skills, list)
self.assertGreater(len(skills), 0)
class TestRules(unittest.TestCase):
"""Verify all declared rules files exist and are non-empty."""
RULES_DIR = os.path.join(REPO_ROOT, 'rules')
@classmethod
def setUpClass(cls):
cls.rules = load_manifest().get('rules', [])
def test_rules_directory_exists(self):
self.assertTrue(os.path.isdir(self.RULES_DIR))
def test_each_declared_rule_file_exists(self):
for rule in self.rules:
# plugin.yaml declares rules as 'rules/foo.md' — take basename
filename = os.path.basename(rule)
path = os.path.join(self.RULES_DIR, filename)
self.assertTrue(
os.path.isfile(path),
f"Rule {rule!r} declared but file not found at {path}"
)
def test_each_rule_file_is_nonempty(self):
for rule in self.rules:
filename = os.path.basename(rule)
path = os.path.join(self.RULES_DIR, filename)
size = os.path.getsize(path)
self.assertGreater(size, 100, f"Rule {rule!r} is suspiciously small ({size} bytes)")
def test_guardrails_rule_has_content(self):
path = os.path.join(self.RULES_DIR, 'everything-claude-code-guardrails.md')
if os.path.isfile(path):
with open(path) as f:
content = f.read()
self.assertGreater(len(content), 500, "Guardrails rule should have substantive content")
class TestSkills(unittest.TestCase):
"""Verify all declared skills have SKILL.md with valid frontmatter."""
SKILLS_DIR = os.path.join(REPO_ROOT, 'skills')
@classmethod
def setUpClass(cls):
cls.skills = load_manifest().get('skills', [])
def test_skills_directory_exists(self):
self.assertTrue(os.path.isdir(self.SKILLS_DIR))
def test_each_declared_skill_directory_exists(self):
for skill in self.skills:
path = os.path.join(self.SKILLS_DIR, skill)
self.assertTrue(
os.path.isdir(path),
f"Skill {skill!r} declared but directory not found at {path}"
)
def test_each_skill_has_skill_md(self):
import yaml
for skill in self.skills:
path = os.path.join(self.SKILLS_DIR, skill, 'SKILL.md')
self.assertTrue(os.path.isfile(path), f"Skill {skill!r} missing SKILL.md at {path}")
def test_each_skill_md_has_frontmatter(self):
import yaml
for skill in self.skills:
path = os.path.join(self.SKILLS_DIR, skill, 'SKILL.md')
with open(path) as f:
content = f.read()
self.assertTrue(
content.startswith('---'),
f"{skill}: SKILL.md must have YAML frontmatter"
)
parts = content.split('---', 2)
self.assertEqual(len(parts), 3, f"{skill}: SKILL.md must have opening and closing ---")
_, frontmatter, _ = parts
data = yaml.safe_load(frontmatter)
self.assertIsInstance(data, dict)
self.assertIn('name', data, f"{skill}: frontmatter must have 'name'")
def test_each_skill_body_has_heading(self):
for skill in self.skills:
path = os.path.join(self.SKILLS_DIR, skill, 'SKILL.md')
with open(path) as f:
content = f.read()
parts = content.split('---', 2)
_, _, body = parts
self.assertRegex(
body.lstrip(), r'^# ',
f"{skill}: SKILL.md body must start with # heading"
)
class TestAdapters(unittest.TestCase):
"""Verify Claude Code and deepagents adapters exist."""
def test_claude_code_adapter_exists(self):
path = os.path.join(REPO_ROOT, 'adapters', 'claude_code.py')
self.assertTrue(os.path.isfile(path))
def test_claude_code_adapter_imports_adaptor(self):
path = os.path.join(REPO_ROOT, 'adapters', 'claude_code.py')
with open(path) as f:
content = f.read()
self.assertIn('Adaptor', content)
def test_deepagents_adapter_exists(self):
path = os.path.join(REPO_ROOT, 'adapters', 'deepagents.py')
self.assertTrue(os.path.isfile(path))
class TestKnownIssues(unittest.TestCase):
"""Verify known-issues.md structure."""
KI_PATH = os.path.join(REPO_ROOT, 'known-issues.md')
def test_file_exists(self):
self.assertTrue(os.path.isfile(self.KI_PATH))
def test_has_active_issues_section(self):
with open(self.KI_PATH) as f:
self.assertIn('Active Issues', f.read())
def test_has_severity_definitions(self):
with open(self.KI_PATH) as f:
content = f.read()
self.assertIn('Severity Definitions', content)
class TestValidatePlugin(unittest.TestCase):
"""Smoke-test validate-plugin.py."""
def test_exits_zero(self):
import subprocess
result = subprocess.run(
[sys.executable, os.path.join(REPO_ROOT, '.molecule-ci', 'scripts', 'validate-plugin.py')],
capture_output=True,
text=True,
cwd=REPO_ROOT,
)
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}\nstderr: {result.stderr}")
self.assertIn('ecc', result.stdout)
if __name__ == '__main__':
unittest.main(verbosity=2)