threadkeeper 0.17.2__tar.gz → 0.17.3__tar.gz

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 (253) hide show
  1. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/PKG-INFO +14 -7
  2. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/README.md +13 -6
  3. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/pyproject.toml +1 -1
  4. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_agent_status.py +38 -0
  5. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_notify_daemon.py +42 -2
  6. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/agent_status.py +42 -11
  7. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/notify.py +40 -4
  8. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/PKG-INFO +14 -7
  9. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/LICENSE +0 -0
  10. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/setup.cfg +0 -0
  11. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_adapters.py +0 -0
  12. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_auto_update.py +0 -0
  13. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_backup.py +0 -0
  14. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_brief_footprint.py +0 -0
  15. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_brief_sections.py +0 -0
  16. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_candidate_reviewer.py +0 -0
  17. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_concepts.py +0 -0
  18. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_config_settings.py +0 -0
  19. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_config_watcher.py +0 -0
  20. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_core_memory.py +0 -0
  21. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_curator.py +0 -0
  22. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_daemon_host_config.py +0 -0
  23. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_daemon_host_integration.py +0 -0
  24. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_daemon_liveness.py +0 -0
  25. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_daemon_state.py +0 -0
  26. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dashboard.py +0 -0
  27. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_db_compact.py +0 -0
  28. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_db_contention.py +0 -0
  29. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_db_migration_fts.py +0 -0
  30. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_db_runtime.py +0 -0
  31. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_db_schema.py +0 -0
  32. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_delegated_search.py +0 -0
  33. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic.py +0 -0
  34. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_feed_tools.py +0 -0
  35. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_miner.py +0 -0
  36. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_observation_resolve.py +0 -0
  37. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_recompute.py +0 -0
  38. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_tier.py +0 -0
  39. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialectic_validator.py +0 -0
  40. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_dialog_fts_external.py +0 -0
  41. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_egress_policy.py +0 -0
  42. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_embed_routing.py +0 -0
  43. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_embedding_generation.py +0 -0
  44. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_embedding_model_revision.py +0 -0
  45. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_embedding_storage_dedup.py +0 -0
  46. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_error_paths.py +0 -0
  47. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_eval_harness.py +0 -0
  48. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_evolve_applier.py +0 -0
  49. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_evolve_apply_2.py +0 -0
  50. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_evolve_apply_3.py +0 -0
  51. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_evolve_daemon.py +0 -0
  52. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_extract_daemon.py +0 -0
  53. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_extract_dedup.py +0 -0
  54. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_forget.py +0 -0
  55. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_github_budget.py +0 -0
  56. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_github_safety.py +0 -0
  57. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_helpers.py +0 -0
  58. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_host_embed.py +0 -0
  59. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_host_env.py +0 -0
  60. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_host_main.py +0 -0
  61. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_host_wedged.py +0 -0
  62. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_hybrid_retrieval.py +0 -0
  63. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_i18n_multilang.py +0 -0
  64. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_identity.py +0 -0
  65. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_ingest_cursor.py +0 -0
  66. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_ingest_denylist.py +0 -0
  67. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_ingest_secret_redaction.py +0 -0
  68. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_ingest_status.py +0 -0
  69. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_ingest_transactions.py +0 -0
  70. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_lessons.py +0 -0
  71. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_mcp_resources_prompts.py +0 -0
  72. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_memory_eval.py +0 -0
  73. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_memory_guard.py +0 -0
  74. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_memory_guard_host.py +0 -0
  75. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_menubar_app.py +0 -0
  76. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_missed_spawns.py +0 -0
  77. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_model_catalog.py +0 -0
  78. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_nudges.py +0 -0
  79. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_onnx_embeddings.py +0 -0
  80. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_panel.py +0 -0
  81. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_permissions.py +0 -0
  82. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_pickup.py +0 -0
  83. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_probe_daemon.py +0 -0
  84. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_process_health.py +0 -0
  85. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_release_workflows.py +0 -0
  86. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_retention.py +0 -0
  87. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_review_prompts.py +0 -0
  88. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_search_fts_punctuation.py +0 -0
  89. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_session_read_purity.py +0 -0
  90. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_shadow_review.py +0 -0
  91. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_single_flight_prompt_prefixes.py +0 -0
  92. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_hint.py +0 -0
  93. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_passive_tier.py +0 -0
  94. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_tier.py +0 -0
  95. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_updater.py +0 -0
  96. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_use_parser.py +0 -0
  97. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skill_watcher.py +0 -0
  98. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_skills.py +0 -0
  99. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_budget.py +0 -0
  100. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_codex_stdin.py +0 -0
  101. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_config.py +0 -0
  102. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_hint.py +0 -0
  103. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_permission_gate.py +0 -0
  104. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_reap.py +0 -0
  105. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_slim.py +0 -0
  106. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_watchdog.py +0 -0
  107. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_spawn_wrap.py +0 -0
  108. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_capture.py +0 -0
  109. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_identity.py +0 -0
  110. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_migrate.py +0 -0
  111. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_protocol.py +0 -0
  112. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_reset_node.py +0 -0
  113. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_sync_transport.py +0 -0
  114. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_task_retention.py +0 -0
  115. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_task_spool.py +0 -0
  116. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_thin_session.py +0 -0
  117. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_thread_janitor.py +0 -0
  118. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_threads.py +0 -0
  119. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_tool_annotations.py +0 -0
  120. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_tools_smoke.py +0 -0
  121. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_validate_threads.py +0 -0
  122. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_vec_search.py +0 -0
  123. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/tests/test_verify_ingest.py +0 -0
  124. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/__init__.py +0 -0
  125. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/_mcp.py +0 -0
  126. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/_setup.py +0 -0
  127. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/_spawn_wrap.py +0 -0
  128. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/__init__.py +0 -0
  129. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/_hook_helpers.py +0 -0
  130. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/antigravity.py +0 -0
  131. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/base.py +0 -0
  132. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/claude_code.py +0 -0
  133. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/claude_desktop.py +0 -0
  134. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/codex.py +0 -0
  135. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/copilot.py +0 -0
  136. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/adapters/vscode.py +0 -0
  137. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/agent_metadata.py +0 -0
  138. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
  139. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/assets/macos-agent-status/README.md +0 -0
  140. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
  141. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
  142. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
  143. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/auto_update.py +0 -0
  144. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/backup.py +0 -0
  145. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/brief.py +0 -0
  146. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/candidate_reviewer.py +0 -0
  147. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/config.py +0 -0
  148. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/config_io.py +0 -0
  149. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/config_watcher.py +0 -0
  150. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/curator.py +0 -0
  151. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/curator_snapshots.py +0 -0
  152. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/daemon_liveness.py +0 -0
  153. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/daemon_state.py +0 -0
  154. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/daemon_supervisor.py +0 -0
  155. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/db.py +0 -0
  156. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/dialectic_miner.py +0 -0
  157. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/dialectic_validator.py +0 -0
  158. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/egress.py +0 -0
  159. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/elicitation.py +0 -0
  160. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/embeddings.py +0 -0
  161. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/eval/__init__.py +0 -0
  162. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/eval/__main__.py +0 -0
  163. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/eval/harness.py +0 -0
  164. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/evolve_applier.py +0 -0
  165. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/evolve_daemon.py +0 -0
  166. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/extract_daemon.py +0 -0
  167. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/forget.py +0 -0
  168. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/github_budget.py +0 -0
  169. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/github_safety.py +0 -0
  170. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/harvest.py +0 -0
  171. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/helpers.py +0 -0
  172. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/host.py +0 -0
  173. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/host_embed.py +0 -0
  174. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/i18n.py +0 -0
  175. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/identity.py +0 -0
  176. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/ingest.py +0 -0
  177. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/lessons.py +0 -0
  178. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/memory_guard.py +0 -0
  179. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/menubar_app.py +0 -0
  180. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/migrate_embeddings.py +0 -0
  181. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/model_catalog.py +0 -0
  182. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/nudges.py +0 -0
  183. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/permissions.py +0 -0
  184. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/probe_daemon.py +0 -0
  185. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/process_health.py +0 -0
  186. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/retention.py +0 -0
  187. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/retrieval.py +0 -0
  188. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/review_prompts.py +0 -0
  189. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/search_proxy.py +0 -0
  190. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/server.py +0 -0
  191. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/shadow_review.py +0 -0
  192. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/skill_audit.py +0 -0
  193. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/skill_updater.py +0 -0
  194. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/skill_watcher.py +0 -0
  195. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/spawn_budget.py +0 -0
  196. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/spawn_config.py +0 -0
  197. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/__init__.py +0 -0
  198. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/capture.py +0 -0
  199. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/daemon.py +0 -0
  200. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/identity.py +0 -0
  201. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/migrate.py +0 -0
  202. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/protocol.py +0 -0
  203. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/reset_node.py +0 -0
  204. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/sync/server.py +0 -0
  205. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/task_spool.py +0 -0
  206. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/thread_janitor.py +0 -0
  207. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tool_schemas.py +0 -0
  208. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/__init__.py +0 -0
  209. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/agent_status.py +0 -0
  210. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/candidate_reviewer.py +0 -0
  211. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/concepts.py +0 -0
  212. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/config_watch.py +0 -0
  213. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/consolidate.py +0 -0
  214. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/core_memory.py +0 -0
  215. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/correlation.py +0 -0
  216. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/curator.py +0 -0
  217. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/dashboard.py +0 -0
  218. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/db_maintenance.py +0 -0
  219. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/dialectic.py +0 -0
  220. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/dialectic_feed.py +0 -0
  221. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/dialog.py +0 -0
  222. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/distill.py +0 -0
  223. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/evolve_applier.py +0 -0
  224. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/extract.py +0 -0
  225. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/forget.py +0 -0
  226. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/graph.py +0 -0
  227. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/invariants.py +0 -0
  228. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/lessons.py +0 -0
  229. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/memory_guard.py +0 -0
  230. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/missed_spawns.py +0 -0
  231. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/panel.py +0 -0
  232. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/peers.py +0 -0
  233. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/pickup.py +0 -0
  234. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/probes.py +0 -0
  235. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/process_health.py +0 -0
  236. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/prompts.py +0 -0
  237. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/resources.py +0 -0
  238. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/session.py +0 -0
  239. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/shadow_review.py +0 -0
  240. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/skills.py +0 -0
  241. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/spawn.py +0 -0
  242. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/style.py +0 -0
  243. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/sync.py +0 -0
  244. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/threads.py +0 -0
  245. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/tools/validate.py +0 -0
  246. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/trash.py +0 -0
  247. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/verify_ingest.py +0 -0
  248. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper/wikilinks.py +0 -0
  249. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/SOURCES.txt +0 -0
  250. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/dependency_links.txt +0 -0
  251. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/entry_points.txt +0 -0
  252. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/requires.txt +0 -0
  253. {threadkeeper-0.17.2 → threadkeeper-0.17.3}/threadkeeper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadkeeper
3
- Version: 0.17.2
3
+ Version: 0.17.3
4
4
  Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
5
5
  Author: thread-keeper contributors
6
6
  License: MIT
@@ -1058,7 +1058,10 @@ Three detection sources per tick:
1058
1058
  from an exhausted subscription; the real outcome only lands in
1059
1059
  `tasks.return_code`. The reason is read from the child's log tail.
1060
1060
  3. **Materialization** — `skill_materialized` / `skill_create` (skill) and
1061
- `lesson_append` (lesson).
1061
+ `lesson_append` (lesson). Each notification contains one readable artifact
1062
+ name: the skill's first heading (falling back to its directory name), or
1063
+ the lesson name with slug separators replaced by spaces. Paths, provenance,
1064
+ and operation metadata stay out of the notification text.
1062
1065
 
1063
1066
  A per-loop cooldown collapses a lapsed-subscription storm into one actionable
1064
1067
  alert; the first run seeds its cursor to the current position, so historical
@@ -1089,12 +1092,16 @@ will register the app in **System Settings ▸ Notifications** and show banners;
1089
1092
  banners are titled **Thread-Keeper** (`CFBundleDisplayName`). The first launch
1090
1093
  after an update shows a one-time permission prompt.
1091
1094
 
1092
- `agent_status` feeds the app two lists — `recent_results` (positive: captured
1093
- skills/lessons) and `recent_failures` (the two failure sources above) — each item
1094
- tagged with a `notify` flag computed from the toggles below. The app **lists**
1095
+ `agent_status` feeds the app two lists — `recent_results` (named skill/lesson
1096
+ write events plus completed-task history) and `recent_failures` (the two failure
1097
+ sources above) — each item tagged with a `notify` flag computed from the toggles
1098
+ below. The app **lists**
1095
1099
  every item in its menu but only **posts a banner** for flagged ones, so turning a
1096
- category off silences the banner without hiding the history. Enabling a toggle
1097
- never replays backlog.
1100
+ category off silences the banner without hiding the history. Each materialization
1101
+ uses its own skill or lesson toggle; generic task reports are history-only.
1102
+ Foreground writes appear too, without waiting for a background child to finish.
1103
+ A pathless skill mark only silences its reminder and has no materialization
1104
+ banner. Enabling a toggle never replays backlog.
1098
1105
 
1099
1106
  **Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
1100
1107
  edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
@@ -1014,7 +1014,10 @@ Three detection sources per tick:
1014
1014
  from an exhausted subscription; the real outcome only lands in
1015
1015
  `tasks.return_code`. The reason is read from the child's log tail.
1016
1016
  3. **Materialization** — `skill_materialized` / `skill_create` (skill) and
1017
- `lesson_append` (lesson).
1017
+ `lesson_append` (lesson). Each notification contains one readable artifact
1018
+ name: the skill's first heading (falling back to its directory name), or
1019
+ the lesson name with slug separators replaced by spaces. Paths, provenance,
1020
+ and operation metadata stay out of the notification text.
1018
1021
 
1019
1022
  A per-loop cooldown collapses a lapsed-subscription storm into one actionable
1020
1023
  alert; the first run seeds its cursor to the current position, so historical
@@ -1045,12 +1048,16 @@ will register the app in **System Settings ▸ Notifications** and show banners;
1045
1048
  banners are titled **Thread-Keeper** (`CFBundleDisplayName`). The first launch
1046
1049
  after an update shows a one-time permission prompt.
1047
1050
 
1048
- `agent_status` feeds the app two lists — `recent_results` (positive: captured
1049
- skills/lessons) and `recent_failures` (the two failure sources above) — each item
1050
- tagged with a `notify` flag computed from the toggles below. The app **lists**
1051
+ `agent_status` feeds the app two lists — `recent_results` (named skill/lesson
1052
+ write events plus completed-task history) and `recent_failures` (the two failure
1053
+ sources above) — each item tagged with a `notify` flag computed from the toggles
1054
+ below. The app **lists**
1051
1055
  every item in its menu but only **posts a banner** for flagged ones, so turning a
1052
- category off silences the banner without hiding the history. Enabling a toggle
1053
- never replays backlog.
1056
+ category off silences the banner without hiding the history. Each materialization
1057
+ uses its own skill or lesson toggle; generic task reports are history-only.
1058
+ Foreground writes appear too, without waiting for a background child to finish.
1059
+ A pathless skill mark only silences its reminder and has no materialization
1060
+ banner. Enabling a toggle never replays backlog.
1054
1061
 
1055
1062
  **Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
1056
1063
  edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadkeeper"
7
- version = "0.17.2"
7
+ version = "0.17.3"
8
8
  description = "Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server."
9
9
  requires-python = ">=3.11"
10
10
  authors = [{ name = "thread-keeper contributors" }]
@@ -4,6 +4,8 @@ import json
4
4
  import os
5
5
  import time
6
6
 
7
+ import pytest
8
+
7
9
 
8
10
  _FAKE_CID = "33334444-5555-6666-7777-888899990000"
9
11
 
@@ -468,6 +470,42 @@ def test_agent_status_recent_results_for_useful_completed_tasks(mp_with_cid):
468
470
  assert snap["recent_results"][0]["summary"] == (
469
471
  "Processed 2 candidates into durable notes."
470
472
  )
473
+ assert snap["recent_results"][0]["notify"] is False
474
+
475
+
476
+ @pytest.mark.parametrize("poll,skill,lesson", [
477
+ (30, True, True), (30, True, False), (30, False, True), (0, True, True),
478
+ ])
479
+ def test_materialization_results_name_actual_writes(mp_with_cid, poll, skill, lesson):
480
+ pkg = mp_with_cid(_FAKE_CID)
481
+ pkg["config"].NOTIFY_POLL_S = poll
482
+ pkg["config"].NOTIFY_SKILL_MATERIALIZED = skill
483
+ pkg["config"].NOTIFY_LESSON = lesson
484
+ result = _txt(_tool(pkg, "skill_manage")(
485
+ action="create", name="api-contract-testing",
486
+ description="Test API contracts.", content="# API contract testing\n\nUse schemas.",
487
+ ))
488
+ assert result.startswith("ok"), result
489
+ result = _txt(_tool(pkg, "lesson_append")(
490
+ title="Verify state transitions", body="Assert the state after the action.",
491
+ source="T1",
492
+ ))
493
+ assert result.startswith("ok"), result
494
+ _insert_completed_task(pkg, "tk_generic_done",
495
+ "You are a CANDIDATE REVIEWER for thread-keeper's extract queue.",
496
+ "Processed 2 candidates into durable notes.\n")
497
+
498
+ from threadkeeper.agent_status import agent_status_snapshot
499
+
500
+ items = agent_status_snapshot(refresh=False)["recent_results"]
501
+ by_role = {item["role"]: item for item in items}
502
+ assert by_role["skill"]["summary"] == "API contract testing"
503
+ assert by_role["lesson"]["summary"] == "Verify state transitions"
504
+ assert by_role["skill"]["notify"] is bool(poll and skill)
505
+ assert by_role["lesson"]["notify"] is bool(poll and lesson)
506
+ assert by_role["candidate_reviewer"]["notify"] is False
507
+ assert all(item["id"].startswith("materialization:")
508
+ for item in items if item["role"] in ("skill", "lesson"))
471
509
 
472
510
 
473
511
  def test_agent_status_mcp_json_output(mp_with_cid):
@@ -228,8 +228,48 @@ def test_positives_on_fire(tmp_path, monkeypatch, caplog):
228
228
  with caplog.at_level(logging.WARNING, logger="threadkeeper.notify"):
229
229
  out = notify.run_notify_pass(force=True)
230
230
  assert out == "ok fired=2", out
231
- assert "skill materialized" in caplog.text
232
- assert "lesson added" in caplog.text and "my-lesson" in caplog.text
231
+ assert "skill materialized | Foo" in caplog.text
232
+ assert "lesson added | My lesson" in caplog.text
233
+ assert "/skills/" not in caplog.text
234
+ assert "op=create" not in caplog.text and "source=shadow" not in caplog.text
235
+
236
+
237
+ @pytest.mark.parametrize("directory", [False, True])
238
+ def test_skill_notification_uses_heading(tmp_path, monkeypatch, directory):
239
+ m = _bootstrap(tmp_path, monkeypatch, skill="true")
240
+ md = tmp_path / "api-contract-testing" / "SKILL.md"
241
+ md.parent.mkdir()
242
+ md.write_text("---\nname: api-contract-testing\ndescription: |\n"
243
+ " Internal description\n---\n\n# API contract testing\n"
244
+ "\nLong implementation details that do not belong in a banner.\n")
245
+ notify = m["notify"]
246
+ conn = m["db"].get_db()
247
+ sent = []
248
+ monkeypatch.setattr(notify, "_dispatch", lambda *args: sent.append(args))
249
+ assert notify.run_notify_pass(force=True) == "seed"
250
+ _ev(conn, "skill_materialized", target="T1",
251
+ summary=str(md.parent if directory else md))
252
+ assert notify.run_notify_pass(force=True) == "ok fired=1"
253
+ assert sent == [("Thread-keeper: skill materialized", "API contract testing")]
254
+ assert notify.run_notify_pass(force=True) == "ok fired=0"
255
+
256
+
257
+ @pytest.mark.parametrize("body", [b"---\nname: foo\n---\nNo heading.", b"\xff"])
258
+ def test_skill_notification_falls_back_to_name(tmp_path, monkeypatch, body):
259
+ notify = _bootstrap(tmp_path, monkeypatch)["notify"]
260
+ md = tmp_path / "api-contract-testing" / "SKILL.md"
261
+ md.parent.mkdir()
262
+ md.write_bytes(body)
263
+ assert notify._fmt_skill({"kind": "skill_create", "target": "api-contract-testing",
264
+ "summary": str(md)})[1] == "Api contract testing"
265
+
266
+
267
+ def test_pathless_skill_mark_does_not_claim_a_materialization(tmp_path, monkeypatch):
268
+ m = _bootstrap(tmp_path, monkeypatch, skill="true")
269
+ notify, conn = m["notify"], m["db"].get_db()
270
+ assert notify.run_notify_pass(force=True) == "seed"
271
+ _ev(conn, "skill_materialized", target="T1", summary="(no path recorded)")
272
+ assert notify.run_notify_pass(force=True) == "ok fired=0"
233
273
 
234
274
 
235
275
  # ── disabled ────────────────────────────────────────────────────────────────
@@ -878,17 +878,47 @@ def _extract_useful_result(task_id: str) -> str:
878
878
  return ""
879
879
 
880
880
 
881
- def _recent_results(conn, now: int, limit: int = 10) -> list[dict[str, Any]]:
881
+ def _recent_materializations(conn, now: int, limit: int) -> list[dict[str, Any]]:
882
+ """Name actual writes, including foreground writes without a child task."""
882
883
  from . import config
883
- # Positive materialization notifications (#257). These are captured skills /
884
- # lessons surfaced as useful loop output; the app posts a banner only when
885
- # notifications are enabled (NOTIFY_POLL_S master gate) and a positive toggle
886
- # is on, but the menu always lists them (`notify` flag).
884
+ from .notify import _fmt_skill, _fmt_lesson
885
+
887
886
  enabled = float(getattr(config, "NOTIFY_POLL_S", 0) or 0) > 0
888
- positive_notify = enabled and bool(
889
- getattr(config, "NOTIFY_SKILL_MATERIALIZED", False)
890
- or getattr(config, "NOTIFY_LESSON", False)
891
- )
887
+ rows = conn.execute(
888
+ "SELECT id, kind, target, summary, created_at FROM events "
889
+ "WHERE kind IN ('skill_create', 'skill_materialized', 'lesson_append') "
890
+ "AND created_at>=? ORDER BY created_at DESC, id DESC LIMIT ?",
891
+ (now - _RESULT_WINDOW_S, int(limit)),
892
+ ).fetchall()
893
+ results = []
894
+ for row in rows:
895
+ skill = row["kind"] != "lesson_append"
896
+ title, summary = (_fmt_skill if skill else _fmt_lesson)(dict(row))
897
+ if not summary:
898
+ continue
899
+ age_s = max(0, now - row["created_at"])
900
+ results.append({
901
+ "id": f"materialization:{row['id']}",
902
+ "task_id": "",
903
+ "role": "skill" if skill else "lesson",
904
+ "loop_id": "",
905
+ "loop_name": "Skill" if skill else "Lesson",
906
+ "title": title,
907
+ "summary": summary,
908
+ "ended_at": row["created_at"],
909
+ "age_s": age_s,
910
+ "age": fmt_age(age_s),
911
+ "notify": enabled and bool(
912
+ config.NOTIFY_SKILL_MATERIALIZED if skill else config.NOTIFY_LESSON
913
+ ),
914
+ })
915
+ return results
916
+
917
+
918
+ def _recent_results(conn, now: int, limit: int = 10) -> list[dict[str, Any]]:
919
+ # Event-backed materializations carry names and per-category toggles.
920
+ # Generic completion reports remain in history without duplicate banners.
921
+ materializations = _recent_materializations(conn, now, limit)
892
922
  role_loop = _role_to_loop()
893
923
  rows = conn.execute(
894
924
  "SELECT id, prompt, ended_at, return_code FROM tasks "
@@ -919,11 +949,12 @@ def _recent_results(conn, now: int, limit: int = 10) -> list[dict[str, Any]]:
919
949
  "ended_at": ended_at,
920
950
  "age_s": age_s,
921
951
  "age": fmt_age(age_s),
922
- "notify": positive_notify,
952
+ "notify": False,
923
953
  })
924
954
  if len(results) >= limit:
925
955
  break
926
- return results
956
+ return sorted(materializations + results, key=lambda r: r["ended_at"],
957
+ reverse=True)[:limit]
927
958
 
928
959
 
929
960
  def _recent_failures(conn, now: int, limit: int = 10) -> list[dict[str, Any]]:
@@ -27,6 +27,8 @@ from __future__ import annotations
27
27
 
28
28
  import logging
29
29
  import os
30
+ from pathlib import Path
31
+ import re
30
32
  import sqlite3
31
33
  import subprocess
32
34
  import threading
@@ -349,11 +351,43 @@ def _fmt_child(c: dict) -> tuple[str, str]:
349
351
 
350
352
 
351
353
  def _fmt_skill(s: dict) -> tuple[str, str]:
352
- return ("Thread-keeper: skill materialized", (s["summary"] or s["target"])[:200])
354
+ path = (s.get("summary") or "").strip()
355
+ name = s.get("target", "") if s.get("kind") == "skill_create" else ""
356
+ if path and path != "(no path recorded)":
357
+ md = Path(path).expanduser()
358
+ if md.name == "SKILL.md":
359
+ name = md.parent.name
360
+ else:
361
+ name = md.name
362
+ md = md / "SKILL.md"
363
+ try:
364
+ # A title is near the top. Never load an entire skill (or follow
365
+ # paths mentioned in its body) just to render a notification.
366
+ body = ""
367
+ if md.is_file():
368
+ with md.open(encoding="utf-8") as stream:
369
+ body = stream.read(8192)
370
+ if body.startswith("---\n"):
371
+ body = body.split("\n---", 1)[-1]
372
+ heading = re.search(r"^#\s+(.+?)(?:\s+#+)?\s*$", body, re.MULTILINE)
373
+ if heading:
374
+ name = heading.group(1)
375
+ except (OSError, ValueError):
376
+ pass
377
+ return ("Thread-keeper: skill materialized", _artifact_name(name))
378
+
379
+
380
+ def _artifact_name(name: str) -> str:
381
+ """One readable name, without slug separators or Markdown decoration."""
382
+ name = " ".join((name or "").replace("`", "").split())
383
+ if " " not in name:
384
+ name = re.sub(r"[-_]+", " ", name)
385
+ name = name[:1].upper() + name[1:]
386
+ return name[:200]
353
387
 
354
388
 
355
389
  def _fmt_lesson(l: dict) -> tuple[str, str]:
356
- return ("Thread-keeper: lesson added", f"{l['target']} ({l['summary']})"[:200])
390
+ return ("Thread-keeper: lesson added", _artifact_name(l["target"]))
357
391
 
358
392
 
359
393
  # ── pass + daemon lifecycle ─────────────────────────────────────────────────
@@ -401,8 +435,10 @@ def run_notify_pass(force: bool = False, *, scheduled: bool = False) -> str:
401
435
  _dispatch(*_fmt_child(c))
402
436
  fired += 1
403
437
  for s in skills:
404
- _dispatch(*_fmt_skill(s))
405
- fired += 1
438
+ title, body = _fmt_skill(s)
439
+ if body: # A pathless mark only silences a nudge; no named artifact.
440
+ _dispatch(title, body)
441
+ fired += 1
406
442
  for l in lessons:
407
443
  _dispatch(*_fmt_lesson(l))
408
444
  fired += 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadkeeper
3
- Version: 0.17.2
3
+ Version: 0.17.3
4
4
  Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
5
5
  Author: thread-keeper contributors
6
6
  License: MIT
@@ -1058,7 +1058,10 @@ Three detection sources per tick:
1058
1058
  from an exhausted subscription; the real outcome only lands in
1059
1059
  `tasks.return_code`. The reason is read from the child's log tail.
1060
1060
  3. **Materialization** — `skill_materialized` / `skill_create` (skill) and
1061
- `lesson_append` (lesson).
1061
+ `lesson_append` (lesson). Each notification contains one readable artifact
1062
+ name: the skill's first heading (falling back to its directory name), or
1063
+ the lesson name with slug separators replaced by spaces. Paths, provenance,
1064
+ and operation metadata stay out of the notification text.
1062
1065
 
1063
1066
  A per-loop cooldown collapses a lapsed-subscription storm into one actionable
1064
1067
  alert; the first run seeds its cursor to the current position, so historical
@@ -1089,12 +1092,16 @@ will register the app in **System Settings ▸ Notifications** and show banners;
1089
1092
  banners are titled **Thread-Keeper** (`CFBundleDisplayName`). The first launch
1090
1093
  after an update shows a one-time permission prompt.
1091
1094
 
1092
- `agent_status` feeds the app two lists — `recent_results` (positive: captured
1093
- skills/lessons) and `recent_failures` (the two failure sources above) — each item
1094
- tagged with a `notify` flag computed from the toggles below. The app **lists**
1095
+ `agent_status` feeds the app two lists — `recent_results` (named skill/lesson
1096
+ write events plus completed-task history) and `recent_failures` (the two failure
1097
+ sources above) — each item tagged with a `notify` flag computed from the toggles
1098
+ below. The app **lists**
1095
1099
  every item in its menu but only **posts a banner** for flagged ones, so turning a
1096
- category off silences the banner without hiding the history. Enabling a toggle
1097
- never replays backlog.
1100
+ category off silences the banner without hiding the history. Each materialization
1101
+ uses its own skill or lesson toggle; generic task reports are history-only.
1102
+ Foreground writes appear too, without waiting for a background child to finish.
1103
+ A pathless skill mark only silences its reminder and has no materialization
1104
+ banner. Enabling a toggle never replays backlog.
1098
1105
 
1099
1106
  **Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
1100
1107
  edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
File without changes
File without changes