@davesheffer/hunch 0.36.1 → 0.36.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 +2 -1
- package/dist/core/constraintmatch.js +13 -1
- package/dist/core/correction.js +3 -2
- package/dist/mcp/server.js +2 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -946,7 +946,8 @@ program
|
|
|
946
946
|
let forbids = deps.length || symbols.length ? { deps, symbols, patterns: [] } : null;
|
|
947
947
|
let derived = false;
|
|
948
948
|
if (!forbids && !opts.match) {
|
|
949
|
-
|
|
949
|
+
const deps = knownRepoDeps(root);
|
|
950
|
+
forbids = deriveForbids(statement, deps.length ? deps : undefined);
|
|
950
951
|
derived = !!forbids;
|
|
951
952
|
}
|
|
952
953
|
const c = store.json.put("constraints", {
|
|
@@ -72,7 +72,7 @@ export function importedDeps(lines) {
|
|
|
72
72
|
* correction → enforced constraint) mints a PRECISE matcher, not a scope-only rule that
|
|
73
73
|
* goes stale. Conservative: only fires on an explicit import/require verb + a
|
|
74
74
|
* module-shaped token; returns null otherwise (caller falls back to scope-only). */
|
|
75
|
-
export function deriveForbids(rule) {
|
|
75
|
+
export function deriveForbids(rule, knownDeps) {
|
|
76
76
|
// Derive ONLY from a NEGATIVE rule with an UNAMBIGUOUS import verb. "use"/"add" are
|
|
77
77
|
// deliberately excluded: "don't use react hooks" names hooks, not react, and "never use
|
|
78
78
|
// synchronous fs" names no dependency at all — over-deriving there mints a wrong or
|
|
@@ -86,6 +86,18 @@ export function deriveForbids(rule) {
|
|
|
86
86
|
const dep = m[1].replace(/[).,;:'"`]+$/, "").toLowerCase();
|
|
87
87
|
if (!dep || dep.length < 2 || /\s/.test(dep) || STOP_TOKENS.has(dep))
|
|
88
88
|
return null;
|
|
89
|
+
// When the caller can supply the repo's real dependencies, only auto-mint a matcher for a
|
|
90
|
+
// dep that ACTUALLY exists — so a typo or a non-dependency concept ("never import the old
|
|
91
|
+
// helper") doesn't silently create a never-firing rule. Submodule-aware both ways. No list
|
|
92
|
+
// supplied (no/unreadable package.json) → skip validation rather than reject everything.
|
|
93
|
+
if (knownDeps && knownDeps.length) {
|
|
94
|
+
const ok = knownDeps.some((d) => {
|
|
95
|
+
const k = d.toLowerCase();
|
|
96
|
+
return k === dep || dep.startsWith(`${k}/`) || k.startsWith(`${dep}/`);
|
|
97
|
+
});
|
|
98
|
+
if (!ok)
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
89
101
|
return { deps: [dep], symbols: [], patterns: [] };
|
|
90
102
|
}
|
|
91
103
|
// English words a forbid-verb might grab that are never a dependency.
|
package/dist/core/correction.js
CHANGED
|
@@ -71,8 +71,9 @@ export function buildCorrectionConstraint(input, now) {
|
|
|
71
71
|
match: null,
|
|
72
72
|
// Best-effort precise matcher from the rule text ("never import lodash" → forbids lodash),
|
|
73
73
|
// so the seamless capture path mints enforcement that survives file churn, not a scope-only
|
|
74
|
-
// rule that goes stale.
|
|
75
|
-
|
|
74
|
+
// rule that goes stale. Validated against the repo's real deps when supplied → never mints a
|
|
75
|
+
// never-firing rule for a non-dependency. null when nothing derivable → falls back to scope.
|
|
76
|
+
forbids: deriveForbids(rule, input.knownDeps),
|
|
76
77
|
rationale: input.rationale ?? "Captured from a human correction of the agent (Never Twice).",
|
|
77
78
|
source_decision: input.source_decision ?? null,
|
|
78
79
|
violations: [],
|
package/dist/mcp/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import { HunchStore } from "../store/hunchStore.js";
|
|
|
14
14
|
import { selectEmbedder } from "../store/embedder.js";
|
|
15
15
|
import { decisionId } from "../core/ids.js";
|
|
16
16
|
import { buildCorrectionConstraint } from "../core/correction.js";
|
|
17
|
+
import { knownRepoDeps } from "../synthesis/tripwires.js";
|
|
17
18
|
import { revParse, asOfDate, revExists, lastChangeDate, rangeFiles, rangeDiff, commitFiles, commitDiff, stagedFiles, stagedDiff, commitAndPushHunch, pullHunch } from "../extractors/git.js";
|
|
18
19
|
import { formatContext } from "../core/format.js";
|
|
19
20
|
import { compareCandidates } from "../core/compare.js";
|
|
@@ -360,7 +361,7 @@ export function buildServer(root) {
|
|
|
360
361
|
try {
|
|
361
362
|
if (!input.rule || !input.rule.trim())
|
|
362
363
|
return err("rule is required — state the invariant in plain words.");
|
|
363
|
-
const rec = buildCorrectionConstraint(input, new Date().toISOString());
|
|
364
|
+
const rec = buildCorrectionConstraint({ ...input, knownDeps: knownRepoDeps(root) }, new Date().toISOString());
|
|
364
365
|
// Private corrections go to the overlay (enforced locally via the merged read,
|
|
365
366
|
// never rendered into the public CI comment, which is public-only by construction).
|
|
366
367
|
const existing = input.private ? undefined : store.json.get("constraints", rec.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.2",
|
|
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 graph of the decisions, bugs, and rules behind your code, served to any MCP coding assistant (Claude Code, Cursor, Copilot, Windsurf, Codex).",
|