Skip to content

[persona-kit 7/8] Comprehensive test suite for @agentworkforce/persona-kit #70

@willwashburn

Description

@willwashburn

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.tsbuildPersonaSpawnPlan

  • 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.tsexecutePersonaSpawnPlan

  • 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.tsloadPersonas 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.tsrunSkillInstalls

  • 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.tsapplyPersonaMount

  • 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.tswritePersonaSidecars

  • 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/:

  • minimal-claude.json
  • minimal-codex.json
  • minimal-opencode.json
  • full-claude.json (skills, mount, claudeMdContent, inputs, mcpServers)
  • full-codex.json (same fields, codex harness)
  • full-opencode.json
  • broken-skill-source.json

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.
  • Mock @relayfile/local-mount in 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.
  • Tests run in CI on Linux + macOS at minimum. Windows is best-effort if the monorepo supports it; otherwise skip with a clear note.

Verification

  • pnpm --filter @agentworkforce/persona-kit test passes with full coverage.
  • Coverage report shows >90% line coverage for persona-kit, >95% for the parse/plan paths.
  • CI passes on all supported platforms.
  • Adding a one-character change to buildPersonaSpawnPlan causes at least one snapshot test to fail (sanity check that snapshots are real).

Reference

Plan section 1.7.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions