@davesheffer/hunch 1.31.1 → 1.32.1

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.
Files changed (223) hide show
  1. package/README.md +2 -1
  2. package/dist/cli/automaticReviewMemory.d.ts +13 -0
  3. package/dist/cli/dna.d.ts +2 -0
  4. package/dist/cli/index.d.ts +16 -0
  5. package/dist/cli/index.js +606 -43
  6. package/dist/cli/integrations.d.ts +2 -0
  7. package/dist/cli/integrations.js +5 -2
  8. package/dist/cli/invocation.d.ts +35 -0
  9. package/dist/cli/preflight.d.ts +1 -0
  10. package/dist/cli/reviewMemory.d.ts +4 -0
  11. package/dist/cli/reviewMemoryProvider.d.ts +14 -0
  12. package/dist/cli/serve.d.ts +2 -0
  13. package/dist/cli/taskReport.d.ts +6 -0
  14. package/dist/cli/taskReport.js +128 -0
  15. package/dist/cli/update.d.ts +14 -0
  16. package/dist/client/state.d.ts +248 -0
  17. package/dist/constitution/adapters.d.ts +29 -0
  18. package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
  19. package/dist/constitution/behaviorEvaluator.d.ts +8 -0
  20. package/dist/constitution/behaviorProof.d.ts +9 -0
  21. package/dist/constitution/behaviorWorkspace.d.ts +25 -0
  22. package/dist/constitution/bootstrap.d.ts +30 -0
  23. package/dist/constitution/canonical.d.ts +15 -0
  24. package/dist/constitution/card.d.ts +58 -0
  25. package/dist/constitution/compiler.d.ts +52 -0
  26. package/dist/constitution/composition.d.ts +10 -0
  27. package/dist/constitution/corpus.d.ts +7 -0
  28. package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
  29. package/dist/constitution/delta.d.ts +4 -0
  30. package/dist/constitution/disposition.d.ts +20 -0
  31. package/dist/constitution/evaluator.d.ts +47 -0
  32. package/dist/constitution/experiment.d.ts +466 -0
  33. package/dist/constitution/experimentRunner.d.ts +7 -0
  34. package/dist/constitution/g2.d.ts +209 -0
  35. package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
  36. package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
  37. package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
  38. package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
  39. package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
  40. package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
  41. package/dist/constitution/g2Candidates.d.ts +80 -0
  42. package/dist/constitution/g2Drills.d.ts +33 -0
  43. package/dist/constitution/g3.d.ts +311 -0
  44. package/dist/constitution/g3Conformance.d.ts +33 -0
  45. package/dist/constitution/lifecycle.d.ts +25 -0
  46. package/dist/constitution/mutation.d.ts +15 -0
  47. package/dist/constitution/nodeTestEvidence.d.ts +13 -0
  48. package/dist/constitution/plan.d.ts +18 -0
  49. package/dist/constitution/policyRuntime.d.ts +5 -0
  50. package/dist/constitution/proof.d.ts +11 -0
  51. package/dist/constitution/repairPolicies.d.ts +34 -0
  52. package/dist/constitution/replacementFreeGit.d.ts +14 -0
  53. package/dist/constitution/replay.d.ts +40 -0
  54. package/dist/constitution/replayCache.d.ts +14 -0
  55. package/dist/constitution/replayWorker.d.ts +1 -0
  56. package/dist/constitution/repository.d.ts +129 -0
  57. package/dist/constitution/safeCheckout.d.ts +9 -0
  58. package/dist/constitution/schema.d.ts +1763 -0
  59. package/dist/constitution/scorecard.d.ts +219 -0
  60. package/dist/constitution/service.d.ts +398 -0
  61. package/dist/constitution/shadow.d.ts +54 -0
  62. package/dist/constitution/sourceMutation.d.ts +15 -0
  63. package/dist/constitution/staticGraphBaseline.d.ts +13 -0
  64. package/dist/constitution/structural.d.ts +64 -0
  65. package/dist/core/agenthook.d.ts +63 -0
  66. package/dist/core/agenthook.js +1 -0
  67. package/dist/core/automaticReviewMemory.d.ts +60 -0
  68. package/dist/core/autoreview.d.ts +66 -0
  69. package/dist/core/canonicalOrder.d.ts +3 -0
  70. package/dist/core/capturetoken.d.ts +19 -0
  71. package/dist/core/changeIdentity.d.ts +22 -0
  72. package/dist/core/changeProof.d.ts +14 -0
  73. package/dist/core/checkreport.d.ts +140 -0
  74. package/dist/core/commitrepair.d.ts +171 -0
  75. package/dist/core/commitrepair.js +238 -0
  76. package/dist/core/compare.d.ts +21 -0
  77. package/dist/core/config.d.ts +20 -0
  78. package/dist/core/conformance.d.ts +35 -0
  79. package/dist/core/constraintmatch.d.ts +54 -0
  80. package/dist/core/correction.d.ts +42 -0
  81. package/dist/core/correctionStage.d.ts +224 -0
  82. package/dist/core/declarationClusters.d.ts +149 -0
  83. package/dist/core/delivery.d.ts +110 -0
  84. package/dist/core/docanchors.d.ts +32 -0
  85. package/dist/core/docscan.d.ts +26 -0
  86. package/dist/core/drift.d.ts +13 -0
  87. package/dist/core/drift.js +31 -1
  88. package/dist/core/dupdetect.d.ts +43 -0
  89. package/dist/core/escalations.d.ts +129 -0
  90. package/dist/core/escalations.js +165 -0
  91. package/dist/core/events.d.ts +29 -0
  92. package/dist/core/evidenceMap.d.ts +164 -0
  93. package/dist/core/externalImports.d.ts +8 -0
  94. package/dist/core/format.d.ts +13 -0
  95. package/dist/core/glob.d.ts +9 -0
  96. package/dist/core/groundingLag.d.ts +84 -0
  97. package/dist/core/groundingLag.js +16 -2
  98. package/dist/core/groundingMerge.d.ts +15 -0
  99. package/dist/core/groundingMerge.js +95 -0
  100. package/dist/core/hookObservations.d.ts +9 -0
  101. package/dist/core/hookObservations.js +33 -0
  102. package/dist/core/hookcache.d.ts +10 -0
  103. package/dist/core/hookpolicy.d.ts +47 -0
  104. package/dist/core/ids.d.ts +36 -0
  105. package/dist/core/importReview.d.ts +30 -0
  106. package/dist/core/io.d.ts +23 -0
  107. package/dist/core/jsonc.d.ts +4 -0
  108. package/dist/core/landscapeAdoption.d.ts +66 -0
  109. package/dist/core/landscapeDelivery.d.ts +76 -0
  110. package/dist/core/memorylog.d.ts +37 -0
  111. package/dist/core/migrate.d.ts +28 -0
  112. package/dist/core/outcomeExperience.d.ts +89 -0
  113. package/dist/core/overlaySafety.d.ts +24 -0
  114. package/dist/core/overlaySafety.js +7 -1
  115. package/dist/core/paths.d.ts +31 -0
  116. package/dist/core/pipeline.d.ts +289 -0
  117. package/dist/core/premises.d.ts +43 -0
  118. package/dist/core/provenance.d.ts +21 -0
  119. package/dist/core/publication.d.ts +54 -0
  120. package/dist/core/refrepair.d.ts +24 -0
  121. package/dist/core/refrepair.js +1 -1
  122. package/dist/core/relativeImports.d.ts +11 -0
  123. package/dist/core/repair.d.ts +44 -0
  124. package/dist/core/repairqueue.d.ts +40 -0
  125. package/dist/core/repairqueue.js +133 -0
  126. package/dist/core/reviewMemory.d.ts +40 -0
  127. package/dist/core/reviewqueue.d.ts +54 -0
  128. package/dist/core/safeRepoFile.d.ts +20 -0
  129. package/dist/core/served.d.ts +57 -0
  130. package/dist/core/served.js +14 -0
  131. package/dist/core/stateContract.d.ts +979 -0
  132. package/dist/core/stateDelivery.d.ts +78 -0
  133. package/dist/core/stateRecords.d.ts +383 -0
  134. package/dist/core/stats.d.ts +88 -0
  135. package/dist/core/strictgate.d.ts +33 -0
  136. package/dist/core/taskReport.d.ts +226 -0
  137. package/dist/core/taskReport.js +514 -0
  138. package/dist/core/taskReportCapture.d.ts +8 -0
  139. package/dist/core/taskReportCapture.js +36 -0
  140. package/dist/core/taskReportEvidence.d.ts +28 -0
  141. package/dist/core/taskReportEvidence.js +272 -0
  142. package/dist/core/taskReportHook.d.ts +20 -0
  143. package/dist/core/taskReportHook.js +76 -0
  144. package/dist/core/taskReportPaths.d.ts +3 -0
  145. package/dist/core/taskReportPaths.js +23 -0
  146. package/dist/core/taskReportPublic.d.ts +31 -0
  147. package/dist/core/taskReportPublic.js +45 -0
  148. package/dist/core/taskReportRender.d.ts +6 -0
  149. package/dist/core/taskReportRender.js +97 -0
  150. package/dist/core/topics.d.ts +73 -0
  151. package/dist/core/types.d.ts +1239 -0
  152. package/dist/core/version.d.ts +10 -0
  153. package/dist/eval/guards.d.ts +45 -0
  154. package/dist/eval/harness.d.ts +58 -0
  155. package/dist/extractors/adrImport.d.ts +59 -0
  156. package/dist/extractors/comments.d.ts +7 -0
  157. package/dist/extractors/correctionSources.d.ts +11 -0
  158. package/dist/extractors/diff.d.ts +40 -0
  159. package/dist/extractors/git.d.ts +304 -0
  160. package/dist/extractors/git.js +208 -20
  161. package/dist/extractors/helm.d.ts +31 -0
  162. package/dist/extractors/indexer.d.ts +55 -0
  163. package/dist/extractors/landscapeDiscovery.d.ts +41 -0
  164. package/dist/extractors/languages.d.ts +77 -0
  165. package/dist/extractors/nativeTreeSitter.d.ts +16 -0
  166. package/dist/extractors/parse.d.ts +54 -0
  167. package/dist/extractors/php.d.ts +26 -0
  168. package/dist/extractors/repoSource.d.ts +53 -0
  169. package/dist/extractors/testreport.d.ts +36 -0
  170. package/dist/integrations/ciAction.d.ts +8 -0
  171. package/dist/integrations/claudeConfig.d.ts +44 -0
  172. package/dist/integrations/claudemd.d.ts +13 -0
  173. package/dist/integrations/claudemd.js +11 -2
  174. package/dist/integrations/gitignore.d.ts +16 -0
  175. package/dist/integrations/gitignore.js +7 -0
  176. package/dist/integrations/health.d.ts +74 -0
  177. package/dist/integrations/health.js +32 -2
  178. package/dist/integrations/hooks.d.ts +41 -0
  179. package/dist/integrations/hooks.js +92 -66
  180. package/dist/integrations/madrExport.d.ts +39 -0
  181. package/dist/integrations/madrManifest.d.ts +69 -0
  182. package/dist/integrations/mergeDriver.d.ts +3 -0
  183. package/dist/integrations/mergeDriver.js +25 -8
  184. package/dist/integrations/probe.d.ts +4 -0
  185. package/dist/integrations/providers.d.ts +103 -0
  186. package/dist/integrations/scaffold.d.ts +29 -0
  187. package/dist/integrations/sync.d.ts +30 -0
  188. package/dist/integrations/sync.js +3 -2
  189. package/dist/integrations/team.d.ts +70 -0
  190. package/dist/integrations/worktree.d.ts +8 -0
  191. package/dist/mcp/roots.d.ts +16 -0
  192. package/dist/mcp/server.d.ts +36 -0
  193. package/dist/mcp/server.js +74 -22
  194. package/dist/mcp/taskReportTools.d.ts +188 -0
  195. package/dist/mcp/taskReportTools.js +147 -0
  196. package/dist/serve/app.d.ts +36 -0
  197. package/dist/serve/config.d.ts +100 -0
  198. package/dist/serve/writelock.d.ts +22 -0
  199. package/dist/store/changeLedger.d.ts +132 -0
  200. package/dist/store/compact.d.ts +33 -0
  201. package/dist/store/db.d.ts +9 -0
  202. package/dist/store/embedder.d.ts +32 -0
  203. package/dist/store/hunchStore.d.ts +624 -0
  204. package/dist/store/hunchStore.js +6 -3
  205. package/dist/store/jsonStore.d.ts +125 -0
  206. package/dist/store/merge.d.ts +39 -0
  207. package/dist/store/privateMigrate.d.ts +24 -0
  208. package/dist/store/replay.d.ts +56 -0
  209. package/dist/store/schema.d.ts +17 -0
  210. package/dist/store/stateBinding.d.ts +145 -0
  211. package/dist/store/stateCapture.d.ts +15 -0
  212. package/dist/synthesis/cliAdapter.d.ts +22 -0
  213. package/dist/synthesis/initiator.d.ts +13 -0
  214. package/dist/synthesis/provider.d.ts +307 -0
  215. package/dist/synthesis/synthesize.d.ts +117 -0
  216. package/dist/synthesis/tripwires.d.ts +9 -0
  217. package/dist/taskReports.d.ts +79 -0
  218. package/dist/taskReports.js +69 -0
  219. package/dist/wiki/adopt.d.ts +22 -0
  220. package/dist/wiki/graph.d.ts +87 -0
  221. package/dist/wiki/wiki.d.ts +242 -0
  222. package/package.json +6 -5
  223. package/server.json +2 -2
@@ -40,6 +40,10 @@ import { PROJECT_DNA_DELTA_SCHEMA_VERSION, diffProjectDna } from "../core/projec
40
40
  import { projectDnaDeliverySupplement } from "../core/projectDnaDelivery.js";
41
41
  import { armExecutionObligations, loadPipelineState, savePipelineState } from "../core/pipeline.js";
42
42
  import { recordServed } from "../core/served.js";
43
+ import { TaskIdSchema, recordTaskDelivery } from "../core/taskReport.js";
44
+ import { observeReportCapture } from "../core/taskReportCapture.js";
45
+ import { snapshotDeliveredRecords } from "../core/taskReportEvidence.js";
46
+ import { registerTaskReportTools } from "./taskReportTools.js";
43
47
  import { EdgeSchema, ResourceSchema } from "../core/types.js";
44
48
  import { compareCandidates } from "../core/compare.js";
45
49
  import { checkConformance } from "../core/conformance.js";
@@ -51,7 +55,8 @@ import { nowData, wikiStatus, publicHome, readWikiManifestAt } from "../wiki/wik
51
55
  import { HUNCH_VERSION } from "../core/version.js";
52
56
  import { assertCompleteRepoScan, indexRepo, scanRepo } from "../extractors/indexer.js";
53
57
  import { liveForTopic, historyForTopic, rejectedForTopic, captureConflicts } from "../core/topics.js";
