@davesheffer/hunch 1.19.0 → 1.20.0-rc.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/README.md +78 -61
- package/dist/cli/index.js +99 -2
- package/dist/core/delivery.js +213 -13
- package/dist/core/landscapeAdoption.js +271 -0
- package/dist/core/landscapeDelivery.js +281 -0
- package/dist/core/types.js +128 -1
- package/dist/extractors/git.js +2 -2
- package/dist/extractors/landscapeDiscovery.js +2217 -280
- package/dist/mcp/server.js +45 -4
- package/dist/store/hunchStore.js +7 -0
- package/dist/synthesis/synthesize.js +22 -7
- package/package.json +3 -2
- package/server.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -28,6 +28,7 @@ import { collectCorrectionStageSources } from "../extractors/correctionSources.j
|
|
|
28
28
|
import { buildDeliveryEnvelope } from "../core/delivery.js";
|
|
29
29
|
import { armExecutionObligations, loadPipelineState, savePipelineState } from "../core/pipeline.js";
|
|
30
30
|
import { recordServed } from "../core/served.js";
|
|
31
|
+
import { EdgeSchema, ResourceSchema } from "../core/types.js";
|
|
31
32
|
import { compareCandidates } from "../core/compare.js";
|
|
32
33
|
import { checkConformance } from "../core/conformance.js";
|
|
33
34
|
import { ConstitutionService, policyEvaluationEnvelope } from "../constitution/service.js";
|
|
@@ -144,13 +145,45 @@ const EXECUTION_OBLIGATION_SCHEMA = z.object({
|
|
|
144
145
|
output_excludes: z.array(z.string()).optional(),
|
|
145
146
|
}),
|
|
146
147
|
});
|
|
148
|
+
const LANDSCAPE_SELECTION_RECEIPT_SCHEMA = z.object({
|
|
149
|
+
selectionReason: z.enum(["exact-target", "task-match", "orientation-root", "graph-neighbor", "graph-connection"]),
|
|
150
|
+
selectionRank: z.number().int().positive(),
|
|
151
|
+
rank: z.number().int().positive(),
|
|
152
|
+
deliveryReason: z.literal("ranked"),
|
|
153
|
+
required: z.literal(false),
|
|
154
|
+
blocking: z.literal(false),
|
|
155
|
+
provenanceStatus: z.literal("current"),
|
|
156
|
+
tokenCost: z.number().int().positive(),
|
|
157
|
+
});
|
|
158
|
+
const LANDSCAPE_FRAGMENT_SCHEMA = z.object({
|
|
159
|
+
schema: z.literal("hunch.landscape-fragment/1"),
|
|
160
|
+
authority: z.literal("human_confirmed"),
|
|
161
|
+
target: z.string(),
|
|
162
|
+
resources: z.array(LANDSCAPE_SELECTION_RECEIPT_SCHEMA.extend({ record: ResourceSchema })),
|
|
163
|
+
relationships: z.array(LANDSCAPE_SELECTION_RECEIPT_SCHEMA.extend({
|
|
164
|
+
selectionReason: z.literal("graph-connection"),
|
|
165
|
+
record: EdgeSchema,
|
|
166
|
+
})),
|
|
167
|
+
omitted: z.array(z.object({
|
|
168
|
+
kind: z.enum(["resources", "relationships"]),
|
|
169
|
+
recordId: z.string(),
|
|
170
|
+
reason: z.enum(["budget", "stale-provenance", "endpoint-not-delivered", "landscape-cap"]),
|
|
171
|
+
detail: z.string(),
|
|
172
|
+
})),
|
|
173
|
+
reviewIds: z.array(z.string()),
|
|
174
|
+
discoveryHashes: z.array(z.string()),
|
|
175
|
+
sourceRevisions: z.array(z.string()),
|
|
176
|
+
fragmentHash: z.string(),
|
|
177
|
+
});
|
|
147
178
|
/** Public MCP shape for the canonical delivery envelope. Keeping the schema on
|
|
148
179
|
* the tool means orchestrators can consume receipt facts without scraping the
|
|
149
180
|
* backward-compatible text block. */
|
|
150
181
|
const DELIVERY_OUTPUT_SCHEMA = z.object({
|
|
182
|
+
schema_version: z.literal("hunch.delivery-envelope/1"),
|
|
183
|
+
receipt_id: z.string().regex(/^hdr_[a-f0-9]{24}$/),
|
|
151
184
|
text: z.string(),
|
|
152
185
|
delivered: z.array(z.object({
|
|
153
|
-
kind: z.enum(["constraints", "decisions", "bugs", "findings"]),
|
|
186
|
+
kind: z.enum(["constraints", "decisions", "bugs", "findings", "resources", "relationships"]),
|
|
154
187
|
record_id: z.string(),
|
|
155
188
|
rank: z.number().int().positive(),
|
|
156
189
|
delivery_reason: z.enum(["ranked", "blocking-reserved"]),
|
|
@@ -178,13 +211,15 @@ const DELIVERY_OUTPUT_SCHEMA = z.object({
|
|
|
178
211
|
token_cost: z.number().int().nonnegative(),
|
|
179
212
|
})),
|
|
180
213
|
omitted: z.array(z.object({
|
|
181
|
-
kind: z.enum(["constraints", "decisions", "bugs", "findings"]),
|
|
214
|
+
kind: z.enum(["constraints", "decisions", "bugs", "findings", "resources", "relationships"]),
|
|
182
215
|
record_id: z.string(),
|
|
183
|
-
reason: z.enum(["budget", "stale-provenance", "retired", "actionability-cap", "low-confidence", "insufficient-context", "low-relevance"]),
|
|
216
|
+
reason: z.enum(["budget", "stale-provenance", "retired", "actionability-cap", "endpoint-not-delivered", "landscape-cap", "low-confidence", "insufficient-context", "low-relevance"]),
|
|
184
217
|
detail: z.string(),
|
|
185
218
|
})),
|
|
219
|
+
landscape: LANDSCAPE_FRAGMENT_SCHEMA.nullable(),
|
|
186
220
|
budget_tokens: z.number().int().nonnegative(),
|
|
187
221
|
used_chars: z.number().int().nonnegative(),
|
|
222
|
+
accounted_chars: z.number().int().nonnegative(),
|
|
188
223
|
blocking_overflow: z.boolean(),
|
|
189
224
|
abstention: z.object({
|
|
190
225
|
active: z.boolean(),
|
|
@@ -775,7 +810,13 @@ export function buildServerWithRootControl(initialRoot) {
|
|
|
775
810
|
// Task-phrase input ("improve retrieval ranking") resolves no file/symbol and
|
|
776
811
|
// used to return an empty brief while the graph held the answer — fall back to
|
|
777
812
|
// FTS so the assistant always leaves with the closest matches, not a shrug.
|
|
778
|
-
const empty = !ctx.constraints.length &&
|
|
813
|
+
const empty = !ctx.constraints.length &&
|
|
814
|
+
!ctx.decisions.length &&
|
|
815
|
+
!ctx.bugs.length &&
|
|
816
|
+
!ctx.blast_radius.length &&
|
|
817
|
+
!ctx.findings.length &&
|
|
818
|
+
!ctx.landscape?.resources.length &&
|
|
819
|
+
!ctx.landscape?.relationships.length;
|
|
779
820
|
if (empty && !asOf) {
|
|
780
821
|
const hits = store.search(target, 8);
|
|
781
822
|
if (hits.length) {
|
package/dist/store/hunchStore.js
CHANGED
|
@@ -25,6 +25,7 @@ import { edgeId } from "../core/ids.js";
|
|
|
25
25
|
import { isStrictBlocker, isVetoBlocker } from "../core/strictgate.js";
|
|
26
26
|
import { effectiveForbids, matchForbids } from "../core/constraintmatch.js";
|
|
27
27
|
import { analyzeDiff } from "../extractors/diff.js";
|
|
28
|
+
import { selectReviewedLandscape, } from "../core/landscapeDelivery.js";
|
|
28
29
|
/** Git cannot resolve repository identity from a cwd that does not exist yet.
|
|
29
30
|
* Probe the nearest real directory so a planned nested overlay cannot evade the
|
|
30
31
|
* public-repository boundary merely by deferring mkdir until its first write. */
|
|
@@ -1666,6 +1667,12 @@ export class HunchStore {
|
|
|
1666
1667
|
blast_radius: [...blast.values()].sort((a, b) => a.depth - b.depth).slice(0, 12),
|
|
1667
1668
|
components: w.components,
|
|
1668
1669
|
findings: this.liveFindingsFor(target).slice(0, 8),
|
|
1670
|
+
// Landscape records do not yet carry a valid-time window. A historical
|
|
1671
|
+
// query therefore withholds them instead of mixing current graph state
|
|
1672
|
+
// into an as-of memory envelope.
|
|
1673
|
+
landscape: opts.asOf
|
|
1674
|
+
? undefined
|
|
1675
|
+
: selectReviewedLandscape(this.recs("resources"), this.recs("edges"), target),
|
|
1669
1676
|
budget_tokens: budget,
|
|
1670
1677
|
};
|
|
1671
1678
|
return ctx;
|
|
@@ -121,10 +121,15 @@ export async function syncCommit(store, root, sha, opts = {}) {
|
|
|
121
121
|
// grounding. No-ops when no assistant CLI is available; never raises trust (dec_9a2f2fe72a).
|
|
122
122
|
if (wantVerify)
|
|
123
123
|
draft = await verifyDecisionSafe(await selectVerifier({ root }), input, draft);
|
|
124
|
+
// The draft's own drafter, not merely the one selected — draftDecisionSafe sets
|
|
125
|
+
// fellBackTo when the selected provider threw and it silently substituted the
|
|
126
|
+
// deterministic heuristic. Every telemetry/return below must report THIS, since
|
|
127
|
+
// backfill's LLM-vs-heuristic tally reads it directly off the return value.
|
|
128
|
+
const actualProvider = draft.fellBackTo ?? provider.name;
|
|
124
129
|
// Advisory synthesis telemetry for `hunch review` — which provider ran, how many drafts
|
|
125
130
|
// were reconciled, their agreement, and the verifier's grounding. Rides in `evidence`
|
|
126
131
|
// (no schema change → respects forward-migration invariant con_947c578b2c).
|
|
127
|
-
const synthBits = [`provider=${
|
|
132
|
+
const synthBits = [`provider=${actualProvider}`];
|
|
128
133
|
if (draft.samples)
|
|
129
134
|
synthBits.push(`samples=${draft.samples}`);
|
|
130
135
|
if (draft.agreement != null)
|
|
@@ -215,7 +220,7 @@ export async function syncCommit(store, root, sha, opts = {}) {
|
|
|
215
220
|
store.putPrivate("decisions", decision);
|
|
216
221
|
else
|
|
217
222
|
store.json.put("decisions", decision);
|
|
218
|
-
return { status: "written", decision, provider:
|
|
223
|
+
return { status: "written", decision, provider: actualProvider };
|
|
219
224
|
}
|
|
220
225
|
function putBugInHome(store, bug, home) {
|
|
221
226
|
return home === "private" ? store.putPrivate("bugs", bug) : store.json.put("bugs", bug);
|
|
@@ -244,6 +249,9 @@ export async function recordFailure(store, root, failure, opts = {}) {
|
|
|
244
249
|
suspects: suspects.map((s) => `${s.name} @ ${s.file}`),
|
|
245
250
|
};
|
|
246
251
|
const draft = await draftBugSafe(provider, input);
|
|
252
|
+
// The draft's own drafter, not merely the one selected — see the identical
|
|
253
|
+
// comment in syncCommit above.
|
|
254
|
+
const actualProvider = draft.fellBackTo ?? provider.name;
|
|
247
255
|
// Seed the id from the test id (stable), not the LLM title — one bug per test.
|
|
248
256
|
const id = bugId(failure.test);
|
|
249
257
|
// recurrence = a DIFFERENT prior bug with a similar symptom (not this same one).
|
|
@@ -284,7 +292,7 @@ export async function recordFailure(store, root, failure, opts = {}) {
|
|
|
284
292
|
putBugInHome(store, bug, home);
|
|
285
293
|
}
|
|
286
294
|
raiseFragility(store, affectedFiles, home);
|
|
287
|
-
return { status: "written", bug, constraint, provider:
|
|
295
|
+
return { status: "written", bug, constraint, provider: actualProvider, touchedHomes: [home] };
|
|
288
296
|
}
|
|
289
297
|
/** Orchestrate one `hunch test` run into graph writes: capture each failing test
|
|
290
298
|
* as a Bug (recordFailure → suspects / recurrence / Constraint promotion), and
|
|
@@ -506,19 +514,26 @@ export async function draftDecisionSafe(provider, input) {
|
|
|
506
514
|
try {
|
|
507
515
|
return await provider.draftDecision(input);
|
|
508
516
|
}
|
|
509
|
-
catch {
|
|
517
|
+
catch (error) {
|
|
510
518
|
// A provider failure (network, bad creds, CLI crash, unparseable output) must
|
|
511
519
|
// never abort the learning loop — fall back to the deterministic heuristic
|
|
512
520
|
// draft, which is honestly labeled ("inferred") rather than a hollow llm_draft.
|
|
513
|
-
|
|
521
|
+
// fellBackTo/fallbackReason tell the caller what ACTUALLY drafted this, not
|
|
522
|
+
// merely what was selected — see syncCommit, which must report this instead
|
|
523
|
+
// of the originally-selected provider's name.
|
|
524
|
+
const draft = await new DeterministicProvider().draftDecision(input);
|
|
525
|
+
return { ...draft, fellBackTo: "deterministic", fallbackReason: error instanceof Error ? error.message : String(error) };
|
|
514
526
|
}
|
|
515
527
|
}
|
|
516
528
|
export async function draftBugSafe(provider, input) {
|
|
517
529
|
try {
|
|
518
530
|
return await provider.draftBug(input);
|
|
519
531
|
}
|
|
520
|
-
catch {
|
|
521
|
-
|
|
532
|
+
catch (error) {
|
|
533
|
+
// See draftDecisionSafe above — the same fallback contract applies to bugs,
|
|
534
|
+
// and recordFailure must report the actual (fallback) provider likewise.
|
|
535
|
+
const draft = await new DeterministicProvider().draftBug(input);
|
|
536
|
+
return { ...draft, fellBackTo: "deterministic", fallbackReason: error instanceof Error ? error.message : String(error) };
|
|
522
537
|
}
|
|
523
538
|
}
|
|
524
539
|
//# sourceMappingURL=synthesize.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0-rc.2",
|
|
4
4
|
"mcpName": "io.github.davesheffer/hunch",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dave Sheffer <dave.sheffer1@gmail.com>",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"build": "npm run clean && tsc -p tsconfig.json",
|
|
59
59
|
"dev": "tsx src/cli/index.ts",
|
|
60
60
|
"hunch": "tsx src/cli/index.ts",
|
|
61
|
-
"test": "tsx --test --test-concurrency=
|
|
61
|
+
"test": "tsx --test --test-concurrency=4 test/*.test.ts",
|
|
62
62
|
"test:e2e:md1": "tsx --test test/md1-e2e.test.ts",
|
|
63
63
|
"test:e2e:team-matrix": "tsx --test test/team-matrix-e2e.test.ts",
|
|
64
64
|
"verify:matrix": "node tooling/matrix-release-verification.mjs",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"site:proof": "npm run build && node tooling/generate-public-proof.mjs",
|
|
69
69
|
"bench:md1": "node tooling/md1-benchmark.mjs",
|
|
70
70
|
"research:competitors": "node tooling/competitive-watch.mjs",
|
|
71
|
+
"outreach": "node tooling/outreach-pipeline.mjs",
|
|
71
72
|
"prepublishOnly": "npm run build"
|
|
72
73
|
},
|
|
73
74
|
"dependencies": {
|
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.20.0-rc.2",
|
|
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.20.0-rc.2",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|