You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of the persona-kit consolidation. Depends on #69 (or can run in parallel with #69 once #66 lands). This is issue 7 of 8.
Goal
Bring persona-kit's test coverage to a level worthy of being the single source of truth for persona instantiation across both repos. Tests ported in #65 covered the moved-in functions individually. This issue adds:
End-to-end persona-JSON → plan → execute round-trips.
Cross-harness coverage (claude, codex, opencode) for every public function.
Failure-path coverage with cleanup verification.
Property/snapshot tests for buildPersonaSpawnPlan so future refactors don't silently change the plan shape.
Plan JSON-serialization tests (the plan is a contract — must round-trip through JSON.parse(JSON.stringify(...))).
Test files to add (in packages/persona-kit/src/)
plan.test.ts — buildPersonaSpawnPlan
Snapshot test: minimal persona (id + intent + best tier with harness/model/systemPrompt) → plan.
Snapshot test: full-featured persona (skills + mount + claudeMdContent + agentsMdContent + inputs + env + mcpServers + permissions) — across all three harnesses.
Tier cascade: persona with top-level harness/model + per-tier override → plan reflects the requested tier.
installRoot option: claude harness only → plan's skills.installs[*].cwd reflects the install root; codex/opencode + installRoot throws with a clear error.
Empty skills array: plan has skills.installs.length === 0 and (for claude) the scaffold prelude from buildInstallArtifacts is still emitted.
JSON round-trip: every plan value survives JSON.parse(JSON.stringify(plan)) byte-for-byte.
execute.test.ts — executePersonaSpawnPlan
Happy path with fixture install command (e.g., argv: ['echo', 'installed-x']):
Verify execution order: mount → skills → sidecars → configFiles.
Verify dispose() reverses everything in LIFO order.
Verify cwd state matches pre-execute state after dispose.
Failure mid-execution: skills install step throws → mount handle disposed → no orphan files → original error propagates.
Failure at sidecar step: skills succeeded, sidecar throws → skills cleanup runs → mount disposed → cwd is clean.
dispose() is idempotent — calling twice doesn't double-delete or throw.
Two concurrent executes against different cwds work independently (uses --prefix or per-cwd state correctly).
parse.test.ts — schema validation
Persona with extra unknown fields → parse succeeds, unknowns are preserved or stripped per the workload-router precedent (port the existing test cases).
Persona with malformed tiers → throws with file path + field path in the error message.
Persona with malformed skills[i].source → does NOT throw at parse time; defers to plan time so a typo only blows up its own persona, not the entire directory.
Mount validation: invalid permission mode → throws.
Input validation: input names must match [A-Z_][A-Z0-9_]* (env-var convention).
load.test.ts — loadPersonas cascade
Cascade order matches the documented contract: cwd → user dir → built-in.
Lower-priority persona is overridden by higher-priority (override fully replaces; no array concat).
extends resolves one level deep; circular extends throws with both paths in the error.
Missing search dir is silently skipped (not an error).
Malformed JSON file → load continues; warning collected; other personas in the dir still load.
skills.test.ts — runSkillInstalls
Empty plan → no-op (no spawn calls).
Multi-skill plan → installs run sequentially in plan order; one fails → subsequent ones skipped; cleanup runs for already-installed.
cleanupSkillsOnDispose: false → installs persist after dispose().
mount.test.ts — applyPersonaMount
Mount with ignore patterns → mount handle's dispose() reverses any side effects (mock @relayfile/local-mount for unit tests; real integration test in workforce CLI smoke).
Persona without mount → applyPersonaMount(undefined, ...) returns undefined (no handle).
sidecars.test.ts — writePersonaSidecars
claudeMdContent inline → file written, restore on dispose puts the original content back (or removes the file if there was none).
claudeMd: 'path/to/file.md' → file resolved relative to persona source, copied to spawn cwd.
Both claudeMd and claudeMdContent set → throws with a clear "pick one" error.
Cross-harness fixtures
Create fixture personas in packages/persona-kit/test/fixtures/:
Snapshot the buildPersonaSpawnPlan output for each. Future PRs that change plan shape have to explicitly update snapshots, surfacing the change in review.
Property tests (optional but valuable)
If the test framework supports it (fast-check or similar):
For any valid persona, JSON.parse(JSON.stringify(buildPersonaSpawnPlan(persona))) deep-equals the original plan.
For any valid persona, executePersonaSpawnPlan(plan, opts).then(h => h.dispose()) leaves the cwd in its initial state (run inside a temp dir each iteration).
Constraints
Use real child_process.spawn with echo (or a test-only fixture binary) for skill-install execution tests. Do not mock child_process — the contract we're testing IS the subprocess interaction.
Part of the persona-kit consolidation. Depends on #69 (or can run in parallel with #69 once #66 lands). This is issue 7 of 8.
Goal
Bring persona-kit's test coverage to a level worthy of being the single source of truth for persona instantiation across both repos. Tests ported in #65 covered the moved-in functions individually. This issue adds:
buildPersonaSpawnPlanso future refactors don't silently change the plan shape.JSON.parse(JSON.stringify(...))).Test files to add (in
packages/persona-kit/src/)plan.test.ts—buildPersonaSpawnPlaninstallRootoption: claude harness only → plan'sskills.installs[*].cwdreflects the install root; codex/opencode +installRootthrows with a clear error.skills.installs.length === 0and (for claude) the scaffoldpreludefrombuildInstallArtifactsis still emitted.planvalue survivesJSON.parse(JSON.stringify(plan))byte-for-byte.execute.test.ts—executePersonaSpawnPlanargv: ['echo', 'installed-x']):dispose()reverses everything in LIFO order.dispose()is idempotent — calling twice doesn't double-delete or throw.--prefixor per-cwd state correctly).parse.test.ts— schema validationtiers→ throws with file path + field path in the error message.skills[i].source→ does NOT throw at parse time; defers to plan time so a typo only blows up its own persona, not the entire directory.[A-Z_][A-Z0-9_]*(env-var convention).load.test.ts—loadPersonascascadeextendsresolves one level deep; circular extends throws with both paths in the error.skills.test.ts—runSkillInstallscleanupSkillsOnDispose: false→ installs persist afterdispose().mount.test.ts—applyPersonaMountdispose()reverses any side effects (mock@relayfile/local-mountfor unit tests; real integration test in workforce CLI smoke).applyPersonaMount(undefined, ...)returnsundefined(no handle).sidecars.test.ts—writePersonaSidecarsclaudeMdContentinline → file written, restore on dispose puts the original content back (or removes the file if there was none).claudeMd: 'path/to/file.md'→ file resolved relative to persona source, copied to spawn cwd.claudeMdandclaudeMdContentset → throws with a clear "pick one" error.Cross-harness fixtures
Create fixture personas in
packages/persona-kit/test/fixtures/:minimal-claude.jsonminimal-codex.jsonminimal-opencode.jsonfull-claude.json(skills, mount, claudeMdContent, inputs, mcpServers)full-codex.json(same fields, codex harness)full-opencode.jsonbroken-skill-source.jsonSnapshot the
buildPersonaSpawnPlanoutput for each. Future PRs that change plan shape have to explicitly update snapshots, surfacing the change in review.Property tests (optional but valuable)
If the test framework supports it (fast-check or similar):
JSON.parse(JSON.stringify(buildPersonaSpawnPlan(persona)))deep-equals the original plan.executePersonaSpawnPlan(plan, opts).then(h => h.dispose())leaves the cwd in its initial state (run inside a temp dir each iteration).Constraints
child_process.spawnwithecho(or a test-only fixture binary) for skill-install execution tests. Do not mockchild_process— the contract we're testing IS the subprocess interaction.@relayfile/local-mountin unit tests; verify real integration in the workforce CLI smoke test ([persona-kit 4/8] Migrate workforce CLI to use @agentworkforce/persona-kit #67) and the relay-side issue.Verification
pnpm --filter @agentworkforce/persona-kit testpasses with full coverage.buildPersonaSpawnPlancauses at least one snapshot test to fail (sanity check that snapshots are real).Reference
Plan section 1.7.