@davesheffer/hunch 1.32.2 → 1.32.4
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 -4
- package/dist/cli/index.js +40 -7
- package/dist/cli/taskReport.js +80 -1
- package/dist/core/agenthook.d.ts +1 -1
- package/dist/core/agenthook.js +22 -3
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.js +4 -1
- package/dist/core/stateContract.d.ts +3 -0
- package/dist/core/stateContract.js +1 -0
- package/dist/core/taskReport.d.ts +49 -0
- package/dist/core/taskReport.js +99 -0
- package/dist/core/taskReportHook.d.ts +7 -1
- package/dist/core/taskReportHook.js +18 -5
- package/dist/extractors/git.js +25 -5
- package/dist/integrations/claudemd.js +1 -1
- package/dist/integrations/health.d.ts +19 -5
- package/dist/integrations/health.js +35 -10
- package/dist/integrations/providers.d.ts +7 -0
- package/dist/integrations/providers.js +27 -1
- package/dist/integrations/registry.d.ts +15 -0
- package/dist/integrations/registry.js +41 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +345 -322
- package/dist/mcp/toolset.d.ts +30 -0
- package/dist/mcp/toolset.js +72 -0
- package/dist/store/stateBinding.js +16 -0
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -11,6 +11,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
11
11
|
import { RootsListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { hunchPaths, findRoot, toPosixTarget } from "../core/paths.js";
|
|
14
|
+
import { resolveMcpToolset } from "./toolset.js";
|
|
15
|
+
import { readConfig } from "../core/config.js";
|
|
14
16
|
import { canonicalRootPath, resolveActiveRoot } from "./roots.js";
|
|
15
17
|
import { HunchStore } from "../store/hunchStore.js";
|
|
16
18
|
import { StateRefusal, SubscribeResponseSchema, capabilities, partitionOf, readState, recordsState, subscribeState, writeState } from "../store/stateBinding.js";
|
|
@@ -536,6 +538,15 @@ function prepareRoot(root, explicitOverlay, requireIndex) {
|
|
|
536
538
|
throw error;
|
|
537
539
|
}
|
|
538
540
|
}
|
|
541
|
+
/** Delivered to every MCP client at initialize — the one grounding channel that
|
|
542
|
+
* needs no host hook or instruction file. Host-neutral by design (con_e04226bd05);
|
|
543
|
+
* per-host prose (CLAUDE.md, AGENTS.md) and hooks add to it, never replace it. */
|
|
544
|
+
export const MCP_INSTRUCTIONS = [
|
|
545
|
+
"Hunch is this repository's engineering memory: decisions, bug history, invariants, components, with provenance.",
|
|
546
|
+
"Per user task: (1) hunch_task(action:\"start\", title) once — unless the host's prompt hook already printed a task_id, then reuse it; (2) hunch_context(target, task_id) FIRST, before reading or editing, for the file, symbol, or task phrase; (3) hunch_check_constraints(scope) before editing shared code; (4) hunch_task(action:\"finish\", task_id) before the final response and show its contribution card.",
|
|
547
|
+
"Then by moment: hunch_why(target) for rationale and rejected alternatives, hunch_bug_lineage before fixing a failure, hunch_record_decision after a non-trivial choice, hunch_record_correction when a human corrects you.",
|
|
548
|
+
"Hosts without lifecycle hooks (Windsurf, Cursor, or Codex before its hooks are trusted) receive no automatic grounding: call these tools yourself.",
|
|
549
|
+
].join("\n");
|
|
539
550
|
export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
540
551
|
const pinned = options.pinned === true;
|
|
541
552
|
const explicitOverlay = !!process.env.HUNCH_PRIVATE_DIR?.trim();
|
|
@@ -598,7 +609,13 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
598
609
|
// the optional model isn't installed). The model then loads lazily on the first
|
|
599
610
|
// hunch_query and stays warm — and hybridSearch degrades to FTS until then.
|
|
600
611
|
const embedderReady = selectEmbedder();
|
|
601
|
-
|
|
612
|
+
// Everyday tools by default; specialist groups by evidence, config, or env
|
|
613
|
+
// (src/mcp/toolset.ts). Hidden tools are never registered, so tools/list is
|
|
614
|
+
// exactly what the host can call.
|
|
615
|
+
const toolset = resolveMcpToolset(root, { configSpec: readConfig(hunchPaths(root)).mcp_tools ?? null, pinned });
|
|
616
|
+
if (toolset.hidden.length)
|
|
617
|
+
process.stderr.write(`[hunch-mcp] tool groups: ${toolset.groups.length ? toolset.groups.join(", ") : "core only"} (${toolset.source}); ${toolset.hidden.length} specialist tool(s) hidden — HUNCH_MCP_TOOLS=all or .hunch/config.json mcp_tools to expose\n`);
|
|
618
|
+
const server = new McpServer({ name: "hunch", version: HUNCH_VERSION }, { instructions: MCP_INSTRUCTIONS });
|
|
602
619
|
let activeRequests = 0;
|
|
603
620
|
let pendingRoot = null;
|
|
604
621
|
let pendingScheduled = false;
|
|
@@ -1774,155 +1791,157 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
1774
1791
|
return err(`nuryel.state/1 failed: ${e.message}`);
|
|
1775
1792
|
};
|
|
1776
1793
|
const stateResult = (text, structured) => ({ content: [{ type: "text", text }], structuredContent: structured });
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
...
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1794
|
+
if (toolset.enabled("nuryel")) {
|
|
1795
|
+
server.registerTool("nuryel_capabilities", {
|
|
1796
|
+
title: "nuryel.state/1 — what this state layer supports",
|
|
1797
|
+
description: "Negotiate before depending on anything: returns the contract version, the capability list (verbs + record schemas), the repository partition this store serves, and which partition kinds it can hold. A capability you need that is missing here is a typed refusal on use, never a degraded answer.",
|
|
1798
|
+
inputSchema: {},
|
|
1799
|
+
}, async () => {
|
|
1800
|
+
const caps = capabilities(store);
|
|
1801
|
+
return stateResult(`${caps.protocol} · repository ${caps.repository.id} · partitions ${caps.partitions.join(", ")} · ${caps.capabilities.length} capabilities`, caps);
|
|
1802
|
+
});
|
|
1803
|
+
server.registerTool("nuryel_read", {
|
|
1804
|
+
title: "nuryel.state/1 read — the system-of-record answer for a subject",
|
|
1805
|
+
description: "Read organizational state under a delivery receipt. Pass the principal (id, kind, grants) and the scope; optionally a subject (an entity id, a decision topic, an external `object_type:object_key`) to get state_of_record — what is current, in force, done, what it depends on and what invalidates it — plus a task phrase for the ranked delivery envelope. Scopes the principal is not granted are named in denied_scopes, never silently dropped. To read observations beyond the default 64, use observed_page:{} with one scope and a subject, then pass state_of_record.observed_page.next_cursor as observed_page.cursor until null. A conflict means the observations changed: restart from the first page. Never claim complete coverage while a next cursor remains.",
|
|
1806
|
+
inputSchema: ReadRequestSchema.omit({ schema: true }).shape,
|
|
1807
|
+
outputSchema: ReadResponseSchema.shape,
|
|
1808
|
+
}, async (input) => {
|
|
1809
|
+
try {
|
|
1810
|
+
const { response, envelope } = readState(store, { schema: STATE_READ_VERSION, ...input });
|
|
1811
|
+
const sor = response.state_of_record;
|
|
1812
|
+
const summary = sor
|
|
1813
|
+
? `subject ${sor.subject}: current ${sor.current.length} · in force ${sor.in_force.length} · done ${sor.done.length} · observed ${sor.observed?.length ?? 0} · depends on ${sor.depends_on.length} · invalidated by ${sor.invalidated_by.length}`
|
|
1814
|
+
: "no subject — delivery envelope only";
|
|
1815
|
+
const deniedNote = response.denied_scopes.length ? `\ndenied scopes: ${response.denied_scopes.map((s) => `${s.kind}/${s.id}`).join(", ")}` : "";
|
|
1816
|
+
// Render the state of record itself, not only its refs: a consumer answers from this text.
|
|
1817
|
+
const line = (label, ref) => {
|
|
1818
|
+
const r = (response.records ?? {})[ref.id] ?? {};
|
|
1819
|
+
const g = (k) => { const v = r[k]; return typeof v === "string" ? v : v == null ? "" : JSON.stringify(v); };
|
|
1820
|
+
if (ref.facet === "derived")
|
|
1821
|
+
return `- ${label} derived ${ref.id} · computed ${g("computed_at")} · ${r.dependencies?.length ?? 0} dependencies\n ${g("content").slice(0, 1200)}`;
|
|
1822
|
+
if (ref.facet === "commitments")
|
|
1823
|
+
return `- ${label} commitment ${ref.id} · ${g("status")} · due ${g("due")} · owner ${g("owner")}: ${g("title")}${r.closed_by ? ` · closed by ${g("closed_by")}` : ""}`;
|
|
1824
|
+
if (ref.facet === "receipts") {
|
|
1825
|
+
const t = (r.target ?? {});
|
|
1826
|
+
// The chain: what the action rested on, one pointer per line, so a reader follows
|
|
1827
|
+
// incident → decision → change proof → closure without a second call.
|
|
1828
|
+
const rests = (Array.isArray(r.rests_on) ? r.rests_on : []);
|
|
1829
|
+
const restLines = rests.map((d) => {
|
|
1830
|
+
if (d.kind === "record") {
|
|
1831
|
+
const sc = d.scope;
|
|
1832
|
+
return `\n rests on record ${String(d.id)}${sc ? ` in ${String(sc.kind)}/${String(sc.id)}` : ""}`;
|
|
1833
|
+
}
|
|
1834
|
+
if (d.kind === "external") {
|
|
1835
|
+
const x = (d.ref ?? {});
|
|
1836
|
+
return `\n rests on ${String(x.system ?? "")} ${String(x.object_type ?? "")}:${String(x.object_key ?? "")}`;
|
|
1837
|
+
}
|
|
1838
|
+
return `\n rests on ${String(d.kind)} ${String(d.name ?? "")}`;
|
|
1839
|
+
}).join("");
|
|
1840
|
+
return `- ${label} receipt ${ref.id} · ${g("action_kind")} on ${String(t.system ?? "")} ${String(t.object_type ?? "")}:${String(t.object_key ?? "")} · ${g("state")} at ${g("occurred_at")} by ${g("actor")}${restLines}`;
|
|
1841
|
+
}
|
|
1842
|
+
if (ref.facet === "decisions")
|
|
1843
|
+
return `- ${label} decision ${ref.id} · ${g("status")}: ${g("title")}`;
|
|
1844
|
+
if (ref.facet === "constraints")
|
|
1845
|
+
return `- ${label} constraint ${ref.id} · ${g("severity")}: ${g("statement")}`;
|
|
1846
|
+
if (ref.facet === "entities")
|
|
1847
|
+
return `- ${label} entity ${ref.id} · ${g("kind")} ${g("name")} · ${g("lifecycle")}`;
|
|
1848
|
+
return `- ${label} ${ref.facet} ${ref.id}`;
|
|
1849
|
+
};
|
|
1850
|
+
const stateText = sor
|
|
1851
|
+
? [...sor.current.map((r) => line("current", r)), ...sor.in_force.map((r) => line("in force", r)), ...sor.done.map((r) => line("done", r)),
|
|
1852
|
+
...(sor.observed ?? []).map(r => line("observed; verify currentness before relying on it", r)),
|
|
1853
|
+
...(sor.observed_page ? [`- Observation page: ${sor.observed_page.total} total; next_cursor: ${JSON.stringify(sor.observed_page.next_cursor)}`]
|
|
1854
|
+
: sor.observed_truncated ? ['- More observations exist; read this subject with observed_page:{} in one partition, then follow next_cursor.'] : []),
|
|
1855
|
+
...(sor.invalidated_by.length ? [`- invalidated by: ${sor.invalidated_by.join(", ")}`] : [])].join("\n") || "(nothing on record for this subject)"
|
|
1856
|
+
: "";
|
|
1857
|
+
return stateResult(`${response.receipt_id} · ${summary}${deniedNote}${stateText ? `\n\nState of record:\n${stateText}` : ""}\n\n${envelope.text}`, response);
|
|
1858
|
+
}
|
|
1859
|
+
catch (e) {
|
|
1860
|
+
return stateRefusal(e);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
server.registerTool("nuryel_write", {
|
|
1864
|
+
title: "nuryel.state/1 write — provenance + idempotency in, durability out",
|
|
1865
|
+
description: "Write one record into a facet (receipts, commitments, derived, entities, relationships, or the legacy decisions/constraints/bugs/findings). The record must carry provenance; the request must carry an idempotency_key — a replay returns the original, a reused key with a different payload is refused. Ids are derived from the record's facts, never chosen. A second live decision on a topic is refused with the incumbent named; pass supersedes to replace it explicitly. organization/team/user partitions never ride a repository: they require an overlay. To show an existing captured observation under another subject without copying it, write a relationship type observation_about with from=observation id, to=subject, observation_hash, lifecycle=active, reason and hashed external evidence of the explicit association. Retire the relationship to unlink; reactivation requires expected_version.",
|
|
1866
|
+
inputSchema: { ...WriteRequestSchema.omit({ schema: true }).shape, cwd: cwdHintField },
|
|
1867
|
+
outputSchema: WriteResultSchema.shape,
|
|
1868
|
+
}, async ({ cwd: _cwd, ...input }) => {
|
|
1869
|
+
try {
|
|
1870
|
+
// Same cross-process lock `hunch serve` takes: a second agent writing over stdio must
|
|
1871
|
+
// not race the HTTP server between the ledger read and the record write.
|
|
1872
|
+
const result = await withWriteLock(hunchPaths(root).hunch, () => writeState(store, { schema: STATE_WRITE_VERSION, ...input }, {
|
|
1873
|
+
flush: (isPrivate, message) => flushCapture(store, hunchPaths(root).hunch, isPrivate, message, startupTeamRoute ?? undefined),
|
|
1874
|
+
}));
|
|
1875
|
+
return stateResult(`${result.outcome} ${result.record_id} (${result.durability}) ${result.record_hash}`, result);
|
|
1876
|
+
}
|
|
1877
|
+
catch (e) {
|
|
1878
|
+
return stateRefusal(e);
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
server.registerTool("nuryel_capture", {
|
|
1882
|
+
title: "nuryel.state/1 capture — one relevant assertion with exact source excerpts",
|
|
1883
|
+
description: "Save ONE relevant atomic assertion learned during the task. First split mixed source material into independent assertions; check each one, retaining new relevant details inside otherwise known passages. Exclude chatter, speculation, unsupported conclusions and transient tool output. Supply a concrete future-use reason and exact excerpts from the source text. Whole source text is transient and is never stored. Deduplication is per assertion + subject + source excerpt, independent of agent and read time; never skip a whole document because some of it is known. Records are observations, not verified current summaries or execution receipts. Call after substantive learning without waiting for the user to say remember. Read back the returned record before claiming it was saved.",
|
|
1884
|
+
inputSchema: { ...CaptureRequestSchema.omit({ schema: true }).shape, cwd: cwdHintField },
|
|
1885
|
+
outputSchema: WriteResultSchema.shape,
|
|
1886
|
+
}, async ({ cwd: _cwd, ...input }) => {
|
|
1887
|
+
try {
|
|
1888
|
+
const result = await withWriteLock(hunchPaths(root).hunch, () => captureState(store, { schema: STATE_CAPTURE_VERSION, ...input }, {
|
|
1889
|
+
flush: (isPrivate, message) => flushCapture(store, hunchPaths(root).hunch, isPrivate, message, startupTeamRoute ?? undefined),
|
|
1890
|
+
}));
|
|
1891
|
+
return stateResult(`${result.outcome} observation ${result.record_id} (${result.durability}); this does not assert currentness. ${result.record_hash}`, result);
|
|
1892
|
+
}
|
|
1893
|
+
catch (e) {
|
|
1894
|
+
return stateRefusal(e);
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
server.registerTool("nuryel_capture_batch", {
|
|
1898
|
+
title: "nuryel.state/1 capture batch — save relevant atomic observations",
|
|
1899
|
+
description: "Preferred capture for multiple facts learned during the task. Split source material into independent relevant assertions, select exact supporting excerpts and give each a concrete future-use reason. Exclude chatter, unsupported inference and transient output. Check every assertion even in a known paragraph: deduplication never discards a whole passage. Send each source once and reference its zero-based index. At most 32 observations and 8 sources; split larger work into batches. One partition lock and index update, no extra model call. Results preserve input indexes; inspect every refusal and stored record. Saved observations have unknown currentness, not verified receipts or current summaries. Use your own initiating agent identity automatically after substantive learning. Optional reviews withdraw specific prior observations: supply record_id, expected_hash, a reason, and exact excerpts from a changed source that observation depends on. Mere hash changes, missing text or uncertain interpretation never suffice; the initiating agent must identify explicit contradiction or withdrawal. Original facts remain in history with the review author and evidence. Review results are separately indexed; inspect every refusal.",
|
|
1900
|
+
inputSchema: { ...CaptureBatchRequestSchema.omit({ schema: true }).shape, cwd: cwdHintField },
|
|
1901
|
+
outputSchema: CaptureBatchResultSchema.shape,
|
|
1902
|
+
}, async ({ cwd: _cwd, ...input }) => {
|
|
1903
|
+
try {
|
|
1904
|
+
const result = await withWriteLock(hunchPaths(root).hunch, () => captureBatchState(store, { schema: STATE_CAPTURE_BATCH_VERSION, ...input }, {
|
|
1905
|
+
flush: (isPrivate, message) => flushCapture(store, hunchPaths(root).hunch, isPrivate, message, startupTeamRoute ?? undefined),
|
|
1906
|
+
}));
|
|
1907
|
+
return stateResult(`Capture batch: ${result.results.filter(r => r.status === "saved").length} saved/replayed, ${result.results.filter(r => r.status === "refused").length} refused.${result.reviews ? ` Reviews: ${result.reviews.filter(r => r.status === "saved").length} withdrawn/replayed, ${result.reviews.filter(r => r.status === "refused").length} refused.` : ''} Inspect each indexed result.`, result);
|
|
1908
|
+
}
|
|
1909
|
+
catch (e) {
|
|
1910
|
+
return stateRefusal(e);
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
server.registerTool("nuryel_subscribe", {
|
|
1914
|
+
title: "nuryel.state/1 subscribe — the scope's ordered change stream after a cursor",
|
|
1915
|
+
description: "Return the change events for a scope with seq > after_seq, strictly ordered. Unfiltered, the events are contiguous (a gap means resynchronize); with facets/subjects filters the response is a subsequence and head_seq is still your next cursor. Each event names the record, its hash, what changed, what it invalidates, and the cause.",
|
|
1916
|
+
inputSchema: SubscribeRequestSchema.omit({ schema: true }).shape,
|
|
1917
|
+
outputSchema: SubscribeResponseSchema.shape,
|
|
1918
|
+
}, async (input) => {
|
|
1919
|
+
try {
|
|
1920
|
+
const response = subscribeState(store, { schema: STATE_SUBSCRIBE_VERSION, ...input });
|
|
1921
|
+
const lines = response.events.map((e) => `${e.seq} ${e.at} ${e.change} ${e.facet}/${e.record_id}${e.invalidates.length ? ` invalidates ${e.invalidates.join(", ")}` : ""}`);
|
|
1922
|
+
return stateResult(`${response.scope.kind}/${response.scope.id} head_seq ${response.head_seq} · ${response.events.length} event(s)${response.filtered ? " (filtered)" : ""}\n${lines.join("\n")}`, response);
|
|
1923
|
+
}
|
|
1924
|
+
catch (e) {
|
|
1925
|
+
return stateRefusal(e);
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
server.registerTool("nuryel_records", {
|
|
1929
|
+
title: "nuryel.state/1 records — fetch records by id, grants first",
|
|
1930
|
+
description: "Fetch state records by id (from a subscribe event, a read ref, or a write result). Every id is accounted for: found (with its facet), denied (its scope is outside your grants — named, never described) or missing.",
|
|
1931
|
+
inputSchema: RecordsRequestSchema.omit({ schema: true }).shape,
|
|
1932
|
+
outputSchema: RecordsResponseSchema.shape,
|
|
1933
|
+
}, async (input) => {
|
|
1934
|
+
try {
|
|
1935
|
+
const response = recordsState(store, { schema: STATE_RECORDS_VERSION, ...input });
|
|
1936
|
+
const lines = Object.entries(response.records).map(([id, r]) => `- ${response.facets[id]} ${id}: ${JSON.stringify(r).slice(0, 600)}`);
|
|
1937
|
+
const tail = [...(response.missing.length ? [`missing: ${response.missing.join(", ")}`] : []), ...(response.denied.length ? [`denied: ${response.denied.join(", ")}`] : [])];
|
|
1938
|
+
return stateResult(`${Object.keys(response.records).length} record(s)\n${lines.join("\n")}${tail.length ? `\n${tail.join("\n")}` : ""}`, response);
|
|
1939
|
+
}
|
|
1940
|
+
catch (e) {
|
|
1941
|
+
return stateRefusal(e);
|
|
1942
|
+
}
|
|
1943
|
+
});
|
|
1944
|
+
} // toolset: nuryel
|
|
1926
1945
|
// -- hunch_findings (read: the open-observations ledger) --------------------
|
|
1927
1946
|
server.registerTool("hunch_findings", {
|
|
1928
1947
|
title: "Open findings for a scope",
|
|
@@ -2270,78 +2289,80 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
2270
2289
|
return err(`Failed to evaluate policy: ${e.message}`);
|
|
2271
2290
|
}
|
|
2272
2291
|
});
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2292
|
+
if (toolset.enabled("constitution-experiments")) {
|
|
2293
|
+
server.registerTool("hunch_constitution_g2_readiness", {
|
|
2294
|
+
title: "Inspect Constitution G2 readiness",
|
|
2295
|
+
description: "Return the exact private G2 dogfood evidence packet: human-selected policies, bound proof/corpus/shadow evidence, operational runbook rehearsals, and blockers. Read-only; it never creates evidence, signs off G2, activates policy, warns, or blocks.",
|
|
2296
|
+
inputSchema: {},
|
|
2297
|
+
}, async () => {
|
|
2298
|
+
try {
|
|
2299
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2Readiness(), null, 2));
|
|
2300
|
+
}
|
|
2301
|
+
catch (e) {
|
|
2302
|
+
return err(`Failed to inspect G2 readiness: ${e.message}`);
|
|
2303
|
+
}
|
|
2304
|
+
});
|
|
2305
|
+
server.registerTool("hunch_constitution_g3_readiness", {
|
|
2306
|
+
title: "Inspect Constitution G3 readiness",
|
|
2307
|
+
description: "Return the exact private G3 advisory packet: human-selected policies and clients, immutable experiment preregistrations, proof-card comprehension/review measurements, executable adapter conformance, scorecard, and blockers. Read-only; it never records evidence, activates policy, or signs off G3.",
|
|
2308
|
+
inputSchema: {},
|
|
2309
|
+
}, async () => {
|
|
2310
|
+
try {
|
|
2311
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g3Readiness(), null, 2));
|
|
2312
|
+
}
|
|
2313
|
+
catch (e) {
|
|
2314
|
+
return err(`Failed to inspect G3 readiness: ${e.message}`);
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
server.registerTool("hunch_constitution_g2_shadow_queue", {
|
|
2318
|
+
title: "Review unclassified G2 shadow violations",
|
|
2319
|
+
description: "Return a bounded private queue of exact-current-proof G2 shadow violations that still require human classification. Read-only; it never records an observation or disposition, changes lifecycle, grants authority, warns, or blocks.",
|
|
2320
|
+
inputSchema: {
|
|
2321
|
+
limit: z.number().int().min(1).max(100).optional().describe("Maximum queue items to return (default 20)."),
|
|
2322
|
+
},
|
|
2323
|
+
}, async ({ limit }) => {
|
|
2324
|
+
try {
|
|
2325
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2ShadowQueue(limit ?? 20), null, 2));
|
|
2326
|
+
}
|
|
2327
|
+
catch (e) {
|
|
2328
|
+
return err(`Failed to inspect the G2 shadow queue: ${e.message}`);
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
server.registerTool("hunch_constitution_g2_operational_drill", {
|
|
2332
|
+
title: "Execute one exact G2 operational drill",
|
|
2333
|
+
description: "Execute the selected private G2 runbook's exact safety regression and return a content-addressed hash-only receipt. Diagnostic only: it writes no rehearsal or shadow evidence, grants no authority, and never signs off G2.",
|
|
2334
|
+
inputSchema: {
|
|
2335
|
+
category: z.enum(G2_RUNBOOK_CATEGORIES).describe("Exact operational category selected by the current private G2 plan."),
|
|
2336
|
+
},
|
|
2337
|
+
}, async ({ category }) => {
|
|
2338
|
+
try {
|
|
2339
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2OperationalDrill(category), null, 2));
|
|
2340
|
+
}
|
|
2341
|
+
catch (e) {
|
|
2342
|
+
return err(`Failed to execute the G2 operational drill: ${e.message}`);
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
server.registerTool("hunch_constitution_g2_candidates", {
|
|
2346
|
+
title: "Review potential G2 dogfood candidates",
|
|
2347
|
+
description: "Return a bounded private review packet of exact structural candidates from fix-labeled git history, including the current append-only human selection/rejection when present. Read-only: proposed before/after corpus refs are not replayed evidence, and the tool creates no attestation, policy, proof, corpus, authority, warning, or block.",
|
|
2348
|
+
inputSchema: {
|
|
2349
|
+
since: z.string().min(1).max(100).optional().describe("Git history window (default 180d)."),
|
|
2350
|
+
max_commits: z.number().int().min(1).max(200).optional().describe("Maximum fix-labeled commits to inspect (default 100)."),
|
|
2351
|
+
limit: z.number().int().min(1).max(100).optional().describe("Maximum ranked candidates to return (default 30)."),
|
|
2352
|
+
},
|
|
2353
|
+
}, async ({ since, max_commits, limit }) => {
|
|
2354
|
+
try {
|
|
2355
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2CandidateReview({
|
|
2356
|
+
since: since ?? "180d",
|
|
2357
|
+
maxCommits: max_commits ?? 100,
|
|
2358
|
+
limit: limit ?? 30,
|
|
2359
|
+
}), null, 2));
|
|
2360
|
+
}
|
|
2361
|
+
catch (e) {
|
|
2362
|
+
return err(`Failed to inspect G2 candidates: ${e.message}`);
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
} // toolset: constitution-experiments
|
|
2345
2366
|
// -- hunch_conformance ----------------------------------------------------
|
|
2346
2367
|
server.registerTool("hunch_conformance", {
|
|
2347
2368
|
title: "Does the code still satisfy the recorded intent?",
|
|
@@ -2363,106 +2384,108 @@ export function buildServerWithRootControl(initialRoot, options = {}) {
|
|
|
2363
2384
|
return err(`Conformance refused an incomplete working graph: ${error.message}`);
|
|
2364
2385
|
}
|
|
2365
2386
|
});
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2387
|
+
if (toolset.enabled("constitution-experiments")) {
|
|
2388
|
+
server.registerTool("hunch_constitution_g2_behavior_candidates", {
|
|
2389
|
+
title: "Review executable G2 behavior candidates",
|
|
2390
|
+
description: "Derive a bounded private review packet from human-grounded rejected structural proxies and newly added literal node:test cases in their exact fixing commits. Read-only: candidates remain unselected and create no policy, corpus, proof, authority, warning, or block.",
|
|
2391
|
+
inputSchema: {
|
|
2392
|
+
decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact current human-confirmed decision to use as the direct behavior grounding batch."),
|
|
2393
|
+
since: z.string().min(1).max(100).optional().describe("Git history window (default 180d)."),
|
|
2394
|
+
max_commits: z.number().int().min(1).max(200).optional().describe("Maximum fix-labeled commits to inspect (default 100)."),
|
|
2395
|
+
limit: z.number().int().min(1).max(100).optional().describe("Maximum behavior candidates to return (default 30)."),
|
|
2396
|
+
},
|
|
2397
|
+
}, async ({ decision_id, since, max_commits, limit }) => {
|
|
2398
|
+
try {
|
|
2399
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorCandidateReview({
|
|
2400
|
+
since: since ?? "180d",
|
|
2401
|
+
maxCommits: max_commits ?? 100,
|
|
2402
|
+
limit: limit ?? 30,
|
|
2403
|
+
decisionId: decision_id,
|
|
2404
|
+
}), null, 2));
|
|
2405
|
+
}
|
|
2406
|
+
catch (e) {
|
|
2407
|
+
return err(`Failed to inspect G2 behavior candidates: ${e.message}`);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
server.registerTool("hunch_constitution_g2_behavior_replay", {
|
|
2411
|
+
title: "Replay one G2 behavior candidate",
|
|
2412
|
+
description: "Execute one exact behavior candidate without a shell in disposable known-bad and known-good worktrees, transplanting the hash-bound known-good test file into both. Diagnostic only: writes no Constitution artifact and grants no policy or G2 authority.",
|
|
2413
|
+
inputSchema: {
|
|
2414
|
+
candidate_id: z.string().regex(/^g2behavior_[a-f0-9]{10}$/),
|
|
2415
|
+
review_hash: z.string().regex(/^sha1:[a-f0-9]{40}$/),
|
|
2416
|
+
decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch used by the reviewed candidate."),
|
|
2417
|
+
since: z.string().min(1).max(100).optional().describe("Git history window used by the exact review packet (default 180d)."),
|
|
2418
|
+
max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
|
|
2419
|
+
limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
|
|
2420
|
+
timeout_ms: z.number().int().min(1).max(120000).optional().describe("Per-leg execution timeout (default 30000ms)."),
|
|
2421
|
+
},
|
|
2422
|
+
}, async ({ candidate_id, review_hash, decision_id, since, max_commits, limit, timeout_ms }) => {
|
|
2423
|
+
try {
|
|
2424
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorCandidateReplay(candidate_id, review_hash, {
|
|
2425
|
+
since: since ?? "180d",
|
|
2426
|
+
maxCommits: max_commits ?? 100,
|
|
2427
|
+
limit: limit ?? 30,
|
|
2428
|
+
decisionId: decision_id,
|
|
2429
|
+
timeoutMs: timeout_ms ?? 30_000,
|
|
2430
|
+
}), null, 2));
|
|
2431
|
+
}
|
|
2432
|
+
catch (e) {
|
|
2433
|
+
return err(`Failed to replay G2 behavior candidate: ${e.message}`);
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
server.registerTool("hunch_constitution_g2_behavior_materialization", {
|
|
2437
|
+
title: "Assess selected G2 behavior materialization",
|
|
2438
|
+
description: "Bind the complete current private behavior review and exact selected attestations, then report whether their durable meanings are expressible by the supported Policy IR. Read-only and fail-closed: unsupported behavior creates no policy, corpus, plan, proof, authority, warning, or block.",
|
|
2439
|
+
inputSchema: {
|
|
2440
|
+
decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch to assess."),
|
|
2441
|
+
since: z.string().min(1).max(100).optional().describe("Git history window used by the exact review packet (default 180d)."),
|
|
2442
|
+
max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
|
|
2443
|
+
limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
|
|
2444
|
+
},
|
|
2445
|
+
}, async ({ decision_id, since, max_commits, limit }) => {
|
|
2446
|
+
try {
|
|
2447
|
+
return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorMaterializationAssessment({
|
|
2448
|
+
since: since ?? "180d",
|
|
2449
|
+
maxCommits: max_commits ?? 100,
|
|
2450
|
+
limit: limit ?? 30,
|
|
2451
|
+
decisionId: decision_id,
|
|
2452
|
+
}), null, 2));
|
|
2453
|
+
}
|
|
2454
|
+
catch (e) {
|
|
2455
|
+
return err(`Failed to assess G2 behavior materialization: ${e.message}`);
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
server.registerTool("hunch_constitution_g2_behavior_policy_materialize", {
|
|
2459
|
+
title: "Materialize selected G2 behavior policies",
|
|
2460
|
+
description: "Materialize every current exact selected behavior attestation into a separate private Policy IR v2 proposal, exact corpus and plan, and P3 executable proof. Writes private non-authoritative artifacts only; activation remains a separate explicit human action.",
|
|
2461
|
+
inputSchema: {
|
|
2462
|
+
decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch to materialize."),
|
|
2463
|
+
since: z.string().min(1).max(100).optional().describe("Git history window used by the complete exact review packet (default 180d)."),
|
|
2464
|
+
max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
|
|
2465
|
+
limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
|
|
2466
|
+
allow_install_scripts: z.array(z.string().min(1).max(214)).max(20).optional().describe("Exact dependency package names allowed to run lifecycle scripts while provisioning snapshots."),
|
|
2467
|
+
dependency_timeout_ms: z.number().int().min(1).max(900000).optional().describe("Timeout for each exact dependency snapshot operation (default 300000ms)."),
|
|
2468
|
+
cwd: cwdHintField,
|
|
2469
|
+
},
|
|
2470
|
+
}, async ({ decision_id, since, max_commits, limit, allow_install_scripts, dependency_timeout_ms }) => {
|
|
2471
|
+
try {
|
|
2472
|
+
const materialized = new ConstitutionService(store, root).g2BehaviorPolicyMaterialize({
|
|
2473
|
+
since: since ?? "180d",
|
|
2474
|
+
maxCommits: max_commits ?? 100,
|
|
2475
|
+
limit: limit ?? 30,
|
|
2476
|
+
decisionId: decision_id,
|
|
2477
|
+
allowInstallScripts: allow_install_scripts ?? [],
|
|
2478
|
+
dependencyTimeoutMs: dependency_timeout_ms ?? 300_000,
|
|
2479
|
+
});
|
|
2480
|
+
flushMemoryHome(store, hunchPaths(root).hunch, "private", "hunch: materialize G2 behavior policies", startupTeamRoute ?? undefined);
|
|
2481
|
+
const destRoot = resolveDestRoot("private", store, root);
|
|
2482
|
+
return ok(JSON.stringify({ ...materialized, destination: { root: destRoot, branch: currentBranch(destRoot) } }, null, 2));
|
|
2483
|
+
}
|
|
2484
|
+
catch (e) {
|
|
2485
|
+
return err(`Failed to materialize G2 behavior policies: ${e.message}`);
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
} // toolset: constitution-experiments
|
|
2466
2489
|
return {
|
|
2467
2490
|
server,
|
|
2468
2491
|
getRoot: () => root,
|