@try-works/dsh-memory 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@try-works/dsh-memory",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Git-backed, workspace-scoped memory for the DeepSeek Harness (global layer + per-workspace layers)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/git.js CHANGED
@@ -26,12 +26,18 @@ export class GitRunner {
26
26
  */
27
27
  export function classifyGitResult(result) {
28
28
  if (!result) return { ok: false, reason: 'git produced no result' }
29
- if (result.code === 0) return { ok: true }
29
+ // BOTH names, because the two halves disagreed and nobody noticed: the doc comment above the
30
+ // GitRunner constructor promises a run() result carrying `code`, so this read `result.code` -
31
+ // while the runner the plugin actually injects returns `exitCode`. Every successful git command
32
+ // was therefore classified as a failure, every pull and push was reported as failed, and sync had
33
+ // never once succeeded. A classifier that rejects success is worse than no classifier.
34
+ const code = result.exitCode ?? result.code
35
+ if (code === 0) return { ok: true }
30
36
  const stdout = String(result.stdout ?? '')
31
37
  const stderr = String(result.stderr ?? '')
32
38
  const combined = stdout + ' ' + stderr
33
39
  const conflicted = /conflict|automatic merge failed|needs merge|could not apply/i.test(combined)
34
- const reason = (stderr.trim() || stdout.trim() || 'exit code ' + String(result.code)).slice(0, 400)
40
+ const reason = (stderr.trim() || stdout.trim() || 'exit code ' + String(code)).slice(0, 400)
35
41
  return { ok: false, conflicted, reason }
36
42
  }
37
43
 
package/src/host.js CHANGED
@@ -28,6 +28,19 @@ const MAP_PUBLISH_MS = 60 * 1000
28
28
  const TASKS_BACKUP_MS = 30 * 60 * 1000
29
29
  const IDLE_TICK_MS = 60_000
30
30
 
31
+ /**
32
+ * Did the pull fail only because the remote has no such branch yet?
33
+ *
34
+ * The first sync of a store that has been committing to itself - which is every store that has ever
35
+ * been used - meets a remote with no refs at all. git says so in a few ways depending on version and
36
+ * transport, and none of them is a real failure: there is simply nothing to pull and everything to
37
+ * push.
38
+ */
39
+ function isMissingRemoteBranch(result) {
40
+ const text = String((result && result.stdout) || '') + ' ' + String((result && result.stderr) || '')
41
+ return /couldn't find remote ref|could not find remote ref|no such ref|remote branch .* not found|unable to find remote ref/i.test(text)
42
+ }
43
+
31
44
  function makeGitRunner(ctx) {
32
45
  // The subprocess service is NOT resolvable during apply. cordis ctx.get() only returns an
33
46
  // implementation whose PROVIDING FIBER IS ACTIVE (vendor/cordis reflect.ts _getImpl), and no
@@ -627,7 +640,11 @@ export function wireHost(ctx, store, cfg) {
627
640
  }
628
641
  const pulled = await git.pull(store.root, branch).catch((error) => ({ code: -1, stderr: String((error && error.message) || error) }))
629
642
  const pullVerdict = classifyGitResult(pulled)
630
- if (!pullVerdict.ok) return failSync('pull', pullVerdict, committed)
643
+ // An EMPTY remote has no branch to pull, and that is not a failure - it is the first push.
644
+ // The store has been committing to itself since it was created, so its very first sync meets a
645
+ // remote with no refs at all; failing there would mean a freshly configured origin could never
646
+ // be bootstrapped by the plugin, only by hand.
647
+ if (!pullVerdict.ok && !isMissingRemoteBranch(pulled)) return failSync('pull', pullVerdict, committed)
631
648
  const pushed = await git.push(store.root, branch).catch((error) => ({ code: -1, stderr: String((error && error.message) || error) }))
632
649
  const pushVerdict = classifyGitResult(pushed)
633
650
  if (!pushVerdict.ok) return failSync('push', pushVerdict, committed)
package/src/tools.js CHANGED
@@ -758,7 +758,12 @@ export function registerTools(ctx, store, extras = {}) {
758
758
  if (!syncNow) return { synced: false }
759
759
  const status = await syncNow()
760
760
  return {
761
- synced: Boolean(status && (status.synced || status.committed || status.initialized)),
761
+ // `synced` means SYNCED, not 'it did something'. It used to include committed and
762
+ // initialized, so a push that FAILED still reported synced:true whenever the local commit
763
+ // had succeeded - the result read {"synced":true,"conflict":true}, claiming success and
764
+ // failure at once. A local commit is progress, not a sync, and saying otherwise is the same
765
+ // defect as every other one this layer has produced: a green answer over a silent failure.
766
+ synced: Boolean(status && status.synced),
762
767
  ...(status && status.conflict ? { conflict: true } : {}),
763
768
  ...(status && status.error ? { error: String(status.error) } : {}),
764
769
  }