threadnote 0.7.1 → 0.7.2

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.
@@ -34108,10 +34108,8 @@ async function runShareSyncQuiet(config2, state, options) {
34108
34108
  const git = await requiredExecutable("git");
34109
34109
  const worktree = team.config.worktree;
34110
34110
  const pendingChanges = state.pendingReindexes.get(team.name);
34111
- if (pendingChanges) {
34112
- await applyChangesToOpenViking(config2, team.config, pendingChanges, { quiet: true });
34113
- state.pendingReindexes.delete(team.name);
34114
- await writePendingReindexes(config2, state);
34111
+ if (pendingChanges && pendingChanges.length > 0) {
34112
+ await applyAndPersistChanges(config2, team.config, state, pendingChanges, { quiet: true });
34115
34113
  }
34116
34114
  if (await hasUncommittedChanges(worktree)) {
34117
34115
  return `Shared team "${team.name}" has uncommitted changes; skipped automatic sync. Run \`threadnote share sync --team ${team.name}\` to publish or resolve them.`;
@@ -34139,11 +34137,9 @@ async function runShareSyncQuiet(config2, state, options) {
34139
34137
  if (beforeRev && afterRev && beforeRev !== afterRev) {
34140
34138
  const changes = await listChangedFiles(worktree, beforeRev, afterRev);
34141
34139
  if (changes.length > 0) {
34142
- state.pendingReindexes.set(team.name, changes);
34143
- await writePendingReindexes(config2, state);
34144
- await applyChangesToOpenViking(config2, team.config, changes, { quiet: true });
34145
- state.pendingReindexes.delete(team.name);
34146
- await writePendingReindexes(config2, state);
34140
+ const stillPending = state.pendingReindexes.get(team.name) ?? [];
34141
+ const combined = mergeChanges(stillPending, changes);
34142
+ await applyAndPersistChanges(config2, team.config, state, combined, { quiet: true });
34147
34143
  }
34148
34144
  }
34149
34145
  return void 0;
@@ -34339,8 +34335,9 @@ async function writeMemoryFile(config2, ov, uri, content, initialMode, dryRun, o
34339
34335
  await (0, import_promises3.rm)(stagingDir, { force: true, recursive: true });
34340
34336
  }
34341
34337
  }
34338
+ var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
34342
34339
  async function writeOvFileWithRetry(config2, ov, uri, fromFile, initialMode, options = {}) {
34343
- const maxAttempts = 4;
34340
+ const maxAttempts = BUSY_RETRY_BACKOFF_MS.length + 1;
34344
34341
  const existedBeforeWrite = await vikingResourceExists(ov, config2, uri);
34345
34342
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
34346
34343
  const existsNow = attempt === 0 ? existedBeforeWrite : await vikingResourceExists(ov, config2, uri);
@@ -34384,6 +34381,7 @@ async function writeOvFileWithRetry(config2, ov, uri, fromFile, initialMode, opt
34384
34381
  }
34385
34382
  if (isResourceBusyFailure(result.stderr, result.stdout)) {
34386
34383
  await waitForOvQueue(ov, config2, options);
34384
+ await sleep(BUSY_RETRY_BACKOFF_MS[attempt] ?? 3e4);
34387
34385
  } else {
34388
34386
  await sleep(1e3 * (attempt + 1));
34389
34387
  }
@@ -34461,7 +34459,7 @@ async function removeMemoryUri(config2, ov, uri, dryRun, options = {}) {
34461
34459
  }
34462
34460
  return;
34463
34461
  }
34464
- const maxAttempts = 4;
34462
+ const maxAttempts = BUSY_RETRY_BACKOFF_MS.length + 1;
34465
34463
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
34466
34464
  const result = await runCommand(ov, args, { allowFailure: true });
34467
34465
  if (result.exitCode === 0) {
@@ -34475,6 +34473,7 @@ async function removeMemoryUri(config2, ov, uri, dryRun, options = {}) {
34475
34473
  }
34476
34474
  if (isResourceBusyFailure(result.stderr, result.stdout)) {
34477
34475
  await waitForOvQueue(ov, config2, options);
34476
+ await sleep(BUSY_RETRY_BACKOFF_MS[attempt] ?? 3e4);
34478
34477
  } else {
34479
34478
  await sleep(1e3 * (attempt + 1));
34480
34479
  }
@@ -34531,6 +34530,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
34531
34530
  }
34532
34531
  async function applyChangesToOpenViking(config2, team, changes, options = {}) {
34533
34532
  const ov = await openVikingCliForMode(false);
34533
+ const failed = [];
34534
34534
  for (const change of changes) {
34535
34535
  if (!change.relativePath.endsWith(".md")) {
34536
34536
  continue;
@@ -34540,24 +34540,57 @@ async function applyChangesToOpenViking(config2, team, changes, options = {}) {
34540
34540
  continue;
34541
34541
  }
34542
34542
  const uri = workfileToVikingUri(config2, team, change.path);
34543
- if (change.status === "removed") {
34544
- await removeMemoryUri(config2, ov, uri, false, options);
34545
- continue;
34546
- }
34547
- if (!await isFile(change.path)) {
34548
- continue;
34549
- }
34550
- const ovHasResource = await vikingResourceExists(ov, config2, uri);
34551
- if (ovHasResource) {
34552
- const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
34543
+ try {
34544
+ if (change.status === "removed") {
34545
+ await removeMemoryUri(config2, ov, uri, false, options);
34546
+ continue;
34547
+ }
34548
+ if (!await isFile(change.path)) {
34549
+ continue;
34550
+ }
34551
+ const ovHasResource = await vikingResourceExists(ov, config2, uri);
34552
+ if (ovHasResource) {
34553
+ const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
34554
+ if (options.quiet !== true) {
34555
+ console.warn(`share sync: ${uri}: ${reason}.`);
34556
+ }
34557
+ }
34558
+ await ensureSharedDirectoryChain(config2, ov, uri, false, options);
34559
+ const writeMode = ovHasResource ? "replace" : "create";
34560
+ await ingestSingleFile(ov, config2, uri, change.path, writeMode, options);
34561
+ } catch (err) {
34562
+ const message = err instanceof Error ? err.message : String(err);
34553
34563
  if (options.quiet !== true) {
34554
- console.warn(`share sync: ${uri}: ${reason}.`);
34564
+ console.warn(`share sync: ${uri}: ingest failed \u2014 will retry on the next sync. ${message}`);
34555
34565
  }
34566
+ failed.push(change);
34567
+ }
34568
+ }
34569
+ return { failed };
34570
+ }
34571
+ function mergeChanges(...lists) {
34572
+ const map3 = /* @__PURE__ */ new Map();
34573
+ for (const list of lists) {
34574
+ for (const change of list) {
34575
+ map3.set(change.relativePath, change);
34556
34576
  }
34557
- await ensureSharedDirectoryChain(config2, ov, uri, false, options);
34558
- const writeMode = ovHasResource ? "replace" : "create";
34559
- await ingestSingleFile(ov, config2, uri, change.path, writeMode, options);
34560
34577
  }
34578
+ return [...map3.values()];
34579
+ }
34580
+ async function applyAndPersistChanges(config2, team, state, changes, options = {}) {
34581
+ if (changes.length === 0) {
34582
+ return { failed: [] };
34583
+ }
34584
+ state.pendingReindexes.set(team.name, changes);
34585
+ await writePendingReindexes(config2, state);
34586
+ const result = await applyChangesToOpenViking(config2, team, changes, options);
34587
+ if (result.failed.length > 0) {
34588
+ state.pendingReindexes.set(team.name, result.failed);
34589
+ } else {
34590
+ state.pendingReindexes.delete(team.name);
34591
+ }
34592
+ await writePendingReindexes(config2, state);
34593
+ return result;
34561
34594
  }
34562
34595
 
34563
34596
  // src/mcp_server.ts
@@ -7583,12 +7583,24 @@ Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or
7583
7583
  );
7584
7584
  }
7585
7585
  const afterRev = await gitOutput(worktree, ["rev-parse", "HEAD"], dryRun);
7586
- if (!dryRun && beforeRev && afterRev && beforeRev !== afterRev) {
7587
- const changes = await listChangedFiles(worktree, beforeRev, afterRev);
7588
- await applyChangesToOpenViking(config, team.config, changes);
7589
- console.log(`Reindexed ${changes.length} file change(s) into OpenViking.`);
7590
- } else if (!dryRun) {
7591
- console.log("No upstream changes to reindex.");
7586
+ if (!dryRun) {
7587
+ const state = autoShareState(config);
7588
+ await loadPendingReindexes(config, state);
7589
+ const previouslyPending = state.pendingReindexes.get(team.name) ?? [];
7590
+ const newChanges = beforeRev && afterRev && beforeRev !== afterRev ? await listChangedFiles(worktree, beforeRev, afterRev) : [];
7591
+ const combined = mergeChanges(previouslyPending, newChanges);
7592
+ if (combined.length === 0) {
7593
+ console.log("No upstream changes to reindex.");
7594
+ } else {
7595
+ const result = await applyAndPersistChanges(config, team.config, state, combined);
7596
+ const succeeded = combined.length - result.failed.length;
7597
+ console.log(`Reindexed ${succeeded} file change(s) into OpenViking.`);
7598
+ if (result.failed.length > 0) {
7599
+ console.warn(
7600
+ `share sync: ${result.failed.length} file(s) could not be ingested on this run; they are persisted and will be retried on the next sync or agent recall/read.`
7601
+ );
7602
+ }
7603
+ }
7592
7604
  }
7593
7605
  if (options.push !== false) {
7594
7606
  await maybeRun(dryRun, git, ["-C", worktree, "push", DEFAULT_GIT_REMOTE_NAME], { allowFailure: true });
@@ -7599,10 +7611,8 @@ async function runShareSyncQuiet(config, state, options) {
7599
7611
  const git = await requiredExecutable("git");
7600
7612
  const worktree = team.config.worktree;
7601
7613
  const pendingChanges = state.pendingReindexes.get(team.name);
7602
- if (pendingChanges) {
7603
- await applyChangesToOpenViking(config, team.config, pendingChanges, { quiet: true });
7604
- state.pendingReindexes.delete(team.name);
7605
- await writePendingReindexes(config, state);
7614
+ if (pendingChanges && pendingChanges.length > 0) {
7615
+ await applyAndPersistChanges(config, team.config, state, pendingChanges, { quiet: true });
7606
7616
  }
7607
7617
  if (await hasUncommittedChanges(worktree)) {
7608
7618
  return `Shared team "${team.name}" has uncommitted changes; skipped automatic sync. Run \`threadnote share sync --team ${team.name}\` to publish or resolve them.`;
@@ -7630,11 +7640,9 @@ async function runShareSyncQuiet(config, state, options) {
7630
7640
  if (beforeRev && afterRev && beforeRev !== afterRev) {
7631
7641
  const changes = await listChangedFiles(worktree, beforeRev, afterRev);
7632
7642
  if (changes.length > 0) {
7633
- state.pendingReindexes.set(team.name, changes);
7634
- await writePendingReindexes(config, state);
7635
- await applyChangesToOpenViking(config, team.config, changes, { quiet: true });
7636
- state.pendingReindexes.delete(team.name);
7637
- await writePendingReindexes(config, state);
7643
+ const stillPending = state.pendingReindexes.get(team.name) ?? [];
7644
+ const combined = mergeChanges(stillPending, changes);
7645
+ await applyAndPersistChanges(config, team.config, state, combined, { quiet: true });
7638
7646
  }
7639
7647
  }
7640
7648
  return void 0;
@@ -8074,8 +8082,9 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
8074
8082
  await (0, import_promises4.rm)(stagingDir, { force: true, recursive: true });
8075
8083
  }
8076
8084
  }
8085
+ var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
8077
8086
  async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, options = {}) {
8078
- const maxAttempts = 4;
8087
+ const maxAttempts = BUSY_RETRY_BACKOFF_MS.length + 1;
8079
8088
  const existedBeforeWrite = await vikingResourceExists(ov, config, uri);
8080
8089
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
8081
8090
  const existsNow = attempt === 0 ? existedBeforeWrite : await vikingResourceExists(ov, config, uri);
@@ -8119,6 +8128,7 @@ async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, opti
8119
8128
  }
8120
8129
  if (isResourceBusyFailure(result.stderr, result.stdout)) {
8121
8130
  await waitForOvQueue(ov, config, options);
8131
+ await sleep(BUSY_RETRY_BACKOFF_MS[attempt] ?? 3e4);
8122
8132
  } else {
8123
8133
  await sleep(1e3 * (attempt + 1));
8124
8134
  }
@@ -8196,7 +8206,7 @@ async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
8196
8206
  }
8197
8207
  return;
8198
8208
  }
8199
- const maxAttempts = 4;
8209
+ const maxAttempts = BUSY_RETRY_BACKOFF_MS.length + 1;
8200
8210
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
8201
8211
  const result = await runCommand(ov, args, { allowFailure: true });
8202
8212
  if (result.exitCode === 0) {
@@ -8210,6 +8220,7 @@ async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
8210
8220
  }
8211
8221
  if (isResourceBusyFailure(result.stderr, result.stdout)) {
8212
8222
  await waitForOvQueue(ov, config, options);
8223
+ await sleep(BUSY_RETRY_BACKOFF_MS[attempt] ?? 3e4);
8213
8224
  } else {
8214
8225
  await sleep(1e3 * (attempt + 1));
8215
8226
  }
@@ -8266,6 +8277,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
8266
8277
  }
8267
8278
  async function applyChangesToOpenViking(config, team, changes, options = {}) {
8268
8279
  const ov = await openVikingCliForMode(false);
8280
+ const failed = [];
8269
8281
  for (const change of changes) {
8270
8282
  if (!change.relativePath.endsWith(".md")) {
8271
8283
  continue;
@@ -8275,24 +8287,57 @@ async function applyChangesToOpenViking(config, team, changes, options = {}) {
8275
8287
  continue;
8276
8288
  }
8277
8289
  const uri = workfileToVikingUri(config, team, change.path);
8278
- if (change.status === "removed") {
8279
- await removeMemoryUri(config, ov, uri, false, options);
8280
- continue;
8281
- }
8282
- if (!await isFile(change.path)) {
8283
- continue;
8284
- }
8285
- const ovHasResource = await vikingResourceExists(ov, config, uri);
8286
- if (ovHasResource) {
8287
- const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
8290
+ try {
8291
+ if (change.status === "removed") {
8292
+ await removeMemoryUri(config, ov, uri, false, options);
8293
+ continue;
8294
+ }
8295
+ if (!await isFile(change.path)) {
8296
+ continue;
8297
+ }
8298
+ const ovHasResource = await vikingResourceExists(ov, config, uri);
8299
+ if (ovHasResource) {
8300
+ const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
8301
+ if (options.quiet !== true) {
8302
+ console.warn(`share sync: ${uri}: ${reason}.`);
8303
+ }
8304
+ }
8305
+ await ensureSharedDirectoryChain(config, ov, uri, false, options);
8306
+ const writeMode = ovHasResource ? "replace" : "create";
8307
+ await ingestSingleFile(ov, config, uri, change.path, writeMode, options);
8308
+ } catch (err) {
8309
+ const message = err instanceof Error ? err.message : String(err);
8288
8310
  if (options.quiet !== true) {
8289
- console.warn(`share sync: ${uri}: ${reason}.`);
8311
+ console.warn(`share sync: ${uri}: ingest failed \u2014 will retry on the next sync. ${message}`);
8290
8312
  }
8313
+ failed.push(change);
8314
+ }
8315
+ }
8316
+ return { failed };
8317
+ }
8318
+ function mergeChanges(...lists) {
8319
+ const map2 = /* @__PURE__ */ new Map();
8320
+ for (const list of lists) {
8321
+ for (const change of list) {
8322
+ map2.set(change.relativePath, change);
8291
8323
  }
8292
- await ensureSharedDirectoryChain(config, ov, uri, false, options);
8293
- const writeMode = ovHasResource ? "replace" : "create";
8294
- await ingestSingleFile(ov, config, uri, change.path, writeMode, options);
8295
8324
  }
8325
+ return [...map2.values()];
8326
+ }
8327
+ async function applyAndPersistChanges(config, team, state, changes, options = {}) {
8328
+ if (changes.length === 0) {
8329
+ return { failed: [] };
8330
+ }
8331
+ state.pendingReindexes.set(team.name, changes);
8332
+ await writePendingReindexes(config, state);
8333
+ const result = await applyChangesToOpenViking(config, team, changes, options);
8334
+ if (result.failed.length > 0) {
8335
+ state.pendingReindexes.set(team.name, result.failed);
8336
+ } else {
8337
+ state.pendingReindexes.delete(team.name);
8338
+ }
8339
+ await writePendingReindexes(config, state);
8340
+ return result;
8296
8341
  }
8297
8342
 
8298
8343
  // src/memory.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",