@davesheffer/hunch 1.20.1 → 1.20.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.
package/README.md CHANGED
@@ -58,6 +58,9 @@ existing configuration.
58
58
  passes, needs attention, or should be blocked.
59
59
  - **Past bugs stay useful** — see which old incident a piece of code fixed before accidentally
60
60
  undoing it.
61
+ - **Fresh context without lost history** — decisions whose code anchors are still current rank
62
+ ahead of ones whose files changed after verification; older evidence remains visible and keeps
63
+ its existing authority.
61
64
  - **Understands how code connects** — for TypeScript, JavaScript, Python, Go, PHP, YAML, and Helm, Hunch
62
65
  can see what calls or depends on the code you are about to change. Its memory works with any
63
66
  language.
@@ -171,7 +174,7 @@ repository, separate from the code repository. Hunch does not host it. Create a
171
174
  that every teammate can access, install Hunch on team machines and CI, then have one maintainer run:
172
175
 
173
176
  ```bash
174
- npm i -g @davesheffer/hunch@1.20.1
177
+ npm i -g @davesheffer/hunch@1.20.3
175
178
  hunch shared --repo git@github.com:acme/project-hunch-memory.git
176
179
  git add .gitignore .hunch/team.json
177
180
  git commit -m "chore: connect shared Hunch memory"
@@ -186,7 +189,7 @@ printed by Hunch. Omit `--migrate` for a new setup.
186
189
  After the pointer commit lands, teammates need Hunch installed and Git access to the memory repo:
187
190
 
188
191
  ```bash
189
- npm i -g @davesheffer/hunch@1.20.1
192
+ npm i -g @davesheffer/hunch@1.20.3
190
193
  git pull
191
194
  hunch init
192
195
  hunch doctor
@@ -234,7 +237,7 @@ but stops automatic memory commits and pushes. As a team-coordinated rollback, r
234
237
  commit to stop discovery after teammates pull the revert. Existing machines retain their ignored
235
238
  local overlay until they are deliberately disconnected; do not delete the memory repo as part of a
236
239
  rollback. For this rollout, reinstall the previous published package with
237
- `npm i -g @davesheffer/hunch@1.20.0`; the release receipt resolves and records the verified rollback
240
+ `npm i -g @davesheffer/hunch@1.20.2`; the release receipt resolves and records the verified rollback
238
241
  target from the npm registry instead of trusting Git tags. Pause enforcement first as shown above,
239
242
  and keep every team client on the same release before resuming Matrix policy workflows.
240
243
 
package/dist/cli/index.js CHANGED
@@ -1292,7 +1292,7 @@ program
1292
1292
  const emb = await selectEmbedder();
1293
1293
  if (!store.semanticReady(emb)) {
1294
1294
  console.log("· semantic search isn't enabled yet — run `hunch embed` (using keyword search for now).\n");
1295
- hits = store.search(q, 12);
1295
+ hits = store.rankedSearch(q, 12);
1296
1296
  }
1297
1297
  else {
1298
1298
  hits = await store.hybridSearch(q, 12, { embedder: emb });
@@ -1300,7 +1300,7 @@ program
1300
1300
  }
1301
1301
  }
1302
1302
  else {
1303
- hits = store.search(q, 12);
1303
+ hits = store.rankedSearch(q, 12);
1304
1304
  }
