holonovel 2026.9.7 → 2026.9.10
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/core/state.d.ts
CHANGED
|
@@ -506,7 +506,7 @@ export declare class StateManager {
|
|
|
506
506
|
narrative_tag?: string;
|
|
507
507
|
}>;
|
|
508
508
|
codex: Map<string, CodexEntry>;
|
|
509
|
-
readonly dataFormat = "
|
|
509
|
+
readonly dataFormat = "b2665b830a4076bc83619d7495e199f525ba72b040ec294bc5152c3c5d526d0d";
|
|
510
510
|
staleData: Map<string, string>;
|
|
511
511
|
corruptData: Map<string, string>;
|
|
512
512
|
private restoredThisSession;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PACKAGE_FORMAT = "2e36c2ccb9283086dd91056cd4571fd648854a4b83df4f391150e3942bcb85c8";
|
|
2
|
-
export declare const DATA_FORMAT = "
|
|
2
|
+
export declare const DATA_FORMAT = "b2665b830a4076bc83619d7495e199f525ba72b040ec294bc5152c3c5d526d0d";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/lib/contract-fingerprint.ts — do not edit by hand.
|
|
2
2
|
// Regenerated by build-order whenever the spec changes.
|
|
3
3
|
export const PACKAGE_FORMAT = "2e36c2ccb9283086dd91056cd4571fd648854a4b83df4f391150e3942bcb85c8";
|
|
4
|
-
export const DATA_FORMAT = "
|
|
4
|
+
export const DATA_FORMAT = "b2665b830a4076bc83619d7495e199f525ba72b040ec294bc5152c3c5d526d0d";
|
|
5
5
|
//# sourceMappingURL=contract-fingerprints.js.map
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,7 @@ state.buildFingerprint.lastSpecReview = new Date().toISOString();
|
|
|
54
54
|
// ── Server ─────────────────────────────────────────────────────────
|
|
55
55
|
const server = new McpServer({
|
|
56
56
|
name: "holonovel",
|
|
57
|
-
version: "2026.09.
|
|
57
|
+
version: "2026.09.10",
|
|
58
58
|
});
|
|
59
59
|
// REQ-426c — MCP Apps capability negotiation: the server declares the
|
|
60
60
|
// `io.modelcontextprotocol/ui` extension in its capabilities; a client that
|
|
@@ -62,22 +62,43 @@ const server = new McpServer({
|
|
|
62
62
|
// appsNegotiated() below). REQ-426a — UI resources are served under the
|
|
63
63
|
// `ui://` scheme as `text/html;profile=mcp-app`.
|
|
64
64
|
server.server.registerCapabilities({ extensions: { "io.modelcontextprotocol/ui": {} } });
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// violation_type: boundary) in the Novel audit log before propagating.
|
|
68
|
-
// REQ-429 — server-wide action-discriminator surface: one tool per persisted
|
|
69
|
-
// entity type within a twenty-eight-tool budget; every persisted type has a
|
|
70
|
-
// list/get/info/status/knowledge action; uniform verb-noun/noun+action naming.
|
|
71
|
-
// REQ-450 — TDQS-conformant tool definitions: every registered tool carries a
|
|
72
|
-
// mutation-class annotation (read-only vs open-world) so callers see behavior
|
|
73
|
-
// hints beyond the description.
|
|
65
|
+
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
66
|
+
const MUTATING = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
74
67
|
const TOOL_ANNOTATIONS = {
|
|
75
|
-
help:
|
|
68
|
+
help: READ_ONLY,
|
|
69
|
+
set_badge: MUTATING,
|
|
70
|
+
respond: MUTATING,
|
|
71
|
+
undo: MUTATING,
|
|
72
|
+
redo: MUTATING,
|
|
73
|
+
character: MUTATING,
|
|
74
|
+
npc: MUTATING,
|
|
75
|
+
world: MUTATING,
|
|
76
|
+
command: MUTATING,
|
|
77
|
+
combat: MUTATING,
|
|
78
|
+
scene: MUTATING,
|
|
79
|
+
countdown: MUTATING,
|
|
80
|
+
lore: MUTATING,
|
|
81
|
+
condition: MUTATING,
|
|
82
|
+
faction: MUTATING,
|
|
83
|
+
relationship: MUTATING,
|
|
84
|
+
vow: MUTATING,
|
|
85
|
+
fate: MUTATING,
|
|
86
|
+
ironsworn: MUTATING,
|
|
87
|
+
forged: MUTATING,
|
|
88
|
+
story: MUTATING,
|
|
89
|
+
note: MUTATING,
|
|
90
|
+
session: MUTATING,
|
|
91
|
+
adventure: MUTATING,
|
|
92
|
+
novel: MUTATING,
|
|
93
|
+
ruleset: MUTATING,
|
|
94
|
+
codex: MUTATING,
|
|
95
|
+
synthesis: MUTATING,
|
|
76
96
|
};
|
|
77
|
-
const DEFAULT_TOOL_ANNOTATIONS = { readOnlyHint: false, openWorldHint: true };
|
|
78
97
|
const _registerTool = server.registerTool.bind(server);
|
|
79
98
|
server.registerTool = ((name, config, handler) => {
|
|
80
|
-
const annotations = TOOL_ANNOTATIONS[name]
|
|
99
|
+
const annotations = config.annotations ?? TOOL_ANNOTATIONS[name];
|
|
100
|
+
if (!annotations)
|
|
101
|
+
throw new Error(`Tool '${name}' has no REQ-450 mutation-class annotation; add it to TOOL_ANNOTATIONS.`);
|
|
81
102
|
return _registerTool(name, { ...config, annotations }, withForbiddenAudit(handler, name));
|
|
82
103
|
});
|
|
83
104
|
// ── §5.12 Narrative Architecture helpers (REQ-335 through REQ-366) ──
|
|
@@ -731,11 +752,18 @@ function gatedRulesetTool(slug, schema, toolName) {
|
|
|
731
752
|
for (const slug of rulesets.installedSlugs()) {
|
|
732
753
|
for (const schema of rulesets.toolSchemas(slug)) {
|
|
733
754
|
const toolName = `${slug}_${schema.name}`;
|
|
755
|
+
// REQ-450 — ruleset-derived tools read indexed data (lookup/search/info:
|
|
756
|
+
// idempotent) or generate content (roll/table): neither mutates Novel
|
|
757
|
+
// state, so destructiveHint is false for every ruleset-derived kind.
|
|
758
|
+
const annotations = (schema.kind === "lookup" || schema.kind === "search" || schema.kind === "info")
|
|
759
|
+
? { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
760
|
+
: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
734
761
|
try {
|
|
735
762
|
server.registerTool(toolName, {
|
|
736
763
|
title: schema.title ?? schema.name,
|
|
737
764
|
description: `${schema.description ?? ""} (ruleset: ${slug})`,
|
|
738
765
|
inputSchema: jsonSchemaToZod(schema.inputSchema),
|
|
766
|
+
annotations,
|
|
739
767
|
}, gatedRulesetTool(slug, schema, toolName));
|
|
740
768
|
}
|
|
741
769
|
catch (e) {
|
|
@@ -1651,6 +1679,8 @@ server.registerTool("redo", {
|
|
|
1651
1679
|
state.redo(novel, getBadge());
|
|
1652
1680
|
return ok("Redo successful.");
|
|
1653
1681
|
});
|
|
1682
|
+
// REQ-067 — Help and tool discovery: badge-filtered categorized task map with
|
|
1683
|
+
// query search and Novel-scoped category reassignment (T62/T118).
|
|
1654
1684
|
server.registerTool("help", {
|
|
1655
1685
|
title: "Help and Tool Discovery",
|
|
1656
1686
|
description: "Show the available tools grouped by category (badge-filtered) or reassign a tool's category. Use when: the caller needs to discover tools, find one by keyword, or override a tool's category for a session. Do NOT use when: reading the current badge's guidance — use the badge_briefing prompt.",
|
|
@@ -1727,16 +1757,35 @@ server.registerTool("help", {
|
|
|
1727
1757
|
return raw(top.map(m => `**${m.name}** — ${m.description}\nExample: ${m.example}`).join("\n\n"));
|
|
1728
1758
|
}
|
|
1729
1759
|
const builderCategories = BUILDER_CATEGORIES;
|
|
1760
|
+
// REQ-067b — GM category reassignment: an overridden tool leaves its
|
|
1761
|
+
// builder-assigned category and renders under the user-defined category.
|
|
1762
|
+
// Player badge always reflects builder-assigned categories.
|
|
1763
|
+
const overrides = novel?.help_category_overrides ?? {};
|
|
1764
|
+
const overriddenTools = new Set(Object.keys(overrides));
|
|
1730
1765
|
let result = "## Holonovel MCP Server\n\n### Tool Categories\n\n";
|
|
1731
1766
|
for (const [cat, tools] of Object.entries(builderCategories)) {
|
|
1732
1767
|
let displayTools = [...tools];
|
|
1733
|
-
if (
|
|
1734
|
-
displayTools =
|
|
1768
|
+
if (isGM) {
|
|
1769
|
+
displayTools = displayTools.filter(t => !overriddenTools.has(t));
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
displayTools = displayTools.filter(t => !GMToolsSet.has(t) && !(t === "command" && novel?.ruleset));
|
|
1735
1773
|
}
|
|
1736
1774
|
if (displayTools.length > 0) {
|
|
1737
1775
|
result += `**${cat}:** ${displayTools.join(", ")}\n`;
|
|
1738
1776
|
}
|
|
1739
1777
|
}
|
|
1778
|
+
if (isGM) {
|
|
1779
|
+
const userCategories = new Map();
|
|
1780
|
+
for (const [tool, cat] of Object.entries(overrides)) {
|
|
1781
|
+
if (!userCategories.has(cat))
|
|
1782
|
+
userCategories.set(cat, []);
|
|
1783
|
+
userCategories.get(cat).push(tool);
|
|
1784
|
+
}
|
|
1785
|
+
for (const [cat, tools] of userCategories) {
|
|
1786
|
+
result += `**${cat}:** ${tools.join(", ")}\n`;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1740
1789
|
result += "\nUse the intro prompt to get started, or badge_briefing for current badge guidance.";
|
|
1741
1790
|
// Add world-model hint if populated
|
|
1742
1791
|
if (novel && novel.world.rooms.size > 0) {
|