ifcraftcorpus 1.3.0__py3-none-any.whl → 1.5.0__py3-none-any.whl

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.
Files changed (60) hide show
  1. ifcraftcorpus/index.py +120 -43
  2. ifcraftcorpus/mcp_server.py +18 -3
  3. ifcraftcorpus-1.5.0.data/data/share/ifcraftcorpus/corpus/agent-design/agent_memory_architecture.md +765 -0
  4. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/agent-design/agent_prompt_engineering.md +247 -0
  5. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/agent-design/multi_agent_patterns.md +1 -0
  6. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/METADATA +1 -1
  7. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/RECORD +60 -59
  8. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/audience-and-access/accessibility_guidelines.md +0 -0
  9. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/audience-and-access/audience_targeting.md +0 -0
  10. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/audience-and-access/localization_considerations.md +0 -0
  11. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/audio_visual_integration.md +0 -0
  12. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/collaborative_if_writing.md +0 -0
  13. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/creative_workflow_pipeline.md +0 -0
  14. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/diegetic_design.md +0 -0
  15. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/idea_capture_and_hooks.md +0 -0
  16. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/if_platform_tools.md +0 -0
  17. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/player_analytics_metrics.md +0 -0
  18. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/quality_standards_if.md +0 -0
  19. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/research_and_verification.md +0 -0
  20. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/craft-foundations/testing_interactive_fiction.md +0 -0
  21. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/emotional-design/conflict_patterns.md +0 -0
  22. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/emotional-design/emotional_beats.md +0 -0
  23. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/game-design/mechanics_design_patterns.md +0 -0
  24. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/children_and_ya_conventions.md +0 -0
  25. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/fantasy_conventions.md +0 -0
  26. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/historical_fiction.md +0 -0
  27. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/horror_conventions.md +0 -0
  28. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/mystery_conventions.md +0 -0
  29. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/genre-conventions/sci_fi_conventions.md +0 -0
  30. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/branching_narrative_construction.md +0 -0
  31. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/branching_narrative_craft.md +0 -0
  32. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/endings_patterns.md +0 -0
  33. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/episodic_serialized_if.md +0 -0
  34. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/nonlinear_structure.md +0 -0
  35. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/pacing_and_tension.md +0 -0
  36. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/romance_and_relationships.md +0 -0
  37. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/scene_structure_and_beats.md +0 -0
  38. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/narrative-structure/scene_transitions.md +0 -0
  39. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/character_voice.md +0 -0
  40. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/dialogue_craft.md +0 -0
  41. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/exposition_techniques.md +0 -0
  42. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/narrative_point_of_view.md +0 -0
  43. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/prose_patterns.md +0 -0
  44. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/subtext_and_implication.md +0 -0
  45. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/prose-and-language/voice_register_consistency.md +0 -0
  46. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/scope-and-planning/scope_and_length.md +0 -0
  47. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/world-and-setting/canon_management.md +0 -0
  48. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/world-and-setting/setting_as_character.md +0 -0
  49. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/corpus/world-and-setting/worldbuilding_patterns.md +0 -0
  50. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/README.md +0 -0
  51. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_genre_consultant.md +0 -0
  52. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_platform_advisor.md +0 -0
  53. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_prose_writer.md +0 -0
  54. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_quality_reviewer.md +0 -0
  55. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_story_architect.md +0 -0
  56. {ifcraftcorpus-1.3.0.data → ifcraftcorpus-1.5.0.data}/data/share/ifcraftcorpus/subagents/if_world_curator.md +0 -0
  57. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/WHEEL +0 -0
  58. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/entry_points.txt +0 -0
  59. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/licenses/LICENSE +0 -0
  60. {ifcraftcorpus-1.3.0.dist-info → ifcraftcorpus-1.5.0.dist-info}/licenses/LICENSE-CONTENT +0 -0
ifcraftcorpus/index.py CHANGED
@@ -56,21 +56,58 @@ from ifcraftcorpus.parser import Document, parse_directory
56
56
  def _sanitize_fts_query(query: str) -> str:
57
57
  """Sanitize a query string for the FTS5 MATCH clause.
58
58
 
59
- This function replaces hyphens with spaces to prevent FTS5 from
60
- interpreting them as the `NOT` operator. This is intended to correctly
61
- handle natural language queries with hyphenated words, for example
62
- transforming "haunted-house" into a search for "haunted house".
59
+ This function replaces special characters that could cause FTS5 syntax
60
+ errors with spaces. This is intended to correctly handle natural language
61
+ queries from LLMs and users, for example transforming:
62
+ - "haunted-house" into "haunted house" (hyphen as NOT operator)
63
+ - "dialogue, subtext" into "dialogue subtext" (comma syntax error)
63
64
 
64
65
  It also collapses any resulting multiple spaces into a single space.
65
66
 
67
+ Note: This is used as a fallback when raw FTS5 queries fail. The search
68
+ method first tries the raw query to support advanced FTS5 syntax, then
69
+ falls back to sanitized query on syntax errors.
70
+ See https://github.com/pvliesdonk/if-craft-corpus/issues/10
71
+
66
72
  Args:
67
73
  query: Raw query string from user input.
68
74
 
69
75
  Returns:
70
76
  Sanitized query safe for FTS5 MATCH.
71
77
  """
72
- # Replace hyphens and collapse whitespace in one go.
73
- return " ".join(query.replace("-", " ").split())
78
+ # Replace problematic characters with spaces:
79
+ # - hyphen: FTS5 interprets as NOT operator
80
+ # - comma: FTS5 column list syntax
81
+ # - parentheses: FTS5 grouping syntax
82
+ # - curly braces: FTS5 column filter syntax
83
+ # - caret: FTS5 position marker
84
+ # - plus: FTS5 column weight
85
+ # - colon after words could affect column queries, but we preserve it
86
+ # to allow intentional column:value syntax
87
+ # Using str.translate is more efficient for replacing multiple single characters.
88
+ translation_table = str.maketrans("-,(){}^+", " " * 8)
89
+ sanitized = query.translate(translation_table)
90
+ # Collapse whitespace
91
+ return " ".join(sanitized.split())
92
+
93
+
94
+ def _is_fts5_query_error(error: sqlite3.OperationalError) -> bool:
95
+ """Check if an OperationalError is an FTS5 query parsing error.
96
+
97
+ Args:
98
+ error: The SQLite OperationalError to check.
99
+
100
+ Returns:
101
+ True if this is an FTS5 query error that might be recoverable
102
+ by sanitizing the query.
103
+ """
104
+ msg = str(error).lower()
105
+ # FTS5 syntax errors (e.g., "fts5: syntax error near ','")
106
+ if "fts5" in msg and "syntax error" in msg:
107
+ return True
108
+ # Column errors from FTS5 query parsing (e.g., "no such column: voice")
109
+ # This can happen when hyphens are interpreted as column filters
110
+ return "no such column" in msg
74
111
 
75
112
 
76
113
  @dataclass
@@ -359,51 +396,25 @@ class CorpusIndex:
359
396
  self.add_document(doc)
360
397
  return len(documents)
361
398
 
362
- def search(
399
+ def _execute_fts_query(
363
400
  self,
364
- query: str,
365
- *,
366
- cluster: str | None = None,
367
- limit: int = 10,
401
+ fts_query: str,
402
+ cluster: str | None,
403
+ limit: int,
368
404
  ) -> list[SearchResult]:
369
- """Search the corpus using FTS5 full-text search.
370
-
371
- Performs a keyword search using SQLite FTS5 with BM25 ranking.
372
- Supports the full FTS5 query syntax for advanced searches.
405
+ """Execute an FTS5 query and return results.
373
406
 
374
407
  Args:
375
- query: Search query. Supports FTS5 syntax:
376
-
377
- - Simple keywords: ``dialogue``
378
- - Phrases: ``"character voice"``
379
- - Boolean: ``tension AND suspense``, ``horror NOT comedy``
380
- - Prefix: ``narrat*``
381
- - Column-specific: ``title:craft``
382
-
383
- cluster: Optional cluster name to filter results. Only returns
384
- matches from the specified cluster.
385
- limit: Maximum number of results to return. Default 10.
408
+ fts_query: The FTS5 query string.
409
+ cluster: Optional cluster filter.
410
+ limit: Maximum results to return.
386
411
 
387
412
  Returns:
388
- List of :class:`SearchResult` objects, sorted by BM25 relevance
389
- score in descending order (best matches first).
413
+ List of SearchResult objects.
390
414
 
391
- Example:
392
- >>> # Simple search
393
- >>> results = index.search("dialogue")
394
-
395
- >>> # Phrase search
396
- >>> results = index.search('"character voice"')
397
-
398
- >>> # Boolean with cluster filter
399
- >>> results = index.search("tension OR suspense",
400
- ... cluster="emotional-design",
401
- ... limit=5)
415
+ Raises:
416
+ sqlite3.OperationalError: If the query has invalid FTS5 syntax.
402
417
  """
403
- # Build FTS5 query - sanitize to handle special characters
404
- fts_query = _sanitize_fts_query(query)
405
-
406
- # Add cluster filter if specified
407
418
  where_clause = ""
408
419
  params: list[str | int] = [fts_query]
409
420
  if cluster:
@@ -447,6 +458,72 @@ class CorpusIndex:
447
458
 
448
459
  return results
449
460
 
461
+ def search(
462
+ self,
463
+ query: str,
464
+ *,
465
+ cluster: str | None = None,
466
+ limit: int = 10,
467
+ ) -> list[SearchResult]:
468
+ """Search the corpus using FTS5 full-text search.
469
+
470
+ Performs a keyword search using SQLite FTS5 with BM25 ranking.
471
+ Supports the full FTS5 query syntax for advanced searches.
472
+
473
+ The search first attempts to execute the query as-is to support
474
+ advanced FTS5 syntax. If that fails with a syntax error, it falls
475
+ back to a sanitized version of the query that treats special
476
+ characters as word separators.
477
+
478
+ Args:
479
+ query: Search query. Supports FTS5 syntax:
480
+
481
+ - Simple keywords: ``dialogue``
482
+ - Phrases: ``"character voice"``
483
+ - Boolean: ``tension AND suspense``, ``horror NOT comedy``
484
+ - Prefix: ``narrat*``
485
+ - Column-specific: ``title:craft``
486
+
487
+ Natural language queries with punctuation (e.g., "dialogue,
488
+ subtext") are also supported - they will be automatically
489
+ sanitized if they cause syntax errors.
490
+
491
+ cluster: Optional cluster name to filter results. Only returns
492
+ matches from the specified cluster.
493
+ limit: Maximum number of results to return. Default 10.
494
+
495
+ Returns:
496
+ List of :class:`SearchResult` objects, sorted by BM25 relevance
497
+ score in descending order (best matches first).
498
+
499
+ Example:
500
+ >>> # Simple search
501
+ >>> results = index.search("dialogue")
502
+
503
+ >>> # Phrase search
504
+ >>> results = index.search('"character voice"')
505
+
506
+ >>> # Boolean with cluster filter
507
+ >>> results = index.search("tension OR suspense",
508
+ ... cluster="emotional-design",
509
+ ... limit=5)
510
+
511
+ >>> # Natural language (auto-sanitized)
512
+ >>> results = index.search("dialogue, subtext")
513
+ """
514
+ # Try raw query first to support advanced FTS5 syntax
515
+ try:
516
+ return self._execute_fts_query(query, cluster, limit)
517
+ except sqlite3.OperationalError as e:
518
+ if not _is_fts5_query_error(e):
519
+ raise
520
+
521
+ # Fallback to sanitized query for natural language input
522
+ sanitized = _sanitize_fts_query(query)
523
+ if not sanitized:
524
+ return []
525
+ return self._execute_fts_query(sanitized, cluster, limit)
526
+
450
527
  def list_documents(self) -> list[dict[str, str]]:
451
528
  """List all indexed documents with their metadata.
452
529
 
@@ -194,9 +194,24 @@ def search_corpus(
194
194
  worldbuilding, and genre conventions.
195
195
 
196
196
  Args:
197
- query: Search query describing what craft guidance you need.
198
- Examples: "dialogue subtext", "branching narrative",
199
- "pacing action scenes", "horror atmosphere".
197
+ query: Search query. Supports natural language or FTS5 syntax:
198
+
199
+ Natural language examples:
200
+ - "dialogue subtext"
201
+ - "branching narrative"
202
+ - "pacing action scenes"
203
+
204
+ FTS5 advanced syntax:
205
+ - Exact phrases: '"character voice"'
206
+ - Boolean NOT: "dialogue NOT comedy"
207
+ - Boolean OR: "tension OR suspense"
208
+ - Boolean AND: "dialogue AND subtext"
209
+ - Prefix search: "narrat*"
210
+ - Column filter: "title:craft", "cluster:genre-conventions"
211
+
212
+ Natural language queries with punctuation are automatically
213
+ sanitized, so both styles work seamlessly.
214
+
200
215
  cluster: Optional topic cluster to filter by. Valid clusters:
201
216
  narrative-structure, prose-and-language, genre-conventions,
202
217
  audience-and-access, world-and-setting, emotional-design,