@davesheffer/hunch 1.39.3 → 1.40.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.
Files changed (43) hide show
  1. package/dist/cli/index.js +61 -17
  2. package/dist/constitution/g2BehaviorCandidates.js +6 -1
  3. package/dist/constitution/g2Candidates.js +11 -1
  4. package/dist/constitution/structural.js +10 -0
  5. package/dist/core/delivery.d.ts +34 -0
  6. package/dist/core/delivery.js +60 -1
  7. package/dist/core/docanchors.js +181 -11
  8. package/dist/core/format.js +6 -1
  9. package/dist/core/glob.d.ts +12 -6
  10. package/dist/core/glob.js +12 -6
  11. package/dist/core/hookcache.d.ts +9 -2
  12. package/dist/core/hookcache.js +10 -3
  13. package/dist/core/paths.d.ts +21 -0
  14. package/dist/core/paths.js +39 -1
  15. package/dist/core/taskDelivery.js +27 -1
  16. package/dist/core/taskReportHook.d.ts +19 -0
  17. package/dist/core/taskReportHook.js +40 -4
  18. package/dist/core/taskReportRender.d.ts +3 -0
  19. package/dist/core/taskReportRender.js +22 -12
  20. package/dist/core/verifyLauncher.d.ts +21 -0
  21. package/dist/core/verifyLauncher.js +37 -0
  22. package/dist/extractors/indexer.d.ts +5 -0
  23. package/dist/extractors/indexer.js +56 -11
  24. package/dist/extractors/k8sManifest.d.ts +13 -0
  25. package/dist/extractors/k8sManifest.js +103 -7
  26. package/dist/extractors/landscapeDiscovery.js +9 -1
  27. package/dist/extractors/nativeTreeSitter.d.ts +24 -0
  28. package/dist/extractors/nativeTreeSitter.js +54 -1
  29. package/dist/extractors/parse.d.ts +3 -1
  30. package/dist/extractors/parse.js +12 -3
  31. package/dist/integrations/claudemd.js +3 -3
  32. package/dist/integrations/hooks.d.ts +43 -4
  33. package/dist/integrations/hooks.js +309 -22
  34. package/dist/mcp/server.js +19 -11
  35. package/dist/mcp/taskReportTools.d.ts +5 -9
  36. package/dist/mcp/taskReportTools.js +9 -20
  37. package/dist/store/changeLedger.d.ts +9 -3
  38. package/dist/store/changeLedger.js +36 -10
  39. package/dist/store/hunchStore.d.ts +42 -2
  40. package/dist/store/hunchStore.js +74 -16
  41. package/dist/store/stateBinding.js +1 -1
  42. package/package.json +1 -1
  43. package/server.json +2 -2
@@ -5,9 +5,9 @@
5
5
  * other record, appended atomically (con_902759b3dc). It holds the strictly ordered
6
6
  * ChangeEvent stream for that scope (seq 1, 2, 3 … with no gaps) plus the idempotency
7
7
  * table the write verb replays from. Seq is per scope, assigned by the writer in the
8
- * home the scope lives in; a scope has exactly ONE ledger, so there is never a second
9
- * sequence to reconcile. Merging two clones' ledgers for the same scope is not decided
10
- * here (see docs/nuryel-state-contract.md, "Not decided here").
8
+ * home the scope lives in; a scope has exactly ONE ledger per clone, so within a clone there
9
+ * is never a second sequence to reconcile. Two clones that both appended to one scope are
10
+ * reconciled by `mergeLedgers` (below), for the git merge driver.
11
11
  */
12
12
  import { mkdirSync } from "node:fs";
13
13
  import { basename, join, resolve } from "node:path";
