@litfamily/lithermes 1.0.0

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 (557) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/README_Ko-KR.md +188 -0
  4. package/assets/lithermes-plugin/NOTICE.md +46 -0
  5. package/assets/lithermes-plugin/README.md +390 -0
  6. package/assets/lithermes-plugin/__init__.py +589 -0
  7. package/assets/lithermes-plugin/auto_update.py +338 -0
  8. package/assets/lithermes-plugin/bounded_work.py +1120 -0
  9. package/assets/lithermes-plugin/bounded_work_tools.py +115 -0
  10. package/assets/lithermes-plugin/core.py +1813 -0
  11. package/assets/lithermes-plugin/core_commands.py +243 -0
  12. package/assets/lithermes-plugin/core_contexts.py +458 -0
  13. package/assets/lithermes-plugin/core_contract.py +456 -0
  14. package/assets/lithermes-plugin/core_plans.py +418 -0
  15. package/assets/lithermes-plugin/core_reader_args.py +34 -0
  16. package/assets/lithermes-plugin/core_recap_command.py +141 -0
  17. package/assets/lithermes-plugin/core_recap_state.py +175 -0
  18. package/assets/lithermes-plugin/core_review.py +198 -0
  19. package/assets/lithermes-plugin/core_routing.py +641 -0
  20. package/assets/lithermes-plugin/core_runs.py +283 -0
  21. package/assets/lithermes-plugin/core_runtime.py +251 -0
  22. package/assets/lithermes-plugin/core_workflow_commands.py +207 -0
  23. package/assets/lithermes-plugin/deliverable_hedge_guard.py +562 -0
  24. package/assets/lithermes-plugin/diagnostics.py +468 -0
  25. package/assets/lithermes-plugin/handoff.py +97 -0
  26. package/assets/lithermes-plugin/knowledge.py +920 -0
  27. package/assets/lithermes-plugin/knowledge_tools.py +130 -0
  28. package/assets/lithermes-plugin/lit_mark.py +140 -0
  29. package/assets/lithermes-plugin/lit_mark_rows.json +1363 -0
  30. package/assets/lithermes-plugin/litgoal/__init__.py +10 -0
  31. package/assets/lithermes-plugin/litgoal/cli.py +207 -0
  32. package/assets/lithermes-plugin/litgoal/hook.py +81 -0
  33. package/assets/lithermes-plugin/litgoal/model.py +203 -0
  34. package/assets/lithermes-plugin/litgoal/runtime.py +522 -0
  35. package/assets/lithermes-plugin/litgoal/store.py +367 -0
  36. package/assets/lithermes-plugin/litgoal/tools.py +300 -0
  37. package/assets/lithermes-plugin/output-styles/asd-ste100-ko.md +41 -0
  38. package/assets/lithermes-plugin/output-styles/asd-ste100.md +40 -0
  39. package/assets/lithermes-plugin/output-styles/eli5-ko.md +13 -0
  40. package/assets/lithermes-plugin/output-styles/eli5.md +11 -0
  41. package/assets/lithermes-plugin/output_styles.py +62 -0
  42. package/assets/lithermes-plugin/payload-version.json +2107 -0
  43. package/assets/lithermes-plugin/plugin.yaml +16 -0
  44. package/assets/lithermes-plugin/provider_metrics.py +161 -0
  45. package/assets/lithermes-plugin/redaction.py +148 -0
  46. package/assets/lithermes-plugin/rules/__init__.py +55 -0
  47. package/assets/lithermes-plugin/rules/bundled-rules/baseline-discipline.md +20 -0
  48. package/assets/lithermes-plugin/rules/bundled-rules/build-decision-gate.md +19 -0
  49. package/assets/lithermes-plugin/rules/bundled-rules/windows-git-bash.md +17 -0
  50. package/assets/lithermes-plugin/rules/constants.py +122 -0
  51. package/assets/lithermes-plugin/rules/discovery.py +276 -0
  52. package/assets/lithermes-plugin/rules/engine.py +447 -0
  53. package/assets/lithermes-plugin/rules/frontmatter.py +149 -0
  54. package/assets/lithermes-plugin/rules/globmatch.py +333 -0
  55. package/assets/lithermes-plugin/scientific_visualization.py +158 -0
  56. package/assets/lithermes-plugin/session_context.py +8 -0
  57. package/assets/lithermes-plugin/skill_loop.py +2044 -0
  58. package/assets/lithermes-plugin/skill_observer.py +1778 -0
  59. package/assets/lithermes-plugin/skills/autoconference/LICENSE +21 -0
  60. package/assets/lithermes-plugin/skills/autoconference/ORIGIN.json +11 -0
  61. package/assets/lithermes-plugin/skills/autoconference/SKILL.md +163 -0
  62. package/assets/lithermes-plugin/skills/autoconference/assets/conference_template.md +76 -0
  63. package/assets/lithermes-plugin/skills/autoconference/assets/report_template.md +53 -0
  64. package/assets/lithermes-plugin/skills/autoconference/assets/synthesis_template.md +39 -0
  65. package/assets/lithermes-plugin/skills/autoconference/dependencies/autoresearch.md +14 -0
  66. package/assets/lithermes-plugin/skills/autoconference/modes/analyze/SKILL.md +269 -0
  67. package/assets/lithermes-plugin/skills/autoconference/modes/core/SKILL.md +134 -0
  68. package/assets/lithermes-plugin/skills/autoconference/modes/core/convergence-guide.md +162 -0
  69. package/assets/lithermes-plugin/skills/autoconference/modes/core/crash-recovery.md +63 -0
  70. package/assets/lithermes-plugin/skills/autoconference/modes/debate/SKILL.md +383 -0
  71. package/assets/lithermes-plugin/skills/autoconference/modes/plan/SKILL.md +304 -0
  72. package/assets/lithermes-plugin/skills/autoconference/modes/resume/SKILL.md +82 -0
  73. package/assets/lithermes-plugin/skills/autoconference/modes/ship/SKILL.md +78 -0
  74. package/assets/lithermes-plugin/skills/autoconference/modes/survey/SKILL.md +376 -0
  75. package/assets/lithermes-plugin/skills/autoconference/references/agent-prompts.md +110 -0
  76. package/assets/lithermes-plugin/skills/autoconference/references/conference-protocol.md +90 -0
  77. package/assets/lithermes-plugin/skills/autoconference/references/core-principles.md +78 -0
  78. package/assets/lithermes-plugin/skills/autoconference/references/results-logging.md +165 -0
  79. package/assets/lithermes-plugin/skills/autoconference/references/visualization-guide.md +99 -0
  80. package/assets/lithermes-plugin/skills/autoconference/scripts/check_conference.sh +197 -0
  81. package/assets/lithermes-plugin/skills/autoconference/scripts/init_conference.py +489 -0
  82. package/assets/lithermes-plugin/skills/autoconference/scripts/style_presets.py +104 -0
  83. package/assets/lithermes-plugin/skills/autoconference/templates/code-performance.md +59 -0
  84. package/assets/lithermes-plugin/skills/autoconference/templates/debate-mode.md +50 -0
  85. package/assets/lithermes-plugin/skills/autoconference/templates/prompt-optimization.md +58 -0
  86. package/assets/lithermes-plugin/skills/autoconference/templates/quick-conference.md +48 -0
  87. package/assets/lithermes-plugin/skills/autoconference/templates/research-synthesis.md +56 -0
  88. package/assets/lithermes-plugin/skills/autoconference/templates/survey-mode.md +63 -0
  89. package/assets/lithermes-plugin/skills/autoresearch/LICENSE +21 -0
  90. package/assets/lithermes-plugin/skills/autoresearch/ORIGIN.json +11 -0
  91. package/assets/lithermes-plugin/skills/autoresearch/SKILL.md +175 -0
  92. package/assets/lithermes-plugin/skills/autoresearch/assets/report_template.md +50 -0
  93. package/assets/lithermes-plugin/skills/autoresearch/assets/research_template.md +38 -0
  94. package/assets/lithermes-plugin/skills/autoresearch/assets/results_template.tsv +2 -0
  95. package/assets/lithermes-plugin/skills/autoresearch/modes/core/SKILL.md +204 -0
  96. package/assets/lithermes-plugin/skills/autoresearch/modes/core/evaluator-contract.md +108 -0
  97. package/assets/lithermes-plugin/skills/autoresearch/modes/core/stuck-detection.md +102 -0
  98. package/assets/lithermes-plugin/skills/autoresearch/modes/debug/SKILL.md +240 -0
  99. package/assets/lithermes-plugin/skills/autoresearch/modes/debug/investigation-techniques.md +254 -0
  100. package/assets/lithermes-plugin/skills/autoresearch/modes/fix/SKILL.md +176 -0
  101. package/assets/lithermes-plugin/skills/autoresearch/modes/learn/SKILL.md +96 -0
  102. package/assets/lithermes-plugin/skills/autoresearch/modes/plan/SKILL.md +291 -0
  103. package/assets/lithermes-plugin/skills/autoresearch/modes/predict/SKILL.md +271 -0
  104. package/assets/lithermes-plugin/skills/autoresearch/modes/predict/persona-templates.md +227 -0
  105. package/assets/lithermes-plugin/skills/autoresearch/modes/reason/SKILL.md +174 -0
  106. package/assets/lithermes-plugin/skills/autoresearch/modes/scenario/SKILL.md +168 -0
  107. package/assets/lithermes-plugin/skills/autoresearch/modes/scenario/dimensions.md +173 -0
  108. package/assets/lithermes-plugin/skills/autoresearch/modes/security/SKILL.md +287 -0
  109. package/assets/lithermes-plugin/skills/autoresearch/modes/security/owasp-checklist.md +156 -0
  110. package/assets/lithermes-plugin/skills/autoresearch/modes/security/stride-model.md +191 -0
  111. package/assets/lithermes-plugin/skills/autoresearch/modes/ship/SKILL.md +99 -0
  112. package/assets/lithermes-plugin/skills/autoresearch/modes/ship/type-checklists.md +158 -0
  113. package/assets/lithermes-plugin/skills/autoresearch/references/core-principles.md +75 -0
  114. package/assets/lithermes-plugin/skills/autoresearch/references/results-logging.md +55 -0
  115. package/assets/lithermes-plugin/skills/autoresearch/references/visualization-guide.md +90 -0
  116. package/assets/lithermes-plugin/skills/autoresearch/scripts/check_progress.sh +139 -0
  117. package/assets/lithermes-plugin/skills/autoresearch/scripts/init_research.py +219 -0
  118. package/assets/lithermes-plugin/skills/autoresearch/scripts/style_presets.py +104 -0
  119. package/assets/lithermes-plugin/skills/browser-drive/SKILL.md +147 -0
  120. package/assets/lithermes-plugin/skills/browser-drive/scripts/capability_probe.py +528 -0
  121. package/assets/lithermes-plugin/skills/comment-checker/SKILL.md +162 -0
  122. package/assets/lithermes-plugin/skills/debugging/SKILL.md +271 -0
  123. package/assets/lithermes-plugin/skills/debugging/references/methodology/00-setup.md +108 -0
  124. package/assets/lithermes-plugin/skills/debugging/references/methodology/02-investigate.md +122 -0
  125. package/assets/lithermes-plugin/skills/debugging/references/methodology/04-oracle-triple.md +136 -0
  126. package/assets/lithermes-plugin/skills/debugging/references/methodology/05-escalate.md +69 -0
  127. package/assets/lithermes-plugin/skills/debugging/references/methodology/06-fix.md +116 -0
  128. package/assets/lithermes-plugin/skills/debugging/references/methodology/08-qa.md +94 -0
  129. package/assets/lithermes-plugin/skills/debugging/references/methodology/09-cleanup.md +164 -0
  130. package/assets/lithermes-plugin/skills/debugging/references/methodology/partial-runtime-evidence.md +229 -0
  131. package/assets/lithermes-plugin/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  132. package/assets/lithermes-plugin/skills/debugging/references/runtimes/go.md +252 -0
  133. package/assets/lithermes-plugin/skills/debugging/references/runtimes/native-binary.md +484 -0
  134. package/assets/lithermes-plugin/skills/debugging/references/runtimes/node.md +260 -0
  135. package/assets/lithermes-plugin/skills/debugging/references/runtimes/python.md +248 -0
  136. package/assets/lithermes-plugin/skills/debugging/references/runtimes/rust.md +234 -0
  137. package/assets/lithermes-plugin/skills/debugging/references/tools/ghidra.md +212 -0
  138. package/assets/lithermes-plugin/skills/debugging/references/tools/playwright-cli.md +194 -0
  139. package/assets/lithermes-plugin/skills/debugging/references/tools/pwndbg.md +263 -0
  140. package/assets/lithermes-plugin/skills/debugging/references/tools/pwntools.md +265 -0
  141. package/assets/lithermes-plugin/skills/deep-interview/SKILL.md +419 -0
  142. package/assets/lithermes-plugin/skills/frontend-ui-ux/SKILL.md +49 -0
  143. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/.gitattributes +2 -0
  144. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/README.md +248 -0
  145. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/_INDEX.md +191 -0
  146. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/airbnb.md +393 -0
  147. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/airtable.md +92 -0
  148. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/apple.md +250 -0
  149. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/aside.md +209 -0
  150. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/binance.md +348 -0
  151. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/bmw.md +183 -0
  152. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/brutalist-skill.md +92 -0
  153. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/bugatti.md +271 -0
  154. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/cal.md +262 -0
  155. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/claude.md +315 -0
  156. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/clay.md +307 -0
  157. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/clickhouse.md +284 -0
  158. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/clone-from-url.md +65 -0
  159. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/cohere.md +269 -0
  160. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/coinbase.md +132 -0
  161. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/composio.md +310 -0
  162. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/cursor.md +312 -0
  163. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/design-system-architecture.md +244 -0
  164. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/elevenlabs.md +268 -0
  165. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/expo.md +284 -0
  166. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/ferrari.md +317 -0
  167. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/figma.md +223 -0
  168. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/framer.md +249 -0
  169. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/gpt-tasteskill.md +74 -0
  170. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/hashicorp.md +281 -0
  171. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/ibm.md +335 -0
  172. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/image-to-code-skill.md +1228 -0
  173. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/imagegen-brandkit.md +798 -0
  174. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/imagegen-frontend-mobile.md +1465 -0
  175. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/imagegen-frontend-web.md +987 -0
  176. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/intercom.md +149 -0
  177. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/kraken.md +128 -0
  178. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/lamborghini.md +291 -0
  179. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/layout-skill.md +107 -0
  180. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/lazyweb.md +77 -0
  181. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/linear.app.md +370 -0
  182. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/lovable.md +301 -0
  183. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/mastercard.md +368 -0
  184. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/meta.md +369 -0
  185. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/minimalist-skill.md +85 -0
  186. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/minimax.md +260 -0
  187. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/mintlify.md +329 -0
  188. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/miro.md +111 -0
  189. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/mistral.ai.md +264 -0
  190. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/mongodb.md +269 -0
  191. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/nike.md +366 -0
  192. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/notion.md +312 -0
  193. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/nvidia.md +296 -0
  194. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/ollama.md +270 -0
  195. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/opencode.ai.md +284 -0
  196. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/output-skill.md +49 -0
  197. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/pinterest.md +233 -0
  198. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/playstation.md +367 -0
  199. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/posthog.md +259 -0
  200. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/raycast.md +271 -0
  201. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/react-dev-tooling-skill.md +230 -0
  202. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/redesign-skill.md +178 -0
  203. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/renault.md +314 -0
  204. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/replicate.md +264 -0
  205. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/resend.md +306 -0
  206. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/revolut.md +188 -0
  207. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/runwayml.md +247 -0
  208. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/sanity.md +360 -0
  209. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/sentry.md +265 -0
  210. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/shopify.md +353 -0
  211. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/soft-skill.md +98 -0
  212. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/spacex.md +197 -0
  213. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/spotify.md +249 -0
  214. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/starbucks.md +583 -0
  215. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/stitch-design-example.md +121 -0
  216. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/stitch-skill.md +184 -0
  217. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/stripe.md +325 -0
  218. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/supabase.md +258 -0
  219. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/superhuman.md +255 -0
  220. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/taste-skill.md +1206 -0
  221. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/tesla.md +289 -0
  222. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/theverge.md +342 -0
  223. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/together.ai.md +266 -0
  224. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/uber.md +298 -0
  225. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/vercel.md +313 -0
  226. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/vodafone.md +426 -0
  227. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/voltagent.md +326 -0
  228. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/warp.md +256 -0
  229. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/webflow.md +95 -0
  230. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/wired.md +281 -0
  231. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/wise.md +176 -0
  232. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/x.ai.md +260 -0
  233. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/design/zapier.md +331 -0
  234. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/EVIDENCE.md +97 -0
  235. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/README.md +48 -0
  236. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/UPSTREAM.md +80 -0
  237. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/lane-a-direction.md +64 -0
  238. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/lane-b-execution.md +65 -0
  239. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/lane-c-review.md +65 -0
  240. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/lane-d-memory.md +83 -0
  241. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/orchestration.md +80 -0
  242. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/routing.md +79 -0
  243. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/LICENSE +21 -0
  244. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/accessibility-reviewer.md +83 -0
  245. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/content-writer.md +132 -0
  246. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/design-builder.md +109 -0
  247. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/design-critic.md +89 -0
  248. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/design-lead.md +113 -0
  249. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/design-scout.md +78 -0
  250. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/design-strategist.md +121 -0
  251. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/heuristic-evaluator.md +268 -0
  252. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/inspiration-scout.md +107 -0
  253. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/agents/motion-designer.md +120 -0
  254. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/accessible-content/reference.md +101 -0
  255. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/adaptive-interfaces/reference.md +109 -0
  256. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/cognitive-accessibility/reference.md +107 -0
  257. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-debate/reference.md +199 -0
  258. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-debt-tracker/reference.md +174 -0
  259. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-handoff/reference.md +125 -0
  260. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-md/reference.md +106 -0
  261. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-retrospective/reference.md +266 -0
  262. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-review/reference.md +123 -0
  263. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/design-system-alignment/reference.md +120 -0
  264. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/designpowers-critique/reference.md +164 -0
  265. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/heuristic-evaluation/reference.md +85 -0
  266. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/inclusive-personas/reference.md +98 -0
  267. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/inspiration-scouting/reference.md +165 -0
  268. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/interaction-design/reference.md +122 -0
  269. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/motion-choreography/reference.md +81 -0
  270. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/research-planning/reference.md +96 -0
  271. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/responsive-patterns/reference.md +77 -0
  272. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/synthetic-user-testing/reference.md +192 -0
  273. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/taste-feedback/reference.md +165 -0
  274. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/taste-report/reference.md +78 -0
  275. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/token-architecture/reference.md +75 -0
  276. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/ui-composition/reference.md +117 -0
  277. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/usability-testing/reference.md +78 -0
  278. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/verification-before-shipping/reference.md +125 -0
  279. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/voice-and-tone/reference.md +79 -0
  280. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/designpowers/vendor/skills/writing-design-plans/reference.md +119 -0
  281. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/perfection/README.md +160 -0
  282. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/perfection/react-perf-tooling.md +127 -0
  283. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/README.md +659 -0
  284. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/charts.csv +26 -0
  285. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/colors.csv +162 -0
  286. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/icons.csv +106 -0
  287. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/landing.csv +35 -0
  288. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/products.csv +162 -0
  289. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/react-performance.csv +45 -0
  290. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/astro.csv +54 -0
  291. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/flutter.csv +53 -0
  292. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/html-tailwind.csv +56 -0
  293. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/jetpack-compose.csv +53 -0
  294. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/nextjs.csv +53 -0
  295. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/nuxt-ui.csv +51 -0
  296. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/nuxtjs.csv +59 -0
  297. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/react-native.csv +52 -0
  298. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/react.csv +54 -0
  299. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/shadcn.csv +61 -0
  300. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/svelte.csv +54 -0
  301. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/swiftui.csv +51 -0
  302. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/stacks/vue.csv +50 -0
  303. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/styles.csv +85 -0
  304. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/typography.csv +74 -0
  305. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/ui-reasoning.csv +162 -0
  306. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/ux-guidelines.csv +100 -0
  307. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/data/web-interface.csv +31 -0
  308. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/scripts/core.py +262 -0
  309. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/scripts/design_system.py +1148 -0
  310. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/corpus/ui-ux-db/scripts/search.py +114 -0
  311. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/legal/ATTRIBUTION.md +217 -0
  312. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/legal/LICENSE +21 -0
  313. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/legal/LICENSE-Apache-2.0.txt +201 -0
  314. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/_canonical-corpus/manifest.json +876 -0
  315. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/adaptive-layout.md +81 -0
  316. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/brand-and-imagery.md +84 -0
  317. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +457 -0
  318. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/composition.md +89 -0
  319. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/creative-directions.md +81 -0
  320. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/evidence-review.md +113 -0
  321. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/implementation-platforms.md +92 -0
  322. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/inclusive-interface.md +85 -0
  323. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/interaction-motion.md +87 -0
  324. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/operating-lanes.md +85 -0
  325. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/performance-delivery.md +85 -0
  326. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/product-direction.md +86 -0
  327. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/redesign-playbook.md +91 -0
  328. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/system-foundations.md +82 -0
  329. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/taste-direction.md +86 -0
  330. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/visual-language.md +85 -0
  331. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/visual-reconstruction.md +94 -0
  332. package/assets/lithermes-plugin/skills/frontend-ui-ux/resources/LICENSE +21 -0
  333. package/assets/lithermes-plugin/skills/frontend-ui-ux/resources/PROVENANCE.json +38 -0
  334. package/assets/lithermes-plugin/skills/frontend-ui-ux/resources/THIRD-PARTY-NOTICE.txt +14 -0
  335. package/assets/lithermes-plugin/skills/frontend-ui-ux/resources/design-intelligence.json +1 -0
  336. package/assets/lithermes-plugin/skills/frontend-ui-ux/resources/import-manifest.json +1060 -0
  337. package/assets/lithermes-plugin/skills/frontend-ui-ux/schemas/design-contract-v1alpha1.schema.json +264 -0
  338. package/assets/lithermes-plugin/skills/frontend-ui-ux/schemas/design-contract-v1beta1.schema.json +268 -0
  339. package/assets/lithermes-plugin/skills/frontend-ui-ux/schemas/design-contract-v1beta2.schema.json +280 -0
  340. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/design_contract_validation.py +996 -0
  341. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/design_intelligence.py +128 -0
  342. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/design_runtime.py +98 -0
  343. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/import_design_intelligence.py +56 -0
  344. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/source_import.py +144 -0
  345. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/source_manifest.py +65 -0
  346. package/assets/lithermes-plugin/skills/lit-burnoff/SKILL.md +448 -0
  347. package/assets/lithermes-plugin/skills/lit-burnoff-file/SKILL.md +254 -0
  348. package/assets/lithermes-plugin/skills/lit-code/SKILL.md +609 -0
  349. package/assets/lithermes-plugin/skills/lit-code/references/go/README.md +90 -0
  350. package/assets/lithermes-plugin/skills/lit-code/references/go/backend-stack.md +641 -0
  351. package/assets/lithermes-plugin/skills/lit-code/references/go/bootstrap.md +328 -0
  352. package/assets/lithermes-plugin/skills/lit-code/references/go/bubbletea-v2.md +359 -0
  353. package/assets/lithermes-plugin/skills/lit-code/references/go/cobra-stack.md +468 -0
  354. package/assets/lithermes-plugin/skills/lit-code/references/go/concurrency.md +362 -0
  355. package/assets/lithermes-plugin/skills/lit-code/references/go/data-modeling.md +329 -0
  356. package/assets/lithermes-plugin/skills/lit-code/references/go/error-handling.md +359 -0
  357. package/assets/lithermes-plugin/skills/lit-code/references/go/golangci-strict.md +236 -0
  358. package/assets/lithermes-plugin/skills/lit-code/references/go/grpc-connect.md +375 -0
  359. package/assets/lithermes-plugin/skills/lit-code/references/go/libraries.md +337 -0
  360. package/assets/lithermes-plugin/skills/lit-code/references/go/one-liners.md +202 -0
  361. package/assets/lithermes-plugin/skills/lit-code/references/go/sqlc-pgx.md +471 -0
  362. package/assets/lithermes-plugin/skills/lit-code/references/go/testing.md +467 -0
  363. package/assets/lithermes-plugin/skills/lit-code/references/go/type-patterns.md +298 -0
  364. package/assets/lithermes-plugin/skills/lit-code/references/python/README.md +314 -0
  365. package/assets/lithermes-plugin/skills/lit-code/references/python/async-anyio.md +442 -0
  366. package/assets/lithermes-plugin/skills/lit-code/references/python/data-modeling.md +233 -0
  367. package/assets/lithermes-plugin/skills/lit-code/references/python/data-processing.md +133 -0
  368. package/assets/lithermes-plugin/skills/lit-code/references/python/error-handling.md +218 -0
  369. package/assets/lithermes-plugin/skills/lit-code/references/python/fastapi-stack.md +316 -0
  370. package/assets/lithermes-plugin/skills/lit-code/references/python/httpx2-optimization.md +360 -0
  371. package/assets/lithermes-plugin/skills/lit-code/references/python/libraries.md +307 -0
  372. package/assets/lithermes-plugin/skills/lit-code/references/python/one-liners.md +268 -0
  373. package/assets/lithermes-plugin/skills/lit-code/references/python/orjson-stack.md +378 -0
  374. package/assets/lithermes-plugin/skills/lit-code/references/python/pydantic-ai.md +285 -0
  375. package/assets/lithermes-plugin/skills/lit-code/references/python/pyproject-strict.md +232 -0
  376. package/assets/lithermes-plugin/skills/lit-code/references/python/textual-tui.md +201 -0
  377. package/assets/lithermes-plugin/skills/lit-code/references/python/type-patterns.md +176 -0
  378. package/assets/lithermes-plugin/skills/lit-code/references/rust/README.md +317 -0
  379. package/assets/lithermes-plugin/skills/lit-code/references/rust/async-tokio.md +299 -0
  380. package/assets/lithermes-plugin/skills/lit-code/references/rust/axum-stack.md +467 -0
  381. package/assets/lithermes-plugin/skills/lit-code/references/rust/cargo-strict.md +317 -0
  382. package/assets/lithermes-plugin/skills/lit-code/references/rust/clap-stack.md +409 -0
  383. package/assets/lithermes-plugin/skills/lit-code/references/rust/concurrency.md +375 -0
  384. package/assets/lithermes-plugin/skills/lit-code/references/rust/libraries.md +439 -0
  385. package/assets/lithermes-plugin/skills/lit-code/references/rust/one-liners.md +291 -0
  386. package/assets/lithermes-plugin/skills/lit-code/references/rust/proptest-insta.md +429 -0
  387. package/assets/lithermes-plugin/skills/lit-code/references/rust/type-state.md +354 -0
  388. package/assets/lithermes-plugin/skills/lit-code/references/rust/unsafe-discipline.md +250 -0
  389. package/assets/lithermes-plugin/skills/lit-code/references/rust/zero-cost-safety.md +527 -0
  390. package/assets/lithermes-plugin/skills/lit-code/references/rust-ub/README.md +289 -0
  391. package/assets/lithermes-plugin/skills/lit-code/references/rust-ub/miri-sanitizers-loom.md +411 -0
  392. package/assets/lithermes-plugin/skills/lit-code/references/rust-ub/ub-taxonomy.md +269 -0
  393. package/assets/lithermes-plugin/skills/lit-code/references/typescript/README.md +195 -0
  394. package/assets/lithermes-plugin/skills/lit-code/references/typescript/backend-hono.md +672 -0
  395. package/assets/lithermes-plugin/skills/lit-code/references/typescript/bootstrap.md +199 -0
  396. package/assets/lithermes-plugin/skills/lit-code/references/typescript/data-modeling.md +202 -0
  397. package/assets/lithermes-plugin/skills/lit-code/references/typescript/error-handling.md +169 -0
  398. package/assets/lithermes-plugin/skills/lit-code/references/typescript/tsconfig-strict.md +152 -0
  399. package/assets/lithermes-plugin/skills/lit-code/references/typescript/type-patterns.md +196 -0
  400. package/assets/lithermes-plugin/skills/lit-code/scripts/go/check-no-excuse-rules.sh +173 -0
  401. package/assets/lithermes-plugin/skills/lit-code/scripts/go/new-project.py +138 -0
  402. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/.editorconfig +13 -0
  403. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/.golangci.yml +95 -0
  404. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/AGENTS.md.tmpl +24 -0
  405. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/README.md.tmpl +12 -0
  406. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/Taskfile.yml +40 -0
  407. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/ci.yml +37 -0
  408. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/config.go +24 -0
  409. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/gitignore +15 -0
  410. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/main.go.tmpl +22 -0
  411. package/assets/lithermes-plugin/skills/lit-code/scripts/go/templates/run.go +15 -0
  412. package/assets/lithermes-plugin/skills/lit-code/scripts/python/check-no-excuse-rules.py +687 -0
  413. package/assets/lithermes-plugin/skills/lit-code/scripts/python/new-project.py +172 -0
  414. package/assets/lithermes-plugin/skills/lit-code/scripts/python/new-script.py +116 -0
  415. package/assets/lithermes-plugin/skills/lit-code/scripts/rust/check-no-excuse-rules.py +296 -0
  416. package/assets/lithermes-plugin/skills/lit-code/scripts/rust/check-no-excuse-rules.sh +158 -0
  417. package/assets/lithermes-plugin/skills/lit-code/scripts/rust/new-project.py +175 -0
  418. package/assets/lithermes-plugin/skills/lit-code/scripts/typescript/check-no-excuse-rules.ts +282 -0
  419. package/assets/lithermes-plugin/skills/lit-code/scripts/typescript/new-project.ts +177 -0
  420. package/assets/lithermes-plugin/skills/lit-commit/SKILL.md +242 -0
  421. package/assets/lithermes-plugin/skills/lit-comprehend/SKILL.md +259 -0
  422. package/assets/lithermes-plugin/skills/lit-comprehend/assets/explainer-scaffold.html +123 -0
  423. package/assets/lithermes-plugin/skills/lit-comprehend/references/artifact-template.md +205 -0
  424. package/assets/lithermes-plugin/skills/lit-comprehend/references/micro-worlds.md +139 -0
  425. package/assets/lithermes-plugin/skills/lit-comprehend/scripts/verify-explainer.py +324 -0
  426. package/assets/lithermes-plugin/skills/lit-crucible/SKILL.md +315 -0
  427. package/assets/lithermes-plugin/skills/lit-handoff/ORIGIN.json +23 -0
  428. package/assets/lithermes-plugin/skills/lit-handoff/SKILL.md +145 -0
  429. package/assets/lithermes-plugin/skills/lit-init/SKILL.md +329 -0
  430. package/assets/lithermes-plugin/skills/lit-korean/SKILL.md +237 -0
  431. package/assets/lithermes-plugin/skills/lit-korean/references/quick-rules.md +24 -0
  432. package/assets/lithermes-plugin/skills/lit-korean/references/safety-checklist.md +23 -0
  433. package/assets/lithermes-plugin/skills/lit-plan/SKILL.md +596 -0
  434. package/assets/lithermes-plugin/skills/lit-recap/SKILL.md +183 -0
  435. package/assets/lithermes-plugin/skills/lit-scientific-visualization/ORIGIN.json +32 -0
  436. package/assets/lithermes-plugin/skills/lit-scientific-visualization/SKILL.md +170 -0
  437. package/assets/lithermes-plugin/skills/litgoal/.gitkeep +0 -0
  438. package/assets/lithermes-plugin/skills/litgoal/SKILL.md +372 -0
  439. package/assets/lithermes-plugin/skills/litresearch/ATTRIBUTION.md +50 -0
  440. package/assets/lithermes-plugin/skills/litresearch/SKILL.md +593 -0
  441. package/assets/lithermes-plugin/skills/litwork/SKILL.md +419 -0
  442. package/assets/lithermes-plugin/skills/lsp/SKILL.md +165 -0
  443. package/assets/lithermes-plugin/skills/lsp-setup/SKILL.md +265 -0
  444. package/assets/lithermes-plugin/skills/lsp-setup/references/bash/README.md +68 -0
  445. package/assets/lithermes-plugin/skills/lsp-setup/references/c-cpp/README.md +78 -0
  446. package/assets/lithermes-plugin/skills/lsp-setup/references/csharp/README.md +90 -0
  447. package/assets/lithermes-plugin/skills/lsp-setup/references/dart/README.md +59 -0
  448. package/assets/lithermes-plugin/skills/lsp-setup/references/elixir/README.md +61 -0
  449. package/assets/lithermes-plugin/skills/lsp-setup/references/go/README.md +63 -0
  450. package/assets/lithermes-plugin/skills/lsp-setup/references/haskell/README.md +69 -0
  451. package/assets/lithermes-plugin/skills/lsp-setup/references/java/README.md +70 -0
  452. package/assets/lithermes-plugin/skills/lsp-setup/references/julia/README.md +70 -0
  453. package/assets/lithermes-plugin/skills/lsp-setup/references/kotlin/README.md +72 -0
  454. package/assets/lithermes-plugin/skills/lsp-setup/references/lua/README.md +68 -0
  455. package/assets/lithermes-plugin/skills/lsp-setup/references/php/README.md +61 -0
  456. package/assets/lithermes-plugin/skills/lsp-setup/references/python/README.md +73 -0
  457. package/assets/lithermes-plugin/skills/lsp-setup/references/ruby/README.md +67 -0
  458. package/assets/lithermes-plugin/skills/lsp-setup/references/rust/README.md +65 -0
  459. package/assets/lithermes-plugin/skills/lsp-setup/references/swift/README.md +64 -0
  460. package/assets/lithermes-plugin/skills/lsp-setup/references/terraform/README.md +66 -0
  461. package/assets/lithermes-plugin/skills/lsp-setup/references/typescript/README.md +82 -0
  462. package/assets/lithermes-plugin/skills/lsp-setup/references/yaml/README.md +65 -0
  463. package/assets/lithermes-plugin/skills/lsp-setup/references/zig/README.md +59 -0
  464. package/assets/lithermes-plugin/skills/lsp-setup/scripts/detect-lsp.ts +221 -0
  465. package/assets/lithermes-plugin/skills/lsp-setup/scripts/lsp-server-table.ts +146 -0
  466. package/assets/lithermes-plugin/skills/lsp-setup/scripts/tsconfig.json +18 -0
  467. package/assets/lithermes-plugin/skills/lsp-setup/scripts/verify-lsp.ts +252 -0
  468. package/assets/lithermes-plugin/skills/refactor/SKILL.md +896 -0
  469. package/assets/lithermes-plugin/skills/review-work/SKILL.md +450 -0
  470. package/assets/lithermes-plugin/skills/rules/SKILL.md +194 -0
  471. package/assets/lithermes-plugin/skills/skill-observer/SKILL.md +190 -0
  472. package/assets/lithermes-plugin/skills/skill-observer/references/review-contract.md +77 -0
  473. package/assets/lithermes-plugin/skills/start-work/SKILL.md +550 -0
  474. package/assets/lithermes-plugin/skills/structural-search/SKILL.md +274 -0
  475. package/assets/lithermes-plugin/skills/visual-qa/SKILL.md +48 -0
  476. package/assets/lithermes-plugin/skills/visual-qa/references/capture-playbook.md +133 -0
  477. package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +522 -0
  478. package/assets/lithermes-plugin/skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json +135 -0
  479. package/assets/lithermes-plugin/skills/visual-qa/schemas/evidence-manifest-v1beta1.schema.json +110 -0
  480. package/assets/lithermes-plugin/skills/visual-qa/schemas/review-receipt-v1alpha1.schema.json +49 -0
  481. package/assets/lithermes-plugin/skills/visual-qa/scripts/contract_checks.py +71 -0
  482. package/assets/lithermes-plugin/skills/visual-qa/scripts/evidence_contract.py +288 -0
  483. package/assets/lithermes-plugin/skills/visual-qa/scripts/png_runtime.py +379 -0
  484. package/assets/lithermes-plugin/skills/visual-qa/scripts/review_contract.py +151 -0
  485. package/assets/lithermes-plugin/skills/visual-qa/scripts/tier_checks.py +190 -0
  486. package/assets/lithermes-plugin/skills/visual-qa/scripts/tier_contract.py +103 -0
  487. package/assets/lithermes-plugin/skills/visual-qa/scripts/tui_runtime.py +170 -0
  488. package/assets/lithermes-plugin/skills/visual-qa/scripts/visual_contracts.py +31 -0
  489. package/assets/lithermes-plugin/skills/visual-qa/scripts/visual_qa.py +79 -0
  490. package/assets/lithermes-plugin/skills/wikify/LICENSE +21 -0
  491. package/assets/lithermes-plugin/skills/wikify/ORIGIN.json +12 -0
  492. package/assets/lithermes-plugin/skills/wikify/SKILL.md +192 -0
  493. package/assets/lithermes-plugin/skills/wikify/assets/home-template.md +44 -0
  494. package/assets/lithermes-plugin/skills/wikify/assets/maintenance-report-template.md +46 -0
  495. package/assets/lithermes-plugin/skills/wikify/assets/paper-source-note-template.md +138 -0
  496. package/assets/lithermes-plugin/skills/wikify/assets/source-note-template.md +45 -0
  497. package/assets/lithermes-plugin/skills/wikify/assets/wiki-rules-template.md +117 -0
  498. package/assets/lithermes-plugin/skills/wikify/modes/ingest/SKILL.md +24 -0
  499. package/assets/lithermes-plugin/skills/wikify/modes/init/SKILL.md +26 -0
  500. package/assets/lithermes-plugin/skills/wikify/modes/lint/SKILL.md +30 -0
  501. package/assets/lithermes-plugin/skills/wikify/modes/query/SKILL.md +22 -0
  502. package/assets/lithermes-plugin/skills/wikify/modes/save/SKILL.md +32 -0
  503. package/assets/lithermes-plugin/skills/wikify/references/full-contract.md +761 -0
  504. package/assets/lithermes-plugin/uiux_runtime_common.py +117 -0
  505. package/assets/lithermes-plugin/vendor/NOTICE.md +21 -0
  506. package/assets/lithermes-plugin/vendor/handoff/SKILL.md +199 -0
  507. package/assets/lithermes-plugin/vendor/handoff/evals/evals.json +154 -0
  508. package/assets/lithermes-plugin/vendor/handoff/examples/HANDOFF-example-generic-auth-refactor.md +97 -0
  509. package/assets/lithermes-plugin/vendor/handoff/templates/HANDOFF.md +121 -0
  510. package/assets/lithermes-plugin/vendor/licenses/022_handoff-MIT.txt +21 -0
  511. package/assets/lithermes-plugin/vendor/licenses/045_scientific-visualization-MIT.txt +21 -0
  512. package/assets/lithermes-plugin/vendor/provenance/022_handoff.md +16 -0
  513. package/assets/lithermes-plugin/vendor/provenance/045_scientific-visualization.md +18 -0
  514. package/assets/lithermes-plugin/vendor/scientific-visualization/SKILL.md +283 -0
  515. package/assets/lithermes-plugin/vendor/scientific-visualization/assets/color_palettes.py +197 -0
  516. package/assets/lithermes-plugin/vendor/scientific-visualization/assets/nature.mplstyle +75 -0
  517. package/assets/lithermes-plugin/vendor/scientific-visualization/assets/presentation.mplstyle +74 -0
  518. package/assets/lithermes-plugin/vendor/scientific-visualization/assets/publication.mplstyle +78 -0
  519. package/assets/lithermes-plugin/vendor/scientific-visualization/evals/evals.json +158 -0
  520. package/assets/lithermes-plugin/vendor/scientific-visualization/references/color_palettes.md +380 -0
  521. package/assets/lithermes-plugin/vendor/scientific-visualization/references/journal_requirements.md +359 -0
  522. package/assets/lithermes-plugin/vendor/scientific-visualization/references/matplotlib_examples.md +608 -0
  523. package/assets/lithermes-plugin/vendor/scientific-visualization/references/mdanalysis_martini_visualization.md +85 -0
  524. package/assets/lithermes-plugin/vendor/scientific-visualization/references/publication_guidelines.md +217 -0
  525. package/assets/lithermes-plugin/vendor/scientific-visualization/references/seaborn_for_publications.md +293 -0
  526. package/assets/lithermes-plugin/vendor/scientific-visualization/scripts/figure_export.py +238 -0
  527. package/assets/lithermes-plugin/vendor/scientific-visualization/scripts/style_presets.py +467 -0
  528. package/assets/lithermes-plugin/vendor/scientific-visualization/tests/test_figure_export.py +51 -0
  529. package/assets/lithermes-plugin/vendor/scientific-visualization/tests/test_style_presets.py +114 -0
  530. package/bin/lithermes.js +8 -0
  531. package/package.json +58 -0
  532. package/readme-assets/badge-license.svg +1 -0
  533. package/readme-assets/badge-version.svg +1 -0
  534. package/readme-assets/cover.webp +0 -0
  535. package/readme-assets/lithermes-clay-icon.png +0 -0
  536. package/readme-assets/lithermes-wordmark.svg +5 -0
  537. package/src/cli.js +396 -0
  538. package/src/lib/autoUpdate.js +4 -0
  539. package/src/lib/canonicalCorpus.js +436 -0
  540. package/src/lib/check.js +200 -0
  541. package/src/lib/config.js +229 -0
  542. package/src/lib/files.js +126 -0
  543. package/src/lib/hermesDiscovery.js +122 -0
  544. package/src/lib/hud.js +135 -0
  545. package/src/lib/install.js +559 -0
  546. package/src/lib/litMark.js +68 -0
  547. package/src/lib/modelConfig.js +431 -0
  548. package/src/lib/modelConfigBoundary.js +206 -0
  549. package/src/lib/modelPrompt.js +201 -0
  550. package/src/lib/modelRoutePolicy.js +379 -0
  551. package/src/lib/patch.js +153 -0
  552. package/src/lib/skillLoop.js +62 -0
  553. package/src/lib/skillPayload.js +306 -0
  554. package/src/lib/skins.js +274 -0
  555. package/src/lib/spinner.js +193 -0
  556. package/src/lib/updateNotifier.js +1133 -0
  557. package/src/lib/updateNotifierWorker.js +9 -0
@@ -0,0 +1,1813 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shlex
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any
8
+ from urllib.parse import urlsplit
9
+
10
+ try:
11
+ from .bounded_work import *
12
+ from .core_commands import *
13
+ from .core_commands import _command_lit_dispatch, _escape_command_content, _join_positional
14
+ from .core_contexts import *
15
+ from .core_contexts import (
16
+ BROWSER_IDENTITY_BLOCKER,
17
+ _SKILL_ROUTE_CONTRACTS,
18
+ _probe_browser_driver_report as _browser_drive_probe_report,
19
+ )
20
+ from .core_contract import *
21
+ from .core_contract import _post_edit_skill_names, _skill_body, _skill_body_block
22
+ from .core_plans import *
23
+ from .core_plans import _CRITERION_PATTERN
24
+ from .core_recap_command import *
25
+ from .core_recap_command import _recap_render_options, _recap_safe_value
26
+ from .core_recap_state import *
27
+ from .core_recap_state import _recap_item, _recap_run_completions, _recap_tech_kind
28
+ from .core_review import *
29
+ from .core_review import _run_git
30
+ from .core_routing import *
31
+ from .core_routing import _after_mode_word, _after_start_work, _extract_bind_goal, _extract_run_context_task, _normalized_exact_phrase
32
+ from .core_runs import *
33
+ from .core_runs import _same_plan_path
34
+ from .core_runtime import *
35
+ from .core_runtime import _clamp_task, _redact_local_only_preview
36
+ from .core_workflow_commands import *
37
+ from .session_context import is_delegate_child_platform
38
+ except (ImportError, ModuleNotFoundError):
39
+ from bounded_work import *
40
+ from core_commands import *
41
+ from core_commands import _command_lit_dispatch, _escape_command_content, _join_positional
42
+ from core_contexts import *
43
+ from core_contexts import (
44
+ BROWSER_IDENTITY_BLOCKER,
45
+ _SKILL_ROUTE_CONTRACTS,
46
+ _probe_browser_driver_report as _browser_drive_probe_report,
47
+ )
48
+ from core_contract import *
49
+ from core_contract import _post_edit_skill_names, _skill_body, _skill_body_block
50
+ from core_plans import *
51
+ from core_plans import _CRITERION_PATTERN
52
+ from core_recap_command import *
53
+ from core_recap_command import _recap_render_options, _recap_safe_value
54
+ from core_recap_state import *
55
+ from core_recap_state import _recap_item, _recap_run_completions, _recap_tech_kind
56
+ from core_review import *
57
+ from core_review import _run_git
58
+ from core_routing import *
59
+ from core_routing import _after_mode_word, _after_start_work, _extract_bind_goal, _extract_run_context_task, _normalized_exact_phrase
60
+ from core_runs import *
61
+ from core_runs import _same_plan_path
62
+ from core_runtime import *
63
+ from core_runtime import _clamp_task, _redact_local_only_preview
64
+ from core_workflow_commands import *
65
+ from session_context import is_delegate_child_platform
66
+
67
+ # The rules engine is a subpackage, so it is imported separately from the flat
68
+ # star-imports above: it must not leak its module names into this facade.
69
+ try:
70
+ from . import rules as _rules
71
+ except (ImportError, ModuleNotFoundError):
72
+ import rules as _rules
73
+
74
+ try:
75
+ from . import auto_update as _auto_update
76
+ except (ImportError, ModuleNotFoundError):
77
+ import auto_update as _auto_update
78
+
79
+ try:
80
+ from . import output_styles as _output_styles
81
+ except (ImportError, ModuleNotFoundError):
82
+ import output_styles as _output_styles
83
+
84
+ try:
85
+ from . import provider_metrics as _provider_metrics
86
+ except (ImportError, ModuleNotFoundError):
87
+ import provider_metrics as _provider_metrics
88
+
89
+ try:
90
+ from . import deliverable_hedge_guard as _deliverable_hedges
91
+ except (ImportError, ModuleNotFoundError):
92
+ import deliverable_hedge_guard as _deliverable_hedges
93
+
94
+ # Current-turn state stays in the compatibility facade because tests and the
95
+ # plugin register surface intentionally inspect it directly.
96
+ _PENDING_IGNITE: set[str] = set()
97
+ _PENDING_DISCIPLINE: dict[str, str] = {}
98
+ _PENDING_BLOCK: dict[str, str] = {}
99
+ # Mutated paths observed by post_tool_call, keyed by session, drained by the next
100
+ # pre_llm_call. Bounded so an abandoned or delegate-child session cannot grow it.
101
+ _PENDING_POST_EDIT: dict[str, list[str]] = {}
102
+ _MAX_POST_EDIT_SESSIONS = 64
103
+ _MAX_POST_EDIT_PATHS = 40
104
+
105
+
106
+ def post_tool_call(**kwargs: Any) -> None:
107
+ """Record the paths a completed tool call mutated, for event-shaped routing.
108
+
109
+ Hermes treats ``post_tool_call`` as an observer: ``model_tools.
110
+ _emit_post_tool_call_hook`` discards whatever a handler returns, so this hook
111
+ structurally cannot put text in front of the model. It is still the correct
112
+ event source — it is the only hook that sees a *completed* mutation — so it
113
+ buffers the routing input here and ``pre_llm_call``, the one hook whose return
114
+ value Hermes uses, renders and delivers it on the next turn.
115
+
116
+ Side-effect-free by design: no ledger write, because this fires once per tool
117
+ call and an events.jsonl line per edit would be pure noise.
118
+ """
119
+ if str(kwargs.get("status") or "ok") != "ok":
120
+ return None
121
+ paths = mutated_tool_paths(str(kwargs.get("tool_name") or ""), kwargs.get("args"))
122
+ if not paths:
123
+ return None
124
+ key = str(kwargs.get("session_id") or "") or str(kwargs.get("task_id") or "")
125
+ if not key:
126
+ return None
127
+ _deliverable_hedges.observe_completed_paths(
128
+ key,
129
+ paths,
130
+ workspace=kwargs.get("workspace"),
131
+ )
132
+ pending = _PENDING_POST_EDIT.setdefault(key, [])
133
+ for path in paths:
134
+ if path in pending:
135
+ pending.remove(path)
136
+ pending.append(path)
137
+ del pending[:-_MAX_POST_EDIT_PATHS]
138
+ while len(_PENDING_POST_EDIT) > _MAX_POST_EDIT_SESSIONS:
139
+ _PENDING_POST_EDIT.pop(next(iter(_PENDING_POST_EDIT)), None)
140
+ return None
141
+
142
+
143
+ def post_api_request(**kwargs: Any) -> None:
144
+ """Record Hermes' numeric provider usage receipt in the local ledger.
145
+
146
+ The provider observer payload also carries request/response compatibility
147
+ fields, but the measurement module intentionally ignores them. Returning
148
+ ``None`` preserves the observer-only hook contract.
149
+ """
150
+ return _provider_metrics.post_api_request(**kwargs)
151
+
152
+
153
+ def consume_post_edit_skill_context(session_id: Any) -> str:
154
+ """Drain this session's buffered mutations into a model-facing route block."""
155
+ paths = _PENDING_POST_EDIT.pop(str(session_id or ""), None)
156
+ return conditional_post_edit_skill_blocks(paths) if paths else ""
157
+
158
+
159
+ def consume_post_edit_context(session_id: Any, compacted: bool = False) -> str:
160
+ """Drain buffered mutations into BOTH event-shaped lanes.
161
+
162
+ The skill route and the dynamic rules lane read the same buffer, so it is
163
+ popped exactly once here rather than by each consumer.
164
+ """
165
+ paths = _PENDING_POST_EDIT.pop(str(session_id or ""), None)
166
+ if not paths:
167
+ return ""
168
+ blocks = [
169
+ conditional_post_edit_skill_blocks(paths),
170
+ _rules.dynamic_rules_block(paths, session_id, compacted=compacted),
171
+ ]
172
+ return "\n\n".join(block for block in blocks if block)
173
+
174
+
175
+ def on_session_start(**kwargs: Any) -> None:
176
+ """Initialize session-scoped rule state for a brand-new session.
177
+
178
+ Hermes ignores this hook's return value, so it cannot inject the static rule
179
+ block itself; it resets the dedup ledger so a recycled session id cannot
180
+ inherit "already injected" from a previous session. `pre_llm_call` renders
181
+ the static lane on the first turn.
182
+ """
183
+ release_browser_drive_state(kwargs.get("session_id"))
184
+ _deliverable_hedges.release_session(kwargs.get("session_id"))
185
+ _rules.begin_session(str(kwargs.get("session_id") or ""))
186
+ _print_agents_nudge(str(kwargs.get("platform") or ""))
187
+ return None
188
+
189
+
190
+ def _print_agents_nudge(platform: str) -> None:
191
+ """Point interactive classic-CLI sessions at Hermes' native /agents view.
192
+
193
+ ADR-001: the /agents overlay (alias /tasks) is the host-owned surface for
194
+ live delegate_task visibility; LitHermes only nudges, it renders nothing.
195
+ Gated to platform "cli" (cli.py fires the hook with platform="cli") AND a
196
+ real stdout tty: the TUI/gateway/telegram surfaces and test runs stay
197
+ byte-identical, and a gateway process under a pseudo-tty never prints
198
+ because its platform is not "cli".
199
+ """
200
+ try:
201
+ if platform == "cli" and sys.stdout.isatty():
202
+ print("helper agents: type /agents")
203
+ except Exception:
204
+ pass
205
+
206
+
207
+ def consume_rules_context(**kwargs: Any) -> str:
208
+ """Render both rule lanes plus the post-edit skill route for this turn.
209
+
210
+ One owner, because all three share state that must be consumed exactly once:
211
+ the compaction budget and the mutated-path buffer.
212
+
213
+ Static lane runs on the first turn of a session and once more after a context
214
+ compaction. Hermes stamps its compaction summary message with
215
+ `_compressed_summary`, so the reopen is detected structurally rather than
216
+ guessed from token counts. The reopen also shrinks both budgets and clears the
217
+ dedup ledger, since the compacted history no longer holds the earlier copy.
218
+ """
219
+ session_id = str(kwargs.get("session_id") or "")
220
+ reopened = False
221
+ if _rules.history_was_compacted(kwargs.get("conversation_history")):
222
+ reopened = _rules.consume_compaction_budget(session_id)
223
+
224
+ blocks = []
225
+ if kwargs.get("is_first_turn") or reopened:
226
+ try:
227
+ blocks.append(_rules.static_rules_block(Path.cwd(), session_id, compacted=reopened))
228
+ except OSError:
229
+ pass
230
+ style_block = _output_styles.output_style_block()
231
+ if style_block:
232
+ blocks.append(style_block)
233
+ blocks.append(_deliverable_hedges.consume_context(session_id))
234
+ blocks.append(consume_post_edit_context(session_id, compacted=reopened))
235
+ return "\n\n".join(block for block in blocks if block)
236
+
237
+
238
+ def release_rules_session(session_id: Any) -> None:
239
+ _rules.end_session(str(session_id or ""))
240
+ _deliverable_hedges.release_session(session_id)
241
+
242
+ def ignite(result: str | dict[str, str], route: str = "litwork") -> str | dict[str, str]:
243
+ """Draw a command acknowledgement and require a separate model probe."""
244
+ mark = acknowledgement(route, color=True)
245
+ if isinstance(result, dict):
246
+ updated = dict(result)
247
+ display = result.get("display")
248
+ if display and not str(display).startswith(mark):
249
+ updated["display"] = f"{mark}\n{display}"
250
+ if result.get("agent_message"):
251
+ updated["agent_message"] = f"{probe_contract(route)}\n\n{result['agent_message']}"
252
+ return updated
253
+ if isinstance(result, str) and result.strip() and not result.startswith(mark):
254
+ return f"{mark}\n{result}"
255
+ return result
256
+
257
+ # browser-drive substitution guard.
258
+ #
259
+ # The route already measures the driver and states the verdict in context. On this
260
+ # host that was not enough: the model read `status: unavailable` and an explicit
261
+ # prohibition, then fetched the page anyway in two separate toolset configurations.
262
+ # A contract the host can overrule is a suggestion. So when the route fires and the
263
+ # driver is absent, the substitution is refused at the tool boundary, where the
264
+ # answer is a fact rather than a request.
265
+ #
266
+ # Scope is deliberately narrow. Only page-retrieval is refused: exact browser/web
267
+ # tools, URL-bearing aliases, and a terminal command that performs a fetch. Ordinary
268
+ # terminal and file work in the same turn is untouched.
269
+ _BROWSER_DRIVE_GUARD: set[str] = set()
270
+ _BROWSER_DRIVE_REPORTS: dict[str, dict[str, Any]] = {}
271
+ # Keep state-loss blocking tied to the sessions that actually lost their entry.
272
+ _BROWSER_DRIVE_EVICTED_SESSIONS: set[str] = set()
273
+
274
+ _FETCH_TOOL_NAMES = frozenset(
275
+ {
276
+ "browser_navigate",
277
+ "browser_open",
278
+ "browser_snapshot",
279
+ "fetch",
280
+ "fetch_url",
281
+ "get_url",
282
+ "http.client",
283
+ "http.get",
284
+ "http.request",
285
+ "http_client",
286
+ "open_url",
287
+ "page_fetch",
288
+ "page_open",
289
+ "url_fetch",
290
+ "web_fetch",
291
+ "web_open",
292
+ "web_search",
293
+ }
294
+ )
295
+ _FETCH_TOOL_ALIASES = frozenset({"browse", "get", "navigate", "open", "request", "retrieve"})
296
+ _FETCH_COMMAND_NAMES = frozenset(
297
+ {
298
+ "curl",
299
+ "fetch",
300
+ "http",
301
+ "http.client",
302
+ "httpie",
303
+ "lynx",
304
+ "nc",
305
+ "netcat",
306
+ "open_url",
307
+ "socat",
308
+ "telnet",
309
+ "wget",
310
+ "w3m",
311
+ }
312
+ )
313
+ _SHELL_COMMAND_NAMES = frozenset({"ash", "bash", "dash", "fish", "ksh", "sh", "zsh"})
314
+ _HOST_BROWSER_TOOL = re.compile(
315
+ r"(?:^|[^a-z0-9])(?:browser|web|computer[_\-.]?use)(?:$|[^a-z0-9])",
316
+ re.IGNORECASE,
317
+ )
318
+ _SHELL_NETWORK_ACCESS = re.compile(r"/dev/(?:tcp|udp)/", re.IGNORECASE)
319
+ _URL_INERT_COMMAND_NAMES = frozenset(
320
+ {
321
+ "awk",
322
+ "cat",
323
+ "cut",
324
+ "echo",
325
+ "grep",
326
+ "head",
327
+ "git",
328
+ "less",
329
+ "ls",
330
+ "more",
331
+ "printf",
332
+ "pwd",
333
+ "rg",
334
+ "sed",
335
+ "sort",
336
+ "tail",
337
+ "tr",
338
+ "true",
339
+ "false",
340
+ "uniq",
341
+ "wc",
342
+ "bun",
343
+ "deno",
344
+ "node",
345
+ "nodejs",
346
+ "perl",
347
+ "php",
348
+ "python",
349
+ "python2",
350
+ "python3",
351
+ "ruby",
352
+ }
353
+ )
354
+ _GIT_NETWORK_SUBCOMMANDS = frozenset({"clone", "fetch", "pull", "push", "ls-remote"})
355
+ _PYTHON_COMMAND_NAMES = frozenset({"python", "python2", "python3"})
356
+ _NODE_COMMAND_NAMES = frozenset({"bun", "deno", "node", "nodejs"})
357
+ _COMMAND_FIELDS = ("command", "cmd", "script", "code", "input")
358
+ _MAX_URL_ARGUMENT_DEPTH = 16
359
+ _MAX_URL_ARGUMENT_NODES = 256
360
+ _HTTP_URL = re.compile(r"(?<![A-Za-z0-9+.-])https?://[^\s'\"`<>()|;&]+", re.IGNORECASE)
361
+ _SHELL_SEPARATOR_NAMES = frozenset({";", "&", "|", "&&", "||"})
362
+ _SHELL_ASSIGNMENT = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)(\+?=)(.*)$")
363
+ _SHELL_CONTROL_WORD_NAMES = frozenset(
364
+ {
365
+ "!",
366
+ "if",
367
+ "while",
368
+ "until",
369
+ "then",
370
+ "do",
371
+ "else",
372
+ "elif",
373
+ }
374
+ )
375
+ _SHELL_PARAMETER_COMMAND_START = re.compile(
376
+ r'(?m)(?:^|[;&|])\s*(?:(?:if|while|until|then|do|else|elif|!)\s+)?'
377
+ r'"?(\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$[A-Za-z_][A-Za-z0-9_]*)'
378
+ )
379
+ _SHELL_PARAMETER = re.compile(r"\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))")
380
+ _OPAQUE_SHELL_VALUE = "\x00lithermes-opaque-shell-value\x00"
381
+ _NODE_REQUEST = re.compile(
382
+ r"(?:\bfetch\s*\()"
383
+ r"|(?:\b(?:http|https|node:http|node:https)\s*\.\s*(?:get|request)\s*\()"
384
+ r"|(?:\brequire\s*\(\s*['\"]?(?:node:)?https?['\"]?\s*\)\s*\.\s*(?:get|request)\s*\()"
385
+ r"|(?:\brequire\s*\(\s*['\"](?:node:)?https?['\"]\s*\)[^\n]{0,512}\b(?:get|request)\s*\()"
386
+ r"|(?:\brequire\s*\([^)]{1,128}\)[^\n]{0,512}\b(?:get|request)\s*\()"
387
+ r"|(?:\[['\"](?:fetch|get|request)['\"]\]\s*\()"
388
+ r"|(?:\b(?:axios|undici)\s*\.\s*(?:fetch|get|request)\s*\()",
389
+ re.IGNORECASE,
390
+ )
391
+ _PYTHON_REQUEST = re.compile(
392
+ r"(?:\b(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp)\s*\.\s*"
393
+ r"(?:delete|get|patch|post|put|request|urlopen|urlretrieve)\s*\()"
394
+ r"|(?:\b(?:urlopen|urlretrieve)\s*\()"
395
+ r"|(?:\bhttp\s*\.\s*client\s*\.\s*(?:HTTPConnection|HTTPSConnection|request)\s*\()"
396
+ r"|(?:\b(?:HTTPConnection|HTTPSConnection)\s*\()"
397
+ r"|(?:\bgetattr\s*\(\s*http\s*\.\s*client\b[^)]{0,160}\)\s*\()"
398
+ r"|(?:\bgetattr\s*\([^,()]{1,80},\s*['\"](?:request|get|connect|urlopen|create_connection|create_server|socket|HTTPConnection|HTTPSConnection)['\"]\s*\)\s*\()"
399
+ r"|(?:\bsocket\s*\.\s*(?:create_connection|create_server|socket)\s*\()"
400
+ r"|(?:\b(?:create_connection|create_server)\s*\()"
401
+ r"|(?:\b(?:import|from)\s+(?:requests|httpx|urllib3|aiohttp|socket)\b[^\n]{0,256}\b[A-Za-z_]\w*\s*\.\s*(?:delete|get|patch|post|put|request|urlopen|urlretrieve|create_connection|create_server)\s*\()",
402
+ re.IGNORECASE,
403
+ )
404
+ _BASE64_DECODE = re.compile(
405
+ r"(?:\bbase64\s+(?:-[A-Za-z]*d|--decode)\b"
406
+ r"|\b(?:base64\.)?(?:b64decode|urlsafe_b64decode)\s*\("
407
+ r"|\bBuffer\s*\.\s*from\s*\([^\n]{0,8192}['\"]base64['\"])",
408
+ re.IGNORECASE,
409
+ )
410
+ _BASE64_EXECUTION = re.compile(
411
+ r"(?:"
412
+ r"\|\s*(?:(?:/[A-Za-z0-9_.-]+)*/)?(?:env\s+)?(?:(?:/[A-Za-z0-9_.-]+)*/)?(?:busybox\s+)?(?:ash|bash|dash|fish|ksh|node|perl|php|python|python3|ruby|sh|zsh)\b"
413
+ r"|\b(?:ash|bash|dash|fish|ksh|node|python|python3|sh|zsh)\s+-c\b"
414
+ r"|\b(?:exec|eval|popen|source|system)\s*\(?"
415
+ r"|\b(?:new\s+Function|os\s*\.\s*system|subprocess\s*\.\s*"
416
+ r"(?:call|Popen|run))\s*\("
417
+ r")",
418
+ re.IGNORECASE,
419
+ )
420
+ _MAX_GUARD_TEXT = 8192
421
+ _MAX_COMMAND_DEPTH = 8
422
+ _MAX_BROWSER_DRIVE_SESSIONS = 64
423
+ _MAX_BROWSER_SESSION_ID_LENGTH = 256
424
+ _MAX_BROWSER_REPORT_FIELD_LENGTH = 512
425
+ _BLOCKER_STATE = "BLOCKED_BROWSER_DRIVER_STATE_UNAVAILABLE"
426
+ _BROWSER_DRIVER_VERSION = re.compile(
427
+ r"^agent-browser\s+v?\d+(?:\.\d+)+$", re.IGNORECASE
428
+ )
429
+ _RUBY_NETWORK_REQUEST = re.compile(r"\b(?:URI\s*\.\s*open|Net\s*::\s*HTTP|open-uri)\b", re.IGNORECASE)
430
+ _PHP_NETWORK_REQUEST = re.compile(
431
+ r"\b(?:file_get_contents|fopen|curl_exec|fsockopen)\s*\(", re.IGNORECASE
432
+ )
433
+ _PYTHON_NETWORK_MEMBERS = frozenset(
434
+ {
435
+ "create_connection",
436
+ "create_server",
437
+ "delete",
438
+ "get",
439
+ "patch",
440
+ "post",
441
+ "put",
442
+ "request",
443
+ "socket",
444
+ "urlopen",
445
+ "urlretrieve",
446
+ "HTTPConnection",
447
+ "HTTPSConnection",
448
+ }
449
+ )
450
+ _PYTHON_NETWORK_MODULES = frozenset(
451
+ {"aiohttp", "http", "http.client", "httpx", "requests", "socket", "urllib", "urllib.request", "urllib3"}
452
+ )
453
+ _PYTHON_NETWORK_IMPORT = re.compile(
454
+ r"(?:\bimport\s+(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp|http(?:\.client)?|socket)\b"
455
+ r"|\bfrom\s+(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp|http(?:\.client)?|socket)\s+import\b"
456
+ r"|(?:^|[^A-Za-z0-9_])(?:__import__|importlib\s*\.\s*import_module)\s*\(\s*['\"]"
457
+ r"(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp|http(?:\.client)?|socket)['\"]\s*\))",
458
+ re.IGNORECASE,
459
+ )
460
+ _PYTHON_MEMBER_ASSIGNMENT = re.compile(
461
+ r"\b([A-Za-z_]\w*)\s*=\s*[A-Za-z_]\w*\s*\.\s*"
462
+ r"(?:delete|get|patch|post|put|request|urlopen|urlretrieve|create_connection|create_server|socket|"
463
+ r"HTTPConnection|HTTPSConnection)\b",
464
+ re.IGNORECASE,
465
+ )
466
+ _PYTHON_ALIAS_IMPORT = re.compile(
467
+ r"\bfrom\s+(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp|http(?:\.client)?|socket)\s+import\b[^;]*"
468
+ r"\bas\s+([A-Za-z_]\w*)\b",
469
+ re.IGNORECASE,
470
+ )
471
+ _PYTHON_MEMBER_CALL = re.compile(
472
+ r"\b[A-Za-z_]\w*\s*\.\s*(?:delete|get|patch|post|put|request|urlopen|urlretrieve|"
473
+ r"create_connection|create_server|socket|HTTPConnection|HTTPSConnection)\s*\(",
474
+ re.IGNORECASE,
475
+ )
476
+ _PYTHON_DYNAMIC_ALIAS = re.compile(r"\b([A-Za-z_]\w*)\s*=\s*getattr\s*\([^;]{1,256}\)", re.IGNORECASE)
477
+ _PYTHON_FROM_REQUEST = re.compile(
478
+ r"\bfrom\s+(?:requests|httpx|urllib(?:\.request)?|urllib3|aiohttp|http(?:\.client)?|socket)\s+import\b"
479
+ r"[^\n]{0,256}\b(?:delete|get|patch|post|put|request|urlopen|urlretrieve|create_connection|create_server|socket|"
480
+ r"HTTPConnection|HTTPSConnection)\s*\(",
481
+ re.IGNORECASE,
482
+ )
483
+ _NODE_NETWORK_MARKER = re.compile(
484
+ r"(?:\bfetch\b|\b(?:node:)?https?\b|\b(?:axios|undici)\b|"
485
+ r"\brequire\s*\(\s*['\"](?:node:)?https?['\"]\s*\))",
486
+ re.IGNORECASE,
487
+ )
488
+ _NODE_GLOBAL_FETCH_ALIAS = re.compile(
489
+ r"\b(?:const|let|var)?\s*([A-Za-z_]\w*)\s*=\s*globalThis\s*\[\s*['\"]fetch['\"]\s*\]",
490
+ re.IGNORECASE,
491
+ )
492
+ _NODE_REQUIRE_ALIAS = re.compile(
493
+ r"\b(?:const|let|var)?\s*([A-Za-z_]\w*)\s*=\s*require\s*\(\s*['\"](?:node:)?https?['\"]\s*\)\s*\.\s*(get|request)\b",
494
+ re.IGNORECASE,
495
+ )
496
+ _NODE_IMPORT_ALIASES = re.compile(
497
+ r"\bimport\s*\{([^}]*)\}\s*from\s*['\"](?:node:)?https?['\"]",
498
+ re.IGNORECASE,
499
+ )
500
+ _NODE_REQUIRE_DESTRUCTURED = re.compile(
501
+ r"\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*require\s*\(\s*['\"](?:node:)?https?['\"]\s*\)",
502
+ re.IGNORECASE,
503
+ )
504
+ _NODE_SIMPLE_ALIAS = re.compile(
505
+ r"\b(?:const|let|var)?\s*([A-Za-z_]\w*)\s*=\s*(?![=])([A-Za-z_]\w*)(?:\s*\.\s*([A-Za-z_]\w*))?",
506
+ re.IGNORECASE,
507
+ )
508
+ _NODE_MEMBER_ASSIGNMENT = re.compile(
509
+ r"\b([A-Za-z_]\w*)\s*=\s*(?:[A-Za-z_]\w*\s*\.\s*)?"
510
+ r"(?:fetch|get|request)\b|\b([A-Za-z_]\w*)\s*=\s*"
511
+ r"(?:require\s*\([^)]{0,128}\)|[A-Za-z_]\w*)\s*\.\s*(?:get|request)\b",
512
+ re.IGNORECASE,
513
+ )
514
+ _NODE_DIRECT_ALIAS = re.compile(r"\b(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:globalThis\s*\.\s*)?fetch\b", re.IGNORECASE)
515
+ _NODE_NAMED_IMPORT_ALIAS = re.compile(
516
+ r"\bimport\s*\{[^}]*\b(?:fetch|get|request)\s+as\s+([A-Za-z_]\w*)\b",
517
+ re.IGNORECASE,
518
+ )
519
+ _NODE_DESTRUCTURED_ALIAS = re.compile(
520
+ r"\b(?:const|let|var)\s*\{[^}]*\b(?:fetch|get|request)\s*:\s*([A-Za-z_]\w*)\b",
521
+ re.IGNORECASE,
522
+ )
523
+ _SHELL_EXECUTION_NAMES = frozenset({".", "eval", "source", "xargs"})
524
+ _SHELL_PREFIX_WRAPPER_NAMES = frozenset(
525
+ {"command", "exec", "env", "sudo", "time", "nice", "nohup"}
526
+ )
527
+ _SHELL_EXECUTION_WRAPPER_NAMES = frozenset(
528
+ {"command", "exec", "env", "sudo", "time", "nice", "nohup"}
529
+ ) | _SHELL_EXECUTION_NAMES
530
+ _SHELL_SCRIPT_EXECUTION_NAMES = frozenset({".", "eval", "source"})
531
+ _SHELL_WRAPPER_OPTION_VALUES = {
532
+ "exec": frozenset({"-a"}),
533
+ "env": frozenset({"-u", "--unset", "-C", "--chdir"}),
534
+ "nice": frozenset({"-n", "--adjustment"}),
535
+ "sudo": frozenset({"-u", "--user", "-g", "--group", "-h", "--host", "-C", "--chdir"}),
536
+ "xargs": frozenset({"-a", "--arg-file", "-d", "--delimiter", "-I", "--replace", "-P", "--max-procs"}),
537
+ }
538
+ _RUBY_COMMAND_NAMES = frozenset({"ruby"})
539
+ _PHP_COMMAND_NAMES = frozenset({"php"})
540
+
541
+
542
+ class _NormalisedBrowserDriverReport(dict[str, Any]):
543
+ pass
544
+
545
+
546
+ def _default_browser_driver_report() -> dict[str, Any]:
547
+ return _NormalisedBrowserDriverReport(
548
+ {
549
+ "status": "unavailable",
550
+ "command": None,
551
+ "version": None,
552
+ "blocker": "BLOCKED_BROWSER_DRIVER_UNAVAILABLE",
553
+ "detail": "the browser driver was not measured",
554
+ }
555
+ )
556
+
557
+
558
+ def _browser_session_id(value: Any) -> str | None:
559
+ if (
560
+ isinstance(value, str)
561
+ and value.strip()
562
+ and len(value) <= _MAX_BROWSER_SESSION_ID_LENGTH
563
+ and not any(ord(character) < 32 or ord(character) == 127 for character in value)
564
+ ):
565
+ return value
566
+ return None
567
+
568
+
569
+ def _bounded_report_value(value: Any) -> str | None:
570
+ if not isinstance(value, str) or not value:
571
+ return None
572
+ return value[:_MAX_BROWSER_REPORT_FIELD_LENGTH]
573
+
574
+
575
+ def _normalise_browser_driver_report(report: Any) -> dict[str, Any]:
576
+ if isinstance(report, _NormalisedBrowserDriverReport):
577
+ return report
578
+ if not isinstance(report, dict):
579
+ return _default_browser_driver_report()
580
+ status = report.get("status")
581
+ command = _bounded_report_value(report.get("command"))
582
+ version = _bounded_report_value(report.get("version"))
583
+ blocker = report.get("blocker")
584
+ detail = _bounded_report_value(report.get("detail")) or "the browser driver was not measured"
585
+ if status == "available":
586
+ command_name = command.replace("\\", "/").rsplit("/", 1)[-1].casefold() if command else None
587
+ if (
588
+ command_name not in {"agent-browser", "agent-browser.exe"}
589
+ or version is None
590
+ or _BROWSER_DRIVER_VERSION.fullmatch(version) is None
591
+ or blocker is not None
592
+ ):
593
+ status = "unverified-identity"
594
+ blocker = "BLOCKED_BROWSER_DRIVER_IDENTITY_UNVERIFIED"
595
+ else:
596
+ blocker = None
597
+ elif status not in {"unavailable", "unverified-identity", "unknown"}:
598
+ status = "unknown"
599
+ blocker = "BLOCKED_BROWSER_DRIVER_UNAVAILABLE"
600
+ elif blocker not in {
601
+ "BLOCKED_BROWSER_DRIVER_UNAVAILABLE",
602
+ "BLOCKED_BROWSER_DRIVER_IDENTITY_UNVERIFIED",
603
+ BROWSER_IDENTITY_BLOCKER,
604
+ None,
605
+ }:
606
+ blocker = (
607
+ "BLOCKED_BROWSER_DRIVER_IDENTITY_UNVERIFIED"
608
+ if status == "unverified-identity"
609
+ else "BLOCKED_BROWSER_DRIVER_UNAVAILABLE"
610
+ )
611
+ return _NormalisedBrowserDriverReport(
612
+ {
613
+ "status": status,
614
+ "command": command,
615
+ "version": version,
616
+ "blocker": blocker,
617
+ "detail": detail,
618
+ }
619
+ )
620
+
621
+
622
+ def _evict_browser_drive_state() -> None:
623
+ while len(_BROWSER_DRIVE_REPORTS) > _MAX_BROWSER_DRIVE_SESSIONS:
624
+ stale = next(iter(_BROWSER_DRIVE_REPORTS))
625
+ _BROWSER_DRIVE_GUARD.discard(stale)
626
+ _BROWSER_DRIVE_REPORTS.pop(stale, None)
627
+ _BROWSER_DRIVE_EVICTED_SESSIONS.add(stale)
628
+ while len(_BROWSER_DRIVE_GUARD) > _MAX_BROWSER_DRIVE_SESSIONS:
629
+ stale = next(iter(_BROWSER_DRIVE_GUARD))
630
+ _BROWSER_DRIVE_GUARD.discard(stale)
631
+ _BROWSER_DRIVE_REPORTS.pop(stale, None)
632
+ _BROWSER_DRIVE_EVICTED_SESSIONS.add(stale)
633
+
634
+
635
+ def arm_browser_drive_guard(session_id: Any, report: dict[str, Any] | None = None) -> None:
636
+ session = _browser_session_id(session_id)
637
+ if session is not None:
638
+ _BROWSER_DRIVE_EVICTED_SESSIONS.discard(session)
639
+ _BROWSER_DRIVE_GUARD.add(session)
640
+ normalised = (
641
+ report
642
+ if isinstance(report, _NormalisedBrowserDriverReport)
643
+ else _normalise_browser_driver_report(report)
644
+ )
645
+ _BROWSER_DRIVE_REPORTS[session] = dict(normalised)
646
+ _evict_browser_drive_state()
647
+
648
+
649
+ def release_browser_drive_guard(session_id: Any) -> None:
650
+ session = _browser_session_id(session_id)
651
+ if session is None:
652
+ return None
653
+ _BROWSER_DRIVE_GUARD.discard(session)
654
+ _BROWSER_DRIVE_REPORTS.pop(session, None)
655
+ _BROWSER_DRIVE_EVICTED_SESSIONS.discard(session)
656
+
657
+
658
+ def release_browser_drive_state(session_id: Any) -> None:
659
+ session = _browser_session_id(session_id)
660
+ if session is None:
661
+ return None
662
+ release_browser_drive_guard(session)
663
+ _PENDING_IGNITE.discard(session)
664
+ _PENDING_DISCIPLINE.pop(session, None)
665
+ _PENDING_BLOCK.pop(session, None)
666
+ _PENDING_POST_EDIT.pop(session, None)
667
+
668
+
669
+ def _evict_pending_browser_state() -> None:
670
+ while len(_PENDING_IGNITE) > _MAX_BROWSER_DRIVE_SESSIONS:
671
+ stale = next(iter(_PENDING_IGNITE))
672
+ _PENDING_IGNITE.discard(stale)
673
+ _PENDING_DISCIPLINE.pop(stale, None)
674
+ while len(_PENDING_BLOCK) > _MAX_BROWSER_DRIVE_SESSIONS:
675
+ _PENDING_BLOCK.pop(next(iter(_PENDING_BLOCK)), None)
676
+
677
+
678
+ def _has_http_url(value: str) -> bool:
679
+ for match in _HTTP_URL.finditer(value):
680
+ candidate = match.group(0).rstrip(".,!?]}")
681
+ try:
682
+ parsed = urlsplit(candidate)
683
+ except ValueError:
684
+ continue
685
+ if parsed.scheme.lower() in {"http", "https"} and parsed.netloc:
686
+ return True
687
+ return False
688
+
689
+
690
+ def _shell_tokens(command: str) -> list[str]:
691
+ try:
692
+ lexer = shlex.shlex(command, posix=True, punctuation_chars=";&|()<>")
693
+ lexer.whitespace_split = True
694
+ lexer.commenters = ""
695
+ return list(lexer)
696
+ except (TypeError, ValueError):
697
+ return []
698
+
699
+
700
+ def _split_unquoted_newlines(command: str) -> list[str]:
701
+ parts: list[str] = []
702
+ start = 0
703
+ quote: str | None = None
704
+ escaped = False
705
+ index = 0
706
+ while index < len(command):
707
+ character = command[index]
708
+ if escaped:
709
+ escaped = False
710
+ elif character == "\\" and quote != "'":
711
+ escaped = True
712
+ elif quote is not None:
713
+ if character == quote:
714
+ quote = None
715
+ elif character in {"'", '"'}:
716
+ quote = character
717
+ elif character in {"\r", "\n"}:
718
+ parts.append(command[start:index])
719
+ if character == "\r" and index + 1 < len(command) and command[index + 1] == "\n":
720
+ index += 1
721
+ start = index + 1
722
+ index += 1
723
+ parts.append(command[start:])
724
+ return parts
725
+
726
+
727
+ def _command_segments(tokens: list[str]) -> list[list[str]]:
728
+ segments: list[list[str]] = []
729
+ current: list[str] = []
730
+ for token in tokens:
731
+ if token in _SHELL_SEPARATOR_NAMES:
732
+ if current:
733
+ segments.append(current)
734
+ current = []
735
+ continue
736
+ current.append(token)
737
+ if current:
738
+ segments.append(current)
739
+ return segments
740
+
741
+
742
+ def _shell_substitutions(command: str) -> list[str]:
743
+ if len(command) > _MAX_GUARD_TEXT:
744
+ return []
745
+ substitutions: list[str] = []
746
+ index = 0
747
+ quote: str | None = None
748
+ while index < len(command):
749
+ character = command[index]
750
+ if quote == "'":
751
+ if character == "'":
752
+ quote = None
753
+ index += 1
754
+ continue
755
+ if character == "\\":
756
+ index += 2
757
+ continue
758
+ if quote == '"':
759
+ if character == '"':
760
+ quote = None
761
+ index += 1
762
+ continue
763
+ elif character in {'"', "'"}:
764
+ quote = character
765
+ index += 1
766
+ continue
767
+ if character == "`":
768
+ end = index + 1
769
+ escaped = False
770
+ while end < len(command):
771
+ nested = command[end]
772
+ if escaped:
773
+ escaped = False
774
+ elif nested == "\\":
775
+ escaped = True
776
+ elif nested == "`":
777
+ substitutions.append(command[index + 1 : end])
778
+ index = end + 1
779
+ break
780
+ end += 1
781
+ else:
782
+ return substitutions
783
+ continue
784
+ if character == "$" and index + 1 < len(command) and command[index + 1] == "(":
785
+ end = index + 2
786
+ depth = 1
787
+ nested_quote: str | None = None
788
+ escaped = False
789
+ while end < len(command):
790
+ nested = command[end]
791
+ if escaped:
792
+ escaped = False
793
+ elif nested == "\\":
794
+ escaped = True
795
+ elif nested_quote == "'":
796
+ if nested == "'":
797
+ nested_quote = None
798
+ elif nested_quote == '"':
799
+ if nested == '"':
800
+ nested_quote = None
801
+ elif nested in {'"', "'"}:
802
+ nested_quote = nested
803
+ elif nested == "(":
804
+ depth += 1
805
+ elif nested == ")":
806
+ depth -= 1
807
+ if depth == 0:
808
+ substitutions.append(command[index + 2 : end])
809
+ index = end + 1
810
+ break
811
+ end += 1
812
+ else:
813
+ return substitutions
814
+ continue
815
+ index += 1
816
+ return substitutions
817
+
818
+
819
+ def _shell_substitution_is_opaque(command: str) -> bool:
820
+ if len(command) > _MAX_GUARD_TEXT and re.search(r"(?:\$\(|`)", command):
821
+ return True
822
+ word: list[str] = []
823
+ has_substitution = False
824
+ quote: str | None = None
825
+ expect_command = True
826
+ redirection = False
827
+ control_mode: str | None = None
828
+ execution_wrapper: str | None = None
829
+ wrapper_option_value = False
830
+ wrapper_options_ended = False
831
+ find_command = False
832
+
833
+ def flush_word() -> bool:
834
+ nonlocal control_mode, execution_wrapper, expect_command, find_command
835
+ nonlocal has_substitution, redirection, wrapper_option_value, wrapper_options_ended
836
+ if not word:
837
+ return False
838
+ token = "".join(word)
839
+ word.clear()
840
+ token_has_substitution = has_substitution
841
+ has_substitution = False
842
+ lowered = token.casefold()
843
+ if execution_wrapper is not None:
844
+ if token_has_substitution:
845
+ return True
846
+ if wrapper_option_value:
847
+ wrapper_option_value = False
848
+ return False
849
+ if not wrapper_options_ended and lowered == "--":
850
+ wrapper_options_ended = True
851
+ return False
852
+ option_values = _SHELL_WRAPPER_OPTION_VALUES.get(execution_wrapper, frozenset())
853
+ if not wrapper_options_ended and lowered in option_values:
854
+ wrapper_option_value = True
855
+ return False
856
+ if not wrapper_options_ended and lowered.startswith("-"):
857
+ return False
858
+ if execution_wrapper == "env" and _SHELL_ASSIGNMENT.fullmatch(token) is not None:
859
+ return False
860
+ if lowered in _SHELL_EXECUTION_WRAPPER_NAMES:
861
+ execution_wrapper = lowered
862
+ wrapper_option_value = False
863
+ wrapper_options_ended = False
864
+ return False
865
+ if execution_wrapper in _SHELL_SCRIPT_EXECUTION_NAMES:
866
+ return False
867
+ find_command = lowered == "find"
868
+ execution_wrapper = None
869
+ wrapper_option_value = False
870
+ wrapper_options_ended = False
871
+ expect_command = False
872
+ return False
873
+ if redirection:
874
+ redirection = False
875
+ return False
876
+ if control_mode == "for_name":
877
+ control_mode = "for_in"
878
+ return False
879
+ if control_mode == "for_in":
880
+ control_mode = "for_list" if lowered == "in" else None
881
+ return False
882
+ if control_mode == "for_list":
883
+ if lowered == "do":
884
+ control_mode = None
885
+ expect_command = True
886
+ return False
887
+ if control_mode == "case_expr":
888
+ if lowered == "in":
889
+ control_mode = "case_pattern"
890
+ return False
891
+ if control_mode == "case_pattern":
892
+ return False
893
+ if control_mode == "case_body" and lowered == "esac":
894
+ control_mode = None
895
+ expect_command = False
896
+ return False
897
+ if token_has_substitution and expect_command and _SHELL_ASSIGNMENT.fullmatch(token) is None:
898
+ return True
899
+ if expect_command and lowered == "for":
900
+ control_mode = "for_name"
901
+ expect_command = False
902
+ return False
903
+ if expect_command and lowered == "case":
904
+ control_mode = "case_expr"
905
+ expect_command = False
906
+ return False
907
+ if find_command and lowered in {"-exec", "-execdir"}:
908
+ execution_wrapper = "find-exec"
909
+ wrapper_option_value = False
910
+ wrapper_options_ended = False
911
+ find_command = False
912
+ return False
913
+ if expect_command and lowered in _SHELL_EXECUTION_WRAPPER_NAMES:
914
+ execution_wrapper = lowered
915
+ wrapper_option_value = False
916
+ wrapper_options_ended = False
917
+ return False
918
+ if expect_command and lowered in _SHELL_CONTROL_WORD_NAMES:
919
+ return False
920
+ if expect_command and _SHELL_ASSIGNMENT.fullmatch(token) is not None:
921
+ return False
922
+ if expect_command and lowered == "find":
923
+ find_command = True
924
+ expect_command = False
925
+ return False
926
+
927
+ index = 0
928
+ while index < len(command):
929
+ character = command[index]
930
+ if quote == "'":
931
+ word.append(character)
932
+ if character == "'":
933
+ quote = None
934
+ index += 1
935
+ continue
936
+ if quote == '"':
937
+ if character == "\\" and index + 1 < len(command):
938
+ word.extend((character, command[index + 1]))
939
+ index += 2
940
+ continue
941
+ if character == '"':
942
+ word.append(character)
943
+ quote = None
944
+ index += 1
945
+ continue
946
+ if character not in {"$", "`"}:
947
+ word.append(character)
948
+ index += 1
949
+ continue
950
+ elif character in {'"', "'"}:
951
+ word.append(character)
952
+ quote = character
953
+ index += 1
954
+ continue
955
+ if character == "\\":
956
+ word.append(character)
957
+ if index + 1 < len(command):
958
+ word.append(command[index + 1])
959
+ index += 2
960
+ else:
961
+ index += 1
962
+ continue
963
+ if character == "$" and index + 1 < len(command) and command[index + 1] == "(":
964
+ word.append("x")
965
+ has_substitution = True
966
+ index += 2
967
+ depth = 1
968
+ nested_quote: str | None = None
969
+ nested_backtick = False
970
+ escaped = False
971
+ while index < len(command):
972
+ nested = command[index]
973
+ if escaped:
974
+ escaped = False
975
+ elif nested == "\\":
976
+ escaped = True
977
+ elif nested_backtick:
978
+ if nested == "`":
979
+ nested_backtick = False
980
+ elif nested_quote == "'":
981
+ if nested == "'":
982
+ nested_quote = None
983
+ elif nested_quote == '"':
984
+ if nested == '"':
985
+ nested_quote = None
986
+ elif nested in {'"', "'"}:
987
+ nested_quote = nested
988
+ elif nested == "`":
989
+ nested_backtick = True
990
+ elif nested == "(":
991
+ depth += 1
992
+ elif nested == ")":
993
+ depth -= 1
994
+ if depth == 0:
995
+ index += 1
996
+ break
997
+ index += 1
998
+ continue
999
+ if character == "`":
1000
+ word.append("x")
1001
+ has_substitution = True
1002
+ index += 1
1003
+ escaped = False
1004
+ while index < len(command):
1005
+ nested = command[index]
1006
+ if escaped:
1007
+ escaped = False
1008
+ elif nested == "\\":
1009
+ escaped = True
1010
+ elif nested == "`":
1011
+ index += 1
1012
+ break
1013
+ index += 1
1014
+ continue
1015
+ if character.isspace():
1016
+ if flush_word():
1017
+ return True
1018
+ if character in {"\r", "\n"}:
1019
+ expect_command = True
1020
+ execution_wrapper = None
1021
+ wrapper_option_value = False
1022
+ wrapper_options_ended = False
1023
+ find_command = False
1024
+ index += 1
1025
+ continue
1026
+ if character == "#" and not word:
1027
+ newline = command.find("\n", index)
1028
+ if newline == -1:
1029
+ break
1030
+ expect_command = True
1031
+ execution_wrapper = None
1032
+ wrapper_option_value = False
1033
+ wrapper_options_ended = False
1034
+ find_command = False
1035
+ index = newline + 1
1036
+ continue
1037
+ if character in ";&|":
1038
+ if flush_word():
1039
+ return True
1040
+ if control_mode != "case_pattern":
1041
+ expect_command = True
1042
+ execution_wrapper = None
1043
+ wrapper_option_value = False
1044
+ wrapper_options_ended = False
1045
+ find_command = False
1046
+ index += 1
1047
+ continue
1048
+ if character in "(){}":
1049
+ if flush_word():
1050
+ return True
1051
+ if character == "(" and control_mode != "case_pattern":
1052
+ expect_command = True
1053
+ elif character == ")" and control_mode == "case_pattern":
1054
+ control_mode = "case_body"
1055
+ expect_command = True
1056
+ elif character == ")":
1057
+ expect_command = False
1058
+ elif character == "{":
1059
+ expect_command = True
1060
+ else:
1061
+ expect_command = False
1062
+ index += 1
1063
+ continue
1064
+ if character in "<>":
1065
+ if flush_word():
1066
+ return True
1067
+ redirection = True
1068
+ index += 1
1069
+ if index < len(command) and command[index] == character:
1070
+ index += 1
1071
+ continue
1072
+ word.append(character)
1073
+ index += 1
1074
+ return flush_word()
1075
+
1076
+
1077
+ def _runtime_reads_code_from_stdin(tokens: list[str], aliases: dict[str, str]) -> bool:
1078
+ for index, token in enumerate(tokens):
1079
+ if token != "|":
1080
+ continue
1081
+ end = index + 1
1082
+ while end < len(tokens) and tokens[end] not in _SHELL_SEPARATOR_NAMES:
1083
+ end += 1
1084
+ segment = tokens[index + 1 : end]
1085
+ if not segment:
1086
+ continue
1087
+ name = _resolve_shell_alias(_command_name(segment), aliases)
1088
+ command_index = next(
1089
+ (
1090
+ candidate
1091
+ for candidate, value in enumerate(segment)
1092
+ if _resolve_shell_alias(
1093
+ value[1:] if value.startswith("$") else value.rsplit("/", 1)[-1], aliases
1094
+ )
1095
+ == name
1096
+ ),
1097
+ 0,
1098
+ )
1099
+ arguments = segment[command_index + 1 :]
1100
+ if name in _PYTHON_COMMAND_NAMES:
1101
+ if "-" in arguments or not any(not argument.startswith("-") for argument in arguments):
1102
+ return True
1103
+ if name in _NODE_COMMAND_NAMES:
1104
+ has_runtime_option = any(
1105
+ argument in {"-e", "--eval", "-p", "--print", "--check"}
1106
+ or argument.startswith(("-e", "--eval=", "-p", "--print="))
1107
+ for argument in arguments
1108
+ )
1109
+ has_script_argument = any(not argument.startswith("-") for argument in arguments)
1110
+ if not has_runtime_option and not has_script_argument:
1111
+ return True
1112
+ return False
1113
+
1114
+
1115
+ def _shell_prefix_command_name(tokens: list[str]) -> str:
1116
+ index = 0
1117
+ while index < len(tokens):
1118
+ token = tokens[index]
1119
+ lowered = token.casefold()
1120
+ if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*=.*", token):
1121
+ index += 1
1122
+ continue
1123
+ if lowered not in _SHELL_PREFIX_WRAPPER_NAMES:
1124
+ if token.startswith("$"):
1125
+ return token.casefold()
1126
+ return token.rsplit("/", 1)[-1].casefold()
1127
+ option_values = _SHELL_WRAPPER_OPTION_VALUES.get(lowered, frozenset())
1128
+ index += 1
1129
+ while index < len(tokens):
1130
+ option = tokens[index]
1131
+ if option == "--":
1132
+ index += 1
1133
+ break
1134
+ if lowered == "env" and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*=.*", option):
1135
+ index += 1
1136
+ continue
1137
+ if option in option_values:
1138
+ index += 2
1139
+ continue
1140
+ if option.startswith("-"):
1141
+ index += 1
1142
+ continue
1143
+ break
1144
+ return ""
1145
+
1146
+
1147
+ def _command_name(tokens: list[str]) -> str:
1148
+ return _shell_prefix_command_name(tokens)
1149
+
1150
+
1151
+ def _shell_aliases(command: str) -> dict[str, str]:
1152
+ aliases: dict[str, str] = {}
1153
+ for token in _shell_tokens(command):
1154
+ match = _SHELL_ASSIGNMENT.fullmatch(token)
1155
+ if match is None:
1156
+ continue
1157
+ alias, operator, target = match.groups()
1158
+ alias = alias.casefold()
1159
+ if operator == "+=":
1160
+ target = aliases.get(alias, "") + target
1161
+ aliases[alias] = (
1162
+ _OPAQUE_SHELL_VALUE
1163
+ if _shell_substitutions(target) or "`" in target
1164
+ else target.casefold()
1165
+ )
1166
+ for match in re.finditer(
1167
+ r'(?<![A-Za-z0-9_])([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?=[^;&|\n]*(?:\$\(|`))', command
1168
+ ):
1169
+ substitutions = _shell_substitutions(command[match.start() :])
1170
+ if substitutions or re.search(r"(?:\$\(|`)", command[match.start() :]):
1171
+ aliases[match.group(1).casefold()] = _OPAQUE_SHELL_VALUE
1172
+ for match in re.finditer(
1173
+ r"(?m)(?:^|[;&|{}])\s*for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+([^\n]*?)\s*(?:;|\n)\s*do\b",
1174
+ command,
1175
+ ):
1176
+ words = match.group(2)
1177
+ source_is_opaque = bool(_shell_substitutions(words) or re.search(r"(?:\$\(|`)", words))
1178
+ resolved_sources: list[str] = []
1179
+ if not source_is_opaque:
1180
+ for parameter in _SHELL_PARAMETER.finditer(words):
1181
+ resolved = _resolve_shell_alias(parameter.group(0), aliases)
1182
+ if resolved == _OPAQUE_SHELL_VALUE:
1183
+ source_is_opaque = True
1184
+ break
1185
+ if resolved:
1186
+ resolved_sources.append(resolved)
1187
+ if source_is_opaque:
1188
+ aliases[match.group(1).casefold()] = _OPAQUE_SHELL_VALUE
1189
+ elif resolved_sources and len(set(resolved_sources)) == 1:
1190
+ aliases[match.group(1).casefold()] = resolved_sources[0]
1191
+ return aliases
1192
+
1193
+
1194
+ def _resolve_shell_alias(name: str, aliases: dict[str, str]) -> str:
1195
+ resolved = name.casefold()
1196
+ seen: set[str] = set()
1197
+ for _ in range(_MAX_COMMAND_DEPTH):
1198
+ if _OPAQUE_SHELL_VALUE in resolved:
1199
+ return _OPAQUE_SHELL_VALUE
1200
+ single_parameter = re.fullmatch(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}", resolved)
1201
+ if single_parameter:
1202
+ resolved = single_parameter.group(1)
1203
+ expanded = _SHELL_PARAMETER.sub(
1204
+ lambda match: aliases.get(
1205
+ (match.group(1) or match.group(2)).casefold(), match.group(0)
1206
+ ),
1207
+ resolved,
1208
+ )
1209
+ if expanded != resolved:
1210
+ resolved = expanded
1211
+ continue
1212
+ if resolved not in aliases or resolved in seen:
1213
+ break
1214
+ seen.add(resolved)
1215
+ resolved = aliases[resolved]
1216
+ if _OPAQUE_SHELL_VALUE in resolved:
1217
+ return _OPAQUE_SHELL_VALUE
1218
+ return resolved.strip().split(None, 1)[0].rsplit("/", 1)[-1] if resolved.strip() else ""
1219
+
1220
+
1221
+ def _inline_shell_commands(tokens: list[str]) -> list[str]:
1222
+ if _command_name(tokens) not in _SHELL_COMMAND_NAMES:
1223
+ return []
1224
+ commands: list[str] = []
1225
+ for index, token in enumerate(tokens[:-1]):
1226
+ if token in {"-c", "--command"} or (token.startswith("-") and "c" in token[1:]):
1227
+ commands.append(tokens[index + 1])
1228
+ return commands
1229
+
1230
+
1231
+ def _find_exec_shell_commands(tokens: list[str]) -> list[str]:
1232
+ commands: list[str] = []
1233
+ index = 0
1234
+ while index < len(tokens):
1235
+ if tokens[index] not in {"-exec", "-execdir"}:
1236
+ index += 1
1237
+ continue
1238
+ end = index + 1
1239
+ while end < len(tokens) and tokens[end] not in {"+", ";"}:
1240
+ end += 1
1241
+ commands.extend(_inline_shell_commands(tokens[index + 1 : end]))
1242
+ index = end + 1 if end < len(tokens) else end
1243
+ return commands
1244
+
1245
+
1246
+ def _base64_followed_by_execution(command: str) -> bool:
1247
+ bounded = command[:_MAX_GUARD_TEXT]
1248
+ if _BASE64_DECODE.search(bounded) and _BASE64_EXECUTION.search(bounded):
1249
+ return True
1250
+ if len(command) > _MAX_GUARD_TEXT:
1251
+ return bool(_BASE64_DECODE.search(command) or _BASE64_EXECUTION.search(command))
1252
+ return False
1253
+
1254
+
1255
+ def _alias_is_called(command: str, alias: str) -> bool:
1256
+ return re.search(rf"\b{re.escape(alias)}\s*\(", command) is not None
1257
+
1258
+
1259
+ def _python_aliases(command: str) -> dict[str, str]:
1260
+ aliases: dict[str, str] = {}
1261
+ for match in re.finditer(
1262
+ r"\bimport\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?)(?:\s+as\s+([A-Za-z_]\w*))?",
1263
+ command,
1264
+ re.IGNORECASE,
1265
+ ):
1266
+ module, alias = match.groups()
1267
+ if module.casefold() in _PYTHON_NETWORK_MODULES:
1268
+ aliases[(alias or module.rsplit(".", 1)[-1]).casefold()] = module
1269
+ for match in re.finditer(
1270
+ r"\bfrom\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?)\s+import\s+([^;\n]+)",
1271
+ command,
1272
+ re.IGNORECASE,
1273
+ ):
1274
+ module, imported_names = match.groups()
1275
+ if module.casefold() not in _PYTHON_NETWORK_MODULES:
1276
+ continue
1277
+ for imported in imported_names.split(","):
1278
+ parts = imported.strip().split()
1279
+ if not parts or parts[0] == "*":
1280
+ continue
1281
+ name = parts[0]
1282
+ alias = parts[2] if len(parts) >= 3 and parts[1].casefold() == "as" else name
1283
+ aliases[alias.casefold()] = f"{module}.{name}"
1284
+ for match in re.finditer(
1285
+ r"\b([A-Za-z_]\w*)\s*=(?!=)\s*([A-Za-z_]\w*)(?:\s*\.\s*([A-Za-z_]\w*))?",
1286
+ command,
1287
+ re.IGNORECASE,
1288
+ ):
1289
+ alias, base, member = match.groups()
1290
+ aliases[alias.casefold()] = f"{base}.{member}" if member else base
1291
+ return aliases
1292
+
1293
+
1294
+ def _python_target_is_network(target: str, aliases: dict[str, str], seen: set[str] | None = None) -> bool:
1295
+ current = target.strip()
1296
+ lowered = current.casefold()
1297
+ if lowered in _PYTHON_NETWORK_MODULES:
1298
+ return True
1299
+ seen = set() if seen is None else seen
1300
+ if lowered in seen:
1301
+ return False
1302
+ seen.add(lowered)
1303
+ if "." in current:
1304
+ base, member = current.rsplit(".", 1)
1305
+ if member.casefold() in {name.casefold() for name in _PYTHON_NETWORK_MEMBERS}:
1306
+ if _python_target_is_network(base, aliases, seen):
1307
+ return True
1308
+ alias_target = aliases.get(lowered)
1309
+ return bool(alias_target and _python_target_is_network(alias_target, aliases, seen))
1310
+
1311
+
1312
+ def _python_request_shaped(command: str) -> bool:
1313
+ if _PYTHON_REQUEST.search(command):
1314
+ return True
1315
+ compact = re.sub(r"\s+", " ", command)
1316
+ if _PYTHON_NETWORK_IMPORT.search(compact) is None:
1317
+ return False
1318
+ for match in _PYTHON_MEMBER_ASSIGNMENT.finditer(compact):
1319
+ if _alias_is_called(compact, match.group(1)):
1320
+ return True
1321
+ for match in _PYTHON_ALIAS_IMPORT.finditer(compact):
1322
+ if _alias_is_called(compact, match.group(1)):
1323
+ return True
1324
+ for match in _PYTHON_DYNAMIC_ALIAS.finditer(compact):
1325
+ if _alias_is_called(compact, match.group(1)):
1326
+ return True
1327
+ aliases = _python_aliases(compact)
1328
+ for match in re.finditer(r"\b([A-Za-z_]\w*)\s*\(", compact):
1329
+ if _python_target_is_network(match.group(1), aliases):
1330
+ return True
1331
+ return bool(
1332
+ _PYTHON_MEMBER_CALL.search(compact)
1333
+ or _PYTHON_FROM_REQUEST.search(compact)
1334
+ or _PYTHON_REQUEST.search(compact)
1335
+ )
1336
+
1337
+
1338
+ def _node_request_shaped(command: str) -> bool:
1339
+ if _NODE_REQUEST.search(command):
1340
+ return True
1341
+ compact = re.sub(r"\s+", " ", command)
1342
+ if _NODE_NETWORK_MARKER.search(compact) is None:
1343
+ return False
1344
+ aliases: dict[str, str] = {}
1345
+ for match in _NODE_SIMPLE_ALIAS.finditer(compact):
1346
+ alias, base, member = match.groups()
1347
+ aliases[alias.casefold()] = f"{base}.{member}" if member else base
1348
+ for match in _NODE_GLOBAL_FETCH_ALIAS.finditer(compact):
1349
+ aliases[match.group(1).casefold()] = "fetch"
1350
+ for match in _NODE_REQUIRE_ALIAS.finditer(compact):
1351
+ aliases[match.group(1).casefold()] = f"https.{match.group(2)}"
1352
+ for match in _NODE_IMPORT_ALIASES.finditer(compact):
1353
+ for imported in match.group(1).split(","):
1354
+ parts = imported.strip().split()
1355
+ if not parts:
1356
+ continue
1357
+ name = parts[0]
1358
+ alias = parts[2] if len(parts) >= 3 and parts[1].casefold() == "as" else name
1359
+ aliases[alias.casefold()] = f"https.{name}"
1360
+ for match in _NODE_REQUIRE_DESTRUCTURED.finditer(compact):
1361
+ for imported in match.group(1).split(","):
1362
+ parts = imported.strip().split(":", 1)
1363
+ name = parts[0].strip()
1364
+ alias = parts[1].strip() if len(parts) == 2 else name
1365
+ if name in {"get", "request"}:
1366
+ aliases[alias.casefold()] = f"https.{name}"
1367
+ for match in _NODE_MEMBER_ASSIGNMENT.finditer(compact):
1368
+ member_aliases = [alias for alias in match.groups() if alias]
1369
+ if any(_alias_is_called(compact, alias) for alias in member_aliases):
1370
+ return True
1371
+ for match in _NODE_DIRECT_ALIAS.finditer(compact):
1372
+ if _alias_is_called(compact, match.group(1)):
1373
+ return True
1374
+ for pattern in (_NODE_NAMED_IMPORT_ALIAS, _NODE_DESTRUCTURED_ALIAS):
1375
+ for match in pattern.finditer(compact):
1376
+ if _alias_is_called(compact, match.group(1)):
1377
+ return True
1378
+ def target_is_network(target: str, seen: set[str] | None = None) -> bool:
1379
+ current = target.casefold()
1380
+ if current == "fetch" or re.fullmatch(r"(?:node:)?https?\.(?:get|request)", current):
1381
+ return True
1382
+ seen = set() if seen is None else seen
1383
+ if current in seen:
1384
+ return False
1385
+ seen.add(current)
1386
+ alias_target = aliases.get(current)
1387
+ return bool(alias_target and target_is_network(alias_target, seen))
1388
+
1389
+ for match in re.finditer(r"\b([A-Za-z_]\w*)\s*\(", compact):
1390
+ if target_is_network(match.group(1)):
1391
+ return True
1392
+ return bool(re.search(r"\b(?:fetch|get|request)\s*\(", compact))
1393
+
1394
+
1395
+ def _runtime_code_is_opaque(tokens: list[str]) -> bool:
1396
+ for index, token in enumerate(tokens[:-1]):
1397
+ if token in {"-c", "-e", "--eval", "--command"} and tokens[index + 1].startswith(("$", "<")):
1398
+ return True
1399
+ return False
1400
+
1401
+
1402
+ def _git_network_command(tokens: list[str]) -> bool:
1403
+ if not tokens or _command_name(tokens) != "git":
1404
+ return False
1405
+ return any(token.casefold() in _GIT_NETWORK_SUBCOMMANDS for token in tokens[1:])
1406
+
1407
+
1408
+ def _url_is_retrieval_shaped(tokens: list[str], command: str, name: str) -> bool:
1409
+ if not _has_http_url(command):
1410
+ return False
1411
+ if name in _URL_INERT_COMMAND_NAMES and name != "git":
1412
+ return False
1413
+ if name == "git":
1414
+ return _git_network_command(tokens)
1415
+ return True
1416
+
1417
+
1418
+ def _command_is_fetch_shaped(
1419
+ command: str, _depth: int = 0, _aliases: dict[str, str] | None = None
1420
+ ) -> bool:
1421
+ if not isinstance(command, str):
1422
+ return False
1423
+ if _SHELL_NETWORK_ACCESS.search(command):
1424
+ return True
1425
+ if _shell_substitution_is_opaque(command):
1426
+ return True
1427
+ aliases = _aliases if _aliases is not None else _shell_aliases(command)
1428
+ if any(
1429
+ _resolve_shell_alias(match.group(1), aliases) in (_FETCH_COMMAND_NAMES | {_OPAQUE_SHELL_VALUE})
1430
+ for match in _SHELL_PARAMETER_COMMAND_START.finditer(command)
1431
+ ):
1432
+ return True
1433
+ if any(_command_is_fetch_shaped(value, _depth + 1, aliases) for value in _shell_substitutions(command)):
1434
+ return True
1435
+ lines = _split_unquoted_newlines(command)
1436
+ if len(lines) > 1:
1437
+ return any(_command_is_fetch_shaped(line, _depth, aliases) for line in lines)
1438
+ if command.lstrip().startswith("#"):
1439
+ return False
1440
+ if _depth >= _MAX_COMMAND_DEPTH:
1441
+ tokens = _shell_tokens(command)
1442
+ names = {_command_name(segment) for segment in _command_segments(tokens)}
1443
+ return bool(
1444
+ names & (_SHELL_COMMAND_NAMES | _FETCH_COMMAND_NAMES | _NODE_COMMAND_NAMES | _PYTHON_COMMAND_NAMES)
1445
+ or _has_http_url(command)
1446
+ or _base64_followed_by_execution(command)
1447
+ )
1448
+ if _base64_followed_by_execution(command):
1449
+ return True
1450
+ tokens = _shell_tokens(command)
1451
+ if not tokens:
1452
+ return _has_http_url(command)
1453
+ if _runtime_reads_code_from_stdin(tokens, aliases):
1454
+ return True
1455
+ for segment in _command_segments(tokens):
1456
+ name = _command_name(segment)
1457
+ effective_name = _resolve_shell_alias(name, aliases)
1458
+ if effective_name == _OPAQUE_SHELL_VALUE:
1459
+ return True
1460
+ if effective_name == "find":
1461
+ nested = _find_exec_shell_commands(segment)
1462
+ if nested and any(_command_is_fetch_shaped(value, _depth + 1, aliases) for value in nested):
1463
+ return True
1464
+ if effective_name in _SHELL_COMMAND_NAMES:
1465
+ nested = _inline_shell_commands(segment)
1466
+ if nested and any(_command_is_fetch_shaped(value, _depth + 1, aliases) for value in nested):
1467
+ return True
1468
+ if nested and any(value.lstrip().startswith(("$", "<")) for value in nested):
1469
+ return True
1470
+ if nested:
1471
+ continue
1472
+ if effective_name in _FETCH_COMMAND_NAMES:
1473
+ return True
1474
+ if _git_network_command(segment):
1475
+ return True
1476
+ body = " ".join(segment)
1477
+ if effective_name in _SHELL_EXECUTION_NAMES:
1478
+ if any(token.startswith(("$", "<")) for token in segment[1:]):
1479
+ return True
1480
+ if re.search(
1481
+ r"\b(?:curl|fetch|http|http\.client|httpie|lynx|open_url|wget|w3m)\b",
1482
+ body,
1483
+ re.IGNORECASE,
1484
+ ):
1485
+ return True
1486
+ if effective_name in _NODE_COMMAND_NAMES and _node_request_shaped(body):
1487
+ return True
1488
+ if effective_name in _PYTHON_COMMAND_NAMES and _python_request_shaped(body):
1489
+ return True
1490
+ if effective_name in _RUBY_COMMAND_NAMES and _RUBY_NETWORK_REQUEST.search(body):
1491
+ return True
1492
+ if effective_name in _PHP_COMMAND_NAMES and _PHP_NETWORK_REQUEST.search(body):
1493
+ return True
1494
+ if effective_name in (_NODE_COMMAND_NAMES | _PYTHON_COMMAND_NAMES) and _runtime_code_is_opaque(segment):
1495
+ return True
1496
+ if any(token.startswith("$") for token in segment) and (
1497
+ _node_request_shaped(body) or _python_request_shaped(body)
1498
+ ):
1499
+ return True
1500
+ if _base64_followed_by_execution(body):
1501
+ return True
1502
+ if _url_is_retrieval_shaped(segment, body, effective_name):
1503
+ return True
1504
+ return False
1505
+
1506
+
1507
+ def _url_argument_present(args: Any) -> bool:
1508
+ seen: set[int] = set()
1509
+ nodes = 0
1510
+
1511
+ def visit(value: Any, depth: int) -> bool:
1512
+ nonlocal nodes
1513
+ if isinstance(value, str):
1514
+ return _has_http_url(value)
1515
+ if not isinstance(value, (dict, list, tuple)):
1516
+ return False
1517
+ marker = id(value)
1518
+ if marker in seen:
1519
+ return False
1520
+ if depth > _MAX_URL_ARGUMENT_DEPTH or nodes >= _MAX_URL_ARGUMENT_NODES:
1521
+ return True
1522
+ seen.add(marker)
1523
+ nodes += 1
1524
+ children = value.items() if isinstance(value, dict) else value
1525
+ for child in children:
1526
+ if isinstance(value, dict):
1527
+ key, item = child
1528
+ if visit(key, depth + 1) or visit(item, depth + 1):
1529
+ return True
1530
+ elif visit(child, depth + 1):
1531
+ return True
1532
+ return False
1533
+
1534
+ return visit(args, 0)
1535
+
1536
+
1537
+ def _is_fetch_shaped(tool_name: str, args: Any) -> bool:
1538
+ raw_tool_name = tool_name.strip() if isinstance(tool_name, str) else str(tool_name or "")
1539
+ lowered = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", raw_tool_name).casefold()
1540
+ if lowered in _FETCH_TOOL_NAMES or lowered.startswith(("browser_", "web_")) or _HOST_BROWSER_TOOL.search(lowered):
1541
+ return True
1542
+ if lowered in _FETCH_TOOL_ALIASES and _url_argument_present(args):
1543
+ return True
1544
+ if not isinstance(args, dict):
1545
+ return False
1546
+ for key in _COMMAND_FIELDS:
1547
+ value = args.get(key)
1548
+ if isinstance(value, str) and _command_is_fetch_shaped(value):
1549
+ return True
1550
+ return False
1551
+
1552
+
1553
+ def browser_drive_tool_guard(
1554
+ tool_name: str = "", args: Any = None, session_id: Any = "", **_: Any
1555
+ ) -> dict[str, str] | None:
1556
+ session = _browser_session_id(session_id)
1557
+ tool = str(tool_name or "")
1558
+ fetch_shaped = _is_fetch_shaped(tool, args)
1559
+ if session is None:
1560
+ return None
1561
+ if session not in _BROWSER_DRIVE_GUARD:
1562
+ if not fetch_shaped:
1563
+ return None
1564
+ if session in _BROWSER_DRIVE_EVICTED_SESSIONS:
1565
+ return {
1566
+ "action": "block",
1567
+ "message": (
1568
+ f"{_BLOCKER_STATE}: the browser-drive route state exceeded its bounded session limit. "
1569
+ "Release the tracked session before retrieving a page."
1570
+ ),
1571
+ }
1572
+ return None
1573
+ if not fetch_shaped:
1574
+ return None
1575
+ report = _BROWSER_DRIVE_REPORTS.get(session, _default_browser_driver_report())
1576
+ if report.get("status") == "available":
1577
+ return None
1578
+ blocker = report.get("blocker")
1579
+ if blocker not in {
1580
+ "BLOCKED_BROWSER_DRIVER_IDENTITY_UNVERIFIED",
1581
+ "BLOCKED_BROWSER_DRIVER_UNAVAILABLE",
1582
+ BROWSER_IDENTITY_BLOCKER,
1583
+ }:
1584
+ blocker = "BLOCKED_BROWSER_DRIVER_UNAVAILABLE"
1585
+ # The harness's canonical denial payload is {"action": "block", "message": ...}.
1586
+ # An invented shape is silently ignored, which is how the first attempt at this
1587
+ # guard passed its own tests and changed nothing on the host.
1588
+ return {
1589
+ "action": "block",
1590
+ "message": (
1591
+ f"{blocker}: this turn routed to lithermes:browser-drive and the "
1592
+ "capability probe measured no verified driver. Retrieving the page by another means is the "
1593
+ "substitution this contract exists to prevent. Report the blocker and mark what could not "
1594
+ "be established as [UNVERIFIED]."
1595
+ ),
1596
+ }
1597
+
1598
+
1599
+ def pre_llm_call(**kwargs: Any) -> dict[str, str] | None:
1600
+ # Hermes identifies delegate_task children with platform="subagent".
1601
+ # Child goals may quote words such as "lit" or "lit-crucible" as inert task
1602
+ # context; re-running top-level activation here would contaminate the lane.
1603
+ if is_delegate_child_platform(str(kwargs.get("platform") or "")):
1604
+ release_browser_drive_state(kwargs.get("session_id"))
1605
+ return None
1606
+ # A first interactive top-level turn is the only plugin lifecycle point
1607
+ # where the Python payload can safely foreground an exact npm update. The
1608
+ # bridge owns its own recursion guard, timeout, transaction, rollback, and
1609
+ # receipt; it never contributes text to the model-facing context.
1610
+ try:
1611
+ _auto_update.pre_llm_call(**kwargs)
1612
+ except _auto_update.UnknownStateError:
1613
+ # A failed restore is not an advisory miss: continuing would let the
1614
+ # model operate against a profile whose installed payload is unknown.
1615
+ raise
1616
+ except Exception:
1617
+ # Ordinary registry/installer failures remain fail-open for this turn;
1618
+ # the transaction receipt records the redacted reason.
1619
+ pass
1620
+ user_message = str(kwargs.get("user_message") or "")
1621
+ requested_reader_mode = reader_facing_mode_from_user_request(user_message)
1622
+ reader_contract = reader_facing_contract_block(
1623
+ requested_reader_mode,
1624
+ authority="current_user_request" if requested_reader_mode is not None else None,
1625
+ )
1626
+
1627
+ def with_reader_contract(context: str, *, compact: bool = False) -> dict[str, str]:
1628
+ contract = (
1629
+ reader_facing_contract_block(
1630
+ requested_reader_mode,
1631
+ authority=(
1632
+ "current_user_request" if requested_reader_mode is not None else None
1633
+ ),
1634
+ compact=True,
1635
+ )
1636
+ if compact
1637
+ else reader_contract
1638
+ )
1639
+ return {"context": f"{context.rstrip()}\n\n{contract}"}
1640
+
1641
+ session_id = _browser_session_id(kwargs.get("session_id")) or ""
1642
+ if session_id:
1643
+ # The guard belongs to this turn's route. Clear a previous browser route
1644
+ # before a new message can select another mode.
1645
+ release_browser_drive_guard(session_id)
1646
+ # Clear any stale ignite flag from an interrupted previous turn so it can't
1647
+ # leak the banner onto this turn's response. Re-added below only if THIS turn
1648
+ # is a keyword-lit turn — keeping the flag scoped to the current turn.
1649
+ _PENDING_IGNITE.discard(session_id)
1650
+ _PENDING_DISCIPLINE.pop(session_id, None)
1651
+ _PENDING_BLOCK.pop(session_id, None)
1652
+ # /litgoal & /lit-plan carry a legacy bind-goal marker. It remains a bounded
1653
+ # route envelope only; native /goal is user-managed and never mutated here.
1654
+ bind_obj = _extract_bind_goal(user_message)
1655
+ if bind_obj:
1656
+ return None
1657
+ # /lit and /lit-loop inject a run-context message. Durable run state already
1658
+ # exists, so skip re-injecting context without touching native /goal state.
1659
+ if "<lithermes-run-context>" in user_message:
1660
+ return None
1661
+ # Read-only recap route runs FIRST so `lit recap` never falls through to
1662
+ # the run-writing litwork path. It never binds a goal or writes run state.
1663
+ recap_args = detect_recap(user_message)
1664
+ if recap_args is not None:
1665
+ record_event(
1666
+ "litwork_trigger",
1667
+ session_id=session_id,
1668
+ platform=str(kwargs.get("platform") or ""),
1669
+ mode="lit-recap",
1670
+ )
1671
+ if session_id:
1672
+ _PENDING_IGNITE.add(session_id)
1673
+ _PENDING_DISCIPLINE[session_id] = "lit-recap"
1674
+ _evict_pending_browser_state()
1675
+ return {"context": command_lit_recap(recap_args)["agent_message"]}
1676
+ route = detect_lit_mode(user_message)
1677
+ if route is None:
1678
+ return None
1679
+ _deliverable_hedges.activate_skill(session_id, route.mode)
1680
+
1681
+ record_event(
1682
+ "litwork_trigger",
1683
+ session_id=session_id,
1684
+ platform=str(kwargs.get("platform") or ""),
1685
+ mode=route.mode,
1686
+ )
1687
+ if session_id:
1688
+ _PENDING_IGNITE.add(session_id)
1689
+ _PENDING_DISCIPLINE[session_id] = discipline(route.mode)
1690
+ _evict_pending_browser_state()
1691
+
1692
+ if route.blocked:
1693
+ if session_id:
1694
+ _PENDING_BLOCK[session_id] = route.block_message
1695
+ _evict_pending_browser_state()
1696
+ return with_reader_contract(build_natural_mode_context(route), compact=True)
1697
+
1698
+ browser_report = None
1699
+ if route.mode == "browser-drive":
1700
+ # Measure once. The same structured report goes to the context and the guard.
1701
+ browser_report = _normalise_browser_driver_report(_browser_drive_probe_report())
1702
+ if session_id:
1703
+ arm_browser_drive_guard(session_id, browser_report)
1704
+ if route.mode == "lit-plan" and route.objective:
1705
+ planned = command_lit_plan(shlex.quote(route.objective))
1706
+ return with_reader_contract(
1707
+ f"{build_natural_mode_context(route)}\n\n"
1708
+ f"Durable plan: {planned['plan']}",
1709
+ compact=True,
1710
+ )
1711
+ if route.mode != "litwork":
1712
+ if route.mode in _SKILL_ROUTE_CONTRACTS:
1713
+ compact_contract = reader_facing_contract_block(
1714
+ requested_reader_mode,
1715
+ authority=(
1716
+ "current_user_request" if requested_reader_mode is not None else None
1717
+ ),
1718
+ compact=True,
1719
+ )
1720
+ return {
1721
+ "context": build_natural_mode_context(
1722
+ route,
1723
+ browser_report=browser_report,
1724
+ reader_contract=compact_contract,
1725
+ )
1726
+ }
1727
+ return with_reader_contract(
1728
+ build_natural_mode_context(route, browser_report=browser_report),
1729
+ compact=True,
1730
+ )
1731
+
1732
+ direct = DIRECT_LIT_PATTERN.match(route.visible_message)
1733
+ run_context = ""
1734
+ if direct:
1735
+ task = route.objective or _clamp_task(direct.group("task"))
1736
+ if task:
1737
+ workspace = Path.cwd().resolve()
1738
+ run_dir = write_run_state(
1739
+ workspace,
1740
+ task=task,
1741
+ command="lit",
1742
+ )
1743
+ run_context = "\n\n" + build_run_agent_message(
1744
+ load_run_state(run_dir), include_reader_contract=False
1745
+ )
1746
+ if requested_reader_mode is None:
1747
+ if run_context:
1748
+ return {
1749
+ "context": (
1750
+ f"{LIT_CONTEXT}{run_context}\n\n"
1751
+ "#contract.parent_projection_final: reader disclosure still governs this parent reply; detailed run/evidence/DoneClaim clauses above remain internal unless requested or materially necessary."
1752
+ )
1753
+ }
1754
+ return {"context": LIT_CONTEXT}
1755
+ return with_reader_contract(LIT_CONTEXT_BASE + run_context)
1756
+
1757
+
1758
+ def transform_llm_output(**kwargs: Any) -> str | None:
1759
+ """Draw the route mark once, without forging or normalizing a model probe.
1760
+
1761
+ Natural routes have no native display return channel before inference.
1762
+ Hermes applies this transform when the reply arrives. A blocked route still
1763
+ replaces unsafe output with its measured denial, independently of the probe.
1764
+ """
1765
+ session_id = str(kwargs.get("session_id") or "")
1766
+ block = _PENDING_BLOCK.pop(session_id, "")
1767
+ route = _PENDING_DISCIPLINE.pop(session_id, "litwork")
1768
+ pending = session_id in _PENDING_IGNITE
1769
+ _PENDING_IGNITE.discard(session_id)
1770
+ if not pending and not block:
1771
+ return None
1772
+ response = block or str(kwargs.get("response_text") or "")
1773
+ return acknowledge_reply(route, response)
1774
+
1775
+
1776
+ def subagent_stop(**kwargs: Any) -> None:
1777
+ """Record each delegate_task child (e.g. a review lane) to the LitHermes ledger.
1778
+
1779
+ Hermes fires this once per child after delegate_task finishes. Observer-only:
1780
+ the return value is ignored.
1781
+ """
1782
+ record_event(
1783
+ "subagent_stop",
1784
+ parent_session_id=str(kwargs.get("parent_session_id") or ""),
1785
+ child_role=str(kwargs.get("child_role") or ""),
1786
+ child_status=str(kwargs.get("child_status") or ""),
1787
+ duration_ms=kwargs.get("duration_ms"),
1788
+ )
1789
+ return None
1790
+
1791
+
1792
+ try:
1793
+ from . import diagnostics as _diagnostics
1794
+ except (ImportError, ModuleNotFoundError):
1795
+ import diagnostics as _diagnostics
1796
+
1797
+ rules = _rules
1798
+
1799
+ PLUGIN_ROOT = _diagnostics.PLUGIN_ROOT
1800
+ HOOKS = _diagnostics.HOOKS
1801
+ SLASH_COMMANDS = _diagnostics.SLASH_COMMANDS
1802
+ GOAL_TOOLS = _diagnostics.GOAL_TOOLS
1803
+ plugin_version = _diagnostics.plugin_version
1804
+ version_line = _diagnostics.version_line
1805
+ hermes_host_version = _diagnostics.hermes_host_version
1806
+
1807
+
1808
+ def status_report() -> str:
1809
+ return _diagnostics.status_report(hermes_host_version())
1810
+
1811
+
1812
+ def doctor_report() -> tuple[list[str], int]:
1813
+ return _diagnostics.doctor_report(hermes_host_version())