feat(org-templates): add ux-ab-lab + manifest entry + schema smoke test
Introduces the UX A/B Lab org template — a 7-agent cell for rapid
landing-page variant generation. The template is also the first
consumer of the new any_of env schema (ANTHROPIC_API_KEY OR
CLAUDE_CODE_OAUTH_TOKEN), so it doubles as an end-to-end fixture
for that feature.
Canvas tree (all claude-code / sonnet):
Design Director
├── UX Researcher
├── Visual Designer
├── React Engineer
├── Deploy Engineer
├── A11y + SEO Auditor ← WCAG AA + canonical/noindex gate
└── Perf Auditor ← Core Web Vitals gate
Template files live in their own standalone repo
(Molecule-AI/molecule-ai-org-template-ux-ab-lab, to be published);
this change adds the manifest.json entry so fresh clones + CI
populate the template via scripts/clone-manifest.sh.
Tests:
- TestOrgTemplate_ClaudeAnyOfAuthPreflight — parses the exact
required_env / recommended_env shape the template ships with
via inline YAML (not on-disk, since org-templates/ is
gitignored in this monorepo) and verifies either member
alternative satisfies the preflight.
SEO safety built into the auditor's system prompt:
- One canonical variant; all others canonicalise to it.
- noindex, follow on non-canonical variants.
- Sitemap contains only the canonical URL.
- No robots.txt disallow (blocked pages can't emit canonical).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ad73a56db1
commit
a7eb071e35
@ -39,6 +39,7 @@
|
||||
{"name": "free-beats-all", "repo": "Molecule-AI/molecule-ai-org-template-free-beats-all", "ref": "main"},
|
||||
{"name": "medo-smoke", "repo": "Molecule-AI/molecule-ai-org-template-medo-smoke", "ref": "main"},
|
||||
{"name": "molecule-worker-gemini", "repo": "Molecule-AI/molecule-ai-org-template-molecule-worker-gemini", "ref": "main"},
|
||||
{"name": "reno-stars", "repo": "Molecule-AI/molecule-ai-org-template-reno-stars", "ref": "main"}
|
||||
{"name": "reno-stars", "repo": "Molecule-AI/molecule-ai-org-template-reno-stars", "ref": "main"},
|
||||
{"name": "ux-ab-lab", "repo": "Molecule-AI/molecule-ai-org-template-ux-ab-lab", "ref": "main"}
|
||||
]
|
||||
}
|
||||
|
||||
@ -829,6 +829,73 @@ func TestCollectOrgEnv_RejectsInvalidNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrgTemplate_ClaudeAnyOfAuthPreflight exercises the shape the
|
||||
// ux-ab-lab template ships with: a single any-of group at the org
|
||||
// level covering ANTHROPIC_API_KEY vs. CLAUDE_CODE_OAUTH_TOKEN, plus
|
||||
// two strict recommended entries (SERPER_API_KEY, VERCEL_TOKEN).
|
||||
// Proves the end-to-end YAML → OrgTemplate → collectOrgEnv → IsSatisfied
|
||||
// pipeline works for the canonical "Claude sub OR API key" pattern
|
||||
// without depending on the on-disk template file (org-templates/ is
|
||||
// populated by the clone-manifest, not tracked in this monorepo).
|
||||
func TestOrgTemplate_ClaudeAnyOfAuthPreflight(t *testing.T) {
|
||||
src := `
|
||||
name: UX A/B Lab
|
||||
required_env:
|
||||
- any_of:
|
||||
- ANTHROPIC_API_KEY
|
||||
- CLAUDE_CODE_OAUTH_TOKEN
|
||||
recommended_env:
|
||||
- SERPER_API_KEY
|
||||
- VERCEL_TOKEN
|
||||
workspaces:
|
||||
- name: Design Director
|
||||
children:
|
||||
- name: UX Researcher
|
||||
- name: Visual Designer
|
||||
- name: React Engineer
|
||||
- name: Deploy Engineer
|
||||
- name: A11y + SEO Auditor
|
||||
- name: Perf Auditor
|
||||
`
|
||||
var tmpl OrgTemplate
|
||||
if err := yaml.Unmarshal([]byte(src), &tmpl); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if len(tmpl.Workspaces) != 1 || len(tmpl.Workspaces[0].Children) != 6 {
|
||||
t.Fatalf("expected 1 root with 6 children, got shape %+v", tmpl.Workspaces)
|
||||
}
|
||||
|
||||
required, recommended := collectOrgEnv(&tmpl)
|
||||
if len(required) != 1 {
|
||||
t.Fatalf("expected 1 required requirement (the any-of group), got %d: %v", len(required), reqNames(required))
|
||||
}
|
||||
if required[0].Name != "" {
|
||||
t.Errorf("expected any-of group, got strict name %q", required[0].Name)
|
||||
}
|
||||
wantMembers := []string{"ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"}
|
||||
got := append([]string(nil), required[0].AnyOf...)
|
||||
sort.Strings(got)
|
||||
if !stringSlicesEqual(got, wantMembers) {
|
||||
t.Errorf("any-of members mismatch: got %v, want %v", got, wantMembers)
|
||||
}
|
||||
|
||||
// Either member should independently satisfy the group.
|
||||
if !required[0].IsSatisfied(map[string]struct{}{"ANTHROPIC_API_KEY": {}}) {
|
||||
t.Errorf("ANTHROPIC_API_KEY alone should satisfy the group")
|
||||
}
|
||||
if !required[0].IsSatisfied(map[string]struct{}{"CLAUDE_CODE_OAUTH_TOKEN": {}}) {
|
||||
t.Errorf("CLAUDE_CODE_OAUTH_TOKEN alone should satisfy the group")
|
||||
}
|
||||
if required[0].IsSatisfied(map[string]struct{}{"OPENAI_API_KEY": {}}) {
|
||||
t.Errorf("unrelated key should NOT satisfy the group")
|
||||
}
|
||||
|
||||
wantRec := []string{"SERPER_API_KEY", "VERCEL_TOKEN"}
|
||||
if !stringSlicesEqual(reqNames(recommended), wantRec) {
|
||||
t.Errorf("recommended mismatch: got %v, want %v", reqNames(recommended), wantRec)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEnvRequirement_UnmarshalYAML proves the on-disk YAML shape
|
||||
// (scalar OR `{any_of: [...]}` block) round-trips into EnvRequirement
|
||||
// correctly. The preflight pipeline reads user-authored org.yaml
|
||||
|
||||
Loading…
Reference in New Issue
Block a user