@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.
@@ -0,0 +1,30 @@
1
+ export declare const MCP_TOOL_GROUPS: {
2
+ /** nuryel.state/1 — the state-partition contract (Sofia-style agents). */
3
+ readonly nuryel: readonly ["nuryel_capabilities", "nuryel_read", "nuryel_write", "nuryel_capture", "nuryel_capture_batch", "nuryel_subscribe", "nuryel_records"];
4
+ /** Constitution G2/G3 experiment-track tools; the CLI remains the primary surface. */
5
+ readonly "constitution-experiments": readonly ["hunch_constitution_g2_readiness", "hunch_constitution_g3_readiness", "hunch_constitution_g2_shadow_queue", "hunch_constitution_g2_operational_drill", "hunch_constitution_g2_candidates", "hunch_constitution_g2_behavior_candidates", "hunch_constitution_g2_behavior_replay", "hunch_constitution_g2_behavior_materialization", "hunch_constitution_g2_behavior_policy_materialize"];
6
+ };
7
+ export type McpToolGroup = keyof typeof MCP_TOOL_GROUPS;
8
+ export declare const MCP_TOOL_GROUP_NAMES: McpToolGroup[];
9
+ export interface McpToolset {
10
+ enabled: (group: McpToolGroup) => boolean;
11
+ groups: McpToolGroup[];
12
+ hidden: string[];
13
+ /** Where the selection came from, for the startup log and doctor. */
14
+ source: "env" | "config" | "default";
15
+ }
16
+ /** Grammar shared by the env var and the config value: `all`, `core`, or a
17
+ * comma-separated list of extra groups on top of core (`core,nuryel`). Unknown
18
+ * words are ignored rather than failing the server. */
19
+ export declare function parseToolsetSpec(spec: string): McpToolGroup[] | null;
20
+ /** A root that already stores nuryel state records is a state partition and
21
+ * needs the nuryel tools; every other root gets the everyday set by default. */
22
+ export declare function rootStoresState(root: string): boolean;
23
+ /** `pinned` is `hunch mcp --root <dir>`: a server dedicated to one root, which is
24
+ * how state partitions are served — including a brand-new partition that has no
25
+ * state records yet and could never receive its first nuryel_write otherwise. */
26
+ export declare function resolveMcpToolset(root: string, opts?: {
27
+ env?: NodeJS.ProcessEnv;
28
+ configSpec?: string | null;
29
+ pinned?: boolean;
30
+ }): McpToolset;
@@ -0,0 +1,72 @@
1
+ /** Which MCP tool groups a server exposes. Every host shares one server, so the
2
+ * selection surface is the same for all of them: 57 tools with ~24 KB of
3
+ * descriptions dilute tool choice for everyday grounding. The everyday set is
4
+ * the default; the two specialist groups are enabled by evidence (a root that
5
+ * stores nuryel state records), by `.hunch/config.json` `mcp_tools`, or by the
6
+ * `HUNCH_MCP_TOOLS` environment variable. Hidden tools are not registered at
7
+ * all, so a client never sees them in tools/list. */
8
+ import { existsSync, readdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { STATE_KINDS } from "../core/stateDelivery.js";
11
+ export const MCP_TOOL_GROUPS = {
12
+ /** nuryel.state/1 — the state-partition contract (Sofia-style agents). */
13
+ nuryel: ["nuryel_capabilities", "nuryel_read", "nuryel_write", "nuryel_capture", "nuryel_capture_batch", "nuryel_subscribe", "nuryel_records"],
14
+ /** Constitution G2/G3 experiment-track tools; the CLI remains the primary surface. */
15
+ "constitution-experiments": [
16
+ "hunch_constitution_g2_readiness", "hunch_constitution_g3_readiness", "hunch_constitution_g2_shadow_queue",
17
+ "hunch_constitution_g2_operational_drill", "hunch_constitution_g2_candidates", "hunch_constitution_g2_behavior_candidates",
18
+ "hunch_constitution_g2_behavior_replay", "hunch_constitution_g2_behavior_materialization", "hunch_constitution_g2_behavior_policy_materialize",
19
+ ],
20
+ };
21
+ export const MCP_TOOL_GROUP_NAMES = Object.keys(MCP_TOOL_GROUPS);
22
+ /** Grammar shared by the env var and the config value: `all`, `core`, or a
23
+ * comma-separated list of extra groups on top of core (`core,nuryel`). Unknown
24
+ * words are ignored rather than failing the server. */
25
+ export function parseToolsetSpec(spec) {
26
+ const words = spec.split(",").map(w => w.trim().toLowerCase()).filter(Boolean);
27
+ if (!words.length)
28
+ return null;
29
+ if (words.includes("all"))
30
+ return [...MCP_TOOL_GROUP_NAMES];
31
+ return MCP_TOOL_GROUP_NAMES.filter(g => words.includes(g));
32
+ }
33
+ /** A root that already stores nuryel state records is a state partition and
34
+ * needs the nuryel tools; every other root gets the everyday set by default. */
35
+ export function rootStoresState(root) {
36
+ return STATE_KINDS.some(kind => {
37
+ const dir = join(root, ".hunch", kind);
38
+ try {
39
+ return existsSync(dir) && readdirSync(dir).some(f => f.endsWith(".json"));
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ });
45
+ }
46
+ /** `pinned` is `hunch mcp --root <dir>`: a server dedicated to one root, which is
47
+ * how state partitions are served — including a brand-new partition that has no
48
+ * state records yet and could never receive its first nuryel_write otherwise. */
49
+ export function resolveMcpToolset(root, opts = {}) {
50
+ const env = opts.env ?? process.env;
51
+ let groups = null;
52
+ let source = "default";
53
+ const fromEnv = env.HUNCH_MCP_TOOLS?.trim();
54
+ if (fromEnv) {
55
+ groups = parseToolsetSpec(fromEnv);
56
+ if (groups)
57
+ source = "env";
58
+ }
59
+ if (!groups && opts.configSpec?.trim()) {
60
+ groups = parseToolsetSpec(opts.configSpec);
61
+ if (groups)
62
+ source = "config";
63
+ }
64
+ if (!groups) {
65
+ groups = opts.pinned || rootStoresState(root) ? ["nuryel"] : [];
66
+ source = "default";
67
+ }
68
+ const set = new Set(groups);
69
+ const hidden = MCP_TOOL_GROUP_NAMES.filter(g => !set.has(g)).flatMap(g => [...MCP_TOOL_GROUPS[g]]);
70
+ return { enabled: g => set.has(g), groups: [...set], hidden, source };
71
+ }
72
+ //# sourceMappingURL=toolset.js.map
@@ -806,6 +806,22 @@ export function writeState(store, input, opts = {}) {
806
806
  supersedes = null; // already closed by this record: nothing to close again, no second "superseded" event
807
807
  }
808
808
  }
809
+ // one-current-derived-per-subject-transform: a NEW current derived statement on a subject that
810
+ // already holds a current statement under the same transform must name it in `supersedes`.
811
+ // Otherwise a writer that never names its predecessor leaves a growing pile of "current"
812
+ // statements that every reader has to reconcile (season finding fnd_1939ced249: up to 58 on
813
+ // one subject over half a year). Writing the same identity again is an update or a replay of
814
+ // that record and is not affected; a different transform is a different statement.
815
+ if (facet === "derived" && record.state === "current") {
816
+ const d = record;
817
+ const incumbent = store.recsInHome("derived", home).find((r) => {
818
+ const x = r;
819
+ return x.id !== id && x.id !== supersedes && x.subject === d.subject && x.transform_version === d.transform_version && x.state === "current" && x.valid_to === null;
820
+ });
821
+ if (incumbent) {
822
+ throw new StateRefusal("conflict", `${d.subject} already has a current ${d.transform_version} statement ${incumbent.id}; pass supersedes: "${incumbent.id}" to replace it, or write that identity to update it`, { incumbent_id: incumbent.id, reason: "one-current-derived-per-subject-transform" });
823
+ }
824
+ }
809
825
  // The chain (Gate 4): a receipt names what it rested on, a closure names the receipt.
810
826
  // Both are checked against the drawer, grants first, before anything lands.
811
827
  if (facet === "receipts")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.32.2",
3
+ "version": "1.32.4",
4
4
  "mcpName": "io.github.davesheffer/hunch",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://www.hunchmemory.com",
10
- "version": "1.32.2",
10
+ "version": "1.32.4",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.32.2",
16
+ "version": "1.32.4",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {