@ghostpaw/questlog 0.9.0 → 0.9.2

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.
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { registerSchemaActions, minimalInput, requiredForAction } from './chunk-QYSIJ2YN.js';
3
+
2
4
  // src/questlines/map_questline_row.ts
3
5
  function mapQuestlineRow(row) {
4
6
  return {
@@ -1080,8 +1082,181 @@ function listRumors(db) {
1080
1082
  return db.prepare("SELECT * FROM rumors WHERE deleted_at IS NULL ORDER BY created_at DESC, id DESC").all().map(mapRumorRow);
1081
1083
  }
1082
1084
 
1085
+ // src/surfaces/validate_input.ts
1086
+ function validateInput(schema, input, path = "$") {
1087
+ const errors = [];
1088
+ const matchesType = (type) => {
1089
+ switch (type) {
1090
+ case "object":
1091
+ return typeof input === "object" && input !== null && !Array.isArray(input);
1092
+ case "array":
1093
+ return Array.isArray(input);
1094
+ case "integer":
1095
+ return typeof input === "number" && Number.isInteger(input);
1096
+ case "number":
1097
+ return typeof input === "number" && Number.isFinite(input);
1098
+ case "null":
1099
+ return input === null;
1100
+ default:
1101
+ return typeof input === type;
1102
+ }
1103
+ };
1104
+ if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(matchesType)) {
1105
+ return [`${path}: expected ${String(schema.type)}`];
1106
+ }
1107
+ if (schema.enum && !schema.enum.some((value) => JSON.stringify(value) === JSON.stringify(input)))
1108
+ errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
1109
+ const constant = "const" in schema ? schema.const : void 0;
1110
+ if (constant !== void 0 && constant !== input)
1111
+ errors.push(`${path}: expected ${JSON.stringify(constant)}`);
1112
+ if (schema.oneOf) {
1113
+ const branches = schema.oneOf.map((branch) => validateInput(branch, input, path));
1114
+ if (!branches.some((errors2) => errors2.length === 0)) {
1115
+ errors.push(
1116
+ ...branches.sort((a, b) => a.length - b.length)[0] ?? [
1117
+ `${path}: must match at least one schema branch`
1118
+ ]
1119
+ );
1120
+ }
1121
+ }
1122
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
1123
+ const record = input;
1124
+ for (const name of [...schema.required ?? [], ...requiredForAction(schema, record.action)])
1125
+ if (!Object.hasOwn(record, name)) errors.push(`${path}.${name}: required`);
1126
+ for (const [name, property] of Object.entries(schema.properties ?? {}))
1127
+ if (Object.hasOwn(record, name))
1128
+ errors.push(...validateInput(property, record[name], `${path}.${name}`));
1129
+ }
1130
+ if (Array.isArray(input) && schema.items)
1131
+ input.forEach((value, index) => {
1132
+ errors.push(...validateInput(schema.items, value, `${path}[${index}]`));
1133
+ });
1134
+ return errors;
1135
+ }
1136
+
1137
+ // src/tools/action_requirements.ts
1138
+ var actionRequirements = {
1139
+ shape_work: {
1140
+ create_quest: ["quest"],
1141
+ create_questline: ["questline"],
1142
+ attach_quest_to_questline: ["questId", "questlineId"],
1143
+ detach_quest_from_questline: ["questId"],
1144
+ dismiss_rumor: ["rumorId"],
1145
+ reopen_rumor: ["rumorId"],
1146
+ settle_rumor: ["rumorId"],
1147
+ update_rumor: ["rumorId"]
1148
+ },
1149
+ manage_repeatable: {
1150
+ create: ["repeatableQuest"],
1151
+ archive: ["repeatableQuestId"],
1152
+ update: ["repeatableQuestId"],
1153
+ spawn_due: ["now"]
1154
+ },
1155
+ organize_work: {
1156
+ add_unlock: ["fromQuestId", "toQuestId"],
1157
+ remove_unlock: ["fromQuestId", "toQuestId"],
1158
+ replace_unlocks: ["fromQuestIds", "toQuestId"],
1159
+ archive_questline: ["questlineId"],
1160
+ set_questline_fields: ["questlineId"]
1161
+ },
1162
+ plan_quest: { revise_objective: ["objective"] },
1163
+ run_quest: {
1164
+ abandon_and_spawn_followups: ["followups"],
1165
+ log_effort: ["effortSeconds"]
1166
+ },
1167
+ reward_work: { add: ["reward"], update: ["reward"], replace_repeatable_template: ["rewards"] }
1168
+ };
1169
+
1170
+ // src/tools/tool_types.ts
1171
+ function withOptionalFields(result, options) {
1172
+ if (options.next && options.next.length > 0) {
1173
+ result.next = options.next;
1174
+ }
1175
+ if (options.warnings && options.warnings.length > 0) {
1176
+ result.warnings = options.warnings;
1177
+ }
1178
+ return result;
1179
+ }
1180
+ function toolWarning(code, message) {
1181
+ return { code, message };
1182
+ }
1183
+ function toolSuccess(summary, data, options = {}) {
1184
+ return withOptionalFields(
1185
+ {
1186
+ ok: true,
1187
+ outcome: "success",
1188
+ summary,
1189
+ entities: options.entities ?? [],
1190
+ data
1191
+ },
1192
+ options
1193
+ );
1194
+ }
1195
+ function toolNoOp(summary, data, options = {}) {
1196
+ return withOptionalFields(
1197
+ {
1198
+ ok: true,
1199
+ outcome: "no_op",
1200
+ summary,
1201
+ entities: options.entities ?? [],
1202
+ data
1203
+ },
1204
+ options
1205
+ );
1206
+ }
1207
+ function toolNeedsClarification(code, question, missing, options = {}) {
1208
+ return withOptionalFields(
1209
+ {
1210
+ ok: false,
1211
+ outcome: "needs_clarification",
1212
+ summary: question,
1213
+ entities: options.entities ?? [],
1214
+ clarification: {
1215
+ code,
1216
+ question,
1217
+ missing,
1218
+ options: options.options
1219
+ }
1220
+ },
1221
+ options
1222
+ );
1223
+ }
1224
+ function toolFailure(kind, code, summary, message, options = {}) {
1225
+ return withOptionalFields(
1226
+ {
1227
+ ok: false,
1228
+ outcome: "error",
1229
+ summary,
1230
+ entities: options.entities ?? [],
1231
+ error: {
1232
+ kind,
1233
+ code,
1234
+ message,
1235
+ recovery: options.recovery,
1236
+ details: options.details
1237
+ }
1238
+ },
1239
+ options
1240
+ );
1241
+ }
1242
+
1083
1243
  // src/tools/tool_metadata.ts
1084
1244
  function defineQuestlogTool(definition) {
1245
+ const requirements = actionRequirements[definition.name];
1246
+ if (requirements) registerSchemaActions(definition.inputSchema, requirements);
1247
+ const handler = definition.handler;
1248
+ definition.handler = (db, input) => {
1249
+ const errors = validateInput(definition.inputSchema, input);
1250
+ if (errors.length) {
1251
+ const action = input?.action;
1252
+ const example = minimalInput(definition.inputSchema, action);
1253
+ const message = errors.join("; ");
1254
+ return toolFailure("protocol", "invalid_input", message, message, {
1255
+ recovery: `${message}; pass ${JSON.stringify(example)}`
1256
+ });
1257
+ }
1258
+ return handler(db, input);
1259
+ };
1085
1260
  return definition;
1086
1261
  }
1087
1262
  function stringSchema(description, options = {}) {
@@ -1243,79 +1418,6 @@ function summarizeCount(count, singular, plural = `${singular}s`) {
1243
1418
  return `Found ${count} ${plural}.`;
1244
1419
  }
1245
1420
 
1246
- // src/tools/tool_types.ts
1247
- function withOptionalFields(result, options) {
1248
- if (options.next && options.next.length > 0) {
1249
- result.next = options.next;
1250
- }
1251
- if (options.warnings && options.warnings.length > 0) {
1252
- result.warnings = options.warnings;
1253
- }
1254
- return result;
1255
- }
1256
- function toolWarning(code, message) {
1257
- return { code, message };
1258
- }
1259
- function toolSuccess(summary, data, options = {}) {
1260
- return withOptionalFields(
1261
- {
1262
- ok: true,
1263
- outcome: "success",
1264
- summary,
1265
- entities: options.entities ?? [],
1266
- data
1267
- },
1268
- options
1269
- );
1270
- }
1271
- function toolNoOp(summary, data, options = {}) {
1272
- return withOptionalFields(
1273
- {
1274
- ok: true,
1275
- outcome: "no_op",
1276
- summary,
1277
- entities: options.entities ?? [],
1278
- data
1279
- },
1280
- options
1281
- );
1282
- }
1283
- function toolNeedsClarification(code, question, missing, options = {}) {
1284
- return withOptionalFields(
1285
- {
1286
- ok: false,
1287
- outcome: "needs_clarification",
1288
- summary: question,
1289
- entities: options.entities ?? [],
1290
- clarification: {
1291
- code,
1292
- question,
1293
- missing,
1294
- options: options.options
1295
- }
1296
- },
1297
- options
1298
- );
1299
- }
1300
- function toolFailure(kind, code, summary, message, options = {}) {
1301
- return withOptionalFields(
1302
- {
1303
- ok: false,
1304
- outcome: "error",
1305
- summary,
1306
- entities: options.entities ?? [],
1307
- error: {
1308
- kind,
1309
- code,
1310
- message,
1311
- recovery: options.recovery,
1312
- details: options.details
1313
- }
1314
- },
1315
- options
1316
- );
1317
- }
1318
-
1319
1421
  // src/tools/review_questlog_tool.ts
1320
1422
  function hasQuestFilters(filters) {
1321
1423
  return Boolean(
@@ -3191,7 +3293,7 @@ var rewardWorkTool = defineQuestlogTool({
3191
3293
  name: stringSchema("Reward name."),
3192
3294
  now: integerSchema("Optional update timestamp for the update action.")
3193
3295
  },
3194
- []
3296
+ ["kind", "name"]
3195
3297
  ),
3196
3298
  rewards: arraySchema(
3197
3299
  objectSchema(
@@ -4018,7 +4120,7 @@ var shapeWorkTool = defineQuestlogTool({
4018
4120
  title: stringSchema("Questline title."),
4019
4121
  description: stringSchema("Optional questline description.")
4020
4122
  },
4021
- []
4123
+ ["title"]
4022
4124
  ),
4023
4125
  rumorId: integerSchema("Rumor to settle, dismiss, or reopen."),
4024
4126
  dismissedAt: integerSchema("Optional dismissal timestamp."),
@@ -4300,9 +4402,9 @@ var questlogSurfaceTools = questlogTools.map(
4300
4402
  whenNotToUse: tool.whenNotToUse ?? "",
4301
4403
  readOnly: tool.readOnly,
4302
4404
  sideEffects: tool.sideEffects,
4303
- inputSchema: { ...tool.inputSchema, type: "object" },
4405
+ inputSchema: tool.inputSchema,
4304
4406
  handler: tool.handler
4305
4407
  })
4306
4408
  );
4307
4409
 
4308
- export { getQuestDetail, questlogSurfaceTools, reviewQuestlogTool };
4410
+ export { getQuestDetail, questlogSurfaceTools, reviewQuestlogTool, validateInput };
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  function checkNodeVersion(version, stderr) {
4
4
  if (Number(version.split(".")[0]) >= 24) return true;
5
5
  stderr.write(
6
- `questlog needs Node 24+ (found ${version}). Install Node 24 or put it first on PATH.
6
+ `questlog needs Node 24+ (found ${version}). Install Node 24+ (e.g. via nvm, mise or your package manager) or put it first on PATH.
7
7
  `
8
8
  );
9
9
  return false;
@@ -13,5 +13,5 @@ function checkNodeVersion(version, stderr) {
13
13
  if (!checkNodeVersion(process.versions.node, process.stderr)) {
14
14
  process.exitCode = 2;
15
15
  } else {
16
- await import('./cli_main-ED5BJPSR.js');
16
+ await import('./cli_main-XNZP4TY2.js');
17
17
  }
@@ -1,9 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { validateInput, isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-RUSE7ZFF.js';
3
- import { reviewQuestlogTool, getQuestDetail, questlogSurfaceTools } from './chunk-3Y5F3SMB.js';
2
+ import { isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-DKH623HU.js';
3
+ import { questlogSkills } from './chunk-6KTQK3ZE.js';
4
+ import { reviewQuestlogTool, validateInput, getQuestDetail, questlogSurfaceTools } from './chunk-UTUQPSGO.js';
5
+ import { minimalInput } from './chunk-QYSIJ2YN.js';
6
+ import { isSea } from 'node:sea';
4
7
  import { readFileSync, existsSync } from 'node:fs';
5
8
  import { resolve } from 'node:path';
6
9
 
10
+ var npmEntry = false;
11
+ function markNpmEntry() {
12
+ npmEntry = true;
13
+ }
14
+ function cliVersion(version, {
15
+ sea = isSea,
16
+ npm = npmEntry,
17
+ platform = process.platform,
18
+ arch = process.arch
19
+ } = {}) {
20
+ let binary = false;
21
+ try {
22
+ binary = sea();
23
+ } catch {
24
+ }
25
+ const target = `${platform === "win32" ? "windows" : platform}-${arch}`;
26
+ return `questlog ${version} (${binary ? `binary, ${target}` : npm ? "npm" : "library"})`;
27
+ }
7
28
  var CliUsageError = class extends Error {
8
29
  exitCode = 2;
9
30
  };
@@ -71,7 +92,7 @@ function parseCliArgs(argv, spec) {
71
92
  if (arg.type === "boolean" && raw === void 0) raw = negative ? "false" : "true";
72
93
  if (raw === void 0) {
73
94
  const next = argv[i + 1];
74
- if (next === void 0 || next.startsWith("--"))
95
+ if (next === void 0 || /^--[a-z]/i.test(next))
75
96
  throw new CliUsageError(`Missing value: --${arg.name}`);
76
97
  raw = next;
77
98
  i++;
@@ -202,7 +223,7 @@ ${r.error.message}
202
223
  ["title", detail.title],
203
224
  ["objective", detail.objective],
204
225
  ["state", detail.state],
205
- ["tags", detail.tagNames.map((tag) => `#${tag}`).join(" ")],
226
+ ["tags", detail.tagNames.length ? detail.tagNames.map((tag) => `#${tag}`).join(" ") : "-"],
206
227
  ["due", detail.effectiveDueAt == null ? "-" : formatPlainDate(detail.effectiveDueAt, true)],
207
228
  ["created", formatPlainDate(detail.createdAt, true)]
208
229
  ];
@@ -240,9 +261,10 @@ function questlogPlainCommands() {
240
261
  {
241
262
  spec: {
242
263
  command: "add",
243
- description: "Create a quest. Objective defaults to the title.",
264
+ description: 'Create a quest: add "My title" or add --title "My title". Objective defaults to the title.',
265
+ positionals: [{ name: "positionalTitle", type: "string" }],
244
266
  flags: [
245
- { name: "title", type: "string", required: true },
267
+ { name: "title", type: "string" },
246
268
  { name: "tag", type: "array" },
247
269
  { name: "objective", type: "string" },
248
270
  {
@@ -255,9 +277,15 @@ function questlogPlainCommands() {
255
277
  },
256
278
  tool: "shape_work",
257
279
  input: (v) => {
280
+ if (v.title !== void 0 && v.positionalTitle !== void 0)
281
+ throw new CliUsageError(
282
+ "give the title once: either as the first argument or with --title"
283
+ );
284
+ const title = v.positionalTitle ?? v.title;
285
+ if (title === void 0) throw new CliUsageError("Missing required argument: title");
258
286
  const quest = {
259
- title: v.title,
260
- objective: v.objective ?? v.title
287
+ title,
288
+ objective: v.objective ?? title
261
289
  };
262
290
  if (v.tag !== void 0)
263
291
  quest.tags = v.tag.flatMap(
@@ -342,6 +370,7 @@ function selectedDbPath(options) {
342
370
  var questlogCliOptions = {
343
371
  bin: "ghostpaw-questlog",
344
372
  version: questlogVersion,
373
+ versionLine: () => cliVersion(questlogVersion),
345
374
  description: "Local task and calendar library",
346
375
  tools: questlogSurfaceTools,
347
376
  describeDb: (options) => `Database: ${selectedDbPath(options)}
@@ -358,13 +387,14 @@ Use --db <path> or GHOSTPAW_QUESTLOG_DB to override the default ~/.ghostpaw/ques
358
387
  closeContext: (db) => db.close(),
359
388
  commands: {
360
389
  plain: questlogPlainCommands(),
390
+ guides: questlogSkills,
361
391
  mcp: async (options) => {
362
- const { serveQuestlogMcp } = await import('./serve_questlog_mcp-J4ZHNEOT.js');
392
+ const { serveQuestlogMcp } = await import('./serve_questlog_mcp-KFUYJB2P.js');
363
393
  await serveQuestlogMcp(options.db === void 0 ? {} : { dbPath: options.db });
364
394
  },
365
395
  skill: async () => {
366
- const { renderSkillMarkdown } = await import('./skill_markdown-7HCMSJYR.js');
367
- const { questlogSkillSpec } = await import('./questlog_skill_spec-JLZH3JL2.js');
396
+ const { renderSkillMarkdown } = await import('./skill_markdown-HXYQ3K6U.js');
397
+ const { questlogSkillSpec } = await import('./questlog_skill_spec-EM6DGGVN.js');
368
398
  return renderSkillMarkdown(questlogSkillSpec);
369
399
  }
370
400
  }
@@ -452,7 +482,7 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
452
482
  return 0;
453
483
  }
454
484
  if (["--version", "-v"].includes(command)) {
455
- stdout.write(`${options.version}
485
+ stdout.write(`${options.versionLine?.() ?? options.version}
456
486
  `);
457
487
  return 0;
458
488
  }
@@ -470,7 +500,26 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
470
500
  }
471
501
  if (command === "skill") {
472
502
  if (!options.commands?.skill) return fail("skill command unavailable");
473
- stdout.write(await options.commands.skill());
503
+ if (args.length > 1) return fail(`Usage: ${options.bin} skill [--list | <guide> | --help]`);
504
+ const guide = args[0];
505
+ if (guide === "--help" || guide === "-h") {
506
+ stdout.write(
507
+ `Usage:
508
+ ${options.bin} skill
509
+ ${options.bin} skill --list
510
+ ${options.bin} skill <guide>
511
+ `
512
+ );
513
+ } else if (guide === "--list") {
514
+ stdout.write(
515
+ `${(options.commands.guides ?? []).map((entry) => `${entry.name} ${entry.description.replace(/\s+/g, " ")}`).join("\n")}
516
+ `
517
+ );
518
+ } else if (guide) {
519
+ const entry = options.commands.guides?.find((entry2) => entry2.name === guide);
520
+ if (!entry) return fail(`unknown guide ${guide}; run ${options.bin} skill --list`);
521
+ stdout.write(entry.content);
522
+ } else stdout.write(await options.commands.skill());
474
523
  return 0;
475
524
  }
476
525
  if (command === "mcp") {
@@ -599,7 +648,11 @@ ${JSON.stringify(tool.inputSchema, null, 2)}
599
648
  return fail(`Invalid input: ${cause instanceof Error ? cause.message : String(cause)}`, 1);
600
649
  }
601
650
  const errors = validateInput(tool.inputSchema, input);
602
- if (errors.length) return fail(errors.join("\n"), 1);
651
+ if (errors.length)
652
+ return fail(
653
+ `${errors.join("\n")}; pass ${JSON.stringify(minimalInput(tool.inputSchema, input?.action))}`,
654
+ 1
655
+ );
603
656
  let context;
604
657
  try {
605
658
  context = await options.openContext(ctxOptions);
@@ -623,4 +676,5 @@ ${JSON.stringify(tool.inputSchema, null, 2)}
623
676
  }
624
677
 
625
678
  // src/cli_main.ts
679
+ markNpmEntry();
626
680
  process.exitCode = await runToolCli(process.argv.slice(2), questlogCliOptions);
package/dist/index.cjs CHANGED
@@ -3183,8 +3183,153 @@ function translateToolError(error, options = {}) {
3183
3183
  );
3184
3184
  }
3185
3185
 
3186
+ // src/surfaces/schema_actions.ts
3187
+ var requirements = /* @__PURE__ */ new WeakMap();
3188
+ function registerSchemaActions(schema, actions) {
3189
+ requirements.set(schema, actions);
3190
+ }
3191
+ function requiredForAction(schema, action) {
3192
+ return requirements.get(schema)?.[String(action)] ?? [];
3193
+ }
3194
+
3195
+ // src/surfaces/schema_examples.ts
3196
+ function minimalInput(schema, action, name = "") {
3197
+ const constant = "const" in schema ? schema.const : void 0;
3198
+ if (constant !== void 0) return constant;
3199
+ if (schema.enum)
3200
+ return action !== void 0 && schema.enum.includes(action) ? action : schema.enum[0];
3201
+ const branch = schema.oneOf?.[0];
3202
+ if (schema.type === "object" || schema.properties) {
3203
+ action ??= schema.properties?.action?.enum?.[0];
3204
+ const properties = { ...schema.properties, ...branch?.properties };
3205
+ return Object.fromEntries(
3206
+ [
3207
+ .../* @__PURE__ */ new Set([
3208
+ ...schema.required ?? [],
3209
+ ...requiredForAction(schema, action),
3210
+ ...schema.properties?.outcome && ["finish", "abandon", "abandon_and_spawn_followups"].includes(String(action)) ? ["outcome"] : [],
3211
+ ...branch?.required ?? []
3212
+ ])
3213
+ ].map((key) => [
3214
+ key,
3215
+ minimalInput(properties[key] ?? {}, key === "action" ? action : void 0, key)
3216
+ ])
3217
+ );
3218
+ }
3219
+ if (branch) return minimalInput(branch, action, name);
3220
+ if (schema.type === "array") return [minimalInput(schema.items ?? {}, void 0, name)];
3221
+ if (schema.type === "integer" || schema.type === "number")
3222
+ return name === "anchorAt" ? 18e11 : name === "toQuestId" ? 2 : 1;
3223
+ if (schema.type === "boolean") return false;
3224
+ if (schema.type === "null") return null;
3225
+ if (name === "rrule") return "FREQ=WEEKLY";
3226
+ if (name === "title") return "Review release notes";
3227
+ if (name === "objective") return "Check changes against the release";
3228
+ return "release";
3229
+ }
3230
+
3231
+ // src/surfaces/validate_input.ts
3232
+ function validateInput(schema, input, path = "$") {
3233
+ const errors = [];
3234
+ const matchesType = (type) => {
3235
+ switch (type) {
3236
+ case "object":
3237
+ return typeof input === "object" && input !== null && !Array.isArray(input);
3238
+ case "array":
3239
+ return Array.isArray(input);
3240
+ case "integer":
3241
+ return typeof input === "number" && Number.isInteger(input);
3242
+ case "number":
3243
+ return typeof input === "number" && Number.isFinite(input);
3244
+ case "null":
3245
+ return input === null;
3246
+ default:
3247
+ return typeof input === type;
3248
+ }
3249
+ };
3250
+ if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(matchesType)) {
3251
+ return [`${path}: expected ${String(schema.type)}`];
3252
+ }
3253
+ if (schema.enum && !schema.enum.some((value) => JSON.stringify(value) === JSON.stringify(input)))
3254
+ errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
3255
+ const constant = "const" in schema ? schema.const : void 0;
3256
+ if (constant !== void 0 && constant !== input)
3257
+ errors.push(`${path}: expected ${JSON.stringify(constant)}`);
3258
+ if (schema.oneOf) {
3259
+ const branches = schema.oneOf.map((branch) => validateInput(branch, input, path));
3260
+ if (!branches.some((errors2) => errors2.length === 0)) {
3261
+ errors.push(
3262
+ ...branches.sort((a, b) => a.length - b.length)[0] ?? [
3263
+ `${path}: must match at least one schema branch`
3264
+ ]
3265
+ );
3266
+ }
3267
+ }
3268
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3269
+ const record = input;
3270
+ for (const name of [...schema.required ?? [], ...requiredForAction(schema, record.action)])
3271
+ if (!Object.hasOwn(record, name)) errors.push(`${path}.${name}: required`);
3272
+ for (const [name, property] of Object.entries(schema.properties ?? {}))
3273
+ if (Object.hasOwn(record, name))
3274
+ errors.push(...validateInput(property, record[name], `${path}.${name}`));
3275
+ }
3276
+ if (Array.isArray(input) && schema.items)
3277
+ input.forEach((value, index) => {
3278
+ errors.push(...validateInput(schema.items, value, `${path}[${index}]`));
3279
+ });
3280
+ return errors;
3281
+ }
3282
+
3283
+ // src/tools/action_requirements.ts
3284
+ var actionRequirements = {
3285
+ shape_work: {
3286
+ create_quest: ["quest"],
3287
+ create_questline: ["questline"],
3288
+ attach_quest_to_questline: ["questId", "questlineId"],
3289
+ detach_quest_from_questline: ["questId"],
3290
+ dismiss_rumor: ["rumorId"],
3291
+ reopen_rumor: ["rumorId"],
3292
+ settle_rumor: ["rumorId"],
3293
+ update_rumor: ["rumorId"]
3294
+ },
3295
+ manage_repeatable: {
3296
+ create: ["repeatableQuest"],
3297
+ archive: ["repeatableQuestId"],
3298
+ update: ["repeatableQuestId"],
3299
+ spawn_due: ["now"]
3300
+ },
3301
+ organize_work: {
3302
+ add_unlock: ["fromQuestId", "toQuestId"],
3303
+ remove_unlock: ["fromQuestId", "toQuestId"],
3304
+ replace_unlocks: ["fromQuestIds", "toQuestId"],
3305
+ archive_questline: ["questlineId"],
3306
+ set_questline_fields: ["questlineId"]
3307
+ },
3308
+ plan_quest: { revise_objective: ["objective"] },
3309
+ run_quest: {
3310
+ abandon_and_spawn_followups: ["followups"],
3311
+ log_effort: ["effortSeconds"]
3312
+ },
3313
+ reward_work: { add: ["reward"], update: ["reward"], replace_repeatable_template: ["rewards"] }
3314
+ };
3315
+
3186
3316
  // src/tools/tool_metadata.ts
3187
3317
  function defineQuestlogTool(definition) {
3318
+ const requirements2 = actionRequirements[definition.name];
3319
+ if (requirements2) registerSchemaActions(definition.inputSchema, requirements2);
3320
+ const handler = definition.handler;
3321
+ definition.handler = (db, input) => {
3322
+ const errors = validateInput(definition.inputSchema, input);
3323
+ if (errors.length) {
3324
+ const action = input?.action;
3325
+ const example = minimalInput(definition.inputSchema, action);
3326
+ const message = errors.join("; ");
3327
+ return toolFailure("protocol", "invalid_input", message, message, {
3328
+ recovery: `${message}; pass ${JSON.stringify(example)}`
3329
+ });
3330
+ }
3331
+ return handler(db, input);
3332
+ };
3188
3333
  return definition;
3189
3334
  }
3190
3335
  function stringSchema(description, options = {}) {
@@ -4727,7 +4872,7 @@ var rewardWorkTool = defineQuestlogTool({
4727
4872
  name: stringSchema("Reward name."),
4728
4873
  now: integerSchema("Optional update timestamp for the update action.")
4729
4874
  },
4730
- []
4875
+ ["kind", "name"]
4731
4876
  ),
4732
4877
  rewards: arraySchema(
4733
4878
  objectSchema(
@@ -5393,7 +5538,7 @@ var shapeWorkTool = defineQuestlogTool({
5393
5538
  title: stringSchema("Questline title."),
5394
5539
  description: stringSchema("Optional questline description.")
5395
5540
  },
5396
- []
5541
+ ["title"]
5397
5542
  ),
5398
5543
  rumorId: integerSchema("Rumor to settle, dismiss, or reopen."),
5399
5544
  dismissedAt: integerSchema("Optional dismissal timestamp."),