1305
1305
  if (!hits.length) {
1306
1306
  console.log(`No matches for "${q}".`);
@@ -3904,7 +3904,7 @@ program
3904
3904
  !ctx.landscape?.resources.length &&
3905
3905
  !ctx.landscape?.relationships.length;
3906
3906
  if (empty && !asOf) {
3907
- const hits = store.search(target, 8);
3907
+ const hits = store.rankedSearch(target, 8);
3908
3908
  if (hits.length) {
3909
3909
  console.log(`No file/symbol resolves for "${target}" — closest graph matches instead:\n`);
3910
3910
  for (const h of hits)
@@ -592,12 +592,27 @@ export function commitAndPushHunch(hunchDir, message, opts) {
592
592
  else {
593
593
  run(["add", "--", "."]);
594
594
  }
595
- // SAFETY BACKSTOP (critical bug_overlay_clobber): a memory sync is PURELY ADDITIVE small
596
- // JSON. If the staged set contains a DELETION, rename, or any non-.json file, hunchDir is NOT
597
- // a clean overlay store most dangerously, the overlay was never its own git repo so `git -C`
598
- // walked UP to the PROJECT repo. Committing/pushing there would overwrite/delete the user's
599
- // code (we shipped exactly this). Refuse hard: unstage and bail without committing or pushing.
600
- const staged = stagedMemoryPaths(hunchDir, env);
595
+ // Snapshot replacement can remove stale per-record JSON. Only a push-capable
596
+ // private/shared store may stage those deletions: the publication boundary
597
+ // above has already proved that it is a standalone repository distinct from
598
+ // the protected code repository. `-u` is still scoped to this exact Hunch
599
+ // subtree, and the staged-set guard below admits only contained JSON deletes.
600
+ // Public stores retain the older no-deletion rule because they live inside
601
+ // the user's code repository.
602
+ if (opts.push !== false && !run([
603
+ "-c", `core.attributesFile=${gitNullDevice()}`,
604
+ "-c", "core.autocrlf=false",
605
+ "add", "-u", "--", ".",
606
+ ])) {
607
+ run(["reset", "-q", "--", "."]);
608
+ return null;
609
+ }
610
+ // SAFETY BACKSTOP (critical — bug_overlay_clobber): only contained JSON
611
+ // memory bytes may be committed. Renames, copies, type changes, local.json,
612
+ // derived artifacts and every path outside this exact Hunch subtree remain
613
+ // fail-closed. A deletion is admitted only for the already-proven standalone
614
+ // private/shared repository above; it can never delete protected source.
615
+ const staged = stagedMemoryPaths(hunchDir, env, opts.push !== false);
601
616
  if (staged === null) {
602
617
  try {
603
618
  execFileSync("git", ["-C", hunchDir, "reset", "-q", "--", "."], { stdio: "ignore", env });
@@ -608,7 +623,7 @@ export function commitAndPushHunch(hunchDir, message, opts) {
608
623
  // stays on disk and the next flush's `git add .` sweeps it up. The overlay path
609
624
  // stays loud: there it signals the escaped-to-project-repo misconfiguration.
610
625
  if (opts.push !== false) {
611
- console.error(`hunch: refusing to auto-commit memory at "${hunchDir}" — the staged change includes deletions or non-memory files, so this is not a clean overlay repo. Nothing was committed or pushed. (Use \`hunch shared --repo <url>\` so the overlay is its OWN git repo.)`);
626
+ console.error(`hunch: refusing to auto-commit memory at "${hunchDir}" — the staged change includes an unsafe deletion or non-memory file, so this is not a clean overlay repo. Nothing was committed or pushed. (Use \`hunch shared --repo <url>\` so the overlay is its OWN git repo.)`);
612
627
  }
613
628
  return null;
614
629
  }
@@ -744,7 +759,7 @@ export function headFileContent(root, rel) {
744
759
  return null;
745
760
  }
746
761
  }
747
- function stagedMemoryPaths(hunchDir, env) {
762
+ function stagedMemoryPaths(hunchDir, env, allowMemoryDeletions = false) {
748
763
  let out = "";
749
764
  let prefix = "";
750
765
  try {
@@ -755,8 +770,11 @@ function stagedMemoryPaths(hunchDir, env) {
755
770
  }
756
771
  if (!prefix)
757
772
  return null; // a Hunch layout is a scoped subdirectory, never the whole repository
773
+ // Snapshot ID churn is semantically one delete plus one add. Disable Git's
774
+ // heuristic rename presentation so the exact paths remain independently
775
+ // auditable against the contained-memory rules below.
758
776
  try {
759
- out = execFileSync("git", ["-C", hunchDir, "diff", "--cached", "--no-ext-diff", "--no-textconv", "--name-status"], { encoding: "utf8", env });
777
+ out = execFileSync("git", ["-C", hunchDir, "diff", "--cached", "--no-ext-diff", "--no-textconv", "--no-renames", "--name-status"], { encoding: "utf8", env });
760
778
  }
761
779
  catch {
762
780
  return null;
@@ -772,8 +790,8 @@ function stagedMemoryPaths(hunchDir, env) {
772
790
  const path = (parts[parts.length - 1] ?? "").trim();
773
791
  if (path.includes(".hunch-commit.lock"))
774
792
  continue; // transient lock dir, never a record
775
- if (!/^[AM]$/.test(status))
776
- return null; // only Add / Modify — any D/R/C/T → not a memory sync
793
+ if (!/^[AMD]$/.test(status) || (status === "D" && !allowMemoryDeletions))
794
+ return null;
777
795
  const normalizedPath = path.replace(/\\/g, "/");
778
796
  if (!normalizedPath.startsWith(prefix))
779
797
  return null; // never bless another staged JSON file
@@ -2107,6 +2125,53 @@ export function firstCommitForFile(file, cwd) {
2107
2125
  export function lastChangeDate(file, cwd) {
2108
2126
  return gitSafe(["log", "-1", "--format=%aI", "--", file], cwd);
2109
2127
  }
2128
+ /**
2129
+ * Newest author date for every changed path selected by a bounded set of repository-relative
2130
+ * Hunch scopes. One Git history walk replaces the per-file process loop used by `staleness()`.
2131
+ *
2132
+ * The return keys are the concrete paths Git observed, not the input scopes: callers can apply
2133
+ * Hunch's own exact/glob/directory matcher without treating Git pathspec interpretation as graph
2134
+ * authority. A failed or oversized read returns null, so freshness stays unknown rather than
2135
+ * partially scoring a record.
2136
+ */
2137
+ export function scopedLastChangeDates(scopes, cwd, maxChangedPaths = 4_096) {
2138
+ const pathspecs = [...new Set(scopes)].map((scope) => {
2139
+ const normalized = scope.replaceAll("\\", "/");
2140
+ return /[*?[]/.test(normalized) ? `:(glob)${normalized}` : `:(literal)${normalized}`;
2141
+ });
2142
+ if (pathspecs.length === 0)
2143
+ return new Map();
2144
+ const raw = gitRawSafeIsolated([
2145
+ "-c", "core.quotePath=false",
2146
+ "log", "-z", "--name-only", "--format=HUNCH_DATE:%aI%x00", "--", ...pathspecs,
2147
+ ], cwd, 64 * 1024 * 1024);
2148
+ if (raw === null)
2149
+ return null;
2150
+ const out = new Map();
2151
+ let commitDate = "";
2152
+ for (const rawToken of raw.split("\0")) {
2153
+ // Git inserts one presentation newline between a custom commit format and its
2154
+ // first NUL-delimited path. Remove exactly that byte; a real leading newline in
2155
+ // a path remains as the second byte and therefore cannot alias a normal path.
2156
+ const token = rawToken.startsWith("\n") ? rawToken.slice(1) : rawToken;
2157
+ if (!token)
2158
+ continue;
2159
+ if (token.startsWith("HUNCH_DATE:")) {
2160
+ const candidate = token.slice("HUNCH_DATE:".length);
2161
+ commitDate = Number.isFinite(Date.parse(candidate)) ? candidate : "";
2162
+ continue;
2163
+ }
2164
+ if (!commitDate)
2165
+ continue;
2166
+ const previous = out.get(token);
2167
+ if (!previous || Date.parse(commitDate) > Date.parse(previous)) {
2168
+ out.set(token, commitDate);
2169
+ if (out.size > maxChangedPaths)
2170
+ return null;
2171
+ }
2172
+ }
2173
+ return out;
2174
+ }
2110
2175
  /** Batched per-file git metrics for indexing: churn (commits touching the file in
2111
2176
  * the last `days`; pass 0 to skip) and the most-recent commit (`commit:<sha>`).
2112
2177
  *
@@ -870,7 +870,7 @@ export function buildServerWithRootControl(initialRoot) {
870
870
  !ctx.landscape?.resources.length &&
871
871
  !ctx.landscape?.relationships.length;
872
872
  if (empty && !asOf) {
873
- const hits = store.search(target, 8);
873
+ const hits = store.rankedSearch(target, 8);
874
874
  if (hits.length) {
875
875
  const resolved = hits.map((hit) => ({ hit, record: store.resolve(hit.ref)?.record }));
876
876
  const fallback = {
@@ -18,7 +18,7 @@ import { openDb, withTx } from "./db.js";
18
18
  import { RESET_SQL, embedHash } from "./schema.js";
19
19
  import { selectEmbedder } from "./embedder.js";
20
20
  import { JsonStore } from "./jsonStore.js";
21
- import { gitCommonDir, gitWorktreeRoot, sameGitPublication } from "../extractors/git.js";
21
+ import { gitCommonDir, gitWorktreeRoot, isolatedHeadSha, sameGitPublication, scopedLastChangeDates, } from "../extractors/git.js";
22
22
  import { pathMatchesGlob, pathsRelated } from "../core/glob.js";
23
23
  import { currentForTopic, isInForce } from "../core/topics.js";
24
24
  import { edgeId } from "../core/ids.js";
@@ -85,6 +85,11 @@ export class HunchStore {
85
85
  * equal to a committed file (dec_d7bad4ccb7). */
86
86
  suppressPrivate = false;
87
87
  _db = null;
88
+ /** HEAD-keyed, process-local ranking evidence. It never changes record authority or storage. */
89
+ decisionFreshnessRoot = "";
90
+ decisionFreshnessHead = "";
91
+ decisionFreshnessScopes = new Set();
92
+ decisionFreshnessChanges = new Map();
88
93
  constructor(paths) {
89
94
  this.paths = paths;
90
95
  this.json = new JsonStore(paths);
@@ -621,7 +626,8 @@ export class HunchStore {
621
626
  /** Post-fusion rerank by graph PRIORS (dec_25e277f479): relevance ordering, not
622
627
  * just reachability. Trust weight w = liveness × provenance × recency: liveness 0.6
623
628
  * for superseded/retired/rejected, provenance 1.0 / 0.85 / 0.75 for
624
- * human_confirmed / llm_draft / extracted-inferred, recency 0.7 + 0.3·½^(age/90d).
629
+ * human_confirmed / llm_draft / extracted-inferred, recency 0.7 + 0.3·½^(age/90d),
630
+ * and proven anchored-file staleness 0.8. Every factor is ranking-only.
625
631
  * Runbook trigger phrases matching the query boost ×1.5 (exact intent beats
626
632
  * keyword luck). Structural refs (symbols/components/edges) stay neutral.
627
633
  *
@@ -643,7 +649,7 @@ export class HunchStore {
643
649
  * a stale or low-provenance record visibly dims, an exact runbook-trigger match
644
650
  * visibly promotes, and neither can leapfrog the whole pool. Same measurement
645
651
  * after: 0% evicted from fused ranks 0–8. Deterministic; ties keep fused order. */
646
- rerankByPriors(hits, limit, query) {
652
+ rerankByPriors(hits, limit, query, freshnessRoot = this.paths.root) {
647
653
  if (!hits.length)
648
654
  return hits; // a SINGLE hit still runs — topic-chain promotion must fire for the lone stale match
649
655
  const now = Date.now();
@@ -672,6 +678,10 @@ export class HunchStore {
672
678
  pos: i + 0.5,
673
679
  });
674
680
  }
681
+ const decisionsById = new Map(this.recs("decisions").map((decision) => [decision.id, decision]));
682
+ const staleDecisionIds = this.staleDecisionIds(pool.filter(({ h }) => h.kind === "decisions")
683
+ .map(({ h }) => decisionsById.get(h.ref))
684
+ .filter((decision) => !!decision), freshnessRoot);
675
685
  const scored = pool.map(({ h, pos }) => {
676
686
  const m = this.priorMeta(h.ref, h.kind);
677
687
  let w = 1;
@@ -694,6 +704,10 @@ export class HunchStore {
694
704
  if (Number.isFinite(ageDays))
695
705
  w *= 0.7 + 0.3 * Math.pow(0.5, ageDays / 90);
696
706
  }
707
+ // File-change staleness is a bounded relevance signal only. It cannot retire,
708
+ // withhold, supersede, or weaken a decision's enforcement authority.
709
+ if (h.kind === "decisions" && staleDecisionIds.has(h.ref))
710
+ w *= STALE_DECISION_PRIOR_WEIGHT;
697
711
  if (q && m.triggers?.some((tr) => q.includes(tr) || tr.includes(q)))
698
712
  w *= 1.5;
699
713
  }
@@ -708,6 +722,77 @@ export class HunchStore {
708
722
  scored.sort((a, b) => a.pos - b.pos);
709
723
  return scored.slice(0, limit).map((x) => x.h);
710
724
  }
725
+ /**
726
+ * Score only freshness that the existing graph clocks can prove: an anchored path changed after
727
+ * `provenance.last_verified`. One bounded Git pass fills a HEAD-keyed cache for newly encountered
728
+ * scopes; repeated MCP/CLI queries perform no history walk until HEAD changes.
729
+ */
730
+ staleDecisionIds(decisions, freshnessRoot) {
731
+ const root = resolve(freshnessRoot);
732
+ const head = isolatedHeadSha(root);
733
+ if (!head)
734
+ return new Set();
735
+ if (root !== this.decisionFreshnessRoot || head !== this.decisionFreshnessHead) {
736
+ this.decisionFreshnessRoot = root;
737
+ this.decisionFreshnessHead = head;
738
+ this.decisionFreshnessScopes.clear();
739
+ this.decisionFreshnessChanges.clear();
740
+ }
741
+ const eligible = new Map();
742
+ let scopeCount = 0;
743
+ for (const decision of decisions) {
744
+ const verifiedAt = Date.parse(decision.provenance.last_verified ?? "");
745
+ if (!Number.isFinite(verifiedAt))
746
+ continue;
747
+ const scopes = [...new Set(decision.related_files.map(safeFreshnessScope).filter(Boolean))];
748
+ if (scopes.length === 0 || scopeCount + scopes.length > DECISION_FRESHNESS_SCOPE_QUERY_CAP)
749
+ continue;
750
+ scopeCount += scopes.length;
751
+ eligible.set(decision.id, { verifiedAt, scopes });
752
+ }
753
+ let missing = [...new Set([...eligible.values()].flatMap((candidate) => candidate.scopes))]
754
+ .filter((scope) => !this.decisionFreshnessScopes.has(scope));
755
+ if (missing.length) {
756
+ if (this.decisionFreshnessScopes.size + missing.length > DECISION_FRESHNESS_SCOPE_CACHE_CAP) {
757
+ this.decisionFreshnessScopes.clear();
758
+ this.decisionFreshnessChanges.clear();
759
+ missing = [...new Set([...eligible.values()].flatMap((candidate) => candidate.scopes))];
760
+ }
761
+ const observed = scopedLastChangeDates(missing, root, DECISION_FRESHNESS_PATH_CACHE_CAP);
762
+ if (observed) {
763
+ if (this.decisionFreshnessChanges.size + observed.size > DECISION_FRESHNESS_PATH_CACHE_CAP) {
764
+ this.decisionFreshnessScopes.clear();
765
+ this.decisionFreshnessChanges.clear();
766
+ }
767
+ else {
768
+ missing.forEach((scope) => this.decisionFreshnessScopes.add(scope));
769
+ observed.forEach((date, path) => {
770
+ const previous = this.decisionFreshnessChanges.get(path);
771
+ if (!previous || Date.parse(date) > Date.parse(previous))
772
+ this.decisionFreshnessChanges.set(path, date);
773
+ });
774
+ }
775
+ }
776
+ }
777
+ const stale = new Set();
778
+ for (const [decisionId, candidate] of eligible) {
779
+ if (candidate.scopes.some((scope) => !this.decisionFreshnessScopes.has(scope)))
780
+ continue;
781
+ for (const [changedPath, changedAt] of this.decisionFreshnessChanges) {
782
+ if (Date.parse(changedAt) <= candidate.verifiedAt)
783
+ continue;
784
+ if (candidate.scopes.some((scope) => freshnessScopeMatches(changedPath, scope))) {
785
+ stale.add(decisionId);
786
+ break;
787
+ }
788
+ }
789
+ }
790
+ return stale;
791
+ }
792
+ /** Fast relevance ranking for task-phrase context fallback: FTS + bounded graph priors, no model. */
793
+ rankedSearch(query, limit = 12, opts = {}) {
794
+ return this.rerankByPriors(this.search(query, Math.max(limit, 24)), limit, query, opts.freshnessRoot ?? this.paths.root);
795
+ }
711
796
  /** The prior-bearing metadata for a hit: liveness, provenance, effective date,
712
797
  * and (runbooks) trigger phrases. null = structural ref, neutral prior. */
713
798
  priorMeta(ref, kind) {
@@ -779,9 +864,9 @@ export class HunchStore {
779
864
  tokenCap: boundedWhole(opts.graphTokenCap, GRAPH_TOKEN_CAP, GRAPH_TOKEN_HARD_MAX),
780
865
  }, gw);
781
866
  if (!sem.length && !graph.length)
782
- return this.rerankByPriors(fts, limit, query);
867
+ return this.rerankByPriors(fts, limit, query, opts.freshnessRoot ?? this.paths.root);
783
868
  // Fuse with headroom so the prior rerank can promote from below the cut line.
784
- return this.rerankByPriors(this.rrfFuse(fts, sem, graph, Math.max(limit, 24), gw), limit, query);
869
+ return this.rerankByPriors(this.rrfFuse(fts, sem, graph, Math.max(limit, 24), gw), limit, query, opts.freshnessRoot ?? this.paths.root);
785
870
  }
786
871
  /** Runbook-scoped retrieval (roadmap #5): the same FTS+graph(+semantic) fusion,
787
872
  * restricted to the `runbooks` kind — so a "what's the procedure for X" query
@@ -1777,6 +1862,11 @@ const GRAPH_TOKEN_CAP = boundedWhole(numEnv("HUNCH_GRAPH_TOKEN_CAP", 2_000), 2_0
1777
1862
  * rerankByPriors for the measurement that fixed it at 4. */
1778
1863
  const PRIOR_SHIFT_SCALE = numEnv("HUNCH_PRIOR_SHIFT_SCALE", 12);
1779
1864
  const MAX_PRIOR_SHIFT = numEnv("HUNCH_MAX_PRIOR_SHIFT", 4);
1865
+ /** A stale anchored decision stays visible and authoritative; it moves down by at most the shared prior clamp. */
1866
+ const STALE_DECISION_PRIOR_WEIGHT = Math.min(1, numEnv("HUNCH_STALE_DECISION_PRIOR_WEIGHT", 0.8));
1867
+ const DECISION_FRESHNESS_SCOPE_QUERY_CAP = 256;
1868
+ const DECISION_FRESHNESS_SCOPE_CACHE_CAP = 512;
1869
+ const DECISION_FRESHNESS_PATH_CACHE_CAP = 4_096;
1780
1870
  /** Memory-record prior: a "why" question is answered by RECORDED INTENT (decisions,
1781
1871
  * constraints, bugs, runbooks, policies), not by the code symbols that merely share
1782
1872
  * its vocabulary. Symbols carry a neutral prior (priorMeta -> null), so on a graph
@@ -1789,6 +1879,23 @@ const MAX_PRIOR_SHIFT = numEnv("HUNCH_MAX_PRIOR_SHIFT", 4);
1789
1879
  * 0.402 -> 0.575. Set HUNCH_MEMORY_PRIOR_SHIFT=0 to disable. */
1790
1880
  const MEMORY_PRIOR_SHIFT = numEnv("HUNCH_MEMORY_PRIOR_SHIFT", 12);
1791
1881
  const MEMORY_KINDS = new Set(["decisions", "constraints", "bugs", "runbooks", "policies"]);
1882
+ function safeFreshnessScope(value) {
1883
+ const normalized = toPosixTarget(value.trim());
1884
+ if (!normalized || normalized.length > 1_024 || normalized.includes("\0")
1885
+ || isAbsolute(normalized) || /^[a-zA-Z]:/.test(normalized)
1886
+ || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")
1887
+ || normalized.startsWith("private:"))
1888
+ return null;
1889
+ return normalized.replace(/^\.\//, "");
1890
+ }
1891
+ function freshnessScopeMatches(path, scope) {
1892
+ try {
1893
+ return pathsRelated(path, scope) || pathMatchesGlob(path, scope);
1894
+ }
1895
+ catch {
1896
+ return false;
1897
+ }
1898
+ }
1792
1899
  function numEnv(name, dflt) {
1793
1900
  const v = Number(process.env[name]);
1794
1901
  // >= 0, not > 0: zero is the documented kill-switch (HUNCH_RRF_W_*=0 disables
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.20.1",
3
+ "version": "1.20.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://hunch-pi.vercel.app",
10
- "version": "1.20.1",
10
+ "version": "1.20.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.20.1",
16
+ "version": "1.20.3",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {