@davesheffer/hunch 1.41.1 → 1.41.3

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.
@@ -26,6 +26,27 @@ import { renderHunchSection, stripManagedSection, upsertSection, updateClaudeMd
26
26
  import { headFileContent, isGitCleanPath } from "../extractors/git.js";
27
27
  import { parseJsonc } from "../core/jsonc.js";
28
28
  import { parse as parseToml } from "smol-toml";
29
+ /** JSONC comments are user prose. A parsed object cannot retain their position,
30
+ * so refuse a write rather than reserialize the file without them. */
31
+ function hasJsoncComments(raw) {
32
+ let inString = false;
33
+ for (let i = 0; i < raw.length; i++) {
34
+ const char = raw[i];
35
+ if (inString) {
36
+ if (char === "\\")
37
+ i++;
38
+ else if (char === '"')
39
+ inString = false;
40
+ }
41
+ else if (char === '"') {
42
+ inString = true;
43
+ }
44
+ else if (char === "/" && (raw[i + 1] === "/" || raw[i + 1] === "*")) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
29
50
  /** Read a JSON/JSONC object. Returns {} only for an ABSENT or empty file. A
30
51
  * non-empty file we cannot parse THROWS — overwriting it would silently wipe the
31
52
  * user's other MCP servers. */
@@ -35,15 +56,21 @@ function readJsonObj(file) {
35
56
  const raw = readFileSync(file, "utf8");
36
57
  if (!raw.trim())
37
58
  return {};
59
+ let value;
38
60
  try {
39
61
  const v = parseJsonc(raw);
40
62
  if (v && typeof v === "object" && !Array.isArray(v))
41
- return v;
42
- throw new Error("not a JSON object");
63
+ value = v;
64
+ else
65
+ throw new Error("not a JSON object");
43
66
  }
44
67
  catch (e) {
45
68
  throw new Error(`refusing to overwrite ${file}: could not parse it (${e.message}). Fix or remove it, then re-run.`);
46
69
  }
70
+ if (hasJsoncComments(raw)) {
71
+ throw new Error(`refusing to edit ${file}: JSONC comments cannot be preserved by this writer. Edit the Hunch entry manually or remove the comments, then re-run.`);
72
+ }
73
+ return value;
47
74
  }
48
75
  /** Render a string as a TOML value: a literal '…' when safe (no escaping needed —
49
76
  * ideal for Windows backslash paths), else a basic "…" with escapes. */
@@ -170,8 +170,14 @@ export function mergeLedgers(base, ours, theirs) {
170
170
  seen.set(k, e);
171
171
  order.push(e);
172
172
  } };
173
- for (const e of base?.events ?? [])
174
- add(e);
173
+ const ourIdentities = new Set(ours.events.map(eventIdentity));
174
+ const theirIdentities = new Set(theirs.events.map(eventIdentity));
175
+ // Base is a merge ancestor, not a source of history both clones have compacted away.
176
+ for (const e of base?.events ?? []) {
177
+ const key = eventIdentity(e);
178
+ if (ourIdentities.has(key) || theirIdentities.has(key) || (e.seq > ours.floor_seq && e.seq > theirs.floor_seq))
179
+ add(e);
180
+ }
175
181
  for (const e of ours.events)
176
182
  add(e);
177
183
  for (const e of theirs.events)
@@ -180,7 +186,6 @@ export function mergeLedgers(base, ours, theirs) {
180
186
  // side 1 iff theirs holds it and neither ours nor base does. Deciding by object identity
181
187
  // (`includes`) would instead put anything ours compacted away — base's object, which `add`
182
188
  // keeps — after its same-`at` siblings and silently reorder a batch both sides already had.
183
- const ourIdentities = new Set(ours.events.map(eventIdentity));
184
189
  const baseIdentities = new Set((base?.events ?? []).map(eventIdentity));
185
190
  const theirsOnly = (e) => {
186
191
  const k = eventIdentity(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.41.1",
3
+ "version": "1.41.3",
4
4
  "mcpName": "io.github.davesheffer/hunch",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://www.hunchmemory.com",
10
- "version": "1.41.1",
10
+ "version": "1.41.3",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.41.1",
16
+ "version": "1.41.3",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {