@davesheffer/hunch 1.18.1 → 1.19.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 +59 -2
- package/dist/cli/index.js +110 -7
- package/dist/core/agenthook.js +41 -0
- package/dist/core/correctionStage.js +513 -0
- package/dist/core/declarationClusters.js +321 -0
- package/dist/core/delivery.js +307 -6
- package/dist/core/evidenceMap.js +202 -0
- package/dist/core/jsonc.js +72 -0
- package/dist/core/pipeline.js +953 -10
- package/dist/extractors/correctionSources.js +40 -0
- package/dist/extractors/landscapeDiscovery.js +1205 -0
- package/dist/integrations/providers.js +2 -78
- package/dist/integrations/scaffold.js +6 -0
- package/dist/mcp/server.js +84 -2
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -23,83 +23,7 @@ import { homedir } from "node:os";
|
|
|
23
23
|
import { join, dirname } from "node:path";
|
|
24
24
|
import { renderHunchSection, stripManagedSection, upsertSection, updateClaudeMd } from "./claudemd.js";
|
|
25
25
|
import { headFileContent, isGitCleanPath } from "../extractors/git.js";
|
|
26
|
-
|
|
27
|
-
* (double-quoted, with escapes) so a // inside a value isn't mangled. VS Code's
|
|
28
|
-
* .vscode/mcp.json is JSONC, so we must tolerate comments. */
|
|
29
|
-
function stripJsonc(s) {
|
|
30
|
-
let out = "";
|
|
31
|
-
let inStr = false;
|
|
32
|
-
for (let i = 0; i < s.length; i++) {
|
|
33
|
-
const c = s[i];
|
|
34
|
-
const n = s[i + 1];
|
|
35
|
-
if (inStr) {
|
|
36
|
-
out += c;
|
|
37
|
-
if (c === "\\") {
|
|
38
|
-
out += n ?? "";
|
|
39
|
-
i++;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (c === '"')
|
|
43
|
-
inStr = false;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (c === '"') {
|
|
47
|
-
inStr = true;
|
|
48
|
-
out += c;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (c === "/" && n === "/") {
|
|
52
|
-
while (i < s.length && s[i] !== "\n")
|
|
53
|
-
i++;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (c === "/" && n === "*") {
|
|
57
|
-
i += 2;
|
|
58
|
-
while (i < s.length && !(s[i] === "*" && s[i + 1] === "/"))
|
|
59
|
-
i++;
|
|
60
|
-
i++;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
out += c;
|
|
64
|
-
}
|
|
65
|
-
return dropTrailingCommas(out);
|
|
66
|
-
}
|
|
67
|
-
/** Remove trailing commas (`,` before `}`/`]`) — string-aware, so a comma inside
|
|
68
|
-
* a string value (e.g. "a,]") is never touched. A blanket regex would corrupt it
|
|
69
|
-
* (the same trap test/migrate.test.ts guards against). Runs on comment-free text,
|
|
70
|
-
* so lookahead need only skip whitespace. */
|
|
71
|
-
function dropTrailingCommas(s) {
|
|
72
|
-
let out = "";
|
|
73
|
-
let inStr = false;
|
|
74
|
-
let esc = false;
|
|
75
|
-
for (let i = 0; i < s.length; i++) {
|
|
76
|
-
const c = s[i];
|
|
77
|
-
if (inStr) {
|
|
78
|
-
out += c;
|
|
79
|
-
if (esc)
|
|
80
|
-
esc = false;
|
|
81
|
-
else if (c === "\\")
|
|
82
|
-
esc = true;
|
|
83
|
-
else if (c === '"')
|
|
84
|
-
inStr = false;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (c === '"') {
|
|
88
|
-
inStr = true;
|
|
89
|
-
out += c;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (c === ",") {
|
|
93
|
-
let j = i + 1;
|
|
94
|
-
while (j < s.length && /\s/.test(s[j]))
|
|
95
|
-
j++;
|
|
96
|
-
if (s[j] === "}" || s[j] === "]")
|
|
97
|
-
continue; // trailing comma → drop
|
|
98
|
-
}
|
|
99
|
-
out += c;
|
|
100
|
-
}
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
26
|
+
import { parseJsonc } from "../core/jsonc.js";
|
|
103
27
|
/** Read a JSON/JSONC object. Returns {} only for an ABSENT or empty file. A
|
|
104
28
|
* non-empty file we cannot parse THROWS — overwriting it would silently wipe the
|
|
105
29
|
* user's other MCP servers. */
|
|
@@ -110,7 +34,7 @@ function readJsonObj(file) {
|
|
|
110
34
|
if (!raw.trim())
|
|
111
35
|
return {};
|
|
112
36
|
try {
|
|
113
|
-
const v =
|
|
37
|
+
const v = parseJsonc(raw);
|
|
114
38
|
if (v && typeof v === "object" && !Array.isArray(v))
|
|
115
39
|
return v;
|
|
116
40
|
throw new Error("not a JSON object");
|
|
@@ -175,6 +175,12 @@ export function installClaudeHooks(root, hookCmd) {
|
|
|
175
175
|
...keep(json.hooks.PostToolUse),
|
|
176
176
|
{ matcher: "Edit|Write|MultiEdit|Bash|PowerShell|Skill", hooks: [{ type: "command", command: hookCmd }] },
|
|
177
177
|
];
|
|
178
|
+
// Modern Claude Code separates failed tools from PostToolUse. Observe that
|
|
179
|
+
// event too so a failed test cannot be mistaken for a completed proof.
|
|
180
|
+
json.hooks.PostToolUseFailure = [
|
|
181
|
+
...keep(json.hooks.PostToolUseFailure),
|
|
182
|
+
{ matcher: "Bash|PowerShell", hooks: [{ type: "command", command: hookCmd }] },
|
|
183
|
+
];
|
|
178
184
|
json.hooks.Stop = [
|
|
179
185
|
...keep(json.hooks.Stop),
|
|
180
186
|
{ hooks: [{ type: "command", command: hookCmd }] },
|
package/dist/mcp/server.js
CHANGED
|
@@ -22,7 +22,11 @@ import { revParse, asOfDate, revExists, lastChangeDate, rangeFiles, rangeDiff, c
|
|
|
22
22
|
import { flushCapture, flushMemoryHome, pinSharedRemote } from "../integrations/sync.js";
|
|
23
23
|
import { advertisedTeamRemoteContract, ensureTeamOverlay, overlayMatchesTeamRemote, readTeamConfig, teamRemoteContract, teamSharedRef } from "../integrations/team.js";
|
|
24
24
|
import { formatStructure } from "../core/format.js";
|
|
25
|
+
import { diagnoseIssueCorrectionStage, formatCorrectionStageDiagnostic } from "../core/correctionStage.js";
|
|
26
|
+
import { compileVerifiedEvidenceMap, EvidenceExecutionSchema, EvidenceInterventionSchema, EvidenceProbeSchema, formatVerifiedEvidenceMap, VerifiedEvidenceReceiptSchema, } from "../core/evidenceMap.js";
|
|
27
|
+
import { collectCorrectionStageSources } from "../extractors/correctionSources.js";
|
|
25
28
|
import { buildDeliveryEnvelope } from "../core/delivery.js";
|
|
29
|
+
import { armExecutionObligations, loadPipelineState, savePipelineState } from "../core/pipeline.js";
|
|
26
30
|
import { recordServed } from "../core/served.js";
|
|
27
31
|
import { compareCandidates } from "../core/compare.js";
|
|
28
32
|
import { checkConformance } from "../core/conformance.js";
|
|
@@ -127,6 +131,19 @@ const FINDINGS_CAP = 12; // hunch_findings listing
|
|
|
127
131
|
const SEV_CONSTRAINT = { blocking: 3, warning: 2, advisory: 1 };
|
|
128
132
|
const SEV_BUG = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
129
133
|
const more = (total, cap, hint = "") => total > cap ? `\n …(+${total - cap} more${hint ? ` — ${hint}` : ""})` : "";
|
|
134
|
+
const EXECUTION_OBLIGATION_SCHEMA = z.object({
|
|
135
|
+
id: z.string(),
|
|
136
|
+
origin: z.enum(["memory", "episode", "manual"]),
|
|
137
|
+
category: z.enum(["evidence", "behavior", "types", "serialization", "compatibility", "other"]),
|
|
138
|
+
phase: z.enum(["session", "after-edit"]),
|
|
139
|
+
description: z.string(),
|
|
140
|
+
command_alternatives: z.array(z.array(z.string())),
|
|
141
|
+
expected: z.object({
|
|
142
|
+
success: z.boolean(),
|
|
143
|
+
output_includes: z.array(z.string()).optional(),
|
|
144
|
+
output_excludes: z.array(z.string()).optional(),
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
130
147
|
/** Public MCP shape for the canonical delivery envelope. Keeping the schema on
|
|
131
148
|
* the tool means orchestrators can consume receipt facts without scraping the
|
|
132
149
|
* backward-compatible text block. */
|
|
@@ -140,23 +157,45 @@ const DELIVERY_OUTPUT_SCHEMA = z.object({
|
|
|
140
157
|
provenance_status: z.enum(["current", "unverified", "stale"]),
|
|
141
158
|
token_cost: z.number().int().nonnegative(),
|
|
142
159
|
})),
|
|
160
|
+
hypotheses: z.array(z.object({
|
|
161
|
+
kind: z.literal("decision"),
|
|
162
|
+
record_id: z.string(),
|
|
163
|
+
rank: z.number().int().positive(),
|
|
164
|
+
why: z.string(),
|
|
165
|
+
where: z.array(z.string()),
|
|
166
|
+
historical_pattern: z.string(),
|
|
167
|
+
verify: z.string(),
|
|
168
|
+
disprove: z.string(),
|
|
169
|
+
obligations: z.array(EXECUTION_OBLIGATION_SCHEMA),
|
|
170
|
+
})),
|
|
171
|
+
obligations: z.array(EXECUTION_OBLIGATION_SCHEMA),
|
|
143
172
|
supplements: z.array(z.object({
|
|
144
173
|
id: z.string(),
|
|
145
174
|
kind: z.string(),
|
|
146
175
|
delivered: z.boolean(),
|
|
147
|
-
reason: z.enum(["supplemental", "budget", "empty"]),
|
|
176
|
+
reason: z.enum(["supplemental", "budget", "empty", "abstained"]),
|
|
148
177
|
rank: z.number().int().positive(),
|
|
149
178
|
token_cost: z.number().int().nonnegative(),
|
|
150
179
|
})),
|
|
151
180
|
omitted: z.array(z.object({
|
|
152
181
|
kind: z.enum(["constraints", "decisions", "bugs", "findings"]),
|
|
153
182
|
record_id: z.string(),
|
|
154
|
-
reason: z.enum(["budget", "stale-provenance", "retired"]),
|
|
183
|
+
reason: z.enum(["budget", "stale-provenance", "retired", "actionability-cap", "low-confidence", "insufficient-context", "low-relevance"]),
|
|
155
184
|
detail: z.string(),
|
|
156
185
|
})),
|
|
157
186
|
budget_tokens: z.number().int().nonnegative(),
|
|
158
187
|
used_chars: z.number().int().nonnegative(),
|
|
159
188
|
blocking_overflow: z.boolean(),
|
|
189
|
+
abstention: z.object({
|
|
190
|
+
active: z.boolean(),
|
|
191
|
+
withheld: z.number().int().nonnegative(),
|
|
192
|
+
reasons: z.object({
|
|
193
|
+
"low-confidence": z.number().int().nonnegative(),
|
|
194
|
+
"insufficient-context": z.number().int().nonnegative(),
|
|
195
|
+
"low-relevance": z.number().int().nonnegative(),
|
|
196
|
+
}),
|
|
197
|
+
retry_hint: z.string().nullable(),
|
|
198
|
+
}),
|
|
160
199
|
});
|
|
161
200
|
/** Return the same human-readable brief older clients consume plus the exact
|
|
162
201
|
* machine-readable envelope. Receipt recording is deliberately best-effort:
|
|
@@ -166,6 +205,10 @@ function deliveredContext(root, target, envelope, sessionId) {
|
|
|
166
205
|
// advertised MCP contract, the SDK will reject the call and the local ledger
|
|
167
206
|
// must not claim that response was served.
|
|
168
207
|
const structuredContent = DELIVERY_OUTPUT_SCHEMA.parse(envelope);
|
|
208
|
+
if (sessionId) {
|
|
209
|
+
const state = armExecutionObligations(loadPipelineState(sessionId), structuredContent.obligations, { replaceOrigin: "memory" });
|
|
210
|
+
savePipelineState(sessionId, state);
|
|
211
|
+
}
|
|
169
212
|
recordServed(root, structuredContent.delivered.map((item) => ({
|
|
170
213
|
event: "served",
|
|
171
214
|
kind: item.kind,
|
|
@@ -761,6 +804,45 @@ export function buildServerWithRootControl(initialRoot) {
|
|
|
761
804
|
const envelope = buildDeliveryEnvelope(ctx, options);
|
|
762
805
|
return deliveredContext(root, as_of ? `${target} (as_of:${as_of})` : target, envelope, extra.sessionId);
|
|
763
806
|
});
|
|
807
|
+
// -- hunch_shortlist (bounded correction-stage diagnostic) ----------------
|
|
808
|
+
server.registerTool("hunch_shortlist", {
|
|
809
|
+
title: "Shortlist the likely correction stage and declarations",
|
|
810
|
+
description: "Experimental, deterministic, read-only repository-adaptive diagnostic for a schema/validation issue or reproduction. Preserves a flat top five, adds a transfer-tested hierarchical inspection view, and emits an efficiency-tested advisory progressive inspection queue capped at eleven declarations with deterministic receipts. Optional authenticated same-claim evidence is annotated but cannot reorder candidates because fresh transfer rejected that mechanism. It never claims an exact implementation owner or per-case confidence and does not edit, gate, or capture memory.",
|
|
811
|
+
inputSchema: {
|
|
812
|
+
issue: z.string().min(1).max(100_000).describe("Issue report or reproduction prose, including observed and expected behavior when available."),
|
|
813
|
+
limit: z.number().int().min(1).max(5).optional().describe("Candidate count, capped at five (default 5)."),
|
|
814
|
+
evidence: VerifiedEvidenceReceiptSchema.optional().describe("Optional verified evidence for this exact issue claim. It is reported and attached to candidates but cannot change ranking."),
|
|
815
|
+
cwd: cwdHintField,
|
|
816
|
+
},
|
|
817
|
+
}, async ({ issue, limit, evidence }) => {
|
|
818
|
+
try {
|
|
819
|
+
const collection = collectCorrectionStageSources(root, issue);
|
|
820
|
+
const diagnostic = diagnoseIssueCorrectionStage(issue, collection.sources, limit ?? 5, evidence);
|
|
821
|
+
return ok(`${formatCorrectionStageDiagnostic(diagnostic)}\nScan: ${collection.files_read} source file(s), ${collection.files_skipped} skipped by safety/budget limits.`);
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
return err(`Could not build the correction-stage shortlist: ${error.message}`);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
// -- hunch_evidence_map (verified behavioral receipt compiler) ------------
|
|
828
|
+
server.registerTool("hunch_evidence_map", {
|
|
829
|
+
title: "Compile a verified behavioral evidence map",
|
|
830
|
+
description: "Compile supplied red-target/green-control, execution, and intervention observations into a bounded read-only evidence map. Useful for bugs, regressions, design invariants, and any other testable behavior. This tool executes no code, mutates nothing, and never converts behavioral influence into an exact correction-owner claim.",
|
|
831
|
+
inputSchema: {
|
|
832
|
+
version: z.literal(1).describe("Receipt schema version; currently 1."),
|
|
833
|
+
claim: z.string().trim().min(1).max(100_000).describe("The behavior or invariant being tested."),
|
|
834
|
+
probe: EvidenceProbeSchema,
|
|
835
|
+
execution: z.array(EvidenceExecutionSchema).max(500).optional(),
|
|
836
|
+
interventions: z.array(EvidenceInterventionSchema).max(500).optional(),
|
|
837
|
+
},
|
|
838
|
+
}, async (receipt) => {
|
|
839
|
+
try {
|
|
840
|
+
return ok(formatVerifiedEvidenceMap(compileVerifiedEvidenceMap(receipt)));
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
return err(`Could not compile the verified evidence map: ${error.message}`);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
764
846
|
// -- hunch_now (the hot view: recent activity + roadmap) --------------------
|
|
765
847
|
// PUBLIC store only, per dec_29eff08c69's jurisdiction rule: an assistant may
|
|
766
848
|
// paste this anywhere, so it must be publishable by construction. Union view
|
package/package.json
CHANGED
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.
|
|
10
|
+
"version": "1.19.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.
|
|
16
|
+
"version": "1.19.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|