@davesheffer/hunch 0.31.0 → 0.33.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/README.md +10 -0
- package/dist/cli/index.js +71 -1
- package/dist/extractors/git.js +77 -8
- package/dist/integrations/worktree.js +33 -0
- package/dist/store/hunchStore.js +26 -9
- package/dist/synthesis/synthesize.js +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,16 @@ is **OS-agnostic**: paths are stored in POSIX form and an installed Hunch regist
|
|
|
185
185
|
server by package name, so Windows / macOS / Linux teammates share one memory without
|
|
186
186
|
per-machine fixups. → [docs](https://hunch-pi.vercel.app/docs#team)
|
|
187
187
|
|
|
188
|
+
### Branches & worktrees
|
|
189
|
+
|
|
190
|
+
Memory follows you across every branch and **git worktree**, with no per-worktree setup. The
|
|
191
|
+
private overlay is registered once at the repo's **git common dir** (shared by all worktrees), so
|
|
192
|
+
a fresh `git worktree add` on any branch sees the same decisions, bugs, and invariants. Create one
|
|
193
|
+
already wired in with **`hunch worktree <path> [-b <branch>]`**, or just run `hunch init` / `hunch
|
|
194
|
+
private` once and every worktree picks it up. Auto-captured decisions are tagged with their branch,
|
|
195
|
+
and concurrent overlay writes are serialized — so parallel worktrees never corrupt or lose memory.
|
|
196
|
+
`hunch doctor` confirms a worktree is sharing.
|
|
197
|
+
|
|
188
198
|
## Private memory (public repo, private context)
|
|
189
199
|
|
|
190
200
|
Open-source your code without open-sourcing your *reasoning*. **`hunch private`** sets up a
|
package/dist/cli/index.js
CHANGED
|
@@ -28,12 +28,13 @@ import { indexRepo } from "../extractors/indexer.js";
|
|
|
28
28
|
import { syncCommit, recordFailure, captureTestRun } from "../synthesis/synthesize.js";
|
|
29
29
|
import { parseTestReport } from "../extractors/testreport.js";
|
|
30
30
|
import { selectProvider } from "../synthesis/provider.js";
|
|
31
|
-
import { isGitRepo, headSha, logSince, lastChangeDate, stagedFiles, commitFiles, asOfDate, stagedDiff, commitDiff, rangeFiles, rangeDiff, rangeSubjects, revExists, commitAndPushHunch, gitUntrackCached } from "../extractors/git.js";
|
|
31
|
+
import { isGitRepo, headSha, logSince, lastChangeDate, stagedFiles, commitFiles, asOfDate, stagedDiff, commitDiff, rangeFiles, rangeDiff, rangeSubjects, revExists, commitAndPushHunch, gitUntrackCached, gitCommonDir, isLinkedWorktree } from "../extractors/git.js";
|
|
32
32
|
import { runbookId, decisionId } from "../core/ids.js";
|
|
33
33
|
import { extractInlineIntent } from "../extractors/comments.js";
|
|
34
34
|
import { renderText, renderMarkdown, reportFailsStrict } from "../core/checkreport.js";
|
|
35
35
|
import { partitionReview, READY_MIN_GROUNDED } from "../core/reviewqueue.js";
|
|
36
36
|
import { installPostCommitHook, installPreCommitHook } from "../integrations/hooks.js";
|
|
37
|
+
import { ensureSharedOverlayPointer } from "../integrations/worktree.js";
|
|
37
38
|
import { installMergeDriver } from "../integrations/mergeDriver.js";
|
|
38
39
|
import { ensureGitignore, ignoreHunchMemory, HUNCH_MEMORY_DIRS } from "../integrations/gitignore.js";
|
|
39
40
|
import { writeCiWorkflow } from "../integrations/ciAction.js";
|
|
@@ -154,6 +155,15 @@ program
|
|
|
154
155
|
// case-split in ~/.claude.json, merge it so hunch resolves under either casing.
|
|
155
156
|
// No-op (silent) off Windows.
|
|
156
157
|
reportClaudeConfigHeal();
|
|
158
|
+
// Worktree-seamless: register any configured overlay at the git common dir so EVERY
|
|
159
|
+
// worktree of this repo auto-discovers it (also backfills pre-0.32 single-worktree setups),
|
|
160
|
+
// and note when we're initializing inside a linked worktree (memory is shared, not separate).
|
|
161
|
+
if (ensureSharedOverlayPointer(root, store.privateDir, store.privateAutoCommit)) {
|
|
162
|
+
console.log(` ✓ private overlay registered at the git common dir — shared by every worktree of this repo`);
|
|
163
|
+
}
|
|
164
|
+
if (isLinkedWorktree(root)) {
|
|
165
|
+
console.log(` ✓ linked worktree — sharing the repo's hooks + memory (no separate setup needed)`);
|
|
166
|
+
}
|
|
157
167
|
store.close();
|
|
158
168
|
console.log("\nNext: make a commit (the hook captures a decision), then ask your coding assistant \"why is X built this way?\"");
|
|
159
169
|
console.log("Cold start? Seed from history: hunch backfill --since 90d");
|
|
@@ -344,6 +354,14 @@ program
|
|
|
344
354
|
const stored = rel && !rel.startsWith("..") && !isAbsolute(rel) ? toPosixTarget(rel) : hunchDir;
|
|
345
355
|
writeFileAtomic(join(paths.hunch, "local.json"), JSON.stringify({ privateDir: stored, autoCommit: !!opts.autoCommit }, null, 2) + "\n");
|
|
346
356
|
ensureGitignore(root); // keeps .hunch/local.json + .hunch-private/ out of git
|
|
357
|
+
// Also register the overlay at the SHARED git common dir, so EVERY worktree of this repo
|
|
358
|
+
// (current + future, any branch) auto-discovers the same memory with zero per-worktree
|
|
359
|
+
// setup. Stored ABSOLUTE — a linked worktree resolves relative paths from its OWN root, so
|
|
360
|
+
// only an absolute path survives the move. Lives under .git/ (never tracked; nothing to ignore).
|
|
361
|
+
let worktreeNote = "";
|
|
362
|
+
if (ensureSharedOverlayPointer(root, hunchDir, !!opts.autoCommit)) {
|
|
363
|
+
worktreeNote = ` ✓ registered in the git common dir — shared by every worktree of this repo, on any branch\n`;
|
|
364
|
+
}
|
|
347
365
|
// 4) route post-commit synthesis to the overlay (local hook, never committed)
|
|
348
366
|
let hookNote = "";
|
|
349
367
|
if (opts.hook && isGitRepo(root)) {
|
|
@@ -379,11 +397,53 @@ program
|
|
|
379
397
|
}
|
|
380
398
|
console.log(`✓ private overlay enabled → ${hunchDir}\n` +
|
|
381
399
|
` ✓ recorded in .hunch/local.json (gitignored) — auto-detected, no env var or shell-profile edit\n` +
|
|
400
|
+
worktreeNote +
|
|
382
401
|
hookNote +
|
|
383
402
|
migrateNote +
|
|
384
403
|
` record sensitive items with private:true (hunch_record_decision / hunch_record_correction)\n` +
|
|
385
404
|
` override per-shell with HUNCH_PRIVATE_DIR; CI / public PR comments stay public-only.`);
|
|
386
405
|
});
|
|
406
|
+
// ---- worktree (one-command worktree wired into Hunch) ----------------------
|
|
407
|
+
program
|
|
408
|
+
.command("worktree <path>")
|
|
409
|
+
.description("Create a git worktree already wired into Hunch — it shares this repo's memory (the private overlay), with zero per-worktree setup.")
|
|
410
|
+
.option("-b, --branch <name>", "create the worktree on a NEW branch")
|
|
411
|
+
.option("--no-share", "don't register the overlay at the git common dir (the worktree won't see private memory)")
|
|
412
|
+
.action((path, opts) => {
|
|
413
|
+
const root = findRoot();
|
|
414
|
+
if (!isGitRepo(root))
|
|
415
|
+
return fail("`hunch worktree` needs a git repo");
|
|
416
|
+
const dest = resolve(root, path);
|
|
417
|
+
if (existsSync(dest))
|
|
418
|
+
return fail(`path already exists: ${dest}`);
|
|
419
|
+
// 1) create the worktree (on a new branch if asked, else a checkout of HEAD)
|
|
420
|
+
const r = spawnSync("git", ["-C", root, "worktree", "add", ...(opts.branch ? ["-b", opts.branch] : []), dest], { stdio: "inherit" });
|
|
421
|
+
if (r.status !== 0)
|
|
422
|
+
return fail("git worktree add failed");
|
|
423
|
+
// 2) register the overlay at the SHARED git common dir so the new worktree (and every
|
|
424
|
+
// other) auto-discovers the same memory — also backfills pre-0.32 single-worktree setups.
|
|
425
|
+
const store = new HunchStore(hunchPaths(root));
|
|
426
|
+
const overlay = store.privateDir;
|
|
427
|
+
const autoCommit = store.privateAutoCommit;
|
|
428
|
+
store.close();
|
|
429
|
+
let shareNote;
|
|
430
|
+
if (!opts.share) {
|
|
431
|
+
shareNote = ` · --no-share — the worktree will NOT see private memory`;
|
|
432
|
+
}
|
|
433
|
+
else if (overlay && ensureSharedOverlayPointer(root, overlay, autoCommit)) {
|
|
434
|
+
shareNote = ` ✓ memory shared via the git common dir — this worktree sees the same decisions / bugs / constraints`;
|
|
435
|
+
}
|
|
436
|
+
else if (overlay) {
|
|
437
|
+
shareNote = ` · could not register the shared overlay pointer (no git common dir?)`;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
shareNote = ` · no private overlay configured — run \`hunch private\` to share memory across worktrees`;
|
|
441
|
+
}
|
|
442
|
+
console.log(`✓ worktree created → ${dest}${opts.branch ? ` (new branch ${opts.branch})` : ""}\n` +
|
|
443
|
+
`${shareNote}\n` +
|
|
444
|
+
` hooks + MCP server are shared (worktree-aware) — open your assistant in the new worktree to start.\n` +
|
|
445
|
+
` (needs \`hunch\` installed globally; a worktree has no node_modules of its own)`);
|
|
446
|
+
});
|
|
387
447
|
// ---- query ----------------------------------------------------------------
|
|
388
448
|
program
|
|
389
449
|
.command("query")
|
|
@@ -1467,6 +1527,16 @@ program
|
|
|
1467
1527
|
console.log(store.privateDir
|
|
1468
1528
|
? `private: on → ${store.privateDir} (local overlay — unioned into queries; never committed or posted publicly)`
|
|
1469
1529
|
: dim(`private: off — run \`hunch private\` to keep sensitive memory in a separate repo (or set HUNCH_PRIVATE_DIR)`));
|
|
1530
|
+
// Worktree posture: linked worktrees share ONE memory via the git common dir. Only
|
|
1531
|
+
// surfaced in a linked worktree (no noise in a normal single checkout), so a
|
|
1532
|
+
// "memory missing here" symptom has an obvious cause + fix.
|
|
1533
|
+
if (isLinkedWorktree(root)) {
|
|
1534
|
+
const common = gitCommonDir(root);
|
|
1535
|
+
const sharedPtr = !!common && existsSync(join(common, "hunch", "local.json"));
|
|
1536
|
+
console.log(store.privateDir
|
|
1537
|
+
? `worktree: linked — sharing the repo's memory${sharedPtr ? " via the git common dir" : ""}`
|
|
1538
|
+
: dim(`worktree: linked, but no overlay resolved here — run \`hunch private\` once (any worktree) so all worktrees share it`));
|
|
1539
|
+
}
|
|
1470
1540
|
// Semantic search is opt-in and local. Report availability + coverage without
|
|
1471
1541
|
// loading the model (selectEmbedder only probes; embeddingStats just counts rows).
|
|
1472
1542
|
const emb = await selectEmbedder();
|
package/dist/extractors/git.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Deterministic git introspection for the extractor + learning loop.
|
|
2
2
|
* No LLM here — just parsing what git already knows. */
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { isAbsolute, resolve, join } from "node:path";
|
|
5
|
+
import { mkdirSync, rmSync, statSync, realpathSync } from "node:fs";
|
|
4
6
|
function git(args, cwd, maxBuffer = 64 * 1024 * 1024) {
|
|
5
7
|
// stdio: capture stdout, silence stderr (so "no commits yet" etc. don't leak).
|
|
6
8
|
return execFileSync("git", args, {
|
|
@@ -25,16 +27,50 @@ export function isGitRepo(cwd) {
|
|
|
25
27
|
* (no recursion). Stages with a pathspec scoped to `hunchDir`, so it never sweeps unrelated
|
|
26
28
|
* working-tree changes. Never throws — a non-repo dir / offline push just no-ops. */
|
|
27
29
|
export function commitAndPushHunch(hunchDir, message) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
// Serialize across worktrees: several worktrees auto-committing the SAME overlay repo
|
|
31
|
+
// at once would race git's index.lock. An atomic-mkdir lock lets one proceed; the others
|
|
32
|
+
// skip — safe because each record is already written to disk, so `git add .` here sweeps
|
|
33
|
+
// up anything a skipped run left pending (eventually-consistent, never lost).
|
|
34
|
+
const lock = join(hunchDir, ".hunch-commit.lock");
|
|
35
|
+
if (!acquireCommitLock(lock))
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
const env = { ...process.env, HUNCH_SYNC: "1" };
|
|
39
|
+
const run = (args) => {
|
|
40
|
+
try {
|
|
41
|
+
execFileSync("git", ["-C", hunchDir, ...args], { stdio: "ignore", env });
|
|
42
|
+
}
|
|
43
|
+
catch { /* best-effort: nothing staged / not a repo / offline */ }
|
|
44
|
+
};
|
|
45
|
+
run(["add", "--", "."]);
|
|
46
|
+
run(["commit", "-m", message]);
|
|
47
|
+
run(["push"]);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
30
50
|
try {
|
|
31
|
-
|
|
51
|
+
rmSync(lock, { recursive: true, force: true });
|
|
32
52
|
}
|
|
33
|
-
catch { /* best-effort
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
catch { /* released best-effort */ }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Atomic mkdir lock; reclaims a stale lock (a crashed holder) older than 60s. Returns
|
|
57
|
+
* false when another live holder owns it — the caller skips rather than blocks. */
|
|
58
|
+
function acquireCommitLock(lock) {
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(lock);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
try {
|
|
65
|
+
if (Date.now() - statSync(lock).mtimeMs > 60_000) {
|
|
66
|
+
rmSync(lock, { recursive: true, force: true });
|
|
67
|
+
mkdirSync(lock);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* lock vanished or races another reclaimer — treat as held */ }
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
38
74
|
}
|
|
39
75
|
export function headSha(cwd) {
|
|
40
76
|
return gitSafe(["rev-parse", "HEAD"], cwd);
|
|
@@ -65,6 +101,39 @@ export function hooksDir(cwd) {
|
|
|
65
101
|
export function gitDir(cwd) {
|
|
66
102
|
return gitSafe(["rev-parse", "--git-dir"], cwd) || ".git";
|
|
67
103
|
}
|
|
104
|
+
/** The SHARED git dir for the repo — identical across ALL linked worktrees (unlike
|
|
105
|
+
* `gitDir`, which is per-worktree). Absolute, so callers can anchor worktree-shared
|
|
106
|
+
* state (the private-overlay pointer) at one stable place. "" when not a git repo. */
|
|
107
|
+
export function gitCommonDir(cwd) {
|
|
108
|
+
const p = gitSafe(["rev-parse", "--git-common-dir"], cwd);
|
|
109
|
+
if (!p)
|
|
110
|
+
return "";
|
|
111
|
+
return isAbsolute(p) ? p : resolve(cwd, p);
|
|
112
|
+
}
|
|
113
|
+
/** True when `cwd` is inside a LINKED worktree (not the main checkout): its own git
|
|
114
|
+
* dir differs from the shared common dir. Used by `hunch doctor` and setup messaging. */
|
|
115
|
+
export function isLinkedWorktree(cwd) {
|
|
116
|
+
const common = gitCommonDir(cwd);
|
|
117
|
+
const own = gitSafe(["rev-parse", "--absolute-git-dir"], cwd);
|
|
118
|
+
if (!common || !own)
|
|
119
|
+
return false;
|
|
120
|
+
// realpath BOTH before comparing: `--absolute-git-dir` is symlink-resolved while
|
|
121
|
+
// gitCommonDir is not, so on macOS the main checkout would otherwise mismatch on
|
|
122
|
+
// /var vs /private/var and falsely read as "linked".
|
|
123
|
+
const norm = (p) => { try {
|
|
124
|
+
return realpathSync(p);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return resolve(p);
|
|
128
|
+
} };
|
|
129
|
+
return norm(own) !== norm(common);
|
|
130
|
+
}
|
|
131
|
+
/** Current branch name (e.g. "main", "feat/x"), or "" in detached HEAD / non-repo.
|
|
132
|
+
* Stamped onto auto-captured decisions so branch-scoped work stays filterable. */
|
|
133
|
+
export function currentBranch(cwd) {
|
|
134
|
+
const b = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
135
|
+
return b === "HEAD" ? "" : b; // detached HEAD reports "HEAD" — treat as no branch
|
|
136
|
+
}
|
|
68
137
|
/** Files changed in a single commit. `--root` makes the initial commit (which
|
|
69
138
|
* has no parent) report its files as additions instead of returning nothing. */
|
|
70
139
|
export function commitFiles(sha, cwd) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree wiring — make a repo's private-overlay memory seamless across every git
|
|
3
|
+
* worktree (v0.32+). A worktree's gitignored `.hunch/local.json` pointer doesn't exist
|
|
4
|
+
* in a fresh checkout, so we register the overlay at the SHARED git common dir
|
|
5
|
+
* (`git rev-parse --git-common-dir`), which every linked worktree resolves identically.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { gitCommonDir } from "../extractors/git.js";
|
|
10
|
+
import { writeFileAtomic } from "../core/io.js";
|
|
11
|
+
/** Register the resolved private overlay at the shared git common dir, so every worktree
|
|
12
|
+
* of this repo auto-discovers the same memory. Idempotent (writes only when missing or
|
|
13
|
+
* changed). Stored ABSOLUTE — a worktree resolves relative paths from its OWN root.
|
|
14
|
+
* Returns true once the shared pointer is in place (memory is worktree-shared), false when
|
|
15
|
+
* there's no overlay configured or no git common dir. Reused by `init`/`worktree`/`private`. */
|
|
16
|
+
export function ensureSharedOverlayPointer(root, overlayDir, autoCommit) {
|
|
17
|
+
const common = overlayDir ? gitCommonDir(root) : "";
|
|
18
|
+
if (!common || !overlayDir)
|
|
19
|
+
return false;
|
|
20
|
+
const file = join(common, "hunch", "local.json");
|
|
21
|
+
const want = JSON.stringify({ privateDir: resolve(overlayDir), autoCommit }, null, 2) + "\n";
|
|
22
|
+
try {
|
|
23
|
+
if (!(existsSync(file) && readFileSync(file, "utf8") === want)) {
|
|
24
|
+
mkdirSync(join(common, "hunch"), { recursive: true });
|
|
25
|
+
writeFileAtomic(file, want);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=worktree.js.map
|
package/dist/store/hunchStore.js
CHANGED
|
@@ -18,6 +18,7 @@ import { openDb } 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 } from "../extractors/git.js";
|
|
21
22
|
import { pathMatchesGlob } from "../core/glob.js";
|
|
22
23
|
import { edgeId } from "../core/ids.js";
|
|
23
24
|
import { isStrictBlocker, isVetoBlocker } from "../core/strictgate.js";
|
|
@@ -57,17 +58,33 @@ export class HunchStore {
|
|
|
57
58
|
/** The private-overlay config from the gitignored `.hunch/local.json` (per-machine,
|
|
58
59
|
* never committed). Tolerant: returns {} on missing/invalid so reads never crash. */
|
|
59
60
|
localConfig() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const read = (file) => {
|
|
62
|
+
try {
|
|
63
|
+
if (!existsSync(file))
|
|
64
|
+
return {};
|
|
65
|
+
const v = JSON.parse(readFileSync(file, "utf8"));
|
|
66
|
+
const privateDir = typeof v.privateDir === "string" && v.privateDir.trim() ? v.privateDir.trim() : undefined;
|
|
67
|
+
return { privateDir, autoCommit: v.autoCommit === true };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
63
70
|
return {};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
// Per-worktree pointer first (explicit / back-compat). If it names no overlay, fall back to
|
|
74
|
+
// the SHARED pointer in the git common dir — identical across ALL worktrees, so a freshly
|
|
75
|
+
// added worktree (whose gitignored .hunch/local.json doesn't exist yet) still auto-discovers
|
|
76
|
+
// the same memory. The git lookup runs ONLY when the cheap per-worktree read is empty, keeping
|
|
77
|
+
// it off the hot path for already-configured checkouts.
|
|
78
|
+
const perWorktree = read(join(this.paths.hunch, "local.json"));
|
|
79
|
+
if (perWorktree.privateDir)
|
|
80
|
+
return perWorktree;
|
|
81
|
+
const common = gitCommonDir(this.paths.root);
|
|
82
|
+
if (common) {
|
|
83
|
+
const shared = read(join(common, "hunch", "local.json"));
|
|
84
|
+
if (shared.privateDir)
|
|
85
|
+
return shared;
|
|
70
86
|
}
|
|
87
|
+
return perWorktree;
|
|
71
88
|
}
|
|
72
89
|
/** Merged read: public ∪ private overlay (private wins on id collision). Every
|
|
73
90
|
* QUERY / REINDEX path uses this so MCP + the guards see private memory. Public-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { commitMeta, commitDiff, headSha } from "../extractors/git.js";
|
|
1
|
+
import { commitMeta, commitDiff, headSha, currentBranch } from "../extractors/git.js";
|
|
2
2
|
import { analyzeDiff } from "../extractors/diff.js";
|
|
3
3
|
import { selectProvider, selectEnsemble, selectVerifier, verifyDecisionSafe, DeterministicProvider } from "./provider.js";
|
|
4
4
|
import { decisionId, bugId, constraintId } from "../core/ids.js";
|
|
@@ -101,6 +101,11 @@ export async function syncCommit(store, root, sha, opts = {}) {
|
|
|
101
101
|
if (draft.pruned)
|
|
102
102
|
synthBits.push(`pruned=${draft.pruned}`); // the Critic's visible value
|
|
103
103
|
const synthEvidence = `synth:${synthBits.join(" ")}`;
|
|
104
|
+
// Tag the capturing branch so branch-scoped work stays FILTERABLE in the one shared store
|
|
105
|
+
// (every worktree/branch writes to the same overlay — this keeps "what was decided on
|
|
106
|
+
// feature-x?" answerable without fragmenting memory per branch). Empty in detached HEAD.
|
|
107
|
+
const branch = currentBranch(root);
|
|
108
|
+
const branchTag = branch ? [`branch:${branch}`] : [];
|
|
104
109
|
const components = store.json.loadAll("components");
|
|
105
110
|
const relatedComponents = components
|
|
106
111
|
.filter((c) => codeFiles.some((f) => c.paths.some((g) => pathMatchesGlob(f, g))))
|
|
@@ -144,7 +149,7 @@ export async function syncCommit(store, root, sha, opts = {}) {
|
|
|
144
149
|
provenance: {
|
|
145
150
|
source: draft.source,
|
|
146
151
|
confidence: draft.confidence,
|
|
147
|
-
evidence: [`commit:${meta.shortSha}`, synthEvidence, ...codeFiles.slice(0, 8)],
|
|
152
|
+
evidence: [`commit:${meta.shortSha}`, synthEvidence, ...branchTag, ...codeFiles.slice(0, 8)],
|
|
148
153
|
last_verified: new Date().toISOString(), // when the Hunch last re-derived this
|
|
149
154
|
},
|
|
150
155
|
date: meta.date, // the commit date
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
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.",
|