@davesheffer/hunch 1.33.0 → 1.35.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 +50 -6
- 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/stateContract.d.ts +3 -3
- 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 +2 -1
- package/dist/core/taskReportHook.d.ts +18 -3
- package/dist/core/taskReportHook.js +77 -8
- 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/gitignore.js +1 -0
- package/dist/integrations/health.js +27 -2
- package/dist/mcp/server.js +5 -1
- package/dist/mcp/taskReportTools.d.ts +4 -4
- package/dist/mcp/taskReportTools.js +32 -3
- 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
package/dist/mcp/server.js
CHANGED
|
@@ -845,6 +845,10 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
845
845
|
parts.push(`\nCOMPONENTS: ${w.components.map((c) => `${c.name} (${c.id})`).join(", ")}`);
|
|
846
846
|
if (w.symbols.length)
|
|
847
847
|
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)}`);
|
|
848
|
+
const recentTasks = store.tasksFor(target, 5);
|
|
849
|
+
if (recentTasks.length) {
|
|
850
|
+
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")}`);
|
|
851
|
+
}
|
|
848
852
|
if (parts.length === 1)
|
|
849
853
|
parts.push("\n(No recorded decisions/bugs/constraints yet for this target.)");
|
|
850
854
|
return ok(parts.join("\n"));
|
|
@@ -1071,7 +1075,7 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1071
1075
|
const records = as_of ? [] : snapshotDeliveredRecords(store, envelope);
|
|
1072
1076
|
// First delivery of a revision in this task earns one line; repeats stay quiet.
|
|
1073
1077
|
const recalled = renderRecalledLine(unseenLessons(root, task_id, records));
|
|
1074
|
-
const occurrence = recordTaskDelivery(root, task_id, envelope, records);
|
|
1078
|
+
const occurrence = recordTaskDelivery(root, task_id, envelope, records, undefined, target);
|
|
1075
1079
|
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
1080
|
}
|
|
1077
1081
|
catch {
|
|
@@ -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,6 +102,23 @@ export function registerTaskReportTools(server, getRoot, getStore) {
|
|
|
90
102
|
catch { /* unknowns disclose it */ }
|
|
91
103
|
}
|
|
92
104
|
finishReportTask(root, task_id, outcome ?? "completed");
|
|
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 = "";
|
|
109
|
+
try {
|
|
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}`;
|
|
121
|
+
}
|
|
93
122
|
const report = readTaskReport(root, task_id, reportSourceSnapshot(root).hash);
|
|
94
123
|
const show = reportPresentationEnabled(root);
|
|
95
124
|
let file = null;
|
|
@@ -97,8 +126,8 @@ export function registerTaskReportTools(server, getRoot, getStore) {
|
|
|
97
126
|
file = writeTaskReportHtml(root, task_id);
|
|
98
127
|
}
|
|
99
128
|
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 } };
|
|
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 } };
|
|
102
131
|
}
|
|
103
132
|
catch (error) {
|
|
104
133
|
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.35.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.35.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|