NAME

Skilletor::State - Instance state under ~/.claude/skilletor – trust, the sync lock and the records a sync keeps

VERSION

version 0.001

SYNOPSIS

use Skilletor::State;

my $state = Skilletor::State->for_home( $ENV{HOME} );     # ~/.claude/skilletor
$state->trust( team => { kind => 'git', address => 'https://example.com/team/skills' } );

my $ok = $state->is_trusted( team => { kind => 'git', address => '…', origin => 'project' } );

DESCRIPTION

The reference's state.ts: trust (spec §4.3), the sync lock (spec §6.5) and the records a sync keeps for check (spec §14.3). Both implementations read and write the same files, the same bytes: entries keep their order, a new key is appended, a file is written only when its content changes, and an unreadable file counts as empty.

  • trust.json: a source only a project config declares is not fetched until the user trusts it – name, backend kind and address.

  • unreached.json, sources-read.json, render-inputs.json: per scope, keyed by its lock path – the drift the last sync left in place with every source at hand (k70), what it read of each source and through which backend (k80), the hash of what it rendered with (k76). Skilletor::Engine writes and reads them.

  • last-check.json: when the in-session check last ran per key.

  • hermes-home.json: the Hermes home the user scope was synced under; the port, which never targets Hermes, only ever drops it as the reference does.

Pending reports belong to the reference's hooks and are not ported.

root

The state directory; created when the object is.

for_home

Skilletor::State->for_home($home);

default_root

<home>/.claude/skilletor.

trust

$state->trust( $name, { kind => 'git', address => $url } );

Replaces an earlier entry of the name.

is_trusted

$state->is_trusted( $name, { kind => ..., address => ..., origin => 'user' | 'project' } );

unreached

my $ids = $state->unreached($lock_path);   # [ 'missing:skills/x@shared', ... ]

put_unreached

$state->put_unreached( $lock_path, \@ids );   # sorted, deduplicated; none drops the key

sources_read

my $read = $state->sources_read($lock_path);
# { shared => { kind, address, version, ref?, path? } } – malformed entries left out

put_sources_read

$state->put_sources_read( $lock_path, \%read );   # names sorted; none drops the key

render_inputs

my $hash = $state->render_inputs($lock_path);   # 'sha256:…' or undef

put_render_inputs

$state->put_render_inputs( $lock_path, $hash );   # undef drops it

is_due

$state->is_due( $key, $interval_seconds );

True when no check was marked for the key, or the interval has passed.

mark_checked

clear_checked

hermes_home

put_hermes_home

$state->put_hermes_home(undef);   # drop the record

with_lock

my $result = $state->with_lock( sub { ... }, timeout_ms => 5000 );

Runs the code holding <root>/sync.lock/, the mutex the reference takes around every resolve, sync and read of the source cache – the same directory, taken the same way, so the two implementations exclude each other. It waits for another holder up to timeout_ms (default 5000), breaking a lock a dead run left (no refresh for stale_ms, default 300000; an owner-less directory after grace_ms, default 2000), then throws Skilletor::Error::LockTimeout. While the code runs, a forked child refreshes the owner record every refresh_ms (default 10000; none on Windows). The lock is released however the code ends; its result (in the caller's context) is returned and its exception rethrown.

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.