@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.
- package/README.md +2 -1
- package/dist/cli/automaticReviewMemory.d.ts +13 -0
- package/dist/cli/dna.d.ts +2 -0
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.js +606 -43
- package/dist/cli/integrations.d.ts +2 -0
- package/dist/cli/integrations.js +5 -2
- package/dist/cli/invocation.d.ts +35 -0
- package/dist/cli/preflight.d.ts +1 -0
- package/dist/cli/reviewMemory.d.ts +4 -0
- package/dist/cli/reviewMemoryProvider.d.ts +14 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/taskReport.d.ts +6 -0
- package/dist/cli/taskReport.js +128 -0
- package/dist/cli/update.d.ts +14 -0
- package/dist/client/state.d.ts +248 -0
- package/dist/constitution/adapters.d.ts +29 -0
- package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
- package/dist/constitution/behaviorEvaluator.d.ts +8 -0
- package/dist/constitution/behaviorProof.d.ts +9 -0
- package/dist/constitution/behaviorWorkspace.d.ts +25 -0
- package/dist/constitution/bootstrap.d.ts +30 -0
- package/dist/constitution/canonical.d.ts +15 -0
- package/dist/constitution/card.d.ts +58 -0
- package/dist/constitution/compiler.d.ts +52 -0
- package/dist/constitution/composition.d.ts +10 -0
- package/dist/constitution/corpus.d.ts +7 -0
- package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
- package/dist/constitution/delta.d.ts +4 -0
- package/dist/constitution/disposition.d.ts +20 -0
- package/dist/constitution/evaluator.d.ts +47 -0
- package/dist/constitution/experiment.d.ts +466 -0
- package/dist/constitution/experimentRunner.d.ts +7 -0
- package/dist/constitution/g2.d.ts +209 -0
- package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
- package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
- package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
- package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
- package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
- package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
- package/dist/constitution/g2Candidates.d.ts +80 -0
- package/dist/constitution/g2Drills.d.ts +33 -0
- package/dist/constitution/g3.d.ts +311 -0
- package/dist/constitution/g3Conformance.d.ts +33 -0
- package/dist/constitution/lifecycle.d.ts +25 -0
- package/dist/constitution/mutation.d.ts +15 -0
- package/dist/constitution/nodeTestEvidence.d.ts +13 -0
- package/dist/constitution/plan.d.ts +18 -0
- package/dist/constitution/policyRuntime.d.ts +5 -0
- package/dist/constitution/proof.d.ts +11 -0
- package/dist/constitution/repairPolicies.d.ts +34 -0
- package/dist/constitution/replacementFreeGit.d.ts +14 -0
- package/dist/constitution/replay.d.ts +40 -0
- package/dist/constitution/replayCache.d.ts +14 -0
- package/dist/constitution/replayWorker.d.ts +1 -0
- package/dist/constitution/repository.d.ts +129 -0
- package/dist/constitution/safeCheckout.d.ts +9 -0
- package/dist/constitution/schema.d.ts +1763 -0
- package/dist/constitution/scorecard.d.ts +219 -0
- package/dist/constitution/service.d.ts +398 -0
- package/dist/constitution/shadow.d.ts +54 -0
- package/dist/constitution/sourceMutation.d.ts +15 -0
- package/dist/constitution/staticGraphBaseline.d.ts +13 -0
- package/dist/constitution/structural.d.ts +64 -0
- package/dist/core/agenthook.d.ts +63 -0
- package/dist/core/agenthook.js +1 -0
- package/dist/core/automaticReviewMemory.d.ts +60 -0
- package/dist/core/autoreview.d.ts +66 -0
- package/dist/core/canonicalOrder.d.ts +3 -0
- package/dist/core/capturetoken.d.ts +19 -0
- package/dist/core/changeIdentity.d.ts +22 -0
- package/dist/core/changeProof.d.ts +14 -0
- package/dist/core/checkreport.d.ts +140 -0
- package/dist/core/commitrepair.d.ts +171 -0
- package/dist/core/commitrepair.js +238 -0
- package/dist/core/compare.d.ts +21 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/conformance.d.ts +35 -0
- package/dist/core/constraintmatch.d.ts +54 -0
- package/dist/core/correction.d.ts +42 -0
- package/dist/core/correctionStage.d.ts +224 -0
- package/dist/core/declarationClusters.d.ts +149 -0
- package/dist/core/delivery.d.ts +110 -0
- package/dist/core/docanchors.d.ts +32 -0
- package/dist/core/docscan.d.ts +26 -0
- package/dist/core/drift.d.ts +13 -0
- package/dist/core/drift.js +31 -1
- package/dist/core/dupdetect.d.ts +43 -0
- package/dist/core/escalations.d.ts +129 -0
- package/dist/core/escalations.js +165 -0
- package/dist/core/events.d.ts +29 -0
- package/dist/core/evidenceMap.d.ts +164 -0
- package/dist/core/externalImports.d.ts +8 -0
- package/dist/core/format.d.ts +13 -0
- package/dist/core/glob.d.ts +9 -0
- package/dist/core/groundingLag.d.ts +84 -0
- package/dist/core/groundingLag.js +16 -2
- package/dist/core/groundingMerge.d.ts +15 -0
- package/dist/core/groundingMerge.js +95 -0
- package/dist/core/hookObservations.d.ts +9 -0
- package/dist/core/hookObservations.js +33 -0
- package/dist/core/hookcache.d.ts +10 -0
- package/dist/core/hookpolicy.d.ts +47 -0
- package/dist/core/ids.d.ts +36 -0
- package/dist/core/importReview.d.ts +30 -0
- package/dist/core/io.d.ts +23 -0
- package/dist/core/jsonc.d.ts +4 -0
- package/dist/core/landscapeAdoption.d.ts +66 -0
- package/dist/core/landscapeDelivery.d.ts +76 -0
- package/dist/core/memorylog.d.ts +37 -0
- package/dist/core/migrate.d.ts +28 -0
- package/dist/core/outcomeExperience.d.ts +89 -0
- package/dist/core/overlaySafety.d.ts +24 -0
- package/dist/core/overlaySafety.js +7 -1
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/pipeline.d.ts +289 -0
- package/dist/core/premises.d.ts +43 -0
- package/dist/core/provenance.d.ts +21 -0
- package/dist/core/publication.d.ts +54 -0
- package/dist/core/refrepair.d.ts +24 -0
- package/dist/core/refrepair.js +1 -1
- package/dist/core/relativeImports.d.ts +11 -0
- package/dist/core/repair.d.ts +44 -0
- package/dist/core/repairqueue.d.ts +40 -0
- package/dist/core/repairqueue.js +133 -0
- package/dist/core/reviewMemory.d.ts +40 -0
- package/dist/core/reviewqueue.d.ts +54 -0
- package/dist/core/safeRepoFile.d.ts +20 -0
- package/dist/core/served.d.ts +57 -0
- package/dist/core/served.js +14 -0
- package/dist/core/stateContract.d.ts +979 -0
- package/dist/core/stateDelivery.d.ts +78 -0
- package/dist/core/stateRecords.d.ts +383 -0
- package/dist/core/stats.d.ts +88 -0
- package/dist/core/strictgate.d.ts +33 -0
- package/dist/core/taskReport.d.ts +226 -0
- package/dist/core/taskReport.js +514 -0
- package/dist/core/taskReportCapture.d.ts +8 -0
- package/dist/core/taskReportCapture.js +36 -0
- package/dist/core/taskReportEvidence.d.ts +28 -0
- package/dist/core/taskReportEvidence.js +272 -0
- package/dist/core/taskReportHook.d.ts +20 -0
- package/dist/core/taskReportHook.js +76 -0
- package/dist/core/taskReportPaths.d.ts +3 -0
- package/dist/core/taskReportPaths.js +23 -0
- package/dist/core/taskReportPublic.d.ts +31 -0
- package/dist/core/taskReportPublic.js +45 -0
- package/dist/core/taskReportRender.d.ts +6 -0
- package/dist/core/taskReportRender.js +97 -0
- package/dist/core/topics.d.ts +73 -0
- package/dist/core/types.d.ts +1239 -0
- package/dist/core/version.d.ts +10 -0
- package/dist/eval/guards.d.ts +45 -0
- package/dist/eval/harness.d.ts +58 -0
- package/dist/extractors/adrImport.d.ts +59 -0
- package/dist/extractors/comments.d.ts +7 -0
- package/dist/extractors/correctionSources.d.ts +11 -0
- package/dist/extractors/diff.d.ts +40 -0
- package/dist/extractors/git.d.ts +304 -0
- package/dist/extractors/git.js +208 -20
- package/dist/extractors/helm.d.ts +31 -0
- package/dist/extractors/indexer.d.ts +55 -0
- package/dist/extractors/landscapeDiscovery.d.ts +41 -0
- package/dist/extractors/languages.d.ts +77 -0
- package/dist/extractors/nativeTreeSitter.d.ts +16 -0
- package/dist/extractors/parse.d.ts +54 -0
- package/dist/extractors/php.d.ts +26 -0
- package/dist/extractors/repoSource.d.ts +53 -0
- package/dist/extractors/testreport.d.ts +36 -0
- package/dist/integrations/ciAction.d.ts +8 -0
- package/dist/integrations/claudeConfig.d.ts +44 -0
- package/dist/integrations/claudemd.d.ts +13 -0
- package/dist/integrations/claudemd.js +11 -2
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +7 -0
- package/dist/integrations/health.d.ts +74 -0
- package/dist/integrations/health.js +32 -2
- package/dist/integrations/hooks.d.ts +41 -0
- package/dist/integrations/hooks.js +92 -66
- package/dist/integrations/madrExport.d.ts +39 -0
- package/dist/integrations/madrManifest.d.ts +69 -0
- package/dist/integrations/mergeDriver.d.ts +3 -0
- package/dist/integrations/mergeDriver.js +25 -8
- package/dist/integrations/probe.d.ts +4 -0
- package/dist/integrations/providers.d.ts +103 -0
- package/dist/integrations/scaffold.d.ts +29 -0
- package/dist/integrations/sync.d.ts +30 -0
- package/dist/integrations/sync.js +3 -2
- package/dist/integrations/team.d.ts +70 -0
- package/dist/integrations/worktree.d.ts +8 -0
- package/dist/mcp/roots.d.ts +16 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.js +74 -22
- package/dist/mcp/taskReportTools.d.ts +188 -0
- package/dist/mcp/taskReportTools.js +147 -0
- package/dist/serve/app.d.ts +36 -0
- package/dist/serve/config.d.ts +100 -0
- package/dist/serve/writelock.d.ts +22 -0
- package/dist/store/changeLedger.d.ts +132 -0
- package/dist/store/compact.d.ts +33 -0
- package/dist/store/db.d.ts +9 -0
- package/dist/store/embedder.d.ts +32 -0
- package/dist/store/hunchStore.d.ts +624 -0
- package/dist/store/hunchStore.js +6 -3
- package/dist/store/jsonStore.d.ts +125 -0
- package/dist/store/merge.d.ts +39 -0
- package/dist/store/privateMigrate.d.ts +24 -0
- package/dist/store/replay.d.ts +56 -0
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/stateBinding.d.ts +145 -0
- package/dist/store/stateCapture.d.ts +15 -0
- package/dist/synthesis/cliAdapter.d.ts +22 -0
- package/dist/synthesis/initiator.d.ts +13 -0
- package/dist/synthesis/provider.d.ts +307 -0
- package/dist/synthesis/synthesize.d.ts +117 -0
- package/dist/synthesis/tripwires.d.ts +9 -0
- package/dist/taskReports.d.ts +79 -0
- package/dist/taskReports.js +69 -0
- package/dist/wiki/adopt.d.ts +22 -0
- package/dist/wiki/graph.d.ts +87 -0
- package/dist/wiki/wiki.d.ts +242 -0
- package/package.json +6 -5
- package/server.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
1124
|
+
return deliver(envelope);
|
|
1102
1125
|
}
|
|
1103
1126
|
}
|
|
1104
1127
|
const envelope = buildDeliveryEnvelope(ctx, options);
|
|
1105
|
-
return
|
|
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
|
-
|
|
1171
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|