@ghostpaw/questlog 0.9.1 → 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.
package/dist/mcp.js CHANGED
@@ -384,7 +384,7 @@ async function openQuestlogDb(path) {
384
384
  }
385
385
 
386
386
  // src/version.ts
387
- var questlogVersion = "0.9.1";
387
+ var questlogVersion = "0.9.2";
388
388
 
389
389
  // src/surface/questlog_mcp_instructions.ts
390
390
  var questlogMcpInstructions = "Questlog manages local tasks, calendar commitments, and recurring work. Search before creating work, inspect before changing it, and keep uncertain work as rumors. Use unlock prerequisites to sequence work. run_quest refuses blocked starts unless force is true. Review commitments and resolve work with an honest outcome.";
@@ -630,8 +630,153 @@ function translateToolError(error, options = {}) {
630
630
  );
631
631
  }
632
632
 
633
+ // src/surfaces/schema_actions.ts
634
+ var requirements = /* @__PURE__ */ new WeakMap();
635
+ function registerSchemaActions(schema, actions) {
636
+ requirements.set(schema, actions);
637
+ }
638
+ function requiredForAction(schema, action) {
639
+ return requirements.get(schema)?.[String(action)] ?? [];
640
+ }
641
+
642
+ // src/surfaces/schema_examples.ts
643
+ function minimalInput(schema, action, name = "") {
644
+ const constant = "const" in schema ? schema.const : void 0;
645
+ if (constant !== void 0) return constant;
646
+ if (schema.enum)
647
+ return action !== void 0 && schema.enum.includes(action) ? action : schema.enum[0];
648
+ const branch = schema.oneOf?.[0];
649
+ if (schema.type === "object" || schema.properties) {
650
+ action ??= schema.properties?.action?.enum?.[0];
651
+ const properties = { ...schema.properties, ...branch?.properties };
652
+ return Object.fromEntries(
653
+ [
654
+ .../* @__PURE__ */ new Set([
655
+ ...schema.required ?? [],
656
+ ...requiredForAction(schema, action),
657
+ ...schema.properties?.outcome && ["finish", "abandon", "abandon_and_spawn_followups"].includes(String(action)) ? ["outcome"] : [],
658
+ ...branch?.required ?? []
659
+ ])
660
+ ].map((key) => [
661
+ key,
662
+ minimalInput(properties[key] ?? {}, key === "action" ? action : void 0, key)
663
+ ])
664
+ );
665
+ }
666
+ if (branch) return minimalInput(branch, action, name);
667
+ if (schema.type === "array") return [minimalInput(schema.items ?? {}, void 0, name)];
668
+ if (schema.type === "integer" || schema.type === "number")
669
+ return name === "anchorAt" ? 18e11 : name === "toQuestId" ? 2 : 1;
670
+ if (schema.type === "boolean") return false;
671
+ if (schema.type === "null") return null;
672
+ if (name === "rrule") return "FREQ=WEEKLY";
673
+ if (name === "title") return "Review release notes";
674
+ if (name === "objective") return "Check changes against the release";
675
+ return "release";
676
+ }
677
+
678
+ // src/surfaces/validate_input.ts
679
+ function validateInput(schema, input, path = "$") {
680
+ const errors = [];
681
+ const matchesType = (type) => {
682
+ switch (type) {
683
+ case "object":
684
+ return typeof input === "object" && input !== null && !Array.isArray(input);
685
+ case "array":
686
+ return Array.isArray(input);
687
+ case "integer":
688
+ return typeof input === "number" && Number.isInteger(input);
689
+ case "number":
690
+ return typeof input === "number" && Number.isFinite(input);
691
+ case "null":
692
+ return input === null;
693
+ default:
694
+ return typeof input === type;
695
+ }
696
+ };
697
+ if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(matchesType)) {
698
+ return [`${path}: expected ${String(schema.type)}`];
699
+ }
700
+ if (schema.enum && !schema.enum.some((value) => JSON.stringify(value) === JSON.stringify(input)))
701
+ errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
702
+ const constant = "const" in schema ? schema.const : void 0;
703
+ if (constant !== void 0 && constant !== input)
704
+ errors.push(`${path}: expected ${JSON.stringify(constant)}`);
705
+ if (schema.oneOf) {
706
+ const branches = schema.oneOf.map((branch) => validateInput(branch, input, path));
707
+ if (!branches.some((errors2) => errors2.length === 0)) {
708
+ errors.push(
709
+ ...branches.sort((a, b) => a.length - b.length)[0] ?? [
710
+ `${path}: must match at least one schema branch`
711
+ ]
712
+ );
713
+ }
714
+ }
715
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
716
+ const record = input;
717
+ for (const name of [...schema.required ?? [], ...requiredForAction(schema, record.action)])
718
+ if (!Object.hasOwn(record, name)) errors.push(`${path}.${name}: required`);
719
+ for (const [name, property] of Object.entries(schema.properties ?? {}))
720
+ if (Object.hasOwn(record, name))
721
+ errors.push(...validateInput(property, record[name], `${path}.${name}`));
722
+ }
723
+ if (Array.isArray(input) && schema.items)
724
+ input.forEach((value, index) => {
725
+ errors.push(...validateInput(schema.items, value, `${path}[${index}]`));
726
+ });
727
+ return errors;
728
+ }
729
+
730
+ // src/tools/action_requirements.ts
731
+ var actionRequirements = {
732
+ shape_work: {
733
+ create_quest: ["quest"],
734
+ create_questline: ["questline"],
735
+ attach_quest_to_questline: ["questId", "questlineId"],
736
+ detach_quest_from_questline: ["questId"],
737
+ dismiss_rumor: ["rumorId"],
738
+ reopen_rumor: ["rumorId"],
739
+ settle_rumor: ["rumorId"],
740
+ update_rumor: ["rumorId"]
741
+ },
742
+ manage_repeatable: {
743
+ create: ["repeatableQuest"],
744
+ archive: ["repeatableQuestId"],
745
+ update: ["repeatableQuestId"],
746
+ spawn_due: ["now"]
747
+ },
748
+ organize_work: {
749
+ add_unlock: ["fromQuestId", "toQuestId"],
750
+ remove_unlock: ["fromQuestId", "toQuestId"],
751
+ replace_unlocks: ["fromQuestIds", "toQuestId"],
752
+ archive_questline: ["questlineId"],
753
+ set_questline_fields: ["questlineId"]
754
+ },
755
+ plan_quest: { revise_objective: ["objective"] },
756
+ run_quest: {
757
+ abandon_and_spawn_followups: ["followups"],
758
+ log_effort: ["effortSeconds"]
759
+ },
760
+ reward_work: { add: ["reward"], update: ["reward"], replace_repeatable_template: ["rewards"] }
761
+ };
762
+
633
763
  // src/tools/tool_metadata.ts
634
764
  function defineQuestlogTool(definition) {
765
+ const requirements2 = actionRequirements[definition.name];
766
+ if (requirements2) registerSchemaActions(definition.inputSchema, requirements2);
767
+ const handler = definition.handler;
768
+ definition.handler = (db, input) => {
769
+ const errors = validateInput(definition.inputSchema, input);
770
+ if (errors.length) {
771
+ const action = input?.action;
772
+ const example = minimalInput(definition.inputSchema, action);
773
+ const message = errors.join("; ");
774
+ return toolFailure("protocol", "invalid_input", message, message, {
775
+ recovery: `${message}; pass ${JSON.stringify(example)}`
776
+ });
777
+ }
778
+ return handler(db, input);
779
+ };
635
780
  return definition;
636
781
  }
637
782
  function stringSchema(description, options = {}) {
@@ -3581,7 +3726,7 @@ var rewardWorkTool = defineQuestlogTool({
3581
3726
  name: stringSchema("Reward name."),
3582
3727
  now: integerSchema("Optional update timestamp for the update action.")
3583
3728
  },
3584
- []
3729
+ ["kind", "name"]
3585
3730
  ),
3586
3731
  rewards: arraySchema(
3587
3732
  objectSchema(
@@ -4408,7 +4553,7 @@ var shapeWorkTool = defineQuestlogTool({
4408
4553
  title: stringSchema("Questline title."),
4409
4554
  description: stringSchema("Optional questline description.")
4410
4555
  },
4411
- []
4556
+ ["title"]
4412
4557
  ),
4413
4558
  rumorId: integerSchema("Rumor to settle, dismiss, or reopen."),
4414
4559
  dismissedAt: integerSchema("Optional dismissal timestamp."),
@@ -4690,7 +4835,7 @@ var questlogSurfaceTools = questlogTools.map(
4690
4835
  whenNotToUse: tool.whenNotToUse ?? "",
4691
4836
  readOnly: tool.readOnly,
4692
4837
  sideEffects: tool.sideEffects,
4693
- inputSchema: { ...tool.inputSchema, type: "object" },
4838
+ inputSchema: tool.inputSchema,
4694
4839
  handler: tool.handler
4695
4840
  })
4696
4841
  );
@@ -4725,47 +4870,6 @@ function isErrorResult(result) {
4725
4870
  return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
4726
4871
  }
4727
4872
 
4728
- // src/surfaces/validate_input.ts
4729
- function validateInput(schema, input, path = "$") {
4730
- const errors = [];
4731
- const matchesType = (type) => {
4732
- switch (type) {
4733
- case "object":
4734
- return typeof input === "object" && input !== null && !Array.isArray(input);
4735
- case "array":
4736
- return Array.isArray(input);
4737
- case "integer":
4738
- return typeof input === "number" && Number.isInteger(input);
4739
- case "number":
4740
- return typeof input === "number" && Number.isFinite(input);
4741
- case "null":
4742
- return input === null;
4743
- default:
4744
- return typeof input === type;
4745
- }
4746
- };
4747
- if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(matchesType)) {
4748
- return [`${path}: expected ${String(schema.type)}`];
4749
- }
4750
- if (schema.enum && !schema.enum.some((value) => JSON.stringify(value) === JSON.stringify(input)))
4751
- errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
4752
- if (schema.oneOf && !schema.oneOf.some((branch) => validateInput(branch, input, path).length === 0))
4753
- errors.push(`${path}: must match at least one schema branch`);
4754
- if (typeof input === "object" && input !== null && !Array.isArray(input)) {
4755
- const record = input;
4756
- for (const name of schema.required ?? [])
4757
- if (!Object.hasOwn(record, name)) errors.push(`${path}.${name}: required`);
4758
- for (const [name, property] of Object.entries(schema.properties ?? {}))
4759
- if (Object.hasOwn(record, name))
4760
- errors.push(...validateInput(property, record[name], `${path}.${name}`));
4761
- }
4762
- if (Array.isArray(input) && schema.items)
4763
- input.forEach((value, index) => {
4764
- errors.push(...validateInput(schema.items, value, `${path}[${index}]`));
4765
- });
4766
- return errors;
4767
- }
4768
-
4769
4873
  // src/surfaces/mcp_server.ts
4770
4874
  var MCP_LATEST_PROTOCOL_VERSION = "2026-07-28";
4771
4875
  var MCP_SUPPORTED_PROTOCOL_VERSIONS = [
@@ -4865,7 +4969,15 @@ Not for: ${tool.whenNotToUse}` : ""),
4865
4969
  const input = params.arguments === void 0 ? {} : params.arguments;
4866
4970
  const errors = validateInput(tool.inputSchema, input);
4867
4971
  if (errors.length)
4868
- return result({ content: [{ type: "text", text: errors.join("\n") }], isError: true });
4972
+ return result({
4973
+ content: [
4974
+ {
4975
+ type: "text",
4976
+ text: `${errors.join("\n")}; pass ${JSON.stringify(minimalInput(tool.inputSchema, input?.action))}`
4977
+ }
4978
+ ],
4979
+ isError: true
4980
+ });
4869
4981
  try {
4870
4982
  context ??= Promise.resolve().then(options.context);
4871
4983
  const value = await tool.handler(await context, input);
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
3
+ import { questlogSkills } from './chunk-6KTQK3ZE.js';
4
+ import { reviewQuestlogTool, questlogSurfaceTools } from './chunk-UTUQPSGO.js';
5
+ import './chunk-QYSIJ2YN.js';
6
+
7
+ // src/soul.ts
8
+ var questlogSoulTraits = [
9
+ {
10
+ principle: "Store the least committed truth first.",
11
+ provenance: "The worst downstream errors usually start upstream: vague asks are turned into quests, soft ideas become fake deadlines, and half-understood work is treated as fully shaped. Using rumors and clarification before commitment keeps uncertainty where it belongs."
12
+ },
13
+ {
14
+ principle: "Read before you prescribe.",
15
+ provenance: "Many management mistakes are category mistakes: blocked work gets treated as available, missed schedule as overdue, or resolved work as fully closed when follow-through still remains. Reading the derived state first prevents confident but wrong interventions."
16
+ },
17
+ {
18
+ principle: "Protect history when reality changes.",
19
+ provenance: "Once execution starts, rewriting a quest into a different commitment destroys the meaning of the record. Honest finishes, abandonments, and follow-up quests preserve what actually happened and keep later review, planning, and reporting trustworthy."
20
+ },
21
+ {
22
+ principle: "Close the last mile, not just the main work.",
23
+ provenance: "Operational debt often hides in almost-done states: work finished but not claimed, reported, handed off, reviewed, or retired. Treating closure as a real phase prevents stale rewards, lingering initiatives, and misleading progress signals."
24
+ }
25
+ ];
26
+
27
+ // src/surface/questlog_skill_spec.ts
28
+ var questlogSkillSpec = {
29
+ name: "questlog",
30
+ cheatSheet: [
31
+ "## Install",
32
+ "",
33
+ "Linux/macOS:",
34
+ "",
35
+ "```sh",
36
+ "curl -fsSL https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.sh | sh",
37
+ "```",
38
+ "",
39
+ "Installs `~/.local/bin/ghostpaw-questlog` after checking SHA256SUMS.",
40
+ "",
41
+ "Windows x64 (PowerShell):",
42
+ "",
43
+ "```powershell",
44
+ "irm https://raw.githubusercontent.com/GhostPawJS/questlog/main/install.ps1 | iex",
45
+ "```",
46
+ "",
47
+ "Run tools as `ghostpaw-questlog ...`; use `~/.local/bin/ghostpaw-questlog` if it is not on PATH.",
48
+ "",
49
+ "`questlog` and `ghostpaw-questlog` are the same binary.",
50
+ "",
51
+ "- `questlog search <words>` before `add` to avoid duplicates.",
52
+ '- `ghostpaw-questlog add "..." --tag a,b --due tomorrow`',
53
+ `- \`questlog list [--view <view>]\`: ${reviewQuestlogTool.inputSchema.properties?.view?.enum?.join(", ")}.`,
54
+ '- `questlog done <id> --outcome "..."`',
55
+ "- `questlog search <words>`",
56
+ "- `questlog show <id>`",
57
+ "- Use `--json` on any plain command.",
58
+ "- Exit codes: 0 ok / 1 error / 2 usage / 3 needs input.",
59
+ "- Database: `~/.ghostpaw/questlog.db`; override with `--db` or `GHOSTPAW_QUESTLOG_DB`.",
60
+ "- Use one database per agent or project."
61
+ ].join("\n"),
62
+ description: "Use the release binary ghostpaw-questlog to manage local tasks, calendar commitments, and recurring work. Use when planning, executing, reviewing, or organizing work.",
63
+ license: "MIT",
64
+ compatibility: "Release binary ghostpaw-questlog (no runtime needed); MCP stdio via ghostpaw-questlog mcp.",
65
+ bin: "ghostpaw-questlog",
66
+ packageName: "@ghostpaw/questlog",
67
+ server: "ghostpaw-questlog",
68
+ about: questlogMcpInstructions,
69
+ tools: questlogSurfaceTools,
70
+ workflows: questlogSkills,
71
+ principles: questlogSoulTraits.map((trait) => trait.principle)
72
+ };
73
+
74
+ export { questlogSkillSpec };
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { openQuestlogDb } from './chunk-3B6FMHZU.js';
3
3
  import { questlogMcpInstructions } from './chunk-SVWAEAKC.js';
4
- import { validateInput, isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-KRQDV4KF.js';
5
- import { questlogSurfaceTools } from './chunk-3Y5F3SMB.js';
4
+ import { isErrorResult, questlogVersion, resolveQuestlogDbPath } from './chunk-DKH623HU.js';
5
+ import { validateInput, questlogSurfaceTools } from './chunk-UTUQPSGO.js';
6
+ import { minimalInput } from './chunk-QYSIJ2YN.js';
6
7
 
7
8
  // src/surfaces/mcp_server.ts
8
9
  var MCP_LATEST_PROTOCOL_VERSION = "2026-07-28";
@@ -103,7 +104,15 @@ Not for: ${tool.whenNotToUse}` : ""),
103
104
  const input = params.arguments === void 0 ? {} : params.arguments;
104
105
  const errors = validateInput(tool.inputSchema, input);
105
106
  if (errors.length)
106
- return result({ content: [{ type: "text", text: errors.join("\n") }], isError: true });
107
+ return result({
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: `${errors.join("\n")}; pass ${JSON.stringify(minimalInput(tool.inputSchema, input?.action))}`
112
+ }
113
+ ],
114
+ isError: true
115
+ });
107
116
  try {
108
117
  context ??= Promise.resolve().then(options.context);
109
118
  const value = await tool.handler(await context, input);
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { minimalInput } from './chunk-QYSIJ2YN.js';
3
+
2
4
  // src/surfaces/skill_markdown.ts
3
5
  function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
4
6
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(spec.name) || spec.name.length > 64)
@@ -26,7 +28,7 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
26
28
  "",
27
29
  `MCP: connect to ${spec.server}, then call a tool by name with its JSON arguments.`,
28
30
  "",
29
- `CLI: \`${spec.bin} call <tool> '<JSON object>'\` or \`${spec.bin} <tool> --parameter value\`${spec.packageName ? ` (without a global install: \`npx -y ${spec.packageName} <tool> ...\`)` : ""}.`,
31
+ `CLI: \`${spec.bin} call <tool> '<JSON object>'\` or \`${spec.bin} <tool> --parameter value\`.`,
30
32
  "",
31
33
  `Inspect schemas with \`${spec.bin} describe <tool>\`. Use \`--db <path>\` to select a database.`,
32
34
  ""
@@ -46,17 +48,38 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
46
48
  "| Parameter | Type | Required | Values / description |",
47
49
  "| --- | --- | --- | --- |"
48
50
  );
49
- for (const [name, property] of Object.entries(tool.inputSchema.properties ?? {}))
51
+ const rows = (schema, prefix = "") => {
52
+ const properties = { ...schema.properties };
53
+ const required = new Set(schema.required);
54
+ for (const branch of schema.oneOf ?? []) {
55
+ for (const [key, value] of Object.entries(branch.properties ?? {}))
56
+ properties[key] ??= value;
57
+ for (const key of branch.required ?? []) required.add(key);
58
+ }
59
+ for (const [name, property] of Object.entries(properties)) {
60
+ if (prefix && Object.keys(properties).length > 8 && !required.has(name)) continue;
61
+ const path = `${prefix}${name}`;
62
+ lines.push(
63
+ `| ${path} | ${property.type ?? property.oneOf?.map((branch) => branch.type).join(" / ") ?? "any"} | ${required.has(name) ? "yes" : "no"} | ${((property.enum ? `${property.enum.join(", ")}. ` : "") + (property.description ?? "")).replaceAll("|", "\\|").replaceAll("\n", " ")} |`
64
+ );
65
+ if (property.properties || property.oneOf) rows(property, `${path}.`);
66
+ }
67
+ };
68
+ rows(tool.inputSchema);
69
+ lines.push("", "```sh");
70
+ for (const action of tool.inputSchema.properties?.action?.enum?.slice(0, 3) ?? [void 0]) {
50
71
  lines.push(
51
- `| ${name} | ${property.type ?? property.oneOf?.map((branch) => branch.type).join(" / ") ?? "any"} | ${tool.inputSchema.required?.includes(name) ? "yes" : "no"} | ${(property.enum ? `${property.enum.join(", ")}. ` : "") + (property.description ?? "").replaceAll("|", "\\|").replaceAll("\n", " ")} |`
72
+ `${spec.bin} call ${tool.name} '${JSON.stringify(minimalInput(tool.inputSchema, action))}'`
52
73
  );
74
+ }
75
+ lines.push("```");
53
76
  lines.push("");
54
77
  }
55
78
  lines.push(
56
79
  "## Workflow guides",
57
80
  "",
58
81
  ...spec.workflows.map(
59
- (workflow) => `- [${workflow.name}](${referencesBase}/${workflow.name}.md): ${workflow.description}`
82
+ (workflow) => `- \`${spec.bin} skill ${workflow.name}\` ([${workflow.name}](${referencesBase}/${workflow.name}.md)): ${workflow.description}`
60
83
  ),
61
84
  "",
62
85
  "## Principles",
@@ -65,7 +88,12 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
65
88
  ""
66
89
  );
67
90
  const output = lines.join("\n");
68
- if (!spec.cheatSheet) return output;
91
+ const fallback = spec.packageName ? `
92
+ ## Without the binary
93
+
94
+ Use \`npx -y ${spec.packageName} ...\` in npm-based environments (Node 24+). Harnesses that embed it in code can import \`${spec.packageName}\` as a library.
95
+ ` : "";
96
+ if (!spec.cheatSheet) return output + fallback;
69
97
  const title = `# ${spec.name}
70
98
 
71
99
  `;
@@ -74,7 +102,7 @@ function renderSkillMarkdown(spec, { referencesBase = "references" } = {}) {
74
102
 
75
103
  ## Appendix: MCP and tool calls
76
104
 
77
- ${output.slice(start).replace(/^## /gm, "### ")}`;
105
+ ${output.slice(start).replace(/^## /gm, "### ")}${fallback}`;
78
106
  }
79
107
 
80
108
  export { renderSkillMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostpaw/questlog",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Unified task and calendar model for Node.js — SQLite, zero dependencies, with a built-in LLM tool facade",
5
5
  "type": "module",
6
6
  "exports": {
@@ -127,7 +127,7 @@
127
127
  "devDependencies": {
128
128
  "@biomejs/biome": "2.4.4",
129
129
  "@types/node": "^25.3.0",
130
- "esbuild": "^0.27.4",
130
+ "esbuild": "^0.28.2",
131
131
  "fflate": "0.8.3",
132
132
  "preact": "^10.29.0",
133
133
  "sql.js-fts5": "^1.4.0",
@@ -138,5 +138,8 @@
138
138
  "ghostpaw-questlog": "./dist/cli.js",
139
139
  "questlog": "./dist/cli.js"
140
140
  },
141
- "mcpName": "io.github.GhostPawJS/questlog"
141
+ "mcpName": "io.github.GhostPawJS/questlog",
142
+ "overrides": {
143
+ "esbuild": "$esbuild"
144
+ }
142
145
  }