@davesheffer/hunch 1.33.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/README.md +9 -1
- package/dist/cli/index.js +55 -7
- package/dist/cli/invocation.d.ts +8 -0
- package/dist/cli/invocation.js +17 -10
- package/dist/cli/taskReport.js +52 -4
- package/dist/cli/update.js +5 -5
- package/dist/client/state.d.ts +4 -4
- package/dist/constitution/schema.d.ts +12 -12
- package/dist/core/spawnCommand.d.ts +14 -0
- package/dist/core/spawnCommand.js +61 -0
- package/dist/core/stateContract.d.ts +3 -3
- package/dist/core/taskDelivery.d.ts +15 -0
- package/dist/core/taskDelivery.js +38 -0
- package/dist/core/taskRecord.d.ts +39 -0
- package/dist/core/taskRecord.js +185 -0
- package/dist/core/taskReport.d.ts +8 -1
- package/dist/core/taskReport.js +28 -14
- package/dist/core/taskReportEvidence.js +18 -3
- package/dist/core/taskReportHook.d.ts +18 -3
- package/dist/core/taskReportHook.js +82 -17
- package/dist/core/taskReportPaths.d.ts +6 -0
- package/dist/core/taskReportPaths.js +13 -0
- package/dist/core/types.d.ts +176 -1
- package/dist/core/types.js +40 -1
- package/dist/core/updatecheck.d.ts +51 -0
- package/dist/core/updatecheck.js +266 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.js +3 -1
- package/dist/integrations/claudemd.js +1 -1
- package/dist/integrations/gitignore.js +1 -0
- package/dist/integrations/health.js +27 -2
- package/dist/mcp/server.js +11 -2
- package/dist/mcp/taskReportTools.d.ts +4 -4
- package/dist/mcp/taskReportTools.js +34 -8
- package/dist/store/hunchStore.d.ts +5 -1
- package/dist/store/hunchStore.js +18 -0
- package/dist/taskReports.d.ts +1 -1
- package/dist/taskReports.js +16 -4
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -164,14 +164,39 @@ export function inspectIntegrations(root, selected) {
|
|
|
164
164
|
continue;
|
|
165
165
|
const capabilities = Object.fromEntries(CAPABILITIES.map(c => [c, { status: "untested", detail: "No runtime evidence" }]));
|
|
166
166
|
report.harnesses.push({ harness, capabilities });
|
|
167
|
+
const mcpPath = join(root, spec.mcp);
|
|
168
|
+
const hooksFileExists = Boolean(spec.hooks && existsSync(join(root, spec.hooks)));
|
|
169
|
+
let mcpEntryAbsent = false;
|
|
170
|
+
try {
|
|
171
|
+
lstatSync(mcpPath);
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
mcpEntryAbsent = e.code === "ENOENT";
|
|
175
|
+
}
|
|
167
176
|
try {
|
|
168
177
|
const launcher = readLauncher(root, harness);
|
|
169
178
|
recordPins(spec.mcp, [launcher.command, ...launcher.args]);
|
|
170
179
|
capabilities.mcp.detail = "Configured locally; use --probe to verify a fresh server, then reconnect the host";
|
|
171
180
|
}
|
|
172
181
|
catch (e) {
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
// A harness can be detected here via its hooks file alone —
|
|
183
|
+
// some hooks files are deliberately committed while their MCP config is
|
|
184
|
+
// a per-clone, gitignored scaffold (e.g. this repo's own
|
|
185
|
+
// .windsurf/hooks.json). On a fresh checkout that config simply doesn't
|
|
186
|
+
// exist yet, which is a "not configured on this machine" state, not a
|
|
187
|
+
// repository-level misconfiguration — it must stay `untested`
|
|
188
|
+
// (informational, matching every other not-yet-evidenced capability
|
|
189
|
+
// here), not a hard `issues` entry that fails `hunch doctor` on every
|
|
190
|
+
// clone forever. A file that EXISTS but is malformed/disabled/
|
|
191
|
+
// unreadable in some other way is still a genuine issue.
|
|
192
|
+
// lstat distinguishes a truly absent per-machine file from a dangling
|
|
193
|
+
// symlink. The latter is a broken configuration and must remain loud.
|
|
194
|
+
const notConfiguredHere = !selected && hooksFileExists && mcpEntryAbsent && e.code === "ENOENT";
|
|
195
|
+
if (!notConfiguredHere)
|
|
196
|
+
report.issues.push({ file: spec.mcp, code: "mcp-config", detail: e.message });
|
|
197
|
+
capabilities.mcp.detail = notConfiguredHere
|
|
198
|
+
? "Not configured on this machine — a hooks file exists, but no local MCP config exists yet; run `hunch init` or set up this host"
|
|
199
|
+
: "MCP configuration disabled, invalid, or outside supported inspection format";
|
|
175
200
|
}
|
|
176
201
|
let events = {};
|
|
177
202
|
let disabled = false;
|
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";
|
|
@@ -845,6 +846,10 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
845
846
|
parts.push(`\nCOMPONENTS: ${w.components.map((c) => `${c.name} (${c.id})`).join(", ")}`);
|
|
846
847
|
if (w.symbols.length)
|
|
847
848
|
parts.push(`\nSYMBOLS: ${w.symbols.slice(0, WHY_CAP * 2).map((s) => `${s.name} [fan-in ${s.metrics.fan_in}, churn ${s.metrics.churn_90d}]`).join(", ")}${more(w.symbols.length, WHY_CAP * 2)}`);
|
|
849
|
+
const recentTasks = store.tasksFor(target, 5);
|
|
850
|
+
if (recentTasks.length) {
|
|
851
|
+
parts.push(`\nRECENT TASKS (agent work that touched this):\n${recentTasks.map((t) => ` • ${t.id} ${t.finished_at.slice(0, 10)} ${t.title} — ${t.lessons.length} lesson(s), ${t.applied.length} applied, ${t.saved.length} saved${t.conformance.some((c) => c.outcome === "violated") ? ", rule VIOLATED" : ""}`).join("\n")}`);
|
|
852
|
+
}
|
|
848
853
|
if (parts.length === 1)
|
|
849
854
|
parts.push("\n(No recorded decisions/bugs/constraints yet for this target.)");
|
|
850
855
|
return ok(parts.join("\n"));
|
|
@@ -1071,7 +1076,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1071
1076
|
const records = as_of ? [] : snapshotDeliveredRecords(store, envelope);
|
|
1072
1077
|
// First delivery of a revision in this task earns one line; repeats stay quiet.
|
|
1073
1078
|
const recalled = renderRecalledLine(unseenLessons(root, task_id, records));
|
|
1074
|
-
const occurrence = recordTaskDelivery(root, task_id, envelope, records);
|
|
1079
|
+
const occurrence = recordTaskDelivery(root, task_id, envelope, records, undefined, target);
|
|
1075
1080
|
result.content.push({ type: "text", text: `${recalled ? `${recalled}\n` : ""}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)." : ""}` });
|
|
1076
1081
|
}
|
|
1077
1082
|
catch {
|
|
@@ -1097,6 +1102,9 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1097
1102
|
// latest receipts whose subject/text matches the target — bounded, ordered, sharing the
|
|
1098
1103
|
// brief's budget as supplements. Withheld on time-travel: state records carry no as-of view.
|
|
1099
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);
|
|
1100
1108
|
const options = {
|
|
1101
1109
|
root,
|
|
1102
1110
|
symbols: store.recs("symbols"),
|
|
@@ -1104,7 +1112,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1104
1112
|
decisionCorpus: store.recs("decisions"),
|
|
1105
1113
|
historical: !!asOf,
|
|
1106
1114
|
profile: profile ?? "builder",
|
|
1107
|
-
supplements: [...(dnaSupplement ? [dnaSupplement] : []), ...stateGrounding, ...(asOf ? [] : conventionSupplements(store.recs("conventions")))],
|
|
1115
|
+
supplements: [...(dnaSupplement ? [dnaSupplement] : []), ...stateGrounding, ...recentTasks, ...(asOf ? [] : conventionSupplements(store.recs("conventions")))],
|
|
1108
1116
|
};
|
|
1109
1117
|
// Task-phrase input ("improve retrieval ranking") resolves no file/symbol and
|
|
1110
1118
|
// used to return an empty brief while the graph held the answer — fall back to
|
|
@@ -1132,6 +1140,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1132
1140
|
supplements: [
|
|
1133
1141
|
...(dnaSupplement ? [dnaSupplement] : []),
|
|
1134
1142
|
...stateGrounding,
|
|
1143
|
+
...recentTasks,
|
|
1135
1144
|
...hits
|
|
1136
1145
|
// State hits are delivered through the State section above, not as raw search lines.
|
|
1137
1146
|
.filter((hit) => !["constraints", "decisions", "bugs", "findings"].includes(hit.kind) && !isStateKind(hit.kind))
|
|
@@ -17,7 +17,7 @@ export declare function boundedTaskReport(report: ReturnType<typeof readTaskRepo
|
|
|
17
17
|
finished_at: string | null;
|
|
18
18
|
state: "open" | "completed" | "interrupted";
|
|
19
19
|
};
|
|
20
|
-
coverage: "
|
|
20
|
+
coverage: "no-delivery-observed" | "no-relevant-memory" | "delivered";
|
|
21
21
|
content_hash: string;
|
|
22
22
|
unknowns: string[];
|
|
23
23
|
deliveries: {
|
|
@@ -73,7 +73,7 @@ export declare function boundedTaskReport(report: ReturnType<typeof readTaskRepo
|
|
|
73
73
|
title: string;
|
|
74
74
|
home: "public" | "private";
|
|
75
75
|
operation: "updated" | "created";
|
|
76
|
-
durability: "
|
|
76
|
+
durability: "local" | "committed" | "pushed";
|
|
77
77
|
}[];
|
|
78
78
|
refusals: ({
|
|
79
79
|
source: "native-edit-gate";
|
|
@@ -106,7 +106,7 @@ export declare function boundedTaskReportForHost(report: ReturnType<typeof readT
|
|
|
106
106
|
finished_at: string | null;
|
|
107
107
|
state: "open" | "completed" | "interrupted";
|
|
108
108
|
};
|
|
109
|
-
coverage: "
|
|
109
|
+
coverage: "no-delivery-observed" | "no-relevant-memory" | "delivered";
|
|
110
110
|
content_hash: string;
|
|
111
111
|
unknowns: string[];
|
|
112
112
|
deliveries: {
|
|
@@ -162,7 +162,7 @@ export declare function boundedTaskReportForHost(report: ReturnType<typeof readT
|
|
|
162
162
|
title: string;
|
|
163
163
|
home: "public" | "private";
|
|
164
164
|
operation: "updated" | "created";
|
|
165
|
-
durability: "
|
|
165
|
+
durability: "local" | "committed" | "pushed";
|
|
166
166
|
}[];
|
|
167
167
|
refusals: ({
|
|
168
168
|
source: "native-edit-gate";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { TaskIdSchema, ReportClaimSchema, LessonReferenceSchema, finishReportTask, listReportTasks, readTaskReport, readLessonHistory, recordReportClaim, reportPresentationEnabled, startReportTask } from "../core/taskReport.js";
|
|
4
|
+
import { persistTaskRecord } from "../core/taskRecord.js";
|
|
4
5
|
import { reportSourceSnapshot, runReportConformance } from "../core/taskReportEvidence.js";
|
|
5
6
|
import { renderTaskReport, writeTaskReportHtml } from "../core/taskReportRender.js";
|
|
6
7
|
function applicationReferences(report) {
|
|
@@ -73,7 +74,18 @@ export function registerTaskReportTools(server, getRoot, getStore) {
|
|
|
73
74
|
if (action === "start") {
|
|
74
75
|
if (!title || applications?.length || outcome)
|
|
75
76
|
throw new Error("start requires a short task title and no completion evidence");
|
|
76
|
-
|
|
77
|
+
let task;
|
|
78
|
+
try {
|
|
79
|
+
task = startReportTask(root, title, task_id);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// A native prompt task already exists under this exact ID (opened by the
|
|
83
|
+
// host hook). Its persisted title is authoritative; a paraphrased title
|
|
84
|
+
// from the model must not fork a second report or fail the start.
|
|
85
|
+
if (!task_id || !/different title/.test(error.message))
|
|
86
|
+
throw error;
|
|
87
|
+
task = readTaskReport(root, task_id, reportSourceSnapshot(root).hash).task;
|
|
88
|
+
}
|
|
77
89
|
const launcher = verificationLauncher();
|
|
78
90
|
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
91
|
}
|
|
@@ -90,15 +102,29 @@ export function registerTaskReportTools(server, getRoot, getStore) {
|
|
|
90
102
|
catch { /* unknowns disclose it */ }
|
|
91
103
|
}
|
|
92
104
|
finishReportTask(root, task_id, outcome ?? "completed");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let
|
|
105
|
+
// The finished task becomes graph memory through the normal capture path;
|
|
106
|
+
// a failed write is disclosed on the card, never a reason to lose it.
|
|
107
|
+
let graph = null;
|
|
108
|
+
let graphNote = "";
|
|
96
109
|
try {
|
|
97
|
-
|
|
110
|
+
const saved = persistTaskRecord(root, getStore(), task_id);
|
|
111
|
+
if (saved) {
|
|
112
|
+
graph = { id: saved.record.id, home: saved.home, flushed: saved.flushed, changed: saved.changed };
|
|
113
|
+
graphNote = `\nGraph ${saved.changed ? "saved" : "already saved"} as ${saved.record.id} (${saved.home}${saved.flushed ? `, ${saved.flushed}` : ""})`;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
graphNote = "\nGraph nothing to keep (no observation, or task records disabled)";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
graphNote = `\nGraph not saved: ${error.message}`;
|
|
98
121
|
}
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
122
|
+
const report = readTaskReport(root, task_id, reportSourceSnapshot(root).hash);
|
|
123
|
+
const show = reportPresentationEnabled(root);
|
|
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 } };
|
|
102
128
|
}
|
|
103
129
|
catch (error) {
|
|
104
130
|
const message = `Task report unavailable: ${error.message}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HunchPaths } from "../core/paths.js";
|
|
2
|
-
import { type Component, type Constraint, type Bug, type Decision, type Symbol, type Edge, type Finding, type EntityKind, type EntityFor } from "../core/types.js";
|
|
2
|
+
import { type Component, type Constraint, type Bug, type Decision, type Symbol, type Edge, type Finding, type EntityKind, type EntityFor, type TaskRecord } from "../core/types.js";
|
|
3
3
|
import { type DB } from "./db.js";
|
|
4
4
|
import { type Embedder } from "./embedder.js";
|
|
5
5
|
import { JsonStore } from "./jsonStore.js";
|
|
@@ -405,6 +405,10 @@ export declare class HunchStore {
|
|
|
405
405
|
* glob, and the queried scope may be either too. Advisory only — findings never
|
|
406
406
|
* enter any block path. Sorted worst-first, then id for stable output. */
|
|
407
407
|
liveFindingsFor(scope: string): Finding[];
|
|
408
|
+
/** Finished agent tasks that touched a file or glob (rule-checked changes and
|
|
409
|
+
* denied edits), newest first. Graph memory, so it spans machines and survives
|
|
410
|
+
* the local ledger's retention window. */
|
|
411
|
+
tasksFor(scope: string, limit?: number): TaskRecord[];
|
|
408
412
|
/** The causal chain behind a constraint — the WHY a diff-only reviewer can't see.
|
|
409
413
|
* Deterministic graph join: constraint → source_decision (the decision that
|
|
410
414
|
* motivated the guard) → the bug whose root cause spawned it (via
|
package/dist/store/hunchStore.js
CHANGED
|
@@ -491,6 +491,14 @@ export class HunchStore {
|
|
|
491
491
|
fts(f.id, "findings", f.title, `${f.observation} ${f.evidence.join(" ")} ${f.affected_files.join(" ")} ${f.affected_symbols.join(" ")} ${f.triage}`);
|
|
492
492
|
}
|
|
493
493
|
counts.findings = fnds.length;
|
|
494
|
+
// Tasks (finished agent work as graph memory): same FTS-only ride. Title +
|
|
495
|
+
// the lesson/save/application record ids and touched files, so "what did
|
|
496
|
+
// an agent do around X" and a record id both hit.
|
|
497
|
+
const tasks = this.recs("tasks");
|
|
498
|
+
for (const t of tasks) {
|
|
499
|
+
fts(t.id, "tasks", t.title, `${t.lessons.map((l) => `${l.record_id} ${l.title}`).join(" ")} ${t.applied.map((a) => a.record_id).join(" ")} ${t.saved.map((s) => s.record_id).join(" ")} ${t.files.join(" ")} ${t.state} ${t.coverage}`);
|
|
500
|
+
}
|
|
501
|
+
counts.tasks = tasks.length;
|
|
494
502
|
// nuryel.state/1 kinds (receipts, commitments, derived, entities, relationships):
|
|
495
503
|
// advisory records on the same FTS-only ride as runbooks/findings — no dedicated
|
|
496
504
|
// SQL table. kind = the store kind; title = the subject key; body = the human words
|
|
@@ -1435,6 +1443,16 @@ export class HunchStore {
|
|
|
1435
1443
|
|| f.affected_symbols.some((s) => s === scope))
|
|
1436
1444
|
.sort((a, b) => (SEV_FINDING[b.severity] ?? 0) - (SEV_FINDING[a.severity] ?? 0) || a.id.localeCompare(b.id));
|
|
1437
1445
|
}
|
|
1446
|
+
/** Finished agent tasks that touched a file or glob (rule-checked changes and
|
|
1447
|
+
* denied edits), newest first. Graph memory, so it spans machines and survives
|
|
1448
|
+
* the local ledger's retention window. */
|
|
1449
|
+
tasksFor(scope, limit = 8) {
|
|
1450
|
+
const t = toPosixTarget(scope);
|
|
1451
|
+
return this.recs("tasks")
|
|
1452
|
+
.filter((r) => r.files.some((f) => pathMatchesGlob(t, f) || pathMatchesGlob(f, t) || pathsRelated(toPosixTarget(f), t)))
|
|
1453
|
+
.sort((a, b) => b.finished_at.localeCompare(a.finished_at) || a.id.localeCompare(b.id))
|
|
1454
|
+
.slice(0, Math.max(1, limit));
|
|
1455
|
+
}
|
|
1438
1456
|
/** The causal chain behind a constraint — the WHY a diff-only reviewer can't see.
|
|
1439
1457
|
* Deterministic graph join: constraint → source_decision (the decision that
|
|
1440
1458
|
* motivated the guard) → the bug whose root cause spawned it (via
|
package/dist/taskReports.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare function createTaskReporter(root: string): {
|
|
|
25
25
|
/** The caller supplies the exact envelope it issued plus snapshots of the
|
|
26
26
|
* included revisions. Return the occurrence with the context to the agent.
|
|
27
27
|
* Issuance alone does not prove the model applied or even attended to it. */
|
|
28
|
-
delivered(taskId: string, envelope: DeliveryEnvelope, records: ReportRecord[], occurrenceId?: string): string;
|
|
28
|
+
delivered(taskId: string, envelope: DeliveryEnvelope, records: ReportRecord[], occurrenceId?: string, target?: string): string;
|
|
29
29
|
applied(taskId: string, claim: ReportClaim): string;
|
|
30
30
|
/** Runs locally as argv, without a shell. Only use commands authorized by
|
|
31
31
|
* the task owner. This API does not accept remote claimed-success receipts. */
|
package/dist/taskReports.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Provider-neutral, local engine integration. The authorized harness owns
|
|
2
2
|
* lifecycle and display; neither operation depends on model compliance. */
|
|
3
|
-
import { realpathSync } from "node:fs";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { finishReportTask, listReportTasks, readTaskReport, readLessonHistory, recordReportClaim, recordTaskDelivery, reportHash, reportPresentationEnabled, startReportTask } from "./core/taskReport.js";
|
|
6
5
|
import { DEFAULT_CHECK_TIMEOUT_MS, reportSourceSnapshot, runReportCheck, runReportConformance } from "./core/taskReportEvidence.js";
|
|
@@ -10,12 +9,14 @@ import { hunchPaths } from "./core/paths.js";
|
|
|
10
9
|
import { HunchStore } from "./store/hunchStore.js";
|
|
11
10
|
export { TASK_REPORT_SCHEMA, TaskIdSchema } from "./core/taskReport.js";
|
|
12
11
|
export { renderTaskReport, renderTaskReportHtml } from "./core/taskReportRender.js";
|
|
12
|
+
import { canonicalReportRoot } from "./core/taskReportPaths.js";
|
|
13
|
+
import { persistTaskRecord } from "./core/taskRecord.js";
|
|
13
14
|
const IdentitySchema = z.object({ task: z.string().min(1).max(1024), attempt: z.string().min(1).max(1024) }).strict();
|
|
14
15
|
/** Create this only after authorizing a local repository/worktree. A task ID is
|
|
15
16
|
* a correlation reference, never an access token. Remote adapters must enforce
|
|
16
17
|
* their own principal/partition boundary before reaching this local API. */
|
|
17
18
|
export function createTaskReporter(root) {
|
|
18
|
-
const scope =
|
|
19
|
+
const scope = canonicalReportRoot(root);
|
|
19
20
|
const report = (taskId) => readTaskReport(scope, taskId, reportSourceSnapshot(scope).hash);
|
|
20
21
|
return {
|
|
21
22
|
/** Omit identity for a fresh task, or supply the harness's stable task AND
|
|
@@ -28,8 +29,8 @@ export function createTaskReporter(root) {
|
|
|
28
29
|
/** The caller supplies the exact envelope it issued plus snapshots of the
|
|
29
30
|
* included revisions. Return the occurrence with the context to the agent.
|
|
30
31
|
* Issuance alone does not prove the model applied or even attended to it. */
|
|
31
|
-
delivered(taskId, envelope, records, occurrenceId) {
|
|
32
|
-
return recordTaskDelivery(scope, taskId, envelope, records, occurrenceId);
|
|
32
|
+
delivered(taskId, envelope, records, occurrenceId, target) {
|
|
33
|
+
return recordTaskDelivery(scope, taskId, envelope, records, occurrenceId, target);
|
|
33
34
|
},
|
|
34
35
|
applied(taskId, claim) { return recordReportClaim(scope, taskId, claim); },
|
|
35
36
|
/** Runs locally as argv, without a shell. Only use commands authorized by
|
|
@@ -57,6 +58,17 @@ export function createTaskReporter(root) {
|
|
|
57
58
|
catch { /* the report's unknowns disclose it */ }
|
|
58
59
|
}
|
|
59
60
|
finishReportTask(scope, taskId, outcome);
|
|
61
|
+
// Graph memory of the finished task; disclosed in the report, never blocking.
|
|
62
|
+
try {
|
|
63
|
+
const store = new HunchStore(hunchPaths(scope));
|
|
64
|
+
try {
|
|
65
|
+
persistTaskRecord(scope, store, taskId);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
store.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* the ledger keeps the observations; the graph write is retried on the next finish */ }
|
|
60
72
|
const result = report(taskId);
|
|
61
73
|
return { report: result, contribution_card: reportPresentationEnabled(scope) ? renderTaskReport(result) : null };
|
|
62
74
|
},
|
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
|
{
|