@davesheffer/hunch 1.47.1 → 1.47.2

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/dist/cli/index.js CHANGED
@@ -23,7 +23,8 @@ import { Command } from "commander";
23
23
  import { hunchPaths, hunchPathsForDir, findRoot, toPosixTarget, isDir, realpathNorm, repoRelativeTarget } from "../core/paths.js";
24
24
  import { writeFileAtomic } from "../core/io.js";
25
25
  import { boundedSpawnSync } from "../core/boundedSpawn.js";
26
- import { looksLikeCorrection, CORRECTION_NUDGE } from "../core/correction.js";
26
+ import { looksLikeCorrection, CORRECTION_NUDGE, stripHostBlocks } from "../core/correction.js";
27
+ import { roleHasHunchTools } from "../core/subagentrole.js";
27
28
  import { HUNCH_VERSION } from "../core/version.js";
28
29
  import { registerIntegrationCommands } from "./integrations.js";
29
30
  import { registerTaskReportCommands } from "./taskReport.js";
@@ -5456,9 +5457,11 @@ program
5456
5457
  return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat;
5457
5458
  };
5458
5459
  const type = (evt.agent_type ?? "").toLowerCase();
5460
+ // A role whose tool list leaves out Hunch's MCP tools is not told to call them.
5461
+ const tools = roleHasHunchTools(root, evt.agent_type ?? "");
5459
5462
  const L = [];
5460
5463
  const served = [];
5461
- const route = routedCwd
5464
+ const route = routedCwd && tools
5462
5465
  ? `Worktree routing: call hunch_context first with cwd: ${JSON.stringify(routedCwd)}, and pass the same cwd to hunch_task, hunch_report, and every Hunch capture/write call in this delegated task.`
5463
5466
  : null;
5464
5467
  const activeProvider = provider;
@@ -5478,8 +5481,9 @@ program
5478
5481
  served.push({ kind: "components", record_id: c.id, token_cost: Math.ceil([...line].length / 4) });
5479
5482
  }
5480
5483
  if (components.length > 12)
5481
- L.push(`…and ${components.length - 12} more — hunch_structure() for the full map.`);
5482
- L.push("Orient: hunch_structure(target) · hunch_why(target) · hunch_context(task).");
5484
+ L.push(`…and ${components.length - 12} more${tools ? " — hunch_structure() for the full map" : ""}.`);
5485
+ if (tools)
5486
+ L.push("Orient: hunch_structure(target) · hunch_why(target) · hunch_context(task).");
5483
5487
  }
5484
5488
  else if (/plan|architect|design/.test(type)) {
5485
5489
  // A plan drafted blind re-proposes what the graph already rejected.
@@ -5496,7 +5500,8 @@ program
5496
5500
  L.push(line);
5497
5501
  served.push({ kind: "decisions", record_id: d.id, token_cost: Math.ceil([...line].length / 4) });
5498
5502
  }
5499
- L.push("Before finalizing a plan: hunch_why(target) · hunch_current_decision(topic) · hunch_check_constraints(scope).");
5503
+ if (tools)
5504
+ L.push("Before finalizing a plan: hunch_why(target) · hunch_current_decision(topic) · hunch_check_constraints(scope).");
5500
5505
  }
5501
5506
  else {
5502
5507
  const sevRank = { blocking: 0, warning: 1, advisory: 2 };
@@ -5514,8 +5519,9 @@ program
5514
5519
  served.push({ kind: "constraints", record_id: c.id, token_cost: Math.ceil([...line].length / 4) });
5515
5520
  }
5516
5521
  if (constraints.length > 8)
5517
- L.push(`…and ${constraints.length - 8} more — hunch_check_constraints(scope) for your files.`);
5518
- L.push("Before editing: hunch_check_constraints(scope) · hunch_why(target). Orient: hunch_context(task).");
5522
+ L.push(`…and ${constraints.length - 8} more${tools ? " — hunch_check_constraints(scope) for your files" : "; the edit hook shows the ones your files touch"}.`);
5523
+ if (tools)
5524
+ L.push("Before editing: hunch_check_constraints(scope) · hunch_why(target). Orient: hunch_context(task).");
5519
5525
  }
5520
5526
  if (route)
5521
5527
  L.push(route);
@@ -7768,19 +7774,21 @@ function promptTaskSelection(root, store, provider, evt) {
7768
7774
  const taskId = hookReportTaskId(root, provider, evt);
7769
7775
  if (!taskId)
7770
7776
  return "";
7777
+ // Host-injected blocks (reminders, browser instructions) are not the task the human named.
7778
+ const humanText = stripHostBlocks(evt.prompt ?? "");
7771
7779
  const selection = selectForTask(liveSelectionRecords({
7772
7780
  decisions: store.recs("decisions"),
7773
7781
  bugs: store.recs("bugs"),
7774
7782
  constraints: store.recs("constraints"),
7775
7783
  findings: store.recs("findings"),
7776
- }), evt.prompt ?? "", { root, pathExists: (p) => existsSync(join(root, p)) });
7784
+ }), humanText, { root, pathExists: (p) => existsSync(join(root, p)) });
7777
7785
  // Same task id again (a notification alias) keeps its own earlier selection; a
