NAME
Skilletor::Engine - sync, check and status for Claude Code – the reference's engine pipeline after the resolve
VERSION
version 0.001
SYNOPSIS
use Skilletor::Engine;
my $engine = Skilletor::Engine->new( home => $ENV{HOME}, project_dir => $project );
my $report = $engine->sync( scope => 'all', force => 0 ); # what `skilletor sync --json` prints
my $check = $engine->check( scope => 'all' ); # `skilletor check --json`
my $status = $engine->status( scope => 'all' ); # `skilletor status --json`
DESCRIPTION
The reference's engine (engine.ts) after the resolve: sync, check and status for Claude Code. It extends Skilletor::Resolver – a sync runs the very resolve that class reports, then builds every declared item in memory, applies it (Skilletor::Apply), writes the managed blocks (Skilletor::Gitignore) and the project's plugin requirement (Skilletor::ClaudeSettings), runs the briefing check (Skilletor::Briefing) and keeps the records check compares with (Skilletor::State). Most callers want "sync" in Skilletor.
What it writes is the reference's, byte for byte – installed files with their executable bits, the lock, the .gitignore blocks, .worktreeinclude, .claude/settings.json – and the reports are the reference's SyncReport, CheckReport and StatusReport, field for field, booleans as JSON::MaybeXS booleans. The state records (render-inputs.json with the same RENDER_FORMAT, sources-read.json, unreached.json) are the same bytes too, so a check of either implementation accepts what the other synced: the shared conformance cases and t/52-parity-sync.t hold both to that. Like the reference, a sync holds the sync lock ("with_lock" in Skilletor::State); check and status read without it.
Claude Code only
The port installs for Claude Code alone. A scope whose targets name another harness – "targets" with codex or hermes, or Codex detected on the machine without "targets" – is refused whole: sync, check and status return the report with an error and touch nothing. A project narrowed to no harness in use is no error (the reference's warning). A sync is also refused, before anything is written, when a scope holds state of another harness the reference would change: lock entries of Codex or Hermes (the reference removes them from their roots), block entries (sections of the Codex rules file) or a Codex rules file (skilletor-rules.md). Lock entries of a harness neither knows are kept untouched, as the reference keeps them. Run the skilletor CLI once on such a machine.
Templates
The port renders no Nunjucks. A declared item with a .njk file is never installed unrendered: the sync warns (skill greet@shared is a Nunjucks template (skills/greet/SKILL.md.njk) the Perl port of skilletor does not render; an installed copy stays) and leaves the item exactly as it is installed – files and lock entry, never deleted – as for an item whose source cannot be reached. For the records the item's source then counts as not reached: drift it keeps is not recorded as unreached, its sources-read.json entry stays what an earlier sync read, and the scope's render inputs are not recorded. So check – the port's and the reference's – keeps asking for a sync, and the reference's next sync renders the item. A renders-empty skip can only come from a render; such lock entries the reference left are kept like any other.
Errors
A config error, or no agent harness, is the report's error (the reference's message); nothing is touched. Like the reference, a sync that fails while it writes (Skilletor::Error::Apply, a file system error) throws: what landed is owned by the lock, its entries marked partial, and the scope's records are cleared so check asks for the retry. An unreadable lock throws Skilletor::Error::Lock, a sync lock held too long Skilletor::Error::LockTimeout.
is_git_work_tree
is_git_work_tree => sub { my ( $dir ) = @_; ... }
Does a user-scope root lie inside a git work tree (spec §6.4)? Default: ask git ("is_git_work_tree" in Skilletor::Gitignore). A user root gets a .gitignore block only inside one.
git_tracked
git_tracked => sub { my ( $dir, $paths ) = @_; return [ ...tracked... ] }
Which of the written paths does git track? Default: one git ls-files ("git_tracked" in Skilletor::Gitignore). Each tracked item is a warning with the command that untracks it.
git_remote
git_remote => sub { my ( $project_dir ) = @_; return $url }
The project's origin, a render input of the project scope. Default: ask git.
plugin_root
CLAUDE_PLUGIN_ROOT by default: the running plugin's install, which names skilletor's marketplace for the settings requirement. Unset outside a plugin.
sync
my $report = $engine->sync( scope => 'user' | 'project' | 'all', force => 0 );
{
scopes => [ {
scope => 'user',
added => [ { key, type, name, source } ], updated => [...], removed => [...],
unchanged => [...], skipped => [...],
conflicts => [ { path, replace?, notFile? } ], overwritten => [ { path } ],
warnings => [...], trustRequests => [ { name, kind, url } ],
briefingMissing? => [ { key, harness, missing => [...] } ],
gitignoreUpdated? => [ '.claude/.gitignore', '.worktreeinclude' ],
settingsUpdated? => { path, enabled => [...], declared => [...] },
settingsSkipped? => [...],
} ],
error? => $message,
}
force adopts a file in an item's way, deletes one at a claimed path or replaces a link (spec §6.3). "report_text" in Skilletor::Report words the report as the CLI does. The sync honours "fetch" in Skilletor::Resolver: never syncs from the cache alone.
check
my $check = $engine->check( scope => 'all' );
# { changed, sources => [ { name, scope, changed } ], warnings,
# targetsChanged?, layoutChanged?, declaredChanged?, varsChanged?, trustRequests?, error? }
Whether a sync is due, without one: source versions (git ls-remote, an HTTP HEAD; a local source always counts as changed), the drift of the lock against the config and the layout, the render inputs. It reads no installed file – an edited file is a sync's to overwrite, not drift.
status
my $status = $engine->status( scope => 'all' );
# { scopes => [ { scope, targets, declared => [ { key, source, installed, via?, skipped?,
# partial?, briefingMissing? } ], orphans, wildcards, bundles, trustRequests,
# sourceVersions } ], projectIsHome?, error? }
Offline and read-only: the lock and the config.
RENDER_FORMAT
The render format hashed into the render inputs (the reference's, 2).
SUPPORT
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.