Aller au contenu

Pattern Materializer

Un Materializer transforme un artefact logique (agent, hook, skill) en fichiers sur disque dans un dossier projet. Le pattern est codifié dans ADR 0003 : le core ne fait jamais d’écriture directe pour ces artefacts ; il passe par un port dédié.


Sans Materializer, le core écrirait directement des fichiers .claude/agents/*.md, .claude/hooks/*.json, etc. Cela créerait plusieurs problèmes :

  • Couplage : le core connaîtrait le format de fichier d’un agent (Markdown YAML, JSON, etc.) — ce n’est pas son métier.
  • Frontières : impossible de mocker l’écriture pour les tests sans patcher fs.
  • Évolution : changer le format (par exemple ajouter une signature) demanderait de toucher au core.

Le Materializer isole tout ça derrière un port :

core/ports/outbound/agent-workspace-materializer.ts
export interface AgentWorkspaceMaterializer {
materialize(input: MaterializeAgentInput): Promise<MaterializedAgent>;
unmaterialize(input: { projectPath: string; sourceId: string }): Promise<void>;
list(projectPath: string): Promise<readonly MaterializedAgent[]>;
}

Le use-case buildForCatalogue consomme ce port. L’implémentation concrète ClaudeAgentWorkspaceMaterializer connaît, elle, le format Claude Code.


MaterializerCibleFormat de sortie
ClaudeAgentWorkspaceMaterializer.claude/agents/<slug>.mdMarkdown + frontmatter YAML
HookWorkspaceMaterializer.claude/hooks/<name>.jsonJSON
SkillWorkspaceMaterializer.claude/skills/<name>/Dossier + manifest

Tous écrivent dans le dossier projet (<projectPath>/.claude/), jamais ailleurs. L’allowlist Filesystem valide les chemins.


  • Vous voulez exposer un artefact logique (par exemple “policy de gouvernance”) en tant que fichier consommable par un outil externe (un éditeur, un agent IA, un CI).
  • Le format de sortie peut évoluer sans que le core en soit affecté.
  • Plusieurs cibles peuvent exister (par exemple un agent pour Claude Code + un agent pour Codex CLI → deux Materializers).

Évitez le pattern Materializer pour des écritures purement internes (sessions chat, mémoire) — celles-ci passent par leur store dédié.


Install agent (use-case forCatalogue.install)
[Lit profil depuis Cortex]
[Materializer.materialize → écrit .claude/agents/<slug>.md]
[Met à jour <project>/.arka-deck/agents/installed.json]
EventBus → 'agent.installed' (async)
Remove agent (forCatalogue.remove)
[Materializer.unmaterialize → supprime .claude/agents/<slug>.md]
[Met à jour installed.json]
EventBus → 'agent.removed' (async)

Le suivi installed.json permet une purge sûre : seuls les fichiers tracés sont supprimés, jamais les artefacts de l’utilisateur (autres agents non-arka-deck dans .claude/agents/).


L’implémentation concrète est testable comme tout adapter. Pour les use-cases qui le consomment, on injecte un FakeAgentWorkspaceMaterializer :

const fakeMaterializer: AgentWorkspaceMaterializer = {
async materialize(input) { return { /* ... */ }; },
async unmaterialize() {},
async list() { return []; },
};
const forCatalogue = buildForCatalogue({
catalogueClient,
agentMaterializer: fakeMaterializer,
// ...
});