@davesheffer/hunch 0.14.0 → 0.14.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.
package/README.md CHANGED
@@ -109,8 +109,10 @@ The MCP tools Claude calls under the hood: `hunch_why`, `hunch_query`,
109
109
  `hunch_check_constraints`, `hunch_get_dependents` (blast radius), `hunch_blast_radius`
110
110
  (dependent files + near-violations a change could break indirectly), `hunch_bug_lineage`,
111
111
  `hunch_context` (surgical minimal slice for a task), `hunch_timeline` (a target's decision
112
- history over time), `hunch_record_decision` (write-back). `hunch_why` and `hunch_context`
113
- take an optional `as_of` (commit/tag/branch) to **time-travel** the graph to a past state.
112
+ history over time), `hunch_merge_verdict` (a cited BLOCK/WARN/PASS over a diff — see below),
113
+ and the write-backs `hunch_record_decision`, `hunch_record_correction` (turn a correction into
114
+ an enforced invariant — see below). `hunch_why` and `hunch_context` take an optional `as_of`
115
+ (commit/tag/branch) to **time-travel** the graph to a past state.
114
116
 
115
117
  ### Works with any MCP assistant
116
118
 
@@ -177,6 +177,18 @@ class CliSynthProvider {
177
177
  return draft;
178
178
  }
179
179
  }
180
+ // A model id comes from a HUNCH_*_MODEL env var and ends up as an argv token that,
181
+ // on Windows, pexecIn joins into the cmd.exe line (shell:true, to resolve the npm
182
+ // `.cmd` shim). The untrusted prompt always travels via stdin — never argv — so the
183
+ // ONLY non-literal token reaching that shell line is this model id. Reject anything
184
+ // with whitespace or a cmd.exe metacharacter so a poisoned env var can't smuggle
185
+ // `& evil.exe` into the line; fall back to the provider default rather than crash on
186
+ // a mere typo. (pexecIn itself stays general — callers may legitimately pass a
187
+ // pre-quoted path token — so the guard lives here, at the untrusted-input source.)
188
+ const MODEL_RE = /^[A-Za-z0-9._:/-]+$/;
189
+ export function safeModel(v, fallback) {
190
+ return v && MODEL_RE.test(v) ? v : fallback;
191
+ }
180
192
  // --------------------------------------------------------------------------
181
193
  // Provider A: headless `claude -p` CLI — billed to the user's Claude subscription
182
194
  // --------------------------------------------------------------------------
@@ -184,7 +196,7 @@ class ClaudeCliProvider extends CliSynthProvider {
184
196
  name = "claude-cli";
185
197
  // Default to the `haiku` alias (cheap/fast, and survives model retirements)
186
198
  // rather than a pinned dated id; override with HUNCH_SYNTH_MODEL if needed.
187
- model = process.env.HUNCH_SYNTH_MODEL || "haiku";
199
+ model = safeModel(process.env.HUNCH_SYNTH_MODEL, "haiku");
188
200
  async available() {
189
201
  try {
190
202
  await pexecIn("claude", ["--version"], { timeout: 8000 });
@@ -242,7 +254,7 @@ class ClaudeCliProvider extends CliSynthProvider {
242
254
  // --------------------------------------------------------------------------
243
255
  class CodexCliProvider extends CliSynthProvider {
244
256
  name = "codex-cli";
245
- model = process.env.HUNCH_CODEX_MODEL; // omit → codex uses its configured default
257
+ model = safeModel(process.env.HUNCH_CODEX_MODEL, undefined); // omit → codex uses its configured default
246
258
  async available() {
247
259
  try {
248
260
  await pexecIn("codex", ["--version"], { timeout: 8000 });
@@ -266,7 +278,7 @@ class CodexCliProvider extends CliSynthProvider {
266
278
  // --------------------------------------------------------------------------
267
279
  class CursorCliProvider extends CliSynthProvider {
268
280
  name = "cursor-agent";
269
- model = process.env.HUNCH_CURSOR_MODEL;
281
+ model = safeModel(process.env.HUNCH_CURSOR_MODEL, undefined);
270
282
  async available() {
271
283
  try {
272
284
  await pexecIn("cursor-agent", ["--version"], { timeout: 8000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "license": "MIT",
5
5
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
6
6
  "description": "Hunch — an Engineering Memory OS: a persistent, git-native reasoning graph over a codebase, exposed to Claude Code via MCP.",
@@ -20,7 +20,8 @@
20
20
  "dist/**/*.js"
21
21
  ],
22
22
  "publishConfig": {
23
- "access": "public"
23
+ "access": "public",
24
+ "provenance": true
24
25
  },
25
26
  "keywords": [
26
27
  "claude-code",