@davesheffer/hunch 1.10.7 → 1.10.8

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 CHANGED
@@ -4939,6 +4939,17 @@ program
4939
4939
  console.log(`· ${f.id} — ${f.detail}`);
4940
4940
  console.log(`\nHeal: run \`hunch wiki --heal\` — regenerates only the stale pages (the wiki is a derived view; never edit it by hand).\n`);
4941
4941
  }
4942
+ // Every drift kind heals here — see bug_drift_heal_asymmetry above. premise-stale
4943
+ // shipped in the drift report without a section here, so a repo whose ONLY drift
4944
+ // was a dead premise got "N findings" from `hunch drift` and a bare closing line
4945
+ // from `hunch heal` — exactly the broken loop that bug is about.
4946
+ const premiseStale = kind("premise-stale");
4947
+ if (premiseStale.length) {
4948
+ console.log(`${premiseStale.length} decision(s) rest on a premise that no longer holds (world≠graph):\n`);
4949
+ for (const f of premiseStale)
4950
+ console.log(`· ${f.id} — ${f.detail}`);
4951
+ console.log(`\nHeal: this is a HUMAN call — the decision's authority is unchanged until you make it. Re-attest (update the premise's review_by/attested), supersede via /capture, or retire the decision. Keeping it for consistency is a valid answer.\n`);
4952
+ }
4942
4953
  console.log(`Hunch never rewrites prose for you; this is a read-only reconciliation report.`);
4943
4954
  }
4944
4955
  finally {
@@ -20,6 +20,15 @@ function checkPath(claim, rel, wantExists, env) {
20
20
  : { claim, holds: true, reason: `"${rel}" still absent` };
21
21
  }
22
22
  function checkOne(p, env) {
23
+ // The clock is an INJECTED input, so it is an unevaluable case like any other.
24
+ // `Date.parse("nope")` is NaN, and `NaN > due` is false — which fell through to
25
+ // "attested until …", i.e. HOLDS. The module's hard rule ("cannot-evaluate is
26
+ // never holds") was enforced for a bad review_by but not for a bad now, and the
27
+ // unenforced half is the one a future caller can get wrong. Checked once, here,
28
+ // so it covers every check kind rather than only the dated one.
29
+ if (!Number.isFinite(Date.parse(env.now))) {
30
+ return { claim: p.claim, holds: false, reason: `unevaluable: caller supplied a non-ISO clock ("${env.now}")` };
31
+ }
23
32
  if (p.path_absent !== undefined)
24
33
  return checkPath(p.claim, p.path_absent, false, env);
25
34
  if (p.path_exists !== undefined)
@@ -731,14 +731,19 @@ export function buildServerWithRootControl(initialRoot) {
731
731
  related_files: z.array(z.string()).optional(),
732
732
  related_components: z.array(z.string()).optional(),
733
733
  topic: z.string().optional().describe("decision-grounding anchor — one topic per decision; enables doc≠graph drift detection for it. Omit to leave un-anchored."),
734
+ // FLAT, matching PremiseSchema exactly. A nested { check: {...} } shape is
735
+ // silently STRIPPED by Zod, leaving a claim-only premise — and a claim-only
736
+ // premise is "documented only (no check attached)", which ALWAYS HOLDS. An
737
+ // agent following a wrong schema would record a premise that can never fire:
738
+ // the exact fail-open this feature exists to prevent. Keep in lockstep with
739
+ // PremiseSchema in src/core/types.ts.
734
740
  premises: z.array(z.object({
735
- claim: z.string().describe("the checkable reason this decision rests on, in plain words"),
736
- check: z.object({
737
- kind: z.enum(["path_absent", "path_exists", "review_by"]),
738
- path: z.string().optional().describe("repo-relative path for path_absent / path_exists"),
739
- review_by: z.string().optional().describe("ISO date this attestation expires (review_by)"),
740
- }).optional().describe("at most one deterministic check; omit for an unchecked note"),
741
- })).optional().describe("the checkable reasons this decision rests on. A dead premise NEVER changes authority — it raises an escalation for the human. Omit on re-record to keep the incumbent's premises."),
741
+ claim: z.string().min(1).describe("the human-readable reason this decision rests on"),
742
+ path_absent: z.string().optional().describe("premise holds while this repo-relative path does NOT exist"),
743
+ path_exists: z.string().optional().describe("premise holds while this repo-relative path exists"),
744
+ review_by: z.string().optional().describe("dated attestation: premise holds until this ISO date, then needs re-attesting"),
745
+ attested: z.string().optional().describe("ISO date a human last attested the claim (informational)"),
746
+ })).optional().describe("the checkable reasons this decision rests on — at most ONE check per premise (path_absent | path_exists | review_by). A dead premise NEVER changes authority; it raises an escalation for the human. Omit on re-record to keep the incumbent's premises."),
742
747
  status: z.enum(["proposed", "accepted", "rejected", "superseded"]).optional(),
743
748
  commit: z.string().optional(),
744
749
  supersedes: z.string().optional().describe("id of a decision this one replaces — closes its valid-time window (invalidate, don't delete)"),
@@ -571,7 +571,17 @@ export class HunchStore {
571
571
  if (m) {
572
572
  if (m.dead)
573
573
  w *= 0.6;
574
- w *= m.provenance.includes("human_confirmed") ? 1 : m.provenance.includes("llm_draft") ? 0.85 : 0.75;
574
+ // agent_recorded sits BETWEEN human_confirmed and llm_draft. It is testimony
575
+ // a human directed the capture but did not countersign it through /capture — so
576
+ // it must not carry human authority (strict/veto gates key on human_confirmed).
577
+ // But it is a deliberate, human-prompted write, and the unlabelled tier (0.75)
578
+ // is for extracted/inferred machine output. Without this it fell to 0.75 and
579
+ // ranked BELOW an llm_draft the model produced unprompted, which inverts what
580
+ // the authorship stamp is trying to express.
581
+ w *= m.provenance.includes("human_confirmed") ? 1
582
+ : m.provenance.includes("agent_recorded") ? 0.9
583
+ : m.provenance.includes("llm_draft") ? 0.85
584
+ : 0.75;
575
585
  if (m.at) {
576
586
  const ageDays = Math.max(0, now - Date.parse(m.at)) / 86400000;
577
587
  if (Number.isFinite(ageDays))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.10.7",
3
+ "version": "1.10.8",
4
4
  "mcpName": "io.github.davesheffer/hunch",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
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.7",
10
+ "version": "1.10.8",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.10.7",
16
+ "version": "1.10.8",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {