@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
package/README.md
CHANGED
|
@@ -18,6 +18,23 @@ strict enforcement.
|
|
|
18
18
|
**Memory is the input. The product boundary is the receipt:** relevant evidence before an edit,
|
|
19
19
|
then a deterministic check of the change against the rules your team has explicitly trusted.
|
|
20
20
|
|
|
21
|
+
> **New in v1.19.0:** correction search now keeps the flat top five but adds
|
|
22
|
+
> transfer-tested semantic declaration families and an efficiency-tested progressive queue.
|
|
23
|
+
> Evidence receipts are read-only annotations: they never reorder candidates or claim an exact
|
|
24
|
+
> owner. Install the stable release with `npm i -g @davesheffer/hunch`.
|
|
25
|
+
|
|
26
|
+
The measured improvement is deliberately scoped to fresh, preregistered transfer cases:
|
|
27
|
+
|
|
28
|
+
| Measure | Flat/baseline | v1.19 bounded view | Change |
|
|
29
|
+
| --- | ---: | ---: | ---: |
|
|
30
|
+
| Changed declaration found | 3/12 (25%) | 6/12 (50%) | +25 percentage points |
|
|
31
|
+
| Correct file found | 8/12 (66.7%) | 10/12 (83.3%) | +16.6 percentage points |
|
|
32
|
+
| Declarations inspected for the same five hits | 18.9 average | 11 average | 41.9% less |
|
|
33
|
+
|
|
34
|
+
These are bounded diagnostic and inspection results, not a claim that Hunch is universally twice as
|
|
35
|
+
accurate. Failed evidence and causal rerankers remain disabled; the receipts annotate the shortlist
|
|
36
|
+
without changing its order or asserting an exact owner.
|
|
37
|
+
|
|
21
38
|
> **New in v1.18.0:** YAML and Helm now enter the same dependency graph as application code.
|
|
22
39
|
> Anchors and aliases become reference edges; chart-scoped `define`, `include`, and `template`
|
|
23
40
|
> relationships survive pre-render syntax without weakening fail-closed handling for ordinary
|
|
@@ -72,10 +89,50 @@ Most memory work happens automatically after commits. These commands cover the c
|
|
|
72
89
|
| `hunch why <file>` | Decisions, bugs, constraints, and blast radius behind a file |
|
|
73
90
|
| `hunch query "<question>"` | Search project memory |
|
|
74
91
|
| `hunch check --working` | Review all current changes against recorded intent |
|
|
92
|
+
| `hunch shortlist --issue "..."` | Experimental flat shortlist plus a progressive, file-anchored semantic inspection plan; add `--evidence receipt.json` for read-only evidence annotations |
|
|
93
|
+
| `hunch evidence-map receipt.json` | Compile observed probe/execution receipts without guessing an exact owner |
|
|
75
94
|
| `hunch log` | See the memory timeline and its reversible moves |
|
|
76
95
|
| `hunch escalations` | See the rare decisions only a human can make |
|
|
77
96
|
| `hunch doctor` | Diagnose setup, provider, index, or private-overlay problems |
|
|
78
97
|
|
|
98
|
+
`hunch evidence-map` accepts a bounded JSON receipt containing a red target, a distinct green
|
|
99
|
+
control, optional execution counts, and optional intervention outcomes. It reports target-only and
|
|
100
|
+
shared execution plus behavior-sensitive files. It does not run the probes, edit the repository, or
|
|
101
|
+
claim that behaviorally influential code owns the correction. Use `--json` for the machine-readable
|
|
102
|
+
map; MCP clients can submit the same receipt through `hunch_evidence_map`.
|
|
103
|
+
|
|
104
|
+
`hunch shortlist --evidence` attaches authenticated observations to the relevant candidates but does
|
|
105
|
+
not reorder them. Three fresh transfer experiments failed to prove that execution or intervention
|
|
106
|
+
influence identifies the correction owner, so the production path converts that result into a hard
|
|
107
|
+
safeguard: no candidate is promoted or displaced by evidence. JSON output still includes a
|
|
108
|
+
deterministic receipt and the explicit `exact_owner_enabled: false` policy.
|
|
109
|
+
|
|
110
|
+
Every shortlist also preserves its flat top five and adds a deterministic hierarchical inspection
|
|
111
|
+
view anchored to those files: at most five files, two semantic declaration families per file, and
|
|
112
|
+
three declarations per family. On a preregistered 12-case fresh transfer, the preserved union found
|
|
113
|
+
6/12 changed declarations versus 3/12 for the flat top five (**+25 percentage points**, three
|
|
114
|
+
rescues), while correct-file coverage improved from 8/12 to 10/12. The view averaged 18.8 unique
|
|
115
|
+
declarations and never exceeded 24. This promotes the clusters as a supplemental diagnostic, not as
|
|
116
|
+
a top-five accuracy claim; exact-owner output and per-case confidence remain disabled. JSON output
|
|
117
|
+
includes the deterministic cluster receipt and the transfer calibration.
|
|
118
|
+
|
|
119
|
+
The default output also turns those clusters into a progressive inspection queue. It preserves the
|
|
120
|
+
flat shortlist, adds only the strongest members of already-selected semantic families, stops at ten
|
|
121
|
+
when the behavior is explained, and permits one final fallback declaration before reporting
|
|
122
|
+
uncertainty. Development replay retained all 21/36 combined hits from the full cluster view while
|
|
123
|
+
reducing the hard inspection ceiling to 11 from an average of 19.8 declarations (44% less). On a
|
|
124
|
+
separate preregistered 12-case ArkType transfer it retained all 5 full-cluster hits with zero losses
|
|
125
|
+
and reduced mean inspection from 18.9 declarations to 11 (41.9% less). It found no additional fresh
|
|
126
|
+
hit, so the queue is retained as an efficiency advisory rather than promoted as an accuracy gain.
|
|
127
|
+
|
|
128
|
+
Follow-up optimization attempts stay out of production. Replacing cluster slots with same-file
|
|
129
|
+
declarations produced four development rescues but also three losses. Appending two same-file slots
|
|
130
|
+
removed those development losses, but a second blind 12-case ArkType transfer produced 3/12 hits for
|
|
131
|
+
both the existing and expanded plans, with zero rescues. Product-source filtering lost one prior hit,
|
|
132
|
+
one-hop relationship expansion added none, and evidence/causal rerankers also failed their frozen
|
|
133
|
+
transfer gates. The receipts remain in `bench/external/results`; rejected mechanisms cannot silently
|
|
134
|
+
change the production ordering.
|
|
135
|
+
|
|
79
136
|
Corrections can become scoped rules, but captured memory cannot hard-block on its own. Enforcement is
|
|
80
137
|
deterministic and opt-in:
|
|
81
138
|
|
|
@@ -92,7 +149,7 @@ Git repo that every teammate can access, install the Matrix release on team mach
|
|
|
92
149
|
have one maintainer run:
|
|
93
150
|
|
|
94
151
|
```bash
|
|
95
|
-
npm i -g @davesheffer/hunch@1.
|
|
152
|
+
npm i -g @davesheffer/hunch@1.19.0
|
|
96
153
|
hunch shared --repo git@github.com:acme/project-hunch-memory.git
|
|
97
154
|
git add .gitignore .hunch/team.json
|
|
98
155
|
git commit -m "chore: connect shared Hunch memory"
|
|
@@ -107,7 +164,7 @@ printed by Hunch. Omit `--migrate` for a new setup.
|
|
|
107
164
|
After the pointer commit lands, teammates need Hunch installed and Git access to the memory repo:
|
|
108
165
|
|
|
109
166
|
```bash
|
|
110
|
-
npm i -g @davesheffer/hunch@1.
|
|
167
|
+
npm i -g @davesheffer/hunch@1.19.0
|
|
111
168
|
git pull
|
|
112
169
|
hunch init
|
|
113
170
|
hunch doctor
|
package/dist/cli/index.js
CHANGED
|
@@ -52,6 +52,9 @@ import { writeMcpJson, writeSlashCommands, installClaudeHooks } from "../integra
|
|
|
52
52
|
import { scaffoldProviders, regenerateGrounding, refreshExistingGrounding, refreshCommittableGrounding } from "../integrations/providers.js";
|
|
53
53
|
import { healClaudeConfigCaseSplit } from "../integrations/claudeConfig.js";
|
|
54
54
|
import { formatContext, formatStructure } from "../core/format.js";
|
|
55
|
+
import { diagnoseIssueCorrectionStage, formatCorrectionStageDiagnostic } from "../core/correctionStage.js";
|
|
56
|
+
import { compileVerifiedEvidenceMap, formatVerifiedEvidenceMap } from "../core/evidenceMap.js";
|
|
57
|
+
import { collectCorrectionStageSources } from "../extractors/correctionSources.js";
|
|
55
58
|
import { buildDeliveryEnvelope } from "../core/delivery.js";
|
|
56
59
|
import { readConfig, writeConfig, FIRMNESS_LEVELS, isFirmness } from "../core/config.js";
|
|
57
60
|
import { blockingInScope, vetoInScope, proposedEditLines } from "../core/hookpolicy.js";
|
|
@@ -61,7 +64,7 @@ import { computeStats, formatStats } from "../core/stats.js";
|
|
|
61
64
|
import { injectionMode, resetSessionInjections } from "../core/hookcache.js";
|
|
62
65
|
import { recordServed, servedSummary } from "../core/served.js";
|
|
63
66
|
import { contextHookOutput, denyHookOutput, hookProvider, normalizeHookEvent, stopHookOutput } from "../core/agenthook.js";
|
|
64
|
-
import { PIPELINE_LOOP,
|
|
67
|
+
import { PIPELINE_LOOP, armExecutionObligations, beforeEditProbeVerdict, compileExecutableProbes, environmentExecutableProbes, environmentExecutionObligations, executionObligationBrief, isProductPath, loadPipelineState, onCommand, onEdit, onPrompt, onSkill, pipelineEnabled, proofCheckpoint, savePipelineState, stopVerdict, unverifiedNag, } from "../core/pipeline.js";
|
|
65
68
|
import { draftDuplicateOf, isAcceptedDuplicateAnchor } from "../core/dupdetect.js";
|
|
66
69
|
import { planAutoReview, planMutations } from "../core/autoreview.js";
|
|
67
70
|
import { loadGoldenSet, evaluateRetrieval, evaluateTraversalLift } from "../eval/harness.js";
|
|
@@ -3636,6 +3639,69 @@ program
|
|
|
3636
3639
|
console.log(`· ${rel(root, r.path)} already exists — left untouched. Delete it to regenerate.`);
|
|
3637
3640
|
}
|
|
3638
3641
|
});
|
|
3642
|
+
// ---- shortlist (experimental correction-stage diagnostic) -----------------
|
|
3643
|
+
program
|
|
3644
|
+
.command("shortlist")
|
|
3645
|
+
.description("Experimental repository-adaptive diagnostic: preserve a flat top five, add file-anchored semantic clusters, and emit an efficiency-tested advisory inspection plan capped at eleven. --evidence is annotation-only; no exact-owner claim.")
|
|
3646
|
+
.argument("<issue...>", "issue report or reproduction prose")
|
|
3647
|
+
.option("--limit <n>", "candidate count (1-5)", "5")
|
|
3648
|
+
.option("--evidence <receipt>", "verified-evidence JSON receipt path, or - for stdin")
|
|
3649
|
+
.option("--json", "emit the diagnostic as JSON")
|
|
3650
|
+
.action((issueParts, opts) => {
|
|
3651
|
+
const issue = issueParts.join(" ").trim();
|
|
3652
|
+
if (!issue)
|
|
3653
|
+
return fail("issue text must not be empty");
|
|
3654
|
+
const rawLimit = Number(opts.limit);
|
|
3655
|
+
if (!Number.isSafeInteger(rawLimit) || rawLimit < 1 || rawLimit > 5)
|
|
3656
|
+
return fail("--limit must be an integer from 1 to 5");
|
|
3657
|
+
const root = findRoot();
|
|
3658
|
+
try {
|
|
3659
|
+
let evidence = undefined;
|
|
3660
|
+
if (opts.evidence) {
|
|
3661
|
+
const raw = opts.evidence === "-" ? readFileSync(0, "utf8") : readFileSync(resolve(opts.evidence), "utf8");
|
|
3662
|
+
if (Buffer.byteLength(raw, "utf8") > 1_000_000)
|
|
3663
|
+
return fail("evidence receipt exceeds the 1 MB safety limit");
|
|
3664
|
+
evidence = JSON.parse(raw);
|
|
3665
|
+
}
|
|
3666
|
+
const collection = collectCorrectionStageSources(root, issue);
|
|
3667
|
+
const diagnostic = diagnoseIssueCorrectionStage(issue, collection.sources, rawLimit, evidence);
|
|
3668
|
+
if (opts.json) {
|
|
3669
|
+
console.log(JSON.stringify({
|
|
3670
|
+
...diagnostic,
|
|
3671
|
+
scan: {
|
|
3672
|
+
files_read: collection.files_read,
|
|
3673
|
+
bytes_read: collection.bytes_read,
|
|
3674
|
+
files_skipped: collection.files_skipped,
|
|
3675
|
+
},
|
|
3676
|
+
}, null, 2));
|
|
3677
|
+
}
|
|
3678
|
+
else {
|
|
3679
|
+
console.log(formatCorrectionStageDiagnostic(diagnostic));
|
|
3680
|
+
console.log(`Scan: ${collection.files_read} source file(s), ${collection.files_skipped} skipped by safety/budget limits.`);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
catch (error) {
|
|
3684
|
+
fail(`could not build the correction-stage shortlist: ${error.message}`);
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
// ---- evidence-map (compile supplied behavioral receipts) ------------------
|
|
3688
|
+
program
|
|
3689
|
+
.command("evidence-map")
|
|
3690
|
+
.description("Compile authenticated probe/execution/intervention receipts into a read-only evidence map. Runs no code and never claims an exact owner.")
|
|
3691
|
+
.argument("<receipt>", "JSON receipt path, or - to read stdin")
|
|
3692
|
+
.option("--json", "emit the evidence map as JSON")
|
|
3693
|
+
.action((receiptFile, opts) => {
|
|
3694
|
+
try {
|
|
3695
|
+
const raw = receiptFile === "-" ? readFileSync(0, "utf8") : readFileSync(resolve(receiptFile), "utf8");
|
|
3696
|
+
if (Buffer.byteLength(raw, "utf8") > 1_000_000)
|
|
3697
|
+
return fail("evidence receipt exceeds the 1 MB safety limit");
|
|
3698
|
+
const map = compileVerifiedEvidenceMap(JSON.parse(raw));
|
|
3699
|
+
console.log(opts.json ? JSON.stringify(map, null, 2) : formatVerifiedEvidenceMap(map));
|
|
3700
|
+
}
|
|
3701
|
+
catch (error) {
|
|
3702
|
+
fail(`could not compile the verified evidence map: ${error.message}`);
|
|
3703
|
+
}
|
|
3704
|
+
});
|
|
3639
3705
|
// ---- context (surgical retrieval) -----------------------------------------
|
|
3640
3706
|
program
|
|
3641
3707
|
.command("context")
|
|
@@ -3939,18 +4005,33 @@ program
|
|
|
3939
4005
|
// Verification pipeline (delivery enforced, not hoped for — see core/pipeline.ts).
|
|
3940
4006
|
// PostToolUse records facts; Stop gates on them. Both are pipeline-only events,
|
|
3941
4007
|
// handled before the grounding dispatch below.
|
|
3942
|
-
if (evt.hook_event_name === "PostToolUse" && evt.session_id && pipelineEnabled()) {
|
|
4008
|
+
if ((evt.hook_event_name === "PostToolUse" || evt.hook_event_name === "PostToolUseFailure") && evt.session_id && pipelineEnabled()) {
|
|
3943
4009
|
let st = loadPipelineState(evt.session_id);
|
|
4010
|
+
const before = st;
|
|
4011
|
+
let activity = null;
|
|
3944
4012
|
if (/^(Edit|Write|MultiEdit)$/.test(evt.tool_name ?? "")) {
|
|
3945
4013
|
const p = evt.tool_input?.file_path;
|
|
3946
|
-
if (p)
|
|
4014
|
+
if (p) {
|
|
3947
4015
|
st = onEdit(st, toRepoRel(root, p));
|
|
4016
|
+
activity = { kind: "edit" };
|
|
4017
|
+
}
|
|
3948
4018
|
}
|
|
3949
4019
|
else if (evt.tool_name === "Bash" || evt.tool_name === "PowerShell") {
|
|
3950
|
-
|
|
4020
|
+
const command = String(evt.tool_input?.command ?? "");
|
|
4021
|
+
st = onCommand(st, command, evt.tool_outcome);
|
|
4022
|
+
activity = { kind: "command", command };
|
|
3951
4023
|
}
|
|
3952
4024
|
else if (evt.tool_name === "Skill") {
|
|
3953
4025
|
st = onSkill(st, String(evt.tool_input?.skill ?? ""));
|
|
4026
|
+
activity = { kind: "skill" };
|
|
4027
|
+
}
|
|
4028
|
+
if (activity) {
|
|
4029
|
+
const checkpoint = proofCheckpoint(before, st, activity);
|
|
4030
|
+
st = checkpoint.state;
|
|
4031
|
+
savePipelineState(evt.session_id, st);
|
|
4032
|
+
if (checkpoint.reminder)
|
|
4033
|
+
emitContext(provider, evt.hook_event_name, checkpoint.reminder);
|
|
4034
|
+
return;
|
|
3954
4035
|
}
|
|
3955
4036
|
savePipelineState(evt.session_id, st);
|
|
3956
4037
|
return;
|
|
@@ -3982,8 +4063,8 @@ program
|
|
|
3982
4063
|
if (evt.session_id && pipelineEnabled()) {
|
|
3983
4064
|
const st = onPrompt(loadPipelineState(evt.session_id));
|
|
3984
4065
|
savePipelineState(evt.session_id, st);
|
|
3985
|
-
if (!st.verifyAfterEdit) {
|
|
3986
|
-
text += `\n\n${
|
|
4066
|
+
if (!st.verifyAfterEdit || st.obligations.some((item) => item.status !== "satisfied")) {
|
|
4067
|
+
text += `\n\n${unverifiedNag(st)}`;
|
|
3987
4068
|
mustDeliver = true;
|
|
3988
4069
|
}
|
|
3989
4070
|
}
|
|
@@ -4097,6 +4178,15 @@ program
|
|
|
4097
4178
|
// is instead of pulling (or worse, grepping) for it. Cheap reads only
|
|
4098
4179
|
// (no reindex, no drift walk); public store only — session transcripts
|
|
4099
4180
|
// travel further than a terminal. Union view: `hunch now --private`.
|
|
4181
|
+
let controllerBrief = "";
|
|
4182
|
+
if (evt.session_id && pipelineEnabled()) {
|
|
4183
|
+
const state = armExecutionObligations(loadPipelineState(evt.session_id), [
|
|
4184
|
+
...compileExecutableProbes(environmentExecutableProbes()),
|
|
4185
|
+
...environmentExecutionObligations(),
|
|
4186
|
+
], { replaceOrigin: "episode" });
|
|
4187
|
+
savePipelineState(evt.session_id, state);
|
|
4188
|
+
controllerBrief = executionObligationBrief(state);
|
|
4189
|
+
}
|
|
4100
4190
|
const s = new HunchStore(paths);
|
|
4101
4191
|
try {
|
|
4102
4192
|
// Mode-aware: in unified ("shared") mode the public `.hunch/` is only a routing
|
|
@@ -4109,7 +4199,7 @@ program
|
|
|
4109
4199
|
if (!decisions.length) {
|
|
4110
4200
|
// Fresh graph: nothing to orient on, but the operating loop still ships.
|
|
4111
4201
|
if (pipelineEnabled())
|
|
4112
|
-
emitContext(provider, "SessionStart", PIPELINE_LOOP);
|
|
4202
|
+
emitContext(provider, "SessionStart", [PIPELINE_LOOP, controllerBrief].filter(Boolean).join("\n\n"));
|
|
4113
4203
|
return;
|
|
4114
4204
|
}
|
|
4115
4205
|
const L = [];
|
|
@@ -4142,6 +4232,8 @@ program
|
|
|
4142
4232
|
// (the zod bench showed ambient skills are read in ~0% of sessions).
|
|
4143
4233
|
if (pipelineEnabled())
|
|
4144
4234
|
L.push("", PIPELINE_LOOP);
|
|
4235
|
+
if (controllerBrief)
|
|
4236
|
+
L.push("", controllerBrief);
|
|
4145
4237
|
const orientation = L.join("\n");
|
|
4146
4238
|
// Antigravity's nearest equivalent is PreInvocation, which can fire
|
|
4147
4239
|
// repeatedly in one conversation. Deduplicate it just like edit
|
|
@@ -4165,6 +4257,17 @@ program
|
|
|
4165
4257
|
// → nothing for Hunch to say.
|
|
4166
4258
|
if (!target || target.startsWith("..") || /^[a-zA-Z]:/.test(target))
|
|
4167
4259
|
return;
|
|
4260
|
+
// A compiled red→green probe is only meaningful if its red receipt exists
|
|
4261
|
+
// before implementation. Firm/strict may deny two edits per prompt, then
|
|
4262
|
+
// fail open so a malformed or unavailable probe can never deadlock work.
|
|
4263
|
+
if ((firmness === "firm" || firmness === "strict") && evt.session_id && pipelineEnabled() && isProductPath(target)) {
|
|
4264
|
+
const baseline = beforeEditProbeVerdict(loadPipelineState(evt.session_id));
|
|
4265
|
+
if (baseline.block) {
|
|
4266
|
+
savePipelineState(evt.session_id, baseline.state);
|
|
4267
|
+
emitDeny(provider, baseline.reason ?? "Hunch evidence gate — establish the pre-edit probe baseline first.");
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4168
4271
|
// Pre-edit grounding must resolve the same advertised graph as every CLI
|
|
4169
4272
|
// and MCP consumer. Any unavailable/mismatched team route falls through to
|
|
4170
4273
|
// the outer fail-open catch and emits nothing, preserving the hook's
|
package/dist/core/agenthook.js
CHANGED
|
@@ -68,6 +68,42 @@ function normalizeToolInput(value) {
|
|
|
68
68
|
};
|
|
69
69
|
return Object.values(out).some((v) => v !== undefined) ? out : undefined;
|
|
70
70
|
}
|
|
71
|
+
const MAX_TOOL_OUTPUT = 200_000;
|
|
72
|
+
function toolOutput(value) {
|
|
73
|
+
if (typeof value === "string")
|
|
74
|
+
return value.slice(0, MAX_TOOL_OUTPUT);
|
|
75
|
+
const raw = obj(value);
|
|
76
|
+
if (raw) {
|
|
77
|
+
const parts = ["stdout", "stderr", "output", "content", "error", "message"]
|
|
78
|
+
.map((key) => raw[key])
|
|
79
|
+
.flatMap((item) => typeof item === "string" && item ? [item] : []);
|
|
80
|
+
if (parts.length)
|
|
81
|
+
return parts.join("\n").slice(0, MAX_TOOL_OUTPUT);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(value ?? "").slice(0, MAX_TOOL_OUTPUT);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function normalizeToolOutcome(input, event) {
|
|
91
|
+
if (event !== "PostToolUse" && event !== "PostToolUseFailure")
|
|
92
|
+
return undefined;
|
|
93
|
+
const response = input.tool_response ?? input.toolResponse ?? input.tool_result ?? input.toolResult;
|
|
94
|
+
return {
|
|
95
|
+
// Claude Code splits successful and failed calls into separate lifecycle
|
|
96
|
+
// events. Providers without that split may expose an explicit result flag.
|
|
97
|
+
status: event === "PostToolUseFailure"
|
|
98
|
+
? "failure"
|
|
99
|
+
: obj(response)?.success === false || obj(response)?.is_error === true || obj(response)?.isError === true
|
|
100
|
+
? "failure"
|
|
101
|
+
: "success",
|
|
102
|
+
output: event === "PostToolUseFailure"
|
|
103
|
+
? toolOutput(input.error ?? response)
|
|
104
|
+
: toolOutput(response),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
71
107
|
function eventName(value, provider) {
|
|
72
108
|
if (typeof value !== "string")
|
|
73
109
|
return undefined;
|
|
@@ -75,6 +111,7 @@ function eventName(value, provider) {
|
|
|
75
111
|
const map = {
|
|
76
112
|
pretooluse: "PreToolUse",
|
|
77
113
|
posttooluse: "PostToolUse",
|
|
114
|
+
posttoolusefailure: "PostToolUseFailure",
|
|
78
115
|
userpromptsubmit: "UserPromptSubmit",
|
|
79
116
|
sessionstart: "SessionStart",
|
|
80
117
|
subagentstart: "SubagentStart",
|
|
@@ -139,11 +176,13 @@ export function normalizeHookEvent(raw, provider) {
|
|
|
139
176
|
return null;
|
|
140
177
|
const info = obj(input.tool_info) ?? obj(input.toolInput) ?? obj(input.tool_input);
|
|
141
178
|
const toolInput = normalizeToolInput(info);
|
|
179
|
+
const toolOutcome = normalizeToolOutcome(input, event);
|
|
142
180
|
return {
|
|
143
181
|
hook_event_name: event,
|
|
144
182
|
session_id: stringAt(input, "trajectory_id", "session_id", "sessionId"),
|
|
145
183
|
tool_name: hunchToolName(stringAt(input, "agent_action_name", "tool_name", "toolName"), toolInput ?? {}),
|
|
146
184
|
tool_input: toolInput,
|
|
185
|
+
...(toolOutcome ? { tool_outcome: toolOutcome } : {}),
|
|
147
186
|
prompt: stringAt(input, "prompt", "user_prompt", "userPrompt"),
|
|
148
187
|
};
|
|
149
188
|
}
|
|
@@ -151,11 +190,13 @@ export function normalizeHookEvent(raw, provider) {
|
|
|
151
190
|
if (!event)
|
|
152
191
|
return null;
|
|
153
192
|
const toolInput = normalizeToolInput(input.tool_input ?? input.toolInput);
|
|
193
|
+
const toolOutcome = normalizeToolOutcome(input, event);
|
|
154
194
|
return {
|
|
155
195
|
hook_event_name: event,
|
|
156
196
|
session_id: stringAt(input, "session_id", "sessionId", "conversation_id", "conversationId"),
|
|
157
197
|
tool_name: hunchToolName(stringAt(input, "tool_name", "toolName"), toolInput ?? {}),
|
|
158
198
|
tool_input: toolInput,
|
|
199
|
+
...(toolOutcome ? { tool_outcome: toolOutcome } : {}),
|
|
159
200
|
prompt: stringAt(input, "prompt", "user_prompt", "userPrompt"),
|
|
160
201
|
source: stringAt(input, "source"),
|
|
161
202
|
agent_type: stringAt(input, "agent_type", "agentType", "subagent_type", "subagentType"),
|