fluncle 0.181.0 → 0.182.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fluncle.mjs +75 -2
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -561,7 +561,7 @@ function parseVersion(version) {
|
|
|
561
561
|
var currentVersion;
|
|
562
562
|
var init_version = __esm(() => {
|
|
563
563
|
init_output();
|
|
564
|
-
currentVersion = "0.
|
|
564
|
+
currentVersion = "0.182.0".trim() ? "0.182.0".trim() : "0.1.0";
|
|
565
565
|
});
|
|
566
566
|
|
|
567
567
|
// src/update-notifier.ts
|
|
@@ -2260,7 +2260,7 @@ function parseVersion2(version) {
|
|
|
2260
2260
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2261
2261
|
var init_version2 = __esm(() => {
|
|
2262
2262
|
init_output();
|
|
2263
|
-
currentVersion2 = "0.
|
|
2263
|
+
currentVersion2 = "0.182.0".trim() ? "0.182.0".trim() : "0.1.0";
|
|
2264
2264
|
});
|
|
2265
2265
|
|
|
2266
2266
|
// ../../packages/registry/src/index.ts
|
|
@@ -3212,6 +3212,19 @@ var init_src = __esm(() => {
|
|
|
3212
3212
|
title: "Recording MBIDs",
|
|
3213
3213
|
weights: { status: "hidden" }
|
|
3214
3214
|
},
|
|
3215
|
+
{
|
|
3216
|
+
command: "fluncle admin backfills artist-edges",
|
|
3217
|
+
exposedContent: [
|
|
3218
|
+
"fold each edge-less track's artists_json names onto existing artist identities → track_artists edges"
|
|
3219
|
+
],
|
|
3220
|
+
kind: "cron",
|
|
3221
|
+
name: "cron.artist-edges",
|
|
3222
|
+
operatorNotes: "every 60m, run by a rave-02 HOST systemd timer (docs/agents/hermes/artist-edges-timer/). The track_artists GRAPH BACKFILL (RFC artist-primary-capture, slice 0): the graph is crawl-era-only (born 2026-07-15) — only ~12.3k of ~37.5k tracks carry edges — so this folds each edge-less track's `artists_json` NAMES onto EXISTING `artists` identities and writes the `track_artists` edges, making the graph as full as honest matching allows (slice 1's identity-keyed capture authorization reads it). The matcher is IDENTITY-HONEST: each name matches by exact case-insensitive fold, then via `artist_aliases` (kind='name', status auto|confirmed — the search resolver's alias semantics); a fold two distinct identities share is ambiguous and matches nothing (fail-closed). It MINTS NOTHING — a bare name is not enough identity to create an entity — and reports the UNMATCHED RESIDUAL (credited names with no identity), which decides whether a later paced MusicBrainz credit-sweep is worth running. METADATA / GRAPH IDENTITY ONLY — it certifies nothing and publishes nothing (agent tier, the `backfill_recording_mbids` precedent). Worker-paced with NO vendor call (pure DB set-based matching, so no rate limit / circuit breaker): one bounded batch per tick folds the whole ~1.8k-row artist+alias corpus into one in-memory map and matches each track batch against it. The `tracks` row carries the durable reliability state (the `artist_edges_backfilled_at` stamp on EVERY visited row — matched, partial, or zero — so the worklist drains and a re-run is a no-op). New tracks are minted WITH edges (publish path + crawler link), so this catches history up and drains in a handful of ticks. Zero LLM tokens. Source: docs/agents/hermes/scripts/artist-edges-sweep.*. See docs/artist-relationship.md.",
|
|
3223
|
+
probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-artist-edges", kind: "cron" },
|
|
3224
|
+
statusDescription: "folds artists_json names onto artist identities",
|
|
3225
|
+
title: "Artist edges",
|
|
3226
|
+
weights: { status: "hidden" }
|
|
3227
|
+
},
|
|
3215
3228
|
{
|
|
3216
3229
|
command: "fluncle admin backfills label-lineage",
|
|
3217
3230
|
exposedContent: [
|
|
@@ -4136,6 +4149,7 @@ __export(exports_admin_tracks, {
|
|
|
4136
4149
|
backfillRecordingMbidsCommand: () => backfillRecordingMbidsCommand,
|
|
4137
4150
|
backfillLastfmCommand: () => backfillLastfmCommand,
|
|
4138
4151
|
backfillDiscogsCommand: () => backfillDiscogsCommand,
|
|
4152
|
+
backfillArtistEdgesCommand: () => backfillArtistEdgesCommand,
|
|
4139
4153
|
backfillAppleMusicCommand: () => backfillAppleMusicCommand,
|
|
4140
4154
|
backfillAppleCatalogueCommand: () => backfillAppleCatalogueCommand
|
|
4141
4155
|
});
|
|
@@ -4328,6 +4342,13 @@ async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
|
|
|
4328
4342
|
}
|
|
4329
4343
|
return adminApiPost(`/api/admin/backfill/recording-mbids?${params.toString()}`);
|
|
4330
4344
|
}
|
|
4345
|
+
async function backfillArtistEdgesCommand(limit, dryRun, cursor) {
|
|
4346
|
+
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4347
|
+
if (cursor) {
|
|
4348
|
+
params.set("cursor", cursor);
|
|
4349
|
+
}
|
|
4350
|
+
return adminApiPost(`/api/admin/backfill/artist-edges?${params.toString()}`);
|
|
4351
|
+
}
|
|
4331
4352
|
async function vehiclesCommand(limit) {
|
|
4332
4353
|
const tracks = await fetchAdminTracks({ hasVideo: true, max: limit, order: "desc" });
|
|
4333
4354
|
return tracks.map((track) => ({
|
|
@@ -9741,6 +9762,10 @@ function addAdminCommands(program2) {
|
|
|
9741
9762
|
const { backfillRecordingMbidsCommand: backfillRecordingMbidsCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
9742
9763
|
await runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2);
|
|
9743
9764
|
});
|
|
9765
|
+
backfill.command("artist-edges").description("Fold artists_json names onto existing artist identities \u2192 track_artists").option("--dry-run", "Report the classification without writing edges or stamps", false).option("--limit <limit>", "Max tracks to visit", "200").option("--json", "Print JSON", false).action(async (options) => {
|
|
9766
|
+
const { backfillArtistEdgesCommand: backfillArtistEdgesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
9767
|
+
await runBackfillArtistEdges(options, backfillArtistEdgesCommand2);
|
|
9768
|
+
});
|
|
9744
9769
|
backfill.command("cover-masters").description("Resolve owned \u22641200\xB2 cover masters (album/artist) into R2").option("--kind <kind>", "Which entity to drain: album | artist", "album").option("--dry-run", "Report the eligible worklist without any fetch or write", false).option("--retry-none", "First re-queue a bounded batch of terminal none rows to pending, then run the pass", false).option("--limit <limit>", "Max entities to process", "50").option("--json", "Print JSON", false).action(async (options) => {
|
|
9745
9770
|
const { backfillCoverMastersCommand: backfillCoverMastersCommand2 } = await Promise.resolve().then(() => (init_admin_covers(), exports_admin_covers));
|
|
9746
9771
|
await runBackfillCoverMasters(options, backfillCoverMastersCommand2);
|
|
@@ -10740,6 +10765,54 @@ async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2
|
|
|
10740
10765
|
process.exitCode = 1;
|
|
10741
10766
|
}
|
|
10742
10767
|
}
|
|
10768
|
+
async function runBackfillArtistEdges(options, backfillArtistEdgesCommand2) {
|
|
10769
|
+
const limit = parseListLimit(options.limit) ?? 200;
|
|
10770
|
+
const fullyMatched = [];
|
|
10771
|
+
const partiallyMatched = [];
|
|
10772
|
+
const zeroMatched = [];
|
|
10773
|
+
let cursor;
|
|
10774
|
+
let dryRun = options.dryRun;
|
|
10775
|
+
let edgesWritten = 0;
|
|
10776
|
+
let scanned = 0;
|
|
10777
|
+
let unmatchedNames = 0;
|
|
10778
|
+
while (scanned < limit) {
|
|
10779
|
+
const remaining = limit - scanned;
|
|
10780
|
+
const result = await backfillArtistEdgesCommand2(remaining, options.dryRun, cursor);
|
|
10781
|
+
dryRun = result.dryRun;
|
|
10782
|
+
scanned += result.scanned;
|
|
10783
|
+
edgesWritten += result.edgesWritten;
|
|
10784
|
+
unmatchedNames += result.unmatchedNames;
|
|
10785
|
+
fullyMatched.push(...result.fullyMatched);
|
|
10786
|
+
partiallyMatched.push(...result.partiallyMatched);
|
|
10787
|
+
zeroMatched.push(...result.zeroMatched);
|
|
10788
|
+
if (!options.json) {
|
|
10789
|
+
const verb2 = result.dryRun ? "would write" : "wrote";
|
|
10790
|
+
console.log(` \u2026visited ${result.scanned}; ${verb2} ${result.edgesWritten} edge(s); ${result.fullyMatchedCount} full, ${result.partiallyMatchedCount} partial, ${result.zeroMatchedCount} zero; ${result.unmatchedNames} name(s) unmatched`);
|
|
10791
|
+
}
|
|
10792
|
+
if (result.nextCursor === null) {
|
|
10793
|
+
break;
|
|
10794
|
+
}
|
|
10795
|
+
cursor = result.nextCursor;
|
|
10796
|
+
}
|
|
10797
|
+
if (options.json) {
|
|
10798
|
+
printJson({
|
|
10799
|
+
dryRun,
|
|
10800
|
+
edgesWritten,
|
|
10801
|
+
fullyMatched,
|
|
10802
|
+
fullyMatchedCount: fullyMatched.length,
|
|
10803
|
+
ok: true,
|
|
10804
|
+
partiallyMatched,
|
|
10805
|
+
partiallyMatchedCount: partiallyMatched.length,
|
|
10806
|
+
scanned,
|
|
10807
|
+
unmatchedNames,
|
|
10808
|
+
zeroMatched,
|
|
10809
|
+
zeroMatchedCount: zeroMatched.length
|
|
10810
|
+
});
|
|
10811
|
+
return;
|
|
10812
|
+
}
|
|
10813
|
+
const verb = dryRun ? "Would write" : "Wrote";
|
|
10814
|
+
console.log(`${verb} ${edgesWritten} track_artists edge(s) over ${scanned} track(s): ${fullyMatched.length} fully matched, ${partiallyMatched.length} partially, ${zeroMatched.length} with none; ${unmatchedNames} credited name(s) left unmatched.`);
|
|
10815
|
+
}
|
|
10743
10816
|
async function runArtistResolveQueue(options, listArtistsCommand2) {
|
|
10744
10817
|
const limit = parseListLimit(options.limit);
|
|
10745
10818
|
const result = await listArtistsCommand2(limit ?? 50);
|
package/package.json
CHANGED