@davesheffer/hunch 1.11.0 → 1.12.0

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
@@ -57,7 +57,7 @@ import { blockingInScope, vetoInScope, proposedEditLines } from "../core/hookpol
57
57
  import { isHumanConfirmed } from "../core/strictgate.js";
58
58
  import { appendEvent, readEvents } from "../core/events.js";
59
59
  import { computeStats, formatStats } from "../core/stats.js";
60
- import { injectionMode } from "../core/hookcache.js";
60
+ import { injectionMode, resetSessionInjections } from "../core/hookcache.js";
61
61
  import { contextHookOutput, denyHookOutput, hookProvider, normalizeHookEvent, stopHookOutput } from "../core/agenthook.js";
62
62
  import { PIPELINE_LOOP, UNVERIFIED_NAG, loadPipelineState, onCommand, onEdit, onPrompt, onSkill, pipelineEnabled, savePipelineState, stopVerdict, } from "../core/pipeline.js";
63
63
  import { draftDuplicateOf, isAcceptedDuplicateAnchor } from "../core/dupdetect.js";
@@ -3797,7 +3797,53 @@ program
3797
3797
  emitContext(provider, "UserPromptSubmit", text);
3798
3798
  return;
3799
3799
  }
3800
+ if (evt.hook_event_name === "PreCompact") {
3801
+ // Compaction is about to summarize injected grounding out of the agent's
3802
+ // context while the dedup map still says "delivered". Reset it so every
3803
+ // post-compact injection is full again. Emit nothing — this event has no
3804
+ // context channel worth spending.
3805
+ resetSessionInjections(evt.session_id);
3806
+ return;
3807
+ }
3808
+ if (evt.hook_event_name === "SubagentStart") {
3809
+ // A delegated agent starts with NONE of the parent session's grounding:
3810
+ // session orientation never fired inside it and only per-edit PreToolUse
3811
+ // follows it in — so read-only agents (Explore/Plan) could work fully
3812
+ // blind. Give it the invariants that must survive delegation. Public
3813
+ // store only; once per agent type per session.
3814
+ const s = new HunchStore(paths);
3815
+ try {
3816
+ const sevRank = { blocking: 0, warning: 1, advisory: 2 };
3817
+ const constraints = s.advisoryRecs("constraints")
3818
+ .filter((c) => c.status === "active")
3819
+ .sort((a, b) => sevRank[a.severity] - sevRank[b.severity]);
3820
+ if (!constraints.length)
3821
+ return;
3822
+ const L = [`🧠 Hunch — delegated agent grounding: ${constraints.length} invariant(s) in force in this repo.`];
3823
+ for (const c of constraints.slice(0, 8)) {
3824
+ const flat = c.statement.replace(/\s+/g, " ").trim();
3825
+ const claim = flat.length > 140 ? `${flat.slice(0, 139).trimEnd()}…` : flat;
3826
+ L.push(`- [${c.severity}] ${claim}${c.scope.length ? ` (scope: ${c.scope.slice(0, 3).join(", ")})` : ""}`);
3827
+ }
3828
+ if (constraints.length > 8)
3829
+ L.push(`…and ${constraints.length - 8} more — hunch_check_constraints(scope) for your files.`);
3830
+ L.push("Before editing: hunch_check_constraints(scope) · hunch_why(target). Orient: hunch_context(task).");
3831
+ // No dedup here: the hook event carries the PARENT session id, but each
3832
+ // spawned agent is a fresh empty context — deduping would ground the
3833
+ // first Explore and silently starve every later one.
3834
+ emitContext(provider, "SubagentStart", L.join("\n"));
3835
+ }
3836
+ finally {
3837
+ s.close();
3838
+ }
3839
+ return;
3840
+ }
3800
3841
  if (evt.hook_event_name === "SessionStart") {
3842
+ // A compact-resume means everything injected so far was just summarized
3843
+ // away — the dedup map must forget it delivered anything, or the rest of
3844
+ // the session gets delta one-liners against grounding that is gone.
3845
+ if (evt.source === "compact")
3846
+ resetSessionInjections(evt.session_id);
3801
3847
  // Orientation at the moment it matters: what just happened + what's next,
3802
3848
  // straight from the graph — the agent sits down already knowing where it
3803
3849
  // is instead of pulling (or worse, grepping) for it. Cheap reads only
@@ -77,6 +77,8 @@ function eventName(value, provider) {
77
77
  posttooluse: "PostToolUse",
78
78
  userpromptsubmit: "UserPromptSubmit",
79
79
  sessionstart: "SessionStart",
80
+ subagentstart: "SubagentStart",
81
+ precompact: "PreCompact",
80
82
  stop: "Stop",
81
83
  };
82
84
  if (map[name])
@@ -155,6 +157,8 @@ export function normalizeHookEvent(raw, provider) {
155
157
  tool_name: hunchToolName(stringAt(input, "tool_name", "toolName"), toolInput ?? {}),
156
158
  tool_input: toolInput,
157
159
  prompt: stringAt(input, "prompt", "user_prompt", "userPrompt"),
160
+ source: stringAt(input, "source"),
161
+ agent_type: stringAt(input, "agent_type", "agentType", "subagent_type", "subagentType"),
158
162
  };
159
163
  }
160
164
  /** Provider-aware hook output. Context output is intentionally omitted for
@@ -55,6 +55,22 @@ export function injectionMode(sessionId, key, content) {
55
55
  return "full"; // grounded beats deduped, always
56
56
  }
57
57
  }
58
+ /** Forget everything injected into a session. Compaction summarizes injected
59
+ * grounding out of the agent's context while the dedup map still says
60
+ * "delivered" — so on PreCompact / SessionStart[source=compact] the map must
61
+ * reset, or post-compact edits get delta one-liners against grounding the
62
+ * agent no longer has. Never throws (same posture as injectionMode). */
63
+ export function resetSessionInjections(sessionId) {
64
+ try {
65
+ if (!sessionId)
66
+ return;
67
+ const file = join(tmpdir(), "hunch-hookcache", `${sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 80)}.json`);
68
+ rmSync(file, { force: true });
69
+ }
70
+ catch {
71
+ /* unwritable tmpdir — next injectionMode call falls back to "full" anyway */
72
+ }
73
+ }
58
74
  /** Drop session caches from long-gone sessions (best effort, bounded dir). */
59
75
  function sweep(dir) {
60
76
  try {
@@ -156,6 +156,18 @@ export function installClaudeHooks(root, hookCmd) {
156
156
  ...keep(json.hooks.SessionStart),
157
157
  { hooks: [{ type: "command", command: hookCmd }] },
158
158
  ];
159
+ // Delegated agents start with no session grounding (orientation never fired
160
+ // inside them); compaction summarizes injected grounding away while the dedup
161
+ // map still says "delivered". These two events keep delivery alive across the
162
+ // whole session lifecycle, not just its first context window.
163
+ json.hooks.SubagentStart = [
164
+ ...keep(json.hooks.SubagentStart),
165
+ { hooks: [{ type: "command", command: hookCmd }] },
166
+ ];
167
+ json.hooks.PreCompact = [
168
+ ...keep(json.hooks.PreCompact),
169
+ { hooks: [{ type: "command", command: hookCmd }] },
170
+ ];
159
171
  // Verification pipeline (core/pipeline.ts): PostToolUse records observable
160
172
  // facts (edits, verify commands); Stop refuses to end a turn with unverified
161
173
  // product edits at firm/strict firmness. Delivery is enforced, not hoped for.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
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.11.0",
10
+ "version": "1.12.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.11.0",
16
+ "version": "1.12.0",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {