novacode-cli 1.2.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 (691) hide show
  1. novacode_cli/__init__.py +29 -0
  2. novacode_cli/__main__.py +6 -0
  3. novacode_cli/_lazy_heavy.py +99 -0
  4. novacode_cli/_version.py +3 -0
  5. novacode_cli/agent_stream.py +178 -0
  6. novacode_cli/agents/agent_file.py +250 -0
  7. novacode_cli/agents/agent_mailbox.py +120 -0
  8. novacode_cli/agents/approval_decisions.py +146 -0
  9. novacode_cli/agents/arg_repair.py +196 -0
  10. novacode_cli/agents/async_agents/__init__.py +6 -0
  11. novacode_cli/agents/async_agents/_config.py +213 -0
  12. novacode_cli/agents/async_agents/_mcp_tools.py +192 -0
  13. novacode_cli/agents/async_agents/_specialist.py +84 -0
  14. novacode_cli/agents/async_agents/_test_commands.py +89 -0
  15. novacode_cli/agents/async_agents/_tools.py +432 -0
  16. novacode_cli/agents/async_agents/general_purpose.py +42 -0
  17. novacode_cli/agents/async_agents/langgraph.json +9 -0
  18. novacode_cli/agents/async_agents/research.py +22 -0
  19. novacode_cli/agents/async_context.py +303 -0
  20. novacode_cli/agents/async_on_demand.py +52 -0
  21. novacode_cli/agents/async_task_errors.py +75 -0
  22. novacode_cli/agents/async_workspace.py +27 -0
  23. novacode_cli/agents/attribution.py +165 -0
  24. novacode_cli/agents/browser_mode.py +119 -0
  25. novacode_cli/agents/btw_agent.py +71 -0
  26. novacode_cli/agents/caveman.py +352 -0
  27. novacode_cli/agents/caveman_support.py +370 -0
  28. novacode_cli/agents/code_mode.py +774 -0
  29. novacode_cli/agents/code_runtime.py +160 -0
  30. novacode_cli/agents/context_compaction.py +1339 -0
  31. novacode_cli/agents/core_agent.py +2710 -0
  32. novacode_cli/agents/default_subagents/__init__.py +24 -0
  33. novacode_cli/agents/default_subagents/async_subagents.py +296 -0
  34. novacode_cli/agents/default_subagents/builtin/browser-automation-agent.md +110 -0
  35. novacode_cli/agents/default_subagents/builtin/bug-fix-agent.md +22 -0
  36. novacode_cli/agents/default_subagents/builtin/bug-investigation-agent.md +42 -0
  37. novacode_cli/agents/default_subagents/builtin/code-explorer.md +37 -0
  38. novacode_cli/agents/default_subagents/builtin/code-review-agent.md +33 -0
  39. novacode_cli/agents/default_subagents/builtin/dependency-audit-agent.md +34 -0
  40. novacode_cli/agents/default_subagents/builtin/documentation-update-agent.md +46 -0
  41. novacode_cli/agents/default_subagents/builtin/performance-audit-agent.md +41 -0
  42. novacode_cli/agents/default_subagents/builtin/plan-scout-agent.md +64 -0
  43. novacode_cli/agents/default_subagents/builtin/refactoring-agent.md +35 -0
  44. novacode_cli/agents/default_subagents/builtin/refactoring-specialist-agent.md +175 -0
  45. novacode_cli/agents/default_subagents/builtin/security-audit-agent.md +96 -0
  46. novacode_cli/agents/default_subagents/builtin/test-generation-agent.md +33 -0
  47. novacode_cli/agents/default_subagents/builtin/test-runner-agent.md +55 -0
  48. novacode_cli/agents/default_subagents/prompt.py +88 -0
  49. novacode_cli/agents/default_subagents/subagents.py +138 -0
  50. novacode_cli/agents/dream_agent.py +93 -0
  51. novacode_cli/agents/edit_diagnostics.py +362 -0
  52. novacode_cli/agents/edit_fallback.py +62 -0
  53. novacode_cli/agents/handoff.py +166 -0
  54. novacode_cli/agents/invocation.py +152 -0
  55. novacode_cli/agents/model_budget.py +42 -0
  56. novacode_cli/agents/model_retry.py +113 -0
  57. novacode_cli/agents/model_router.py +420 -0
  58. novacode_cli/agents/openai_decisions.py +186 -0
  59. novacode_cli/agents/plan_agent/__init__.py +12 -0
  60. novacode_cli/agents/plan_agent/plan_agent.py +357 -0
  61. novacode_cli/agents/plan_agent/plan_mode_middleware.py +64 -0
  62. novacode_cli/agents/plan_execution.py +237 -0
  63. novacode_cli/agents/ponytail.py +66 -0
  64. novacode_cli/agents/prime.py +274 -0
  65. novacode_cli/agents/provider_caching.py +99 -0
  66. novacode_cli/agents/server_launcher.py +904 -0
  67. novacode_cli/agents/strict_tools.py +384 -0
  68. novacode_cli/agents/task_discipline.py +294 -0
  69. novacode_cli/agents/tool_call_repair.py +290 -0
  70. novacode_cli/agents/tool_offload.py +291 -0
  71. novacode_cli/agents/tool_search.py +806 -0
  72. novacode_cli/agents/tool_search_variants.py +106 -0
  73. novacode_cli/agents/tool_verdicts.py +1194 -0
  74. novacode_cli/agents/user_async_agents.py +312 -0
  75. novacode_cli/artifacts/__init__.py +10 -0
  76. novacode_cli/artifacts/registry.py +278 -0
  77. novacode_cli/artifacts/server.py +426 -0
  78. novacode_cli/assets/Nova.png +0 -0
  79. novacode_cli/assets/nova-avatar.png +0 -0
  80. novacode_cli/audio/__init__.py +301 -0
  81. novacode_cli/audio/capture.py +137 -0
  82. novacode_cli/audio/downloads.py +273 -0
  83. novacode_cli/audio/pipeline.py +391 -0
  84. novacode_cli/audio/playback.py +87 -0
  85. novacode_cli/audio/providers.py +208 -0
  86. novacode_cli/audio/speakable.py +78 -0
  87. novacode_cli/audio/stt.py +132 -0
  88. novacode_cli/audio/stt_deepgram.py +69 -0
  89. novacode_cli/audio/stt_elevenlabs.py +89 -0
  90. novacode_cli/audio/stt_parakeet.py +94 -0
  91. novacode_cli/audio/stt_whistle.py +61 -0
  92. novacode_cli/audio/summarize.py +92 -0
  93. novacode_cli/audio/tts.py +85 -0
  94. novacode_cli/audio/tts_kokoro.py +73 -0
  95. novacode_cli/audio/tts_orpheus.py +180 -0
  96. novacode_cli/audio/tts_pocket.py +131 -0
  97. novacode_cli/audio/tts_sano.py +69 -0
  98. novacode_cli/audio/vad.py +145 -0
  99. novacode_cli/backends/__init__.py +17 -0
  100. novacode_cli/backends/filesystem.py +894 -0
  101. novacode_cli/backends/native_search.py +92 -0
  102. novacode_cli/bootstrap/__init__.py +9 -0
  103. novacode_cli/bootstrap/env_snapshot.py +240 -0
  104. novacode_cli/bootstrap/steering.py +464 -0
  105. novacode_cli/bootstrap/vision_router.py +604 -0
  106. novacode_cli/brand.py +362 -0
  107. novacode_cli/cli_args.py +718 -0
  108. novacode_cli/commands/__init__.py +237 -0
  109. novacode_cli/commands/agents_commands.py +231 -0
  110. novacode_cli/commands/auth_handler.py +40 -0
  111. novacode_cli/commands/browser_use_handler.py +75 -0
  112. novacode_cli/commands/cache_handler.py +32 -0
  113. novacode_cli/commands/chat_handler.py +1357 -0
  114. novacode_cli/commands/ci_fix.py +171 -0
  115. novacode_cli/commands/claude_plan.py +331 -0
  116. novacode_cli/commands/codex_plan.py +195 -0
  117. novacode_cli/commands/commands.py +1113 -0
  118. novacode_cli/commands/create_handler.py +124 -0
  119. novacode_cli/commands/create_server.py +1887 -0
  120. novacode_cli/commands/cron_handler.py +119 -0
  121. novacode_cli/commands/dream_handler.py +203 -0
  122. novacode_cli/commands/effort_handler.py +65 -0
  123. novacode_cli/commands/evolution_handler.py +214 -0
  124. novacode_cli/commands/file_commands.py +381 -0
  125. novacode_cli/commands/handoff_handler.py +68 -0
  126. novacode_cli/commands/hooks_handler.py +269 -0
  127. novacode_cli/commands/init_handler.py +146 -0
  128. novacode_cli/commands/learning_handler.py +160 -0
  129. novacode_cli/commands/lite_mode_handler.py +76 -0
  130. novacode_cli/commands/log_commands.py +363 -0
  131. novacode_cli/commands/mcp_handler.py +29 -0
  132. novacode_cli/commands/model_handler.py +29 -0
  133. novacode_cli/commands/notifications_handler.py +158 -0
  134. novacode_cli/commands/onboard_handler.py +121 -0
  135. novacode_cli/commands/plan_handler.py +440 -0
  136. novacode_cli/commands/planner_usage.py +250 -0
  137. novacode_cli/commands/plugin_install_handler.py +38 -0
  138. novacode_cli/commands/plugins_handler.py +35 -0
  139. novacode_cli/commands/prime_handler.py +455 -0
  140. novacode_cli/commands/prompt_handler.py +149 -0
  141. novacode_cli/commands/provider_usage.py +160 -0
  142. novacode_cli/commands/ralph_events.py +106 -0
  143. novacode_cli/commands/ralph_handler.py +1293 -0
  144. novacode_cli/commands/refine_handler.py +213 -0
  145. novacode_cli/commands/research_handler.py +202 -0
  146. novacode_cli/commands/rootapp_remote.py +83 -0
  147. novacode_cli/commands/router_handler.py +36 -0
  148. novacode_cli/commands/server_commands.py +249 -0
  149. novacode_cli/commands/session_commands.py +203 -0
  150. novacode_cli/commands/share_handler.py +450 -0
  151. novacode_cli/commands/side_commands.py +366 -0
  152. novacode_cli/commands/skill_invoke.py +420 -0
  153. novacode_cli/commands/skills_commands.py +48 -0
  154. novacode_cli/commands/trace_handler.py +322 -0
  155. novacode_cli/commands/trello_handler.py +226 -0
  156. novacode_cli/commands/trello_remote.py +177 -0
  157. novacode_cli/commands/trello_server.py +1214 -0
  158. novacode_cli/commands/vision_handler.py +127 -0
  159. novacode_cli/commands/voice_handler.py +447 -0
  160. novacode_cli/commands/webhook_handler.py +134 -0
  161. novacode_cli/commands/wiki_commands.py +227 -0
  162. novacode_cli/compaction.py +753 -0
  163. novacode_cli/computation_cache.py +159 -0
  164. novacode_cli/computer/__init__.py +1 -0
  165. novacode_cli/computer/persistent.py +234 -0
  166. novacode_cli/computer/preview.py +253 -0
  167. novacode_cli/computer/runtime.py +474 -0
  168. novacode_cli/computer/windows_console.py +152 -0
  169. novacode_cli/computer/windows_qemu.py +211 -0
  170. novacode_cli/config/browser_mode.py +159 -0
  171. novacode_cli/config/catalog_models.py +95 -0
  172. novacode_cli/config/config.py +1388 -0
  173. novacode_cli/config/credentials.py +540 -0
  174. novacode_cli/config/google_oauth_auth.py +281 -0
  175. novacode_cli/config/model_capabilities.py +343 -0
  176. novacode_cli/config/model_catalog.py +275 -0
  177. novacode_cli/config/model_create.py +1325 -0
  178. novacode_cli/config/model_manager.py +695 -0
  179. novacode_cli/config/nova_config.py +1283 -0
  180. novacode_cli/config/openai_chatgpt_auth.py +384 -0
  181. novacode_cli/config/opencode_gateway.py +25 -0
  182. novacode_cli/config/plan_mode.py +188 -0
  183. novacode_cli/config/provider_auth.py +438 -0
  184. novacode_cli/config/provider_catalog.json +5127 -0
  185. novacode_cli/config/provider_registry.py +38 -0
  186. novacode_cli/config/provider_transports.py +258 -0
  187. novacode_cli/config/role_models.py +175 -0
  188. novacode_cli/context/__init__.py +50 -0
  189. novacode_cli/context/_analysis.py +799 -0
  190. novacode_cli/context/_budget.py +116 -0
  191. novacode_cli/context/_conversation.py +73 -0
  192. novacode_cli/context/_dynamic.py +264 -0
  193. novacode_cli/context/_model_config.py +380 -0
  194. novacode_cli/context/_models_dev.py +99 -0
  195. novacode_cli/context/archives.py +248 -0
  196. novacode_cli/context/estimates.py +65 -0
  197. novacode_cli/context/history.py +17 -0
  198. novacode_cli/context/knowledge_cache.py +292 -0
  199. novacode_cli/context/manager.py +101 -0
  200. novacode_cli/context/observations.py +65 -0
  201. novacode_cli/context/pressure.py +168 -0
  202. novacode_cli/context/provider_accounting.py +109 -0
  203. novacode_cli/context/snapshot.py +493 -0
  204. novacode_cli/context/strategies.py +279 -0
  205. novacode_cli/context/task_progress.py +122 -0
  206. novacode_cli/core/__init__.py +0 -0
  207. novacode_cli/core/agent_loop.py +1821 -0
  208. novacode_cli/core/autonomous_loop.py +277 -0
  209. novacode_cli/core/code_approvals.py +57 -0
  210. novacode_cli/core/input_preparation.py +276 -0
  211. novacode_cli/core/reasoning.py +67 -0
  212. novacode_cli/core/streaming.py +326 -0
  213. novacode_cli/core/subagent_tasks.py +696 -0
  214. novacode_cli/core/subagent_tracking.py +235 -0
  215. novacode_cli/core/tool_results.py +116 -0
  216. novacode_cli/core/verification_loop.py +272 -0
  217. novacode_cli/council.py +595 -0
  218. novacode_cli/council_consensus.py +115 -0
  219. novacode_cli/council_decision.py +157 -0
  220. novacode_cli/council_planner.py +1421 -0
  221. novacode_cli/council_settings.py +208 -0
  222. novacode_cli/council_web_settings.py +41 -0
  223. novacode_cli/council_workspace.py +56 -0
  224. novacode_cli/cowork/__init__.py +10 -0
  225. novacode_cli/cowork/broker_middleware.py +162 -0
  226. novacode_cli/cowork/launcher.py +102 -0
  227. novacode_cli/cowork/policy.py +284 -0
  228. novacode_cli/cowork/ui.py +826 -0
  229. novacode_cli/daemons/__init__.py +1 -0
  230. novacode_cli/daemons/registry.py +342 -0
  231. novacode_cli/desktop_notifications.py +302 -0
  232. novacode_cli/doctor.py +251 -0
  233. novacode_cli/embeddings.py +109 -0
  234. novacode_cli/entrypoint.py +56 -0
  235. novacode_cli/errors/__init__.py +19 -0
  236. novacode_cli/errors/handlers.py +648 -0
  237. novacode_cli/errors/provider_errors.py +249 -0
  238. novacode_cli/errors/taxonomy.py +80 -0
  239. novacode_cli/events.py +179 -0
  240. novacode_cli/file_ops.py +714 -0
  241. novacode_cli/git_safety.py +281 -0
  242. novacode_cli/handoff.py +383 -0
  243. novacode_cli/headless/__init__.py +18 -0
  244. novacode_cli/headless/input.py +58 -0
  245. novacode_cli/headless/output.py +224 -0
  246. novacode_cli/headless/pipe.py +532 -0
  247. novacode_cli/headless/runner.py +461 -0
  248. novacode_cli/hermes/__init__.py +39 -0
  249. novacode_cli/hermes/activation.py +297 -0
  250. novacode_cli/hermes/config.py +129 -0
  251. novacode_cli/hermes/curator.py +199 -0
  252. novacode_cli/hermes/evolution.py +374 -0
  253. novacode_cli/hermes/ledger.py +364 -0
  254. novacode_cli/hermes/memory_tiers.py +969 -0
  255. novacode_cli/hermes/middleware.py +678 -0
  256. novacode_cli/hermes/overview.py +174 -0
  257. novacode_cli/hermes/prompt_evolution.py +503 -0
  258. novacode_cli/hermes/refine_loop.py +643 -0
  259. novacode_cli/hermes/refinement_log.py +190 -0
  260. novacode_cli/hermes/review.py +674 -0
  261. novacode_cli/hermes/skill_debate.py +135 -0
  262. novacode_cli/hermes/skill_discovery.py +450 -0
  263. novacode_cli/hermes/skill_manager.py +335 -0
  264. novacode_cli/hermes/skill_proposals.py +43 -0
  265. novacode_cli/hermes/test_gaming.py +119 -0
  266. novacode_cli/hermes/tracker.py +435 -0
  267. novacode_cli/hermes/tuner.py +206 -0
  268. novacode_cli/hermes/verification_memory.py +134 -0
  269. novacode_cli/hermes/verifier.py +277 -0
  270. novacode_cli/hitl/__init__.py +22 -0
  271. novacode_cli/hitl/interrupts.py +493 -0
  272. novacode_cli/hooks.py +359 -0
  273. novacode_cli/image_utils.py +517 -0
  274. novacode_cli/init/__init__.py +7 -0
  275. novacode_cli/init/events.py +43 -0
  276. novacode_cli/input_utils.py +526 -0
  277. novacode_cli/install_commands.py +87 -0
  278. novacode_cli/integrations/__init__.py +82 -0
  279. novacode_cli/integrations/daytona.py +122 -0
  280. novacode_cli/integrations/docker.py +309 -0
  281. novacode_cli/integrations/host_path.py +111 -0
  282. novacode_cli/integrations/langsmith.py +151 -0
  283. novacode_cli/integrations/modal.py +134 -0
  284. novacode_cli/integrations/runloop.py +130 -0
  285. novacode_cli/integrations/sandbox_factory.py +1388 -0
  286. novacode_cli/integrations/sandbox_registry.py +312 -0
  287. novacode_cli/integrations/workdir_backend.py +589 -0
  288. novacode_cli/main.py +2078 -0
  289. novacode_cli/mcp/__init__.py +73 -0
  290. novacode_cli/mcp/auth.py +189 -0
  291. novacode_cli/mcp/client.py +571 -0
  292. novacode_cli/mcp/code_mode.py +5 -0
  293. novacode_cli/mcp/code_runtime.py +5 -0
  294. novacode_cli/mcp/commands.py +902 -0
  295. novacode_cli/mcp/config.py +406 -0
  296. novacode_cli/mcp/generic.py +128 -0
  297. novacode_cli/mcp/middleware.py +993 -0
  298. novacode_cli/mcp/presets.py +367 -0
  299. novacode_cli/memory/__init__.py +1 -0
  300. novacode_cli/memory/agent_memory.py +1312 -0
  301. novacode_cli/memory/builtin_lessons.py +60 -0
  302. novacode_cli/memory/dream_gates.py +192 -0
  303. novacode_cli/memory/limits.py +59 -0
  304. novacode_cli/memory/parsed_cache.py +106 -0
  305. novacode_cli/memory/recall_stats.py +97 -0
  306. novacode_cli/memory/semantic.py +116 -0
  307. novacode_cli/memory/store.py +335 -0
  308. novacode_cli/memory/wiki.py +274 -0
  309. novacode_cli/migrate.py +185 -0
  310. novacode_cli/onboarding.py +649 -0
  311. novacode_cli/onboarding_check.py +119 -0
  312. novacode_cli/path_approval.py +297 -0
  313. novacode_cli/plan_archive.py +148 -0
  314. novacode_cli/plugins/__init__.py +121 -0
  315. novacode_cli/plugins/claude_plugins.py +538 -0
  316. novacode_cli/plugins/cli.py +168 -0
  317. novacode_cli/plugins/components.py +413 -0
  318. novacode_cli/plugins/hooks.py +356 -0
  319. novacode_cli/plugins/loader.py +317 -0
  320. novacode_cli/plugins/marketplaces.py +199 -0
  321. novacode_cli/plugins/storage.py +70 -0
  322. novacode_cli/prime.py +759 -0
  323. novacode_cli/prime_factory/__init__.py +15 -0
  324. novacode_cli/prime_factory/container.py +198 -0
  325. novacode_cli/prime_factory/controller.py +565 -0
  326. novacode_cli/prime_factory/knowledge.py +261 -0
  327. novacode_cli/prime_factory/publication.py +218 -0
  328. novacode_cli/prime_factory/recovery.py +50 -0
  329. novacode_cli/prime_factory/research.py +370 -0
  330. novacode_cli/prime_factory/runner.py +608 -0
  331. novacode_cli/prime_factory/runtime.py +261 -0
  332. novacode_cli/prime_factory/state.py +201 -0
  333. novacode_cli/prime_factory/store.py +128 -0
  334. novacode_cli/prime_factory/worker.py +129 -0
  335. novacode_cli/prime_factory/workflow.py +899 -0
  336. novacode_cli/prime_factory/workspace.py +317 -0
  337. novacode_cli/prime_memory.py +290 -0
  338. novacode_cli/process_manager.py +585 -0
  339. novacode_cli/project_onboard.py +537 -0
  340. novacode_cli/prompts/System_Prompt_Nova.jinja +23 -0
  341. novacode_cli/prompts/__init__.py +168 -0
  342. novacode_cli/prompts/agent_definitions.jinja +11 -0
  343. novacode_cli/prompts/agent_generation.jinja +125 -0
  344. novacode_cli/prompts/async_agents/general_purpose.jinja +22 -0
  345. novacode_cli/prompts/claude_plan_mode.jinja +12 -0
  346. novacode_cli/prompts/coding_workflow.jinja +18 -0
  347. novacode_cli/prompts/computer_use.jinja +17 -0
  348. novacode_cli/prompts/core_agent_system.jinja +81 -0
  349. novacode_cli/prompts/delegation.jinja +15 -0
  350. novacode_cli/prompts/desktop_power.jinja +4 -0
  351. novacode_cli/prompts/file_paths.jinja +15 -0
  352. novacode_cli/prompts/file_tracker.jinja +23 -0
  353. novacode_cli/prompts/init_agents_md.jinja +56 -0
  354. novacode_cli/prompts/init_exploration.jinja +174 -0
  355. novacode_cli/prompts/init_semantic_orchestrate.jinja +37 -0
  356. novacode_cli/prompts/instruction_scope.jinja +5 -0
  357. novacode_cli/prompts/learnings_extraction.jinja +45 -0
  358. novacode_cli/prompts/longterm_memory.jinja +54 -0
  359. novacode_cli/prompts/mcp.jinja +84 -0
  360. novacode_cli/prompts/memory_consolidation.jinja +40 -0
  361. novacode_cli/prompts/nova_evolution.jinja +73 -0
  362. novacode_cli/prompts/nova_review.jinja +158 -0
  363. novacode_cli/prompts/nova_skill_debate.jinja +36 -0
  364. novacode_cli/prompts/nova_standing_instructions.jinja +26 -0
  365. novacode_cli/prompts/nova_verify.jinja +100 -0
  366. novacode_cli/prompts/onboard_dashboard.jinja +378 -0
  367. novacode_cli/prompts/plan_agent.jinja +87 -0
  368. novacode_cli/prompts/plan_scouts.jinja +5 -0
  369. novacode_cli/prompts/plan_storage.jinja +8 -0
  370. novacode_cli/prompts/planning.jinja +82 -0
  371. novacode_cli/prompts/ponytail/LICENSE +21 -0
  372. novacode_cli/prompts/ponytail/UPSTREAM.json +15 -0
  373. novacode_cli/prompts/ponytail/hooks/ponytail-config.js +175 -0
  374. novacode_cli/prompts/ponytail/hooks/ponytail-instructions.js +98 -0
  375. novacode_cli/prompts/ponytail/skills/ponytail/SKILL.md +52 -0
  376. novacode_cli/prompts/ponytail/skills/ponytail-audit/SKILL.md +103 -0
  377. novacode_cli/prompts/ponytail/skills/ponytail-debt/SKILL.md +44 -0
  378. novacode_cli/prompts/ponytail/skills/ponytail-gain/SKILL.md +52 -0
  379. novacode_cli/prompts/ponytail/skills/ponytail-help/SKILL.md +76 -0
  380. novacode_cli/prompts/ponytail/skills/ponytail-review/SKILL.md +101 -0
  381. novacode_cli/prompts/prompt_evolution.jinja +27 -0
  382. novacode_cli/prompts/ralph_iteration.jinja +75 -0
  383. novacode_cli/prompts/refine_gate.jinja +31 -0
  384. novacode_cli/prompts/refine_plan.jinja +64 -0
  385. novacode_cli/prompts/refine_review.jinja +34 -0
  386. novacode_cli/prompts/research_delivery.jinja +9 -0
  387. novacode_cli/prompts/research_swarm.jinja +130 -0
  388. novacode_cli/prompts/response_style.jinja +5 -0
  389. novacode_cli/prompts/session_summarization.jinja +86 -0
  390. novacode_cli/prompts/skill_creation.jinja +85 -0
  391. novacode_cli/prompts/subagents/fact_checker.jinja +48 -0
  392. novacode_cli/prompts/subagents/financial_analyst.jinja +50 -0
  393. novacode_cli/prompts/subagents/literature_reviewer.jinja +52 -0
  394. novacode_cli/prompts/subagents/market_analyst.jinja +48 -0
  395. novacode_cli/prompts/subagents/research_synthesizer.jinja +57 -0
  396. novacode_cli/prompts/subagents/technical_researcher.jinja +56 -0
  397. novacode_cli/prompts/subagents/web_researcher.jinja +46 -0
  398. novacode_cli/prompts/summarization.jinja +107 -0
  399. novacode_cli/prompts/summarization_answers.jinja +23 -0
  400. novacode_cli/prompts/summarization_questions.jinja +32 -0
  401. novacode_cli/prompts/vision_caption.jinja +21 -0
  402. novacode_cli/prompts/voice_summary.jinja +65 -0
  403. novacode_cli/prompts/wiki_file.jinja +57 -0
  404. novacode_cli/prompts/wiki_ingest.jinja +87 -0
  405. novacode_cli/recovery.py +383 -0
  406. novacode_cli/remote/__init__.py +45 -0
  407. novacode_cli/remote/approvals.py +282 -0
  408. novacode_cli/remote/async_task_clients.py +97 -0
  409. novacode_cli/remote/async_task_watcher.py +240 -0
  410. novacode_cli/remote/bridge.py +905 -0
  411. novacode_cli/remote/command_menu.py +26 -0
  412. novacode_cli/remote/command_ui.py +243 -0
  413. novacode_cli/remote/config.py +199 -0
  414. novacode_cli/remote/discord_bridge.py +530 -0
  415. novacode_cli/remote/discord_format.py +31 -0
  416. novacode_cli/remote/discord_threads.py +225 -0
  417. novacode_cli/remote/images.py +97 -0
  418. novacode_cli/remote/processor.py +601 -0
  419. novacode_cli/remote/rootapp_bot/bot.cjs +16 -0
  420. novacode_cli/remote/rootapp_bot/package-lock.json +2683 -0
  421. novacode_cli/remote/rootapp_bot/package.json +18 -0
  422. novacode_cli/remote/rootapp_bot/transport.cjs +80 -0
  423. novacode_cli/remote/rootapp_bridge.py +370 -0
  424. novacode_cli/remote/rootapp_setup.py +89 -0
  425. novacode_cli/remote/routing.py +103 -0
  426. novacode_cli/remote/scheduler.py +302 -0
  427. novacode_cli/remote/session.py +164 -0
  428. novacode_cli/remote/status.py +411 -0
  429. novacode_cli/remote/streaming.py +188 -0
  430. novacode_cli/remote/telegram_bridge.py +1118 -0
  431. novacode_cli/remote/telegram_format.py +215 -0
  432. novacode_cli/remote/telegram_hub.py +256 -0
  433. novacode_cli/remote/voice_notes.py +120 -0
  434. novacode_cli/remote/webhook_adapters.py +167 -0
  435. novacode_cli/remote/webhook_server.py +154 -0
  436. novacode_cli/rewind.py +150 -0
  437. novacode_cli/rooms/__init__.py +1 -0
  438. novacode_cli/rooms/bridge.py +86 -0
  439. novacode_cli/rooms/cli.py +231 -0
  440. novacode_cli/rooms/nova.py +227 -0
  441. novacode_cli/rooms/runtime.py +299 -0
  442. novacode_cli/rooms/store.py +538 -0
  443. novacode_cli/rooms/workspace.py +94 -0
  444. novacode_cli/security/__init__.py +29 -0
  445. novacode_cli/security/approval_context.py +45 -0
  446. novacode_cli/security/delegated_approval.py +68 -0
  447. novacode_cli/security/local_http.py +85 -0
  448. novacode_cli/security/middleware.py +87 -0
  449. novacode_cli/security/paths.py +35 -0
  450. novacode_cli/security/policy.py +535 -0
  451. novacode_cli/security/policy_writer.py +90 -0
  452. novacode_cli/security/prompt_injection.py +203 -0
  453. novacode_cli/security/remember.py +81 -0
  454. novacode_cli/security/rule_synthesis.py +116 -0
  455. novacode_cli/security/secret_files.py +61 -0
  456. novacode_cli/security/session_allow.py +114 -0
  457. novacode_cli/security/unicode_security.py +516 -0
  458. novacode_cli/security/validator.py +173 -0
  459. novacode_cli/server/__init__.py +9 -0
  460. novacode_cli/server/app.py +632 -0
  461. novacode_cli/server/event_adapter.py +189 -0
  462. novacode_cli/server/session_manager.py +90 -0
  463. novacode_cli/server_main.py +64 -0
  464. novacode_cli/server_runner/dev_server.py +530 -0
  465. novacode_cli/server_runner/test_runner.py +470 -0
  466. novacode_cli/session/__init__.py +1 -0
  467. novacode_cli/session/adapters/__init__.py +240 -0
  468. novacode_cli/session/adapters/claude_code.py +102 -0
  469. novacode_cli/session/adapters/codex.py +77 -0
  470. novacode_cli/session/adapters/harness_native.py +75 -0
  471. novacode_cli/session/checkpoint_coordinator.py +448 -0
  472. novacode_cli/session/context_restore.py +25 -0
  473. novacode_cli/session/history_export.py +228 -0
  474. novacode_cli/session/history_tree.py +174 -0
  475. novacode_cli/session/imported_context.py +267 -0
  476. novacode_cli/session/runtime_identity.py +32 -0
  477. novacode_cli/session/session_persistence.py +1168 -0
  478. novacode_cli/session/session_prompt_builder.py +279 -0
  479. novacode_cli/session/session_restore.py +351 -0
  480. novacode_cli/session/session_summarization.py +20 -0
  481. novacode_cli/session/transcript_journal.py +153 -0
  482. novacode_cli/sessions/__init__.py +11 -0
  483. novacode_cli/sessions/gateway.py +84 -0
  484. novacode_cli/sessions/launch.py +51 -0
  485. novacode_cli/sessions/lease.py +213 -0
  486. novacode_cli/sessions/messaging.py +255 -0
  487. novacode_cli/sessions/protocol.py +216 -0
  488. novacode_cli/sessions/resume.py +31 -0
  489. novacode_cli/sessions/supervisor.py +498 -0
  490. novacode_cli/sessions/tasks.py +73 -0
  491. novacode_cli/sessions/tui_messaging.py +413 -0
  492. novacode_cli/sessions/worker.py +652 -0
  493. novacode_cli/sessions/worktree.py +262 -0
  494. novacode_cli/shell/__init__.py +27 -0
  495. novacode_cli/shell/jobs.py +475 -0
  496. novacode_cli/shell/middleware.py +1568 -0
  497. novacode_cli/shell/os_sandbox.py +205 -0
  498. novacode_cli/shell/output.py +181 -0
  499. novacode_cli/shell/patterns.py +275 -0
  500. novacode_cli/shell/processes.py +87 -0
  501. novacode_cli/shell/utils.py +236 -0
  502. novacode_cli/skills/__init__.py +26 -0
  503. novacode_cli/skills/builtin/handoff/SKILL.md +15 -0
  504. novacode_cli/skills/curation_middleware.py +69 -0
  505. novacode_cli/skills/executable.py +161 -0
  506. novacode_cli/skills/libraries.py +102 -0
  507. novacode_cli/skills/load.py +248 -0
  508. novacode_cli/skills/prune.py +215 -0
  509. novacode_cli/skills/refreshing_middleware.py +896 -0
  510. novacode_cli/skills/retrieval.py +248 -0
  511. novacode_cli/skills/runtime.py +118 -0
  512. novacode_cli/skills/schema.py +254 -0
  513. novacode_cli/skills/skill_creation.py +1549 -0
  514. novacode_cli/skills/skill_lock.py +75 -0
  515. novacode_cli/skills/skill_system_prompt.py +16 -0
  516. novacode_cli/skills/skills_prefs.py +126 -0
  517. novacode_cli/skills/tool_resolver.py +31 -0
  518. novacode_cli/skills/upstream.py +50 -0
  519. novacode_cli/skills/versioning.py +291 -0
  520. novacode_cli/states/Session.py +847 -0
  521. novacode_cli/states/__init__.py +31 -0
  522. novacode_cli/states/slices/__init__.py +17 -0
  523. novacode_cli/states/slices/agent_runtime.py +249 -0
  524. novacode_cli/states/slices/background_tasks.py +21 -0
  525. novacode_cli/states/slices/notifications.py +74 -0
  526. novacode_cli/states/slices/remote_bridge.py +56 -0
  527. novacode_cli/states/slices/ui_settings.py +49 -0
  528. novacode_cli/states/slices/wiki.py +25 -0
  529. novacode_cli/teammates/__init__.py +1 -0
  530. novacode_cli/teammates/controller.py +557 -0
  531. novacode_cli/teammates/graph.py +104 -0
  532. novacode_cli/teammates/registry.py +175 -0
  533. novacode_cli/teammates/service.py +254 -0
  534. novacode_cli/teammates/worker.py +19 -0
  535. novacode_cli/teams/__init__.py +1 -0
  536. novacode_cli/teams/adapters/__init__.py +1 -0
  537. novacode_cli/teams/adapters/container.py +316 -0
  538. novacode_cli/teams/adapters/inprocess.py +206 -0
  539. novacode_cli/teams/adapters/process.py +364 -0
  540. novacode_cli/teams/bootstrap.py +246 -0
  541. novacode_cli/teams/container_git.py +67 -0
  542. novacode_cli/teams/context.py +34 -0
  543. novacode_cli/teams/coordinator.py +815 -0
  544. novacode_cli/teams/gateway.py +200 -0
  545. novacode_cli/teams/host.py +64 -0
  546. novacode_cli/teams/legacy.py +52 -0
  547. novacode_cli/teams/middleware.py +176 -0
  548. novacode_cli/teams/models.py +104 -0
  549. novacode_cli/teams/output.py +47 -0
  550. novacode_cli/teams/protocol.py +50 -0
  551. novacode_cli/teams/recovery.py +80 -0
  552. novacode_cli/teams/scheduler.py +44 -0
  553. novacode_cli/teams/store.py +631 -0
  554. novacode_cli/teams/worker.py +251 -0
  555. novacode_cli/teams/workspace.py +100 -0
  556. novacode_cli/token_utils.py +256 -0
  557. novacode_cli/tools/__init__.py +104 -0
  558. novacode_cli/tools/_shared.py +97 -0
  559. novacode_cli/tools/_types.py +24 -0
  560. novacode_cli/tools/artifact_tools.py +137 -0
  561. novacode_cli/tools/code_search_tools.py +330 -0
  562. novacode_cli/tools/computer_tools.py +77 -0
  563. novacode_cli/tools/cron_tools.py +65 -0
  564. novacode_cli/tools/daemon_tool.py +290 -0
  565. novacode_cli/tools/desktop_tools.py +161 -0
  566. novacode_cli/tools/fetch_tools.py +530 -0
  567. novacode_cli/tools/http_tools.py +279 -0
  568. novacode_cli/tools/inspection_tools.py +107 -0
  569. novacode_cli/tools/job_tools.py +143 -0
  570. novacode_cli/tools/knowledge_tools.py +427 -0
  571. novacode_cli/tools/memory_tools.py +461 -0
  572. novacode_cli/tools/memory_wiki_tools.py +40 -0
  573. novacode_cli/tools/notebook_tools.py +238 -0
  574. novacode_cli/tools/oracle_tool.py +83 -0
  575. novacode_cli/tools/plan_archive_tools.py +80 -0
  576. novacode_cli/tools/plan_mode_tools.py +315 -0
  577. novacode_cli/tools/python_kernel_loop.py +165 -0
  578. novacode_cli/tools/python_kernel_tool.py +222 -0
  579. novacode_cli/tools/reflection_tools.py +34 -0
  580. novacode_cli/tools/scraper_tools.py +377 -0
  581. novacode_cli/tools/skill_tools.py +275 -0
  582. novacode_cli/tools/speak_tool.py +51 -0
  583. novacode_cli/tools/store_memory_tools.py +227 -0
  584. novacode_cli/tools/ui_tools.py +62 -0
  585. novacode_cli/tools/waiter_tools.py +408 -0
  586. novacode_cli/tools/web_tools.py +349 -0
  587. novacode_cli/tools/wiki_tools.py +152 -0
  588. novacode_cli/tools/worktree_tools.py +219 -0
  589. novacode_cli/tracking/__init__.py +10 -0
  590. novacode_cli/tracking/file_tracker.py +826 -0
  591. novacode_cli/tracking/local_metrics.py +170 -0
  592. novacode_cli/tracking/loop_guard.py +507 -0
  593. novacode_cli/tracking/request_metrics.py +60 -0
  594. novacode_cli/tracking/tracing.py +692 -0
  595. novacode_cli/tracking/usage_tree.py +677 -0
  596. novacode_cli/tracking/workspace_anchoring.py +231 -0
  597. novacode_cli/tui/__init__.py +11 -0
  598. novacode_cli/tui/animation_rate.py +9 -0
  599. novacode_cli/tui/animations.py +332 -0
  600. novacode_cli/tui/app.py +19315 -0
  601. novacode_cli/tui/auth_display.py +87 -0
  602. novacode_cli/tui/auth_screens.py +826 -0
  603. novacode_cli/tui/avatar.py +294 -0
  604. novacode_cli/tui/avatar_frames.py +21 -0
  605. novacode_cli/tui/background_tasks.py +35 -0
  606. novacode_cli/tui/browser_settings_screen.py +70 -0
  607. novacode_cli/tui/caveman_screen.py +185 -0
  608. novacode_cli/tui/council_settings_screen.py +182 -0
  609. novacode_cli/tui/council_view.py +535 -0
  610. novacode_cli/tui/decision_settings.py +268 -0
  611. novacode_cli/tui/diff_preview.py +199 -0
  612. novacode_cli/tui/gc_tuning.py +63 -0
  613. novacode_cli/tui/handoff_screen.py +206 -0
  614. novacode_cli/tui/harness.py +244 -0
  615. novacode_cli/tui/history_tree_screen.py +179 -0
  616. novacode_cli/tui/home.py +328 -0
  617. novacode_cli/tui/home_scene.py +94 -0
  618. novacode_cli/tui/icons.py +81 -0
  619. novacode_cli/tui/interface_styles.tcss +327 -0
  620. novacode_cli/tui/matrix3.py +108 -0
  621. novacode_cli/tui/mcp_tools_screen.py +131 -0
  622. novacode_cli/tui/motion.py +178 -0
  623. novacode_cli/tui/output_body.py +136 -0
  624. novacode_cli/tui/output_buffer.py +59 -0
  625. novacode_cli/tui/palette.py +334 -0
  626. novacode_cli/tui/pickers.py +237 -0
  627. novacode_cli/tui/prime_panel.py +506 -0
  628. novacode_cli/tui/provider_signin.py +213 -0
  629. novacode_cli/tui/pty_shell.py +159 -0
  630. novacode_cli/tui/reference_screens.py +444 -0
  631. novacode_cli/tui/resume_picker.py +346 -0
  632. novacode_cli/tui/rooms.py +597 -0
  633. novacode_cli/tui/screens.py +7088 -0
  634. novacode_cli/tui/session_import.py +333 -0
  635. novacode_cli/tui/session_pane.py +242 -0
  636. novacode_cli/tui/session_tab.py +42 -0
  637. novacode_cli/tui/session_transcript.py +118 -0
  638. novacode_cli/tui/shell_output.py +7 -0
  639. novacode_cli/tui/stall_watch.py +174 -0
  640. novacode_cli/tui/statusline.py +40 -0
  641. novacode_cli/tui/statusline_screen.py +91 -0
  642. novacode_cli/tui/teammate_updates.py +162 -0
  643. novacode_cli/tui/teammates.py +548 -0
  644. novacode_cli/tui/terminal.py +289 -0
  645. novacode_cli/tui/terminal_view.py +235 -0
  646. novacode_cli/tui/themes.py +118 -0
  647. novacode_cli/tui/update_notice.py +84 -0
  648. novacode_cli/tui/voice_downloads.py +113 -0
  649. novacode_cli/tui/widgets.py +2038 -0
  650. novacode_cli/tui/windows_input.py +38 -0
  651. novacode_cli/tui/workspace_approval.py +165 -0
  652. novacode_cli/ui/council_web.py +113 -0
  653. novacode_cli/ui/council_workspace.py +72 -0
  654. novacode_cli/ui/cowork_web.py +41 -0
  655. novacode_cli/ui/diff_highlight.py +160 -0
  656. novacode_cli/ui/execution.py +1396 -0
  657. novacode_cli/ui/exit_summary.py +65 -0
  658. novacode_cli/ui/hitl_approval.py +196 -0
  659. novacode_cli/ui/input_preparation.py +2 -0
  660. novacode_cli/ui/interrupt_handlers.py +215 -0
  661. novacode_cli/ui/question_prompt.py +151 -0
  662. novacode_cli/ui/status_phrases.py +157 -0
  663. novacode_cli/ui/stream_preview.py +66 -0
  664. novacode_cli/ui/streaming.py +2 -0
  665. novacode_cli/ui/subagent_tracking.py +2 -0
  666. novacode_cli/ui/ui_elements.py +1585 -0
  667. novacode_cli/ui/web_workspace.py +244 -0
  668. novacode_cli/ui_events.py +299 -0
  669. novacode_cli/update_ui.py +243 -0
  670. novacode_cli/updates.py +489 -0
  671. novacode_cli/utils/backend_patches.py +399 -0
  672. novacode_cli/utils/backend_paths.py +467 -0
  673. novacode_cli/utils/lazy_providers.py +51 -0
  674. novacode_cli/utils/model_info.py +223 -0
  675. novacode_cli/utils/pdf_extraction.py +183 -0
  676. novacode_cli/utils/startup.py +29 -0
  677. novacode_cli/utils/tasks.py +42 -0
  678. novacode_cli/utils/windows.py +9 -0
  679. novacode_cli/vixie/hook_adapter.py +127 -0
  680. novacode_cli/vixie/server.py +375 -0
  681. novacode_cli/wiki/__init__.py +15 -0
  682. novacode_cli/wiki/ask.py +56 -0
  683. novacode_cli/wiki/file.py +71 -0
  684. novacode_cli/wiki/ingest.py +184 -0
  685. novacode_cli/wiki/manager.py +261 -0
  686. novacode_cli-1.2.0.dist-info/METADATA +271 -0
  687. novacode_cli-1.2.0.dist-info/RECORD +691 -0
  688. novacode_cli-1.2.0.dist-info/WHEEL +5 -0
  689. novacode_cli-1.2.0.dist-info/entry_points.txt +2 -0
  690. novacode_cli-1.2.0.dist-info/licenses/LICENSE +21 -0
  691. novacode_cli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,335 @@
1
+ """Skill management — create, clean up, and refine skills from review feedback.
2
+
3
+ Extracted from ``NovaLearningMiddleware``. Delegates to ``skill_discovery``
4
+ for the actual pattern-detection logic; this module owns the orchestration
5
+ decisions (when to create, guard against duplicates, rate-limit refinement).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import logging
13
+ import re
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from langgraph.store.base import BaseStore
19
+
20
+ logger = logging.getLogger("nova.hermes.skill_manager")
21
+
22
+ # Minimum wall-clock gap between curation passes (curation is piggybacked on the
23
+ # review cycle but gated to roughly daily so cadence doesn't depend on chattiness).
24
+ _CURATION_INTERVAL_SECS = 86_400.0 # 24h
25
+
26
+
27
+ # Skill proposals seen so far, by project (see SkillManager._proposed_elsewhere).
28
+ _PROPOSALS_FILE = ".skill-proposals.json"
29
+
30
+ # `<scope>general</scope>` inside a review's `<skill>` block.
31
+ _GENERAL_SKILL_RE = re.compile(
32
+ r"<skill>.*?<scope>\s*general\s*</scope>.*?</skill>", re.DOTALL | re.IGNORECASE
33
+ )
34
+
35
+
36
+ def _similar_skill(skills_dir: Path, name: str, overlap: float = 0.6) -> str | None:
37
+ """An existing skill whose name is mostly the same words as ``name``, if any.
38
+
39
+ An exact match is not reported: writing over a skill of the same name is the
40
+ existing update path and stays as it was.
41
+ """
42
+ words = {w for w in re.split(r"[-_\s]+", name.lower()) if w}
43
+ if not words or not skills_dir.is_dir():
44
+ return None
45
+ for path in skills_dir.iterdir():
46
+ if not path.is_dir() or path.name == name:
47
+ continue
48
+ other = {w for w in re.split(r"[-_\s]+", path.name.lower()) if w}
49
+ if other and len(words & other) / len(words | other) >= overlap:
50
+ return path.name
51
+ return None
52
+
53
+
54
+ class SkillManager:
55
+ """Manages skill lifecycle: creation from reviews, legacy cleanup, refinement.
56
+
57
+ Used by ``ReviewRunner`` (via the middleware) after a review cycle completes.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ store: BaseStore,
63
+ *,
64
+ skills_dir: Path | None = None,
65
+ enabled: bool = True,
66
+ project: str | None = None,
67
+ ) -> None:
68
+ self._store = store
69
+ self._project = project or ""
70
+ self._skills_dir = skills_dir
71
+ self._enabled = enabled
72
+ self._refinement_tasks: set[asyncio.Task] = set()
73
+
74
+ # -- Background task management -----------------------------------------
75
+
76
+ def spawn_task(self, coro) -> None:
77
+ """Run a skill create/refine coroutine as a tracked background task."""
78
+ task = asyncio.create_task(coro)
79
+ self._refinement_tasks.add(task)
80
+
81
+ def _done(t: asyncio.Task) -> None:
82
+ self._refinement_tasks.discard(t)
83
+ if not t.cancelled() and t.exception():
84
+ logger.error("Skill task failed: %s", t.exception())
85
+
86
+ task.add_done_callback(_done)
87
+
88
+ @property
89
+ def pending_tasks(self) -> set[asyncio.Task]:
90
+ """Return the set of tracked background tasks."""
91
+ return self._refinement_tasks
92
+
93
+ def _log_refinement(self, action: str, target: str, detail: str = "") -> None:
94
+ """Append a skill refinement event to the unified audit trail.
95
+
96
+ Best-effort: a failed append never blocks the skill mutation. The skills
97
+ dir lives at ``~/.nova/skills``, so the shared ``~/.nova`` root is its
98
+ parent.
99
+ """
100
+ if not self._skills_dir:
101
+ return
102
+ try:
103
+ from novacode_cli.hermes.refinement_log import append_refinement_event
104
+
105
+ append_refinement_event(
106
+ self._skills_dir.parent,
107
+ domain="skill",
108
+ action=action,
109
+ target=target,
110
+ detail=detail,
111
+ )
112
+ except Exception: # noqa: BLE001
113
+ logger.debug("Could not append skill refinement event", exc_info=True)
114
+
115
+ # -- Skill creation from review -----------------------------------------
116
+
117
+ async def maybe_create_from_review(self, review_text: str) -> None:
118
+ """Persist an episode-grounded skill if the review proposed one.
119
+
120
+ The review LLM writes a ``<skill>`` block (name + trigger + steps) only
121
+ when it recognizes a genuinely reusable workflow from this session.
122
+ Deduped by skill name (directory existence + store record).
123
+ """
124
+ if not self._enabled or not self._skills_dir:
125
+ return
126
+ try:
127
+ from novacode_cli.hermes.skill_discovery import (
128
+ parse_skill_spec,
129
+ write_skill_from_spec,
130
+ )
131
+
132
+ spec = parse_skill_spec(review_text)
133
+ if spec is None:
134
+ return
135
+ # Skills live in one directory shared by every project. A workflow
136
+ # that only fits a throwaway sandbox would be offered everywhere
137
+ # afterwards and useful nowhere: on Terminal-Bench the loop saved
138
+ # `build-rust-cpp-polyglot` — one task's answer — as a skill. Keep
139
+ # only what the review marked as general.
140
+ if self._project.startswith("sandbox-"):
141
+ if not _GENERAL_SKILL_RE.search(review_text):
142
+ self._log_refinement("skill", "skip", f"{spec.get('name')} (sandbox-specific)")
143
+ return
144
+ # "General" is the review's opinion, and it is generous with it
145
+ # (it called `write-arithmetic-coder-encoder` general). Wait for
146
+ # a second, different sandbox to propose the same kind of skill.
147
+ if not self._proposed_elsewhere(spec):
148
+ self._log_refinement("skill", "hold", f"{spec.get('name')} (seen in one project)")
149
+ return
150
+ # Reviews name skills freely, so one workflow came back as
151
+ # `setup-python-torch-container` and `setup-python-torch-env`.
152
+ # Exact-name dedup does not catch that; word overlap does.
153
+ twin = _similar_skill(self._skills_dir, spec.get("name") or "")
154
+ if twin:
155
+ self._log_refinement("skill", "skip", f"{spec.get('name')} (same as {twin})")
156
+ return
157
+ await write_skill_from_spec(spec, self._skills_dir, self._store)
158
+ self._log_refinement("skill", "create", spec.get("name") or "unknown")
159
+ except Exception: # noqa: BLE001
160
+ logger.exception("Failed to create skill from review")
161
+
162
+ def _proposed_elsewhere(self, spec: dict) -> bool:
163
+ """Has a different project already proposed this kind of skill?
164
+
165
+ Proposals are remembered in a small ledger beside the skills. A match is
166
+ a similar name or a description stating the same thing; the first
167
+ sighting is recorded and answers False.
168
+ """
169
+ from novacode_cli.hermes.memory_tiers import _fact_words, _same_fact
170
+
171
+ name = str(spec.get("name") or "")
172
+ described = _fact_words(str(spec.get("description") or ""))
173
+ named = {w for w in re.split(r"[-_\s]+", name.lower()) if w}
174
+ ledger_path = self._skills_dir / _PROPOSALS_FILE
175
+ try:
176
+ ledger = json.loads(ledger_path.read_text(encoding="utf-8"))
177
+ except (OSError, ValueError):
178
+ ledger = {}
179
+ for other_name, entry in ledger.items():
180
+ if entry.get("project") == self._project:
181
+ continue
182
+ other_named = {w for w in re.split(r"[-_\s]+", other_name.lower()) if w}
183
+ same_name = named and len(named & other_named) / len(named | other_named) >= 0.6
184
+ if same_name or _same_fact(described, frozenset(entry.get("words") or ())):
185
+ return True
186
+ # One entry per idea per project: a long task re-proposes the same skill
187
+ # under a new name at every review (five "raw tensor dump" variants from
188
+ # one task), and none of those is a second opinion.
189
+ for other_name, entry in ledger.items():
190
+ if entry.get("project") != self._project:
191
+ continue
192
+ other_named = {w for w in re.split(r"[-_\s]+", other_name.lower()) if w}
193
+ # Looser than the cross-project test (share of the SHORTER name):
194
+ # `inspect-raw-tensor-dump` and `reverse-engineer-raw-tensor-dump`
195
+ # from one task are plainly the same proposal.
196
+ same_name = (
197
+ named
198
+ and other_named
199
+ and len(named & other_named) / min(len(named), len(other_named)) >= 0.6
200
+ )
201
+ if same_name or _same_fact(described, frozenset(entry.get("words") or ())):
202
+ return False # already holding this one
203
+ ledger[name] = {"project": self._project, "words": sorted(described)}
204
+ try:
205
+ self._skills_dir.mkdir(parents=True, exist_ok=True)
206
+ ledger_path.write_text(json.dumps(ledger, indent=1), encoding="utf-8")
207
+ except OSError:
208
+ logger.debug("Could not record skill proposal", exc_info=True)
209
+ return False
210
+
211
+ # -- Legacy cleanup -----------------------------------------------------
212
+
213
+ async def cleanup_legacy_skills_once(self) -> None:
214
+ """One-time removal of old ``nova-<hash>`` n-gram skills (guarded)."""
215
+ if not self._enabled or not self._skills_dir:
216
+ return
217
+ try:
218
+ done = await self._store.aget(("nova", "meta"), "legacy_skills_cleaned_v1")
219
+ if done is not None:
220
+ return
221
+ from novacode_cli.hermes.skill_discovery import (
222
+ cleanup_legacy_pattern_skills,
223
+ )
224
+
225
+ removed = cleanup_legacy_pattern_skills(self._skills_dir)
226
+ await self._store.aput(
227
+ ("nova", "meta"), "legacy_skills_cleaned_v1", {"removed": removed}
228
+ )
229
+ if removed:
230
+ from novacode_cli.events import nova_event_log
231
+
232
+ nova_event_log.append(
233
+ (
234
+ "nova_skill_refinement",
235
+ "◐",
236
+ "yellow",
237
+ f"▤ Nova: removed {len(removed)} legacy auto-skill(s) "
238
+ "(opaque nova-* patterns; replaced by episode-grounded skills)",
239
+ )
240
+ )
241
+ except Exception: # noqa: BLE001
242
+ logger.exception("Failed legacy skill cleanup")
243
+
244
+ # -- Skill refinement ---------------------------------------------------
245
+
246
+ async def maybe_refine_skills(self) -> None:
247
+ """Refine one high-failure skill per cycle, grounded in its failures.
248
+
249
+ ``check_skill_effectiveness`` only surfaces ``high_failure`` candidates
250
+ (with a cooldown + attempt cap baked in), so a skill can't be re-refined
251
+ on every cycle. Chronically-unused skills are handled by the curator
252
+ (archival), not by rewriting — see ``check_skill_effectiveness``.
253
+ """
254
+ if not self._enabled or not self._skills_dir:
255
+ return
256
+ try:
257
+ from novacode_cli.hermes.skill_discovery import (
258
+ check_skill_effectiveness,
259
+ refine_skill,
260
+ )
261
+
262
+ candidates = await check_skill_effectiveness(self._store)
263
+ for skill_name, issue in candidates:
264
+ if issue != "high_failure":
265
+ continue
266
+ if not (self._skills_dir / skill_name / "SKILL.md").exists():
267
+ continue
268
+
269
+ # Pull captured failure samples so the refinement is grounded in
270
+ # what actually went wrong, not a blind regenerate.
271
+ failure_samples: list = []
272
+ try:
273
+ entry = await self._store.aget(("nova", "skill_usage"), skill_name)
274
+ if entry and isinstance(entry.value, dict):
275
+ failure_samples = list(entry.value.get("failure_samples") or [])
276
+ except Exception: # noqa: BLE001
277
+ failure_samples = []
278
+
279
+ from novacode_cli.events import nova_event_log
280
+
281
+ nova_event_log.append(
282
+ (
283
+ "nova_skill_refinement",
284
+ "◐",
285
+ "yellow",
286
+ f"◐ Nova: skill '{skill_name}' needs refinement ({issue})",
287
+ )
288
+ )
289
+ self.spawn_task(
290
+ refine_skill(
291
+ skill_name,
292
+ self._skills_dir,
293
+ issue,
294
+ failure_samples=failure_samples,
295
+ store=self._store,
296
+ )
297
+ )
298
+ self._log_refinement("refine", skill_name, f"high_failure: {issue}")
299
+ break # one refinement per cycle
300
+ except Exception: # noqa: BLE001
301
+ logger.exception("Failed to check skill effectiveness / refine")
302
+
303
+ # -- Skill curation -----------------------------------------------------
304
+
305
+ async def maybe_curate(self) -> None:
306
+ """Run a background curation pass on a real schedule (≥ once/24h).
307
+
308
+ Called opportunistically (every Nth review), but a time gate makes the
309
+ cadence wall-clock-based, not review-count-based — so curation runs about
310
+ daily regardless of how chatty a session is.
311
+ """
312
+ if not self._enabled or not self._skills_dir:
313
+ return
314
+ try:
315
+ import time
316
+
317
+ now = time.time()
318
+ last = 0.0
319
+ try:
320
+ rec = await self._store.aget(("nova", "meta"), "last_curation_ts")
321
+ if rec and isinstance(rec.value, dict):
322
+ last = float(rec.value.get("ts", 0.0))
323
+ except Exception: # noqa: BLE001
324
+ last = 0.0
325
+ if now - last < _CURATION_INTERVAL_SECS:
326
+ return
327
+
328
+ # Stamp before spawning so a slow pass can't trigger a duplicate.
329
+ await self._store.aput(("nova", "meta"), "last_curation_ts", {"ts": now})
330
+
331
+ from novacode_cli.hermes.curator import run_curation
332
+
333
+ self.spawn_task(run_curation(self._store, self._skills_dir))
334
+ except Exception: # noqa: BLE001
335
+ logger.exception("Failed to start skill curation")
@@ -0,0 +1,43 @@
1
+ """Stage autonomous skill changes without changing the active library."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from novacode_cli.hermes.activation import content_hash
8
+ from novacode_cli.hermes.ledger import auto_approve_enabled, main_agent_learning_ledger
9
+
10
+
11
+ def propose_skill(
12
+ name: str,
13
+ content: str,
14
+ *,
15
+ source: Path | None = None,
16
+ archive: bool = False,
17
+ origin: str = "skill_manage",
18
+ evidence: tuple[str, ...] = (),
19
+ ) -> str:
20
+ """Return a revision reference for a create, update, or recoverable archive."""
21
+ from novacode_cli.config.config import Settings
22
+ from novacode_cli.hermes.memory_tiers import project_memory_key
23
+ from novacode_cli.skills.schema import prepare_skill_content
24
+
25
+ if not archive:
26
+ content = prepare_skill_content(content, name)
27
+ settings = Settings.from_environment()
28
+ user_root = settings.ensure_user_skills_dir().resolve()
29
+ scope = "shared" if source is None or source.resolve().parent.parent == user_root else "project"
30
+ source_hash = content_hash(source.read_bytes()) if source else ""
31
+ record = main_agent_learning_ledger().propose(
32
+ content=content,
33
+ project=project_memory_key(Path.cwd()),
34
+ scope=scope,
35
+ kind="skill_archive" if archive else "skill",
36
+ target=name,
37
+ source_hash=source_hash,
38
+ source_path=str(source.resolve()) if source else "",
39
+ origin=origin,
40
+ evidence=evidence,
41
+ auto_approve=auto_approve_enabled(),
42
+ )
43
+ return record.reference
@@ -0,0 +1,119 @@
1
+ """Deterministic test-gaming detector — Loop-Engineering "Verification that Learns".
2
+
3
+ The LLM rubric grader (``nova_verify.jinja``) is the final judge of whether an
4
+ agent weakened its tests to make them pass. This module gives that grader
5
+ *concrete signals* by scanning the agent's diffs for edits to test files that
6
+ are classic reward-hacking patterns:
7
+
8
+ - removed or weakened ``assert`` statements,
9
+ - added ``@pytest.mark.skip`` / ``skipif`` / ``xfail``,
10
+ - changed expected values to match the (possibly broken) output,
11
+ - deleted failing test cases,
12
+ - added trivial always-green tests.
13
+
14
+ The detector is deliberately *conservative*: it only flags candidate lines and
15
+ lets the LLM make the call. It never fails a turn on its own.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import re
21
+ from dataclasses import dataclass, field
22
+
23
+ #: Paths that look like test files.
24
+ _TEST_FILE_RE = re.compile(r"(^|[/\\])(test_|tests?[/\\]|.*_test\.)", re.IGNORECASE)
25
+
26
+ #: A diff line that *removes* an assertion (leading ``-``).
27
+ _REMOVED_ASSERT_RE = re.compile(r"^-\s*(assert|self\.assert|expect\(|\.to(Be|Equal|Throw))")
28
+ #: A diff line that *adds* a skip/xfail marker.
29
+ _ADDED_SKIP_RE = re.compile(
30
+ r"^\+.*@(pytest\.mark\.(skip|skipif|xfail)|unittest\.skip|@Skip|@Disabled)"
31
+ )
32
+ #: A diff line that *removes* an assertion carrying an expected value — the
33
+ #: "old expected value deleted" signal (e.g. ``- assert add(2,2) == 4``). We
34
+ #: only match removed (``-``) lines: *adding* an assertion is legitimate.
35
+ _CHANGED_EXPECTED_RE = re.compile(
36
+ r"^-\s*(assert.*(==|!=|in\b|not in\b)|assertEqual|assert.*expected|"
37
+ r"expect\(.*\.to(Be|Equal|Throw)|toEqual|toBe)",
38
+ re.IGNORECASE,
39
+ )
40
+ #: A diff line that removes a test function entirely.
41
+ _REMOVED_TEST_DEF_RE = re.compile(r"^-\s*(def test_|async def test_|@pytest\.mark\.parametrize)")
42
+
43
+
44
+ @dataclass
45
+ class TestGamingFinding:
46
+ """One candidate reward-hacking signal found in a diff."""
47
+
48
+ path: str
49
+ kind: str
50
+ line: str
51
+
52
+
53
+ @dataclass
54
+ class TestGamingReport:
55
+ """Aggregated candidate signals across all changed files."""
56
+
57
+ findings: list[TestGamingFinding] = field(default_factory=list)
58
+
59
+ @property
60
+ def has_findings(self) -> bool:
61
+ """Whether any candidate test-weakening edits were found."""
62
+ return bool(self.findings)
63
+
64
+ def summarize(self, max_lines: int = 12) -> str:
65
+ """Render the findings as short evidence lines for the rubric prompt."""
66
+ if not self.findings:
67
+ return ""
68
+ lines = [f"Detected {len(self.findings)} candidate test-weakening edit(s):"]
69
+ lines.extend(
70
+ f"- {f.path}: {f.kind}: {f.line.strip()[:120]}" for f in self.findings[:max_lines]
71
+ )
72
+ if len(self.findings) > max_lines:
73
+ lines.append(f"... and {len(self.findings) - max_lines} more")
74
+ return "\n".join(lines)
75
+
76
+
77
+ def _is_test_file(path: str) -> bool:
78
+ return bool(_TEST_FILE_RE.search(path))
79
+
80
+
81
+ def detect_test_gaming(diffs: list[str]) -> TestGamingReport:
82
+ """Scan unified diffs for candidate test-weakening edits.
83
+
84
+ Args:
85
+ diffs: Unified diff strings (as produced by
86
+ :func:`novacode_cli.file_ops.compute_unified_diff`).
87
+
88
+ Returns:
89
+ A :class:`TestGamingReport` of candidate findings. Empty when no test
90
+ files were changed or no suspicious edits were found.
91
+ """
92
+ report = TestGamingReport()
93
+ for diff in diffs or []:
94
+ path = _diff_path(diff)
95
+ if not path or not _is_test_file(path):
96
+ continue
97
+ for line in diff.splitlines():
98
+ if _REMOVED_ASSERT_RE.match(line):
99
+ report.findings.append(
100
+ TestGamingFinding(path=path, kind="removed-assert", line=line)
101
+ )
102
+ elif _ADDED_SKIP_RE.match(line):
103
+ report.findings.append(TestGamingFinding(path=path, kind="added-skip", line=line))
104
+ elif _REMOVED_TEST_DEF_RE.match(line):
105
+ report.findings.append(TestGamingFinding(path=path, kind="removed-test", line=line))
106
+ elif _CHANGED_EXPECTED_RE.match(line):
107
+ report.findings.append(
108
+ TestGamingFinding(path=path, kind="changed-expected", line=line)
109
+ )
110
+ return report
111
+
112
+
113
+ def _diff_path(diff: str) -> str | None:
114
+ """Extract the file path from a unified diff header (``+++ b/path``)."""
115
+ for line in diff.splitlines():
116
+ if line.startswith("+++ "):
117
+ # Strip the "a/"/"b/" prefix and any "(before)"/"(after)" suffix.
118
+ return line[4:].split(" (")[0].lstrip("ab/")
119
+ return None