@davesheffer/hunch 1.35.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +5 -1
- package/dist/core/spawnCommand.d.ts +14 -0
- package/dist/core/spawnCommand.js +61 -0
- package/dist/core/taskDelivery.d.ts +15 -0
- package/dist/core/taskDelivery.js +38 -0
- package/dist/core/taskReportEvidence.js +16 -2
- package/dist/core/taskReportHook.js +5 -9
- package/dist/integrations/claudemd.js +1 -1
- package/dist/mcp/server.js +6 -1
- package/dist/mcp/taskReportTools.js +4 -7
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -66,6 +66,7 @@ import { scaffoldProviders, regenerateGrounding, refreshExistingGrounding, refre
|
|
|
66
66
|
import { healClaudeConfigCaseSplit } from "../integrations/claudeConfig.js";
|
|
67
67
|
import { formatSearchHit, formatStructure } from "../core/format.js";
|
|
68
68
|
import { isStateKind, renderStateLine, stateSupplements } from "../core/stateDelivery.js";
|
|
69
|
+
import { taskSupplements } from "../core/taskDelivery.js";
|
|
69
70
|
import { diagnoseIssueCorrectionStage, formatCorrectionStageDiagnostic } from "../core/correctionStage.js";
|
|
70
71
|
import { compileVerifiedEvidenceMap, formatVerifiedEvidenceMap } from "../core/evidenceMap.js";
|
|
71
72
|
import { collectCorrectionStageSources } from "../extractors/correctionSources.js";
|
|
@@ -4210,7 +4211,7 @@ program
|
|
|
4210
4211
|
decisionCorpus: store.recs("decisions"),
|
|
4211
4212
|
historical: !!asOf,
|
|
4212
4213
|
profile: opts.profile,
|
|
4213
|
-
supplements: stateGrounding,
|
|
4214
|
+
supplements: [...stateGrounding, ...(asOf ? [] : taskSupplements(store.tasksFor(target, 3), target))],
|
|
4214
4215
|
});
|
|
4215
4216
|
process.stdout.write(envelope.text);
|
|
4216
4217
|
if (opts.task) {
|
|
@@ -4880,6 +4881,7 @@ program
|
|
|
4880
4881
|
// from this file. No diff exists yet, so this is context — "don't re-add X" —
|
|
4881
4882
|
// not a block; the commit-time `hunch check` does the actual gating.
|
|
4882
4883
|
const retired = store.retiredForFile(target).filter((r) => r.symbols.length || r.deps.length);
|
|
4884
|
+
const recentTasks = taskSupplements(store.tasksFor(target, 3), target);
|
|
4883
4885
|
const hasContent = ctx.constraints.length ||
|
|
4884
4886
|
ctx.decisions.length ||
|
|
4885
4887
|
ctx.bugs.length ||
|
|
@@ -4888,6 +4890,7 @@ program
|
|
|
4888
4890
|
ctx.landscape?.resources.length ||
|
|
4889
4891
|
ctx.landscape?.relationships.length ||
|
|
4890
4892
|
retired.length ||
|
|
4893
|
+
recentTasks.length ||
|
|
4891
4894
|
docGround;
|
|
4892
4895
|
if (!hasContent)
|
|
4893
4896
|
return; // no noise on files Hunch hasn't learned yet
|
|
@@ -4905,6 +4908,7 @@ program
|
|
|
4905
4908
|
text: `⚠ Deliberately RETIRED from this file — do not re-introduce without cause: ${retired.map((r) => `${[...r.symbols, ...r.deps].join(", ")} (${r.decision})`).join("; ")}.`,
|
|
4906
4909
|
}] : []),
|
|
4907
4910
|
...(docGround ? [{ id: "doc-grounding", kind: "doc-grounding", priority: 100, text: docGround }] : []),
|
|
4911
|
+
...recentTasks,
|
|
4908
4912
|
],
|
|
4909
4913
|
});
|
|
4910
4914
|
const text = envelope.text.trim();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ResolvedSpawn {
|
|
2
|
+
file: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
/** Set when a batch launcher runs through cmd.exe and the line is pre-quoted. */
|
|
5
|
+
windowsVerbatimArguments?: boolean;
|
|
6
|
+
how: "direct" | "npm-cli" | "pathext" | "cmd-shim";
|
|
7
|
+
}
|
|
8
|
+
export interface SpawnResolveOptions {
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
execPath?: string;
|
|
12
|
+
exists?: (path: string) => boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveSpawnCommand(command: readonly string[], options?: SpawnResolveOptions): ResolvedSpawn;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Resolve a user-supplied argv into something `spawn` can run without a shell.
|
|
2
|
+
*
|
|
3
|
+
* On POSIX the argv is already right. On Windows, `spawn("npx", ...)` with
|
|
4
|
+
* `shell: false` fails: the launcher is `npx.cmd`, and Node refuses to run
|
|
5
|
+
* `.cmd`/`.bat` files directly. The verification runner used to swallow that
|
|
6
|
+
* as `exit_code: null`, so every contribution card on Windows said "no
|
|
7
|
+
* independent command result". This keeps `shell: false` for real
|
|
8
|
+
* executables and only routes batch launchers through `cmd.exe`, with the
|
|
9
|
+
* npm/npx launchers run as plain Node scripts (no shell at all). */
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { posix, win32 } from "node:path";
|
|
12
|
+
/** cmd.exe quoting for one argument: wrap when it has whitespace or shell
|
|
13
|
+
* metacharacters; double embedded quotes. Good for test/build commands; a
|
|
14
|
+
* deliberately hostile argument still cannot escape because the whole line is
|
|
15
|
+
* passed as one `/s /c "..."` token. */
|
|
16
|
+
function quoteForCmd(arg) {
|
|
17
|
+
if (arg === "")
|
|
18
|
+
return '""';
|
|
19
|
+
if (!/[\s"&|<>^()%!]/.test(arg))
|
|
20
|
+
return arg;
|
|
21
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
22
|
+
}
|
|
23
|
+
export function resolveSpawnCommand(command, options = {}) {
|
|
24
|
+
const platform = options.platform ?? process.platform;
|
|
25
|
+
const [cmd = "", ...args] = command;
|
|
26
|
+
if (platform !== "win32")
|
|
27
|
+
return { file: cmd, args, how: "direct" };
|
|
28
|
+
const env = options.env ?? process.env;
|
|
29
|
+
const exists = options.exists ?? existsSync;
|
|
30
|
+
const execPath = options.execPath ?? process.execPath;
|
|
31
|
+
// Resolve Windows paths with Windows semantics even when the resolution is
|
|
32
|
+
// exercised (tested) on another platform; the host's default `path` is POSIX there.
|
|
33
|
+
const { join, dirname } = platform === "win32" ? win32 : posix;
|
|
34
|
+
// npm / npx: run the CLI script with this same Node. No shim, no shell.
|
|
35
|
+
if (/^(npm|npx)$/i.test(cmd)) {
|
|
36
|
+
const script = join(dirname(execPath), "node_modules", "npm", "bin", `${cmd.toLowerCase()}-cli.js`);
|
|
37
|
+
if (exists(script))
|
|
38
|
+
return { file: execPath, args: [script, ...args], how: "npm-cli" };
|
|
39
|
+
}
|
|
40
|
+
// A path or an explicit executable extension: spawn as given.
|
|
41
|
+
if (/[\\/]/.test(cmd) || /\.(exe|com)$/i.test(cmd))
|
|
42
|
+
return { file: cmd, args, how: "direct" };
|
|
43
|
+
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
44
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(";").map((d) => d.trim()).filter(Boolean);
|
|
45
|
+
for (const dir of dirs) {
|
|
46
|
+
for (const ext of ["", ...pathExt]) {
|
|
47
|
+
const candidate = join(dir, cmd + ext);
|
|
48
|
+
if (!exists(candidate))
|
|
49
|
+
continue;
|
|
50
|
+
if (/\.(cmd|bat)$/i.test(candidate)) {
|
|
51
|
+
const line = [candidate, ...args].map(quoteForCmd).join(" ");
|
|
52
|
+
return { file: env.ComSpec ?? "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true, how: "cmd-shim" };
|
|
53
|
+
}
|
|
54
|
+
if (ext === "" && !/\.(exe|com)$/i.test(candidate))
|
|
55
|
+
continue; // an extensionless file is not runnable on Windows
|
|
56
|
+
return { file: candidate, args, how: "pathext" };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { file: cmd, args, how: "direct" };
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=spawnCommand.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Recent finished tasks as delivered context.
|
|
2
|
+
*
|
|
3
|
+
* Task records (`.hunch/tasks/`) say what earlier agent work did around a file:
|
|
4
|
+
* which lessons it received, what it applied, saved and checked, and whether a
|
|
5
|
+
* rule was violated. Delivering the newest few next to the invariants lets the
|
|
6
|
+
* next agent build on verified work instead of rediscovering it. Supplements
|
|
7
|
+
* share the brief's budget and are advisory: a task line is history, never a
|
|
8
|
+
* rule, and never an instruction to repeat or skip anything. */
|
|
9
|
+
import type { DeliverySupplement } from "./delivery.js";
|
|
10
|
+
import type { TaskRecord } from "./types.js";
|
|
11
|
+
export declare const TASK_SUPPLEMENT_LIMIT = 3;
|
|
12
|
+
/** One bounded line for a task: identity, when, what reached it, what it did. */
|
|
13
|
+
export declare function describeTaskRecord(t: TaskRecord): string;
|
|
14
|
+
/** Newest first, bounded. Empty input yields no supplement at all (no header noise). */
|
|
15
|
+
export declare function taskSupplements(tasks: readonly TaskRecord[], target: string, limit?: number): DeliverySupplement[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const TASK_SUPPLEMENT_LIMIT = 3;
|
|
2
|
+
function clip(text, max) {
|
|
3
|
+
return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}…`;
|
|
4
|
+
}
|
|
5
|
+
/** One bounded line for a task: identity, when, what reached it, what it did. */
|
|
6
|
+
export function describeTaskRecord(t) {
|
|
7
|
+
const when = t.finished_at.slice(0, 10);
|
|
8
|
+
const lessons = t.lessons.length
|
|
9
|
+
? `${t.lessons.length} lesson(s): ${t.lessons.slice(0, 3).map((l) => l.record_id).join(", ")}${t.lessons.length > 3 ? "…" : ""}`
|
|
10
|
+
: "no memory delivered";
|
|
11
|
+
const applied = t.applied.length
|
|
12
|
+
? `applied ${t.applied.length} (${t.applied.some((a) => a.supported_by) ? "rule-supported" : "agent-reported"})`
|
|
13
|
+
: null;
|
|
14
|
+
const saved = t.saved.length ? `saved ${t.saved.slice(0, 3).map((s) => s.record_id).join(", ")}${t.saved.length > 3 ? "…" : ""}` : null;
|
|
15
|
+
const last = t.checks.at(-1);
|
|
16
|
+
const check = last ? `check "${clip(last.label, 40)}" ${last.state}` : "no check recorded";
|
|
17
|
+
const violated = t.conformance.some((c) => c.outcome === "violated") ? "RULE VIOLATED" : null;
|
|
18
|
+
const denied = t.refusals ? `${t.refusals} edit(s) denied` : null;
|
|
19
|
+
const files = t.files.length ? `files ${t.files.slice(0, 4).join(", ")}${t.files.length > 4 ? "…" : ""}` : null;
|
|
20
|
+
return `${t.id} · ${when} · ${t.state} · "${clip(t.title, 80)}" — ${[lessons, applied, saved, check, violated, denied, files].filter(Boolean).join(" · ")}`;
|
|
21
|
+
}
|
|
22
|
+
/** Newest first, bounded. Empty input yields no supplement at all (no header noise). */
|
|
23
|
+
export function taskSupplements(tasks, target, limit = TASK_SUPPLEMENT_LIMIT) {
|
|
24
|
+
const recent = [...tasks]
|
|
25
|
+
.sort((a, b) => b.finished_at.localeCompare(a.finished_at) || a.id.localeCompare(b.id))
|
|
26
|
+
.slice(0, Math.max(1, limit));
|
|
27
|
+
if (!recent.length)
|
|
28
|
+
return [];
|
|
29
|
+
const older = tasks.length - recent.length;
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
id: "recent-tasks", kind: "recent-tasks", priority: 415,
|
|
33
|
+
text: `RECENT TASKS on ${target} — earlier agent work here, from graph memory (advisory history, not rules): build on what was verified instead of redoing it blind.${older > 0 ? ` ${older} older task(s) not shown; hunch task list.` : ""}`,
|
|
34
|
+
},
|
|
35
|
+
...recent.map((t, i) => ({ id: t.id, kind: "recent-task", priority: 414 - i, text: describeTaskRecord(t) })),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=taskDelivery.js.map
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
3
3
|
import { lstatSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
|
|
4
4
|
import { canonicalReportRoot } from "./taskReportPaths.js";
|
|
5
|
+
import { resolveSpawnCommand } from "./spawnCommand.js";
|
|
5
6
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
6
7
|
import { analyzeDiff } from "../extractors/diff.js";
|
|
7
8
|
import { workingDiff, workingFiles } from "../extractors/git.js";
|
|
@@ -209,7 +210,11 @@ export async function runReportCheck(root, taskId, command, label, timeoutMs = 1
|
|
|
209
210
|
ReportCheckSchema.parse({ label, command, exit_code: null, output_hash: reportHash(""), before_snapshot: before.hash, after_snapshot: null, snapshot_limitations: before.limitations, timed_out: false, source: "local-command-runner" });
|
|
210
211
|
const checkId = beginReportCheck(root, taskId, label);
|
|
211
212
|
const result = await new Promise((resolveResult) => {
|
|
212
|
-
|
|
213
|
+
// Windows launchers (npx.cmd, npm.cmd, other .cmd/.bat shims) cannot be spawned
|
|
214
|
+
// without a shell; resolve them first so a check actually runs instead of
|
|
215
|
+
// silently recording exit_code null (fnd: every Windows card said "no result").
|
|
216
|
+
const resolved = resolveSpawnCommand(command);
|
|
217
|
+
const child = spawn(resolved.file, resolved.args, { cwd: root, shell: false, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: process.platform !== "win32", windowsVerbatimArguments: resolved.windowsVerbatimArguments === true });
|
|
213
218
|
const stdout = createHash("sha256"), stderr = createHash("sha256");
|
|
214
219
|
let timedOut = false, cancelled = false, settled = false;
|
|
215
220
|
let cleanupTimer;
|
|
@@ -258,7 +263,16 @@ export async function runReportCheck(root, taskId, command, label, timeoutMs = 1
|
|
|
258
263
|
stderr.update(chunk);
|
|
259
264
|
options.onStderr?.(chunk);
|
|
260
265
|
} });
|
|
261
|
-
child.once("error", () =>
|
|
266
|
+
child.once("error", (error) => {
|
|
267
|
+
// A launch failure is a result the user must see (ENOENT is the common
|
|
268
|
+
// one); it is hashed like any other stderr and streamed to the caller.
|
|
269
|
+
const message = Buffer.from(`hunch: could not start ${JSON.stringify(command[0])}: ${error.message}\n`);
|
|
270
|
+
if (!settled) {
|
|
271
|
+
stderr.update(message);
|
|
272
|
+
options.onStderr?.(message);
|
|
273
|
+
}
|
|
274
|
+
settle(null);
|
|
275
|
+
});
|
|
262
276
|
child.once("close", code => settle(code));
|
|
263
277
|
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
264
278
|
if (options.signal?.aborted)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Native lifecycle coverage is independent of whether a model follows reporting
|
|
2
2
|
* instructions. Only an authoritative prompt identity may join its evidence. */
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
3
|
import { readFileSync } from "node:fs";
|
|
5
4
|
import { join } from "node:path";
|
|
6
5
|
import { findRoot } from "./paths.js";
|
|
@@ -8,7 +7,7 @@ import { canonicalReportRoot } from "./taskReportPaths.js";
|
|
|
8
7
|
import { isCredentialFreeText } from "./types.js";
|
|
9
8
|
import { isEmptyTaskReport, readTaskReport, recordReportRefusal, reportHash, reportPresentationEnabled, startReportTask } from "./taskReport.js";
|
|
10
9
|
import { reportSourceSnapshot } from "./taskReportEvidence.js";
|
|
11
|
-
import { renderTaskReport
|
|
10
|
+
import { renderTaskReport } from "./taskReportRender.js";
|
|
12
11
|
/** The exact task identity a native host prompt maps to. */
|
|
13
12
|
export function promptTaskId(root, sessionId, promptId, agentId = null, provider = "claude") {
|
|
14
13
|
return `htask_${reportHash([canonicalReportRoot(root), provider, sessionId, promptId, agentId]).slice(7, 31)}`;
|
|
@@ -131,13 +130,10 @@ export function stopHookReport(root, provider, event) {
|
|
|
131
130
|
const report = readTaskReport(root, id, reportSourceSnapshot(root).hash);
|
|
132
131
|
if (isEmptyTaskReport(report))
|
|
133
132
|
return null;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
catch { /* exact CLI evidence link remains available */ }
|
|
140
|
-
return { systemMessage: card };
|
|
133
|
+
// The HTML evidence view is a rendering of the local ledger, generated on
|
|
134
|
+
// demand (`hunch report <id> --html`, or a click in the VS Code view). The
|
|
135
|
+
// graph record is the durable memory; no file is written per prompt.
|
|
136
|
+
return { systemMessage: renderTaskReport(report) };
|
|
141
137
|
}
|
|
142
138
|
catch {
|
|
143
139
|
return { systemMessage: `Hunch report unavailable for ${id}. Contribution is unverified; inspect with hunch report ${id}.` };
|
|
@@ -75,7 +75,7 @@ export function renderHunchSection(store, root) {
|
|
|
75
75
|
lines.push("- When running a relevant check, use the exact verification_argv launcher returned by hunch_task start, followed by the check command and its arguments, from this worktree. It runs `hunch task verify <task_id> -- <command> [arguments]` using the same installation as MCP, avoiding stale global binaries. This retains the actual exit result and source snapshot; raw output is not stored. Do not rerun an expensive check solely for reporting; missing evidence stays unverified.");
|
|
76
76
|
lines.push("- Include the current task_id when calling hunch_record_decision, hunch_record_correction, or hunch_record_finding. The save path records its actual memory home and verifies exact Git revisions when committing or pushing; never infer publication from a successful capture alone.");
|
|
77
77
|
lines.push("- Before claiming an application, call `hunch_report(task_id)` and copy the exact occurrence_id, record_id and content_hash from application_references, adding an action you actually took. Never derive an occurrence ID by replacing a receipt prefix or use the task's scope hash as a record hash. If you did not apply a lesson, omit applications.");
|
|
78
|
-
lines.push("- Call `hunch_task(action: \"finish\", task_id, applications?)` and include the returned contribution_card in your final response without the user asking.
|
|
78
|
+
lines.push("- Call `hunch_task(action: \"finish\", task_id, applications?)` and include the returned contribution_card in your final response without the user asking. Copy the card verbatim, including its Evidence line (the command that renders the local report on demand) and the agent-reported label; the structured result contains the card even when the host hides text blocks. Do not replace it with a generic claim that Hunch helped. If presentation_enabled is false, omit the card. A delivered lesson or passing command alone does not prove causal impact.");
|
|
79
79
|
lines.push("- If interrupted, finish with `outcome: \"interrupted\"` when possible. `hunch_report(task_id, html: true)` opens the evidence trail by generating a local file; it may contain private memory and is not a public export. If report tools are unavailable after an update, say so and reconnect the host rather than inventing a report.");
|
|
80
80
|
lines.push("");
|
|
81
81
|
lines.push("**Build the Constitution review queue:**");
|
package/dist/mcp/server.js
CHANGED
|
@@ -32,6 +32,7 @@ import { withWriteLock } from "../serve/writelock.js";
|
|
|
32
32
|
import { advertisedTeamRemoteContract, ensureTeamOverlay, overlayMatchesTeamRemote, readTeamConfig, teamRemoteContract, teamSharedRef } from "../integrations/team.js";
|
|
33
33
|
import { formatSearchHit, formatStructure } from "../core/format.js";
|
|
34
34
|
import { isStateKind, stateSupplements } from "../core/stateDelivery.js";
|
|
35
|
+
import { taskSupplements } from "../core/taskDelivery.js";
|
|
35
36
|
import { diagnoseIssueCorrectionStage, formatCorrectionStageDiagnostic } from "../core/correctionStage.js";
|
|
36
37
|
import { compileVerifiedEvidenceMap, EvidenceExecutionSchema, EvidenceInterventionSchema, EvidenceProbeSchema, formatVerifiedEvidenceMap, VerifiedEvidenceReceiptSchema, } from "../core/evidenceMap.js";
|
|
37
38
|
import { collectCorrectionStageSources } from "../extractors/correctionSources.js";
|
|
@@ -1101,6 +1102,9 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1101
1102
|
// latest receipts whose subject/text matches the target — bounded, ordered, sharing the
|
|
1102
1103
|
// brief's budget as supplements. Withheld on time-travel: state records carry no as-of view.
|
|
1103
1104
|
const stateGrounding = asOf ? [] : stateSupplements(store.stateSlice(target), target);
|
|
1105
|
+
// Recent finished tasks that touched the target: what earlier agent work did
|
|
1106
|
+
// here, from graph memory. Advisory history sharing the brief's budget.
|
|
1107
|
+
const recentTasks = asOf ? [] : taskSupplements(store.tasksFor(target, 3), target);
|
|
1104
1108
|
const options = {
|
|
1105
1109
|
root,
|
|
1106
1110
|
symbols: store.recs("symbols"),
|
|
@@ -1108,7 +1112,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1108
1112
|
decisionCorpus: store.recs("decisions"),
|
|
1109
1113
|
historical: !!asOf,
|
|
1110
1114
|
profile: profile ?? "builder",
|
|
1111
|
-
supplements: [...(dnaSupplement ? [dnaSupplement] : []), ...stateGrounding, ...(asOf ? [] : conventionSupplements(store.recs("conventions")))],
|
|
1115
|
+
supplements: [...(dnaSupplement ? [dnaSupplement] : []), ...stateGrounding, ...recentTasks, ...(asOf ? [] : conventionSupplements(store.recs("conventions")))],
|
|
1112
1116
|
};
|
|
1113
1117
|
// Task-phrase input ("improve retrieval ranking") resolves no file/symbol and
|
|
1114
1118
|
// used to return an empty brief while the graph held the answer — fall back to
|
|
@@ -1136,6 +1140,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1136
1140
|
supplements: [
|
|
1137
1141
|
...(dnaSupplement ? [dnaSupplement] : []),
|
|
1138
1142
|
...stateGrounding,
|
|
1143
|
+
...recentTasks,
|
|
1139
1144
|
...hits
|
|
1140
1145
|
// State hits are delivered through the State section above, not as raw search lines.
|
|
1141
1146
|
.filter((hit) => !["constraints", "decisions", "bugs", "findings"].includes(hit.kind) && !isStateKind(hit.kind))
|
|
@@ -121,13 +121,10 @@ export function registerTaskReportTools(server, getRoot, getStore) {
|
|
|
121
121
|
}
|
|
122
122
|
const report = readTaskReport(root, task_id, reportSourceSnapshot(root).hash);
|
|
123
123
|
const show = reportPresentationEnabled(root);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
catch { /* retained report remains inspectable through MCP */ }
|
|
129
|
-
const card = (file ? renderTaskReport(report).replace(/^Evidence .*$/m, `Evidence [Open local report](<${file}>)`) : renderTaskReport(report)) + graphNote;
|
|
130
|
-
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, graph_record: graph } };
|
|
124
|
+
// The HTML evidence view is rendered on demand (hunch_report(html: true),
|
|
125
|
+
// `hunch report <id> --html`, or the VS Code view); finish writes no file.
|
|
126
|
+
const card = renderTaskReport(report) + graphNote;
|
|
127
|
+
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: null, graph_record: graph } };
|
|
131
128
|
}
|
|
132
129
|
catch (error) {
|
|
133
130
|
const message = `Task report unavailable: ${error.message}`;
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://www.hunchmemory.com",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.36.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@davesheffer/hunch",
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.36.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|