54
- import { pendingEscalations, policyEscalations } from "../core/escalations.js";
58
+ import { pendingEscalations, policyEscalations, commitRepairEscalations, actionableEscalations, escalationHeadline } from "../core/escalations.js";
59
+ import { readActivePendingRepairs, withheldRewrites } from "../core/repairqueue.js";
55
60
  import { scanRecord, publicationWarning, loadVocabulary } from "../core/publication.js";
56
61
  import { premiseEscalations } from "../core/premises.js";
57
62
  import { applyImportedAdrReview, pendingImportedAdrReviews } from "../core/importReview.js";
@@ -770,6 +775,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
770
775
  });
771
776
  return ok(`Top matches for "${query}":\n\n${lines.join("\n")}`);
772
777
  });
778
+ registerTaskReportTools(server, () => root, () => store);
773
779
  // -- hunch_runbook --------------------------------------------------------
774
780
  server.registerTool("hunch_runbook", {
775
781
  title: "Find a runbook for a task",
@@ -1032,9 +1038,26 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1032
1038
  budget_tokens: z.number().optional().describe("Rough token budget for the brief (default 1500)."),
1033
1039
  profile: z.enum(DELIVERY_PROFILES).optional().describe("Delivery role: builder (default), reviewer, or architect. Changes non-blocking order only."),
1034
1040
  as_of: z.string().optional().describe("Time-travel ref (commit/tag/branch): assemble the slice as it stood then."),
1041
+ task_id: TaskIdSchema.optional().describe("Exact task ID from hunch_task; records this delivery for the task's contribution report."),
1042
+ cwd: cwdHintField,
1035
1043
  },
1036
1044
  outputSchema: DELIVERY_OUTPUT_SCHEMA,
1037
- }, async ({ target, budget_tokens, profile, as_of }, extra) => {
1045
+ }, async ({ target, budget_tokens, profile, as_of, task_id }, extra) => {
1046
+ const deliver = (envelope) => {
1047
+ const result = deliveredContext(root, as_of ? `${target} (as_of:${as_of})` : target, envelope, extra.sessionId);
1048
+ if (task_id) {
1049
+ try {
1050
+ // Historical contexts must not borrow today's record text/revision.
1051
+ const records = as_of ? [] : snapshotDeliveredRecords(store, envelope);
1052
+ const occurrence = recordTaskDelivery(root, task_id, envelope, records);
1053
+ result.content.push({ type: "text", text: `Task evidence: ${task_id} · occurrence ${occurrence}.\n${records.slice(0, 20).map(r => `${r.record_id} @ ${r.content_hash}`).join("\n")}${records.length > 20 ? "\nMore record identities: hunch_report(task_id)." : ""}` });
1054
+ }
1055
+ catch {
1056
+ result.content.push({ type: "text", text: `Task evidence could not be recorded for ${task_id}. Context remains available; this delivery's report attribution is unverified. Check the task ID, working directory, and local ledger.` });
1057
+ }
1058
+ }
1059
+ return result;
1060
+ };
1038
1061
  const asOf = as_of ? asOfDate(as_of, root) : undefined;
1039
1062
  if (as_of && !asOf)
1040
1063
  return invalid(`Could not resolve as_of "${as_of}" to a commit.`);
@@ -1098,11 +1121,11 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1098
1121
  })),
1099
1122
  ],
1100
1123
  });
1101
- return deliveredContext(root, target, envelope, extra.sessionId);
1124
+ return deliver(envelope);
1102
1125
  }
1103
1126
  }
1104
1127
  const envelope = buildDeliveryEnvelope(ctx, options);
1105
- return deliveredContext(root, as_of ? `${target} (as_of:${as_of})` : target, envelope, extra.sessionId);
1128
+ return deliver(envelope);
1106
1129
  });
1107
1130
  // -- hunch_shortlist (bounded correction-stage diagnostic) ----------------
1108
1131
  server.registerTool("hunch_shortlist", {
@@ -1146,10 +1169,13 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1146
1169
  // -- hunch_now (the hot view: recent activity + roadmap) --------------------
1147
1170
  // PUBLIC store only, per dec_29eff08c69's jurisdiction rule: an assistant may
1148
1171
  // paste this anywhere, so it must be publishable by construction. Union view
1149
- // stays behind `hunch now --private` on the local terminal.
1172
+ // stays behind `hunch now --private` on the local terminal. EXCEPTION: a
1173
+ // queued commit-repair's liveness is checked against the full store (see
1174
+ // below), so a private-overlay decision's id and commit shas — never its
1175
+ // title — can surface in the escalation line.
1150
1176
  server.registerTool("hunch_now", {
1151
1177
  title: "Recent activity + the roadmap (the hot view)",
1152
- description: "What just happened and what's next, straight from the graph: the last N decisions, the ROADMAP, and any inline human question such as an imported ADR awaiting explicit approve/decline. Call at session start to orient, or before planning what to work on. Same data as the wiki's now.md. Public store only.",
1178
+ description: "What just happened and what's next, straight from the graph: the last N decisions, the ROADMAP, and any inline human question such as an imported ADR awaiting explicit approve/decline. Call at session start to orient, or before planning what to work on. Same data as the wiki's now.md. Public store only, EXCEPT a queued commit-repair's liveness is checked against the full store (so a private-overlay decision's fully-answerable repair doesn't go silently unanswerable); only its id and the commit shas ever surface, never its title.",
1153
1179
  inputSchema: {
1154
1180
  recent_limit: z.number().optional().describe("How many recent decisions to include (default 10)."),
1155
1181
  },
@@ -1167,8 +1193,18 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1167
1193
  L.push("", `${pendingReview} legacy un-vouched draft(s) — \`hunch adopt-drafts\` auto-trusts them as advisory (new captures land trusted automatically).`);
1168
1194
  const escalations = pendingEscalations(store.advisoryRecs("decisions"));
1169
1195
  escalations.push(...premiseEscalations(store.advisoryRecs("decisions"), { now: new Date().toISOString(), exists: (p) => existsSync(join(root, p)) }));
1170
- if (escalations.length) {
1171
- L.push("", `⚖ ${escalations.length} decision(s) need the human's call ASK inline (never queue): ${escalations.map((e) => e.question).join(" · ")}`);
1196
+ // liveness checked against the full store (repair-provenance reads the
1197
+ // full store too) even though the title stays scoped to advisoryRecs
1198
+ // an overlay decision's repair is fully answerable, so it must not go
1199
+ // silent here just because its title is private.
1200
+ const hunchNowQueue = readActivePendingRepairs(root);
1201
+ escalations.push(...commitRepairEscalations(hunchNowQueue, store.advisoryRecs("decisions"), store.recs("decisions"), withheldRewrites(root, hunchNowQueue)));
1202
+ // Only ACTIONABLE entries are a question the assistant can put to the human
1203
+ // directly — a duplicate-id commit-repair follower whose own resolution
1204
+ // says "act on a different entry first" isn't one (#61).
1205
+ const actionableNow = actionableEscalations(escalations);
1206
+ if (actionableNow.length) {
1207
+ L.push("", `⚖ ${actionableNow.length} decision(s) need the human's call — ASK inline (never queue): ${actionableNow.map((e) => e.question).join(" · ")}`);
1172
1208
  }
1173
1209
  return ok(L.join("\n"));
1174
1210
  });
@@ -1181,22 +1217,32 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1181
1217
  // (con_e04226bd05): no Claude-specific behavior.
1182
1218
  server.registerTool("hunch_escalations", {
1183
1219
  title: "Decisions the human must make now (ask inline, not a queue)",
1184
- description: "The rare decisions the graph cannot resolve on its own — surfaced so you ASK THE USER in the prompt at the moment, then act. This includes one exact imported ADR at a time awaiting approve/decline, topic conflicts, premise-stale decisions, and Constitution human moments. Normally empty. Raise each question with the user; do NOT decide it for them — an entry is a question, silence is never approval. Reads the public store, or the unified overlay when the repo is in shared mode (where the overlay IS the store) never private-mode overlay records.",
1220
+ description: "The rare decisions the graph cannot resolve on its own — surfaced so you ASK THE USER in the prompt at the moment, then act. Auto-captured memory is trusted automatically and never appears here; this returns topic conflicts (>1 live decision for one topic), premise-stale decisions (a live decision whose recorded REASON no longer holds — its authority is unchanged until the human re-attests, supersedes, or retires), one exact imported ADR at a time awaiting approve/decline, a queued commit-provenance repair (the post-merge hook detected a decision's commit was squash-merged away but never applies the fix unattended — apply with `hunch repair-provenance --apply` or leave it queued), and Constitution human moments (candidate policies awaiting review, proposed policies awaiting an activation decision). Normally empty. Raise each question with the user; do NOT decide it for them — an entry is a question, silence is never approval. Reads the public store, or the unified overlay when the repo is in shared mode never private-mode overlay records, EXCEPT a queued commit-repair, whose liveness is checked against the full store; only its id and commit shas ever surface, never its title.",
1185
1221
  inputSchema: {},
1186
1222
  }, async () => {
1187
1223
  const items = pendingEscalations(store.advisoryRecs("decisions"));
1188
1224
  // Premise decay: a live decision whose recorded reason died. Question-framed
1189
1225
  // like every entry here — authority never changes until the human answers.
1190
1226
  items.push(...premiseEscalations(store.advisoryRecs("decisions"), { now: new Date().toISOString(), exists: (p) => existsSync(join(root, p)) }));
1227
+ // See the sibling hunch_now handler above for why `live` is the full store.
1228
+ const hunchEscalationsQueue = readActivePendingRepairs(root);
1229
+ items.push(...commitRepairEscalations(hunchEscalationsQueue, store.advisoryRecs("decisions"), store.recs("decisions"), withheldRewrites(root, hunchEscalationsQueue)));
1191
1230
  try {
1192
1231
  items.push(...policyEscalations(new ConstitutionService(store, root).list({ publicOnly: true }).map((p) => ({ ...p, last_action: p.audit.at(-1)?.action ?? null }))));
1193
1232
  }
1194
1233
  catch { /* constitution unavailable — memory escalations still surface */ }
1195
1234
  if (!items.length)
1196
1235
  return ok("✓ Nothing needs a human decision — memory is auto-trusted and self-consistent.");
1197
- const L = [`${items.length} decision(s) need the human's call — ask each inline, don't decide it for them:`, ""];
1236
+ // Only ACTIONABLE entries are a question to ask the human directly a
1237
+ // duplicate-id commit-repair follower whose own resolution says "act on
1238
+ // a different entry first" still surfaces below for transparency, but
1239
+ // isn't itself something to raise as a decision (#61).
1240
+ const L = [escalationHeadline(items, "mcp"), ""];
1198
1241
  for (const e of items) {
1199
- L.push(`⚖ ${e.question}`);
1242
+ // Marked distinctly (never the "⚖ question" glyph) when it isn't
1243
+ // itself something to raise — an entry whose own answer is "act on a
1244
+ // different entry first" reads as informational, not as a question.
1245
+ L.push(`${e.actionable === false ? "·" : "⚖"} ${e.question}`);
1200
1246
  L.push(` ${e.detail}`);
1201
1247
  L.push(` → ${e.resolution}`, "");
1202
1248
  }
@@ -1371,9 +1417,10 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1371
1417
  private: z.boolean().optional().describe("write into the PRIVATE overlay store (HUNCH_PRIVATE_DIR) instead of the committed repo — for sensitive decisions kept out of a public repo. Errors if no private store is configured."),
1372
1418
  }),
1373
1419
  capture_token: z.string().optional().describe("token from hunch_capture_decision — proves this write is the tail of a grilling interview. Omit only for a quick manual record (a deprecation nudge is returned)."),
1420
+ task_id: TaskIdSchema.optional().describe("Exact task ID for observing this successful save; reporting never changes capture authority."),
1374
1421
  cwd: cwdHintField,
1375
1422
  },
1376
- }, async ({ decision, capture_token }) => {
1423
+ }, async ({ decision, capture_token, task_id }) => {
1377
1424
  try {
1378
1425
  // Commit-keyed on the CANONICAL full sha (resolved via git rev-parse), so a
1379
1426
  // human passing the short sha they see in `commit` produces the SAME id as
@@ -1510,7 +1557,8 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1510
1557
  // closes the other, leaving two live decisions on one topic and grounding
1511
1558
  // silently injecting nothing for it. The guard throws; the surrounding catch
1512
1559
  // turns that into a clean tool error instead of a silent twin.
1513
- store.putCapture("decisions", rec, !!decision.private);
1560
+ const stored = store.putCapture("decisions", rec, !!decision.private);
1561
+ const observed = observeReportCapture(root, task_id, "decisions", stored, home, !!existing, home === "private" ? store.privateDir ?? undefined : hunchPaths(root).hunch);
1514
1562
  // Invalidate, don't delete: closing the superseded decision's valid-time window
1515
1563
  // (+ a supersedes edge) preserves the why-it-changed trail. Route the close to the
1516
1564
  // same store the new record landed in — a private decision supersedes within the
@@ -1523,8 +1571,8 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1523
1571
  // Auto-flush the store the record landed in (on by default in every mode): a private
1524
1572
  // record commits+pushes its overlay repo; a public one commits .hunch/ in THIS repo
1525
1573
  // (commit only — it rides the user's next push, never auto-pushing their code branch).
1526
- const flush = flushCapture(store, hunchPaths(root).hunch, !!decision.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined);
1527
- const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch);
1574
+ const flush = flushCapture(store, hunchPaths(root).hunch, !!decision.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined, observed.observe);
1575
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch) + observed.note;
1528
1576
  // Capture-session gate (staged deprecation, §9.3): the token was consumed
1529
1577
  // above (it also decides the provenance tier). No token still writes
1530
1578
  // (non-breaking) but lands as agent_recorded with a nudge toward /capture.
@@ -1571,6 +1619,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1571
1619
  source_decision: z.string().optional().describe("id of a decision this correction derives from."),
1572
1620
  private: z.boolean().optional().describe("write into the PRIVATE overlay store (HUNCH_PRIVATE_DIR) instead of the committed repo — a sensitive rule enforced locally (pre-edit hook + local check) but never exposed in a public PR comment. Errors if no private store is configured."),
1573
1621
  capture_token: z.string().optional().describe("token from hunch_capture_decision. The rule is recorded and enforced either way — the token only decides whether it may DENY: without one it lands as advisory testimony capped at severity 'warning'."),
1622
+ task_id: TaskIdSchema.optional().describe("Exact task ID for observing this successful save; reporting never changes capture authority."),
1574
1623
  cwd: cwdHintField,
1575
1624
  },