7778
7786
  // new task that CONTINUES the session's previous one inherits that task's
7779
7787
  // selection only on a bare follow-up ("continue", "go on"), so it is not left
7780
7788
  // ungrounded. A substantive prompt names its own task: a same-session prompt
7781
7789
  // inside the continuation window must not hide memory behind an old selection.
7782
7790
  let previous = loadTaskSelection(taskId);
7783
- if (!previous && isBareFollowUp(evt.prompt ?? "")) {
7791
+ if (!previous && isBareFollowUp(humanText)) {
7784
7792
  try {
7785
7793
  const continues = readTaskReport(root, taskId).task.continues;
7786
7794
  previous = continues ? loadTaskSelection(continues) : null;
@@ -30,11 +30,12 @@ const CORRECTION_PATTERNS = [
30
30
  /\b(not like that|don'?t do (that|this)( again)?|stop doing (that|this))\b/i,
31
31
  ];
32
32
  /** Host-generated blocks a prompt can carry: a background task's completion
33
- * (`<task-notification>`, which also wraps a subagent's hand-back report) and
34
- * injected context (`<system-reminder>`, hook output). Their words are not the
33
+ * (`<task-notification>`, which also wraps a subagent's hand-back report),
34
+ * injected context (`<system-reminder>`, hook output) and the browser
35
+ * extension's standing instructions (`<browser_instruction>`). Their words are not the
35
36
  * human's, so "never use X" quoted from a constraint is not a correction. A tag
36
37
  * left unclosed drops only the rest of its own line, never the human's text below. */
37
- const HOST_TAGS = "task-notification|system-reminder|local-command-stdout|local-command-stderr|command-message|command-name|command-args";
38
+ const HOST_TAGS = "task-notification|system-reminder|local-command-stdout|local-command-stderr|command-message|command-name|command-args|browser_instruction";
38
39
  const HOST_BLOCK = new RegExp(`<(${HOST_TAGS})>[\\s\\S]*?<\\/\\1>`, "gi");
39
40
  const HOST_OPEN_LINE = new RegExp(`<(?:${HOST_TAGS})>[^\\n]*`, "gi");
40
41
  /** The prompt with host-generated blocks removed: what the human actually typed. */
@@ -0,0 +1,6 @@
1
+ /** Whether a delegated agent can call Hunch's MCP tools. A named role defined in
2
+ * `.claude/agents/<type>.md` (project first, then user) with a `tools:` list that
3
+ * names no `mcp__hunch` tool and no wildcard cannot: telling it to call
4
+ * hunch_why only costs it a failed lookup. Built-in and unknown types inherit
5
+ * the parent's tools. */
6
+ export declare function roleHasHunchTools(root: string, agentType: string, home?: string): boolean;
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ /** Whether a delegated agent can call Hunch's MCP tools. A named role defined in
5
+ * `.claude/agents/<type>.md` (project first, then user) with a `tools:` list that
6
+ * names no `mcp__hunch` tool and no wildcard cannot: telling it to call
7
+ * hunch_why only costs it a failed lookup. Built-in and unknown types inherit
8
+ * the parent's tools. */
9
+ export function roleHasHunchTools(root, agentType, home = homedir()) {
10
+ const type = agentType.trim();
11
+ if (!type || !/^[\w.-]+$/.test(type))
12
+ return true;
13
+ for (const dir of [join(root, ".claude", "agents"), join(home, ".claude", "agents")]) {
14
+ let text;
15
+ try {
16
+ text = readFileSync(join(dir, `${type}.md`), "utf8");
17
+ }
18
+ catch {
19
+ continue;
20
+ }
21
+ const front = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1];
22
+ if (!front)
23
+ return true;
24
+ // A block list continues on indented "- x" lines; a flow list runs to its "]".
25
+ const tools = /^tools:[ \t]*(\[[^\]]*\]?|.*(?:\r?\n[ \t]+-.*)*)/m.exec(front)?.[1];
26
+ if (tools === undefined || !tools.trim())
27
+ return true;
28
+ return /\*|mcp__hunch/.test(tools);
29
+ }
30
+ return true;
31
+ }
32
+ //# sourceMappingURL=subagentrole.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.47.1",
3
+ "version": "1.47.2",
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.47.1",
10
+ "version": "1.47.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.47.1",
16
+ "version": "1.47.2",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {