@fml-inc/fml 0.8.6 → 0.8.7-rc.1

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.
@@ -183,10 +183,10 @@ function setSyncEnabled(settingsFile, enabled) {
183
183
  }
184
184
 
185
185
  // ../../packages/panopticon/src/artifacts.ts
186
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
187
- import fs7 from "fs";
186
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
187
+ import fs8 from "fs";
188
188
  import os3 from "os";
189
- import path17 from "path";
189
+ import path18 from "path";
190
190
 
191
191
  // ../../packages/agent-targets/src/artifacts/targets/claude.ts
192
192
  import path8 from "path";
@@ -375,6 +375,7 @@ function canonicalGitHubRepository(remote) {
375
375
  // ../../packages/agent-targets/src/artifacts/repository-context.ts
376
376
  var MAX_ARTIFACT_CONTEXT_PREFIX_BYTES = 256 * 1024;
377
377
  var MAX_ARTIFACT_CONTEXT_RECORDS = 64;
378
+ var ARTIFACT_CONTEXT_READ_CHUNK_BYTES = 32 * 1024;
378
379
  var MAX_CWD_BYTES = 4096;
379
380
  var MAX_BRANCH_BYTES = 1024;
380
381
  function utf8Bytes2(value) {
@@ -402,24 +403,39 @@ function readJsonlContextRecords(sourcePath) {
402
403
  const stat = fs5.fstatSync(file);
403
404
  const byteLimit = Math.min(stat.size, MAX_ARTIFACT_CONTEXT_PREFIX_BYTES);
404
405
  if (byteLimit <= 0) return [];
405
- const buffer = Buffer.allocUnsafe(byteLimit);
406
- const bytesRead = fs5.readSync(file, buffer, 0, byteLimit, 0);
407
- let content = buffer.subarray(0, bytesRead).toString("utf8");
408
- if (bytesRead < stat.size) {
409
- const lastNewline = content.lastIndexOf("\n");
410
- if (lastNewline < 0) return [];
411
- content = content.slice(0, lastNewline + 1);
412
- }
413
406
  const records = [];
414
- for (const line of content.split(/\r?\n/)) {
415
- if (records.length >= MAX_ARTIFACT_CONTEXT_RECORDS) break;
407
+ const parseLine = (line) => {
416
408
  const jsonLine = line.startsWith("\uFEFF") ? line.slice(1) : line;
417
- if (!jsonLine.trim()) continue;
409
+ if (!jsonLine.trim()) return;
418
410
  try {
419
411
  const record = recordValue(JSON.parse(jsonLine));
420
412
  if (record) records.push(record);
421
413
  } catch {
422
414
  }
415
+ };
416
+ let carry = Buffer.alloc(0);
417
+ let position = 0;
418
+ const chunk = Buffer.allocUnsafe(ARTIFACT_CONTEXT_READ_CHUNK_BYTES);
419
+ while (position < byteLimit && records.length < MAX_ARTIFACT_CONTEXT_RECORDS) {
420
+ const want = Math.min(chunk.length, byteLimit - position);
421
+ const bytesRead = fs5.readSync(file, chunk, 0, want, position);
422
+ if (bytesRead <= 0) break;
423
+ position += bytesRead;
424
+ const buffered = Buffer.concat([carry, chunk.subarray(0, bytesRead)]);
425
+ const lastNewline = buffered.lastIndexOf(10);
426
+ if (lastNewline < 0) {
427
+ carry = buffered;
428
+ continue;
429
+ }
430
+ const complete = buffered.subarray(0, lastNewline + 1).toString("utf8");
431
+ carry = Buffer.from(buffered.subarray(lastNewline + 1));
432
+ for (const line of complete.split(/\r?\n/)) {
433
+ if (records.length >= MAX_ARTIFACT_CONTEXT_RECORDS) break;
434
+ parseLine(line);
435
+ }
436
+ }
437
+ if (carry.length > 0 && position >= stat.size && records.length < MAX_ARTIFACT_CONTEXT_RECORDS) {
438
+ parseLine(carry.toString("utf8"));
423
439
  }
424
440
  return records;
425
441
  } catch {
@@ -922,41 +938,78 @@ function pruneMaterializedCache(input) {
922
938
 
923
939
  // ../../packages/panopticon/src/repository-context.ts
924
940
  import { execFileSync } from "child_process";
941
+ import { createHash as createHash4 } from "crypto";
942
+ import fs7 from "fs";
943
+ import path17 from "path";
925
944
  var MAX_CWD_BYTES2 = 4096;
926
945
  var GIT_OUTPUT_BYTES = 16 * 1024;
946
+ var GIT_CONFIG_OUTPUT_BYTES = 4 * 1024 * 1024;
927
947
  var GIT_TIMEOUT_MS = 1e3;
928
948
  var DEFAULT_GITHUB_ORIGIN_CACHE_MAX_ENTRIES = 512;
949
+ var ReadOnlyGitCommandError = class extends Error {
950
+ constructor() {
951
+ super("read-only git command failed");
952
+ }
953
+ };
929
954
  function validCwd(cwd) {
930
955
  const trimmed = cwd.trim();
931
956
  return trimmed === cwd && trimmed.length > 0 && !new RegExp("\\p{Cc}", "u").test(trimmed) && Buffer.byteLength(trimmed, "utf8") <= MAX_CWD_BYTES2;
932
957
  }
933
- var runReadOnlyGit = (cwd, args) => {
958
+ function runReadOnlyGit(cwd, args, maxBuffer = GIT_OUTPUT_BYTES) {
934
959
  if (!validCwd(cwd)) return null;
935
960
  try {
936
961
  return execFileSync("git", ["-C", cwd, ...args], {
937
962
  encoding: "utf8",
938
- maxBuffer: GIT_OUTPUT_BYTES,
963
+ maxBuffer,
939
964
  timeout: GIT_TIMEOUT_MS,
940
965
  stdio: ["ignore", "pipe", "ignore"],
941
966
  windowsHide: true
942
967
  }).trim() || null;
968
+ } catch (error) {
969
+ const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
970
+ if (status === 2 && args.length === 3 && args[0] === "remote" && args[1] === "get-url" && args[2] === "origin") {
971
+ return null;
972
+ }
973
+ throw new ReadOnlyGitCommandError();
974
+ }
975
+ }
976
+ function resolveGitHubOriginResult(cwd, runGit = runReadOnlyGit) {
977
+ let remote;
978
+ try {
979
+ remote = runGit(cwd, ["remote", "get-url", "origin"]);
943
980
  } catch {
944
- return null;
981
+ return { origin: null, definitive: false };
945
982
  }
946
- };
947
- function resolveGitHubOrigin(cwd, runGit = runReadOnlyGit) {
948
- const remote = runGit(cwd, ["remote", "get-url", "origin"]);
949
983
  const repository = remote ? canonicalGitHubRepository(remote) : null;
950
- if (!repository) return null;
951
- const branchResult = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
984
+ if (!repository) return { origin: null, definitive: true };
985
+ let branchResult;
986
+ try {
987
+ branchResult = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
988
+ } catch {
989
+ return { origin: { repository }, definitive: false };
990
+ }
952
991
  const branch = branchResult === "HEAD" ? null : branchResult;
953
- const commitHash = runGit(cwd, ["rev-parse", "HEAD"]);
992
+ let commitHash;
993
+ try {
994
+ commitHash = runGit(cwd, ["rev-parse", "HEAD"]);
995
+ } catch {
996
+ return {
997
+ origin: { repository, ...branch ? { branch } : {} },
998
+ definitive: false
999
+ };
1000
+ }
954
1001
  return {
955
- repository,
956
- ...branch ? { branch } : {},
957
- ...commitHash ? { commitHash } : {}
1002
+ origin: {
1003
+ repository,
1004
+ ...branch ? { branch } : {},
1005
+ ...commitHash ? { commitHash } : {}
1006
+ },
1007
+ definitive: true
958
1008
  };
959
1009
  }
1010
+ function resolveGitHubOrigin(cwd, runGit = runReadOnlyGit) {
1011
+ return resolveGitHubOriginResult(cwd, runGit).origin;
1012
+ }
960
1013
  function createMeasuredGitHubOriginResolver(resolve = (cwd) => resolveGitHubOrigin(cwd), nowMs = () => performance.now(), options = {}) {
961
1014
  const cache = /* @__PURE__ */ new Map();
962
1015
  const maxCacheEntries = options.maxCacheEntries ?? DEFAULT_GITHUB_ORIGIN_CACHE_MAX_ENTRIES;
@@ -1023,11 +1076,172 @@ function createMeasuredGitHubOriginResolver(resolve = (cwd) => resolveGitHubOrig
1023
1076
  })
1024
1077
  };
1025
1078
  }
1026
- function createArtifactDiscoveryGitHubOriginResolver() {
1027
- return createMeasuredGitHubOriginResolver(
1028
- (cwd) => resolveGitHubOrigin(cwd),
1029
- () => performance.now()
1030
- );
1079
+ var PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION = 2;
1080
+ var DEFAULT_PERSISTENT_ORIGIN_CACHE_MAX_ENTRIES = 512;
1081
+ function fileContentIdentity(filePath) {
1082
+ try {
1083
+ const contents = fs7.readFileSync(filePath);
1084
+ return `sha256:${createHash4("sha256").update(contents).digest("hex")}`;
1085
+ } catch (error) {
1086
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
1087
+ return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
1088
+ }
1089
+ }
1090
+ function packedRefsIdentity(filePath) {
1091
+ try {
1092
+ const stat = fs7.statSync(filePath, { bigint: true });
1093
+ if (!stat.isFile() || stat.ino === 0n) return null;
1094
+ return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(
1095
+ ":"
1096
+ );
1097
+ } catch (error) {
1098
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
1099
+ return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
1100
+ }
1101
+ }
1102
+ function effectiveConfigIdentity(cwd) {
1103
+ if (["GIT_CONFIG", "GIT_DIR", "GIT_COMMON_DIR", "GIT_WORK_TREE"].some(
1104
+ (name) => process.env[name] !== void 0
1105
+ )) {
1106
+ return null;
1107
+ }
1108
+ try {
1109
+ const config = runReadOnlyGit(
1110
+ cwd,
1111
+ ["config", "--includes", "--null", "--list"],
1112
+ GIT_CONFIG_OUTPUT_BYTES
1113
+ );
1114
+ return createHash4("sha256").update(config ?? "").digest("hex");
1115
+ } catch {
1116
+ return null;
1117
+ }
1118
+ }
1119
+ function readSmallText(filePath) {
1120
+ try {
1121
+ return fs7.readFileSync(filePath, "utf8").trim();
1122
+ } catch {
1123
+ return null;
1124
+ }
1125
+ }
1126
+ function findGitDirectory(cwd) {
1127
+ let current = path17.resolve(cwd);
1128
+ for (; ; ) {
1129
+ const candidate = path17.join(current, ".git");
1130
+ let stat;
1131
+ try {
1132
+ stat = fs7.statSync(candidate);
1133
+ } catch {
1134
+ stat = void 0;
1135
+ }
1136
+ if (stat?.isDirectory()) {
1137
+ return { gitDir: candidate, commonDir: candidate };
1138
+ }
1139
+ if (stat?.isFile()) {
1140
+ const pointer = readSmallText(candidate);
1141
+ const match = pointer ? /^gitdir:\s*(.+)$/u.exec(pointer) : null;
1142
+ if (!match?.[1]) return null;
1143
+ const gitDir = path17.resolve(current, match[1]);
1144
+ const common = readSmallText(path17.join(gitDir, "commondir"));
1145
+ return {
1146
+ gitDir,
1147
+ commonDir: common ? path17.resolve(gitDir, common) : gitDir
1148
+ };
1149
+ }
1150
+ const parent = path17.dirname(current);
1151
+ if (parent === current) return null;
1152
+ current = parent;
1153
+ }
1154
+ }
1155
+ function checkoutSignature(layout, cwd) {
1156
+ const headPath = path17.join(layout.gitDir, "HEAD");
1157
+ const head = readSmallText(headPath);
1158
+ const configIdentity = effectiveConfigIdentity(cwd);
1159
+ if (head === null || configIdentity === null) {
1160
+ return null;
1161
+ }
1162
+ const parts = [layout.gitDir, head, configIdentity];
1163
+ const ref = /^ref:\s*(.+)$/u.exec(head)?.[1];
1164
+ if (ref) {
1165
+ const looseRefPath = path17.join(layout.commonDir, ref);
1166
+ if (/^ref:/u.test(readSmallText(looseRefPath) ?? "")) return null;
1167
+ const looseRefIdentity = fileContentIdentity(looseRefPath);
1168
+ const packedIdentity = packedRefsIdentity(
1169
+ path17.join(layout.commonDir, "packed-refs")
1170
+ );
1171
+ const reftableIdentity = fileContentIdentity(
1172
+ path17.join(layout.commonDir, "reftable", "tables.list")
1173
+ );
1174
+ if (!looseRefIdentity || !packedIdentity || !reftableIdentity || looseRefIdentity === "missing" && packedIdentity === "missing" && reftableIdentity === "missing") {
1175
+ return null;
1176
+ }
1177
+ parts.push(looseRefIdentity, packedIdentity, reftableIdentity);
1178
+ }
1179
+ return parts.join("\0");
1180
+ }
1181
+ function readPersistentOriginCache(cacheFile) {
1182
+ try {
1183
+ const parsed = JSON.parse(fs7.readFileSync(cacheFile, "utf8"));
1184
+ if (parsed && typeof parsed === "object" && parsed.schemaVersion === PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION && parsed.entries && typeof parsed.entries === "object") {
1185
+ return parsed;
1186
+ }
1187
+ } catch {
1188
+ }
1189
+ return { schemaVersion: PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION, entries: {} };
1190
+ }
1191
+ function writePersistentOriginCache(cacheFile, cache) {
1192
+ const temporary = `${cacheFile}.${process.pid}.tmp`;
1193
+ try {
1194
+ fs7.mkdirSync(path17.dirname(cacheFile), { recursive: true, mode: 448 });
1195
+ fs7.writeFileSync(temporary, `${JSON.stringify(cache)}
1196
+ `, { mode: 384 });
1197
+ fs7.renameSync(temporary, cacheFile);
1198
+ } catch {
1199
+ try {
1200
+ fs7.rmSync(temporary, { force: true });
1201
+ } catch {
1202
+ }
1203
+ }
1204
+ }
1205
+ function createPersistentGitHubOriginResolver(options) {
1206
+ const maxEntries = options.maxEntries ?? DEFAULT_PERSISTENT_ORIGIN_CACHE_MAX_ENTRIES;
1207
+ const cache = readPersistentOriginCache(options.cacheFile);
1208
+ const entries = new Map(Object.entries(cache.entries));
1209
+ const remember = (cwd, entry) => {
1210
+ entries.delete(cwd);
1211
+ entries.set(cwd, entry);
1212
+ while (entries.size > maxEntries) {
1213
+ const oldest = entries.keys().next().value;
1214
+ if (oldest === void 0) break;
1215
+ entries.delete(oldest);
1216
+ }
1217
+ writePersistentOriginCache(options.cacheFile, {
1218
+ schemaVersion: PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION,
1219
+ entries: Object.fromEntries(entries)
1220
+ });
1221
+ };
1222
+ return (cwd) => {
1223
+ if (!validCwd(cwd)) return null;
1224
+ const layout = findGitDirectory(cwd);
1225
+ if (!layout) return null;
1226
+ const signature = checkoutSignature(layout, cwd);
1227
+ const cached = entries.get(cwd);
1228
+ if (signature !== null && cached && cached.signature === signature) {
1229
+ return cached.origin;
1230
+ }
1231
+ try {
1232
+ const result = options.resolve ? { origin: options.resolve(cwd), definitive: true } : resolveGitHubOriginResult(cwd);
1233
+ if (signature !== null && result.definitive) {
1234
+ remember(cwd, { signature, origin: result.origin });
1235
+ }
1236
+ return result.origin;
1237
+ } catch {
1238
+ return null;
1239
+ }
1240
+ };
1241
+ }
1242
+ function createArtifactDiscoveryGitHubOriginResolver(options = {}) {
1243
+ const resolve = options.cacheFile ? createPersistentGitHubOriginResolver({ cacheFile: options.cacheFile }) : (cwd) => resolveGitHubOrigin(cwd);
1244
+ return createMeasuredGitHubOriginResolver(resolve, () => performance.now());
1031
1245
  }
1032
1246
 
1033
1247
  // ../../packages/panopticon/src/artifacts.ts
@@ -1071,20 +1285,20 @@ function isAbortError(error) {
1071
1285
  }
1072
1286
  function safeStat(filePath) {
1073
1287
  try {
1074
- return fs7.statSync(filePath);
1288
+ return fs8.statSync(filePath);
1075
1289
  } catch {
1076
1290
  return null;
1077
1291
  }
1078
1292
  }
1079
1293
  function normalizeFilePath2(filePath) {
1080
- return path17.resolve(filePath);
1294
+ return path18.resolve(filePath);
1081
1295
  }
1082
1296
  function artifactReadPath(artifact) {
1083
1297
  return normalizeFilePath2(artifact.readPath ?? artifact.sourcePath);
1084
1298
  }
1085
1299
  function statCandidateSource(sourcePath, transientStatErrors) {
1086
1300
  try {
1087
- return fs7.statSync(sourcePath);
1301
+ return fs8.statSync(sourcePath);
1088
1302
  } catch (error) {
1089
1303
  if (error.code === "ENOENT") return null;
1090
1304
  if (transientStatErrors === "throw") throw error;
@@ -1092,7 +1306,7 @@ function statCandidateSource(sourcePath, transientStatErrors) {
1092
1306
  }
1093
1307
  }
1094
1308
  function contentTypeForPath(filePath) {
1095
- const ext = path17.extname(filePath).toLowerCase();
1309
+ const ext = path18.extname(filePath).toLowerCase();
1096
1310
  if (ext === ".json" || ext === ".jsonl") return "application/json";
1097
1311
  if (ext === ".md") return "text/markdown";
1098
1312
  if (ext === ".toml") return "application/toml";
@@ -1122,7 +1336,9 @@ function buildArtifactChunkId(args) {
1122
1336
  )}`;
1123
1337
  }
1124
1338
  function discoverArtifacts(options = {}) {
1125
- const repositoryResolution = createArtifactDiscoveryGitHubOriginResolver();
1339
+ const repositoryResolution = createArtifactDiscoveryGitHubOriginResolver(
1340
+ options.githubOriginCacheFile ? { cacheFile: options.githubOriginCacheFile } : {}
1341
+ );
1126
1342
  const env = options.env ?? process.env;
1127
1343
  const homeDirInput = options.homeDir ?? env.HOME ?? os3.homedir();
1128
1344
  if (!homeDirInput) {
@@ -1202,7 +1418,7 @@ function readUploadThrottles(value) {
1202
1418
  }
1203
1419
  function readArtifactQueue(queueFile) {
1204
1420
  try {
1205
- const parsed = JSON.parse(fs7.readFileSync(queueFile, "utf8"));
1421
+ const parsed = JSON.parse(fs8.readFileSync(queueFile, "utf8"));
1206
1422
  if (!parsed || typeof parsed !== "object") return emptyQueue();
1207
1423
  const record = parsed;
1208
1424
  if (record.schemaVersion !== RAW_ARTIFACT_SCHEMA_VERSION) {
@@ -1219,15 +1435,15 @@ function readArtifactQueue(queueFile) {
1219
1435
  }
1220
1436
  }
1221
1437
  function writeArtifactQueue(queueFile, queue) {
1222
- fs7.mkdirSync(path17.dirname(queueFile), { recursive: true, mode: 448 });
1438
+ fs8.mkdirSync(path18.dirname(queueFile), { recursive: true, mode: 448 });
1223
1439
  const tempFile = `${queueFile}.${process.pid}.${Date.now()}.tmp`;
1224
- fs7.writeFileSync(tempFile, `${JSON.stringify(queue, null, 2)}
1440
+ fs8.writeFileSync(tempFile, `${JSON.stringify(queue, null, 2)}
1225
1441
  `, {
1226
1442
  mode: 384
1227
1443
  });
1228
- fs7.renameSync(tempFile, queueFile);
1444
+ fs8.renameSync(tempFile, queueFile);
1229
1445
  try {
1230
- fs7.chmodSync(queueFile, 384);
1446
+ fs8.chmodSync(queueFile, 384);
1231
1447
  } catch {
1232
1448
  }
1233
1449
  }
@@ -1248,7 +1464,7 @@ function shouldStartNewGeneration(input) {
1248
1464
  if (input.nextSizeBytes < input.existing.sizeBytes) return true;
1249
1465
  if ((input.nextReadPath !== artifactReadPath(input.existing) || input.existing.readPath !== void 0) && input.nextSizeBytes === input.existing.sizeBytes && input.existing.uploadedSizeBytes >= input.nextSizeBytes && input.existing.contentSha256) {
1250
1466
  try {
1251
- const contentSha256 = createHash4("sha256").update(fs7.readFileSync(input.nextReadPath)).digest("hex");
1467
+ const contentSha256 = createHash5("sha256").update(fs8.readFileSync(input.nextReadPath)).digest("hex");
1252
1468
  if (contentSha256 === input.existing.contentSha256) return false;
1253
1469
  } catch {
1254
1470
  }
@@ -1582,7 +1798,7 @@ function sameSourceRevision(left, right) {
1582
1798
  }
1583
1799
  function safeSourceRevision(filePath) {
1584
1800
  try {
1585
- const stat = fs7.statSync(filePath, { bigint: true });
1801
+ const stat = fs8.statSync(filePath, { bigint: true });
1586
1802
  return stat.isFile() ? sourceRevisionFromStat(stat) : null;
1587
1803
  } catch {
1588
1804
  return null;
@@ -1594,7 +1810,7 @@ function readExactRange(fd, startByte, endByte) {
1594
1810
  const buffer = Buffer.alloc(sizeBytes);
1595
1811
  let offset = 0;
1596
1812
  while (offset < sizeBytes) {
1597
- const bytesRead = fs7.readSync(
1813
+ const bytesRead = fs8.readSync(
1598
1814
  fd,
1599
1815
  buffer,
1600
1816
  offset,
@@ -1613,19 +1829,19 @@ function readFileRangeAtRevision(filePath, startByte, endByte, expectedRevision)
1613
1829
  }
1614
1830
  let fd;
1615
1831
  try {
1616
- fd = fs7.openSync(filePath, "r");
1832
+ fd = fs8.openSync(filePath, "r");
1617
1833
  } catch {
1618
1834
  return null;
1619
1835
  }
1620
1836
  try {
1621
1837
  const openedRevision = sourceRevisionFromStat(
1622
- fs7.fstatSync(fd, { bigint: true })
1838
+ fs8.fstatSync(fd, { bigint: true })
1623
1839
  );
1624
1840
  if (!sameSourceRevision(pathRevisionBefore, openedRevision)) return null;
1625
1841
  const bytes = readExactRange(fd, startByte, endByte);
1626
1842
  if (!bytes) return null;
1627
1843
  const revisionAfterRead = sourceRevisionFromStat(
1628
- fs7.fstatSync(fd, { bigint: true })
1844
+ fs8.fstatSync(fd, { bigint: true })
1629
1845
  );
1630
1846
  const pathRevisionAfter = safeSourceRevision(filePath);
1631
1847
  if (!sameSourceRevision(openedRevision, revisionAfterRead) || !pathRevisionAfter || !sameSourceRevision(openedRevision, pathRevisionAfter)) {
@@ -1635,7 +1851,7 @@ function readFileRangeAtRevision(filePath, startByte, endByte, expectedRevision)
1635
1851
  } catch {
1636
1852
  return null;
1637
1853
  } finally {
1638
- fs7.closeSync(fd);
1854
+ fs8.closeSync(fd);
1639
1855
  }
1640
1856
  }
1641
1857
  function uploadMetadata(input) {
@@ -1767,18 +1983,18 @@ function validateCheckpointPrefix(input) {
1767
1983
  }
1768
1984
  let fd;
1769
1985
  try {
1770
- fd = fs7.openSync(input.sourcePath, "r");
1986
+ fd = fs8.openSync(input.sourcePath, "r");
1771
1987
  } catch {
1772
1988
  return { status: "changed" };
1773
1989
  }
1774
1990
  try {
1775
1991
  const openedRevision = sourceRevisionFromStat(
1776
- fs7.fstatSync(fd, { bigint: true })
1992
+ fs8.fstatSync(fd, { bigint: true })
1777
1993
  );
1778
1994
  if (!sameSourceRevision(pathRevisionBefore, openedRevision)) {
1779
1995
  return { status: "changed" };
1780
1996
  }
1781
- const contentHash = createHash4("sha256");
1997
+ const contentHash = createHash5("sha256");
1782
1998
  let checkpointMismatch = false;
1783
1999
  for (const checkpoint of checkpoints) {
1784
2000
  const chunk = readExactRange(
@@ -1793,7 +2009,7 @@ function validateCheckpointPrefix(input) {
1793
2009
  contentHash.update(chunk);
1794
2010
  }
1795
2011
  const revisionAfterRead = sourceRevisionFromStat(
1796
- fs7.fstatSync(fd, { bigint: true })
2012
+ fs8.fstatSync(fd, { bigint: true })
1797
2013
  );
1798
2014
  const pathRevisionAfter = safeSourceRevision(input.sourcePath);
1799
2015
  if (!sameSourceRevision(openedRevision, revisionAfterRead) || !pathRevisionAfter || !sameSourceRevision(openedRevision, pathRevisionAfter)) {
@@ -1808,7 +2024,7 @@ function validateCheckpointPrefix(input) {
1808
2024
  } catch {
1809
2025
  return { status: "changed" };
1810
2026
  } finally {
1811
- fs7.closeSync(fd);
2027
+ fs8.closeSync(fd);
1812
2028
  }
1813
2029
  }
1814
2030
  function validatedPrefixStillApplies(prefix, entry, uploadDestinationId, revision) {
@@ -1942,14 +2158,14 @@ function defaultIsPidAlive(pid) {
1942
2158
  }
1943
2159
  function readQueueLockHolder(lockFile) {
1944
2160
  try {
1945
- const parsed = JSON.parse(fs7.readFileSync(lockFile, "utf8"));
2161
+ const parsed = JSON.parse(fs8.readFileSync(lockFile, "utf8"));
1946
2162
  if (!parsed || typeof parsed !== "object") return null;
1947
2163
  const record = parsed;
1948
2164
  if (typeof record.pid !== "number" || typeof record.timestampMs !== "number") {
1949
2165
  return null;
1950
2166
  }
1951
2167
  const ownerToken = typeof record.ownerToken === "string" ? record.ownerToken : void 0;
1952
- const timestampMs = ownerToken ? Math.max(record.timestampMs, fs7.statSync(lockFile).mtimeMs) : record.timestampMs;
2168
+ const timestampMs = ownerToken ? Math.max(record.timestampMs, fs8.statSync(lockFile).mtimeMs) : record.timestampMs;
1953
2169
  return { pid: record.pid, timestampMs, ownerToken };
1954
2170
  } catch {
1955
2171
  return null;
@@ -1957,8 +2173,8 @@ function readQueueLockHolder(lockFile) {
1957
2173
  }
1958
2174
  function queueLockFileMatchesOwner(lockFile, fd, ownerToken) {
1959
2175
  try {
1960
- const openStat = fs7.fstatSync(fd);
1961
- const pathStat = fs7.statSync(lockFile);
2176
+ const openStat = fs8.fstatSync(fd);
2177
+ const pathStat = fs8.statSync(lockFile);
1962
2178
  if (openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
1963
2179
  return false;
1964
2180
  }
@@ -1969,14 +2185,14 @@ function queueLockFileMatchesOwner(lockFile, fd, ownerToken) {
1969
2185
  }
1970
2186
  function acquireQueueLock(queueFile, deps) {
1971
2187
  const lockFile = queueLockFile(queueFile);
1972
- fs7.mkdirSync(path17.dirname(lockFile), { recursive: true, mode: 448 });
2188
+ fs8.mkdirSync(path18.dirname(lockFile), { recursive: true, mode: 448 });
1973
2189
  for (let attempt = 0; attempt < 2; attempt += 1) {
1974
2190
  try {
1975
- const fd = fs7.openSync(lockFile, "wx+", 384);
2191
+ const fd = fs8.openSync(lockFile, "wx+", 384);
1976
2192
  const ownerToken = randomUUID3();
1977
2193
  let released = false;
1978
2194
  try {
1979
- fs7.writeSync(
2195
+ fs8.writeSync(
1980
2196
  fd,
1981
2197
  `${JSON.stringify({
1982
2198
  pid: process.pid,
@@ -1986,11 +2202,11 @@ function acquireQueueLock(queueFile, deps) {
1986
2202
  `
1987
2203
  );
1988
2204
  const heartbeatAt = new Date(deps.nowMs());
1989
- fs7.futimesSync(fd, heartbeatAt, heartbeatAt);
2205
+ fs8.futimesSync(fd, heartbeatAt, heartbeatAt);
1990
2206
  } catch (error) {
1991
- fs7.closeSync(fd);
2207
+ fs8.closeSync(fd);
1992
2208
  try {
1993
- fs7.unlinkSync(lockFile);
2209
+ fs8.unlinkSync(lockFile);
1994
2210
  } catch {
1995
2211
  }
1996
2212
  throw error;
@@ -1998,7 +2214,7 @@ function acquireQueueLock(queueFile, deps) {
1998
2214
  const close = () => {
1999
2215
  if (released) return;
2000
2216
  released = true;
2001
- fs7.closeSync(fd);
2217
+ fs8.closeSync(fd);
2002
2218
  };
2003
2219
  return {
2004
2220
  heartbeat() {
@@ -2007,7 +2223,7 @@ function acquireQueueLock(queueFile, deps) {
2007
2223
  return false;
2008
2224
  try {
2009
2225
  const heartbeatAt = new Date(deps.nowMs());
2010
- fs7.futimesSync(fd, heartbeatAt, heartbeatAt);
2226
+ fs8.futimesSync(fd, heartbeatAt, heartbeatAt);
2011
2227
  return true;
2012
2228
  } catch {
2013
2229
  return false;
@@ -2017,7 +2233,7 @@ function acquireQueueLock(queueFile, deps) {
2017
2233
  if (released) return;
2018
2234
  if (queueLockFileMatchesOwner(lockFile, fd, ownerToken)) {
2019
2235
  try {
2020
- fs7.unlinkSync(lockFile);
2236
+ fs8.unlinkSync(lockFile);
2021
2237
  } catch {
2022
2238
  }
2023
2239
  }
@@ -2030,7 +2246,7 @@ function acquireQueueLock(queueFile, deps) {
2030
2246
  const stale = !holder || !deps.isPidAlive(holder.pid) || deps.nowMs() - holder.timestampMs > QUEUE_LOCK_STALE_MS;
2031
2247
  if (!stale) return null;
2032
2248
  try {
2033
- fs7.unlinkSync(lockFile);
2249
+ fs8.unlinkSync(lockFile);
2034
2250
  } catch {
2035
2251
  }
2036
2252
  }
@@ -2088,7 +2304,7 @@ async function postRawArtifact(url, metadata, content, headers, fetchImpl = fetc
2088
2304
  form.append(
2089
2305
  "file",
2090
2306
  new Blob([new Uint8Array(content)], { type: metadata.contentType }),
2091
- path17.basename(metadata.sourcePath) || "raw-artifact"
2307
+ path18.basename(metadata.sourcePath) || "raw-artifact"
2092
2308
  );
2093
2309
  const abort = new AbortController();
2094
2310
  const timeout = setTimeout(() => abort.abort(), timeoutMs);
@@ -2977,10 +3193,11 @@ async function uploadPendingArtifactsLocked(options, run) {
2977
3193
  }
2978
3194
 
2979
3195
  // ../../packages/panopticon/src/raw-sync.ts
2980
- import path18 from "path";
3196
+ import fs10 from "fs";
3197
+ import path19 from "path";
2981
3198
 
2982
3199
  // ../../packages/panopticon/src/artifact-materializer.ts
2983
- import fs8 from "fs";
3200
+ import fs9 from "fs";
2984
3201
  function entryNeedsBytes(entry, amendmentPending) {
2985
3202
  if (entry.uploadedSizeBytes < entry.sizeBytes) return true;
2986
3203
  return amendmentPending(entry);
@@ -2995,7 +3212,7 @@ function createArtifactMaterializer(input) {
2995
3212
  const unsupportedReason = (input.supportsCompression ?? supportsCompressedArtifacts)() ? void 0 : `compressed artifacts need Node 22.15 or newer (running ${process.version})`;
2996
3213
  const chargeExisting = (cachePath) => {
2997
3214
  try {
2998
- const stat = fs8.statSync(cachePath);
3215
+ const stat = fs9.statSync(cachePath);
2999
3216
  if (!stat.isFile()) return false;
3000
3217
  if (!retained.has(cachePath)) {
3001
3218
  retained.add(cachePath);
@@ -3006,6 +3223,11 @@ function createArtifactMaterializer(input) {
3006
3223
  return false;
3007
3224
  }
3008
3225
  };
3226
+ for (const entry of input.entries) {
3227
+ if (entry.readPath && entryNeedsBytes(entry, input.amendmentPending)) {
3228
+ chargeExisting(entry.readPath);
3229
+ }
3230
+ }
3009
3231
  return {
3010
3232
  unsupportedReason,
3011
3233
  materialize: (sourcePath) => {
@@ -3033,19 +3255,30 @@ function createArtifactMaterializer(input) {
3033
3255
  var DEFAULT_RAW_SYNC_MAX_DRAIN_PASSES = 10;
3034
3256
  var DEFAULT_RAW_SYNC_MAX_DRAIN_BYTES = 100 * 1024 * 1024;
3035
3257
  var DEFAULT_RAW_SYNC_MAX_DRAIN_MS = 6e4;
3258
+ var DISCOVERY_CACHE_SCHEMA_VERSION = 1;
3036
3259
  function getRawArtifactSyncPaths(stateDir) {
3037
3260
  if (stateDir.length === 0) {
3038
3261
  throw new Error("raw artifact sync state directory must not be empty");
3039
3262
  }
3040
- const root = path18.resolve(stateDir);
3263
+ const root = path19.resolve(stateDir);
3041
3264
  const managed = getManagedClientPaths({ PANOPTICON_HOME: root });
3042
3265
  return {
3043
3266
  root,
3044
3267
  settingsFile: managed.syncSettingsFile,
3045
3268
  queueFile: managed.artifactQueue,
3046
- artifactCacheDir: managed.artifactCache
3269
+ artifactCacheDir: managed.artifactCache,
3270
+ discoveryCacheFile: path19.join(root, "discovery.cache.json"),
3271
+ githubOriginCacheFile: path19.join(root, "github-origins.cache.json")
3047
3272
  };
3048
3273
  }
3274
+ function invalidateRawArtifactDiscoveryCache(stateDir) {
3275
+ try {
3276
+ fs10.rmSync(getRawArtifactSyncPaths(stateDir).discoveryCacheFile, {
3277
+ force: true
3278
+ });
3279
+ } catch {
3280
+ }
3281
+ }
3049
3282
  function summarizeArtifactQueue(queue) {
3050
3283
  let pending = 0;
3051
3284
  let uploaded = 0;
@@ -3115,7 +3348,7 @@ function createRawArtifactSync(options) {
3115
3348
  settings: getSettings(),
3116
3349
  queue: summarizeArtifactQueue(readArtifactQueue(paths.queueFile))
3117
3350
  });
3118
- const discover = () => {
3351
+ const createMaterializer = () => {
3119
3352
  const materializer = createArtifactMaterializer({
3120
3353
  cacheDir: paths.artifactCacheDir,
3121
3354
  entries: readArtifactQueue(paths.queueFile).artifacts,
@@ -3123,17 +3356,81 @@ function createRawArtifactSync(options) {
3123
3356
  maxTotalBytes: options.maxArtifactCacheBytes
3124
3357
  });
3125
3358
  lastMaterializer = materializer;
3359
+ return materializer;
3360
+ };
3361
+ const discover = () => {
3362
+ const materializer = createMaterializer();
3126
3363
  const discovery = discoverArtifacts({
3127
3364
  homeDir: options.homeDir,
3128
3365
  projectRoots: options.projectRoots,
3129
3366
  maxFileBytes: options.maxFileBytes,
3130
- materializeArtifact: materializer.materialize
3367
+ materializeArtifact: materializer.materialize,
3368
+ githubOriginCacheFile: paths.githubOriginCacheFile
3131
3369
  });
3132
3370
  return materializer.unsupportedReason ? {
3133
3371
  ...discovery,
3134
3372
  compressedArtifactsUnsupportedReason: materializer.unsupportedReason
3135
3373
  } : discovery;
3136
3374
  };
3375
+ const discoveryInputs = () => JSON.stringify([
3376
+ options.homeDir ?? null,
3377
+ options.projectRoots ?? null,
3378
+ options.maxFileBytes ?? null
3379
+ ]);
3380
+ const readReusableDiscovery = (nowMs) => {
3381
+ const reuseMs = options.discoveryReuseMs ?? 0;
3382
+ if (reuseMs <= 0) return null;
3383
+ try {
3384
+ const parsed = JSON.parse(
3385
+ fs10.readFileSync(paths.discoveryCacheFile, "utf8")
3386
+ );
3387
+ if (parsed.schemaVersion !== DISCOVERY_CACHE_SCHEMA_VERSION || typeof parsed.createdAtMs !== "number" || parsed.inputs !== discoveryInputs() || !parsed.discovery || typeof parsed.discovery !== "object") {
3388
+ return null;
3389
+ }
3390
+ const ageMs = nowMs - parsed.createdAtMs;
3391
+ if (ageMs < 0 || ageMs >= reuseMs) return null;
3392
+ return parsed.discovery;
3393
+ } catch {
3394
+ return null;
3395
+ }
3396
+ };
3397
+ const rememberDiscovery = (discovery, nowMs) => {
3398
+ if ((options.discoveryReuseMs ?? 0) <= 0) return;
3399
+ const temporary = `${paths.discoveryCacheFile}.${process.pid}.tmp`;
3400
+ try {
3401
+ fs10.mkdirSync(path19.dirname(paths.discoveryCacheFile), {
3402
+ recursive: true,
3403
+ mode: 448
3404
+ });
3405
+ fs10.writeFileSync(
3406
+ temporary,
3407
+ `${JSON.stringify({
3408
+ schemaVersion: DISCOVERY_CACHE_SCHEMA_VERSION,
3409
+ createdAtMs: nowMs,
3410
+ inputs: discoveryInputs(),
3411
+ discovery
3412
+ })}
3413
+ `,
3414
+ { mode: 384 }
3415
+ );
3416
+ fs10.renameSync(temporary, paths.discoveryCacheFile);
3417
+ } catch {
3418
+ try {
3419
+ fs10.rmSync(temporary, { force: true });
3420
+ } catch {
3421
+ }
3422
+ }
3423
+ };
3424
+ const discoverForRun = (nowMs) => {
3425
+ const reusable = readReusableDiscovery(nowMs);
3426
+ if (reusable) {
3427
+ createMaterializer();
3428
+ return reusable;
3429
+ }
3430
+ const discovery = discover();
3431
+ rememberDiscovery(discovery, nowMs);
3432
+ return discovery;
3433
+ };
3137
3434
  return {
3138
3435
  paths,
3139
3436
  getSettings,
@@ -3166,7 +3463,7 @@ function createRawArtifactSync(options) {
3166
3463
  status: status()
3167
3464
  };
3168
3465
  }
3169
- const discovery = discover();
3466
+ const discovery = discoverForRun((options.nowMs ?? Date.now)());
3170
3467
  const syncOptions = {
3171
3468
  queueFile: paths.queueFile,
3172
3469
  candidates: discovery.candidates,
@@ -3261,10 +3558,17 @@ function createRawArtifactSync(options) {
3261
3558
  }
3262
3559
 
3263
3560
  // src/sync/runner.ts
3561
+ var OBSERVER_DISCOVERY_REUSE_MS = 6e4;
3562
+ function invalidateCliRawSyncDiscoveryCache() {
3563
+ invalidateRawArtifactDiscoveryCache(FML_OBSERVER_DIR);
3564
+ }
3264
3565
  function createCliRawSync(opts = {}) {
3265
3566
  return createRawArtifactSync({
3266
3567
  stateDir: FML_OBSERVER_DIR,
3267
3568
  retryExhausted: opts.continuous ?? false,
3569
+ // Interactive `fml sync` always discovers fresh; only the observer's
3570
+ // back-to-back cycles reuse a recent discovery.
3571
+ ...opts.continuous ? { discoveryReuseMs: OBSERVER_DISCOVERY_REUSE_MS } : {},
3268
3572
  observerIdentity: collectObserverIdentity(FML_INSTALLATION_ID_FILE) ?? void 0
3269
3573
  });
3270
3574
  }
@@ -3304,30 +3608,12 @@ async function runRawSyncOnce(opts = {}) {
3304
3608
  };
3305
3609
  }
3306
3610
 
3307
- // src/package-root.ts
3308
- import fs9 from "fs";
3309
- import path19 from "path";
3310
- import { fileURLToPath as fileURLToPath2 } from "url";
3311
- function resolvePackageRoot(startDir = path19.dirname(fileURLToPath2(import.meta.url))) {
3312
- let directory = startDir;
3313
- while (true) {
3314
- if (fs9.existsSync(path19.join(directory, "package.json")) && fs9.existsSync(path19.join(directory, "bin"))) {
3315
- return directory;
3316
- }
3317
- const parent = path19.dirname(directory);
3318
- if (parent === directory) {
3319
- throw new Error(`Could not resolve FML package root from ${startDir}`);
3320
- }
3321
- directory = parent;
3322
- }
3323
- }
3324
-
3325
3611
  export {
3326
3612
  claudeConfigDirectory,
3327
3613
  geminiConfigDirectory,
3328
3614
  initializeSyncSettings,
3329
3615
  createRawArtifactSync,
3330
- resolvePackageRoot,
3616
+ invalidateCliRawSyncDiscoveryCache,
3331
3617
  createCliRawSync,
3332
3618
  runRawSyncOnce
3333
3619
  };