@@ -34,8 +34,9 @@ export const LedgerSchema = z.object({
34
34
  schema: z.literal(LEDGER_SCHEMA_VERSION),
35
35
  scope: ScopeSchema,
36
36
  head_seq: z.number().int().nonnegative(),
37
- /** Events below this seq were compacted away. `events` starts at floor_seq + 1. A subscriber
38
- * whose cursor is below the floor must resynchronize (the contract's gap rule, made explicit). */
37
+ /** Events below this seq were compacted away, or were invalidated by a merge that renumbered
38
+ * them (see `mergeLedgers`). `events` starts at floor_seq + 1. A subscriber whose cursor is
39
+ * below the floor must resynchronize (the contract's gap rule, made explicit). */
39
40
  floor_seq: z.number().int().nonnegative().default(0),
40
41
  events: z.array(ChangeEventSchema),
41
42
  idempotency: z.record(z.string(), IdempotencyEntrySchema).default({}),
@@ -151,9 +152,15 @@ const eventIdentity = (e) => [e.change, e.facet, e.record_id, e.record_hash, e.a
151
152
  /** Three-way merge of one scope's ledger, for the git merge driver: two clones that both
152
153
  * appended to the same partition. The union of events is kept (identity = what changed, to
153
154
  * which hash, when, by whom), ordered by time then ours-before-theirs, and RE-SEQUENCED from
154
- * the higher floor; every subscriber's cursor is therefore invalid after a merge and the gap
155
- * rule makes it resynchronize. Idempotency entries are unioned; a key both sides used for
156
- * different records is a conflict the caller must surface (ours is kept). */
155
+ * the higher floor. When that renumbering moves an event either side had already published,
156
+ * no pre-merge cursor of EITHER side can be trusted — a cursor could otherwise sit above an
157
+ * event that now holds a lower seq and never see it (issue #285). The floor is then raised
158
+ * past BOTH pre-merge heads, so every such cursor satisfies subscribe's `after_seq <
159
+ * floor_seq` and resynchronizes from the floor; a cursor equal to the larger head is exactly
160
+ * the failing case, hence the `+ 1`. A merge that renumbers nothing keeps today's seqs (no
161
+ * cursor could have seen a different numbering) and the floor stays put. Idempotency entries
162
+ * are unioned; a key both sides used for different records is a conflict the caller must
163
+ * surface (ours is kept). */
157
164
  export function mergeLedgers(base, ours, theirs) {
158
165
  if (scopePath(ours.scope) !== scopePath(theirs.scope))
159
166
  throw new Error("ledgers for different scopes cannot be merged");
@@ -169,9 +176,28 @@ export function mergeLedgers(base, ours, theirs) {
169
176
  add(e);
170
177
  for (const e of theirs.events)
171
178
  add(e);
172
- const ranked = order.map((e, i) => ({ e, i, side: (ours.events.includes(e) ? 0 : 1) }));
179
+ // The tiebreak is "ours before theirs", so only an event theirs ALONE contributed sorts late:
180
+ // side 1 iff theirs holds it and neither ours nor base does. Deciding by object identity
181
+ // (`includes`) would instead put anything ours compacted away — base's object, which `add`
182
+ // 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
+ const baseIdentities = new Set((base?.events ?? []).map(eventIdentity));
185
+ const theirsOnly = (e) => {
186
+ const k = eventIdentity(e);
187
+ return !ourIdentities.has(k) && !baseIdentities.has(k);
188
+ };
189
+ const ranked = order.map((e, i) => ({ e, i, side: (theirsOnly(e) ? 1 : 0) }));
173
190
  ranked.sort((a, b) => a.e.at.localeCompare(b.e.at) || a.side - b.side || a.i - b.i);
174
- const floor = Math.max(base?.floor_seq ?? 0, ours.floor_seq, theirs.floor_seq);
191
+ const theirSeqs = new Map(theirs.events.map((e) => [eventIdentity(e), e.seq]));
192
+ const ourSeqs = new Map(ours.events.map((e) => [eventIdentity(e), e.seq]));
193
+ let floor = Math.max(base?.floor_seq ?? 0, ours.floor_seq, theirs.floor_seq);
194
+ const renumbers = ranked.some(({ e }, i) => {
195
+ const k = eventIdentity(e);
196
+ const merged = floor + i + 1;
197
+ return (ourSeqs.get(k) ?? merged) !== merged || (theirSeqs.get(k) ?? merged) !== merged;
198
+ });
199
+ if (renumbers)
200
+ floor = Math.max(floor, Math.max(ours.head_seq, theirs.head_seq) + 1);
175
201
  const events = ranked.map(({ e }, i) => ({ ...e, seq: floor + i + 1 }));
176
202
  const conflicts = [];
177
203
  const idempotency = { ...(base?.idempotency ?? {}), ...theirs.idempotency, ...ours.idempotency };
@@ -335,6 +335,29 @@ export declare class HunchStore {
335
335
  * present in the indexed symbol/component tables can enter the frontier, so
336
336
  * shared external-package hubs never become context or bridge unrelated symbols. */
337
337
  private graphExpand;
338
+ /** The WIDE definition of "is `t` a real path?" — the gate used by the matchers
339
+ * that ATTRIBUTE records to a target silently (why(), liveFindingsFor, tasksFor,
340
+ * `resolveSymbols` in the MCP server). They also run for a file an agent is
341
+ * about to create, where the covering component glob is the only signal there
342
+ * is, so the answer must not depend on the file already being on disk. The
343
+ * resolvers that NAME the file they resolved to (resolveNodeIds, structure())
344
+ * deliberately use the narrower `isRepoFile` instead.
345
+ *
346
+ * Graph data FIRST (a symbol's exact file, or an indexed component's path glob),
347
+ * so a deleted-but-still-indexed path and a time-travel (`asOf`) query answer the
348
+ * same as before (issue #299). The working tree is consulted only as a LAST
349
+ * resort, for the case the index cannot see: a real file with zero tree-sitter
350
+ * symbols and no covering component glob, which otherwise suffix-leaked an
351
+ * unrelated same-basename file's records (issue #334). When the working tree is
352
+ * unavailable or the file is gone, behaviour is exactly the pre-#334 behaviour.
353
+ *
354
+ * `t` must already be repo-relative (`repoRelativeTarget`). `graph` lets a caller
355
+ * that has already narrowed the graph (why()'s `canRead` visibility filter) ask
356
+ * the question over exactly the records it may see, rather than all of them. */
357
+ isKnownPath(t: string, graph?: {
358
+ symbols: readonly Symbol[];
359
+ components: readonly Component[];
360
+ }): boolean;
338
361
  /** All decisions/bugs/constraints/symbols/components touching a file path or
339
362
  * symbol name (hunch_why). Pass `{ asOf }` (an ISO instant) to TIME-TRAVEL:
340
363
  * return only decisions/constraints whose valid-time window contained that
@@ -379,7 +402,20 @@ export declare class HunchStore {
379
402
  * or the id itself when unindexed. */
380
403
  nodeLabel(id: string): string;
381
404
  /** Resolve a free-form target (symbol id / name / file path, component id / name)
382
- * to graph node ids — symbols win over components, exact file before suffix. */
405
+ * to graph node ids — symbols win over components, exact file before suffix.
406
+ * Shares why()/resolveSymbols' tiered matcher rather than one un-tiered SQL OR:
407
+ * the flat query matched every tier at once, so a root `index.ts` also returned
408
+ * `a/index.ts`'s symbols, and an absolute target matched nothing at all (#335).
409
+ *
410
+ * The suffix gate here is `isRepoFile`, NOT the wider `isKnownPath`: the
411
+ * exact-file tier has already run, so the only case left is a real on-disk file
412
+ * with zero symbols (#334). Glob coverage alone would wrongly block a target
413
+ * that no file actually carries — with a `src/**` component, `src/x.ts` living
414
+ * at `packages/a/src/x.ts` stopped resolving. This resolver names the file it
415
+ * resolved to in its result, so a suffix resolution is visible and only a file
416
+ * that really exists may block it; why()/the pre-edit hook attribute records
417
+ * silently and also run for a file about to be created, so they keep the wider
418
+ * `isKnownPath`. */
383
419
  resolveNodeIds(target: string): string[];
384
420
  /** PR impact (read-only, ADVISORY — never gates): the dependency + memory surface
385
421
  * of a change. Composes the SAME primitives as buildCheckReport (blast radius,
@@ -590,9 +626,13 @@ export type StructureView = {
590
626
  callers: string[];
591
627
  callees: string[];
592
628
  }>;
593
- } | {
629
+ }
630
+ /** `realFile`: the target IS a regular file in the working tree, it just has no
631
+ * indexed symbols — a different answer from "no such path" (#334). */
632
+ | {
594
633
  kind: "none";
595
634
  target: string;
635
+ realFile?: boolean;
596
636
  };
597
637
  export interface StaleRecord {
598
638
  kind: string;
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { resolve, join, dirname, isAbsolute, relative } from "node:path";
14
14
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
15
- import { toPosixTarget, repoRelativeTarget, hunchPathsForDir } from "../core/paths.js";
15
+ import { toPosixTarget, repoRelativeTarget, isRepoFile, hunchPathsForDir } from "../core/paths.js";
16
16
  import { ENTITY_KINDS } from "../core/types.js";
17
17
  import { openDb, withTx } from "./db.js";
18
18
  import { RESET_SQL, embedHash } from "./schema.js";
@@ -1178,6 +1178,31 @@ export class HunchStore {
1178
1178
  }
1179
1179
  return hits;
1180
1180
  }
1181
+ /** The WIDE definition of "is `t` a real path?" — the gate used by the matchers
1182
+ * that ATTRIBUTE records to a target silently (why(), liveFindingsFor, tasksFor,
1183
+ * `resolveSymbols` in the MCP server). They also run for a file an agent is
1184
+ * about to create, where the covering component glob is the only signal there
1185
+ * is, so the answer must not depend on the file already being on disk. The
1186
+ * resolvers that NAME the file they resolved to (resolveNodeIds, structure())
1187
+ * deliberately use the narrower `isRepoFile` instead.
1188
+ *
1189
+ * Graph data FIRST (a symbol's exact file, or an indexed component's path glob),
1190
+ * so a deleted-but-still-indexed path and a time-travel (`asOf`) query answer the
1191
+ * same as before (issue #299). The working tree is consulted only as a LAST
1192
+ * resort, for the case the index cannot see: a real file with zero tree-sitter
1193
+ * symbols and no covering component glob, which otherwise suffix-leaked an
1194
+ * unrelated same-basename file's records (issue #334). When the working tree is
1195
+ * unavailable or the file is gone, behaviour is exactly the pre-#334 behaviour.
1196
+ *
1197
+ * `t` must already be repo-relative (`repoRelativeTarget`). `graph` lets a caller
1198
+ * that has already narrowed the graph (why()'s `canRead` visibility filter) ask
1199
+ * the question over exactly the records it may see, rather than all of them. */
1200
+ isKnownPath(t, graph) {
1201
+ const symbols = graph?.symbols ?? this.recs("symbols");
1202
+ const components = graph?.components ?? this.recs("components");
1203
+ return isIndexedPath(t, symbols.map((s) => s.file), components.map((c) => c.paths))
1204
+ || isRepoFile(this.paths.root, t);
1205
+ }
1181
1206
  /** All decisions/bugs/constraints/symbols/components touching a file path or
1182
1207
  * symbol name (hunch_why). Pass `{ asOf }` (an ISO instant) to TIME-TRAVEL:
1183
1208
  * return only decisions/constraints whose valid-time window contained that
@@ -1202,12 +1227,12 @@ export class HunchStore {
1202
1227
  // it too pulls in every OTHER file that merely shares a basename (issue
1203
1228
  // #299: root index.ts delivered vscode-extension/index.ts's rules and vice
1204
1229
  // versa). "Known to the index" is answered from already-loaded graph data
1205
- // (a symbol's exact file, or an indexed component's path glob — so a
1206
- // symbol-less real file still counts as real), never the filesystem: a
1207
- // deleted-but-still-indexed path must answer the same either way. Suffix
1208
- // matching stays reserved for a target that is not itself a real indexed
1209
- // path, e.g. a short/partial reference like "x/scenario.ts".
1210
- const indexed = isIndexedPath(target, symbols.map((s) => s.file), components.map((c) => c.paths));
1230
+ // first (a symbol's exact file, or an indexed component's path glob — so a
1231
+ // symbol-less indexed file still counts as real), and only then from the
1232
+ // working tree, for a real file the index cannot see at all (issue #334).
1233
+ // See isKnownPath. Suffix matching stays reserved for a target that is not
1234
+ // itself a real path, e.g. a short/partial reference like "x/scenario.ts".
1235
+ const indexed = this.isKnownPath(target, { symbols, components });
1211
1236
  const suffixMatch = (file) => !indexed && pathsRelated(file, target);
1212
1237
  const matchedSymbols = matchSymbolsTiered(target, symbols, indexed);
1213
1238
  const symIds = new Set(matchedSymbols.map((s) => s.id));
@@ -1335,10 +1360,23 @@ export class HunchStore {
1335
1360
  return c?.v ?? id;
1336
1361
  }
1337
1362
  /** Resolve a free-form target (symbol id / name / file path, component id / name)
1338
- * to graph node ids — symbols win over components, exact file before suffix. */
1363
+ * to graph node ids — symbols win over components, exact file before suffix.
1364
+ * Shares why()/resolveSymbols' tiered matcher rather than one un-tiered SQL OR:
1365
+ * the flat query matched every tier at once, so a root `index.ts` also returned
1366
+ * `a/index.ts`'s symbols, and an absolute target matched nothing at all (#335).
1367
+ *
1368
+ * The suffix gate here is `isRepoFile`, NOT the wider `isKnownPath`: the
1369
+ * exact-file tier has already run, so the only case left is a real on-disk file
1370
+ * with zero symbols (#334). Glob coverage alone would wrongly block a target
1371
+ * that no file actually carries — with a `src/**` component, `src/x.ts` living
1372
+ * at `packages/a/src/x.ts` stopped resolving. This resolver names the file it
1373
+ * resolved to in its result, so a suffix resolution is visible and only a file
1374
+ * that really exists may block it; why()/the pre-edit hook attribute records
1375
+ * silently and also run for a file about to be created, so they keep the wider
1376
+ * `isKnownPath`. */
1339
1377
  resolveNodeIds(target) {
1340
- const t = toPosixTarget(target);
1341
- const sym = this.db.prepare(`SELECT id FROM symbols WHERE id = ? OR name = ? OR file = ? OR file LIKE ? LIMIT 20`).all(t, t, t, `%/${t}`);
1378
+ const t = repoRelativeTarget(target, this.paths.root);
1379
+ const sym = matchSymbolsTiered(t, this.recs("symbols"), isRepoFile(this.paths.root, t)).slice(0, 20);
1342
1380
  if (sym.length)
1343
1381
  return sym.map((r) => r.id);
1344
1382
  const cmp = this.db.prepare(`SELECT id FROM components WHERE id = ? OR name = ? LIMIT 5`).all(t, t);
@@ -1395,10 +1433,27 @@ export class HunchStore {
1395
1433
  dirs: [...dirs.entries()].map(([dir, v]) => ({ dir, ...v })).sort((a, b) => b.symbols - a.symbols),
1396
1434
  };
1397
1435
  }
1398
- const t = toPosixTarget(target.trim()).replace(/\/+$/, "");
1436
+ // An absolute target (an agent's edit-payload path, verbatim) never matched the
1437
+ // repo-relative stored file paths below and fell all the way to "none" (#335).
1438
+ const t = repoRelativeTarget(target.trim(), this.paths.root).replace(/\/+$/, "");
1399
1439
  // FILE: exact path or unique suffix
1400
1440
  const fileRows = this.db.prepare(`SELECT id, name, kind, loc, fan_in, fan_out FROM symbols WHERE file = ? ORDER BY fan_in DESC, name`).all(t);
1401
- const fileHit = fileRows.length ? t : this.db.prepare(`SELECT DISTINCT file FROM symbols WHERE file LIKE ?`).all(`%/${t}`).map((r) => r.file);
1441
+ // Only the SUFFIX fallback is gated, and by `isRepoFile` rather than the wider
1442
+ // `isKnownPath`: the exact-path tier above has already run, so the only case
1443
+ // left is a real on-disk file with zero symbols, whose outline must not come
1444
+ // from an unrelated same-basename file (#334). Glob coverage alone is not
1445
+ // existence — with a `src/**` component, `src/x.ts` living at
1446
+ // `packages/a/src/x.ts` would stop resolving. structure() names the file it
1447
+ // resolved to, so a suffix resolution is visible and only a file that really
1448
+ // exists may block it; why()/the pre-edit hook attribute records silently and
1449
+ // also run for a file about to be created, so they keep `isKnownPath`.
1450
+ // Directory targets are unaffected — a directory is never an `isRepoFile`
1451
+ // (regular files only), so they keep flowing to the dir tier below.
1452
+ const fileHit = fileRows.length
1453
+ ? t
1454
+ : isRepoFile(this.paths.root, t)
1455
+ ? []
1456
+ : this.db.prepare(`SELECT DISTINCT file FROM symbols WHERE file LIKE ?`).all(`%/${t}`).map((r) => r.file);
1402
1457
  const file = typeof fileHit === "string" ? fileHit : fileHit.length === 1 ? fileHit[0] : null;
1403
1458
  if (file) {
1404
1459
  const syms = fileRows.length ? fileRows : this.db.prepare(`SELECT id, name, kind, loc, fan_in, fan_out FROM symbols WHERE file = ? ORDER BY fan_in DESC, name`).all(file);
@@ -1427,7 +1482,10 @@ export class HunchStore {
1427
1482
  matches: named.map((m) => ({ ...m, callers: this.edgeNeighbors(m.id, "in", 6), callees: this.edgeNeighbors(m.id, "out", 6) })),
1428
1483
  };
1429
1484
  }
1430
- return { kind: "none", target: t };
1485
+ // A real on-disk file with zero indexed symbols reaches "none" too (#334). Flag
1486
+ // it so the renderer doesn't tell the user it isn't a known file when we just
1487
+ // stat'd it.
1488
+ return { kind: "none", target: t, ...(isRepoFile(this.paths.root, t) ? { realFile: true } : {}) };
1431
1489
  }
1432
1490
  /** Labelled one-hop edge neighbors of a node ("in" = who reaches it, "out" = what it reaches). */
1433
1491
  edgeNeighbors(id, dir, limit) {
@@ -1464,7 +1522,7 @@ export class HunchStore {
1464
1522
  // that merely shares a basename. Normalize once and reuse the same "is this
1465
1523
  // path known to the index" guard.
1466
1524
  const t = repoRelativeTarget(scope, this.paths.root);
1467
- const indexed = isIndexedPath(t, this.recs("symbols").map((s) => s.file), this.recs("components").map((c) => c.paths));
1525
+ const indexed = this.isKnownPath(t);
1468
1526
  const live = (f) => f.triage === "open" || f.triage === "accepted-risk" || f.triage === "scheduled";
1469
1527
  return this.recs("findings")
1470
1528
  .filter(live)
@@ -1476,9 +1534,9 @@ export class HunchStore {
1476
1534
  * denied edits), newest first. Graph memory, so it spans machines and survives
1477
1535
  * the local ledger's retention window. */
1478
1536
  tasksFor(scope, limit = 8) {
1479
- // Same normalization + indexed-suffix guard as liveFindingsFor/why() (issue #299).
1537
+ // Same normalization + known-path suffix guard as liveFindingsFor/why() (issue #299).
1480
1538
  const t = repoRelativeTarget(scope, this.paths.root);
1481
- const indexed = isIndexedPath(t, this.recs("symbols").map((s) => s.file), this.recs("components").map((c) => c.paths));
1539
+ const indexed = this.isKnownPath(t);
1482
1540
  return this.recs("tasks")
1483
1541
  .filter((r) => r.files.some((f) => pathMatchesGlob(t, f) || pathMatchesGlob(f, t) || (!indexed && pathsRelated(toPosixTarget(f), t))))
1484
1542
  .sort((a, b) => b.finished_at.localeCompare(a.finished_at) || a.id.localeCompare(b.id))
@@ -1036,7 +1036,7 @@ export function subscribeState(store, input, options = {}) {
1036
1036
  const access = createStateAccess(store, request.principal, options);
1037
1037
  const visibilityFiltered = ledger.events.some(e => e.visibility !== undefined) || access.restricted;
1038
1038
  const filtered = !!(facets || subjects || visibilityFiltered);
1039
- const resync = request.after_seq < ledger.floor_seq;
1039
+ const resync = request.after_seq < ledger.floor_seq || request.after_seq > ledger.head_seq;
1040
1040
  const after = resync ? ledger.floor_seq : request.after_seq;
1041
1041
  const events = ledger.events.filter((e) => e.seq > after
1042
1042
  && (!visibilityFiltered || (visibilityAllows(e, request.principal.id) && access.canRead(findRecord(store, e.record_id)?.record) && access.referencesVisible(e)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.39.3",
3
+ "version": "1.40.1",
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.39.3",
10
+ "version": "1.40.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.39.3",
16
+ "version": "1.40.1",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {