@ghostpaw/questlog 0.6.0 → 0.6.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/README.md CHANGED
@@ -260,6 +260,24 @@ The `run_quest` tool refuses to start a quest with unmet unlock prerequisites an
260
260
  - **WoW-style markers.** Semantic marker IDs (`attention.available`, `progress.complete`, ...) computed on every read, renderable to any channel.
261
261
  - **Colocated tests.** Every non-type module has a colocated `.test.ts` file. The behavior described in docs is backed by executable coverage.
262
262
 
263
+ ## Performance
264
+
265
+ Measured with `npm run bench` on Node 24.14.0, Linux, Intel Xeon (shared
266
+ cloud vCPU), 10,000 quests in a file-backed database:
267
+
268
+ | Operation | p50 | p95 |
269
+ |---|---:|---:|
270
+ | `search_questlog` (tool) | 0.06 ms | 0.07 ms |
271
+ | MCP `tools/call` search, stdio framing included | 0.07 ms | 0.08 ms |
272
+ | `read.searchQuestlog`, one matching quest | 0.07 ms | 0.08 ms |
273
+ | `read.searchQuestlog`, all 10,000 quests matching | 26.6 ms | 41.9 ms |
274
+ | `write.createQuest` | 0.22 ms | 0.69 ms |
275
+
276
+ The library search returns every match with a highlighted snippet, so a query
277
+ that matches the whole log pays for building all of those rows. Cold start
278
+ (process spawn to first response, median) is about 30 ms for `--version` and
279
+ the MCP server.
280
+
263
281
  ## Package Surface
264
282
 
265
283
  ```ts
package/dist/cli.js CHANGED
@@ -4194,8 +4194,7 @@ function searchQuestlog(db, query) {
4194
4194
  AND blocker.deleted_at IS NULL
4195
4195
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
4196
4196
  ) AS has_blockers
4197
- FROM quests_fts
4198
- JOIN quests q ON q.id = quests_fts.rowid
4197
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
4199
4198
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
4200
4199
  ).all(trimmed).map((row) => {
4201
4200
  const state = deriveQuestState({
@@ -4227,8 +4226,7 @@ function searchQuestlog(db, query) {
4227
4226
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
4228
4227
  r.settled_at,
4229
4228
  r.dismissed_at
4230
- FROM rumors_fts
4231
- JOIN rumors r ON r.id = rumors_fts.rowid
4229
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
4232
4230
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
4233
4231
  ).all(trimmed).map((row) => ({
4234
4232
  entityKind: "rumor",
@@ -5233,7 +5231,7 @@ var init_validate_input = __esm({
5233
5231
  var questlogVersion;
5234
5232
  var init_version = __esm({
5235
5233
  "src/version.ts"() {
5236
- questlogVersion = "0.6.0";
5234
+ questlogVersion = "0.6.2";
5237
5235
  }
5238
5236
  });
5239
5237
 
package/dist/index.cjs CHANGED
@@ -2056,8 +2056,7 @@ function searchQuestlog(db, query) {
2056
2056
  AND blocker.deleted_at IS NULL
2057
2057
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
2058
2058
  ) AS has_blockers
2059
- FROM quests_fts
2060
- JOIN quests q ON q.id = quests_fts.rowid
2059
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
2061
2060
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
2062
2061
  ).all(trimmed).map((row) => {
2063
2062
  const state = deriveQuestState({
@@ -2089,8 +2088,7 @@ function searchQuestlog(db, query) {
2089
2088
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
2090
2089
  r.settled_at,
2091
2090
  r.dismissed_at
2092
- FROM rumors_fts
2093
- JOIN rumors r ON r.id = rumors_fts.rowid
2091
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
2094
2092
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
2095
2093
  ).all(trimmed).map((row) => ({
2096
2094
  entityKind: "rumor",
package/dist/index.d.cts CHANGED
@@ -665,6 +665,8 @@ interface QuestlogSearchResult {
665
665
 
666
666
  /**
667
667
  * Full-text search across quest and rumor content.
668
+ * CROSS JOIN keeps the FTS table as the outer loop; with a plain JOIN SQLite
669
+ * may walk idx_quests_deleted_at and probe the FTS index once per quest.
668
670
  */
669
671
  declare function searchQuestlog(db: QuestlogDb, query: string): QuestlogSearchResult[];
670
672
 
package/dist/index.d.ts CHANGED
@@ -665,6 +665,8 @@ interface QuestlogSearchResult {
665
665
 
666
666
  /**
667
667
  * Full-text search across quest and rumor content.
668
+ * CROSS JOIN keeps the FTS table as the outer loop; with a plain JOIN SQLite
669
+ * may walk idx_quests_deleted_at and probe the FTS index once per quest.
668
670
  */
669
671
  declare function searchQuestlog(db: QuestlogDb, query: string): QuestlogSearchResult[];
670
672
 
package/dist/index.js CHANGED
@@ -2054,8 +2054,7 @@ function searchQuestlog(db, query) {
2054
2054
  AND blocker.deleted_at IS NULL
2055
2055
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
2056
2056
  ) AS has_blockers
2057
- FROM quests_fts
2058
- JOIN quests q ON q.id = quests_fts.rowid
2057
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
2059
2058
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
2060
2059
  ).all(trimmed).map((row) => {
2061
2060
  const state = deriveQuestState({
@@ -2087,8 +2086,7 @@ function searchQuestlog(db, query) {
2087
2086
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
2088
2087
  r.settled_at,
2089
2088
  r.dismissed_at
2090
- FROM rumors_fts
2091
- JOIN rumors r ON r.id = rumors_fts.rowid
2089
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
2092
2090
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
2093
2091
  ).all(trimmed).map((row) => ({
2094
2092
  entityKind: "rumor",
@@ -677,7 +677,7 @@ var init_open_questlog_db = __esm({
677
677
  var questlogVersion;
678
678
  var init_version = __esm({
679
679
  "src/version.ts"() {
680
- questlogVersion = "0.6.0";
680
+ questlogVersion = "0.6.2";
681
681
  }
682
682
  });
683
683
 
@@ -4870,8 +4870,7 @@ function searchQuestlog(db, query) {
4870
4870
  AND blocker.deleted_at IS NULL
4871
4871
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
4872
4872
  ) AS has_blockers
4873
- FROM quests_fts
4874
- JOIN quests q ON q.id = quests_fts.rowid
4873
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
4875
4874
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
4876
4875
  ).all(trimmed).map((row) => {
4877
4876
  const state = deriveQuestState({
@@ -4903,8 +4902,7 @@ function searchQuestlog(db, query) {
4903
4902
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
4904
4903
  r.settled_at,
4905
4904
  r.dismissed_at
4906
- FROM rumors_fts
4907
- JOIN rumors r ON r.id = rumors_fts.rowid
4905
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
4908
4906
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
4909
4907
  ).all(trimmed).map((row) => ({
4910
4908
  entityKind: "rumor",
package/dist/mcp.cjs CHANGED
@@ -386,7 +386,7 @@ async function openQuestlogDb(path) {
386
386
  }
387
387
 
388
388
  // src/version.ts
389
- var questlogVersion = "0.6.0";
389
+ var questlogVersion = "0.6.2";
390
390
 
391
391
  // src/surface/questlog_mcp_instructions.ts
392
392
  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.";
@@ -3846,8 +3846,7 @@ function searchQuestlog(db, query) {
3846
3846
  AND blocker.deleted_at IS NULL
3847
3847
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
3848
3848
  ) AS has_blockers
3849
- FROM quests_fts
3850
- JOIN quests q ON q.id = quests_fts.rowid
3849
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
3851
3850
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
3852
3851
  ).all(trimmed).map((row) => {
3853
3852
  const state = deriveQuestState({
@@ -3879,8 +3878,7 @@ function searchQuestlog(db, query) {
3879
3878
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
3880
3879
  r.settled_at,
3881
3880
  r.dismissed_at
3882
- FROM rumors_fts
3883
- JOIN rumors r ON r.id = rumors_fts.rowid
3881
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
3884
3882
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
3885
3883
  ).all(trimmed).map((row) => ({
3886
3884
  entityKind: "rumor",
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.6.0";
387
+ var questlogVersion = "0.6.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.";
@@ -3844,8 +3844,7 @@ function searchQuestlog(db, query) {
3844
3844
  AND blocker.deleted_at IS NULL
3845
3845
  AND NOT (blocker.resolved_at IS NOT NULL AND blocker.success = 1)
3846
3846
  ) AS has_blockers
3847
- FROM quests_fts
3848
- JOIN quests q ON q.id = quests_fts.rowid
3847
+ FROM quests_fts CROSS JOIN quests q ON q.id = quests_fts.rowid
3849
3848
  WHERE quests_fts MATCH ? AND q.deleted_at IS NULL`
3850
3849
  ).all(trimmed).map((row) => {
3851
3850
  const state = deriveQuestState({
@@ -3877,8 +3876,7 @@ function searchQuestlog(db, query) {
3877
3876
  snippet(rumors_fts, 1, '[', ']', '\u2026', 10) AS snippet,
3878
3877
  r.settled_at,
3879
3878
  r.dismissed_at
3880
- FROM rumors_fts
3881
- JOIN rumors r ON r.id = rumors_fts.rowid
3879
+ FROM rumors_fts CROSS JOIN rumors r ON r.id = rumors_fts.rowid
3882
3880
  WHERE rumors_fts MATCH ? AND r.deleted_at IS NULL`
3883
3881
  ).all(trimmed).map((row) => ({
3884
3882
  entityKind: "rumor",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostpaw/questlog",
3
- "version": "0.6.0",
3
+ "version": "0.6.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": {
@@ -105,7 +105,7 @@
105
105
  "email": "max@anonyfox.com",
106
106
  "url": "https://anonyfox.com"
107
107
  },
108
- "homepage": "https://ghostpaw.anonyfox.com/agent-tasks/",
108
+ "homepage": "https://ghostpaw.dev/agent-tasks/",
109
109
  "bugs": {
110
110
  "url": "https://github.com/GhostPawJS/questlog/issues"
111
111
  },