1576
1625
  }, async (input) => {
@@ -1596,7 +1645,8 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1596
1645
  }
1597
1646
  const existing = home === "private" ? store.getPrivateRec("constraints", rec.id) : store.json.get("constraints", rec.id);
1598
1647
  // Same cross-home twin guard as the decision path above.
1599
- store.putCapture("constraints", rec, !!input.private);
1648
+ const stored = store.putCapture("constraints", rec, !!input.private);
1649
+ const observed = observeReportCapture(root, input.task_id, "constraints", stored, home, !!existing, home === "private" ? store.privateDir ?? undefined : hunchPaths(root).hunch);
1600
1650
  store.reindex();
1601
1651
  // Propagate the new rule to EVERY assistant's ambient grounding (Cursor/Copilot/
1602
1652
  // Windsurf/AGENTS.md/CLAUDE.md), so a correction captured in one assistant is held
@@ -1608,8 +1658,8 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1608
1658
  // dirty AGENTS/assistant docs. Manual mode still refreshes in place.
1609
1659
  if (home === "public" && !store.autoCommit)
1610
1660
  refreshExistingGrounding(root, store); // overlay rules never render into committed grounding
1611
- const flush = flushCapture(store, hunchPaths(root).hunch, !!input.private, `hunch: capture ${rec.id}`, startupTeamRoute ?? undefined);
1612
- const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch);
1661
+ const flush = flushCapture(store, hunchPaths(root).hunch, !!input.private, `hunch: capture ${rec.id}`, startupTeamRoute ?? undefined, observed.observe);
1662
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch) + observed.note;
1613
1663
  const enforce = rec.severity === "blocking"
1614
1664
  ? "blocks a DIRECT edit to its scope at strict firmness, and fails a PR whose diff touches that scope (CI guard); blast-radius hits and lower firmness stay advisory"
1615
1665
  : "flags violating edits and PRs (advisory)";
@@ -1652,9 +1702,10 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1652
1702
  resolved_commit: z.string().optional().describe("the commit that fixed it (with triage:'resolved')"),
1653
1703
  private: z.boolean().optional().describe("write into the PRIVATE overlay store instead of the committed repo. Errors if no private store is configured."),
1654
1704
  }),
1705
+ task_id: TaskIdSchema.optional().describe("Exact task ID for observing this successful save; reporting never changes capture authority."),
1655
1706
  cwd: cwdHintField,
1656
1707
  },
1657
- }, async ({ finding }) => {
1708
+ }, async ({ finding, task_id }) => {
1658
1709
  try {
1659
1710
  if (!finding.title.trim())
1660
1711
  return invalid("title is required.");
@@ -1684,10 +1735,11 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
1684
1735
  resolved_commit: finding.resolved_commit ?? existing?.resolved_commit ?? null,
1685
1736
  provenance: { source: "human_confirmed", confidence: 0.95, evidence: finding.evidence ?? existing?.provenance.evidence ?? [], last_verified: now },
1686
1737
  };
1687
- store.putCapture("findings", rec, !!finding.private);
1738
+ const stored = store.putCapture("findings", rec, !!finding.private);
1739
+ const observed = observeReportCapture(root, task_id, "findings", stored, home, !!existing, home === "private" ? store.privateDir ?? undefined : hunchPaths(root).hunch);
1688
1740
  store.reindex();
1689
- const flush = flushCapture(store, hunchPaths(root).hunch, !!finding.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined);
1690
- const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch);
1741
+ const flush = flushCapture(store, hunchPaths(root).hunch, !!finding.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined, observed.observe);
1742
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate, rec, hunchPaths(root).hunch) + observed.note;
1691
1743
  const where = finding.private
1692
1744
  ? ` [PRIVATE overlay — not committed to this repo]${flushed}`
1693
1745
  : home === "private" ? ` [SHARED store — one source of truth for the whole team]${flushed}` : flushed;
