@davesheffer/hunch 0.17.0 → 0.17.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/dist/cli/index.js CHANGED
@@ -64,6 +64,7 @@ program
64
64
  .option("--no-providers", "skip scaffolding non-Claude assistant configs (Cursor / VS Code / Codex / AGENTS.md)")
65
65
  .option("--no-agent-hooks", "skip installing the Claude Code agent hooks (.claude/settings.json)")
66
66
  .option("--firmness <level>", "agent-hook firmness: off | advisory | firm | strict")
67
+ .option("--private-sync", "post-commit synthesis writes captured decisions into the private overlay (HUNCH_PRIVATE_DIR), never the public repo")
67
68
  .action((opts) => {
68
69
  // Validate --firmness up front, before any side effects (indexing, git hooks,
69
70
  // .mcp.json) or opening the store — a bad value must not leave a half-init.
@@ -92,8 +93,8 @@ program
92
93
  console.log(` ⚠ ${res.skipped} file(s) could not be parsed (skipped)`);
93
94
  }
94
95
  if (isGitRepo(root)) {
95
- const h = installPostCommitHook(root, inv.shell);
96
- console.log(` ✓ post-commit hook ${h.action} (learning loop)`);
96
+ const h = installPostCommitHook(root, inv.shell, { private: opts.privateSync });
97
+ console.log(` ✓ post-commit hook ${h.action} (learning loop)${opts.privateSync ? " — syncs to the private overlay" : ""}`);
97
98
  const m = installMergeDriver(root, inv.shell);
98
99
  console.log(` ✓ team merge driver ${m.action}`);
99
100
  // Auto-install the pre-commit guard by default (advisory: flags invariants
@@ -223,12 +224,17 @@ program
223
224
  .option("--from-hook", "invoked by the git hook")
224
225
  .option("--quiet", "minimal output")
225
226
  .option("--force", "re-synthesize even if a decision already exists for the commit")
227
+ .option("--private", "write the synthesized decision into the private overlay (HUNCH_PRIVATE_DIR), not the public repo — for a repo whose memory is kept private")
226
228
  .action(async (sha, opts) => {
227
229
  const { store, root } = storeFor();
228
230
  if (!isGitRepo(root))
229
231
  return opts.quiet ? undefined : fail("sync needs a git repo");
232
+ if (opts.private && !store.hasPrivate) {
233
+ store.close();
234
+ return opts.quiet ? undefined : fail("--private needs HUNCH_PRIVATE_DIR set to a private store");
235
+ }
230
236
  store.json.ensureDirs();
231
- const r = await syncCommit(store, root, sha ?? headSha(root), { force: opts.force });
237
+ const r = await syncCommit(store, root, sha ?? headSha(root), { force: opts.force, private: opts.private });
232
238
  if (r.status === "written") {
233
239
  store.reindex();
234
240
  // Don't rewrite CLAUDE.md from the hook — it would dirty the working tree
@@ -9,17 +9,21 @@ import { join, isAbsolute } from "node:path";
9
9
  import { hooksDir } from "../extractors/git.js";
10
10
  const MARK = "# >>> hunch post-commit >>>";
11
11
  const ENDMARK = "# <<< hunch post-commit <<<";
12
- function block(invocation) {
12
+ function block(invocation, opts = {}) {
13
+ // --private routes the auto-synthesized decision into the HUNCH_PRIVATE_DIR overlay
14
+ // instead of the public repo. The hook script is local (.git/hooks/), never committed,
15
+ // so a repo whose memory is kept private leaves no trace of this in the public tree.
16
+ const priv = opts.private ? " --private" : "";
13
17
  return [
14
18
  MARK,
15
19
  'if [ -z "$HUNCH_SYNC" ]; then',
16
20
  " export HUNCH_SYNC=1",
17
- ` ( ${invocation} sync --from-hook --quiet >/dev/null 2>&1 || true ) &`,
21
+ ` ( ${invocation} sync --from-hook --quiet${priv} >/dev/null 2>&1 || true ) &`,
18
22
  "fi",
19
23
  ENDMARK,
20
24
  ].join("\n");
21
25
  }
22
- export function installPostCommitHook(root, invocation) {
26
+ export function installPostCommitHook(root, invocation, opts = {}) {
23
27
  const dir = hooksDir(root);
24
28
  // `git rev-parse --git-path hooks` returns a path relative to the repo in a
25
29
  // normal checkout, but an ABSOLUTE one inside a linked worktree (the shared
@@ -28,7 +32,7 @@ export function installPostCommitHook(root, invocation) {
28
32
  const abs = isAbsolute(dir) ? dir : join(root, dir);
29
33
  mkdirSync(abs, { recursive: true });
30
34
  const hookPath = join(abs, "post-commit");
31
- const blk = block(invocation);
35
+ const blk = block(invocation, opts);
32
36
  if (!existsSync(hookPath)) {
33
37
  writeFileSync(hookPath, `#!/bin/sh\n${blk}\n`);
34
38
  chmodSync(hookPath, 0o755);
@@ -119,7 +119,12 @@ export async function syncCommit(store, root, sha, opts = {}) {
119
119
  },
120
120
  date: meta.date, // the commit date
121
121
  };
122
- store.json.put("decisions", decision);
122
+ // Route to the PRIVATE overlay when asked (post-commit sync in a repo whose memory
123
+ // is kept private) — keeps auto-captured decisions out of the public repo entirely.
124
+ if (opts.private)
125
+ store.putPrivate("decisions", decision);
126
+ else
127
+ store.json.put("decisions", decision);
123
128
  return { status: "written", decision, provider: provider.name };
124
129
  }
125
130
  /** Capture a Bug from a test failure. Suspects are ranked churn×recency×fan-in. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "license": "Apache-2.0",
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.",