@@ -0,0 +1,188 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { readTaskReport } from "../core/taskReport.js";
3
+ import type { HunchStore } from "../store/hunchStore.js";
4
+ /** A tool result must fit the host's round-trip. The full report (every
5
+ * envelope's context text, every lesson) belongs to `hunch report --json` and
6
+ * the HTML view; MCP returns exact identities, verdicts and the card. Bounded
7
+ * by caps first, then by byte size (fnd_53991b877b: an 18-delivery task
8
+ * produced a 101 KB result the host refused). */
9
+ export declare const MCP_REPORT_BYTE_BUDGET = 48000;
10
+ export declare function boundedTaskReport(report: ReturnType<typeof readTaskReport>, deliveries?: number, recordsPerDelivery?: number): {
11
+ schema: "hunch.task-report-summary/1";
12
+ task: {
13
+ task_id: string;
14
+ scope: string;
15
+ title: string;
16
+ started_at: string;
17
+ finished_at: string | null;
18
+ state: "open" | "completed" | "interrupted";
19
+ };
20
+ coverage: "delivered" | "no-delivery-observed" | "no-relevant-memory";
21
+ content_hash: string;
22
+ unknowns: string[];
23
+ deliveries: {
24
+ occurrence_id: string;
25
+ receipt_id: string;
26
+ at: string;
27
+ envelope_hash: string;
28
+ delivered: number;
29
+ abstention: boolean;
30
+ records: {
31
+ record_id: string;
32
+ kind: string;
33
+ content_hash: string;
34
+ title: string;
35
+ }[];
36
+ more_records: number;
37
+ }[];
38
+ claims: {
39
+ action: string;
40
+ occurrence_id: string;
41
+ record_id: string;
42
+ content_hash: string;
43
+ at: string;
44
+ attribution: "agent-reported";
45
+ supported_by: string | null;
46
+ }[];
47
+ checks: {
48
+ check_id: string | undefined;
49
+ label: string;
50
+ command: string[];
51
+ exit_code: number | null;
52
+ timed_out: boolean;
53
+ cancelled: boolean;
54
+ current: boolean;
55
+ at: string;
56
+ }[];
57
+ conformance: {
58
+ record_id: string;
59
+ kind: "decisions" | "constraints";
60
+ content_hash: string;
61
+ rule: "constraint-forbids" | "decision-conformance";
62
+ outcome: "satisfied" | "violated" | "not-exercised" | "unavailable";
63
+ current: boolean;
64
+ files: string[];
65
+ detail: string;
66
+ }[];
67
+ saves: {
68
+ event_id: string;
69
+ at: string;
70
+ record_id: string;
71
+ kind: string;
72
+ content_hash: string;
73
+ title: string;
74
+ home: "public" | "private";
75
+ operation: "updated" | "created";
76
+ durability: "committed" | "pushed" | "local";
77
+ }[];
78
+ refusals: ({
79
+ source: "native-edit-gate";
80
+ outcome: "denial-emitted";
81
+ kind: "constraint" | "veto";
82
+ record_id: string;
83
+ target: string;
84
+ reason_hash: string;
85
+ } & {
86
+ event_id: string;
87
+ at: string;
88
+ })[];
89
+ omitted: {
90
+ deliveries: number;
91
+ claims: number;
92
+ checks: number;
93
+ conformance: number;
94
+ saves: number;
95
+ refusals: number;
96
+ };
97
+ full_report: string;
98
+ };
99
+ export declare function boundedTaskReportForHost(report: ReturnType<typeof readTaskReport>): {
100
+ schema: "hunch.task-report-summary/1";
101
+ task: {
102
+ task_id: string;
103
+ scope: string;
104
+ title: string;
105
+ started_at: string;
106
+ finished_at: string | null;
107
+ state: "open" | "completed" | "interrupted";
108
+ };
109
+ coverage: "delivered" | "no-delivery-observed" | "no-relevant-memory";
110
+ content_hash: string;
111
+ unknowns: string[];
112
+ deliveries: {
113
+ occurrence_id: string;
114
+ receipt_id: string;
115
+ at: string;
116
+ envelope_hash: string;
117
+ delivered: number;
118
+ abstention: boolean;
119
+ records: {
120
+ record_id: string;
121
+ kind: string;
122
+ content_hash: string;
123
+ title: string;
124
+ }[];
125
+ more_records: number;
126
+ }[];
127
+ claims: {
128
+ action: string;
129
+ occurrence_id: string;
130
+ record_id: string;
131
+ content_hash: string;
132
+ at: string;
133
+ attribution: "agent-reported";
134
+ supported_by: string | null;
135
+ }[];
136
+ checks: {
137
+ check_id: string | undefined;
138
+ label: string;
139
+ command: string[];
140
+ exit_code: number | null;
141
+ timed_out: boolean;
142
+ cancelled: boolean;
143
+ current: boolean;
144
+ at: string;
145
+ }[];
146
+ conformance: {
147
+ record_id: string;
148
+ kind: "decisions" | "constraints";
149
+ content_hash: string;
150
+ rule: "constraint-forbids" | "decision-conformance";
151
+ outcome: "satisfied" | "violated" | "not-exercised" | "unavailable";
152
+ current: boolean;
153
+ files: string[];
154
+ detail: string;
155
+ }[];
156
+ saves: {
157
+ event_id: string;
158
+ at: string;
159
+ record_id: string;
160
+ kind: string;
161
+ content_hash: string;
162
+ title: string;
163
+ home: "public" | "private";
164
+ operation: "updated" | "created";
165
+ durability: "committed" | "pushed" | "local";
166
+ }[];
167
+ refusals: ({
168
+ source: "native-edit-gate";
169
+ outcome: "denial-emitted";
170
+ kind: "constraint" | "veto";
171
+ record_id: string;
172
+ target: string;
173
+ reason_hash: string;
174
+ } & {
175
+ event_id: string;
176
+ at: string;
177
+ })[];
178
+ omitted: {
179
+ deliveries: number;
180
+ claims: number;
181
+ checks: number;
182
+ conformance: number;
183
+ saves: number;
184
+ refusals: number;
185
+ };
186
+ full_report: string;
187
+ };
188
+ export declare function registerTaskReportTools(server: McpServer, getRoot: () => string, getStore: () => HunchStore): void;
@@ -0,0 +1,147 @@
1
+ import { z } from "zod";
2
+ import { fileURLToPath } from "node:url";
3
+ import { TaskIdSchema, ReportClaimSchema, LessonReferenceSchema, finishReportTask, listReportTasks, readTaskReport, readLessonHistory, recordReportClaim, reportPresentationEnabled, startReportTask } from "../core/taskReport.js";
4
+ import { reportSourceSnapshot, runReportConformance } from "../core/taskReportEvidence.js";
5
+ import { renderTaskReport, writeTaskReportHtml } from "../core/taskReportRender.js";
6
+ function applicationReferences(report) {
7
+ return report.deliveries.flatMap(d => d.records.map(r => ({ occurrence_id: d.occurrence_id, record_id: r.record_id, content_hash: r.content_hash, title: r.title }))).slice(-100);
8
+ }
9
+ /** A tool result must fit the host's round-trip. The full report (every
10
+ * envelope's context text, every lesson) belongs to `hunch report --json` and
11
+ * the HTML view; MCP returns exact identities, verdicts and the card. Bounded
12
+ * by caps first, then by byte size (fnd_53991b877b: an 18-delivery task
13
+ * produced a 101 KB result the host refused). */
14
+ export const MCP_REPORT_BYTE_BUDGET = 48_000;
15
+ export function boundedTaskReport(report, deliveries = 30, recordsPerDelivery = 25) {
16
+ const clip = (s, n) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);
17
+ const tail = (items, n) => items.slice(Math.max(0, items.length - n));
18
+ return {
19
+ schema: "hunch.task-report-summary/1",
20
+ task: report.task, coverage: report.coverage, content_hash: report.content_hash, unknowns: report.unknowns,
21
+ deliveries: tail(report.deliveries, deliveries).map(d => ({
22
+ occurrence_id: d.occurrence_id, receipt_id: d.receipt_id, at: d.at, envelope_hash: d.envelope_hash,
23
+ delivered: d.envelope.delivered.length, abstention: d.envelope.abstention.active,
24
+ records: d.records.slice(0, recordsPerDelivery).map(r => ({ record_id: r.record_id, kind: r.kind, content_hash: r.content_hash, title: clip(r.title, 160) })),
25
+ more_records: Math.max(0, d.records.length - recordsPerDelivery),
26
+ })),
27
+ claims: tail(report.claims, 20).map(c => ({ ...c, action: clip(c.action, 300) })),
28
+ checks: tail(report.checks, 30).map(c => ({ check_id: c.check_id, label: c.label, command: c.command.map(x => clip(x, 120)), exit_code: c.exit_code, timed_out: c.timed_out, cancelled: c.cancelled ?? false, current: c.current, at: c.at })),
29
+ conformance: tail(report.conformance, 50).map(r => ({ record_id: r.record_id, kind: r.kind, content_hash: r.content_hash, rule: r.rule, outcome: r.outcome, current: r.current, files: r.files.slice(0, 8), detail: clip(r.detail, 240) })),
30
+ saves: tail(report.saves, 30).map(s => ({ event_id: s.event_id, at: s.at, record_id: s.record.record_id, kind: s.record.kind, content_hash: s.record.content_hash, title: clip(s.record.title, 160), home: s.home, operation: s.operation, durability: s.durability })),
31
+ refusals: tail(report.refusals, 30),
32
+ omitted: {
33
+ deliveries: Math.max(0, report.deliveries.length - deliveries), claims: Math.max(0, report.claims.length - 20), checks: Math.max(0, report.checks.length - 30),
34
+ conformance: Math.max(0, report.conformance.length - 50), saves: Math.max(0, report.saves.length - 30), refusals: Math.max(0, report.refusals.length - 30),
35
+ },
36
+ full_report: `hunch report ${report.task.task_id} --json`,
37
+ };
38
+ }
39
+ export function boundedTaskReportForHost(report) {
40
+ // Shrink deterministically until the summary fits; identities are never dropped
41
+ // from what remains, and `omitted` says exactly how much fell off.
42
+ for (const [deliveries, records] of [[30, 25], [10, 10], [5, 5], [1, 5], [1, 1]]) {
43
+ const summary = boundedTaskReport(report, deliveries, records);
44
+ if (JSON.stringify(summary).length <= MCP_REPORT_BYTE_BUDGET)
45
+ return summary;
46
+ }
47
+ const minimal = boundedTaskReport(report, 1, 1);
48
+ return { ...minimal, deliveries: [], omitted: { ...minimal.omitted, deliveries: report.deliveries.length } };
49
+ }
50
+ /** Reuse the MCP server's installation, not a potentially stale global binary.
51
+ * Structured argv is authoritative; the shell hint uses literal quoting. */
52
+ function verificationLauncher() {
53
+ const dev = import.meta.url.endsWith(".ts");
54
+ const entry = fileURLToPath(new URL(`../cli/index.${dev ? "ts" : "js"}`, import.meta.url));
55
+ const argv = [process.execPath, ...(dev ? ["--import", fileURLToPath(import.meta.resolve("tsx"))] : []), entry];
56
+ const quote = (s) => process.platform === "win32" ? `'${s.replace(/'/g, "''")}'` : `'${s.replace(/'/g, "'\\''")}'`;
57
+ return { argv, shell: `${process.platform === "win32" ? "& " : ""}${argv.map(quote).join(" ")}` };
58
+ }
59
+ export function registerTaskReportTools(server, getRoot, getStore) {
60
+ server.registerTool("hunch_task", {
61
+ title: "Start or finish a task's contribution report",
62
+ description: "Start once per user task; pass the returned task_id to hunch_context. Finish before your final response and include the returned concise contribution card, without asking the user. Applications are explicitly agent-reported and must name an exact delivered occurrence and record hash. Completion never implies successful verification. Not for storing decisions or claiming tests passed; use the CLI task verify wrapper for observed command results.",
63
+ inputSchema: {
64
+ action: z.enum(["start", "finish"]), task_id: TaskIdSchema.optional(),
65
+ title: z.string().min(1).max(200).optional(),
66
+ outcome: z.enum(["completed", "interrupted"]).optional(),
67
+ applications: z.array(ReportClaimSchema).max(20).optional(),
68
+ cwd: z.string().optional().describe("Actual repository/worktree directory for this task."),
69
+ },
70
+ }, async ({ action, task_id, title, outcome, applications }) => {
71
+ try {
72
+ const root = getRoot();
73
+ if (action === "start") {
74
+ if (!title || applications?.length || outcome)
75
+ throw new Error("start requires a short task title and no completion evidence");
76
+ const task = startReportTask(root, title, task_id);
77
+ const launcher = verificationLauncher();
78
+ return { content: [{ type: "text", text: `Task ${task.task_id} · ${task.state}. Pass task_id to every hunch_context and decision/correction/finding capture call. Before the final response, finish with hunch_task and include its contribution card. For checks use this exact installation (the global hunch binary may be stale): ${launcher.shell} task verify ${task.task_id} -- <command> [arguments]. The default budget is 2 minutes; add --timeout <seconds> before -- for a long suite.` }], structuredContent: { task, verification_argv: [...launcher.argv, "task", "verify", task.task_id, "--"] } };
79
+ }
80
+ if (!task_id)
81
+ throw new Error("finish requires the exact task_id");
82
+ for (const claim of applications ?? [])
83
+ recordReportClaim(root, task_id, claim);
84
+ // Hunch's own rule evaluation of the delivered lessons; the agent submits
85
+ // no verdict. Failure is disclosed by the report and never blocks finish.
86
+ if ((outcome ?? "completed") === "completed") {
87
+ try {
88
+ runReportConformance(root, getStore(), task_id);
89
+ }
90
+ catch { /* unknowns disclose it */ }
91
+ }
92
+ finishReportTask(root, task_id, outcome ?? "completed");
93
+ const report = readTaskReport(root, task_id, reportSourceSnapshot(root).hash);
94
+ const show = reportPresentationEnabled(root);
95
+ let file = null;
96
+ try {
97
+ file = writeTaskReportHtml(root, task_id);
98
+ }
99
+ catch { /* retained report remains inspectable through MCP */ }
100
+ const card = file ? renderTaskReport(report).replace(/^Evidence .*$/m, `Evidence [Open local report](<${file}>)`) : renderTaskReport(report);
101
+ return { content: [{ type: "text", text: show ? card : "Task report retained. Automatic presentation is disabled; omit the contribution card from the final response." }], structuredContent: { ...boundedTaskReportForHost(report), presentation_enabled: show, contribution_card: show ? card : null, report_path: file } };
102
+ }
103
+ catch (error) {
104
+ const message = `Task report unavailable: ${error.message}`;
105
+ // Some hosts show structuredContent instead of text blocks. Return exact
106
+ // recovery references there as well; never ask an agent to invent a hash.
107
+ let references = [];
108
+ if (task_id) {
109
+ try {
110
+ references = applicationReferences(readTaskReport(getRoot(), task_id));
111
+ }
112
+ catch { }
113
+ }
114
+ return { isError: true, content: [{ type: "text", text: message }], structuredContent: { error: message, application_references: references, recovery: "For a real application, copy an exact reference and add the action you took. Do not replace receipt prefixes or substitute a scope hash. For full evidence call hunch_report with the exact task ID." } };
115
+ }
116
+ });
117
+ server.registerTool("hunch_report", {
118
+ title: "Inspect the evidence for Hunch's contribution to a task",
119
+ description: "Read task reports: exact delivered memory, agent-reported applications, observed command results and explicit unknowns, as a bounded summary (identities and verdicts, not envelope text; the full report is `hunch report <id> --json`). Supply lesson for exact revision history across tasks. With neither task_id nor lesson, lists recent tasks without guessing which is yours. html writes a local private evidence view. Not a causal impact score, public export, or authority to execute verification commands.",
120
+ inputSchema: { task_id: TaskIdSchema.optional(), lesson: LessonReferenceSchema.optional().describe("Exact kind and record_id, optionally content_hash, to inspect retained appearances across tasks. Partial indexing requires refreshing before pagination."), before: z.number().int().positive().optional(), html: z.boolean().optional(), cwd: z.string().optional() },
121
+ }, async ({ task_id, lesson, before, html }) => {
122
+ try {
123
+ const root = getRoot();
124
+ if (lesson) {
125
+ if (task_id || html)
126
+ throw new Error("choose lesson history or a task report, not both");
127
+ const history = readLessonHistory(root, lesson, { before });
128
+ return { content: [{ type: "text", text: JSON.stringify(history) }], structuredContent: history };
129
+ }
130
+ if (before !== undefined)
131
+ throw new Error("before requires a lesson reference");
132
+ if (!task_id) {
133
+ if (html)
134
+ throw new Error("html requires an exact task_id");
135
+ const tasks = listReportTasks(root);
136
+ return { content: [{ type: "text", text: tasks.length ? tasks.map(t => `${t.task_id} · ${t.state} · ${t.title}`).join("\n") : "No task reports yet. Start with hunch_task." }], structuredContent: { tasks } };
137
+ }
138
+ const report = readTaskReport(root, task_id, reportSourceSnapshot(root).hash);
139
+ const file = html ? writeTaskReportHtml(root, task_id) : null;
140
+ return { content: [{ type: "text", text: `${renderTaskReport(report)}${file ? `\nLocal evidence view: ${file}` : ""}` }], structuredContent: { ...boundedTaskReportForHost(report), application_references: applicationReferences(report), contribution_card: renderTaskReport(report), report_path: file } };
141
+ }
142
+ catch (error) {
143
+ return { isError: true, content: [{ type: "text", text: `Invalid: ${error.message}` }] };
144
+ }
145
+ });
146
+ }
147
+ //# sourceMappingURL=taskReportTools.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `hunch serve` — the HTTP binding of nuryel.state/1, and the served product's partition host.
3
+ *
4
+ * Folded in from Hunch Memory: bind 127.0.0.1 (the caller's job; never expose a port), a
5
+ * bearer token that resolves the PRINCIPAL (the body never names one — grants come from the
6
+ * config, never from the caller), problem+json errors, a body limit, and a cross-process
7
+ * write lock per partition so a stdio MCP process on the same store cannot race a write.
8
+ *
9
+ * Every rule lives in src/store/stateBinding.ts; this file only maps HTTP to it:
10
+ * GET /nuryel/v1/capabilities → capabilities of the partition named by ?scope=kind:id (default: first granted)
11
+ * POST /nuryel/v1/read → readState; with `scopes` a UNION read: readState per granted
12
+ * served partition, merged by mergeReadResponses (primary's envelope)
13
+ * POST /nuryel/v1/write → writeState (under the partition's write lock)
14
+ * POST /nuryel/v1/subscribe → subscribeState
15
+ * POST /nuryel/v1/records → recordsState (by id, grants first)
16
+ * Request bodies are the contract's request schemas minus `schema` and `principal`.
17
+ */
18
+ import { type Server } from "node:http";
19
+ import { HunchStore } from "../store/hunchStore.js";
20
+ import { type ServeConfig } from "./config.js";
21
+ export declare const BODY_LIMIT_BYTES: number;
22
+ export declare const PROBLEM_TYPE = "https://www.hunchmemory.com/problems/nuryel.state/1/";
23
+ export declare class HttpProblem extends Error {
24
+ readonly status: number;
25
+ readonly code: string;
26
+ readonly extra: Record<string, unknown>;
27
+ constructor(status: number, code: string, message: string, extra?: Record<string, unknown>);
28
+ }
29
+ export interface ServeOptions {
30
+ version?: string;
31
+ /** Injectable for tests: how a partition's store is opened. */
32
+ openStore?: (root: string) => HunchStore;
33
+ }
34
+ export declare function createServeApp(config: ServeConfig, opts?: ServeOptions): Server & {
35
+ closeStores: () => void;
36
+ };